From 44dc6eb30d9a47cc2f7c0c04f012b0dbdae1acae Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Oct 2024 11:08:24 +1100 Subject: [PATCH 0001/2098] all: Bump version to 1.25.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index caea0f4f7a3..b31da2a37c0 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -30,9 +30,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 24 +#define MICROPY_VERSION_MINOR 25 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 4601dcb8a1e88e1cb2d644cc8b0c36378d5966b7 Mon Sep 17 00:00:00 2001 From: Steve Holden Date: Thu, 24 Oct 2024 01:35:02 +0100 Subject: [PATCH 0002/2098] rp2/README: Remove redundant `global` statement from example code. Since `led` is not being rebound inside the `tick` function the standard Python name resolution method will find it. Signed-off-by: Steve Holden --- ports/rp2/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/rp2/README.md b/ports/rp2/README.md index c2f3771cd31..911d797fe01 100644 --- a/ports/rp2/README.md +++ b/ports/rp2/README.md @@ -69,7 +69,6 @@ from machine import Pin, Timer led = Pin(25, Pin.OUT) tim = Timer() def tick(timer): - global led led.toggle() tim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick) From ff70a91581448983d9bdf1251f659ed7bc8189fb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 22 Oct 2024 15:02:32 +1100 Subject: [PATCH 0003/2098] esp32: Move the linker wrap options out of the project CMakeLists. For in-tree builds, these are effectively equivalent. However for out-of-tree builds it's preferable to have as little as possible in the top-level CMakeLists.txt file (as the out-of-tree build needs its own copy). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/CMakeLists.txt | 7 ------- ports/esp32/esp32_common.cmake | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index 83fca88b697..9ca9065afd0 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -71,12 +71,5 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) # Set the location of the main component for the project (one per target). list(APPEND EXTRA_COMPONENT_DIRS main_${IDF_TARGET}) -# Enable the panic handler wrapper -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) - -# Patch LWIP memory pool allocators (see lwip_patch.c) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_malloc" APPEND) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_free" APPEND) - # Define the project. project(micropython) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 565f6feec23..48f067debb7 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -236,6 +236,13 @@ target_include_directories(${MICROPY_TARGET} PUBLIC target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) +# Enable the panic handler wrapper +idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) + +# Patch LWIP memory pool allocators (see lwip_patch.c) +idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_malloc" APPEND) +idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_free" APPEND) + # Collect all of the include directories and compile definitions for the IDF components, # including those added by the IDF Component Manager via idf_components.yaml. foreach(comp ${__COMPONENT_NAMES_RESOLVED}) From 043ba45bc36a4bb19e732ae3f68181a05137906b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 22 Oct 2024 16:01:01 +1100 Subject: [PATCH 0004/2098] esp32: Add some notes about the different CMake files. Signed-off-by: Angus Gratton --- ports/esp32/CMakeLists.txt | 5 ++++- ports/esp32/esp32_common.cmake | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index 9ca9065afd0..d695170fa2b 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -1,5 +1,8 @@ # Top-level cmake file for building MicroPython on ESP32. - +# +# Note for maintainers: Where possible, functionality should be put into +# esp32_common.cmake not this file. This is because this CMakeLists.txt file +# needs to be duplicated for out-of-tree builds, and can easily get out of date. cmake_minimum_required(VERSION 3.12) # Retrieve IDF version diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 48f067debb7..9d51a03aa85 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -1,3 +1,9 @@ +# This is the common ESP-IDF "main component" CMakeLists.txt contents for MicroPython. +# +# This file is included directly from a main_${IDF_TARGET}/CMakeLists.txt file +# (or included from an out-of-tree main component CMakeLists.txt for out-of-tree +# builds.) + # Set location of base MicroPython directory. if(NOT MICROPY_DIR) get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE) From 9ea8d2a03181681feebaede3add82ec4970e30d7 Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Sat, 26 Oct 2024 15:19:54 +1100 Subject: [PATCH 0005/2098] tools/mpremote: Fix UnboundLocalError in Transport.fs_writefile(). The variable `written` was being used before it was defined in the `fs_writefile()` method of the Transport class. This was causing an `UnboundLocalError` to be raised when the `progress_callback` was not provided. Fixes issue #16084. Signed-off-by: Glenn Moloney --- tools/mpremote/mpremote/transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index 0b22a61580a..d845796953a 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -151,9 +151,9 @@ def fs_writefile(self, dest, data, chunk_size=256, progress_callback=None): while data: chunk = data[:chunk_size] self.exec("w(" + repr(chunk) + ")") - written += len(chunk) data = data[len(chunk) :] if progress_callback: + written += len(chunk) progress_callback(written, src_size) self.exec("f.close()") except TransportExecError as e: From 86c71a0307a509b6c47fbb7ce1ab2d827ff92653 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 1 Oct 2024 21:40:06 +0200 Subject: [PATCH 0006/2098] esp32/machine_hw_spi: Reject invalid number of bits in constructor. This commit adds an extra bit of parameters validation to the SPI bus constructor on ESP32. Passing 0 as the number of bits would trigger a division by zero error when performing read/write operations on an SPI bus created in such a fashion. Fixes issue #5910. Signed-off-by: Alessandro Gatti --- ports/esp32/machine_hw_spi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index 4b3d392cbda..6eb83fc09c3 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -196,6 +196,10 @@ static void machine_hw_spi_init_internal(machine_hw_spi_obj_t *self, mp_arg_val_ changed = true; } + if (args[ARG_bits].u_int != -1 && args[ARG_bits].u_int <= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + } + if (args[ARG_bits].u_int != -1 && args[ARG_bits].u_int != self->bits) { self->bits = args[ARG_bits].u_int; changed = true; From 548babf8a04510d99a49b2d129a9579afc33e182 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 27 Oct 2024 14:51:37 +1100 Subject: [PATCH 0007/2098] esp32/machine_pwm: Use IDF functions to calculate resolution correctly. This commit fixes PWM configuration across C3, C6, S2 and S3 chips, which was broken by 6d799378bad4474e77ddb2fa2187ecd6e290e0ba. Without this fix the PWM frequency is limited to a maximum of 2446Hz (on S2 at least). Signed-off-by: Andrew Leech --- ports/esp32/machine_pwm.c | 50 +++++++++++++-------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 71402e1c49e..61efdfa1854 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -34,6 +34,7 @@ #include "py/mphal.h" #include "driver/ledc.h" #include "esp_err.h" +#include "esp_clk_tree.h" #include "soc/gpio_sig_map.h" #define PWM_DBG(...) @@ -209,51 +210,32 @@ static void configure_channel(machine_pwm_obj_t *self) { static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { if (freq != timer->freq_hz) { - // Find the highest bit resolution for the requested frequency - unsigned int i = APB_CLK_FREQ; // 80 MHz - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - i = REF_CLK_FREQ; // 1 MHz - } - #endif - - int divider = (i + freq / 2) / freq; // rounded - if (divider == 0) { - divider = 1; - } - float f = (float)i / divider; // actual frequency - if (f <= 1.0) { - f = 1.0; - } - i = (unsigned int)roundf((float)i / f); - - unsigned int res = 0; - for (; i > 1; i >>= 1) { - ++res; - } - if (res == 0) { - res = 1; - } else if (res > HIGHEST_PWM_RES) { - // Limit resolution to HIGHEST_PWM_RES to match units of our duty - res = HIGHEST_PWM_RES; - } - - // Configure the new resolution and frequency - timer->duty_resolution = res; + // Configure the new frequency and resolution timer->freq_hz = freq; - #if SOC_LEDC_SUPPORT_XTAL_CLOCK + + #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + timer->clk_cfg = LEDC_USE_PLL_DIV_CLK; + #elif SOC_LEDC_SUPPORT_APB_CLOCK + timer->clk_cfg = LEDC_USE_APB_CLK; + #elif SOC_LEDC_SUPPORT_XTAL_CLOCK timer->clk_cfg = LEDC_USE_XTAL_CLK; #else - timer->clk_cfg = LEDC_USE_APB_CLK; + #error No supported PWM / LEDC clocks. #endif #if SOC_LEDC_SUPPORT_REF_TICK if (freq < EMPIRIC_FREQ) { timer->clk_cfg = LEDC_USE_REF_TICK; } #endif + uint32_t src_clk_freq = 0; + esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); + if (err != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); + } + timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); // Set frequency - esp_err_t err = ledc_timer_config(timer); + err = ledc_timer_config(timer); if (err != ESP_OK) { if (err == ESP_FAIL) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); From d34b15ac6f03ecad224d02aec9491d6a04605f72 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 12 Sep 2024 13:08:32 +0200 Subject: [PATCH 0008/2098] pic16bit: Make it build with recent XC16 versions. The PIC16 port didn't catch up with the other ports, so it required a bit of work to make it build with the latest version of XC16. Signed-off-by: Alessandro Gatti --- ports/pic16bit/Makefile | 4 ++-- ports/pic16bit/mpconfigport.h | 4 ---- py/misc.h | 13 +++++++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ports/pic16bit/Makefile b/ports/pic16bit/Makefile index 6d061514f95..dda48fa3ce5 100644 --- a/ports/pic16bit/Makefile +++ b/ports/pic16bit/Makefile @@ -7,7 +7,7 @@ QSTR_DEFS = qstrdefsport.h include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk -XCVERSION ?= 1.35 +XCVERSION ?= 2.10 XC16 ?= /opt/microchip/xc16/v$(XCVERSION) CROSS_COMPILE ?= $(XC16)/bin/xc16- @@ -31,7 +31,7 @@ CFLAGS += -O1 -DNDEBUG endif LDFLAGS += --heap=0 -nostdlib -T $(XC16)/support/$(PARTFAMILY)/gld/p$(PART).gld -Map=$@.map --cref -p$(PART) -LIBS += -L$(XC16)/lib -L$(XC16)/lib/$(PARTFAMILY) -lc -lm -lpic30 +LIBS += -L$(XC16)/lib -L$(XC16)/lib/$(PARTFAMILY) -lc99-elf -lm-elf -lc99-pic30-elf SRC_C = \ main.c \ diff --git a/ports/pic16bit/mpconfigport.h b/ports/pic16bit/mpconfigport.h index a2d607fb293..d80f7edb9e5 100644 --- a/ports/pic16bit/mpconfigport.h +++ b/ports/pic16bit/mpconfigport.h @@ -93,7 +93,3 @@ typedef int mp_off_t; #define MICROPY_MPHALPORT_H "pic16bit_mphal.h" #define MICROPY_HW_BOARD_NAME "dsPICSK" #define MICROPY_HW_MCU_NAME "dsPIC33" - -// XC16 toolchain doesn't seem to define these -typedef int intptr_t; -typedef unsigned int uintptr_t; diff --git a/py/misc.h b/py/misc.h index 96b13c151a6..8ac80bb7b3e 100644 --- a/py/misc.h +++ b/py/misc.h @@ -380,6 +380,18 @@ static inline bool mp_check(bool value) { // mp_int_t can be larger than long, i.e. Windows 64-bit, nan-box variants static inline uint32_t mp_clz_mpi(mp_int_t x) { + #ifdef __XC16__ + mp_uint_t mask = MP_OBJ_WORD_MSBIT_HIGH; + mp_uint_t zeroes = 0; + while (mask != 0) { + if (mask & (mp_uint_t)x) { + break; + } + zeroes++; + mask >>= 1; + } + return zeroes; + #else MP_STATIC_ASSERT(sizeof(mp_int_t) == sizeof(long long) || sizeof(mp_int_t) == sizeof(long)); @@ -389,6 +401,7 @@ static inline uint32_t mp_clz_mpi(mp_int_t x) { } else { return mp_clzll((unsigned long long)x); } + #endif } #endif // MICROPY_INCLUDED_PY_MISC_H From 9591b0a53c599baac79e1e895153d6b6944ebef0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 30 Oct 2024 11:18:59 +1100 Subject: [PATCH 0009/2098] tools/ci.sh: Fix commit msg checking when PR branch HEAD behind master. Fixes the problem noted at https://github.com/micropython/micropython/pull/15547#issuecomment-2434479702 which is that, because default CI HEAD for a PR is a (generated) merge commit into the master branch's current HEAD, then if the PR branch isn't fully rebased then the commit check runs against commits from master as well! Also drops running this check on push, the pull_request event is triggered by default on open and update ("synchronized" event), which probably covers the cases where this check should run. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/commit_formatting.yml | 4 ++-- tools/ci.sh | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml index 3fdcabc4ca7..fcbcaa7092e 100644 --- a/.github/workflows/commit_formatting.yml +++ b/.github/workflows/commit_formatting.yml @@ -1,6 +1,6 @@ name: Check commit message formatting -on: [push, pull_request] +on: [pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: '100' + fetch-depth: 100 - uses: actions/setup-python@v5 - name: Check commit message formatting run: source tools/ci.sh && ci_commit_formatting_run diff --git a/tools/ci.sh b/tools/ci.sh index 6f0f23a1c48..0b4b4e11cde 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -39,12 +39,18 @@ function ci_c_code_formatting_run { # commit formatting function ci_commit_formatting_run { - git remote add upstream https://github.com/micropython/micropython.git - git fetch --depth=100 upstream master + # Default GitHub Actions checkout for a PR is a generated merge commit where + # the parents are the head of base branch (i.e. master) and the head of the + # PR branch, respectively. Use these parents to find the merge-base (i.e. + # where the PR branch head was branched) + # If the common ancestor commit hasn't been found, fetch more. - git merge-base upstream/master HEAD || git fetch --unshallow upstream master - # For a PR, upstream/master..HEAD ends with a merge commit into master, exclude that one. - tools/verifygitlog.py -v upstream/master..HEAD --no-merges + git merge-base HEAD^1 HEAD^2 || git fetch --unshallow origin + + MERGE_BASE=$(git merge-base HEAD^1 HEAD^2) + HEAD=$(git rev-parse HEAD^2) + echo "Checking commits between merge base ${MERGE_BASE} and PR head ${HEAD}..." + tools/verifygitlog.py -v "${MERGE_BASE}..${HEAD}" } ######################################################################################## From 787c424cfc1e07c8881713ee5c1a6e26f6d6bf07 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 31 Oct 2024 10:47:47 +1100 Subject: [PATCH 0010/2098] tools/ci.sh: Fix reference commit for code size comparison. Previously the code size comparison was between the merge base (i.e. where the PR branched), and the generated merge commit into master. If the PR branch was older than current master, this meant the size comparison could incorrectly include changes already merged on master but missing from the PR branch. This commit changes it to compare the generated merge commit against current master, i.e. the size impact if this PR was to be merged. This commit also disables running the code size check on "push", it now only runs on pull_request events. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/code_size.yml | 1 - tools/ci.sh | 44 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 163d4455cff..67261933798 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -1,7 +1,6 @@ name: Check code size on: - push: pull_request: paths: - '.github/workflows/*.yml' diff --git a/tools/ci.sh b/tools/ci.sh index 0b4b4e11cde..db6f1cce011 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -69,25 +69,37 @@ function ci_code_size_build { PORTS_TO_CHECK=bmusxpdv SUBMODULES="lib/asf4 lib/berkeley-db-1.xx lib/btstack lib/cyw43-driver lib/lwip lib/mbedtls lib/micropython-lib lib/nxp_driver lib/pico-sdk lib/stm32lib lib/tinyusb" - # starts off at either the ref/pull/N/merge FETCH_HEAD, or the current branch HEAD - git checkout -b pull_request # save the current location - git remote add upstream https://github.com/micropython/micropython.git - git fetch --depth=100 upstream master - # If the common ancestor commit hasn't been found, fetch more. - git merge-base upstream/master HEAD || git fetch --unshallow upstream master + # Default GitHub pull request sets HEAD to a generated merge commit + # between PR branch (HEAD^2) and base branch (i.e. master) (HEAD^1). + # + # We want to compare this generated commit with the base branch, to see what + # the code size impact would be if we merged this PR. + REFERENCE=$(git rev-parse --short HEAD^1) + COMPARISON=$(git rev-parse --short HEAD) + + echo "Comparing sizes of reference ${REFERENCE} to ${COMPARISON}..." + git log --oneline $REFERENCE..$COMPARISON + + function code_size_build_step { + COMMIT=$1 + OUTFILE=$2 + IGNORE_ERRORS=$3 + + echo "Building ${COMMIT}..." + git checkout --detach $COMMIT + git submodule update --init $SUBMODULES + git show -s + tools/metrics.py clean $PORTS_TO_CHECK + tools/metrics.py build $PORTS_TO_CHECK | tee $OUTFILE || $IGNORE_ERRORS + } + # build reference, save to size0 # ignore any errors with this build, in case master is failing - git checkout `git merge-base --fork-point upstream/master pull_request` - git submodule update --init $SUBMODULES - git show -s - tools/metrics.py clean $PORTS_TO_CHECK - tools/metrics.py build $PORTS_TO_CHECK | tee ~/size0 || true + code_size_build_step $REFERENCE ~/size0 true # build PR/branch, save to size1 - git checkout pull_request - git submodule update --init $SUBMODULES - git log upstream/master..HEAD - tools/metrics.py clean $PORTS_TO_CHECK - tools/metrics.py build $PORTS_TO_CHECK | tee ~/size1 + code_size_build_step $COMPARISON ~/size1 false + + unset -f code_size_build_step } ######################################################################################## From 0e490b7c8f32bb72c516985abe67147b9385dc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Klus=C3=A1=C4=8Dek?= Date: Tue, 29 Oct 2024 22:38:24 +0100 Subject: [PATCH 0011/2098] docs/reference/packages: Fix description of --target option in mip. Descripton of mip usage with micropython port suggest using it like this: ./micropython -m mip install --target=third-party pkgname But it should be called without equal sign: ./micropython -m mip install --target third-party pkgname Signed-off-by: honza.klu@gmail.com --- docs/reference/packages.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index e47c226b241..86b8efaa195 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -69,9 +69,9 @@ On the Unix port, ``mip`` can be used at the REPL as above, and also by using `` $ ./micropython -m mip install pkgname-or-url $ ./micropython -m mip install pkgname-or-url@version -The ``--target=path``, ``--no-mpy``, and ``--index`` arguments can be set:: +The ``--target path``, ``--no-mpy``, and ``--index`` arguments can be set:: - $ ./micropython -m mip install --target=third-party pkgname + $ ./micropython -m mip install --target third-party pkgname $ ./micropython -m mip install --no-mpy pkgname $ ./micropython -m mip install --index https://host/pi pkgname From 7e1098befe178e4b93657a4d49e6a354037beec4 Mon Sep 17 00:00:00 2001 From: Jan Sturm Date: Tue, 29 Oct 2024 19:26:19 +0100 Subject: [PATCH 0012/2098] py/objdeque: Fix buffer overflow in deque_subscr. In `deque_subscr()`, if `index_val` equals `self->alloc`, the index correction `index_val -= self->alloc` does not execute, leading to an out-of-bounds access in `self->items[index_val]`. The fix in this commit ensures that the index correction is applied whenever `index_val >= self->alloc`, preventing access beyond the allocated buffer size. Signed-off-by: Jan Sturm --- py/objdeque.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/objdeque.c b/py/objdeque.c index 2ad771284df..22c380a05a9 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -208,7 +208,7 @@ static mp_obj_t deque_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { size_t offset = mp_get_index(self->base.type, deque_len(self), index, false); size_t index_val = self->i_get + offset; - if (index_val > self->alloc) { + if (index_val >= self->alloc) { index_val -= self->alloc; } From c88a9d60a17594bfd72d57b073eeb9150019b52c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Oct 2024 15:07:58 +1100 Subject: [PATCH 0013/2098] tests/basics/deque2.py: Add tests for deque subscript-from-end. Signed-off-by: Damien George --- tests/basics/deque2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/basics/deque2.py b/tests/basics/deque2.py index 3552d5be37a..ebc0872c7b4 100644 --- a/tests/basics/deque2.py +++ b/tests/basics/deque2.py @@ -31,6 +31,16 @@ d[3] = 5 print(d[3]) +# Access the last element via index, when the last element is at various locations +d = deque((), 2) +for i in range(4): + d.append(i) + print(i, d[-1]) + +# Write the last element then access all elements from the end +d[-1] = 4 +print(d[-2], d[-1]) + # Accessing indices out of bounds raises IndexError try: d[4] From f2ac4719891af9d50520828ae5d68ffa0d9f19c5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Oct 2024 12:04:07 +1100 Subject: [PATCH 0014/2098] tests/run-tests.py: Simplify the way target-specific tests are given. Signed-off-by: Damien George --- tests/run-tests.py | 158 ++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index cd1d681af1e..f144a763c95 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -89,6 +89,88 @@ def open(self, path, mode): __import__('__injected_test') """ +# Tests to skip on specific targets. +# These are tests that are difficult to detect that they should not be run on the given target. +target_tests_to_skip = { + "esp8266": ( + "micropython/viper_args.py", # too large + "micropython/viper_binop_arith.py", # too large + "misc/rge_sm.py", # too large + ), + "minimal": ( + "basics/class_inplace_op.py", # all special methods not supported + "basics/subclass_native_init.py", # native subclassing corner cases not support + "misc/rge_sm.py", # too large + "micropython/opt_level.py", # don't assume line numbers are stored + ), + "nrf": ( + "basics/io_buffered_writer.py", + "basics/io_bytesio_cow.py", + "basics/io_bytesio_ext.py", + "basics/io_bytesio_ext2.py", + "basics/io_iobase.py", + "basics/io_stringio1.py", + "basics/io_stringio_base.py", + "basics/io_stringio_with.py", + "basics/io_write_ext.py", + "basics/memoryview1.py", # no item assignment for memoryview + "extmod/random_basic.py", # unimplemented: random.seed + "micropython/opt_level.py", # no support for line numbers + "misc/non_compliant.py", # no item assignment for bytearray + ), + "renesas-ra": ( + "extmod/time_time_ns.py", # RA fsp rtc function doesn't support nano sec info + ), + "rp2": ( + # Skip thread tests that require more that 2 threads. + "thread/stress_heap.py", + "thread/thread_lock2.py", + "thread/thread_lock3.py", + "thread/thread_shared2.py", + ), + "qemu": ( + # Skip tests that require Cortex-M4. + "inlineasm/asmfpaddsub.py", + "inlineasm/asmfpcmp.py", + "inlineasm/asmfpldrstr.py", + "inlineasm/asmfpmuldiv.py", + "inlineasm/asmfpsqrt.py", + ), + "webassembly": ( + "basics/string_format_modulo.py", # can't print nulls to stdout + "basics/string_strip.py", # can't print nulls to stdout + "extmod/asyncio_basic2.py", + "extmod/asyncio_cancel_self.py", + "extmod/asyncio_current_task.py", + "extmod/asyncio_exception.py", + "extmod/asyncio_gather_finished_early.py", + "extmod/asyncio_get_event_loop.py", + "extmod/asyncio_heaplock.py", + "extmod/asyncio_loop_stop.py", + "extmod/asyncio_new_event_loop.py", + "extmod/asyncio_threadsafeflag.py", + "extmod/asyncio_wait_for_fwd.py", + "extmod/binascii_a2b_base64.py", + "extmod/re_stack_overflow.py", + "extmod/time_res.py", + "extmod/vfs_posix.py", + "extmod/vfs_posix_enoent.py", + "extmod/vfs_posix_paths.py", + "extmod/vfs_userfs.py", + "micropython/emg_exc.py", + "micropython/extreme_exc.py", + "micropython/heapalloc_exc_compressed_emg_exc.py", + ), + "wipy": ( + "misc/print_exception.py", # requires error reporting full + ), + "zephyr": ( + # Skip thread tests that require more than 4 threads. + "thread/stress_heap.py", + "thread/thread_lock3.py", + ), +} + def rm_f(fname): if os.path.exists(fname): @@ -629,85 +711,13 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if t.startswith("thread/mutate_"): skip_tests.add(t) - # Skip thread tests that require many threads on targets that don't support multiple threads. - if args.target == "rp2": - skip_tests.add("thread/stress_heap.py") - skip_tests.add("thread/thread_lock2.py") - skip_tests.add("thread/thread_lock3.py") - skip_tests.add("thread/thread_shared2.py") - elif args.target == "zephyr": - skip_tests.add("thread/stress_heap.py") - skip_tests.add("thread/thread_lock3.py") - # Some tests shouldn't be run on pyboard if args.target != "unix": skip_tests.add("basics/exception_chain.py") # warning is not printed skip_tests.add("micropython/meminfo.py") # output is very different to PC output - if args.target == "wipy": - skip_tests.add("misc/print_exception.py") # requires error reporting full - skip_tests.update( - { - "extmod/uctypes_%s.py" % t - for t in "bytearray le native_le ptr_le ptr_native_le sizeof sizeof_native array_assign_le array_assign_native_le".split() - } - ) # requires uctypes - skip_tests.add("extmod/heapq1.py") # heapq not supported by WiPy - skip_tests.add("extmod/random_basic.py") # requires random - skip_tests.add("extmod/random_extra.py") # requires random - elif args.target == "esp8266": - skip_tests.add("micropython/viper_args.py") # too large - skip_tests.add("micropython/viper_binop_arith.py") # too large - skip_tests.add("misc/rge_sm.py") # too large - elif args.target == "minimal": - skip_tests.add("basics/class_inplace_op.py") # all special methods not supported - skip_tests.add( - "basics/subclass_native_init.py" - ) # native subclassing corner cases not support - skip_tests.add("misc/rge_sm.py") # too large - skip_tests.add("micropython/opt_level.py") # don't assume line numbers are stored - elif args.target == "nrf": - skip_tests.add("basics/memoryview1.py") # no item assignment for memoryview - skip_tests.add("extmod/random_basic.py") # unimplemented: random.seed - skip_tests.add("micropython/opt_level.py") # no support for line numbers - skip_tests.add("misc/non_compliant.py") # no item assignment for bytearray - for t in tests: - if t.startswith("basics/io_"): - skip_tests.add(t) - elif args.target == "renesas-ra": - skip_tests.add( - "extmod/time_time_ns.py" - ) # RA fsp rtc function doesn't support nano sec info - elif args.target == "qemu": - 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 == "webassembly": - skip_tests.add("basics/string_format_modulo.py") # can't print nulls to stdout - skip_tests.add("basics/string_strip.py") # can't print nulls to stdout - skip_tests.add("extmod/asyncio_basic2.py") - skip_tests.add("extmod/asyncio_cancel_self.py") - skip_tests.add("extmod/asyncio_current_task.py") - skip_tests.add("extmod/asyncio_exception.py") - skip_tests.add("extmod/asyncio_gather_finished_early.py") - skip_tests.add("extmod/asyncio_get_event_loop.py") - skip_tests.add("extmod/asyncio_heaplock.py") - skip_tests.add("extmod/asyncio_loop_stop.py") - skip_tests.add("extmod/asyncio_new_event_loop.py") - skip_tests.add("extmod/asyncio_threadsafeflag.py") - skip_tests.add("extmod/asyncio_wait_for_fwd.py") - skip_tests.add("extmod/binascii_a2b_base64.py") - skip_tests.add("extmod/re_stack_overflow.py") - skip_tests.add("extmod/time_res.py") - skip_tests.add("extmod/vfs_posix.py") - skip_tests.add("extmod/vfs_posix_enoent.py") - skip_tests.add("extmod/vfs_posix_paths.py") - skip_tests.add("extmod/vfs_userfs.py") - skip_tests.add("micropython/emg_exc.py") - skip_tests.add("micropython/extreme_exc.py") - skip_tests.add("micropython/heapalloc_exc_compressed_emg_exc.py") + # Skip target-specific tests. + skip_tests.update(target_tests_to_skip.get(args.target, ())) # Some tests are known to fail on 64-bit machines if pyb is None and platform.architecture()[0] == "64bit": From 8978102f3595ae321484a6be44c1dcf25c8909a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 18 Aug 2024 16:21:39 +1000 Subject: [PATCH 0015/2098] tests/run-tests.py: Change --target/--device options to --test-instance. Previously to this commit, running the test suite on a bare-metal board required specifying the target (really platform) and device, eg: $ ./run-tests.py --target pyboard --device /dev/ttyACM1 That's quite a lot to type, and you also need to know what the target platform is, when a lot of the time you either don't care or it doesn't matter. This commit makes it easier to run the tests by replacing both of these options with a single `--test-instance` (`-t` for short) option. That option specifies the executable/port/device to test. Then the target platform is automatically detected. The `--test-instance` can be passed: - "unix" (the default) to use the unix version of MicroPython - "webassembly" to test the webassembly port - anything else is considered a port/device to pass to Pyboard There are also some shortcuts to specify a port/device, following `mpremote`: - a is short for /dev/ttyACM - u is short for /dev/ttyUSB - c is short for COM For example: $ ./run-tests.py -t a1 Note that the default test instance is "unix" and so this commit does not change the standard way to run tests on the unix port, by just doing `./run-tests.py`. As part of this change, the platform (and it's native architecture if it supports importing native .mpy files) is show at the start of the test run. Signed-off-by: Damien George --- docs/develop/gettingstarted.rst | 2 +- docs/develop/writingtests.rst | 2 +- ports/cc3200/tools/smoke.py | 2 +- ports/qemu/Makefile | 2 +- ports/qemu/README.md | 2 +- ports/webassembly/Makefile | 4 +- tests/README.md | 16 +++- tests/feature_check/target_info.py | 3 +- tests/run-tests.py | 148 ++++++++++++++++++----------- tools/ci.sh | 2 +- 10 files changed, 116 insertions(+), 67 deletions(-) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index c1fd338c54c..fed632ea1ac 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -278,7 +278,7 @@ To run a selection of tests on a board/device connected over USB use: .. code-block:: bash $ cd tests - $ ./run-tests.py --target minimal --device /dev/ttyACM0 + $ ./run-tests.py -t /dev/ttyACM0 See also :ref:`writingtests`. diff --git a/docs/develop/writingtests.rst b/docs/develop/writingtests.rst index 9bb5178f55f..a7d033f17e9 100644 --- a/docs/develop/writingtests.rst +++ b/docs/develop/writingtests.rst @@ -60,7 +60,7 @@ Then to run on a board: .. code-block:: bash - $ ./run-tests.py --target minimal --device /dev/ttyACM0 + $ ./run-tests.py -t /dev/ttyACM0 And to run only a certain set of tests (eg a directory): diff --git a/ports/cc3200/tools/smoke.py b/ports/cc3200/tools/smoke.py index 463a485c4d4..f7105411b4d 100644 --- a/ports/cc3200/tools/smoke.py +++ b/ports/cc3200/tools/smoke.py @@ -6,7 +6,7 @@ """ Execute it like this: -python3 run-tests.py --target wipy --device 192.168.1.1 ../cc3200/tools/smoke.py +python3 run-tests.py -t 192.168.1.1 ../cc3200/tools/smoke.py """ pin_map = [23, 24, 11, 12, 13, 14, 15, 16, 17, 22, 28, 10, 9, 8, 7, 6, 30, 31, 3, 0, 4, 5] diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 4d73c025d7d..62601324d6a 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -166,7 +166,7 @@ run: $(BUILD)/firmware.elf .PHONY: test test: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) - cd $(TOP)/tests && ./run-tests.py --target qemu --device execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_ARGS) $(RUN_TESTS_EXTRA) + cd $(TOP)/tests && ./run-tests.py -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_ARGS) $(RUN_TESTS_EXTRA) $(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ) $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) diff --git a/ports/qemu/README.md b/ports/qemu/README.md index d9108311bef..984faf87047 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -95,7 +95,7 @@ Or manually by first starting the emulation with `make run` and then running the tests against the serial device, for example: $ cd ../../tests - $ ./run-tests.py --target qemu --device /dev/pts/1 + $ ./run-tests.py -t /dev/pts/1 Extra make options ------------------ diff --git a/ports/webassembly/Makefile b/ports/webassembly/Makefile index 435636531cc..9a673b757b2 100644 --- a/ports/webassembly/Makefile +++ b/ports/webassembly/Makefile @@ -148,10 +148,10 @@ repl: $(BUILD)/micropython.mjs min: $(BUILD)/micropython.min.mjs test: $(BUILD)/micropython.mjs $(TOP)/tests/run-tests.py - cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly + cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py -t webassembly test_min: $(BUILD)/micropython.min.mjs $(TOP)/tests/run-tests.py - cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly + cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py -t webassembly ################################################################################ # Remaining make rules. diff --git a/tests/README.md b/tests/README.md index 7bd7eb58717..b39833aa096 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,19 @@ # MicroPython Test Suite -This directory contains tests for various functionality areas of MicroPython. -To run all stable tests, run "run-tests.py" script in this directory. +This directory contains tests for most parts of MicroPython. + +To run all stable tests, run the "run-tests.py" script in this directory. By default +that will run the test suite against the unix port of MicroPython. + +To run the test suite against a bare-metal target (a board running MicroPython firmware) +use the `-t` option to specify the serial port. This will automatically detect the +target platform and run the appropriate set of tests for that platform. For example: + + $ ./run-tests.py -t /dev/ttyACM0 + +That will run tests on the `/dev/ttyACM0` serial port. You can also use shortcut +device names like `a` for `/dev/ttyACM` and `c` for `COM`. Use +`./run-tests.py --help` to see all of the device possibilites, and other options. Tests of capabilities not supported on all platforms should be written to check for the capability being present. If it is not, the test diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index df89496708a..f60f3b31919 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -4,6 +4,7 @@ import sys +platform = getattr(sys, "platform", "minimal") sys_mpy = getattr(sys.implementation, "_mpy", 0) arch = [ None, @@ -19,4 +20,4 @@ "xtensawin", "rv32imc", ][sys_mpy >> 10] -print(arch) +print(platform, arch) diff --git a/tests/run-tests.py b/tests/run-tests.py index f144a763c95..3db058a3a1d 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -89,9 +89,12 @@ def open(self, path, mode): __import__('__injected_test') """ +# Platforms associated with the unix port, values of `sys.platform`. +PC_PLATFORMS = ("darwin", "linux", "win32") + # Tests to skip on specific targets. # These are tests that are difficult to detect that they should not be run on the given target. -target_tests_to_skip = { +platform_tests_to_skip = { "esp8266": ( "micropython/viper_args.py", # too large "micropython/viper_binop_arith.py", # too large @@ -161,7 +164,7 @@ def open(self, path, mode): "micropython/extreme_exc.py", "micropython/heapalloc_exc_compressed_emg_exc.py", ), - "wipy": ( + "WiPy": ( "misc/print_exception.py", # requires error reporting full ), "zephyr": ( @@ -197,6 +200,53 @@ def convert_regex_escapes(line): return bytes("".join(cs), "utf8") +def get_test_instance(test_instance, baudrate, user, password): + if test_instance.startswith("port:"): + _, port = test_instance.split(":", 1) + elif test_instance == "unix": + return None + elif test_instance == "webassembly": + return PyboardNodeRunner() + elif test_instance.startswith("a") and test_instance[1:].isdigit(): + port = "/dev/ttyACM" + test_instance[1:] + elif test_instance.startswith("u") and test_instance[1:].isdigit(): + port = "/dev/ttyUSB" + test_instance[1:] + elif test_instance.startswith("c") and test_instance[1:].isdigit(): + port = "COM" + test_instance[1:] + else: + # Assume it's a device path. + port = test_instance + + global pyboard + sys.path.append(base_path("../tools")) + import pyboard + + pyb = pyboard.Pyboard(port, baudrate, user, password) + pyboard.Pyboard.run_script_on_remote_target = run_script_on_remote_target + pyb.enter_raw_repl() + return pyb + + +def detect_test_platform(pyb, args): + # Run a script to detect various bits of information about the target test instance. + output = run_feature_check(pyb, args, "target_info.py") + if output.endswith(b"CRASH"): + raise ValueError("cannot detect platform: {}".format(output)) + platform, arch = str(output, "ascii").strip().split() + if arch == "None": + arch = None + + args.platform = platform + args.arch = arch + if arch and not args.mpy_cross_flags: + args.mpy_cross_flags = "-march=" + arch + + print("platform={}".format(platform), end="") + if arch: + print(" arch={}".format(arch), end="") + print() + + def prepare_script_for_target(args, *, script_filename=None, script_text=None, force_plain=False): if force_plain or (not args.via_mpy and args.emit == "bytecode"): if script_filename is not None: @@ -706,18 +756,18 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("extmod/ssl_poll.py") # Skip thread mutation tests on targets that don't have the GIL. - if args.target in ("rp2", "unix"): + if args.platform in PC_PLATFORMS + ("rp2",): for t in tests: if t.startswith("thread/mutate_"): skip_tests.add(t) # Some tests shouldn't be run on pyboard - if args.target != "unix": + if args.platform not in PC_PLATFORMS: skip_tests.add("basics/exception_chain.py") # warning is not printed skip_tests.add("micropython/meminfo.py") # output is very different to PC output - # Skip target-specific tests. - skip_tests.update(target_tests_to_skip.get(args.target, ())) + # Skip platform-specific tests. + skip_tests.update(platform_tests_to_skip.get(args.platform, ())) # Some tests are known to fail on 64-bit machines if pyb is None and platform.architecture()[0] == "64bit": @@ -932,17 +982,38 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, description="""Run and manage tests for MicroPython. +By default the tests are run against the unix port of MicroPython. To run it +against something else, use the -t option. See below for details. + Tests are discovered by scanning test directories for .py files or using the specified test files. If test files nor directories are specified, the script expects to be ran in the tests directory (where this file is located) and the builtin tests suitable for the target platform are ran. + When running tests, run-tests.py compares the MicroPython output of the test with the output produced by running the test through CPython unless a .exp file is found, in which case it is used as comparison. + If a test fails, run-tests.py produces a pair of .out and .exp files in the result directory with the MicroPython output and the expectations, respectively. """, epilog="""\ +The -t option accepts the following for the test instance: +- unix - use the unix port of MicroPython, specified by the MICROPY_MICROPYTHON + environment variable (which defaults to the standard variant of either the unix + or windows ports, depending on the host platform) +- webassembly - use the webassembly port of MicroPython, specified by the + MICROPY_MICROPYTHON_MJS environment variable (which defaults to the standard + variant of the webassembly port) +- port: - connect to and use the given serial port device +- a - connect to and use /dev/ttyACM +- u - connect to and use /dev/ttyUSB +- c - connect to and use COM +- exec: - execute a command and attach to its stdin/stdout +- execpty: - execute a command and attach to the printed /dev/pts/ device +- ... - connect to the given IPv4 address +- anything else specifies a serial port + Options -i and -e can be multiple and processed in the order given. Regex "search" (vs "match") operation is used. An action (include/exclude) of the last matching regex is used: @@ -951,11 +1022,8 @@ def main(): run-tests.py -e async -i async_foo - include all, exclude async, yet still include async_foo """, ) - cmd_parser.add_argument("--target", default="unix", help="the target platform") cmd_parser.add_argument( - "--device", - default="/dev/ttyACM0", - help="the serial device or the IP address of the pyboard", + "-t", "--test-instance", default="unix", help="the MicroPython instance to test" ) cmd_parser.add_argument( "-b", "--baudrate", default=115200, help="the baud rate of the serial device" @@ -1039,43 +1107,11 @@ def main(): sys.exit(0) - LOCAL_TARGETS = ( - "unix", - "webassembly", - ) - EXTERNAL_TARGETS = ( - "pyboard", - "wipy", - "esp8266", - "esp32", - "minimal", - "nrf", - "qemu", - "renesas-ra", - "rp2", - "zephyr", - ) - if args.target in LOCAL_TARGETS: - pyb = None - if args.target == "webassembly": - pyb = PyboardNodeRunner() - elif args.target in EXTERNAL_TARGETS: - global pyboard - sys.path.append(base_path("../tools")) - import pyboard - - pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password) - pyboard.Pyboard.run_script_on_remote_target = run_script_on_remote_target - pyb.enter_raw_repl() - else: - raise ValueError("target must be one of %s" % ", ".join(LOCAL_TARGETS + EXTERNAL_TARGETS)) + # Get the test instance to run on. + pyb = get_test_instance(args.test_instance, args.baudrate, args.user, args.password) - # Automatically detect the native architecture for mpy-cross if not given. - if not args.mpy_cross_flags: - output = run_feature_check(pyb, args, "target_info.py") - arch = str(output, "ascii").strip() - if arch != "None": - args.mpy_cross_flags = "-march=" + arch + # Automatically detect the platform. + detect_test_platform(pyb, args) if args.run_failures and (any(args.files) or args.test_dirs is not None): raise ValueError( @@ -1091,7 +1127,7 @@ def main(): tests = [] elif len(args.files) == 0: test_extensions = ("*.py",) - if args.target == "webassembly": + if args.platform == "webassembly": test_extensions += ("*.js", "*.mjs") if args.test_dirs is None: @@ -1101,23 +1137,23 @@ def main(): "misc", "extmod", ) - if args.target == "pyboard": + if args.platform == "pyboard": # run pyboard tests test_dirs += ("float", "stress", "inlineasm", "ports/stm32") - elif args.target in ("renesas-ra"): + elif args.platform == "renesas-ra": test_dirs += ("float", "inlineasm", "ports/renesas-ra") - elif args.target == "rp2": + elif args.platform == "rp2": test_dirs += ("float", "stress", "thread", "ports/rp2") if "arm" in args.mpy_cross_flags: test_dirs += ("inlineasm",) - elif args.target == "esp32": + elif args.platform == "esp32": test_dirs += ("float", "stress", "thread") - elif args.target in ("esp8266", "minimal", "nrf"): + elif args.platform in ("esp8266", "minimal", "nrf"): test_dirs += ("float",) - elif args.target == "wipy": + elif args.platform == "WiPy": # run WiPy tests test_dirs += ("ports/cc3200",) - elif args.target == "unix": + elif args.platform in PC_PLATFORMS: # run PC tests test_dirs += ( "float", @@ -1128,13 +1164,13 @@ def main(): "cmdline", "ports/unix", ) - elif args.target == "qemu": + elif args.platform == "qemu": test_dirs += ( "float", "inlineasm", "ports/qemu", ) - elif args.target == "webassembly": + elif args.platform == "webassembly": test_dirs += ("float", "ports/webassembly") else: # run tests from these directories diff --git a/tools/ci.sh b/tools/ci.sh index db6f1cce011..4e6f09b0bb8 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -785,5 +785,5 @@ 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) + (cd tests && ./run-tests.py -t 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) } From 85053adb243800e7168e5736853bea5364770191 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Oct 2024 13:59:31 +1100 Subject: [PATCH 0016/2098] tests/run-tests.py: Add mimxrt and samd platforms. Signed-off-by: Damien George --- tests/run-tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 3db058a3a1d..d0feb4bcd66 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1140,6 +1140,8 @@ def main(): if args.platform == "pyboard": # run pyboard tests test_dirs += ("float", "stress", "inlineasm", "ports/stm32") + elif args.platform == "mimxrt": + test_dirs += ("float", "stress", "inlineasm") elif args.platform == "renesas-ra": test_dirs += ("float", "inlineasm", "ports/renesas-ra") elif args.platform == "rp2": @@ -1148,7 +1150,7 @@ def main(): test_dirs += ("inlineasm",) elif args.platform == "esp32": test_dirs += ("float", "stress", "thread") - elif args.platform in ("esp8266", "minimal", "nrf"): + elif args.platform in ("esp8266", "minimal", "samd", "nrf"): test_dirs += ("float",) elif args.platform == "WiPy": # run WiPy tests From 919756cea4da30ab98cfa00016ed5b3847003219 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Oct 2024 12:41:17 +1100 Subject: [PATCH 0017/2098] extmod/modlwip: Fix IGMP address type when IPv6 is enabled. This was missed in 628abf8f25a7705a2810fffe2ca6ae652c532896. The the bug was that, when IPv6 is enabled, the `sizeof(ip_addr_t)` is much larger than IPv4 size, which is what's needed for IGMP addressing. Fixes issue #16100. Signed-off-by: Damien George --- extmod/modlwip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 21c6f7264be..0d38bf41b20 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -1432,7 +1432,7 @@ static mp_obj_t lwip_socket_setsockopt(size_t n_args, const mp_obj_t *args) { case IP_DROP_MEMBERSHIP: { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); - if (bufinfo.len != sizeof(ip_addr_t) * 2) { + if (bufinfo.len != sizeof(MP_IGMP_IP_ADDR_TYPE) * 2) { mp_raise_ValueError(NULL); } From d278ba5cc53491037a80e5c4fbd3f94d00ebe3d7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Oct 2024 11:32:59 +1100 Subject: [PATCH 0018/2098] extmod/nimble: Remove asserts of ediv_rand_present and adjust comments. Recent versions of NimBLE (since release 1.6.0) removed this variable; see https://github.com/apache/mynewt-nimble/commit/7cc8c08d67d52b373eeeec6b33b113192cf2e996. We never used it except in an assert, so remove those asserts to make the code compatible with newer NimBLE versions (eg for the esp32 port). Signed-off-by: Damien George --- extmod/nimble/modbluetooth_nimble.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 8d555f1124f..6ca0c172677 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -1911,24 +1911,21 @@ static int ble_secret_store_read(int obj_type, const union ble_store_key *key, u // (single) // Find the entry for this specific peer. assert(key->sec.idx == 0); - assert(!key->sec.ediv_rand_present); key_data = (const uint8_t *)&key->sec.peer_addr; key_data_len = sizeof(ble_addr_t); } else { // (with index) // Iterate all known peers. - assert(!key->sec.ediv_rand_present); key_data = NULL; key_data_len = 0; } break; } case BLE_STORE_OBJ_TYPE_OUR_SEC: { - // - // Find our secret for this remote device, matching this ediv/rand key. + // + // Find our secret for this remote device. assert(ble_addr_cmp(&key->sec.peer_addr, BLE_ADDR_ANY)); // Must have address. assert(key->sec.idx == 0); - assert(key->sec.ediv_rand_present); key_data = (const uint8_t *)&key->sec.peer_addr; key_data_len = sizeof(ble_addr_t); break; @@ -1958,10 +1955,6 @@ static int ble_secret_store_read(int obj_type, const union ble_store_key *key, u DEBUG_printf("ble_secret_store_read: found secret\n"); - if (obj_type == BLE_STORE_OBJ_TYPE_OUR_SEC) { - // TODO: Verify ediv_rand matches. - } - return 0; } @@ -1970,14 +1963,13 @@ static int ble_secret_store_write(int obj_type, const union ble_store_value *val switch (obj_type) { case BLE_STORE_OBJ_TYPE_PEER_SEC: case BLE_STORE_OBJ_TYPE_OUR_SEC: { - // + // struct ble_store_key_sec key_sec; const struct ble_store_value_sec *value_sec = &val->sec; ble_store_key_from_value_sec(&key_sec, value_sec); assert(ble_addr_cmp(&key_sec.peer_addr, BLE_ADDR_ANY)); // Must have address. - assert(key_sec.ediv_rand_present); if (!mp_bluetooth_gap_on_set_secret(obj_type, (const uint8_t *)&key_sec.peer_addr, sizeof(ble_addr_t), (const uint8_t *)value_sec, sizeof(struct ble_store_value_sec))) { DEBUG_printf("Failed to write key: type=%d\n", obj_type); @@ -2005,9 +1997,7 @@ static int ble_secret_store_delete(int obj_type, const union ble_store_key *key) case BLE_STORE_OBJ_TYPE_PEER_SEC: case BLE_STORE_OBJ_TYPE_OUR_SEC: { // - assert(ble_addr_cmp(&key->sec.peer_addr, BLE_ADDR_ANY)); // Must have address. - // ediv_rand is optional (will not be present for delete). if (!mp_bluetooth_gap_on_set_secret(obj_type, (const uint8_t *)&key->sec.peer_addr, sizeof(ble_addr_t), NULL, 0)) { DEBUG_printf("Failed to delete key: type=%d\n", obj_type); From 275b3afae70ef71c4c17801d4bf56c0921481b6c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Oct 2024 11:10:46 +1100 Subject: [PATCH 0019/2098] esp32/network_wlan: Add missing WLAN security constants. These were added to the `network` module but not the `network.WLAN` class, which is the new home for such constants. Also: - Mark the WLAN constants in the `network` module as deprecated, to be removed in MicroPython 2.0. - Move the static assert to the WLAN source code, to be close to where it relates to. Signed-off-by: Damien George --- ports/esp32/modnetwork_globals.h | 3 ++- ports/esp32/network_common.c | 8 -------- ports/esp32/network_wlan.c | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index c0cf229421d..c6092bd1421 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -13,7 +13,8 @@ { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_network_phy_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&esp_network_ipconfig_obj) }, -#if MICROPY_PY_NETWORK_WLAN +// These WLAN constants are now in the WLAN class and remain here only for backwards compatibility. +#if !MICROPY_PREVIEW_VERSION_2 && MICROPY_PY_NETWORK_WLAN { MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(WIFI_IF_STA)}, { MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(WIFI_IF_AP)}, diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index d6bba32146c..fce8a0304c8 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -337,11 +337,3 @@ static mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj, 0, 1, esp_phy_mode); - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) -_Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); -#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) -_Static_assert(WIFI_AUTH_MAX == 11, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); -#else -_Static_assert(WIFI_AUTH_MAX == 10, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); -#endif diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index 7e802166c11..d415c56d321 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -744,6 +744,13 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WAPI), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT_192), MP_ROM_INT(WIFI_AUTH_WPA3_ENT_192) }, + #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, + #endif { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, @@ -751,6 +758,14 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +_Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) +_Static_assert(WIFI_AUTH_MAX == 11, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#else +_Static_assert(WIFI_AUTH_MAX == 10, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#endif + MP_DEFINE_CONST_OBJ_TYPE( esp_network_wlan_type, MP_QSTR_WLAN, From 594670e44623e0635ecf45eb34b0a43f27e04101 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 1 Nov 2024 15:57:40 +1100 Subject: [PATCH 0020/2098] esp32/machine_pwm: Restore PWM support for ESP-IDF v5.0.x and v5.1.x. The cleanup in 548babf8 relies on some functions not available in older ESP-IDF. Temporarily restore them, until we drop support for ESP-IDF <5.2. PWM functionality should end up the same regardless of ESP-IDF version, and also no different from MicroPython V1.23. Signed-off-by: Angus Gratton --- ports/esp32/machine_pwm.c | 52 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 61efdfa1854..0134fa2cc8c 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -34,9 +34,12 @@ #include "py/mphal.h" #include "driver/ledc.h" #include "esp_err.h" -#include "esp_clk_tree.h" #include "soc/gpio_sig_map.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) +#include "esp_clk_tree.h" +#endif + #define PWM_DBG(...) // #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); @@ -208,7 +211,41 @@ static void configure_channel(machine_pwm_obj_t *self) { } } +// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + // This implementation is based on the one used in Micropython v1.23 + + // Find the highest bit resolution for the requested frequency + unsigned int freq = src_clk_freq; + + int divider = (freq + timer_freq / 2) / timer_freq; // rounded + if (divider == 0) { + divider = 1; + } + float f = (float)freq / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; + } + freq = (unsigned int)roundf((float)freq / f); + + unsigned int res = 0; + for (; freq > 1; freq >>= 1) { + ++res; + } + if (res == 0) { + res = 1; + } else if (res > HIGHEST_PWM_RES) { + // Limit resolution to HIGHEST_PWM_RES to match units of our duty + res = HIGHEST_PWM_RES; + } + + return res; +} +#endif + static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { + esp_err_t err; if (freq != timer->freq_hz) { // Configure the new frequency and resolution timer->freq_hz = freq; @@ -228,10 +265,21 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf } #endif uint32_t src_clk_freq = 0; - esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) + err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); if (err != ESP_OK) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); } + #else + // Simplified fallback logic for IDF V5.0.x, for targets with APB only. + src_clk_freq = APB_CLK_FREQ; // 80 MHz + #if SOC_LEDC_SUPPORT_REF_TICK + if (timer->clk_cfg == LEDC_USE_REF_TICK) { + src_clk_freq = REF_CLK_FREQ; // 1 MHz + } + #endif // SOC_LEDC_SUPPORT_REF_TICK + #endif // ESP_IDF_VERSION + timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); // Set frequency From df6b40a87fe3ddcb37cde488ba1a2060625ab737 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 14:27:01 +1100 Subject: [PATCH 0021/2098] esp32: Workaround native code execution crash on ESP32-S2. Seemingly ESP-IDF incorrectly marks RTC FAST memory region as MALLOC_CAP_EXEC on ESP32-S2 when it isn't. This memory is the lowest priority, so it only is returned if D/IRAM is exhausted. Apply this workaround to treat the allocation as failed if it gives us non-executable RAM back, rather than crashing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 03dc0807a02..18ef9d73547 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -39,6 +39,7 @@ #include "esp_task.h" #include "esp_event.h" #include "esp_log.h" +#include "esp_memory_utils.h" #include "esp_psram.h" #include "py/cstack.h" @@ -237,6 +238,13 @@ void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; size_t len_node = sizeof(native_code_node_t) + len; native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if CONFIG_IDF_TARGET_ESP32S2 + // Workaround for ESP-IDF bug https://github.com/espressif/esp-idf/issues/14835 + if (node != NULL && !esp_ptr_executable(node)) { + free(node); + node = NULL; + } + #endif // CONFIG_IDF_TARGET_ESP32S2 if (node == NULL) { m_malloc_fail(len_node); } From 48f96e96605c020a646adfc2d06ee0db9b87fbe8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:17:32 +1100 Subject: [PATCH 0022/2098] docs: Specify the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the docs. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 11 ++++++----- docs/esp8266/quickref.rst | 8 ++++---- docs/esp8266/tutorial/network_basics.rst | 13 +++++++------ docs/library/espnow.rst | 24 ++++++++++++------------ docs/library/network.WLAN.rst | 8 ++++---- docs/reference/mpremote.rst | 2 +- 6 files changed, 34 insertions(+), 32 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 3ab4e8f5ecd..b9ca0f8225f 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -79,11 +79,11 @@ Networking WLAN ^^^^ -The :mod:`network` module:: +The :class:`network.WLAN` class in the :mod:`network` module:: import network - wlan = network.WLAN(network.STA_IF) # create station interface + wlan = network.WLAN(network.WLAN.IF_STA) # create station interface wlan.active(True) # activate the interface wlan.scan() # scan for access points wlan.isconnected() # check if the station is connected to an AP @@ -91,7 +91,7 @@ The :mod:`network` module:: wlan.config('mac') # get the interface's MAC address wlan.ipconfig('addr4') # get the interface's IPv4 addresses - ap = network.WLAN(network.AP_IF) # create access-point interface + ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface ap.config(ssid='ESP-AP') # set the SSID of the access point ap.config(max_clients=10) # set how many clients can connect to the network ap.active(True) # activate the interface @@ -100,7 +100,7 @@ A useful function for connecting to your local WiFi network is:: def do_connect(): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') @@ -124,7 +124,8 @@ to reconnect forever). LAN ^^^ -To use the wired interfaces one has to specify the pins and mode :: +To use the wired interfaces via :class:`network.LAN` one has to specify the pins +and mode :: import network diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index b130ce65db2..6f02da95d89 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -49,11 +49,11 @@ The :mod:`esp` module:: Networking ---------- -The :mod:`network` module:: +The :class:`network.WLAN` class in the :mod:`network` module:: import network - wlan = network.WLAN(network.STA_IF) # create station interface + wlan = network.WLAN(network.WLAN.IF_STA) # create station interface wlan.active(True) # activate the interface wlan.scan() # scan for access points wlan.isconnected() # check if the station is connected to an AP @@ -61,7 +61,7 @@ The :mod:`network` module:: wlan.config('mac') # get the interface's MAC address wlan.ipconfig('addr4') # get the interface's IPv4 addresses - ap = network.WLAN(network.AP_IF) # create access-point interface + ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface ap.active(True) # activate the interface ap.config(ssid='ESP-AP') # set the SSID of the access point @@ -69,7 +69,7 @@ A useful function for connecting to your local WiFi network is:: def do_connect(): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') diff --git a/docs/esp8266/tutorial/network_basics.rst b/docs/esp8266/tutorial/network_basics.rst index 9d74a6283ac..e383c00c6ce 100644 --- a/docs/esp8266/tutorial/network_basics.rst +++ b/docs/esp8266/tutorial/network_basics.rst @@ -1,14 +1,15 @@ Network basics ============== -The network module is used to configure the WiFi connection. There are two WiFi -interfaces, one for the station (when the ESP8266 connects to a router) and one -for the access point (for other devices to connect to the ESP8266). Create +The :class:`network.WLAN` class in the :mod:`network` module is used to +configure the WiFi connection. There are two WiFi interfaces, one for +the station (when the ESP8266 connects to a router) and one for the +access point (for other devices to connect to the ESP8266). Create instances of these objects using:: >>> import network - >>> sta_if = network.WLAN(network.STA_IF) - >>> ap_if = network.WLAN(network.AP_IF) + >>> sta_if = network.WLAN(network.WLAN.IF_STA) + >>> ap_if = network.WLAN(network.WLAN.IF_AP) You can check if the interfaces are active by:: @@ -57,7 +58,7 @@ connect to your WiFi network:: def do_connect(): import network - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) if not sta_if.isconnected(): print('connecting to network...') sta_if.active(True) diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index f0b592dffc8..1bcc9d7623d 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -56,7 +56,7 @@ A simple example would be: import espnow # A WLAN interface must be active to send()/recv() - sta = network.WLAN(network.STA_IF) # Or network.AP_IF + sta = network.WLAN(network.WLAN.IF_STA) # Or network.WLAN.IF_AP sta.active(True) sta.disconnect() # For ESP8266 @@ -76,7 +76,7 @@ A simple example would be: import espnow # A WLAN interface must be active to send()/recv() - sta = network.WLAN(network.STA_IF) + sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # Because ESP8266 auto-connects to last Access Point @@ -182,14 +182,14 @@ Configuration Sending and Receiving Data -------------------------- -A wifi interface (``network.STA_IF`` or ``network.AP_IF``) must be +A wifi interface (``network.WLAN.IF_STA`` or ``network.WLAN.IF_AP``) must be `active()` before messages can be sent or received, but it is not necessary to connect or configure the WLAN interface. For example:: import network - sta = network.WLAN(network.STA_IF) + sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # For ESP8266 @@ -445,8 +445,8 @@ must first register the sender and use the same encryption keys as the sender - *ifidx*: (ESP32 only) Index of the wifi interface which will be used to send data to this peer. Must be an integer set to - ``network.STA_IF`` (=0) or ``network.AP_IF`` (=1). - (default=0/``network.STA_IF``). See `ESPNow and Wifi Operation`_ + ``network.WLAN.IF_STA`` (=0) or ``network.WLAN.IF_AP`` (=1). + (default=0/``network.WLAN.IF_STA``). See `ESPNow and Wifi Operation`_ below for more information. - *encrypt*: (ESP32 only) If set to ``True`` data exchanged with @@ -588,7 +588,7 @@ api-reference/network/esp_now.html#api-reference>`_. For example:: elif err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND': e.add_peer(peer) elif err.args[1] == 'ESP_ERR_ESPNOW_IF': - network.WLAN(network.STA_IF).active(True) + network.WLAN(network.WLAN.IF_STA).active(True) else: raise err @@ -645,7 +645,7 @@ A small async server example:: import asyncio # A WLAN interface must be active to send()/recv() - network.WLAN(network.STA_IF).active(True) + network.WLAN(network.WLAN.IF_STA).active(True) e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support e.active(True) @@ -747,8 +747,8 @@ ESPNow and Wifi Operation ------------------------- ESPNow messages may be sent and received on any `active()` -`WLAN` interface (``network.STA_IF`` or ``network.AP_IF``), even -if that interface is also connected to a wifi network or configured as an access +`WLAN` interface (``network.WLAN.IF_STA`` or ``network.WLAN.IF_AP``), +even if that interface is also connected to a wifi network or configured as an access point. When an ESP32 or ESP8266 device connects to a Wifi Access Point (see `ESP32 Quickref <../esp32/quickref.html#networking>`__) the following things happen which affect ESPNow communications: @@ -832,8 +832,8 @@ Other issues to take care with when using ESPNow with wifi are: import network, time def wifi_reset(): # Reset wifi to AP_IF off, STA_IF on and disconnected - sta = network.WLAN(network.STA_IF); sta.active(False) - ap = network.WLAN(network.AP_IF); ap.active(False) + sta = network.WLAN(network.WLAN.IF_STA); sta.active(False) + ap = network.WLAN(network.WLAN.IF_AP); ap.active(False) sta.active(True) while not sta.active(): time.sleep(0.1) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index c1eb520961f..3c401acb420 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -8,7 +8,7 @@ This class provides a driver for WiFi network processors. Example usage:: import network # enable station interface and connect to WiFi access point - nic = network.WLAN(network.STA_IF) + nic = network.WLAN(network.WLAN.IF_STA) nic.active(True) nic.connect('your-ssid', 'your-key') # now use sockets as usual @@ -18,8 +18,8 @@ Constructors .. class:: WLAN(interface_id) Create a WLAN network interface object. Supported interfaces are -``network.STA_IF`` (station aka client, connects to upstream WiFi access -points) and ``network.AP_IF`` (access point, allows other WiFi clients to +``network.WLAN.IF_STA`` (station aka client, connects to upstream WiFi access +points) and ``network.WLAN.IF_AP`` (access point, allows other WiFi clients to connect). Availability of the methods below depends on interface type. For example, only STA interface may `WLAN.connect()` to an access point. @@ -75,7 +75,7 @@ Methods Return the current status of the wireless connection. When called with no argument the return value describes the network link status. - The possible statuses are defined as constants: + The possible statuses are defined as constants in the :mod:`network` module: * ``STAT_IDLE`` -- no connection and no activity, * ``STAT_CONNECTING`` -- connecting in progress, diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index 4d86f0080b1..b47ec76c98e 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -477,7 +477,7 @@ An example ``config.py`` might look like: """,], # Print out nearby WiFi networks. "wl_ipconfig": [ "exec", - "import network; sta_if = network.WLAN(network.STA_IF); print(sta_if.ipconfig('addr4'))", + "import network; sta_if = network.WLAN(network.WLAN.IF_STA); print(sta_if.ipconfig('addr4'))", """,], # Print ip address of station interface. "test": ["mount", ".", "exec", "import test"], # Mount current directory and run test.py. "demo": ["run", "path/to/demo.py"], # Execute demo.py on the device. From 285e1d0b80d821ba910249e8804697ec1739bac7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:18:37 +1100 Subject: [PATCH 0023/2098] esp32: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the esp32 port This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/README.md | 2 +- ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py | 2 +- ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py | 2 +- ports/esp32/help.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index a04ad0c6eea..4eb79138938 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -195,7 +195,7 @@ quickly call `wlan_connect()` and it just works): ```python def wlan_connect(ssid='MYSSID', password='MYPASS'): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) if not wlan.active() or not wlan.isconnected(): wlan.active(True) print('connecting to:', ssid) diff --git a/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py b/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py index bfe02c35765..98ea0adcbf8 100644 --- a/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py +++ b/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py @@ -30,7 +30,7 @@ def display_wifi(self): self.text("Scan...", 0, 0, 1) self.show() - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) sta_if.active(True) _wifi = sta_if.scan() diff --git a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py index 37dc5a340fe..58120d44adf 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py +++ b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py @@ -37,7 +37,7 @@ def display_wifi(self): self.text("Scan...", 0, 0, 1) self.show() - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) sta_if.active(True) _wifi = sta_if.scan() diff --git a/ports/esp32/help.c b/ports/esp32/help.c index 2351d7dc739..62639a0b408 100644 --- a/ports/esp32/help.c +++ b/ports/esp32/help.c @@ -48,7 +48,7 @@ const char esp32_help_text[] = "Basic WiFi configuration:\n" "\n" "import network\n" - "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if = network.WLAN(network.WLAN.IF_STA); sta_if.active(True)\n" "sta_if.scan() # Scan for available access points\n" "sta_if.connect(\"\", \"\") # Connect to an AP\n" "sta_if.isconnected() # Check for successful connection\n" From f5b81bee6141f925ed32d2d7968b0c90d7b41b61 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:19:00 +1100 Subject: [PATCH 0024/2098] esp8266: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the esp8266 port This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp8266/help.c | 4 ++-- ports/esp8266/modules/inisetup.py | 2 +- ports/esp8266/modules/port_diag.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/esp8266/help.c b/ports/esp8266/help.c index a9cb27bad51..694cd90b951 100644 --- a/ports/esp8266/help.c +++ b/ports/esp8266/help.c @@ -36,12 +36,12 @@ const char esp_help_text[] = "Basic WiFi configuration:\n" "\n" "import network\n" - "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if = network.WLAN(network.WLAN.IF_STA); sta_if.active(True)\n" "sta_if.scan() # Scan for available access points\n" "sta_if.connect(\"\", \"\") # Connect to an AP\n" "sta_if.isconnected() # Check for successful connection\n" "# Change name/password of ESP8266's AP:\n" - "ap_if = network.WLAN(network.AP_IF)\n" + "ap_if = network.WLAN(network.WLAN.IF_AP)\n" "ap_if.config(ssid=\"\", security=network.AUTH_WPA_WPA2_PSK, key=\"\")\n" "\n" "Control commands:\n" diff --git a/ports/esp8266/modules/inisetup.py b/ports/esp8266/modules/inisetup.py index 20bb28e80b0..f97c2385200 100644 --- a/ports/esp8266/modules/inisetup.py +++ b/ports/esp8266/modules/inisetup.py @@ -6,7 +6,7 @@ def wifi(): import binascii - ap_if = network.WLAN(network.AP_IF) + ap_if = network.WLAN(network.WLAN.IF_AP) ssid = b"MicroPython-%s" % binascii.hexlify(ap_if.config("mac")[-3:]) ap_if.config(ssid=ssid, security=network.AUTH_WPA_WPA2_PSK, key=b"micropythoN") diff --git a/ports/esp8266/modules/port_diag.py b/ports/esp8266/modules/port_diag.py index 4eea6a6d90d..bbd6225a616 100644 --- a/ports/esp8266/modules/port_diag.py +++ b/ports/esp8266/modules/port_diag.py @@ -23,8 +23,8 @@ def main(): print(esp.check_fw()) print("\nNetworking:") - print("STA ifconfig:", network.WLAN(network.STA_IF).ifconfig()) - print("AP ifconfig:", network.WLAN(network.AP_IF).ifconfig()) + print("STA ifconfig:", network.WLAN(network.WLAN.IF_STA).ifconfig()) + print("AP ifconfig:", network.WLAN(network.WLAN.IF_AP).ifconfig()) print("Free WiFi driver buffers of type:") for i, comm in enumerate( ("1,2 TX", "4 Mngmt TX(len: 0x41-0x100)", "5 Mngmt TX (len: 0-0x40)", "7", "8 RX") From 5dfbd4371480c175e6f54e57e4e5eb5d579cbd00 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:19:19 +1100 Subject: [PATCH 0025/2098] tests: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from unit tests. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/multi_espnow/10_simple_data.py | 2 +- tests/multi_espnow/20_send_echo.py | 2 +- tests/multi_espnow/30_lmk_echo.py | 2 +- tests/multi_espnow/40_recv_test.py | 2 +- tests/multi_espnow/50_esp32_rssi_test.py | 2 +- tests/multi_espnow/60_irq_test.py | 2 +- tests/multi_espnow/80_asyncio_client.py | 2 +- tests/multi_espnow/81_asyncio_server.py | 2 +- tests/multi_espnow/90_memory_test.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/multi_espnow/10_simple_data.py b/tests/multi_espnow/10_simple_data.py index 1d218fe9813..00d69f30d91 100644 --- a/tests/multi_espnow/10_simple_data.py +++ b/tests/multi_espnow/10_simple_data.py @@ -14,7 +14,7 @@ def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/20_send_echo.py b/tests/multi_espnow/20_send_echo.py index 4a1d1624d84..4c325bf68c5 100644 --- a/tests/multi_espnow/20_send_echo.py +++ b/tests/multi_espnow/20_send_echo.py @@ -61,7 +61,7 @@ def echo_client(e, peer, msglens): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/30_lmk_echo.py b/tests/multi_espnow/30_lmk_echo.py index ac890804925..2a6c77c6331 100644 --- a/tests/multi_espnow/30_lmk_echo.py +++ b/tests/multi_espnow/30_lmk_echo.py @@ -92,7 +92,7 @@ def echo_client(e, peer, msglens): # Initialise the wifi and espnow hardware and software def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/40_recv_test.py b/tests/multi_espnow/40_recv_test.py index 46f4f78df48..554db16ac17 100644 --- a/tests/multi_espnow/40_recv_test.py +++ b/tests/multi_espnow/40_recv_test.py @@ -48,7 +48,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/50_esp32_rssi_test.py b/tests/multi_espnow/50_esp32_rssi_test.py index 6a47b540d35..8aded1c0994 100644 --- a/tests/multi_espnow/50_esp32_rssi_test.py +++ b/tests/multi_espnow/50_esp32_rssi_test.py @@ -49,7 +49,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/60_irq_test.py b/tests/multi_espnow/60_irq_test.py index 37fc57ce4ae..db8b8168690 100644 --- a/tests/multi_espnow/60_irq_test.py +++ b/tests/multi_espnow/60_irq_test.py @@ -49,7 +49,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/80_asyncio_client.py b/tests/multi_espnow/80_asyncio_client.py index 4d58a451c1c..8c34b98a3c4 100644 --- a/tests/multi_espnow/80_asyncio_client.py +++ b/tests/multi_espnow/80_asyncio_client.py @@ -50,7 +50,7 @@ def client_send(e, peer, msg, sync): def init(e, sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e.active(True) e.set_pmk(default_pmk) wlans[0].active(sta_active) diff --git a/tests/multi_espnow/81_asyncio_server.py b/tests/multi_espnow/81_asyncio_server.py index df1dbb2ac8a..e6002582c08 100644 --- a/tests/multi_espnow/81_asyncio_server.py +++ b/tests/multi_espnow/81_asyncio_server.py @@ -30,7 +30,7 @@ def client_send(e, peer, msg, sync): def init(e, sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e.active(True) e.set_pmk(default_pmk) wlans[0].active(sta_active) diff --git a/tests/multi_espnow/90_memory_test.py b/tests/multi_espnow/90_memory_test.py index 5e80eb0fdf6..b59ff61b594 100644 --- a/tests/multi_espnow/90_memory_test.py +++ b/tests/multi_espnow/90_memory_test.py @@ -65,7 +65,7 @@ def echo_client(e, peer, msglens): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) From 3844733d604d83fbfec5592cee20aab065ebff48 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 2 Nov 2024 13:43:35 -0500 Subject: [PATCH 0026/2098] tests/cpydiff: Fix test case for modules_json_nonserializable. The test case was producing the following error: Traceback (most recent call last): File "", line 12, in UnicodeError: which did not demonstrate the intended difference (this particular non-json-serializable object DID throw an exception! just not TypeError). The updated test uses a byte string with all ASCII bytes inside, which better illustrates the diference. Signed-off-by: Jeff Epler --- tests/cpydiff/modules_json_nonserializable.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/cpydiff/modules_json_nonserializable.py b/tests/cpydiff/modules_json_nonserializable.py index ffe523786f5..d6a5660cadf 100644 --- a/tests/cpydiff/modules_json_nonserializable.py +++ b/tests/cpydiff/modules_json_nonserializable.py @@ -6,10 +6,7 @@ """ import json -a = bytes(x for x in range(256)) try: - z = json.dumps(a) - x = json.loads(z) - print("Should not get here") + print(json.dumps(b"shouldn't be able to serialise bytes")) except TypeError: print("TypeError") From eab2869990b83dc6baa451f8c4983a312539a0be Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 11:18:42 +1100 Subject: [PATCH 0027/2098] extmod/modlwip: Don't allow writing to a TCP socket that is connecting. This follows the behaviour of unix MicroPython (POSIX sockets) and the esp32 port. Signed-off-by: Damien George --- extmod/modlwip.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 0d38bf41b20..ce70627c31c 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -701,7 +701,12 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui MICROPY_PY_LWIP_ENTER - u16_t available = tcp_sndbuf(socket->pcb.tcp); + // If the socket is still connecting then don't let data be written to it. + // Otherwise, get the number of available bytes in the output buffer. + u16_t available = 0; + if (socket->state != STATE_CONNECTING) { + available = tcp_sndbuf(socket->pcb.tcp); + } if (available == 0) { // Non-blocking socket @@ -718,7 +723,8 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // If peer fully closed socket, we would have socket->state set to ERR_RST (connection // reset) by error callback. // Avoid sending too small packets, so wait until at least 16 bytes available - while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) { + while (socket->state == STATE_CONNECTING + || (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16)) { MICROPY_PY_LWIP_EXIT if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; @@ -1548,7 +1554,7 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ // raw socket is writable ret |= MP_STREAM_POLL_WR; #endif - } else if (socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) { + } else if (socket->state != STATE_CONNECTING && socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) { // TCP socket is writable // Note: pcb.tcp==NULL if state<0, and in this case we can't call tcp_sndbuf ret |= MP_STREAM_POLL_WR; From 69023622ee837df051ef7e606c2f0511015d01f7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 11:19:45 +1100 Subject: [PATCH 0028/2098] tests/net_hosted: Improve and simplify non-block-xfer test. CPython changed its non-blocking socket behaviour recently and this test would not run under CPython anymore. So the following steps were taken to get the test working again and then simplify it: - Run the test against CPython 3.10.10 and capture the output into the .exp file for the test. - Run this test on unix port of MicroPython and verify that the output matches the CPython 3.10.10 output in the new .exp file (it did). From now on take unix MicroPython as the source of truth for this test when modifying it. - Remove all code that was there for CPython compatibility. - Make it print out more useful information during the test run, including names of the OSError errno values. - Add polling of the socket before the send/write/recv/read to verify that the poll gives the correct result in non-blocking mode. Tested on unix MicroPython, ESP32_GENERIC, PYBD_SF2 and RPI_PICO_W boards. Signed-off-by: Damien George --- tests/net_hosted/connect_nonblock_xfer.py | 130 +++++++----------- tests/net_hosted/connect_nonblock_xfer.py.exp | 44 ++++++ 2 files changed, 94 insertions(+), 80 deletions(-) create mode 100644 tests/net_hosted/connect_nonblock_xfer.py.exp diff --git a/tests/net_hosted/connect_nonblock_xfer.py b/tests/net_hosted/connect_nonblock_xfer.py index dc4693cea6a..23620908afb 100644 --- a/tests/net_hosted/connect_nonblock_xfer.py +++ b/tests/net_hosted/connect_nonblock_xfer.py @@ -1,15 +1,24 @@ # test that socket.connect() on a non-blocking socket raises EINPROGRESS # and that an immediate write/send/read/recv does the right thing -import sys, time, socket, errno, ssl +import errno +import select +import socket +import ssl -isMP = sys.implementation.name == "micropython" +# only mbedTLS supports non-blocking mode +if not hasattr(ssl, "MBEDTLS_VERSION"): + print("SKIP") + raise SystemExit -def dp(e): - # uncomment next line for development and testing, to print the actual exceptions - # print(repr(e)) - pass +# get the name of an errno error code +def errno_name(er): + if er == errno.EAGAIN: + return "EAGAIN" + if er == errno.EINPROGRESS: + return "EINPROGRESS" + return er # do_connect establishes the socket and wraps it if tls is True. @@ -22,112 +31,75 @@ def do_connect(peer_addr, tls, handshake): # print("Connecting to", peer_addr) s.connect(peer_addr) except OSError as er: - print("connect:", er.errno == errno.EINPROGRESS) - if er.errno != errno.EINPROGRESS: - print(" got", er.errno) + print("connect:", errno_name(er.errno)) # wrap with ssl/tls if desired if tls: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - if hasattr(ssl_context, "check_hostname"): - ssl_context.check_hostname = False - try: s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake) - print("wrap: True") + print("wrap ok: True") except Exception as e: - dp(e) - print("wrap:", e) - elif handshake: - # just sleep a little bit, this allows any connect() errors to happen - time.sleep(0.2) + print("wrap er:", e) return s +# poll a socket and print out the result +def poll(s): + poller = select.poll() + poller.register(s) + print("poll: ", poller.poll(0)) + + # test runs the test against a specific peer address. -def test(peer_addr, tls=False, handshake=False): - # MicroPython plain sockets have read/write, but CPython's don't - # MicroPython TLS sockets and CPython's have read/write - # hasRW captures this wonderful state of affairs - hasRW = isMP or tls +def test(peer_addr, tls, handshake): + # MicroPython plain and TLS sockets have read/write + hasRW = True - # MicroPython plain sockets and CPython's have send/recv - # MicroPython TLS sockets don't have send/recv, but CPython's do - # hasSR captures this wonderful state of affairs - hasSR = not (isMP and tls) + # MicroPython plain sockets have send/recv + # MicroPython TLS sockets don't have send/recv + hasSR = not tls # connect + send + # non-blocking send should raise EAGAIN if hasSR: s = do_connect(peer_addr, tls, handshake) - # send -> 4 or EAGAIN + poll(s) try: ret = s.send(b"1234") - print("send:", handshake and ret == 4) + print("send ok:", ret) # shouldn't get here except OSError as er: - # - dp(er) - print("send:", er.errno in (errno.EAGAIN, errno.EINPROGRESS)) + print("send er:", errno_name(er.errno)) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("send:", True) # connect + write + # non-blocking write should return None if hasRW: s = do_connect(peer_addr, tls, handshake) - # write -> None - try: - ret = s.write(b"1234") - print("write:", ret in (4, None)) # SSL may accept 4 into buffer - except OSError as er: - dp(er) - print("write:", False) # should not raise - except ValueError as er: # CPython - dp(er) - print("write:", er.args[0] == "Write on closed or unwrapped SSL socket.") + poll(s) + ret = s.write(b"1234") + print("write: ", ret) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("write:", True) + # connect + recv + # non-blocking recv should raise EAGAIN if hasSR: - # connect + recv s = do_connect(peer_addr, tls, handshake) - # recv -> EAGAIN + poll(s) try: - print("recv:", s.recv(10)) + ret = s.recv(10) + print("recv ok:", ret) # shouldn't get here except OSError as er: - dp(er) - print("recv:", er.errno == errno.EAGAIN) + print("recv er:", errno_name(er.errno)) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("recv:", True) # connect + read + # non-blocking read should return None if hasRW: s = do_connect(peer_addr, tls, handshake) - # read -> None - try: - ret = s.read(10) - print("read:", ret is None) - except OSError as er: - dp(er) - print("read:", False) # should not raise - except ValueError as er: # CPython - dp(er) - print("read:", er.args[0] == "Read on closed or unwrapped SSL socket.") + poll(s) + ret = s.read(10) + print("read: ", ret) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("read:", True) if __name__ == "__main__": @@ -136,10 +108,8 @@ def test(peer_addr, tls=False, handshake=False): print("--- Plain sockets to nowhere ---") test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) print("--- SSL sockets to nowhere ---") - # this test fails with AXTLS because do_handshake=False blocks on first read/write and - # there it times out until the connect is aborted test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False) print("--- Plain sockets ---") - test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, True) + test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, False) print("--- SSL sockets ---") test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) diff --git a/tests/net_hosted/connect_nonblock_xfer.py.exp b/tests/net_hosted/connect_nonblock_xfer.py.exp new file mode 100644 index 00000000000..c5498a03873 --- /dev/null +++ b/tests/net_hosted/connect_nonblock_xfer.py.exp @@ -0,0 +1,44 @@ +--- Plain sockets to nowhere --- +connect: EINPROGRESS +poll: [] +send er: EAGAIN +connect: EINPROGRESS +poll: [] +write: None +connect: EINPROGRESS +poll: [] +recv er: EAGAIN +connect: EINPROGRESS +poll: [] +read: None +--- SSL sockets to nowhere --- +connect: EINPROGRESS +wrap ok: True +poll: [] +write: None +connect: EINPROGRESS +wrap ok: True +poll: [] +read: None +--- Plain sockets --- +connect: EINPROGRESS +poll: [] +send er: EAGAIN +connect: EINPROGRESS +poll: [] +write: None +connect: EINPROGRESS +poll: [] +recv er: EAGAIN +connect: EINPROGRESS +poll: [] +read: None +--- SSL sockets --- +connect: EINPROGRESS +wrap ok: True +poll: [(, 4)] +write: 4 +connect: EINPROGRESS +wrap ok: True +poll: [(, 4)] +read: None From 76e6c6345cbaa694ac8f33cfc8b8daaa11fd58ed Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:43:04 +1100 Subject: [PATCH 0029/2098] tools/mpremote: Make sure stdout and stderr output appear in order. mpremote error messages now go to stderr, so make sure stdout is flushed before printing them. Also update the test runner to capture error messages. Signed-off-by: Damien George --- tools/mpremote/mpremote/main.py | 3 +++ tools/mpremote/tests/run-mpremote-tests.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index 8c0a6cd224c..e6e397020fe 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -547,7 +547,10 @@ def main(): return 0 except CommandError as e: + # Make sure existing stdout appears before the error message on stderr. + sys.stdout.flush() print(f"{_PROG}: {e}", file=sys.stderr) + sys.stderr.flush() return 1 finally: do_disconnect(state) diff --git a/tools/mpremote/tests/run-mpremote-tests.sh b/tools/mpremote/tests/run-mpremote-tests.sh index 11d82c9bb38..b25b5fd7d29 100755 --- a/tools/mpremote/tests/run-mpremote-tests.sh +++ b/tools/mpremote/tests/run-mpremote-tests.sh @@ -16,7 +16,7 @@ for t in $TESTS; do TMP=$(mktemp -d) echo -n "${t}: " # Strip CR and replace the random temp dir with a token. - if env MPREMOTE=${MPREMOTE} TMP="${TMP}" "${t}" | tr -d '\r' | sed "s,${TMP},"'${TMP},g' > "${t}.out"; then + if env MPREMOTE=${MPREMOTE} TMP="${TMP}" "${t}" 2>&1 | tr -d '\r' | sed "s,${TMP},"'${TMP},g' > "${t}.out"; then if diff "${t}.out" "${t}.exp" > /dev/null; then echo "OK" else From 3b6024a699491720ac0ba48b8534d6e540e828a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:44:50 +1100 Subject: [PATCH 0030/2098] tools/mpremote: Add test for forced copy. Signed-off-by: Damien George --- tools/mpremote/tests/test_filesystem.sh | 5 +++++ tools/mpremote/tests/test_filesystem.sh.exp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index b6d5a7febcb..727efea90eb 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -66,6 +66,11 @@ $MPREMOTE resume cp "${TMP}/a.py" :aaa $MPREMOTE resume cp "${TMP}/a.py" :bbb/b.py $MPREMOTE resume cat :aaa/a.py bbb/b.py +# Test cp -f (force copy). +echo ----- +$MPREMOTE resume cp -f "${TMP}/a.py" :aaa +$MPREMOTE resume cat :aaa/a.py + echo ----- $MPREMOTE resume rm :b.py c.py $MPREMOTE resume ls diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 7220dc41e47..b252bc7eb0d 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -38,6 +38,10 @@ print("World") print("Hello") print("World") ----- +cp ${TMP}/a.py :aaa +print("Hello") +print("World") +----- rm :b.py rm :c.py ls : From 4fd5b72a8b71c6b3a074bcc3b9f6385b049c3dcb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:45:06 +1100 Subject: [PATCH 0031/2098] tools/mpremote: Support trailing slash on dest for non-recursive copy. This fixes a regression in db59e55fe7a0b67d3af868990468e7b8056afe42: prior to that commit `mpremote` supported trailing slashes on the destination of a normal (non-recursive) copy. Add back support for that, with the semantics that a trailing slash requires the destination to be an existing directory. Also add a test for this. Signed-off-by: Damien George --- tools/mpremote/mpremote/commands.py | 11 +++++++++-- tools/mpremote/tests/test_filesystem.sh | 5 +++++ tools/mpremote/tests/test_filesystem.sh.exp | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 2b1acea4380..f86befd0803 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -129,8 +129,15 @@ def _remote_path_basename(a): def do_filesystem_cp(state, src, dest, multiple, check_hash=False): if dest.startswith(":"): - dest_exists = state.transport.fs_exists(dest[1:]) - dest_isdir = dest_exists and state.transport.fs_isdir(dest[1:]) + dest_no_slash = dest.rstrip("/" + os.path.sep + (os.path.altsep or "")) + dest_exists = state.transport.fs_exists(dest_no_slash[1:]) + dest_isdir = dest_exists and state.transport.fs_isdir(dest_no_slash[1:]) + + # A trailing / on dest forces it to be a directory. + if dest != dest_no_slash: + if not dest_isdir: + raise CommandError("cp: destination is not a directory") + dest = dest_no_slash else: dest_exists = os.path.exists(dest) dest_isdir = dest_exists and os.path.isdir(dest) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index 727efea90eb..afeb7c91da8 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -71,6 +71,11 @@ echo ----- $MPREMOTE resume cp -f "${TMP}/a.py" :aaa $MPREMOTE resume cat :aaa/a.py +# Test cp where the destination has a trailing /. +echo ----- +$MPREMOTE resume cp "${TMP}/a.py" :aaa/ +$MPREMOTE resume cp "${TMP}/a.py" :aaa/a.py/ || echo "expect error" + echo ----- $MPREMOTE resume rm :b.py c.py $MPREMOTE resume ls diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index b252bc7eb0d..82fe7d6bf78 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -42,6 +42,12 @@ cp ${TMP}/a.py :aaa print("Hello") print("World") ----- +cp ${TMP}/a.py :aaa/ +Up to date: aaa/a.py +cp ${TMP}/a.py :aaa/a.py/ +mpremote: cp: destination is not a directory +expect error +----- rm :b.py rm :c.py ls : From 161e2bd37df70556ef2a276c53af2c0cdc79af5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 9 Aug 2023 10:31:29 +0200 Subject: [PATCH 0032/2098] extmod/network_ppp: Add stream config parameter. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the stream that the PPP object wraps, which is normally only set once via the constructor, accessible and configurable via the `ppp.config()` method. Signed-off-by: Daniël van de Giessen --- docs/library/network.PPP.rst | 7 +++++-- extmod/network_ppp_lwip.c | 11 ++++++++++- ports/esp32/network_ppp.c | 9 +++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/library/network.PPP.rst b/docs/library/network.PPP.rst index 85f580ce540..17a8d1c1cd8 100644 --- a/docs/library/network.PPP.rst +++ b/docs/library/network.PPP.rst @@ -70,8 +70,11 @@ Methods .. method:: PPP.config(config_parameters) - Sets or gets parameters of the PPP interface. There are currently no parameter that - can be set or retrieved. + Sets or gets parameters of the PPP interface. The only parameter that can be + retrieved and set is the underlying stream, using:: + + stream = PPP.config("stream") + PPP.config(stream=stream) .. method:: PPP.ipconfig('param') PPP.ipconfig(param=value, ...) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 2b77662a24f..72d02602c0c 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -153,12 +153,17 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t 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]); + 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)) { + case MP_QSTR_stream: { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + self->stream = kwargs->table[i].value; + break; + } default: break; } @@ -174,6 +179,10 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t mp_obj_t val = mp_const_none; switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index f2813f430d4..4f49efaf060 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -323,6 +323,11 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs 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)) { + case MP_QSTR_stream: { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + self->stream = kwargs->table[i].value; + break; + } default: break; } @@ -338,6 +343,10 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs mp_obj_t val = mp_const_none; switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } case MP_QSTR_ifname: { if (self->pcb != NULL) { struct netif *pppif = ppp_netif(self->pcb); From 77406b4240b0489963782a7f18920ba3c5d11263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 28 Feb 2024 12:35:37 +0100 Subject: [PATCH 0033/2098] extmod/network_ppp: Allow stream=None to suspend PPP. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the stream to be set to `None`, which essentially stops all PPP communication without disconnecting the session. This allows replacing the stream on-the-fly to suspend it, for example to send AT commands to a modem without completely disconnecting and re-establishing the PPP connection: uart = ppp.config('stream') ppp.config(stream=None) uart.write(b'+++') # do some AT commands uart.write(b'ATO\r\n') ppp.config(stream=uart) Any attempted communication by PPP while the stream is not connected will register as simple packet loss to the LwIP stack because we return 0 for any write calls, and protocols like TCP will then automatically handle retrying. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 68 ++++++++++++++++++++++++++++----------- ports/esp32/network_ppp.c | 29 +++++++++++++---- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 72d02602c0c..8eb90ea4a65 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -60,6 +60,18 @@ 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_stream_uart_irq_disable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } + + // 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); +} + static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { network_ppp_obj_t *self = ctx; switch (err_code) { @@ -68,12 +80,9 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { 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. + network_ppp_stream_uart_irq_disable(self); + // Indicate that we are no longer connected and thus + // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; } // Clean up the PPP PCB. @@ -91,7 +100,9 @@ static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, s mp_obj_t stream = all_args[0]; - mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (stream != mp_const_none) { + 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; @@ -105,7 +116,7 @@ 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. + // Still connected over the stream. // Force the connection to close, with nocarrier=1. self->state = STATE_INACTIVE; ppp_close(self->pcb, 1); @@ -127,10 +138,11 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } mp_int_t total_len = 0; - for (;;) { + mp_obj_t stream = self->stream; + while (stream != mp_const_none) { uint8_t buf[256]; int err; - mp_uint_t len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); + mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); if (len == 0) { break; } @@ -149,6 +161,19 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_poll_obj, 1, 2, network_ppp_poll); +static void network_ppp_stream_uart_irq_enable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } + + // 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); +} + 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")); @@ -160,8 +185,16 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { case MP_QSTR_stream: { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_disable(self); + } self->stream = kwargs->table[i].value; + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_enable(self); + } break; } default: @@ -210,10 +243,14 @@ static u32_t network_ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t l } mp_printf(&mp_plat_print, ")\n"); #endif + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; + } 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); + return mp_stream_rw(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) { @@ -236,12 +273,7 @@ static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_ } 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); + network_ppp_stream_uart_irq_enable(self); } if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 4f49efaf060..5c41cf948c1 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -85,7 +85,9 @@ static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { } static mp_obj_t ppp_make_new(mp_obj_t stream) { - mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (stream != mp_const_none) { + mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } ppp_if_obj_t *self = mp_obj_malloc_with_finaliser(ppp_if_obj_t, &ppp_if_type); self->stream = stream; @@ -100,8 +102,14 @@ MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) { ppp_if_obj_t *self = ctx; + + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; + } + int err; - return mp_stream_rw(self->stream, data, len, &err, MP_STREAM_RW_WRITE); + return mp_stream_rw(stream, data, len, &err, MP_STREAM_RW_WRITE); } static void pppos_client_task(void *self_in) { @@ -110,10 +118,15 @@ static void pppos_client_task(void *self_in) { int len = 0; while (ulTaskNotifyTake(pdTRUE, len <= 0) == 0) { - int err; - len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); - if (len > 0) { - pppos_input_tcpip(self->pcb, (u8_t *)buf, len); + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + len = 0; + } else { + int err; + len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); + if (len > 0) { + pppos_input_tcpip(self->pcb, (u8_t *)buf, len); + } } } @@ -324,7 +337,9 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { case MP_QSTR_stream: { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } self->stream = kwargs->table[i].value; break; } From 611d8f9ce82ab5e04fb86ab6cfc28d5ed98f33bf Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Nov 2024 12:04:57 +1100 Subject: [PATCH 0034/2098] esp32/modsocket: Fix getaddrinfo hints to set AI_CANONNAME. Because the `ai_canonname` field is subsequently used. ESP32_GENERIC_S3 (at least) crashes with IDF 5.2.3 without this set. Signed-off-by: Damien George --- ports/esp32/modsocket.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 916eb79bd92..7ea5e855d32 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -213,7 +213,7 @@ static int mdns_getaddrinfo(const char *host_str, const char *port_str, #endif // MICROPY_HW_ENABLE_MDNS_QUERIES static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, - const struct addrinfo *hints, struct addrinfo **res) { + struct addrinfo *hints, struct addrinfo **res) { int retval = 0; *res = NULL; @@ -235,6 +235,9 @@ static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, MP_THREAD_GIL_EXIT(); + // The ai_canonname field is used below, so set the hint. + hints->ai_flags |= AI_CANONNAME; + #if MICROPY_HW_ENABLE_MDNS_QUERIES retval = mdns_getaddrinfo(host_str, port_str, hints, res); #endif @@ -264,7 +267,8 @@ static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, static void _socket_getaddrinfo(const mp_obj_t addrtuple, struct addrinfo **resp) { mp_obj_t *elem; mp_obj_get_array_fixed_n(addrtuple, 2, &elem); - _getaddrinfo_inner(elem[0], elem[1], NULL, resp); + struct addrinfo hints = { 0 }; + _getaddrinfo_inner(elem[0], elem[1], &hints, resp); } static mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { From 5dc9eda1953668eb6861be01ca85f147dcf8d406 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 13 Nov 2024 14:21:29 +1100 Subject: [PATCH 0035/2098] extmod/vfs_blockdev: Support bool return from Python read/write blocks. Commit f4ab9d924790581989f2398fe30bbac5d680577f inadvertently broke some Python block devices, for example esp32 and stm32 SDCard classes. Those classes return a bool from their `readblocks` and `writeblocks` methods instead of an integer errno code. With that change, both `False` and `True` return values are now be interpreted as non-zero and hence the block device call fails. The fix in this commit is to allow a bool and explicitly convert `True` to 0 and `False` to `-MP_EIO`. Signed-off-by: Damien George --- extmod/vfs_blockdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index a7c14b76ee3..d43c96b08f3 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -58,6 +58,13 @@ static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t bloc if (ret == mp_const_none) { return 0; } else { + // Some block devices return a bool indicating success, so + // convert those to an errno integer code. + if (ret == mp_const_true) { + return 0; + } else if (ret == mp_const_false) { + return -MP_EIO; + } // Block device functions are expected to return 0 on success // and negative integer on errors. Check for positive integer // results as some callers (i.e. littlefs) will produce corrupt From 898407defbb6327fde379304846999033bf349c5 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 4 Nov 2024 16:04:56 +0100 Subject: [PATCH 0036/2098] ports: Make PWM duty_u16 have an upper value of 65535 across all ports. The following ports used 65536 as the upper value (100% duty cycle) and are changed in this commit to use 65535: esp8266, mimxrt, nrf, samd. Tested that output is high at `duty_u16(65535)` and low at `duty_u16(0)`. Also verified that at `duty_u16(32768)` the high and low pulse have the same length. Partially reverts #10850, commits 9c7ad68165bcd224c94ca6d8f172362cf8000d99 and 2ac643c15bec8c88ece0e944ce58f36d02dfd2dd. Signed-off-by: robert-hh --- docs/mimxrt/quickref.rst | 6 +++--- docs/samd/quickref.rst | 4 ++-- ports/esp8266/machine_pwm.c | 6 +++--- ports/mimxrt/hal/pwm_backport.c | 6 +++--- ports/mimxrt/hal/pwm_backport.h | 2 +- ports/nrf/modules/machine/pwm.c | 6 +++--- ports/samd/machine_pwm.c | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index cfd0605054a..34e0aa79f13 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -193,7 +193,7 @@ PWM Constructor - *freq* should be an integer which sets the frequency in Hz for the PWM cycle. The valid frequency range is 15 Hz resp. 18Hz resp. 24Hz up to > 1 MHz. - - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65536``. + - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65535``. The duty cycle of a X channel can only be changed, if the A and B channel of the respective submodule is not used. Otherwise the duty_16 value of the X channel is 32768 (50%). @@ -231,7 +231,7 @@ is created by dividing the pwm_clk signal by an integral factor, according to th f = pwm_clk / (2**n * m) -with n being in the range of 0..7, and m in the range of 2..65536. pmw_clk is 125Mhz +with n being in the range of 0..7, and m in the range of 2..65535. pmw_clk is 125Mhz for MIMXRT1010/1015/1020, 150 MHz for MIMXRT1050/1060/1064 and 160MHz for MIMXRT1170. The lowest frequency is pwm_clk/2**23 (15, 18, 20Hz). The highest frequency with U16 resolution is pwm_clk/2**16 (1907, 2288, 2441 Hz), the highest frequency @@ -255,7 +255,7 @@ Use the :ref:`machine.ADC ` class:: from machine import ADC adc = ADC(Pin('A2')) # create ADC object on ADC pin - adc.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v + adc.read_u16() # read value, 0-65535 across voltage range 0.0v - 3.3v The resolution of the ADC is 12 bit with 10 to 11 bit accuracy, irrespective of the value returned by read_u16(). If you need a higher resolution or better accuracy, use diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 25b5a8fc8ab..d57dc679083 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -215,7 +215,7 @@ PWM Constructor - *freq* should be an integer which sets the frequency in Hz for the PWM cycle. The valid frequency range is 1 Hz to 24 MHz. - - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65536``. + - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65535``. - *duty_ns* sets the pulse width in nanoseconds. The limitation for X channels apply as well. - *invert*\=True|False. Setting a bit inverts the respective output. @@ -246,7 +246,7 @@ Use the :ref:`machine.ADC ` class:: from machine import ADC adc0 = ADC(Pin('A0')) # create ADC object on ADC pin, average=16 - adc0.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v + adc0.read_u16() # read value, 0-65535 across voltage range 0.0v - 3.3v adc1 = ADC(Pin('A1'), average=1) # create ADC object on ADC pin, average=1 The resolution of the ADC is 12 bit with 12 bit accuracy, irrespective of the diff --git a/ports/esp8266/machine_pwm.c b/ports/esp8266/machine_pwm.c index 25a2d6898f7..0d5ed595428 100644 --- a/ports/esp8266/machine_pwm.c +++ b/ports/esp8266/machine_pwm.c @@ -80,7 +80,7 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, c pwm_set_duty(args[ARG_duty].u_int, self->channel); } if (args[ARG_duty_u16].u_int != -1) { - pwm_set_duty(args[ARG_duty_u16].u_int * 1000 / 65536, self->channel); + pwm_set_duty(args[ARG_duty_u16].u_int * 1000 / 65535, self->channel); } if (args[ARG_duty_ns].u_int != -1) { uint32_t freq = pwm_get_freq(0); @@ -164,13 +164,13 @@ static void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { set_active(self, true); - return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel) * 65536 / 1024); + return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel) * 65535 / 1024); } static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty) { set_active(self, false); self->duty_ns = -1; - pwm_set_duty(duty * 1024 / 65536, self->channel); + pwm_set_duty(duty * 1024 / 65535, self->channel); pwm_start(); } diff --git a/ports/mimxrt/hal/pwm_backport.c b/ports/mimxrt/hal/pwm_backport.c index 7732e0e8167..3df82649639 100644 --- a/ports/mimxrt/hal/pwm_backport.c +++ b/ports/mimxrt/hal/pwm_backport.c @@ -36,7 +36,7 @@ void PWM_UpdatePwmDutycycle_u16( // Setup the PWM dutycycle of channel A or B if (pwmSignal == kPWM_PwmA) { - if (dutyCycle >= 65536) { + if (dutyCycle >= PWM_FULL_SCALE) { base->SM[subModule].VAL2 = 0; base->SM[subModule].VAL3 = pulseCnt; } else { @@ -44,7 +44,7 @@ void PWM_UpdatePwmDutycycle_u16( base->SM[subModule].VAL3 = base->SM[subModule].VAL2 + pwmHighPulse; } } else { - if (dutyCycle >= 65536) { + if (dutyCycle >= PWM_FULL_SCALE) { base->SM[subModule].VAL4 = 0; base->SM[subModule].VAL5 = pulseCnt; } else { @@ -160,7 +160,7 @@ status_t QTMR_SetupPwm_u16(TMR_Type *base, qtmr_channel_selection_t channel, uin if (dutyCycleU16 == 0) { // Clear the output at the next compare reg |= (TMR_CTRL_LENGTH_MASK | TMR_CTRL_OUTMODE(kQTMR_ClearOnCompare)); - } else if (dutyCycleU16 >= 65536) { + } else if (dutyCycleU16 >= PWM_FULL_SCALE) { // Set the output at the next compare reg |= (TMR_CTRL_LENGTH_MASK | TMR_CTRL_OUTMODE(kQTMR_SetOnCompare)); } else { diff --git a/ports/mimxrt/hal/pwm_backport.h b/ports/mimxrt/hal/pwm_backport.h index 04899173e20..9c9f6811add 100644 --- a/ports/mimxrt/hal/pwm_backport.h +++ b/ports/mimxrt/hal/pwm_backport.h @@ -24,7 +24,7 @@ typedef struct _pwm_signal_param_u16 uint16_t deadtimeValue; // The deadtime value; only used if channel pair is operating in complementary mode } pwm_signal_param_u16_t; -#define PWM_FULL_SCALE (65536UL) +#define PWM_FULL_SCALE (65535UL) void PWM_UpdatePwmDutycycle_u16(PWM_Type *base, pwm_submodule_t subModule, pwm_channels_t pwmSignal, uint32_t dutyCycle, uint16_t center); diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 8145509c762..13d824e8667 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -285,7 +285,7 @@ static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); } else if (self->p_config->duty_mode[self->channel] == DUTY_U16) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65536); + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65535); } else { return MP_OBJ_NEW_SMALL_INT(-1); } @@ -301,7 +301,7 @@ static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { if (self->p_config->duty_mode[self->channel] == DUTY_U16) { return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); } else if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65536 / 100); + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65535 / 100); } else { return MP_OBJ_NEW_SMALL_INT(-1); } @@ -365,7 +365,7 @@ static void machine_hard_pwm_start(const machine_pwm_obj_t *self) { if (self->p_config->duty_mode[i] == DUTY_PERCENT) { pulse_width = ((period * self->p_config->duty[i]) / 100); } else if (self->p_config->duty_mode[i] == DUTY_U16) { - pulse_width = ((period * self->p_config->duty[i]) / 65536); + pulse_width = ((period * self->p_config->duty[i]) / 65535); } else if (self->p_config->duty_mode[i] == DUTY_NS) { pulse_width = (uint64_t)self->p_config->duty[i] * tick_freq / 1000000000ULL; } diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index b2a383c21cb..468e34a1653 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -54,7 +54,7 @@ typedef struct _machine_pwm_obj_t { #define PWM_CLK_READY (1) #define PWM_TCC_ENABLED (2) #define PWM_MASTER_CLK (get_peripheral_freq()) -#define PWM_FULL_SCALE (65536) +#define PWM_FULL_SCALE (65535) #define PWM_UPDATE_TIMEOUT (2000) #define VALUE_NOT_SET (-1) From e2532e0f725107becea3cf7e6d14727ab3abec7c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 6 Nov 2024 20:26:27 +0100 Subject: [PATCH 0037/2098] mimxrt/machine_pwm: Fix a few inconsistencies with PWM output. Changes in this commit: - When setting PWM parameters of a FLEXPWM AB channel twice within a PWM cycle, the second setting was ignored. Now the second setting persists. - With `duty_u16(0)` a FLEXPWM X channel was set to high impedance. Now it is set to low impedance with value 0 for `invert=False`, and 1 for `invert=True`. - The align parameter requires a duty rate and frequency to be set. Align will now be ignored if freq or duty are missing. Signed-off-by: robert-hh --- ports/mimxrt/hal/pwm_backport.c | 8 ++------ ports/mimxrt/machine_pwm.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/hal/pwm_backport.c b/ports/mimxrt/hal/pwm_backport.c index 3df82649639..7815fd1dff5 100644 --- a/ports/mimxrt/hal/pwm_backport.c +++ b/ports/mimxrt/hal/pwm_backport.c @@ -110,12 +110,8 @@ void PWM_SetupPwmx_u16(PWM_Type *base, pwm_submodule_t subModule, base->SM[subModule].OCTRL = (base->SM[subModule].OCTRL & ~PWM_OCTRL_POLX_MASK) | PWM_OCTRL_POLX(!invert); - // Switch the output on or off. - if (duty_cycle == 0) { - base->OUTEN &= ~(1U << subModule); - } else { - base->OUTEN |= (1U << subModule); - } + // Enable PWM output + base->OUTEN |= (1U << subModule); } #ifdef FSL_FEATURE_SOC_TMR_COUNT diff --git a/ports/mimxrt/machine_pwm.c b/ports/mimxrt/machine_pwm.c index f9ae5e22edc..b68521281ae 100644 --- a/ports/mimxrt/machine_pwm.c +++ b/ports/mimxrt/machine_pwm.c @@ -45,6 +45,8 @@ typedef struct _machine_pwm_obj_t { mp_obj_base_t base; PWM_Type *instance; + const machine_pin_obj_t *pwm_pin; + const machine_pin_af_obj_t *pwm_pin_af_obj; bool is_flexpwm; uint8_t complementary; uint8_t module; @@ -255,6 +257,8 @@ static void configure_flexpwm(machine_pwm_obj_t *self) { PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel2, kPWM_faultchannel_1, 0); } + // clear the load okay bit for the submodules in case there is a pending load + PWM_SetPwmLdok(self->instance, 1 << self->submodule, false); if (self->channel1 != kPWM_PwmX) { // Only for A/B channels // Initialize the channel parameters pwmSignal.pwmChannel = self->channel1; @@ -283,6 +287,17 @@ static void configure_flexpwm(machine_pwm_obj_t *self) { self->instance->SM[self->submodule].CTRL &= ~(PWM_CTRL_DBLEN_MASK | PWM_CTRL_SPLIT_MASK); } } else { + if (self->duty_u16 == 0) { + // For duty_u16 == 0 just set the output to GPIO mode + if (self->invert) { + mp_hal_pin_high(self->pwm_pin); + } else { + mp_hal_pin_low(self->pwm_pin); + } + IOMUXC_SetPinMux(self->pwm_pin->muxRegister, PIN_AF_MODE_ALT5, 0, 0, 0, 0U); + } else { + IOMUXC_SetPinMux(self->pwm_pin->muxRegister, self->pwm_pin_af_obj->af_mode, 0, 0, 0, 0U); + } PWM_SetupPwmx_u16(self->instance, self->submodule, self->freq, self->duty_u16, self->invert, pwmSourceClockInHz); if (self->xor) { @@ -408,12 +423,16 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, } self->center = center; } else { // Use alignment setting shortcut - if (args[ARG_align].u_int >= 0) { + uint32_t duty = self->duty_u16; + if (duty == VALUE_NOT_SET && self->duty_ns != VALUE_NOT_SET) { + duty = duty_ns_to_duty_u16(self->freq, self->duty_ns); + } + if (args[ARG_align].u_int >= 0 && duty != VALUE_NOT_SET && self->freq != VALUE_NOT_SET) { uint8_t align = args[ARG_align].u_int & 3; // limit to 0..3 if (align == PWM_BEGIN) { - self->center = self->duty_u16 / 2; + self->center = duty / 2; } else if (align == PWM_END) { - self->center = PWM_FULL_SCALE - self->duty_u16 / 2; + self->center = PWM_FULL_SCALE - duty / 2; } else { self->center = 32768; // Default value: mid. } @@ -515,6 +534,8 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Create and populate the PWM object. machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self->pwm_pin = pin1; + self->pwm_pin_af_obj = af_obj1; self->is_flexpwm = is_flexpwm; self->instance = af_obj1->instance; self->module = module; @@ -534,6 +555,8 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Initialize the Pin(s). CLOCK_EnableClock(kCLOCK_Iomuxc); // just in case it was not set yet + // Configure PWMX channels to pin output mode to be prepared for duty_u16 == 0. + mp_hal_pin_output(pin1); IOMUXC_SetPinMux(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, pin1->configRegister, 0U); IOMUXC_SetPinConfig(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, From 5a70850b30e457ee9c6ab12fa735246bc3a1f8aa Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 2 Sep 2024 16:24:46 +0200 Subject: [PATCH 0038/2098] samd/machine_uart: Add full support for 9-bit data. Prior to this commit, 9-bit UART data could be specified in the constructor and was transmitted, but the 9th bit was set to 0 when sending, and ignored when receiving. This commit completes 9-bit support in that the 9th bit is taken from the data. 9-bit data has to be provided with `uart.write()` and and read with `uart.read()` as two bytes for each transmitted item, low order byte first. The data length supplied with `uart.write()` and requested by `uart.read()` has to be even, which is checked. The size of the UART buffers will be transparently doubled to cater for 9-bit data. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 84 ++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index b0dc4c5768d..6e9a5538647 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -107,10 +107,17 @@ static const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, // take all bytes from the fifo and store them in the buffer static void uart_drain_rx_fifo(machine_uart_obj_t *self, Sercom *uart) { + uint8_t bits = self->bits; while (uart->USART.INTFLAG.bit.RXC != 0) { - if (ringbuf_free(&self->read_buffer) > 0) { - // get a byte from uart and put into the buffer - ringbuf_put(&(self->read_buffer), uart->USART.DATA.bit.DATA); + if (ringbuf_free(&self->read_buffer) >= (bits <= 8 ? 1 : 2)) { + // get a word from uart and put into the buffer + if (bits <= 8) { + ringbuf_put(&(self->read_buffer), uart->USART.DATA.bit.DATA); + } else { + uint16_t data = uart->USART.DATA.bit.DATA; + ringbuf_put(&(self->read_buffer), data); + ringbuf_put(&(self->read_buffer), data >> 8); + } } else { // if the buffer is full, disable the RX interrupt // allowing RTS to come up. It will be re-enabled by the next read @@ -150,7 +157,12 @@ void common_uart_irq_handler(int uart_id) { #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); + if (self->bits <= 8) { + uart->USART.DATA.bit.DATA = ringbuf_get(&self->write_buffer); + } else { + uart->USART.DATA.bit.DATA = + ringbuf_get(&self->write_buffer) | (ringbuf_get(&self->write_buffer) << 8); + } } else { #if MICROPY_PY_MACHINE_UART_IRQ // Set the TXIDLE flag @@ -274,6 +286,17 @@ void machine_uart_set_baudrate(mp_obj_t self_in, uint32_t baudrate) { 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); + size_t rxbuf_len = self->read_buffer.size - 1; + #if MICROPY_HW_UART_TXBUF + size_t txbuf_len = self->write_buffer.size - 1; + #endif + if (self->bits > 8) { + rxbuf_len /= 2; + #if MICROPY_HW_UART_TXBUF + txbuf_len /= 2; + #endif + } + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " "timeout=%u, timeout_char=%u, rxbuf=%d" #if MICROPY_HW_UART_TXBUF @@ -287,9 +310,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #endif ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], - self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1 + self->stop + 1, self->timeout, self->timeout_char, rxbuf_len #if MICROPY_HW_UART_TXBUF - , self->write_buffer.size - 1 + , txbuf_len #endif #if MICROPY_HW_UART_RTSCTS , self->rts != 0xff ? pin_find_by_id(self->rts)->name : MP_QSTR_None @@ -411,6 +434,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } } #endif + + // Double the buffer lengths for 9 bit transfer + if (self->bits > 8) { + rxbuf_len *= 2; + #if MICROPY_HW_UART_TXBUF + txbuf_len *= 2; + #endif + } // Initialise the UART peripheral if any arguments given, or it was not initialised previously. if (n_args > 0 || kw_args->used > 0 || self->new) { self->new = false; @@ -619,7 +650,12 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t uint64_t timeout_char = self->timeout_char; uint8_t *dest = buf_in; - for (size_t i = 0; i < size; i++) { + // Check that size is even for 9 bit transfers. + if ((self->bits >= 9) && (size & 1)) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } + for (size_t i = 0; i < size;) { // Wait for the first/next character while (ringbuf_avail(&self->read_buffer) == 0) { if (mp_hal_ticks_ms_64() > t) { // timed out @@ -633,6 +669,11 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t MICROPY_EVENT_POLL_HOOK } *dest++ = ringbuf_get(&(self->read_buffer)); + i++; + if (self->bits >= 9 && i < size) { + *dest++ = ringbuf_get(&(self->read_buffer)); + i++; + } t = mp_hal_ticks_ms_64() + timeout_char; // (Re-)Enable RXC interrupt if ((uart->USART.INTENSET.reg & SERCOM_USART_INTENSET_RXC) == 0) { @@ -647,12 +688,19 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ size_t i = 0; const uint8_t *src = buf_in; Sercom *uart = sercom_instance[self->id]; - uint64_t t = mp_hal_ticks_ms_64() + self->timeout; + uint8_t bits = self->bits; + // Check that size is even for 9 bit transfers. + if ((bits >= 9) && (size & 1)) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } + #if MICROPY_HW_UART_TXBUF #if MICROPY_PY_MACHINE_UART_IRQ // Prefill the FIFO to get rid of the initial IRQ_TXIDLE event + // Do not care for 9 Bit transfer here since the UART is not yet started. while (i < size && ringbuf_free(&(self->write_buffer)) > 0) { ringbuf_put(&(self->write_buffer), *src++); i++; @@ -672,8 +720,16 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ } MICROPY_EVENT_POLL_HOOK } - ringbuf_put(&(self->write_buffer), *src++); - i++; + if (bits >= 9) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + ringbuf_put(&(self->write_buffer), *src++); + ringbuf_put(&(self->write_buffer), *src++); + i += 2; + MICROPY_END_ATOMIC_SECTION(atomic_state); + } else { + ringbuf_put(&(self->write_buffer), *src++); + i += 1; + } uart->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; // kick off the IRQ } @@ -691,7 +747,13 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ } MICROPY_EVENT_POLL_HOOK } - uart->USART.DATA.bit.DATA = *src++; + if (self->bits > 8 && i < (size - 1)) { + uart->USART.DATA.bit.DATA = *(uint16_t *)src; + i++; + src += 2; + } else { + uart->USART.DATA.bit.DATA = *src++; + } i++; } #endif From 4d36ecf8a882e919b27c953c963e3577d9ae46aa Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 27 Sep 2024 09:03:53 +0200 Subject: [PATCH 0039/2098] samd/boards/SAMD21_XPLAINED_PRO: Add specific deploy instructions. Add instructions to install a bootloader to the board. The SAMD21 XPLAINED PRO board is shipped without a bootloader, which therefore has to be installed once before it can be used with MicroPython. Signed-off-by: robert-hh --- .../boards/SAMD21_XPLAINED_PRO/board.json | 2 +- .../deploy_xplained_pro.md | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json b/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json index c59ebc1b7b7..899e56e905a 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json @@ -1,6 +1,6 @@ { "deploy": [ - "../deploy.md" + "deploy_xplained_pro.md" ], "docs": "", "features": [ diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md b/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md new file mode 100644 index 00000000000..d95ab5061a3 --- /dev/null +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md @@ -0,0 +1,24 @@ +The SAMD21 Xplained Pro board is shipped without a bootloader. For use +with MicroPyhton a suitable bootloader has be be installed first. The +following procedure has been found to work and be simple: + +1. Get the bootloader from https://micropython.org/resources/firmware/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex. +2. Connect your board to the debug port. A drive with the name XPLAINED +shall appear. +3. Copy the Intel hex file of the bootloader to that drive. +4. Connect your board to the target port. A drive with the name SAMD21XPL should +appear. If not, push reset twice. Then it should appear. If that does not +happen, the bootloader was not properly installed or is not compatible. +5. Copy the MicroPython firmware, a .uf2 file, to the SAMD21 drive. When the SAMD21 +drive disappears, MicroPython is installed. + +From now on only steps 4 and 5 are needed to update MicroPython. You can use the +usual methods to invoke the bootloader, namely: + +- Push Reset twice. +- Call machine.bootloader(). +- Use the touch 1200 procedure by switching the USB baud rate to 1200 baud and back. + +At the above link above there are as well .uf2 versions of the bootloader +which one can install using steps 5. and 6. above once a .uf2 capable +bootloader is installed. From 85de67f55d2afb5c75388d639e51e3df7640b4a7 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 30 Sep 2024 09:37:13 +0200 Subject: [PATCH 0040/2098] samd/mboot: Provide a UF2 bootloader for SAMD21 Xplained Pro. A bootloader labelled for the SAMD21 XPLAINED PRO board. The only difference to a generic bootloader are the names and ID of the USB port and the label of the drive that is opened. Signed-off-by: robert-hh --- ...oader-xplained-pro-v3.16.0-15-gaa52b22.hex | 513 ++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex diff --git a/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex b/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex new file mode 100644 index 00000000000..df8d5afd19c --- /dev/null +++ b/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex @@ -0,0 +1,513 @@ +:10000000E02D002069020000650200006702000088 +:1000100000000000000000000000000000000000E0 +:100020000000000000000000000000006502000069 +:100030000000000000000000650200005D020000FA +:100040006502000065020000650200006502000014 +:100050006502000065020000650200006502000004 +:1000600065020000650200006502000065020000F4 +:1000700065020000650200006502000065020000E4 +:1000800065020000650200006502000065020000D4 +:1000900065020000650200006502000065020000C4 +:1000A00065020000650200006502000065020000B4 +:1000B00000000000074B1A7DD207FCD52022FF323A +:1000C0001A83054A4008D8611A801A7DD207FCD5E8 +:1000D0007047C0460040004102A5FFFF70B5802573 +:1000E0000400ED02AC4200D370BD20000134FFF7E4 +:1000F000E1FFFF34F6E7002330B59A4200D130BD6E +:100100009C000D5901330551F7E70000F0B580253B +:10011000104C114E6368114FAB436360002A00D14D +:10012000F0BD1300102A00D91023D21A2680257D95 +:10013000ED07FCD59B009C460023CD58C5500433E9 +:100140006345FAD1C018C9182780237DDB07FCD589 +:10015000E4E7C0460040004144A5FFFF04A5FFFFBF +:10016000802270B5002304000D005200E958E058C9 +:10017000814203D104339342F8D170BD2000FFF7D0 +:1001800099FF402229002000FFF7C0FFF5E700009B +:1001900010230249CA681A42FCD070470008004088 +:1001A0001E2270B52149224C4B6893431C3A1343DD +:1001B0004B60A284FFF7ECFF1E4B1B689B0E3F2B8E +:1001C00000D1203B00251C4A9B0213431B4AE262DC +:1001D000A362A584FFF7DCFF194BA384FFF7D8FFC8 +:1001E0000223A28C1343A384FFF7D2FF154B9D601B +:1001F0005A7852B2002AFBDB134A5A605A7852B23C +:10020000002AFBDBC021114A114B12485360036ADC +:1002100009061B021B0A0B4303620023936007338A +:10022000136030220C4B1A6070BDC0460040004184 +:100230000008004024608000FF01000080BB0A1C11 +:1002400024050000000C00400007030010E000E05F +:10025000E703000000ED00E00000002010B500F012 +:1002600007F910BDFEE7FEE72449254870B5814235 +:100270000AD0244BC41E0022A34203D303331A1A0C +:100280009208920001F058FC1F48204BC11E00222A +:10029000994203D803331A1A92089200002101F000 +:1002A00054FCFF221A4B032193431A4A0C259360F6 +:1002B000022208243026184B18485A62C3788B4310 +:1002C0001343C370C378AB432343C370144B987B71 +:1002D000B0430600202030439873987BA843044322 +:1002E0009C73987B884302439A7380230D4A51681C +:1002F0000B43536000F07AFEFEE7C0466C1D000021 +:1003000000000020D0010020D0010020DC0D0020E2 +:100310000000000000ED00E0FC70004100500041D2 +:100320000048004100400041F7B5134D01906B8833 +:10033000002B18D1082738001A0292B214B2A44632 +:1003400066465400A2B2002E02DA0C4A5440A2B211 +:10035000013880B20028F1D158002A52802201339E +:1003600052009342E7D1019A0B0A53405800285A91 +:100370000902484080B2FEBDD001002021100000DB +:10038000074B1B685843074B1A789523002A00D166 +:100390001233012243439A4200D370470132FAE7F5 +:1003A00000000020D003002070B505001C240020B0 +:1003B0000F260B00E3403340002A09D1002B07D160 +:1003C000002C0BD0002809D1043C231DF1D170BDB5 +:1003D000092B03DD37332B540130F5E73033FAE7CF +:1003E000054B064A1A60BFF34F8F054B054ADA608A +:1003F000BFF34F8FFEE7C046FC7F0020EF6926F079 +:1004000000ED00E00400FA0510B50B4A1368002B5C +:100410000ED10A4911600A490A6801320A60094985 +:100420000868002806D0824204D30B60FFF7D8FF8B +:10043000013B136010BDC046E4030020DC05000052 +:10044000E0030020D8030020054B064A1A60BFF3E2 +:100450004F8F054B054ADA60BFF34F8FFEE7C0466A +:10046000FC7F0020EF6916F000ED00E00400FA05C3 +:10047000012210B5194B1A491A701A4A1368013330 +:1004800013600A68002A0CD017481018834203D161 +:1004900080241648E4050460934201D100230B60D8 +:1004A00010BD1348DBB20278002B10D180210F4B16 +:1004B000C905196011000A39C9B20E4BF02902D9D9 +:1004C0001978494219701B78D2180270E8E79342F4 +:1004D000E6D18022084BD2051A60E1E7D003002064 +:1004E000DC030020D403002018FCFFFF98440041E7 +:1004F000050000200400002094440041074B084AF6 +:100500001B681168994207D2FA21C9005B18136071 +:100510008022044BD2051A607047C046D4030020E5 +:10052000DC03002094440041044B80221900D205D2 +:10053000883198330A601A607047C0460044004111 +:1005400070470000202370B5354A0F20D1690B4356 +:10055000D361012233490B7813430B70324B197866 +:100560008143197006211C78214319702F490C789A +:1005700022430A701A7802401A70602219780A43DE +:100580001A702B4A2B4B53805378DB09FCD1012284 +:10059000294B19780A431A709A78D207FCD41F2085 +:1005A00026490A68520B0240824200D105221C8D66 +:1005B000234D024092012C4022431A850A68920C76 +:1005C00002401F2A00D1023A1F24188D2240A04366 +:1005D000024307201A850A68D20D0240824200D1E8 +:1005E0000322198D02401748120301400A437F215C +:1005F0001A851A7814480A401A70042219780A4396 +:100600000C211A7058621A898A431A811A890B3987 +:100610008A431A8180220021520001F096FA70BDAF +:1006200000040040584400413C440041594400410A +:10063000000C004006400000005000412460800093 +:100640003FF8FFFFFF8FFFFF74060020F7B51C0087 +:10065000314B06005B6A0F0015000193002C05D199 +:10066000443454432D4BE418FFF7CEFE63782278D0 +:10067000934210D2D51ABD4200D93D00002E08D0B9 +:10068000211DC9182A00300001F056FA63785B1961 +:1006900063702800FEBD019BA1786A01D318002970 +:1006A00011D1211D196059681D48890B89035960B2 +:1006B0005968014059601B49551840212879014368 +:1006C00029710121A1701449002551180191164981 +:1006D0005218127AD207DCD55D68A278EDB2257087 +:1006E000022A04D12A001968201D01F025FABD4212 +:1006F00000D93D00002E0CD02A0030006570211D6D +:1007000001F01AFA0122019BFF331A720023A37031 +:10071000BFE76670F6E7C046005000413004002095 +:10072000FF3F00F000510041FF50004110B5002391 +:10073000FFF78CFF10BDF8B505000C0016001F0078 +:10074000002C00D1F8BD210028003B003200FFF74B +:100750007DFF241A2D18F3E7F8B50D001F49560147 +:100760004C6A3419002B1BD063695B005B08636122 +:1007700020616369AA049B0B920C9B031343636182 +:100780006369174A174813406361321802238021B6 +:10079000160013729171327A1A42FCD02800F8BD0B +:1007A000012163695B005B0F03339940A94209D8BB +:1007B000043A531E9A416369D1075B005A080A4301 +:1007C0006261D5E744275743074A0437BF18010041 +:1007D0002A00380001F0B0F93800C9E700500041A4 +:1007E000FF3F00F0FF5000413004002010B500230F +:1007F000FFF7B2FF10BD000010B5054A0B001188CD +:10080000994200D919000022FFF7F0FF10BDC04641 +:100810007407002007B500216B460A00D81D01703F +:10082000FFF7E4FF07BD0000F0B5B94C9BB0FFF740 +:10083000EBFDA38B08211A000A400392B54A0B4234 +:100840002ED080234020A183A372C024093151708F +:1008500090715371B04BB14D5968A40529402143A3 +:100860005960596929400C43AD495C6104311960F4 +:10087000AC49AD4C196159680C4080218902214373 +:1008800059605968890B8903596050710022A74B40 +:100890001A70A64B1878431E9841C0B21BB0F0BD29 +:1008A0001020137A0342F4D09D4D10726D899C483C +:1008B000AC46664680799E4D02909948984BC78811 +:1008C000C0792E80402501909B889548994E0089DB +:1008D0005571B34200D1B0E136DC81246400A342FB +:1008E00000D1BFE115DC822B00D190E106DC803B1A +:1008F000012B00D886E120239371CAE780214900AB +:100900008B42F8D00221FF318B42F4D1FFF782FFF6 +:10091000BFE7B02189008B4200D16DE10EDC0139C7 +:10092000FF398B42E7D0A23B8349FF3B0B42E2D128 +:10093000002308210893099308A8C4E0C021890076 +:100940008B42D8D07D49DFE77D488342DED000DD91 +:1009500096E07C498B4212DC01398B4200DBACE033 +:1009600079498B42E4D06031FF318B42C3D1FFF72C +:1009700051FF80235B423B43DBB2A37289E78821AE +:100980006A4809018B4200D195E080318B42B2D197 +:100990000770FFF73FFFA02303225B00E254C02251 +:1009A0005D4B5E49586C92050840104358644620E0 +:1009B000FF302554654830271864902040002754A4 +:1009C000586B8026084010435863922040002654FC +:1009D0005F481863B02040002554586F084058679E +:1009E000B220400026543C307D3E2654FC388446DC +:1009F0009C44604606680E4016430660A626FF36F5 +:100A0000A5551E005348A0363060C0267600A75575 +:100A10001F0094373E6880200E4016433E60C22679 +:100A20007600A0551E004C4F90363760E0264427D4 +:100A30007600A7550436A555A055C01984469C4498 +:100A4000604606680E4016430660D42084469C44E7 +:100A5000604606680E4016430660F0268020760049 +:100A6000A7550436A555A0551C00E4342068F4337E +:100A7000084010432060186801400A431A6008E7E4 +:100A80003648834200D122E10ADC35498B4200D14D +:100A90003CE7344934480B40834200D136E72AE72B +:100AA00032498B4200D131E731498B4200D022E7F5 +:100AB000002308A80380012105E080235B009F42FA +:100AC00004D112212B48FFF797FEE2E680239B001A +:100AD0009F4202D199212848F5E7019B032B78D149 +:100AE000029B032B00D906E708AC48220021200016 +:100AF00001F02BF8019B6370029B002B3ED1092271 +:100B000004332370A270E37020002178DBE7C04635 +:100B100000500041FF50004174060020FFFFFF8F8E +:100B2000E803002034040020FF3F00F02C040020E4 +:100B30007407002002030000FFFE00000103000014 +:100B40002109000081060000A1030000BC04002070 +:100B5000780400208805002044050020A121000021 +:100B600021200000FFFEFFFF210A000021220000DB +:100B7000A1FE0000981B00004400002004AE3200DB +:100B8000544B23CB23C200252F001B681360AB00FE +:100B9000514AF358D01919680122FFF705FC0135B5 +:100BA0003F18042DF3D100234B4AD3554B4B4C4AED +:100BB0009B799B009D58280000F0CFFF2300013057 +:100BC000400020702A7802330135002A9CD01A7028 +:100BD000F8E7019B0F2B02D13921424873E7019BB3 +:100BE000212B02D1092140486DE7019B222B00D027 +:100BF00081E621213D4866E7072800D07BE6AA214F +:100C00003B4860E7039B08A8022103805BE708429A +:100C100000D070E60F235022034008335B011042DE +:100C200006D0344A9B189B799B06DB0F08A8EBE79C +:100C3000304A9B189B79DB06F7E70F24030023401B +:100C4000204200D157E60140394300D053E6294AFB +:100C500008335B019B18020602D520225A7155E623 +:100C60001022FBE70F2403002340204200D142E67C +:100C70000140394300D03EE65B01020613D51E4910 +:100C80001C4A9A185B182021D879084200D13DE609 +:100C900059711B7A2B4200D138E613004022FF33F2 +:100CA0001A723E3ADAE710201349124A9A185B1872 +:100CB000D979014200D129E658711B7A01180B42FB +:100CC00000D123E61300FF3301221972C6E70B4857 +:100CD000F9E6C046881B000050060020E80300200B +:100CE000D01B000008000020E0000020AC1B00002A +:100CF000F400002000500041FF500041EC000020B3 +:100D000010B5FFF71FFC0022034B1A700223034AA1 +:100D100011780B43137010BD2C04002000500041CB +:100D200010B50C000122FFF761FD200010BD10B5C9 +:100D30000C000122FFF75AFD200010BD70B5040021 +:100D40000D00FFF771FD03000020834204D0022252 +:100D500029002000FFF7EAFC70BD70B505000C000B +:100D6000FFF762FD002807D000230222210028009F +:100D7000FFF7E1FC200070BD0400FBE730B5002365 +:100D80002025934200DB30BD0C78002C03D00131CC +:100D9000C4540133F5E72C00FAE70000F8B58022CF +:100DA0000C00050000212000920000F0CEFE002D76 +:100DB0000CD13E2220004A4900F0BEFEFF235522FE +:100DC0005B00E254474B9218E254F8BD7E2D23D8C5 +:100DD0006E1E3E2E01D9403D2E00002E12D08025E1 +:100DE0003302581C404EFF30ED001A1F591CAA4216 +:100DF00003D2B3420ED08BB223800B000234814267 +:100E0000F3D1E2E7F0230922FF212370601C00F0F8 +:100E10009CFEE4E7354BEFE7822D31D87F2DD4D10E +:100E20002F490B2220002B31FFF7A8FF8027282312 +:100E30002F4EE3727D3D3F03F0683B0020340028D5 +:100E400002D000F08AFE03001A0A237762771A0C98 +:100E50001B0EE3772B0A3100A277A5760B22E376EF +:100E60002000FFF78BFF67224D2301355242ADB2C0 +:100E700022746374227663761036052DDCD1A4E7E4 +:100E80002B00833B012B0CD8194A1B01D318DD68BA +:100E9000280000F062FE29000200200000F04CFE55 +:100EA00093E78023853D2902DB02994200D38CE73A +:100EB000104B114A2360114B20006360FE235B003E +:100EC000E2508023DB00A361802380229B01A3608A +:100ED0000B4B52006561E1602261E3612030DDE788 +:100EE000E01B0000FF01000003040000FFFF000002 +:100EF000881C00005546320A306FB10A57515D9E7A +:100F0000882BED6870B516001D000A682F4B0C0089 +:100F10009A4252D12E4B4A689A424ED1FE2252003A +:100F20002C4B8A589A4248D18B689A0403D52A4A96 +:100F3000C969914241D1DB0712D4802322695B0049 +:100F40009A420DD1E068C3B2002B09D1F822234B9D +:100F50009202C318934203D221002031FFF700F917 +:100F6000002D2BD0A369002B28D029681C4A8B4266 +:100F700006D0934201D8002901D001235B422B60A7 +:100F80006369934219D8072201211A409140DB0876 +:100F9000EB181A7AC8B2114204D1696802430131D0 +:100FA0001A7269606A682B689A4206D3002E04D1CF +:100FB0000C4B1B681E330C4A136070BD002EFCD115 +:100FC000084B1B682D33FF33F5E7C0465546320A00 +:100FD00057515D9E306FB10A882BED6800E0FFFF2E +:100FE00063040000E0030020D8030020F8B5624D40 +:100FF000AB68002B00D0FEE70221604B5A6B8A439E +:101000005A63DA681205FCD55D4A5A630222596BAD +:101010000A435A63DA689205FCD5DA685205FCD4B3 +:1010200002215A6B8A435A63DA681205FCD50822FA +:10103000596B0A435A630222596B0A4351495A6356 +:101040000B68013321D120224F4BFF32188B024312 +:101050001A8358684D4A02435A604D4ADA614D4A34 +:101060001A801A7DD207FCD54B4A1A801A7DD20706 +:10107000FCD54A4A0A604A4A4A4911604A4A1A80DB +:101080001A7DD207FCD5FFF7DFF90023474A13701A +:10109000D379DB09FCD1FFF747FA454B1E68454B76 +:1010A000F218F8239B029A4213D8434B434A19681B +:1010B000434C444B914232D11978434AC90702D478 +:1010C000216891422BD0414B22601B68404A323349 +:1010D0001360FFF765F8BFF35F8F62B6FFF710FE8E +:1010E0004020FFF72DFA80270A230126394DFF0102 +:1010F0002B70FFF799FB384C00280AD02378DAB21E +:10110000002B05D1324B38001A60FFF719FA2E7008 +:1011100026702378002B25D000F0D6FAFCE71B7848 +:10112000DB07DA0F002B0DD00023802223602B4B2E +:10113000D2051A6080239B011A6882F30888AB608D +:101140003047C6E72168264B994201D12260C0E7AB +:1011500021681D4A9142E7D0FA2023604000FFF742 +:101160000FF9E1E72378002BC3D1FF33C046013BE1 +:10117000002BFBD1BDE7C04600ED00E000080040B9 +:10118000040027000040800000400041800004006F +:101190000020400005A5FFFF44A5FFFFFAC7E0D8E7 +:1011A000044080005DFCFFFF06A5FFFF001000402B +:1011B0000420000000E0FFFFB42000007CB0EE87B8 +:1011C000FC7F002038040040EF6926F0E003002097 +:1011D000D80300200400002076070020944400413A +:1011E000EF6916F010B5054C12220021200000F026 +:1011F000ACFCF0232370E63BE37110BDB309002083 +:1012000010B5FFF7EFFF0022014B1A7310BDC04667 +:101210009E010020F7B580270400160000250191EB +:10122000BF00A368AB4200D8F7BD3900A278019B8C +:10123000E068FFF780FA80235B01E81801223300A1 +:10124000E168FFF75FFE0135EBE770B513000600BC +:101250000C00150000200A000121FFF7F7F9002318 +:10126000984206D02B0022001F213000FFF7EEF934 +:101270000123180070BD000010B504220D210248A2 +:10128000FFF7B4FA10BDC0469E01002010B50B4C0C +:101290000B4861792379A27909021943E379120491 +:1012A00011431B060B431A0A037142711A0C1B0EE1 +:1012B0008271C371FFF7E0FF10BDC04694090020A2 +:1012C0009E01002070B504220D00FFF78FFA154C27 +:1012D00085420FD0FFF786FF012304222373124BB0 +:1012E0009A700022DA701A715A719A711A735A73CD +:1012F000FFF7CCFFFFF784FF637A217A1B020B43D1 +:10130000A17AE27A0904120619431143491B0B0A18 +:10131000217263720B0C090EA372E172FFF7B6FF24 +:1013200070BDC0469E010020B309002070B5184C66 +:10133000142205000021200000F007FC154A002DB2 +:101340001FD021000823D07D08313F26527C324037 +:101350001C2A01D0B24205D10F4A0C330A80052263 +:10136000DBB2CA70191C834200D9011CC9B2002D1E +:101370000BD0023B9BB21B0223802000FFF7A2FF91 +:1013800070BD0423D07C211DDFE7013B2370F4E70F +:1013900078090020940900201C0A0000F0B5040020 +:1013A00000252A4B85B0597C03AAD170997C917095 +:1013B000D97C5170197D1170997D02AAD170DB7DA5 +:1013C000937053880193019BAB4205D8FFF718FF38 +:1013D000FFF75CFF05B0F0BDFFF726FA0028F9D053 +:1013E000039B1B4FEE18002C1FD039003000FFF775 +:1013F000D5FC8021042238008900FFF7F7F9154A4F +:101400000135507A137A917A00021843D37A09048D +:1014100008431B06104903435B18190A13725172E3 +:10142000190C1B0E9172D372CDE780212300380076 +:1014300005228900FFF77FF9220039003000074BB1 +:10144000FFF760FDFFF75AF8D9E7C046940900207E +:10145000780700209E01002000FEFFFFC809002041 +:101460007FB500F02FFB444E002205213000FFF72E +:10147000ECFE002849D0737A327AB57A1B021A43FF +:10148000F37A2D041B0615433C4C1D432B0A637253 +:101490002B0CA3722B0E2572E372F07B2F2827D81A +:1014A000192815D8032827D012282FD0002840D07B +:1014B000FFF798FE012305222373314B9A70002217 +:1014C000DA701A715A719A711A7320325A7332E0B3 +:1014D0001A381528ECD800F01BFB282CEBEB2CEB72 +:1014E000EBEBEB3FEB31EBEB39EB3DEBEBEBEB2CD1 +:1014F0005A28DDD1012019E0F37C191C122B00D9E8 +:1015000012211F48C9B2FFF7DDFE7FBD1D4C1E49E9 +:10151000200018220830FFF731FCF37C191C242B23 +:1015200000D924212000C9B2EDE70020FFF7FEFE1C +:10153000EBE7FFF765FEFFF7A9FEE6E78023134818 +:101540009B024360124B08210360DCE70120FFF798 +:1015500025FFDAE70020FAE70C260E49320001A841 +:1015600000F0EAFAB54204D9002326726372A3722E +:10157000E3720C2101A8C6E7940900209E01002017 +:10158000B3090020AC010020721B00008C09002070 +:1015900000003E7F231D000072B6BFF35F8F044B37 +:1015A000044A9A829A830022034B9A607047C0468D +:1015B00000500041FF03000000ED00E0F0B5C5B0B1 +:1015C0000DAF0400FFF7E8FF44220021380000F0CF +:1015D000BCFA982200211EA800F0B7FA02230F21BE +:1015E000BB70A37862780B40A370264B0A4005932A +:1015F000636805AD06930023E16807932248627093 +:101600002B7301221EABFFF77DFC390020001EAAC0 +:10161000FFF700FE280001230D216278FFF79CF8F8 +:10162000002505AE3A003000A178FFF70EFE002835 +:101630001ED0144B01AD0193069B6B60079BAB6002 +:1016400000232B73F37B002BE4D02A2B17D16B469E +:10165000B27D3900DA70F27D20009A705E881EAA91 +:10166000A660FFF7D7FDAB6876029E1BAE60D1E7A0 +:10167000064B9D4201D9FEF7B3FE0135D1E7FEF7D7 +:10168000E3FEC7E753425355FF0F0000F824010063 +:1016900030B5EFF30883054C2360036883F30888B3 +:1016A0004568A847236883F3088830BDC00A002036 +:1016B00007B5010001226846FEF776FE082168465C +:1016C000FFF72EFB07BD0000F8B500237A227F4C00 +:1016D00023607F4B1A70FFF7C3FE7E4D4021280028 +:1016E000FFF72CFB7C4B186000232B54984201D051 +:1016F000FEF704FF0022794B1D60794B1A60784891 +:10170000754B02681F68BA42E5D2744E31680B7897 +:10171000FF2B37D0734D232B00D0B4E06C4B1B78DC +:10172000532B34D12B680132013102603160BA1A77 +:101730009A4200D91A006C4D20682A6000F0FCF92A +:10174000674829680368654ACB18013B0360106845 +:101750004B1EC3181360FF23644D29701940614B61 +:101760001B688B4203D92068591AFFF7F6FAC04666 +:101770007A22574B1A7000225A4B1A60574A136844 +:1017800001331360564A136801331360B7E7522BD5 +:1017900004D129682068FFF7CAFAE9E74F2B03D183 +:1017A0002B6822681370E3E7482B03D12B6822686B +:1017B0001380DDE7572B0AD14D4B22689A4202D1A4 +:1017C0000020FEF7BDFE23682A681A60D0E76F2B61 +:1017D00004D101212068FFF7A3FAC9E7682B05D1DE +:1017E000022123681B882B602800F4E7772B04D1A3 +:1017F000236804211B682B60F6E7472B09D1286872 +:10180000FFF746FF3B4B1B78002BB1D001213A4834 +:10181000E1E7542BACD04E2BAAD0562B02D12A2173 +:101820003648D8E7582B05D12868FEF757FC032126 +:101830003348D0E7592B0DD12A682068314B002A54 +:1018400003D1186003213048C5E719689208FEF7F4 +:101850005DFCF7E75A2B8BD12F6800252668F71916 +:10186000B74209D101212948FFF75AFA2800FFF7AA +:101870001FFF03212648AEE729003078FEF754FD0C +:1018800001360500ECE71A00303AD1B2092904D834 +:101890002B681B0113432B6070E71A00413A052A9D +:1018A00003D82A68373B1201F4E71A00613A052A87 +:1018B00003D82A68573B1201ECE72C2B03D12B6885 +:1018C00023600023E7E7024A1370FAE7BC0A00200E +:1018D000610A0020680A0020B40A0020B80A00202B +:1018E000AC0A0020640A0020C80A0020B00A0020C8 +:1018F0000CED00E0600A00202F1D00003F1D0000DD +:10190000311D0000C40A0020351D0000391D0000F3 +:101910003B1D00000300F7B5473304001A7840214F +:1019200003000020FEF792FE002801D10020FEBD3A +:1019300000232370237901930423E356002BF5DB66 +:101940003F262000019FA51DEB8F37404830C0186F +:101950003A00611D00F0F0F8E88F019BC01980B2D9 +:10196000B34301D1E887E1E70023EB87DFE7F0B578 +:1019700006000C001F0093B001923F25AC4203DC2F +:101980002500002F00D140373B0002AA2B431370E3 +:1019900002AB31002A00581C00F0CEF86B46402103 +:1019A0001A7902A80123FEF7D7FE7619641BE4D149 +:1019B00013B0F0BD030010B547331A78043100238B +:1019C0004830FFF7D4FF10BDF7B50400FFF7A2FFC2 +:1019D000002842D0230048339A88A06C1A80002245 +:1019E00001385A80082865D800F09CF8150523278F +:1019F000252A483F3800314E300000F0AEF805008F +:101A0000200031002A004C3000F096F82900200018 +:101A1000FFF7D0FF21E00123E364FF332365802338 +:101A2000DB006365A0235B00A365254B1421E36500 +:101A3000EDE7FEF7D5FC0021E9E7FEF705FDFAE743 +:101A400020000021FFF7B6FF8023206D9B01984204 +:101A500003D321005431FEF783FBF7BD2100626DF3 +:101A6000206D5831FEF747FBE5E72000656D216DDD +:101A70002A004C30FEF73FFBA900C8E70026636D43 +:101A8000276D0193019BB34201DC5900BFE73D0084 +:101A900000212B001878FEF747FC7B1C0135FF3333 +:101AA00001009D42F5D1230072004C332F00985263 +:101AB0000136E7E701225A80BDE7C046B81C0000A6 +:101AC000882BED6807480622030010B547331A70CB +:101AD000FFF77AFF04480722030047331A70FFF725 +:101AE00073FF10BDCC0A0020540C002010B5E2B0EA +:101AF0000400FFF751FDC42200216846520000F0A7 +:101B000024F847236B441C706846FFF75DFFFBE732 +:101B100002B4714649084900095649008E4402BC86 +:101B20007047C04602B4714649084900095C490043 +:101B30008E4402BC7047C046002310B59A4200D1C3 +:101B400010BDCC5CC4540133F8E703008218934203 +:101B500000D1704719700133F9E70023C25C0133EB +:101B6000002AFBD1581E70474D6963726F63686924 +:101B7000700053414D4432312058706C61696E657C +:101B8000642050726F0000000CA0800040A0800014 +:101B900044A0800048A0800012011002EF02014022 +:101BA000D804022401420102030100000697FF0944 +:101BB00001A101150026FF00750895400901810269 +:101BC00095400901910295010901B102C000000090 +:101BD00000000000681B0000721B0000500600207F +:101BE000EB3C905546322055463220000201010060 +:101BF0000240007E3EF83F000100010000000000AE +:101C0000000000008000294200420053414D443250 +:101C10003158504C000046415431362020203C21A0 +:101C2000646F63747970652068746D6C3E0A3C68FB +:101C3000746D6C3E3C626F64793E3C736372697094 +:101C4000743E0A6C6F636174696F6E2E7265706C9E +:101C5000616365282268747470733A2F2F777777E1 +:101C60002E7078742E696F2F22293B0A3C2F7363E4 +:101C7000726970743E3C2F626F64793E3C2F6874C9 +:101C80006D6C3E0A00000000494E464F5F554632DB +:101C900054585400B81C0000494E44455820202098 +:101CA00048544D001E1C000043555252454E5420CE +:101CB000554632000000000055463220426F6F74D6 +:101CC0006C6F616465722076332E31362E302D3183 +:101CD000352D6761613532623232205346485752A2 +:101CE0004F0D0A4D6F64656C3A2053414D443231BB +:101CF0002058706C61696E65642050726F0D0A42E5 +:101D00006F6172642D49443A2053414D4432314A47 +:101D10003138412D58706C61696E65642D50726F59 +:101D20000D0A000000000800003E800200020006CC +:101D300000580A0D00590A0D005A00230A0D0076BA +:101D4000312E31205B41726475696E6F3A58595A71 +:101D50005D205365702032382032303234203135E6 +:101D60003A35353A34340A0D000000000100000015 +:101D700001C80000050F3900021810050038B60828 +:101D800034A909A0478BFDA0768815B6650001012E +:101D9000001C100500DF60DDD88945C74C9CD2656A +:101DA0009D9E648A9F00000306AA000200000000B6 +:101DB000090299000501008032080B0002020201AD +:101DC00000090400000102020100052400100104C2 +:101DD00024020605240600010524010301070583EA +:101DE000030800FF09040100020A00000007058142 +:101DF0000240000007050202400000090402000240 +:101E0000080650000705840240000007050502404F +:101E10000000090403000203000000092100010082 +:101E20000122210007058603400001070506034043 +:101E300000010904040002FF2A010007058703408E +:101E4000000107050703400001000000090403002A +:101E5000020300000300000000C2010000000800AF +:101E60000A00000000000306AA00080002000400A7 +:101E7000A0001400030057494E55534200000000D3 +:101E80000000000000008400040007002A00440055 +:101E90006500760069006300650049006E0074000B +:101EA0006500720066006100630065004700550030 +:101EB000490044007300000050007B0039003200EC +:101EC0004300450036003400360032002D00390052 +:101ED0004300370037002D0034003600460045002F +:101EE0002D0039003300330042002D003300310053 +:101EF00043004200390043003500420042003300F5 +:101F0000420039007D00000000005342535500009C +:101F1000000000000000000000800202200000001D +:101F200000000000000000000000000000000000B1 +:101F30000000000000000000312E303000000000E2 +:101F40000000000000000000000000000000000091 +:101F50000000000000000000000000000000000081 +:101F60000000000000000000000000000000000071 +:101F70000000000000000000000000000000000061 +:101F80000000000000000000000000000000000051 +:101F90000000000000000000000000000000000041 +:101FA0000000000000000000000000000000000031 +:101FB0000000000000000000000000000000000021 +:101FC0000000000000000000000000000000000011 +:101FD0000000000000000000000000000000000001 +:101FE00000000000000000000000000000000000F1 +:101FF00000000000ED1A0000BD150000B81C000034 +:00000001FF From 4a159d16febeba65b2de52cb05657b9515a9bcae Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 24 Sep 2024 17:09:06 +0200 Subject: [PATCH 0041/2098] samd/boards/SAMD21_XPLAINED_PRO: Use the SPI flash for the file system. The initial settings did not support it. The change required to add a dedicated handling of the Adesto 1MByte flash of the XPLAINED PRO board, which does not support the sfdp feature. Fixes the ID check of the Adesto/Renesas 1MByte flash. Signed-off-by: robert-hh --- .../SAMD21_XPLAINED_PRO/mpconfigboard.h | 4 ++ .../SAMD21_XPLAINED_PRO/mpconfigboard.mk | 2 + .../samd/boards/SAMD21_XPLAINED_PRO/pins.csv | 4 ++ ports/samd/samd_spiflash.c | 43 ++++++++++++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h index 064d1ecc0d0..09bad81bb79 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h @@ -2,3 +2,7 @@ #define MICROPY_HW_MCU_NAME "SAMD21J18A" #define MICROPY_HW_XOSC32K (1) + +#define MICROPY_HW_SPIFLASH (1) +#define MICROPY_HW_SPIFLASH_ID (5) +#define MICROPY_HW_SPIFLASH_BAUDRATE (12000000) diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk index f95c6549381..cc43c22cea5 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk @@ -2,3 +2,5 @@ MCU_SERIES = SAMD21 CMSIS_MCU = SAMD21J18A LD_FILES = boards/samd21x18a.ld sections.ld TEXT0 = 0x2000 + +MICROPY_HW_CODESIZE ?= 248K diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv index 69d0c136d08..e5327e7916b 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv @@ -53,3 +53,7 @@ USB_DP,PA25 SWCLK,PA30 SWDIO,PA31 +FLASH_MOSI,PB22 +FLASH_MISO,PB16 +FLASH_SCK,PB23 +FLASH_CS,PA13 diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c index cd4f10640db..8ada7e96092 100644 --- a/ports/samd/samd_spiflash.c +++ b/ports/samd/samd_spiflash.c @@ -45,6 +45,7 @@ const uint8_t _COMMANDS_32BIT[] = {0x13, 0x12, 0x21}; // READ, PROGRAM_PAGE, ER #define COMMAND_JEDEC_ID (0x9F) #define COMMAND_READ_STATUS (0x05) +#define COMMAND_WRITE_SR1 (0x01) #define COMMAND_WRITE_ENABLE (0x06) #define COMMAND_READ_SFDP (0x5A) #define PAGE_SIZE (256) @@ -88,7 +89,7 @@ static void wait(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 0); spi_transfer((mp_obj_base_t *)self->spi, 2, msg, msg); mp_hal_pin_write(self->cs, 1); - } while (msg[1] != 0 && timeout-- > 0); + } while ((msg[1] & 1) != 0 && timeout-- > 0); } static void get_id(spiflash_obj_t *self, uint8_t id[3]) { @@ -123,6 +124,17 @@ static void write_enable(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 1); } +// Write status register 1 +static void write_sr1(spiflash_obj_t *self, uint8_t value) { + uint8_t msg[2]; + msg[0] = COMMAND_WRITE_SR1; + msg[1] = value; + + mp_hal_pin_write(self->cs, 0); + spi_transfer(self->spi, 2, msg, NULL); + mp_hal_pin_write(self->cs, 1); +} + static void get_sfdp(spiflash_obj_t *self, uint32_t addr, uint8_t *buffer, int size) { uint8_t dummy[1]; write_addr(self, COMMAND_READ_SFDP, addr); @@ -155,27 +167,36 @@ static mp_obj_t spiflash_make_new(const mp_obj_type_t *type, size_t n_args, size mp_hal_pin_write(self->cs, 1); wait(self); - // Get the flash size from the device ID (default) uint8_t id[3]; get_id(self, id); + bool read_sfdp = true; + if (id[1] == 0x84 && id[2] == 1) { // Adesto self->size = 512 * 1024; - } else if (id[1] == 0x1f && id[2] == 1) { // Atmel / Renesas + } else if (id[0] == 0x1f && id[1] == 0x45 && id[2] == 1) { // Adesto/Renesas 8 MBit self->size = 1024 * 1024; + read_sfdp = false; + self->sectorsize = 4096; + self->addr_is_32bit = false; + // Globally unlock the sectors, which are locked after power on. + write_enable(self); + write_sr1(self, 0); } else { self->size = 1 << id[2]; } // Get the addr_is_32bit flag and the sector size - uint8_t buffer[128]; - get_sfdp(self, 0, buffer, 16); // get the header - int len = MIN(buffer[11] * 4, sizeof(buffer)); - if (len >= 29) { - int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); - get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table - self->sectorsize = 1 << buffer[28]; - self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + if (read_sfdp) { + uint8_t buffer[128]; + get_sfdp(self, 0, buffer, 16); // get the header + int len = MIN(buffer[11] * 4, sizeof(buffer)); + if (len >= 29) { + int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); + get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table + self->sectorsize = 1 << buffer[28]; + self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + } } self->commands = self->addr_is_32bit ? _COMMANDS_32BIT : _COMMANDS_24BIT; From ceae0e14946b48f0e4b01669f241e66467bedddc Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 18 Nov 2024 10:56:07 +0100 Subject: [PATCH 0042/2098] samd/samd_flash: Make flash read/write methods access self parameters. Use `self` (the first argument) instead of the global `samd_flash_obj` when accessing the `flash_base` parameter. This allows there to be multiple flash objects for various types of filesystem. Signed-off-by: robert-hh --- ports/samd/samd_flash.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/samd/samd_flash.c b/ports/samd/samd_flash.c index ef0de7b6fb5..f68bdf140f4 100644 --- a/ports/samd/samd_flash.c +++ b/ports/samd/samd_flash.c @@ -103,7 +103,8 @@ static mp_obj_t samd_flash_version(void) { static MP_DEFINE_CONST_FUN_OBJ_0(samd_flash_version_obj, samd_flash_version); static mp_obj_t samd_flash_readblocks(size_t n_args, const mp_obj_t *args) { - uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + samd_flash_obj.flash_base; + samd_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + self->flash_base; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); if (n_args == 4) { @@ -118,7 +119,8 @@ static mp_obj_t samd_flash_readblocks(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_flash_readblocks_obj, 3, 4, samd_flash_readblocks); static mp_obj_t samd_flash_writeblocks(size_t n_args, const mp_obj_t *args) { - uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + samd_flash_obj.flash_base; + samd_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + self->flash_base; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); if (n_args == 3) { From cbffe61f96d060ed4af2bde7e3043a3664dc147a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 18 Nov 2024 16:50:03 +0100 Subject: [PATCH 0043/2098] samd/mboot/README.md: Add information about the bootloader source. Includes the LICENSE file of the source and the specific board files for the Xplained Pro board. Signed-off-by: robert-hh --- ports/samd/mboot/LICENSE | 60 ++++++++++++++++++++++++++++++++++++++ ports/samd/mboot/README.md | 5 ++++ 2 files changed, 65 insertions(+) create mode 100644 ports/samd/mboot/LICENSE create mode 100644 ports/samd/mboot/README.md diff --git a/ports/samd/mboot/LICENSE b/ports/samd/mboot/LICENSE new file mode 100644 index 00000000000..7fffbab21e0 --- /dev/null +++ b/ports/samd/mboot/LICENSE @@ -0,0 +1,60 @@ +The MIT License (MIT) + +Copyright (c) Microsoft + +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. + +Third Party Programs: The software may include third party programs that +Microsoft, not the third party, licenses to you under this agreement. +Notices, if any, for the third party programs are included for your +information only. + + + +Otherwise, where noted: + +/* ---------------------------------------------------------------------------- + * SAM Software Package License + * ---------------------------------------------------------------------------- + * Copyright (c) 2011-2014, Atmel Corporation + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the disclaimer below. + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ---------------------------------------------------------------------------- + */ + diff --git a/ports/samd/mboot/README.md b/ports/samd/mboot/README.md new file mode 100644 index 00000000000..1695f0a8911 --- /dev/null +++ b/ports/samd/mboot/README.md @@ -0,0 +1,5 @@ +The SAMD Xplained Pro bootloader is built using the files from the +repository https://github.com/adafruit/uf2-samdx1. The repository +includes the LICENSE file https://github.com/adafruit/uf2-samdx1/LICENSE +and a README https://github.com/adafruit/uf2-samdx1/README.md with +build instructions. From 0e7c3901b897a1edd53b56b6119f2d4119e88842 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 2 Oct 2024 19:16:39 +1000 Subject: [PATCH 0044/2098] docs: Add a "Reset and Boot Sequence" reference page. Previously individual ports documented these aspects to varying degrees, but most of the information is common to all ports. In particular, this adds a canonical explanation of `boot.py` and `main.py`. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp8266/general.rst | 35 +-- docs/esp8266/quickref.rst | 8 +- docs/library/builtins.rst | 4 + docs/library/machine.RTC.rst | 2 +- docs/library/machine.USBDevice.rst | 12 +- docs/library/machine.rst | 9 +- docs/library/pyb.rst | 13 +- docs/pyboard/tutorial/reset.rst | 2 + docs/reference/index.rst | 1 + docs/reference/mpremote.rst | 6 +- docs/reference/repl.rst | 9 +- docs/reference/reset_boot.rst | 260 ++++++++++++++++++ docs/renesas-ra/tutorial/program_in_flash.rst | 2 + docs/renesas-ra/tutorial/reset.rst | 20 +- docs/wipy/tutorial/reset.rst | 2 + 15 files changed, 311 insertions(+), 74 deletions(-) create mode 100644 docs/reference/reset_boot.rst diff --git a/docs/esp8266/general.rst b/docs/esp8266/general.rst index be0437e752e..d7211aa5ab7 100644 --- a/docs/esp8266/general.rst +++ b/docs/esp8266/general.rst @@ -74,40 +74,7 @@ as possible after use. Boot process ------------ -On boot, MicroPython EPS8266 port executes ``_boot.py`` script from internal -frozen modules. It mounts filesystem in FlashROM, or if it's not available, -performs first-time setup of the module and creates the filesystem. This -part of the boot process is considered fixed, and not available for customization -for end users (even if you build from source, please refrain from changes to -it; customization of early boot process is available only to advanced users -and developers, who can diagnose themselves any issues arising from -modifying the standard process). - -Once the filesystem is mounted, ``boot.py`` is executed from it. The standard -version of this file is created during first-time module set up and has -commands to start a WebREPL daemon (disabled by default, configurable -with ``webrepl_setup`` module), etc. This -file is customizable by end users (for example, you may want to set some -parameters or add other services which should be run on -a module start-up). But keep in mind that incorrect modifications to boot.py -may still lead to boot loops or lock ups, requiring to reflash a module -from scratch. (In particular, it's recommended that you use either -``webrepl_setup`` module or manual editing to configure WebREPL, but not -both). - -As a final step of boot procedure, ``main.py`` is executed from filesystem, -if exists. This file is a hook to start up a user application each time -on boot (instead of going to REPL). For small test applications, you may -name them directly as ``main.py``, and upload to module, but instead it's -recommended to keep your application(s) in separate files, and have just -the following in ``main.py``:: - - import my_app - my_app.main() - -This will allow to keep the structure of your application clear, as well as -allow to install multiple applications on a board, and switch among them. - +See :doc:`/reference/reset_boot`. Known Issues ------------ diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 6f02da95d89..635f1f834bb 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -163,10 +163,10 @@ sys.stdin.read() if it's needed to read characters from the UART(0) while it's also used for the REPL (or detach, read, then reattach). When detached the UART(0) can be used for other purposes. -If there are no objects in any of the dupterm slots when the REPL is -started (on hard or soft reset) then UART(0) is automatically attached. -Without this, the only way to recover a board without a REPL would be to -completely erase and reflash (which would install the default boot.py which +If there are no objects in any of the dupterm slots when the REPL is started (on +:doc:`hard or soft reset `) then UART(0) is automatically +attached. Without this, the only way to recover a board without a REPL would be +to completely erase and reflash (which would install the default boot.py which attaches the REPL). To detach the REPL from UART0, use:: diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index e489375b1f9..88b1fbcfe4a 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -170,6 +170,10 @@ Exceptions .. exception:: KeyboardInterrupt + |see_cpython| `python:KeyboardInterrupt`. + + See also in the context of :ref:`soft_bricking`. + .. exception:: KeyError .. exception:: MemoryError diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index fcd78f1c398..ad0b0759efe 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -83,7 +83,7 @@ Methods a `bytes` object. Data written to RTC user memory is persistent across restarts, including - `machine.soft_reset()` and `machine.deepsleep()`. + :ref:`soft_reset` and `machine.deepsleep()`. The maximum length of RTC user memory is 2048 bytes by default on esp32, and 492 bytes on esp8266. diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst index a47fda2a28d..5c18c49a75b 100644 --- a/docs/library/machine.USBDevice.rst +++ b/docs/library/machine.USBDevice.rst @@ -32,10 +32,10 @@ Managing a runtime USB interface can be tricky, especially if you are communicat with MicroPython over a built-in USB-CDC serial port that's part of the same USB device. -- A MicroPython soft reset will always clear all runtime USB interfaces, which - results in the entire USB device disconnecting from the host. If MicroPython - is also providing a built-in USB-CDC serial port then this will re-appear - after the soft reset. +- A MicroPython :ref:`soft reset ` will always clear all runtime USB + interfaces, which results in the entire USB device disconnecting from the + host. If MicroPython is also providing a built-in USB-CDC serial port then + this will re-appear after the soft reset. This means some functions (like ``mpremote run``) that target the USB-CDC serial port will immediately fail if a runtime USB interface is active, @@ -44,9 +44,9 @@ device. no more runtime USB interface. - To configure a runtime USB device on every boot, it's recommended to place the - configuration code in the ``boot.py`` file on the :ref:`device VFS + configuration code in the :ref:`boot.py` file on the :ref:`device VFS `. On each reset this file is executed before the USB subsystem is - initialised (and before ``main.py``), so it allows the board to come up with the runtime + initialised (and before :ref:`main.py`), so it allows the board to come up with the runtime USB device immediately. - For development or debugging, it may be convenient to connect a hardware diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7d2eb26a7ea..76d111f11ef 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -62,14 +62,13 @@ Reset related functions .. function:: reset() - Resets the device in a manner similar to pushing the external RESET - button. + :ref:`Hard resets ` the device in a manner similar to pushing the + external RESET button. .. function:: soft_reset() - Performs a soft reset of the interpreter, deleting all Python objects and - resetting the Python heap. It tries to retain the method by which the user - is connected to the MicroPython REPL (eg serial, USB, Wifi). + Performs a :ref:`soft reset ` of the interpreter, deleting all + Python objects and resetting the Python heap. .. function:: reset_cause() diff --git a/docs/library/pyb.rst b/docs/library/pyb.rst index f169a77f3a6..5eb75c22b60 100644 --- a/docs/library/pyb.rst +++ b/docs/library/pyb.rst @@ -147,10 +147,10 @@ Power related functions (internal oscillator) directly. The higher frequencies use the HSE to drive the PLL (phase locked loop), and then use the output of the PLL. - Note that if you change the frequency while the USB is enabled then - the USB may become unreliable. It is best to change the frequency - in boot.py, before the USB peripheral is started. Also note that sysclk - frequencies below 36MHz do not allow the USB to function correctly. + Note that if you change the frequency while the USB is enabled then the USB + may become unreliable. It is best to change the frequency in :ref:`boot.py`, + before the USB peripheral is started. Also note that sysclk frequencies below + 36MHz do not allow the USB to function correctly. .. function:: wfi() @@ -205,8 +205,9 @@ Miscellaneous functions .. function:: main(filename) - Set the filename of the main script to run after boot.py is finished. If - this function is not called then the default file main.py will be executed. + Set the filename of the main script to run after :ref:`boot.py` is finished. + If this function is not called then the default file :ref:`main.py` will be + executed. It only makes sense to call this function from within boot.py. diff --git a/docs/pyboard/tutorial/reset.rst b/docs/pyboard/tutorial/reset.rst index 59a3cd82ae1..ad56995c60d 100644 --- a/docs/pyboard/tutorial/reset.rst +++ b/docs/pyboard/tutorial/reset.rst @@ -10,6 +10,8 @@ execution of ``boot.py`` and ``main.py`` and gives default USB settings. If you have problems with the filesystem you can do a factory reset, which restores the filesystem to its original state. +For more information, see :doc:`/reference/reset_boot`. + Safe mode --------- diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 98f6b657376..1558c0fdfa9 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -21,6 +21,7 @@ implementation and the best practices to use them. glossary.rst repl.rst + reset_boot.rst mpremote.rst mpyfiles.rst isr_rules.rst diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index b47ec76c98e..d4c6983595f 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -547,9 +547,9 @@ device at ``/dev/ttyACM1``, printing each result. mpremote resume exec "print_state_info()" soft-reset -Connect to the device without triggering a soft reset and execute the -``print_state_info()`` function (e.g. to find out information about the current -program state), then trigger a soft reset. +Connect to the device without triggering a :ref:`soft reset ` and +execute the ``print_state_info()`` function (e.g. to find out information about +the current program state), then trigger a soft reset. .. code-block:: bash diff --git a/docs/reference/repl.rst b/docs/reference/repl.rst index 55f76ee1a03..be02ddebde8 100644 --- a/docs/reference/repl.rst +++ b/docs/reference/repl.rst @@ -143,10 +143,12 @@ the auto-indent feature, and changes the prompt from ``>>>`` to ``===``. For exa Paste Mode allows blank lines to be pasted. The pasted text is compiled as if it were a file. Pressing Ctrl-D exits paste mode and initiates the compilation. +.. _repl_soft_reset: + Soft reset ---------- -A soft reset will reset the python interpreter, but tries not to reset the +A :ref:`soft_reset` will reset the python interpreter, but tries not to reset the method by which you're connected to the MicroPython board (USB-serial, or Wifi). You can perform a soft reset from the REPL by pressing Ctrl-D, or from your python @@ -182,6 +184,9 @@ variables no longer exist: ['__name__', 'pyb'] >>> +For more information about reset types and the startup process, see +:doc:`/reference/reset_boot`. + The special variable _ (underscore) ----------------------------------- @@ -196,6 +201,8 @@ So you can use the underscore to save the result in a variable. For example: 15 >>> +.. _raw_repl: + Raw mode and raw-paste mode --------------------------- diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst new file mode 100644 index 00000000000..7c0d5f334a6 --- /dev/null +++ b/docs/reference/reset_boot.rst @@ -0,0 +1,260 @@ +Reset and Boot Sequence +======================= + +A device running MicroPython follows a particular boot sequence to start up and +initialise itself after a reset. + +.. _hard_reset: + +Hard reset +---------- + +Booting from hard reset is what happens when a board is first powered up, a cold +boot. This is a complete reset of the MCU hardware. + +The MicroPython port code initialises all essential hardware (including embedded +clocks and power regulators, internal serial UART, etc), and then starts the +MicroPython environment. Existing :doc:`RTC ` +configuration may be retained after a hard reset, but all other hardware state +is cleared. + +The same hard reset boot sequence can be triggered by a number of events such as: + +- Python code executing :func:`machine.reset()`. +- User presses a physical Reset button on the board (where applicable). +- Waking from deep sleep (on most ports). +- MCU hardware watchdog reset. +- MCU hardware brown out detector. + +The details of hardware-specific reset triggers depend on the port and +associated hardware. The :func:`machine.reset_cause()` function can be used to +further determine the cause of a reset. + +.. _soft_reset: + +Soft Reset +---------- + +When MicroPython is already running, it's possible to trigger a soft reset by +:ref:`typing Ctrl-D in the REPL ` or executing +:func:`machine.soft_reset()`. + +A soft reset clears the Python interpreter, frees all Python memory, and starts +the MicroPython environment again. + +State which is cleared by a soft reset includes: + +- All Python variables, objects, imported modules, etc. +- Most peripherals configured using the :doc:`machine module + `. There are very limited exceptions, for example + :doc:`machine.Pin ` modes (i.e. if a pin is input or + output, high or low) are not reset on most ports. More advanced configuration + such as :func:`Pin.irq()` is always reset. +- Bluetooth. +- Network sockets. Open TCP sockets are closed cleanly with respect to the other party. +- Open files. The filesystem is left in a valid state. + +Some system state remains the same after a soft reset, including: + +- Any existing network connections (Ethernet, Wi-Fi, etc) remain active at the + IP Network layer. Querying the :doc:`network interface from code + ` may indicate the network interface is still active with a + configured IP address, etc. +- An active :doc:`REPL ` appears continuous before and after soft reset, + except in some unusual cases: + + * If the :ref:`machine.USBDevice ` class has been used to + create a custom USB interface then any built-in USB serial device will + appear to disconnect and reconnect as the custom USB interface must be + cleared during reset. + * A serial UART REPL will restore its default hardware configuration (baud + rate, etc). + +- CPU clock speed is usually not changed by a soft reset. +- :doc:`RTC ` configuration (i.e. setting of the current + time) is not changed by soft reset. + +.. _boot_sequence: + +Boot Sequence +------------- + +When MicroPython boots following either a hard or soft reset, it follows this +boot sequence in order: + +_boot.py +^^^^^^^^ + +This is an internal script :doc:`frozen into the MicroPython firmware +`. It is provided by MicroPython on many ports to do essential +initialisation. + +For example, on most ports ``_boot.py`` will detect the first boot of a new +device and format the :doc:`internal flash filesystem ` ready for +use. + +Unless you're creating a custom MicroPython build or adding a new port then you +probably don't need to worry about ``_boot.py``. It's best not to change the +contents unless you really know what you're doing. + +.. _boot.py: + +boot.py +^^^^^^^ + +A file named ``boot.py`` can be copied to the board's internal :ref:`filesystem +` using :doc:`mpremote `. + +If ``boot.py`` is found then it is executed. You can add code in ``boot.py`` to +perform custom one-off initialisation (for example, to configure the board's +hardware). + +A common practice is to configure a board's network connection in ``boot.py`` so +that it's always available after reset for use with the :doc:`REPL `, +:doc:`mpremote `, etc. + +.. warning:: boot.py should always exit and not run indefinitely. + + Depending on the port, some hardware initialisation is delayed until after + ``boot.py`` exits. This includes initialising USB on the stm32 port and all + ports which support :ref:`machine.USBDevice `. On these + ports, output printed from ``boot.py`` may not be visible on the built-in USB + serial port until after ``boot.py`` finishes running. + + The purpose of this late initialisation is so that it's possible to + pre-configure particular hardware in ``boot.py``, and then have it start with + the correct configuration. + +.. note:: It is sometimes simpler to not have a ``boot.py`` file and place any + initialisation code at the top of ``main.py`` instead. + +.. _main.py: + +main.py +^^^^^^^ + +Similar to ``boot.py``, a file named ``main.py`` can be copied to the board's +internal :ref:`filesystem `. If found then it is executed next in the +startup process. + +``main.py`` is for any Python code that you want to run each time your device +starts. + +Some tips for ``main.py`` usage: + +- ``main.py`` doesn't have to exit, feel free to put an infinite ``while + True`` loop in there. +- For complex Python applications then you don't need to put all your + code in ``main.py``. ``main.py`` can be a simple entry point that + imports your application and starts execution:: + + import my_app + my_app.main() + + This can help keep the structure of your application clear. It also makes + it easy to install multiple applications on a board and switch among them. +- It's good practice when writing robust apps to wrap code in ``main.py`` with an + exception handler to take appropriate action if the code crashes. For example:: + + import machine, sys + import my_app + try: + my_app.main() + except Exception as e: + print("Fatal error in main:") + sys.print_exception(e) + + # Following a normal Exception or main() exiting, reset the board. + # Following a non-Exception error such as KeyboardInterrupt (Ctrl-C), + # this code will drop to a REPL. Place machine.reset() in a finally + # block to always reset, instead. + machine.reset() + + Otherwise MicroPython will drop to the REPL following any crash or if main + exits (see below). + +- Any global variables that were set in ``boot.py`` will still be set in the + global context of ``main.py``. + +- To fully optimise flash usage and memory consumption, you can copy + :doc:`pre-compiled ` ``main.mpy`` and/or ``boot.mpy`` files to the + filesystem, or even :doc:`freeze ` them into the firmware build + instead. +- ``main.py`` execution is skipped when a soft reset is initiated from :ref:`raw + REPL mode ` (for example, when :doc:`mpremote ` or another + program is interacting directly with MicroPython). + +Interactive Interpreter (REPL) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If ``main.py`` is not found, or if ``main.py`` exits, then :doc:`repl` +will start immediately. + +.. note:: Even if ``main.py`` contains an infinite loop, typing Ctrl-C on the + REPL serial port will inject a `KeyboardInterrupt`. If no exception + handler catches it then ``main.py`` will exit and the REPL will start. + +Any global variables that were set in ``boot.py`` and ``main.py`` will still be +set in the global context of the REPL. + +The REPL continues executing until Python code triggers a hard or soft reset. + +.. _soft_bricking: + +Soft Bricking (failure to boot) +--------------------------------- + +It is rare but possible for MicroPython to become unresponsive during startup, a +state sometimes called "soft bricked". For example: + +- If ``boot.py`` execution gets stuck and the native USB serial port + never initialises. +- If Python code reconfigures the REPL interface, making it inaccessible. + +Rest assured, recovery is possible! + +KeyboardInterrupt +^^^^^^^^^^^^^^^^^ + +In many cases, opening the REPL serial port and typing ``Ctrl-C`` will inject +`KeyboardInterrupt` and may cause the running script to exit and a REPL to +start. From the REPL, you can use :func:`os.remove()` to remove the misbehaving +Python file:: + + import os + os.remove('main.py') + +To confirm which files are still present in the internal filesystem:: + + import os + os.listdir() + +Safe Mode and Factory Reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you're unable to easily access the REPL then you may need to perform one of +two processes: + +1. "Safe mode" boot, which skips ``boot.py`` and ``main.py`` and immediately + starts a REPL, allowing you to clean up. This is only supported on some ports. +2. Factory Reset to erase the entire contents of the flash filesystem. This may + also be necessary if the internal flash filesystem has become corrupted + somehow. + +The specific process(es) are different on each port: + +- :doc:`pyboard and stm32 port instructions ` +- :doc:`renesas-ra port instructions ` +- :doc:`wipy port instructions ` + +For ports without specific instructions linked above, the factory reset process +involves erasing the board's entire flash and then flashing MicroPython again +from scratch. Usually this will involve the same tool(s) that were originally +used to install MicroPython. Consult the installation docs for your board, or +ask on the `GitHub Discussions`_ if you're not sure. + +.. warning:: Re-flashing the MicroPython firmware without erasing the entire + flash first will usually not recover from soft bricking, as a + firmware update usually preserves the contents of the filesystem. + +.. _GitHub Discussions: https://github.com/orgs/micropython/discussions diff --git a/docs/renesas-ra/tutorial/program_in_flash.rst b/docs/renesas-ra/tutorial/program_in_flash.rst index 99a7a3839dd..985ce6c7fe6 100644 --- a/docs/renesas-ra/tutorial/program_in_flash.rst +++ b/docs/renesas-ra/tutorial/program_in_flash.rst @@ -28,6 +28,8 @@ As the factory setting, following 2 files are created in the file system: * boot.py : executed first when the system starts * main.py : executed after boot.py completes +See :doc:`/reference/reset_boot` for more information. + Write a program in the internal file system ------------------------------------------- diff --git a/docs/renesas-ra/tutorial/reset.rst b/docs/renesas-ra/tutorial/reset.rst index de5f4db91b3..87997964795 100644 --- a/docs/renesas-ra/tutorial/reset.rst +++ b/docs/renesas-ra/tutorial/reset.rst @@ -20,6 +20,8 @@ If that isn't working you can perform a hard reset (turn-it-off-and-on-again) by pressing the RESET button. This will end your session, disconnecting whatever program (PuTTY, screen, etc) that you used to connect to the board. +For more details, see :doc:`/reference/reset_boot`. + boot mode --------- @@ -29,7 +31,9 @@ There are 3 boot modes: * safe boot mode * factory filesystem boot mode -boot.py and main.py are executed on "normal boot mode". +boot.py and main.py are executed on "normal boot mode". See :ref:`boot_sequence`. + +The other modes can be used to recover from :ref:`soft_bricking`: boot.py and main.py are *NOT* executed on "safe boot mode". @@ -46,16 +50,4 @@ on the board: You have created the main.py which executes LED1 blinking in the previous part. If you change the boot mode to safe boot mode, the MicroPython starts without -the execution of main.py. Then you can remove the main.py by following -command or change the boot mode to factory file system boot mode.:: - - import os - os.remove('main.py') - -or change the boot mode to factory file system boot mode. - -You can confirm that the initialized file system that there are only boot.py and main.py files.:: - - import os - os.listdir() - +the execution of main.py. diff --git a/docs/wipy/tutorial/reset.rst b/docs/wipy/tutorial/reset.rst index 1715d3e2978..fc56429b81a 100644 --- a/docs/wipy/tutorial/reset.rst +++ b/docs/wipy/tutorial/reset.rst @@ -16,6 +16,8 @@ There are soft resets and hard resets. import machine machine.reset() +For more information, see :doc:`/reference/reset_boot`. + Safe boot --------- From 9361a9f50af1e0b657eed270df6556ec253ddaeb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 3 Oct 2024 09:28:53 +1000 Subject: [PATCH 0045/2098] docs/rp2: Add a small factory reset page. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/reference/reset_boot.rst | 1 + docs/rp2/tutorial/intro.rst | 1 + docs/rp2/tutorial/reset.rst | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 docs/rp2/tutorial/reset.rst diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst index 7c0d5f334a6..f6307c08cdf 100644 --- a/docs/reference/reset_boot.rst +++ b/docs/reference/reset_boot.rst @@ -245,6 +245,7 @@ The specific process(es) are different on each port: - :doc:`pyboard and stm32 port instructions ` - :doc:`renesas-ra port instructions ` +- :doc:`rp2 port instructions ` - :doc:`wipy port instructions ` For ports without specific instructions linked above, the factory reset process diff --git a/docs/rp2/tutorial/intro.rst b/docs/rp2/tutorial/intro.rst index 69c3e6b0a54..2d2990105db 100644 --- a/docs/rp2/tutorial/intro.rst +++ b/docs/rp2/tutorial/intro.rst @@ -8,4 +8,5 @@ Let's get started! .. toctree:: :maxdepth: 1 + reset.rst pio.rst diff --git a/docs/rp2/tutorial/reset.rst b/docs/rp2/tutorial/reset.rst new file mode 100644 index 00000000000..3c296ec46ee --- /dev/null +++ b/docs/rp2/tutorial/reset.rst @@ -0,0 +1,18 @@ +Factory reset +============= + +If something unexpected happens and your RP2xxx-based board no longer boots +MicroPython, then you may have to factory reset it. For more details, see +:ref:`soft_bricking`. + +Factory resetting the MicroPython rp2 port involves fully erasing the flash and +resetting the flash memory, so you will need to re-flash the MicroPython +firmware afterwards and copy any Python files to the filesystem again. + +1. Follow the instructions on the Raspberry Pi website for `resetting flash + memory`_. +2. Copy the MicroPython .uf2 firmware file to your board. If needed, this file + can be found on the `MicroPython downloads page`_. + +.. _resetting flash memory: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#resetting-flash-memory +.. _MicroPython downloads page: https://micropython.org/download/?port=rp2 From a23277e3b0400b6b5ca6ce3d88e3a38859754a4e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 3 Oct 2024 09:42:13 +1000 Subject: [PATCH 0046/2098] docs/esp32: Add a factory reset page. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/tutorial/index.rst | 1 + docs/esp32/tutorial/intro.rst | 2 ++ docs/esp32/tutorial/reset.rst | 25 +++++++++++++++++++++++++ docs/reference/reset_boot.rst | 1 + 4 files changed, 29 insertions(+) create mode 100644 docs/esp32/tutorial/reset.rst diff --git a/docs/esp32/tutorial/index.rst b/docs/esp32/tutorial/index.rst index c6242d731f1..2435f2ecd2f 100644 --- a/docs/esp32/tutorial/index.rst +++ b/docs/esp32/tutorial/index.rst @@ -21,3 +21,4 @@ to ``__. intro.rst pwm.rst peripheral_access.rst + reset.rst diff --git a/docs/esp32/tutorial/intro.rst b/docs/esp32/tutorial/intro.rst index be09599871c..cf4d0bcbd2f 100644 --- a/docs/esp32/tutorial/intro.rst +++ b/docs/esp32/tutorial/intro.rst @@ -50,6 +50,8 @@ features, there are daily builds. If your board has SPIRAM support you can use either the standard firmware or the firmware with SPIRAM support, and in the latter case you will have access to more RAM for Python objects. +.. _esp32_flashing: + Deploying the firmware ---------------------- diff --git a/docs/esp32/tutorial/reset.rst b/docs/esp32/tutorial/reset.rst new file mode 100644 index 00000000000..b3fc6a85bd4 --- /dev/null +++ b/docs/esp32/tutorial/reset.rst @@ -0,0 +1,25 @@ +Factory reset +============= + +If something unexpected happens and your ESP32-based board no longer boots +MicroPython, then you may have to factory reset it. For more details, see +:ref:`soft_bricking`. + +Factory resetting the MicroPython esp32 port involves fully erasing the flash +and resetting the flash memory, so you will need to re-flash the MicroPython +firmware afterwards and copy any Python files to the filesystem again. + +1. You will need the Espressif `esptool`_ installed on your system. This is the + same tool that you may have used to initially install MicroPython on your + board (see :ref:`installation instructions `). +2. Find the serial port name of your board, and then use esptool to erase the + entire flash contents:: + + esptool.py -p PORTNAME erase_flash + +3. Use esptool to flash the MicroPython file to your board again. If needed, + this file and flashing instructions can be found on the `MicroPython + downloads page`_. + +.. _esptool: https://github.com/espressif/esptool +.. _MicroPython downloads page: https://micropython.org/download/?port=esp32 diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst index f6307c08cdf..4772d02dd1e 100644 --- a/docs/reference/reset_boot.rst +++ b/docs/reference/reset_boot.rst @@ -244,6 +244,7 @@ two processes: The specific process(es) are different on each port: - :doc:`pyboard and stm32 port instructions ` +- :doc:`esp32 port instructions ` - :doc:`renesas-ra port instructions ` - :doc:`rp2 port instructions ` - :doc:`wipy port instructions ` From c5d74fe46876767cc43d7d23d949d0f7c3dcd6bd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 8 Oct 2024 14:41:44 +1100 Subject: [PATCH 0047/2098] docs/library: Note link between machine.soft_reset() and sys.exit(). This is currently an implementation detail of MicroPython rather than by design. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/builtins.rst | 6 ++++++ docs/library/sys.rst | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index 88b1fbcfe4a..5956aea7ab2 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -194,6 +194,12 @@ Exceptions |see_cpython| `python:SystemExit`. + On non-embedded ports (i.e. Windows and Unix), an unhandled ``SystemExit`` + exits the MicroPython process in a similar way to CPython. + + On embedded ports, an unhandled ``SystemExit`` currently causes a + :ref:`soft_reset` of MicroPython. + .. exception:: TypeError |see_cpython| `python:TypeError`. diff --git a/docs/library/sys.rst b/docs/library/sys.rst index 7b34a0e31c6..c72214c1310 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -12,9 +12,12 @@ Functions .. function:: exit(retval=0, /) Terminate current program with a given exit code. Underlyingly, this - function raise as `SystemExit` exception. If an argument is given, its + function raises a `SystemExit` exception. If an argument is given, its value given as an argument to `SystemExit`. + On embedded ports (i.e. all ports but Windows and Unix), an unhandled + `SystemExit` currently causes a :ref:`soft_reset` of MicroPython. + .. function:: atexit(func) Register *func* to be called upon termination. *func* must be a callable From c1b8e65c8e216ce2e90933aef6772de46ad54407 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 12:24:21 +1100 Subject: [PATCH 0048/2098] docs: Change copyright line to mention "authors and contributors". The docs have been authored by many people now. Instead of singling out individuals in the copyright line, prefer to mention all "MicroPython authors and contributors". Individual contributions can still be discovered via the git history. Signed-off-by: Damien George --- docs/conf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 32cc2be10e3..e5c6ba98090 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,9 @@ "is_release": micropy_version != "latest", } +# Authors used in various parts of the documentation. +micropy_authors = "MicroPython authors and contributors" + # -- General configuration ------------------------------------------------ @@ -68,7 +71,7 @@ # General information about the project. project = "MicroPython" -copyright = "- The MicroPython Documentation is Copyright © 2014-2024, Damien P. George, Paul Sokolovsky, and contributors" +copyright = "- The MicroPython Documentation is Copyright © 2014-2024, " + micropy_authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -244,7 +247,7 @@ master_doc, "MicroPython.tex", "MicroPython Documentation", - "Damien P. George, Paul Sokolovsky, and contributors", + micropy_authors, "manual", ), ] @@ -281,7 +284,7 @@ "index", "micropython", "MicroPython Documentation", - ["Damien P. George, Paul Sokolovsky, and contributors"], + [micropy_authors], 1, ), ] @@ -300,7 +303,7 @@ master_doc, "MicroPython", "MicroPython Documentation", - "Damien P. George, Paul Sokolovsky, and contributors", + micropy_authors, "MicroPython", "One line description of project.", "Miscellaneous", From f562aa1291ad44605f6b5be75b325a40a208ec41 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 18:14:47 +1100 Subject: [PATCH 0049/2098] extmod/network_cyw43: Fix isconnected() result on AP interface. This function is documented to return True if any stations are connected to the AP. Without this fix it returns True whenever the driver has brought the AP interface up. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 3066cac75d3..891e945de51 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -311,7 +311,17 @@ static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_disconnect_obj, network_cyw43_dis static mp_obj_t network_cyw43_isconnected(mp_obj_t self_in) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(cyw43_tcpip_link_status(self->cyw, self->itf) == 3); + bool result = (cyw43_tcpip_link_status(self->cyw, self->itf) == CYW43_LINK_UP); + + if (result && self->itf == CYW43_ITF_AP) { + // For AP we need to not only know if the link is up, but also if any stations + // have associated. + uint8_t mac_buf[6]; + int num_stas = 1; + cyw43_wifi_ap_get_stas(self->cyw, &num_stas, mac_buf); + result = num_stas > 0; + } + return mp_obj_new_bool(result); } static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_isconnected_obj, network_cyw43_isconnected); From af743eaf59196e9133bd3c6de53cae1bb6c9a59a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Nov 2024 11:26:34 +1100 Subject: [PATCH 0050/2098] extmod/network_cyw43: Fix uninitialised variable in status('stations'). The `num_stas` was uninitialised and if it happened to take the value 0 then no results were returned. It now has the correct maximum value. Signed-off-by: Damien George --- extmod/network_cyw43.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 891e945de51..cebc8deb0da 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -361,13 +361,15 @@ static mp_obj_t network_cyw43_status(size_t n_args, const mp_obj_t *args) { if (self->itf != CYW43_ITF_AP) { mp_raise_ValueError(MP_ERROR_TEXT("AP required")); } - int num_stas; - uint8_t macs[32 * 6]; + static const unsigned mac_len = 6; + static const unsigned max_stas = 32; + int num_stas = max_stas; + uint8_t macs[max_stas * mac_len]; cyw43_wifi_ap_get_stas(self->cyw, &num_stas, macs); mp_obj_t list = mp_obj_new_list(num_stas, NULL); for (int i = 0; i < num_stas; ++i) { mp_obj_t tuple[1] = { - mp_obj_new_bytes(&macs[i * 6], 6), + mp_obj_new_bytes(&macs[i * mac_len], mac_len), }; ((mp_obj_list_t *)MP_OBJ_TO_PTR(list))->items[i] = mp_obj_new_tuple(1, tuple); } From 181800eebde2f3dbda2862c175150d884eef728d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 18:04:59 +1100 Subject: [PATCH 0051/2098] extmod/network_cyw43: Allow configuring active AP interface. Configuring the AP for cyw43 writes to some buffers that are only sent to the modem when the interface is brought up. This means you can't configure the AP after calling active(True), the new settings seem to be accepted but the radio doesn't change. This is different to the WLAN behaviour on other ports. The esp8266 port requires calling active(True) on the AP before configuring, even. Fix this by bouncing the AP interface after a config change, if it's active. Configuring with active(False) still works the same as before. Adds a static variable to track interface active state, rather than relying on the LWIP interface state. This is because the interface state is updated by a driver callback and there's a race: if code calls active(True) and then config(a=b) then the driver doesn't know it's active yet and the changes aren't correctly applied. It is possible this pattern will cause the AP to come up briefly with the default "PICOabcd" SSID before being reconfigured, however (due to the aforementioned race condition) it seems like this may not happen at all before the new config is applied. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index cebc8deb0da..02b022cb813 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -60,6 +60,10 @@ typedef struct _network_cyw43_obj_t { static const network_cyw43_obj_t network_cyw43_wl_sta = { { &mp_network_cyw43_type }, &cyw43_state, CYW43_ITF_STA }; static const network_cyw43_obj_t network_cyw43_wl_ap = { { &mp_network_cyw43_type }, &cyw43_state, CYW43_ITF_AP }; +// Avoid race conditions with callbacks by tracking the last up or down request +// we have made for each interface. +static bool if_active[2]; + static void network_cyw43_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); struct netif *netif = &self->cyw->netif[self->itf]; @@ -122,6 +126,10 @@ static MP_DEFINE_CONST_FUN_OBJ_3(network_cyw43_ioctl_obj, network_cyw43_ioctl); /*******************************************************************************/ // network API +static uint32_t get_country_code(void) { + return CYW43_COUNTRY(mod_network_country_code[0], mod_network_country_code[1], 0); +} + static mp_obj_t network_cyw43_deinit(mp_obj_t self_in) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); cyw43_deinit(self->cyw); @@ -132,10 +140,11 @@ static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_deinit_obj, network_cyw43_deinit) static mp_obj_t network_cyw43_active(size_t n_args, const mp_obj_t *args) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 1) { - return mp_obj_new_bool(cyw43_tcpip_link_status(self->cyw, self->itf)); + return mp_obj_new_bool(if_active[self->itf]); } else { - uint32_t country = CYW43_COUNTRY(mod_network_country_code[0], mod_network_country_code[1], 0); - cyw43_wifi_set_up(self->cyw, self->itf, mp_obj_is_true(args[1]), country); + bool value = mp_obj_is_true(args[1]); + cyw43_wifi_set_up(self->cyw, self->itf, value, get_country_code()); + if_active[self->itf] = value; return mp_const_none; } } @@ -457,6 +466,10 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); } + // A number of these options only update buffers in memory, and + // won't do anything until the interface is cycled down and back up + bool cycle_active = false; + for (size_t i = 0; i < kwargs->alloc; ++i) { if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { mp_map_elem_t *e = &kwargs->table[i]; @@ -469,6 +482,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } case MP_QSTR_channel: { cyw43_wifi_ap_set_channel(self->cyw, mp_obj_get_int(e->value)); + cycle_active = true; break; } case MP_QSTR_ssid: @@ -476,6 +490,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map size_t len; const char *str = mp_obj_str_get_data(e->value, &len); cyw43_wifi_ap_set_ssid(self->cyw, len, (const uint8_t *)str); + cycle_active = true; break; } case MP_QSTR_monitor: { @@ -495,6 +510,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } case MP_QSTR_security: { cyw43_wifi_ap_set_auth(self->cyw, mp_obj_get_int(e->value)); + cycle_active = true; break; } case MP_QSTR_key: @@ -502,6 +518,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map size_t len; const char *str = mp_obj_str_get_data(e->value, &len); cyw43_wifi_ap_set_password(self->cyw, len, (const uint8_t *)str); + cycle_active = true; break; } case MP_QSTR_pm: { @@ -531,6 +548,13 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } } + // If the interface is already active, cycle it down and up + if (cycle_active && if_active[self->itf]) { + uint32_t country = get_country_code(); + cyw43_wifi_set_up(self->cyw, self->itf, false, country); + cyw43_wifi_set_up(self->cyw, self->itf, true, country); + } + return mp_const_none; } } From b65e89107c0194bfdaf92720786341a2047f36b6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 12:03:39 +1100 Subject: [PATCH 0052/2098] py/py.mk: Add check that any specified USER_C_MODULES folder exists. Signed-off-by: Andrew Leech --- py/py.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py/py.mk b/py/py.mk index 2e7ffe9905e..9592fbb9170 100644 --- a/py/py.mk +++ b/py/py.mk @@ -33,6 +33,9 @@ ifneq ($(USER_C_MODULES),) # pre-define USERMOD variables as expanded so that variables are immediate # expanded as they're added to them +# Confirm the provided path exists, show abspath if not to make it clearer to fix. +$(if $(wildcard $(USER_C_MODULES)/.),,$(error USER_C_MODULES doesn't exist: $(abspath $(USER_C_MODULES)))) + # C/C++ files that are included in the QSTR/module build SRC_USERMOD_C := SRC_USERMOD_CXX := From dccd206f4cbbf7c15e1a5721ec5ab830d92ffa8e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 13:12:19 +1100 Subject: [PATCH 0053/2098] py/usermod.cmake: Add check that any specified USER_C_MODULES exists. Signed-off-by: Andrew Leech --- py/usermod.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index c814369a486..93fce13b300 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -42,6 +42,12 @@ endfunction() # Include CMake files for user modules. if (USER_C_MODULES) foreach(USER_C_MODULE_PATH ${USER_C_MODULES}) + # Confirm the provided path exists, show abspath if not to make it clearer to fix. + if (NOT EXISTS ${USER_C_MODULE_PATH}) + get_filename_component(USER_C_MODULES_ABS "${USER_C_MODULE_PATH}" ABSOLUTE) + message(FATAL_ERROR "USER_C_MODULES doesn't exist: ${USER_C_MODULES_ABS}") + endif() + message("Including User C Module(s) from ${USER_C_MODULE_PATH}") include(${USER_C_MODULE_PATH}) endforeach() From 78d017fc4e70bf21b7c53f193550d220f8959e38 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 13:12:56 +1100 Subject: [PATCH 0054/2098] py/usermod.cmake: If USER_C_MODULES is a folder add micropython.cmake. This mirrors how it works when using a Makefile. Signed-off-by: Andrew Leech --- py/usermod.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index 93fce13b300..4a8b99ff31b 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -42,6 +42,10 @@ endfunction() # Include CMake files for user modules. if (USER_C_MODULES) foreach(USER_C_MODULE_PATH ${USER_C_MODULES}) + # If a directory is given, append the micropython.cmake to it. + if (IS_DIRECTORY ${USER_C_MODULE_PATH}) + set(USER_C_MODULE_PATH "${USER_C_MODULE_PATH}/micropython.cmake") + endif() # Confirm the provided path exists, show abspath if not to make it clearer to fix. if (NOT EXISTS ${USER_C_MODULE_PATH}) get_filename_component(USER_C_MODULES_ABS "${USER_C_MODULE_PATH}" ABSOLUTE) From 7647c828de4ffb9877163909017bbe909e79aacb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 11:20:03 +1100 Subject: [PATCH 0055/2098] tests/multi_espnow: Add channel setting test, add some docs. Test currently passes. It was added so it can be used to check for regressions when fixing channel selection for AP mode in a follow-up commit. Also add some docs about how channel setting is observed to work for ESP-NOW. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/espnow.rst | 7 ++- docs/library/network.WLAN.rst | 2 +- tests/multi_espnow/70_channel.py | 89 ++++++++++++++++++++++++++++ tests/multi_espnow/70_channel.py.exp | 13 ++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tests/multi_espnow/70_channel.py create mode 100644 tests/multi_espnow/70_channel.py.exp diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index 1bcc9d7623d..379e60486f2 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -441,7 +441,9 @@ must first register the sender and use the same encryption keys as the sender - *channel*: The wifi channel (2.4GHz) to communicate with this peer. Must be an integer from 0 to 14. If channel is set to 0 the current - channel of the wifi device will be used. (default=0) + channel of the wifi device will be used, if channel is set to another + value then this must match the channel currently configured on the + interface (see :func:`WLAN.config`). (default=0) - *ifidx*: (ESP32 only) Index of the wifi interface which will be used to send data to this peer. Must be an integer set to @@ -470,6 +472,9 @@ must first register the sender and use the same encryption keys as the sender registered. - ``OSError(num, "ESP_ERR_ESPNOW_FULL")`` if too many peers are already registered. + - ``OSError(num, "ESP_ERR_ESPNOW_CHAN")`` if a channel value was + set that doesn't match the channel currently configured for this + interface. - ``ValueError()`` on invalid keyword args or values. .. method:: ESPNow.del_peer(mac) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 3c401acb420..09fefc5929f 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -126,7 +126,7 @@ Methods ============= =========== mac MAC address (bytes) ssid WiFi access point name (string) - channel WiFi channel (integer) + channel WiFi channel (integer). Depending on the port this may only be supported on the AP interface. hidden Whether SSID is hidden (boolean) security Security protocol supported (enumeration, see module constants) key Access key (string) diff --git a/tests/multi_espnow/70_channel.py b/tests/multi_espnow/70_channel.py new file mode 100644 index 00000000000..f3e8b947b12 --- /dev/null +++ b/tests/multi_espnow/70_channel.py @@ -0,0 +1,89 @@ +# Test that ESP-NOW picks up the channel configuration for STA +# mode on ESP32. +# +# Note that setting the channel on a peer in ESP-NOW on modern ESP-IDF only +# checks it against the configured channel, it doesn't ever change the radio +# channel +import sys +import time + +try: + import network + import espnow +except ImportError: + print("SKIP") + raise SystemExit + +# ESP8266 doesn't support config('channel') on the STA interface, +# and the channel parameter to add_peer doesn't appear to set the +# channel either. +if sys.platform == "esp8266": + print("SKIP") + raise SystemExit + + +timeout_ms = 1000 +default_pmk = b"MicroPyth0nRules" + +CHANNEL = 3 +WRONG_CHANNEL = 8 + + +def init_sta(): + sta = network.WLAN(network.WLAN.IF_STA) + e = espnow.ESPNow() + e.active(True) + sta.active(True) + sta.disconnect() # Force AP disconnect for any saved config, important so the channel doesn't change + sta.config(channel=CHANNEL) + e.set_pmk(default_pmk) + return sta, e + + +# Receiver +def instance0(): + sta, e = init_sta() + multitest.globals(PEER=sta.config("mac")) + multitest.next() + print(sta.config("channel")) + while True: + peer, msg = e.recv(timeout_ms) + if peer is None: + print("Timeout") + break + print(msg) + e.active(False) + + +# Sender +def instance1(): + sta, e = init_sta() + multitest.next() + peer = PEER + + # both instances set channel via sta.config(), above + msg = b"sent to right channel 1" + e.add_peer(peer, channel=CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + sta.config(channel=WRONG_CHANNEL) + msg = b"sent to wrong channel" + e.add_peer(peer, channel=WRONG_CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + # switching back to the correct channel should also work + sta.config(channel=CHANNEL) + msg = b"sent to right channel 2" + e.add_peer(peer, channel=CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + e.active(False) diff --git a/tests/multi_espnow/70_channel.py.exp b/tests/multi_espnow/70_channel.py.exp new file mode 100644 index 00000000000..d8553966032 --- /dev/null +++ b/tests/multi_espnow/70_channel.py.exp @@ -0,0 +1,13 @@ +--- instance0 --- +3 +b'sent to right channel 1' +b'sent to right channel 1' +b'sent to right channel 1' +b'sent to right channel 2' +b'sent to right channel 2' +b'sent to right channel 2' +Timeout +--- instance1 --- +3 +8 +3 From 951a10e7078413d2dee2758fc2b7cb292e2b50b7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 10:16:25 +1100 Subject: [PATCH 0056/2098] esp32: Fix setting WLAN channel in AP mode. - Previously the call to esp_wifi_set_channel() would be immediately overridden by calling esp_wifi_config(...) with the previous channel set. - AP interface doesn't seem to need more than esp_wifi_config(...) to work. It will automatically configure 40MHz bandwidth and place the secondary channel using similar logic to what was being explicitly calculated here. - However, calling esp_wifi_set_channel() on the STA interface is necessary if using this interface with ESP-NOW (without connecting to an AP). So the esp_wifi_set_channel() call is kept in for this purpose. Without this, tests/multi_espnow/70_channel.py fails. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/network_wlan.c | 43 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index d415c56d321..6509b4fc6aa 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -549,21 +549,38 @@ static mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_ break; } case MP_QSTR_channel: { - uint8_t primary; - wifi_second_chan_t secondary; - // Get the current value of secondary - esp_exceptions(esp_wifi_get_channel(&primary, &secondary)); - primary = mp_obj_get_int(kwargs->table[i].value); - esp_err_t err = esp_wifi_set_channel(primary, secondary); - if (err == ESP_ERR_INVALID_ARG) { - // May need to swap secondary channel above to below or below to above - secondary = ( - (secondary == WIFI_SECOND_CHAN_ABOVE) - ? WIFI_SECOND_CHAN_BELOW - : (secondary == WIFI_SECOND_CHAN_BELOW) + uint8_t channel = mp_obj_get_int(kwargs->table[i].value); + if (self->if_id == ESP_IF_WIFI_AP) { + cfg.ap.channel = channel; + } else { + // This setting is only used to determine the + // starting channel for a scan, so it can result in + // slightly faster connection times. + cfg.sta.channel = channel; + + // This additional code to directly set the channel + // on the STA interface is only relevant for ESP-NOW + // (when there is no STA connection attempt.) + uint8_t old_primary; + wifi_second_chan_t secondary; + // Get the current value of secondary + esp_exceptions(esp_wifi_get_channel(&old_primary, &secondary)); + esp_err_t err = esp_wifi_set_channel(channel, secondary); + if (err == ESP_ERR_INVALID_ARG) { + // May need to swap secondary channel above to below or below to above + secondary = ( + (secondary == WIFI_SECOND_CHAN_ABOVE) + ? WIFI_SECOND_CHAN_BELOW + : (secondary == WIFI_SECOND_CHAN_BELOW) ? WIFI_SECOND_CHAN_ABOVE : WIFI_SECOND_CHAN_NONE); - esp_exceptions(esp_wifi_set_channel(primary, secondary)); + err = esp_wifi_set_channel(channel, secondary); + } + esp_exceptions(err); + if (channel != old_primary) { + // Workaround the ESP-IDF Wi-Fi stack sometimes taking a moment to change channels + mp_hal_delay_ms(1); + } } break; } From 0e383a31b9560334b0c8d26b378deccc1c6af961 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 17:40:02 +1100 Subject: [PATCH 0057/2098] tests: Add basic wlan test. Includes adding some ESP8266 port output to the ignored output list for the multitest runner. This test passes on ESP8266 and various ESP32s (including talking to each other). Without the fix in the parent commit, ESP32 AP will fail if the station can report its channel (i.e. channel is wrong). Testing with a CYW43 (RPI_PICO_W) currently fails but I have some fixes to submit so it can pass as well. Signed-off-by: Angus Gratton --- tests/multi_wlan/01_ap_sta.py | 117 ++++++++++++++++++++++++++++++ tests/multi_wlan/01_ap_sta.py.exp | 13 ++++ tests/run-multitests.py | 11 ++- 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 tests/multi_wlan/01_ap_sta.py create mode 100644 tests/multi_wlan/01_ap_sta.py.exp diff --git a/tests/multi_wlan/01_ap_sta.py b/tests/multi_wlan/01_ap_sta.py new file mode 100644 index 00000000000..1a8e80cd33a --- /dev/null +++ b/tests/multi_wlan/01_ap_sta.py @@ -0,0 +1,117 @@ +# Basic Wi-Fi MAC layer test where one device creates an Access Point and the +# other device connects to it as a Station. Also tests channel assignment (where +# possible) and disconnection. + +try: + import network + + network.WLAN +except (ImportError, NameError): + print("SKIP") + raise SystemExit + +import os +import sys +import time + +CHANNEL = 8 + +# Note that on slower Wi-Fi stacks this bumps up against the run-multitests.py +# timeout which expects <10s between lines of output. We work around this by +# logging something half way through the wait_for loop... +CONNECT_TIMEOUT = 15000 + + +def wait_for(test_func): + has_printed = False + start = time.ticks_ms() + while not test_func(): + time.sleep(0.1) + delta = time.ticks_diff(time.ticks_ms(), start) + if not has_printed and delta > CONNECT_TIMEOUT / 2: + print("...") + has_printed = True + elif delta > CONNECT_TIMEOUT: + break + + if not has_printed: + print("...") # keep the output consistent + + return test_func() + + +# AP +def instance0(): + ap = network.WLAN(network.WLAN.IF_AP) + ssid = "MP-test-" + os.urandom(6).hex() + psk = "Secret-" + os.urandom(6).hex() + + # stop any previous activity + network.WLAN(network.WLAN.IF_STA).active(False) + ap.active(False) + + ap.active(True) + ap.config(ssid=ssid, key=psk, channel=CHANNEL, security=network.WLAN.SEC_WPA_WPA2) + + # print("AP setup", ssid, psk) + print("AP started") + + multitest.globals(SSID=ssid, PSK=psk) + multitest.next() + + # Wait for station + if not wait_for(ap.isconnected): + raise RuntimeError("Timed out waiting for station, status ", ap.status()) + + print("AP got station") + time.sleep( + 3 + ) # depending on port, may still need to negotiate DHCP lease for STA to see connection + + print("AP disabling...") + ap.active(False) + + +# STA +def instance1(): + sta = network.WLAN(network.WLAN.IF_STA) + + # stop any previous activity + network.WLAN(network.WLAN.IF_AP).active(False) + sta.active(False) + + multitest.next() + ssid = SSID + psk = PSK + + # print("STA setup", ssid, psk) + + sta.active(True) + sta.connect(ssid, psk) + + print("STA connecting...") + + if not wait_for(sta.isconnected): + raise RuntimeError("Timed out waiting to connect, status ", sta.status()) + + print("STA connected") + + # Print the current channel, if the port support this + try: + print("channel", sta.config("channel")) + except OSError as e: + if "AP" in str(e): + # ESP8266 only supports reading channel on the AP interface, so fake this result + print("channel", CHANNEL) + else: + raise + + print("STA waiting for disconnect...") + + # Expect the AP to disconnect us immediately + if not wait_for(lambda: not sta.isconnected()): + raise RuntimeError("Timed out waiting for AP to disconnect us, status ", sta.status()) + + print("STA disconnected") + + sta.active(False) diff --git a/tests/multi_wlan/01_ap_sta.py.exp b/tests/multi_wlan/01_ap_sta.py.exp new file mode 100644 index 00000000000..8fc023a3980 --- /dev/null +++ b/tests/multi_wlan/01_ap_sta.py.exp @@ -0,0 +1,13 @@ +--- instance0 --- +AP started +... +AP got station +AP disabling... +--- instance1 --- +STA connecting... +... +STA connected +channel 8 +STA waiting for disconnect... +... +STA disconnected diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 93a6d3844d2..387eec7018b 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -105,15 +105,14 @@ def output_metric(data): multitest.flush() """ -# The btstack implementation on Unix generates some spurious output that we -# can't control. Also other platforms may output certain warnings/errors that -# can be safely ignored. +# Some ports generate output we can't control, and that can be safely ignored. IGNORE_OUTPUT_MATCHES = ( - "libusb: error ", # It tries to open devices that it doesn't have access to (libusb prints unconditionally). + "libusb: error ", # unix btstack tries to open devices that it doesn't have access to (libusb prints unconditionally). "hci_transport_h2_libusb.c", # Same issue. We enable LOG_ERROR in btstack. - "USB Path: ", # Hardcoded in btstack's libusb transport. - "hci_number_completed_packet", # Warning from btstack. + "USB Path: ", # Hardcoded in unix btstack's libusb transport. + "hci_number_completed_packet", # Warning from unix btstack. "lld_pdu_get_tx_flush_nb HCI packet count mismatch (", # From ESP-IDF, see https://github.com/espressif/esp-idf/issues/5105 + " ets_task(", # ESP8266 port debug output ) From ed3c75a3af16405508f7bfa66257abd7b53b4a9d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 16:48:56 +1100 Subject: [PATCH 0058/2098] esp32: Use hardware version for touchpad macro defines. ESP32 has hardware V1 and S2/S3 has V2, and future chips may have different versions. This should still compile to the same binary before and after. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_touchpad.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index 7612063e51d..a35e7fccda2 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -29,12 +29,14 @@ #include "modmachine.h" #include "driver/gpio.h" -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_TOUCH_SENSOR_SUPPORTED -#if CONFIG_IDF_TARGET_ESP32 +#if SOC_TOUCH_VERSION_1 // ESP32 only #include "driver/touch_pad.h" -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#elif SOC_TOUCH_VERSION_2 // All other SoCs with touch, to date #include "driver/touch_sensor.h" +#else +#error "Unknown touch hardware version" #endif typedef struct _mtp_obj_t { @@ -70,6 +72,8 @@ static const mtp_obj_t touchpad_obj[] = { {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM12}, {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM13}, {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM14}, + #else + #error "Please add GPIO mapping for this SoC" #endif }; @@ -92,14 +96,14 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ if (!initialized) { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #if TOUCH_HW_VER == 2 touch_pad_fsm_start(); #endif initialized = 1; } - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 esp_err_t err = touch_pad_config(self->touchpad_id, 0); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { @@ -110,10 +114,10 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ static mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 uint16_t value = mp_obj_get_int(value_in); esp_err_t err = touch_pad_config(self->touchpad_id, value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { @@ -125,10 +129,10 @@ MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); static mp_obj_t mtp_read(mp_obj_t self_in) { mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 uint16_t value; esp_err_t err = touch_pad_read(self->touchpad_id, &value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 uint32_t value; esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); #endif @@ -155,4 +159,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &mtp_locals_dict ); -#endif +#endif // SOC_TOUCH_SENSOR_SUPPORTED From 66e699e8a538ed7c1dc2d65035e5d93643e56667 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 17:14:09 +1100 Subject: [PATCH 0059/2098] esp32: Fix machine.TouchPad startup on ESP32-S2 and S3. Closes #13178. TouchPad confirmed working on both chips, and fixes the the ESP32-S3 reading constant max value. Was unable to reproduce the bug on ESP32-S2 but this may be due to my test setup, and it still works with the fix. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_touchpad.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index a35e7fccda2..48250280bad 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -96,9 +96,6 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ if (!initialized) { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - #if TOUCH_HW_VER == 2 - touch_pad_fsm_start(); - #endif initialized = 1; } #if SOC_TOUCH_VERSION_1 @@ -107,6 +104,10 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { + #if SOC_TOUCH_VERSION_2 + touch_pad_fsm_start(); + #endif + return MP_OBJ_FROM_PTR(self); } mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); From 154d1419659795aa6785778e4ddd6f23f09f2861 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 18:06:48 +1100 Subject: [PATCH 0060/2098] docs,esp32: Update machine.TouchPad docs for ESP32-S2 and ESP32-S3. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index b9ca0f8225f..5cce96d6878 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -751,20 +751,33 @@ APA102 (DotStar) uses a different driver as it has an additional clock pin. Capacitive touch ---------------- -Use the ``TouchPad`` class in the ``machine`` module:: +ESP32, ESP32-S2 and ESP32-S3 support capacitive touch via the ``TouchPad`` class +in the ``machine`` module:: from machine import TouchPad, Pin t = TouchPad(Pin(14)) t.read() # Returns a smaller number when touched -``TouchPad.read`` returns a value relative to the capacitive variation. Small numbers (typically in -the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when -no touch is present. However the values are *relative* and can vary depending on the board -and surrounding composition so some calibration may be required. - -There are ten capacitive touch-enabled pins that can be used on the ESP32: 0, 2, 4, 12, 13 -14, 15, 27, 32, 33. Trying to assign to any other pins will result in a ``ValueError``. +``TouchPad.read`` returns a value proportional to the capacitance between the +pin and the board's Ground connection. On ESP32 the number becomes smaller when +the pin (or connected touch pad) is touched, on ESP32-S2 and ESP32-S3 the number +becomes larger when the pin is touched. + +In all cases, a touch causes a significant change in the return value. Note the +returned values are *relative* and can vary depending on the board and +surrounding environment so some calibration (i.e. comparison to a baseline or +rolling average) may be required. + +========= ============================================== +Chip Touch-enabled pins +--------- ---------------------------------------------- +ESP32 0, 2, 4, 12, 13, 14, 15, 27, 32, 33 +ESP32-S2 1 to 14 inclusive +ESP32-S3 1 to 14 inclusive +========= ============================================== + +Trying to assign to any other pins will result in a ``ValueError``. Note that TouchPads can be used to wake an ESP32 from sleep:: From e70048cf5934bf3db65ec7a1b6490635716282f5 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Thu, 14 Nov 2024 10:15:10 +0000 Subject: [PATCH 0061/2098] extmod/modframebuf: Fix 0 radius bug in FrameBuffer.ellipse. This fixes a bug in FrameBuffer.ellipse where it goes into an infinite loop if both radii are 0. This fixes the bug with a simple pre-check to see if both radii are 0, and in that case sets a single pixel at the center. This is consistent with the behaviour of the method when called with just one of the radii set to 0, where it will draw a horizontal or vertical line of 1 pixel width. The pixel is set with setpixel_checked so it should handle out-of-bounds drawing correctly. This fix also includes three new tests: one for the default behaviour, one for drawing out-of-bounds, and one for when the sector mask is 0. Fixes issue #16053. Signed-off-by: Corran Webster --- extmod/modframebuf.c | 4 ++ tests/extmod/framebuf_ellipse.py | 15 +++++ tests/extmod/framebuf_ellipse.py.exp | 96 ++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index cd0f50d1044..b718a66cc62 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -536,6 +536,10 @@ static mp_obj_t framebuf_ellipse(size_t n_args, const mp_obj_t *args_in) { } else { mask |= ELLIPSE_MASK_ALL; } + if (args[2] == 0 && args[3] == 0) { + setpixel_checked(self, args[0], args[1], args[4], mask & ELLIPSE_MASK_ALL); + return mp_const_none; + } mp_int_t two_asquare = 2 * args[2] * args[2]; mp_int_t two_bsquare = 2 * args[3] * args[3]; mp_int_t x = args[2]; diff --git a/tests/extmod/framebuf_ellipse.py b/tests/extmod/framebuf_ellipse.py index a4c784aff87..ec0461e66ca 100644 --- a/tests/extmod/framebuf_ellipse.py +++ b/tests/extmod/framebuf_ellipse.py @@ -63,3 +63,18 @@ def printbuf(): fbuf.fill(0) fbuf.ellipse(x, y, 6, 12, 0xAA, True) printbuf() + +# Draw an ellipse with both radius 0 +fbuf.fill(0) +fbuf.ellipse(15, 15, 0, 0, 0xFF, True) +printbuf() + +# Draw an ellipse with both radius 0 out of bounds +fbuf.fill(0) +fbuf.ellipse(45, 45, 0, 0, 0xFF, True) +printbuf() + +# Draw an ellipse with radius 0 and all sectors masked out +fbuf.fill(0) +fbuf.ellipse(15, 15, 0, 0, 0xFF, True, 0) +printbuf() diff --git a/tests/extmod/framebuf_ellipse.py.exp b/tests/extmod/framebuf_ellipse.py.exp index ae6ad1ee7e4..96a60c24da1 100644 --- a/tests/extmod/framebuf_ellipse.py.exp +++ b/tests/extmod/framebuf_ellipse.py.exp @@ -702,3 +702,99 @@ aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 -->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000ff0000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- From da692d01ac6c09286ee682c0d4a3780777c67a8f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 27 Nov 2024 16:57:02 +0100 Subject: [PATCH 0062/2098] nrf/drivers/ticker: Reset slow ticker callback count on soft reboot. The micro:bit board (and probably other boards using the music or display module) locked up on soft reboot. Reason was a buffer overflow caused by an index counter, which was not reset on soft_reboot. That's fixed in this commit. Tested with a micro:bit board, performing a series of soft reboots. Signed-off-by: robert-hh --- ports/nrf/drivers/ticker.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/nrf/drivers/ticker.c b/ports/nrf/drivers/ticker.c index c2fa31e7c20..e9c07c842ec 100644 --- a/ports/nrf/drivers/ticker.c +++ b/ports/nrf/drivers/ticker.c @@ -62,6 +62,7 @@ void ticker_init0(void) { #else NRFX_IRQ_PRIORITY_SET(FastTicker_IRQn, 2); #endif + m_num_of_slow_tickers = 0; NRFX_IRQ_PRIORITY_SET(SlowTicker_IRQn, 3); From 3b3b48892f96e2c81e7887f996fd7ff457acbb5d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:15:20 +1100 Subject: [PATCH 0063/2098] py/objfloat: Workaround non-constant NAN definition on Windows MSVC. Recent MSVC versions have changed the definition of NAN to a non-constant expression! This is a bug, C standard says it should be a constant. Good explanation and workaround at: https://stackoverflow.com/a/79199887 This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/objfloat.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/objfloat.c b/py/objfloat.c index 5c90b1491ce..0728fce3151 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -47,6 +47,13 @@ #define M_PI (3.14159265358979323846) #endif +// Workaround a bug in recent MSVC where NAN is no longer constant. +// (By redefining back to the previous MSVC definition of NAN) +#if defined(_MSC_VER) && _MSC_VER >= 1942 +#undef NAN +#define NAN (-(float)(((float)(1e+300 * 1e+300)) * 0.0F)) +#endif + typedef struct _mp_obj_float_t { mp_obj_base_t base; mp_float_t value; From 8e11e5f1a14ca3395afaab2e42edebe41221eba1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:46:21 +1100 Subject: [PATCH 0064/2098] tests/misc/sys_settrace_features.py: Add note about CPython 3.12 issue. CPython 3.12 has a documented issue with settrace for opcodes, apparently due to PEP 669. "This behavior will be changed back in 3.13 to be consistent with previous versions." No easy way to make the test pass on CPython 3.12, but at least this helps signal what the problem is to anyone who runs into a failure. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/misc/sys_settrace_features.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index 8ca6b382e3c..6eeb2b900f1 100644 --- a/tests/misc/sys_settrace_features.py +++ b/tests/misc/sys_settrace_features.py @@ -6,6 +6,10 @@ print("SKIP") raise SystemExit +if sys.version.startswith("3.12"): + # There is a CPython change in settrace that is reverted in 3.13! + print("WARNING: this test will fail when compared to CPython 3.12.x behaviour") + def print_stacktrace(frame, level=0): # Ignore CPython specific helpers. From 8ec067272a2b03c182f4723026e1d94dd6a44835 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:28:33 +1100 Subject: [PATCH 0065/2098] github/workflows: Workaround using CPython 3.12 in MSYS2 builds. Once MSYS2 repository updates past Python 3.12, this commit can be reverted. Explanation: CPython 3.12 can't pass sys_settrace_features test (see parent commit for explanation). MSYS2 mingw-w64-ARCH-python package is currently 3.12.7. MSYS2 doesn't recommend installing old packages from their archive (due to library dependencies), so switch to the GitHub CI setup-python action for now. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_windows.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 91a3192a10a..84e018ba15d 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -110,6 +110,11 @@ jobs: run: shell: msys2 {0} steps: + - uses: actions/setup-python@v5 + # note: can go back to installing mingw-w64-${{ matrix.env }}-python after + # MSYS2 updates to Python >3.12 (due to settrace compatibility issue) + with: + python-version: '3.11' - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.sys }} @@ -118,9 +123,9 @@ jobs: make mingw-w64-${{ matrix.env }}-gcc pkg-config - mingw-w64-${{ matrix.env }}-python3 git diffutils + path-type: inherit # Remove when setup-python is removed - uses: actions/checkout@v4 - name: Build mpy-cross.exe run: make -C mpy-cross -j2 From eb80b04944e01d437efc2fa29189ad1e2bdd997f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:22:56 +1100 Subject: [PATCH 0066/2098] tests/extmod: Workaround CPython warning in asyncio_new_event_loop test. This started failing in CI on the mingw build, after CPython updated to 3.12.7. The test prints two warnings during interpreter shutdown of "Task was destroyed but it is pending!". This didn't happen on other CPython builds, and I think that's because of finalizer order in CPython interpreter shutdown but not certain (the loop finalizer calls loop.close() if not already closed). Adding explicit calls to loop.close() causes the warning to be printed on every run with CPython 3.12.7 on Linux. Next, added the workaround exception handler to swallow this exception as MicroPython doesn't produce an equivalent. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/extmod/asyncio_new_event_loop.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/extmod/asyncio_new_event_loop.py b/tests/extmod/asyncio_new_event_loop.py index bebc3bf70cc..3f05ffdd551 100644 --- a/tests/extmod/asyncio_new_event_loop.py +++ b/tests/extmod/asyncio_new_event_loop.py @@ -12,6 +12,16 @@ asyncio.set_event_loop(asyncio.new_event_loop()) +def exception_handler(loop, context): + # This is a workaround for a difference between CPython and MicroPython: if + # a CPython event loop is closed while there are tasks pending (i.e. not finished) + # on it, then the task will log an error. MicroPython does not log this error. + if context.get("message", "") == "Task was destroyed but it is pending!": + pass + else: + loop.default_exception_handler(context) + + async def task(): for i in range(4): print("task", i) @@ -22,17 +32,21 @@ async def task(): async def main(): print("start") loop.create_task(task()) - await asyncio.sleep(0) + await asyncio.sleep(0) # yields, meaning new task will run once print("stop") loop.stop() # Use default event loop to run some tasks loop = asyncio.get_event_loop() +loop.set_exception_handler(exception_handler) loop.create_task(main()) loop.run_forever() +loop.close() # Create new event loop, old one should not keep running loop = asyncio.new_event_loop() +loop.set_exception_handler(exception_handler) loop.create_task(main()) loop.run_forever() +loop.close() From d49c1e836cc7497f4bd5385491214173af6410ed Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 26 Nov 2024 15:50:13 +1100 Subject: [PATCH 0067/2098] tools/ci.sh: Remove explicit macOS pkg-config install. Reasons to remove this: - GitHub's macOS runners install this package by default nowadays. - Brew renamed this package to 'pkgconf' so installing the old name on top of the new package name has started failing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/ci.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 4e6f09b0bb8..b9f9dec8bc2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -651,9 +651,6 @@ function ci_unix_settrace_stackless_run_tests { } function ci_unix_macos_build { - # Install pkg-config to configure libffi paths. - brew install pkg-config - make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix submodules #make ${MAKEOPTS} -C ports/unix deplibs From 6dd976cc5c42ebed8fec50e4e6aec1fb37347080 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:34:09 +0000 Subject: [PATCH 0068/2098] github/workflows: Bump codecov/codecov-action from 4 to 5. Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ports_unix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 1707fdc9fb3..64fd5c902a0 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -88,7 +88,7 @@ jobs: (cd ports/unix && gcov -o build-coverage/py ../../py/*.c || true) (cd ports/unix && gcov -o build-coverage/extmod ../../extmod/*.c || true) - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true verbose: true From beabef5aac2c578f6b2f87d42bfccfa4ff074416 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 14:32:25 +1100 Subject: [PATCH 0069/2098] esp32: Add missing network.STAT_CONNECT_FAIL constant. The esp32 port had network.STAT_ASSOC_FAIL for the same purpose, but this is undocumented and different to all other ports. That constant is now deprecated. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/modnetwork_globals.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index c6092bd1421..2bfc2393d66 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -82,5 +82,9 @@ #endif { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(WIFI_REASON_AUTH_FAIL)}, { MP_ROM_QSTR(MP_QSTR_STAT_BEACON_TIMEOUT), MP_ROM_INT(WIFI_REASON_BEACON_TIMEOUT)}, +#if !MICROPY_PREVIEW_VERSION_2 +// Deprecated, use STAT_CONNECT_FAIL same as other ports { MP_ROM_QSTR(MP_QSTR_STAT_ASSOC_FAIL), MP_ROM_INT(WIFI_REASON_ASSOC_FAIL)}, +#endif +{ MP_ROM_QSTR(MP_QSTR_STAT_CONNECT_FAIL), MP_ROM_INT(WIFI_REASON_ASSOC_FAIL)}, { MP_ROM_QSTR(MP_QSTR_STAT_HANDSHAKE_TIMEOUT), MP_ROM_INT(WIFI_REASON_HANDSHAKE_TIMEOUT)}, From 579e840de6d71503658f3c427c1e210c7cb85e9e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Nov 2024 19:07:17 +0100 Subject: [PATCH 0070/2098] extmod/modplatform: Distinguish AArch64 from AArch32. This commit adds a new platform architecture name for Arm CPUs running in 64 bits mode ("aarch64"). The 32 bits name is left as "arm" to maintain compatibility with existing code. Signed-off-by: Alessandro Gatti --- extmod/modplatform.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extmod/modplatform.h b/extmod/modplatform.h index b932551c7cc..0c83d7b8c5d 100644 --- a/extmod/modplatform.h +++ b/extmod/modplatform.h @@ -36,7 +36,11 @@ // See: https://sourceforge.net/p/predef/wiki/Home/ #if defined(__ARM_ARCH) +#if defined(__ARM_ARCH_ISA_A64) +#define MICROPY_PLATFORM_ARCH "aarch64" +#else #define MICROPY_PLATFORM_ARCH "arm" +#endif #elif defined(__x86_64__) || defined(_M_X64) #define MICROPY_PLATFORM_ARCH "x86_64" #elif defined(__i386__) || defined(_M_IX86) From 09cf01d7c1f564fd018bd8fb3e6382fbe8cd1e35 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Nov 2024 19:11:30 +0100 Subject: [PATCH 0071/2098] extmod/modplatform: Add Clang to the known compilers list. This commit adds support to distinguish between GCC and Clang to report the appropriate compiler version. Usually Clang also disguises itself as GCC for compatibility reasons, but these changes look for Clang-specific definitions first to avoid that problem. Signed-off-by: Alessandro Gatti --- extmod/modplatform.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extmod/modplatform.h b/extmod/modplatform.h index 0c83d7b8c5d..19246c0fda1 100644 --- a/extmod/modplatform.h +++ b/extmod/modplatform.h @@ -53,7 +53,13 @@ #define MICROPY_PLATFORM_ARCH "" #endif -#if defined(__GNUC__) +#if defined(__clang__) +#define MICROPY_PLATFORM_COMPILER \ + "Clang " \ + MP_STRINGIFY(__clang_major__) "." \ + MP_STRINGIFY(__clang_minor__) "." \ + MP_STRINGIFY(__clang_patchlevel__) +#elif defined(__GNUC__) #define MICROPY_PLATFORM_COMPILER \ "GCC " \ MP_STRINGIFY(__GNUC__) "." \ From 3de3821abf7e245566be227c9799b74d6b80aae3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Nov 2024 19:13:48 +0100 Subject: [PATCH 0072/2098] extmod/modplatform: Add Android to the recognised platforms list. This commit adds code to distinguish between regular Linux and Android, also adding a specific entry for the platform libc. The reported libc is marked as "bionic" and its version matches the Android platform API version (there are no definitions for a specific bionic version). Signed-off-by: Alessandro Gatti --- extmod/modplatform.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extmod/modplatform.h b/extmod/modplatform.h index 19246c0fda1..5c9cbffb2a4 100644 --- a/extmod/modplatform.h +++ b/extmod/modplatform.h @@ -96,12 +96,17 @@ #elif defined(_PICOLIBC__) #define MICROPY_PLATFORM_LIBC_LIB "picolibc" #define MICROPY_PLATFORM_LIBC_VER _PICOLIBC_VERSION +#elif defined(__ANDROID__) +#define MICROPY_PLATFORM_LIBC_LIB "bionic" +#define MICROPY_PLATFORM_LIBC_VER MP_STRINGIFY(__ANDROID_API__) #else #define MICROPY_PLATFORM_LIBC_LIB "" #define MICROPY_PLATFORM_LIBC_VER "" #endif -#if defined(__linux) +#if defined(__ANDROID__) +#define MICROPY_PLATFORM_SYSTEM "Android" +#elif defined(__linux) #define MICROPY_PLATFORM_SYSTEM "Linux" #elif defined(__unix__) #define MICROPY_PLATFORM_SYSTEM "Unix" From 2e796d6c3e33e2d3b1936cd05aa926400ad1d5b7 Mon Sep 17 00:00:00 2001 From: chuangjinglu Date: Mon, 25 Nov 2024 11:06:15 +0800 Subject: [PATCH 0073/2098] docs,ports: Fix some comments and error messages with doubled-up words. Signed-off-by: chuangjinglu --- docs/library/bluetooth.rst | 2 +- docs/library/machine.TimerWiPy.rst | 2 +- docs/library/pyb.Timer.rst | 2 +- docs/reference/manifest.rst | 2 +- ports/cc3200/FreeRTOS/Source/tasks.c | 2 +- ports/stm32/timer.c | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/library/bluetooth.rst b/docs/library/bluetooth.rst index f8b154dd114..b09c370abd4 100644 --- a/docs/library/bluetooth.rst +++ b/docs/library/bluetooth.rst @@ -665,7 +665,7 @@ L2CAP connection-oriented-channels Connect to a listening peer on the specified *psm* with local MTU set to *mtu*. - On successful connection, the the ``_IRQ_L2CAP_CONNECT`` event will be + On successful connection, the ``_IRQ_L2CAP_CONNECT`` event will be raised, allowing the client to obtain the CID and the local and remote (peer) MTU. An unsuccessful connection will raise the ``_IRQ_L2CAP_DISCONNECT`` event diff --git a/docs/library/machine.TimerWiPy.rst b/docs/library/machine.TimerWiPy.rst index f8c8bb29da7..54280a5994a 100644 --- a/docs/library/machine.TimerWiPy.rst +++ b/docs/library/machine.TimerWiPy.rst @@ -71,7 +71,7 @@ Methods Otherwise, a TimerChannel object is initialized and returned. - The operating mode is is the one configured to the Timer object that was used to + The operating mode is the one configured to the Timer object that was used to create the channel. - ``channel`` if the width of the timer is 16-bit, then must be either ``TIMER.A``, ``TIMER.B``. diff --git a/docs/library/pyb.Timer.rst b/docs/library/pyb.Timer.rst index 1749efce2d5..c644e38d786 100644 --- a/docs/library/pyb.Timer.rst +++ b/docs/library/pyb.Timer.rst @@ -163,7 +163,7 @@ Methods - ``callback`` - as per TimerChannel.callback() - ``pin`` None (the default) or a Pin object. If specified (and not None) - this will cause the alternate function of the the indicated pin + this will cause the alternate function of the indicated pin to be configured for this timer channel. An error will be raised if the pin doesn't support any alternate functions for this timer channel. diff --git a/docs/reference/manifest.rst b/docs/reference/manifest.rst index 1a80b1259e3..dc9d6dd75b2 100644 --- a/docs/reference/manifest.rst +++ b/docs/reference/manifest.rst @@ -26,7 +26,7 @@ re-flashing the entire firmware. However, it can still be useful to selectively freeze some rarely-changing dependencies (such as third-party libraries). -The way to list the Python files to be be frozen into the firmware is via +The way to list the Python files to be frozen into the firmware is via a "manifest", which is a Python file that will be interpreted by the build process. Typically you would write a manifest file as part of a board definition, but you can also write a stand-alone manifest file and use it with diff --git a/ports/cc3200/FreeRTOS/Source/tasks.c b/ports/cc3200/FreeRTOS/Source/tasks.c index 6c261a65101..3d35c667274 100644 --- a/ports/cc3200/FreeRTOS/Source/tasks.c +++ b/ports/cc3200/FreeRTOS/Source/tasks.c @@ -1671,7 +1671,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE ) { /* Is it in the suspended list because it is in the Suspended - state, or because is is blocked with no timeout? */ + state, or because it is blocked with no timeout? */ if( listIS_CONTAINED_WITHIN( NULL, &( pxTCB->xEventListItem ) ) != pdFALSE ) { xReturn = pdTRUE; diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index d999e57462e..acfdbd84eb2 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -1089,7 +1089,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pyb_timer_deinit_obj, pyb_timer_deinit); /// - `callback` - as per TimerChannel.callback() /// /// - `pin` None (the default) or a Pin object. If specified (and not None) -/// this will cause the alternate function of the the indicated pin +/// this will cause the alternate function of the indicated pin /// to be configured for this timer channel. An error will be raised if /// the pin doesn't support any alternate functions for this timer channel. /// @@ -1197,7 +1197,7 @@ static mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma mp_obj_t pin_obj = args[2].u_obj; if (pin_obj != mp_const_none) { if (!mp_obj_is_type(pin_obj, &pin_type)) { - mp_raise_ValueError(MP_ERROR_TEXT("pin argument needs to be be a Pin type")); + mp_raise_ValueError(MP_ERROR_TEXT("pin argument needs to be a Pin type")); } const machine_pin_obj_t *pin = MP_OBJ_TO_PTR(pin_obj); const pin_af_obj_t *af = pin_find_af(pin, AF_FN_TIM, self->tim_id); From 406bccc75307bd09fbf20150a29c587e9f12771b Mon Sep 17 00:00:00 2001 From: Amirreza Hamzavi Date: Thu, 23 Nov 2023 21:43:47 +0330 Subject: [PATCH 0074/2098] docs/library/binascii: Add docs for binascii.crc32 method. Signed-off-by: Amirreza Hamzavi --- docs/library/binascii.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/library/binascii.rst b/docs/library/binascii.rst index 6c02f019aaa..c728c5a8a57 100644 --- a/docs/library/binascii.rst +++ b/docs/library/binascii.rst @@ -36,3 +36,9 @@ Functions Encode binary data in base64 format, as in `RFC 3548 `_. Returns the encoded data followed by a newline character if newline is true, as a bytes object. + +.. function:: crc32(data, [value]) + + Compute CRC-32, the 32-bit checksum of *data*, starting with an initial CRC + of *value*. The default initial CRC is zero. The algorithm is consistent + with the ZIP file checksum. From a2554f09573e0a856d9abf9e718bac9977a510db Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Nov 2024 16:26:15 +1100 Subject: [PATCH 0075/2098] tests/run-tests.py: Add support for tests to use unittest. All the existing tests require a .exp file (either manually specified or generated running the test first under CPython) that is used to check the output of running the test under MicroPython. The test passes if the output matches the expected output exactly. This has worked very well for a long time now. But some of the newer hardware tests (eg UART, SPI, PWM) don't really fit this model, for the following main reasons: - Some but not all parts of the test should be skipped on certain hardware targets. With the expected-output approach, skipping tests is either all or nothing. - It's often useful to output diagnostics as part of the test, which should not affect the result of the test (eg the diagnostics change from run to run, like timing values, or from target to target). - Sometimes a test will do a complex check and then print False/True if it passed or not, which obscures the actual test result. To improve upon this, this commit adds support to `run-tests.py` for a test to use `unittest`. It detects this by looking at the end of the output after running the test, looking for the test summary printed by `unittest` (or an error message saying `unittest` was not found). If the test uses `unittest` then it should not have a .exp file, and it's not run under CPython. A `unittest` based test passes or fails based on the summary printed by `unittest`. Note that (as long as `unittest` is installed on the target) the tests are still fully independent and you can still run them without `run-tests.py`: you just run it as usual, eg `mpremote run `. This is very useful when creating and debugging tests. Note also that the standard test suite testing Python semantics (eg everything in `tests/basics/`) will probably never use unittest. Only more advanced tests will, and ones that are not runnable under CPython. Signed-off-by: Damien George --- tests/README.md | 35 ++++++++--- tests/run-tests.py | 141 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 132 insertions(+), 44 deletions(-) diff --git a/tests/README.md b/tests/README.md index b39833aa096..28c1b3a0852 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,6 +15,32 @@ That will run tests on the `/dev/ttyACM0` serial port. You can also use shortcu device names like `a` for `/dev/ttyACM` and `c` for `COM`. Use `./run-tests.py --help` to see all of the device possibilites, and other options. +There are three kinds of tests: + +* Tests that use `unittest`: these tests require `unittest` to be installed on the + target (eg via `mpremote mip install unittest`), and are used to test things that are + MicroPython-specific, such as behaviour that is different to CPython, modules that + aren't available in CPython, and hardware tests. These tests are run only under + MicroPython and the test passes if the `unittest` runner prints "OK" at the end of the + run. Other output may be printed, eg for use as diagnostics, and this output does not + affect the result of the test. + +* Tests with a corresponding `.exp` file: similar to the `unittest` tests, these tests + are for features that generally cannot be run under CPython. In this case the test is + run under MicroPython only and the output from MicroPython is compared against the + provided `.exp` file. The test passes if the output matches exactly. + +* Tests without a corresponding `.exp` file (and don't use `unittest`): these tests are + used to test MicroPython behaviour that should precisely match CPython. These tests + are first run under CPython and the output captured, and then run under MicroPython + and the output compared to the CPython output. The test passes if the output matches + exactly. If the output differs then the test fails and the outputs are saved in a + `.exp` and a `.out` file respectively. + +In all three cases above, the test can usually be run directly on the target MicroPython +instance, either using the unix port with `micropython `, or on a board with +`mpremote run `. This is useful for creating and debugging tests. + Tests of capabilities not supported on all platforms should be written to check for the capability being present. If it is not, the test should merely output 'SKIP' followed by the line terminator, and call @@ -27,15 +53,6 @@ condition a test. The run-tests.py script uses small scripts in the feature_check directory to check whether each such feature is present, and skips the relevant tests if not. -Tests are generally verified by running the test both in MicroPython and -in CPython and comparing the outputs. If the output differs the test fails -and the outputs are saved in a .out and a .exp file respectively. -For tests that cannot be run in CPython, for example because they use -the machine module, a .exp file can be provided next to the test's .py -file. A convenient way to generate that is to run the test, let it fail -(because CPython cannot run it) and then copy the .out file (but not -before checking it manually!) - When creating new tests, anything that relies on float support should go in the float/ subdirectory. Anything that relies on import x, where x is not a built-in module, should go in the import/ subdirectory. diff --git a/tests/run-tests.py b/tests/run-tests.py index d0feb4bcd66..0e491ccd62b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -508,6 +508,10 @@ def run_feature_check(pyb, args, test_file): return run_micropython(pyb, args, test_file_path, test_file_path, is_special=True) +class TestError(Exception): + pass + + class ThreadSafeCounter: def __init__(self, start=0): self._value = start @@ -862,29 +866,10 @@ def run_one_test(test_file): skipped_tests.append(test_name) return - # get expected output - test_file_expected = test_file + ".exp" - if os.path.isfile(test_file_expected): - # expected output given by a file, so read that in - with open(test_file_expected, "rb") as f: - output_expected = f.read() - else: - # run CPython to work out expected output - try: - output_expected = subprocess.check_output( - CPYTHON3_CMD + [test_file_abspath], - cwd=os.path.dirname(test_file), - stderr=subprocess.STDOUT, - ) - except subprocess.CalledProcessError: - output_expected = b"CPYTHON3 CRASH" - - # canonical form for all host platforms is to use \n for end-of-line - output_expected = output_expected.replace(b"\r\n", b"\n") - - # run MicroPython + # Run the test on the MicroPython target. output_mupy = run_micropython(pyb, args, test_file, test_file_abspath) + # Check if the target requested to skip this test. if output_mupy == b"SKIP\n": if pyb is not None and hasattr(pyb, "read_until"): # Running on a target over a serial connection, and the target requested @@ -896,22 +881,96 @@ def run_one_test(test_file): skipped_tests.append(test_name) return - testcase_count.add(len(output_expected.splitlines())) + # Look at the output of the test to see if unittest was used. + uses_unittest = False + output_mupy_lines = output_mupy.splitlines() + if any( + line == b"ImportError: no module named 'unittest'" for line in output_mupy_lines[-3:] + ): + raise TestError( + ( + "error: test {} requires unittest".format(test_file), + "(eg run `mpremote mip install unittest` to install it)", + ) + ) + elif ( + len(output_mupy_lines) > 4 + and output_mupy_lines[-4] == b"-" * 70 + and output_mupy_lines[-2] == b"" + ): + # look for unittest summary + unittest_ran_match = re.match(rb"Ran (\d+) tests$", output_mupy_lines[-3]) + unittest_result_match = re.match( + b"(" + rb"(OK)( \(skipped=(\d+)\))?" + b"|" + rb"(FAILED) \(failures=(\d+), errors=(\d+)\)" + b")$", + output_mupy_lines[-1], + ) + uses_unittest = unittest_ran_match and unittest_result_match + + # Determine the expected output. + if uses_unittest: + # Expected output is result of running unittest. + output_expected = None + else: + test_file_expected = test_file + ".exp" + if os.path.isfile(test_file_expected): + # Expected output given by a file, so read that in. + with open(test_file_expected, "rb") as f: + output_expected = f.read() + else: + # Run CPython to work out expected output. + try: + output_expected = subprocess.check_output( + CPYTHON3_CMD + [test_file_abspath], + cwd=os.path.dirname(test_file), + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError: + output_expected = b"CPYTHON3 CRASH" + + # Canonical form for all host platforms is to use \n for end-of-line. + output_expected = output_expected.replace(b"\r\n", b"\n") + + # Work out if test passed or not. + test_passed = False + extra_info = "" + if uses_unittest: + test_passed = unittest_result_match.group(2) == b"OK" + num_test_cases = int(unittest_ran_match.group(1)) + extra_info = "unittest: {} ran".format(num_test_cases) + if test_passed and unittest_result_match.group(4) is not None: + num_skipped = int(unittest_result_match.group(4)) + num_test_cases -= num_skipped + extra_info += ", {} skipped".format(num_skipped) + elif not test_passed: + num_failures = int(unittest_result_match.group(6)) + num_errors = int(unittest_result_match.group(7)) + extra_info += ", {} failures, {} errors".format(num_failures, num_errors) + extra_info = "(" + extra_info + ")" + testcase_count.add(num_test_cases) + else: + testcase_count.add(len(output_expected.splitlines())) + test_passed = output_expected == output_mupy filename_expected = os.path.join(result_dir, test_basename + ".exp") filename_mupy = os.path.join(result_dir, test_basename + ".out") - if output_expected == output_mupy: - print("pass ", test_file) + # Print test summary, update counters, and save .exp/.out files if needed. + if test_passed: + print("pass ", test_file, extra_info) passed_count.increment() rm_f(filename_expected) rm_f(filename_mupy) else: - with open(filename_expected, "wb") as f: - f.write(output_expected) + print("FAIL ", test_file, extra_info) + if output_expected is not None: + with open(filename_expected, "wb") as f: + f.write(output_expected) with open(filename_mupy, "wb") as f: f.write(output_mupy) - print("FAIL ", test_file) failed_tests.append((test_name, test_file)) test_count.increment() @@ -919,12 +978,17 @@ def run_one_test(test_file): if pyb: num_threads = 1 - if num_threads > 1: - pool = ThreadPool(num_threads) - pool.map(run_one_test, tests) - else: - for test in tests: - run_one_test(test) + try: + if num_threads > 1: + pool = ThreadPool(num_threads) + pool.map(run_one_test, tests) + else: + for test in tests: + run_one_test(test) + except TestError as er: + for line in er.args[0]: + print(line) + sys.exit(1) print( "{} tests performed ({} individual testcases)".format( @@ -1189,8 +1253,15 @@ def main(): tests = args.files if not args.keep_path: - # clear search path to make sure tests use only builtin modules and those in extmod - os.environ["MICROPYPATH"] = ".frozen" + os.pathsep + base_path("../extmod") + # Clear search path to make sure tests use only builtin modules, those in + # extmod, and a path to unittest in case it's needed. + os.environ["MICROPYPATH"] = ( + ".frozen" + + os.pathsep + + base_path("../extmod") + + os.pathsep + + base_path("../lib/micropython-lib/python-stdlib/unittest") + ) try: os.makedirs(args.result_dir, exist_ok=True) From a8422439ab5e86eeaceae09c4655101225fe5fce Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 3 Dec 2024 12:31:50 +1100 Subject: [PATCH 0076/2098] tests/run-tests.py: Print .out file when there is no .exp file. So that a failing unittest-based test has its entire log printed when using `run-tests.py --print-failures`. Signed-off-by: Damien George --- tests/run-tests.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 0e491ccd62b..6fe45707bb6 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1154,11 +1154,18 @@ def main(): args = cmd_parser.parse_args() if args.print_failures: - for exp in glob(os.path.join(args.result_dir, "*.exp")): - testbase = exp[:-4] + for out in glob(os.path.join(args.result_dir, "*.out")): + testbase = out[:-4] print() print("FAILURE {0}".format(testbase)) - os.system("{0} {1}.exp {1}.out".format(DIFF, testbase)) + if os.path.exists(testbase + ".exp"): + # Show diff of expected and actual output. + os.system("{0} {1}.exp {1}.out".format(DIFF, testbase)) + else: + # No expected output, just show the actual output (eg from a unittest). + with open(out) as f: + for line in f: + print(line, end="") sys.exit(0) From 6dcbdec4de2975a08a50828a0b25e9b3844e04d0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Nov 2024 22:23:48 +1100 Subject: [PATCH 0077/2098] lib/micropython-lib: Update submodule to latest. This brings in: - umqtt.simple: add optional socket timeout to connect method - aioespnow,webrepl: use recommended network.WLAN.IF_[AP|STA] constants - unittest: allow SkipTest to work within a subTest - unittest: always use "raise" with an argument Signed-off-by: Damien George --- lib/micropython-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/micropython-lib b/lib/micropython-lib index 68e3e07bc7a..e4cf09527bc 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit 68e3e07bc7ab63931cead3854b2a114e9a084248 +Subproject commit e4cf09527bce7569f5db742cf6ae9db68d50c6a9 From 42f37e951b8990576e16bb7f5449fddeee0c0664 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Nov 2024 15:54:26 +1100 Subject: [PATCH 0078/2098] esp8266/mpconfigport: Enable function attributes. This allows `unittest` to work on esp8266 boards. Signed-off-by: Damien George --- ports/esp8266/mpconfigport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 6504127755e..83d80a7c963 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -23,7 +23,6 @@ #define MICROPY_OPT_MATH_FACTORIAL (0) #define MICROPY_REPL_EMACS_KEYS (0) #define MICROPY_PY_BUILTINS_COMPLEX (0) -#define MICROPY_PY_FUNCTION_ATTRS (0) #define MICROPY_PY_DELATTR_SETATTR (0) #define MICROPY_PY_BUILTINS_STR_CENTER (0) #define MICROPY_PY_BUILTINS_STR_PARTITION (0) From d3d29765863e7b9ed7ea9c68413b80405a75d0e9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Nov 2024 23:57:27 +1100 Subject: [PATCH 0079/2098] qemu/Makefile: Include unittest in firmware. So that this port can run unittest-based tests. Signed-off-by: Damien George --- ports/qemu/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 62601324d6a..237e1e9514c 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -19,10 +19,10 @@ QSTR_DEFS = qstrdefsport.h MICROPY_ROM_TEXT_COMPRESSION ?= 1 ifeq ($(QEMU_ARCH),arm) -FROZEN_MANIFEST ?= "freeze('test-frzmpy')" +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy')" endif ifeq ($(QEMU_ARCH),riscv32) -FROZEN_MANIFEST ?= "freeze('test-frzmpy', ('frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif # include py core make definitions From f62df1a2c2d27d602be29474843a19284d807358 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Nov 2024 16:26:39 +1100 Subject: [PATCH 0080/2098] tests/ports/stm32_hardware: Convert DMA test to use unittest. Signed-off-by: Damien George --- tests/ports/stm32_hardware/dma_alignment.py | 88 +++++++++++-------- .../ports/stm32_hardware/dma_alignment.py.exp | 2 - 2 files changed, 52 insertions(+), 38 deletions(-) delete mode 100644 tests/ports/stm32_hardware/dma_alignment.py.exp diff --git a/tests/ports/stm32_hardware/dma_alignment.py b/tests/ports/stm32_hardware/dma_alignment.py index 1836b25d86d..1c271d48e72 100644 --- a/tests/ports/stm32_hardware/dma_alignment.py +++ b/tests/ports/stm32_hardware/dma_alignment.py @@ -1,43 +1,59 @@ -from machine import SPI # Regression test for DMA for DCache coherency bugs with cache line # written originally for https://github.com/micropython/micropython/issues/13471 # IMPORTANT: This test requires SPI2 MISO (pin Y8 on Pyboard D) to be connected to GND +import unittest +from machine import SPI + SPI_NUM = 2 +BAUDRATE = 5_000_000 + + +class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.spi = SPI(SPI_NUM, baudrate=BAUDRATE) + cls.skip_slow_test = False + + def test_variable_offset_fixed_length(self): + buf = bytearray(1024) + for offs in range(0, len(buf)): + v = memoryview(buf)[offs : offs + 128] + self.spi.readinto(v, 0xFF) + ok = all(b == 0x00 for b in v) + if not ok: + print(offs, v.hex()) + self.skip_slow_test = True + self.assertTrue(ok) + + def test_variable_offset_and_lengths(self): + # this takes around 30s to run, so skipped if already failing + if self.skip_slow_test: + self.skipTest("already failing") + + buf = bytearray(1024) + for op_len in range(1, 66): + print(op_len) + wr = b"\xff" * op_len + for offs in range(1, len(buf) - op_len - 1): + # Place some "sentinel" values before and after the DMA buffer + before = offs & 0xFF + after = (~offs) & 0xFF + buf[offs - 1] = before + buf[offs + op_len] = after + v = memoryview(buf)[offs : offs + op_len] + self.spi.write_readinto(wr, v) + ok = ( + all(b == 0x00 for b in v) + and buf[offs - 1] == before + and buf[offs + op_len] == after + ) + if not ok: + print(v.hex()) + print(hex(op_len), hex(offs), hex(buf[offs - 1]), hex(buf[offs + op_len])) + self.assertTrue(ok) + -spi = SPI(SPI_NUM, baudrate=5_000_000) -buf = bytearray(1024) -ok = True - -for offs in range(0, len(buf)): - v = memoryview(buf)[offs : offs + 128] - spi.readinto(v, 0xFF) - if not all(b == 0x00 for b in v): - print(offs, v.hex()) - ok = False - -print("Variable offset fixed length " + ("OK" if ok else "FAIL")) - -# this takes around 30s to run, so skipped if already failing -if ok: - for op_len in range(1, 66): - wr = b"\xFF" * op_len - for offs in range(1, len(buf) - op_len - 1): - # Place some "sentinel" values before and after the DMA buffer - before = offs & 0xFF - after = (~offs) & 0xFF - buf[offs - 1] = before - buf[offs + op_len] = after - v = memoryview(buf)[offs : offs + op_len] - spi.write_readinto(wr, v) - if ( - not all(b == 0x00 for b in v) - or buf[offs - 1] != before - or buf[offs + op_len] != after - ): - print(v.hex()) - print(hex(op_len), hex(offs), hex(buf[offs - 1]), hex(buf[offs + op_len])) - ok = False - - print("Variable offset and lengths " + ("OK" if ok else "FAIL")) +if __name__ == "__main__": + unittest.main() diff --git a/tests/ports/stm32_hardware/dma_alignment.py.exp b/tests/ports/stm32_hardware/dma_alignment.py.exp deleted file mode 100644 index e890e0081c4..00000000000 --- a/tests/ports/stm32_hardware/dma_alignment.py.exp +++ /dev/null @@ -1,2 +0,0 @@ -Variable offset fixed length OK -Variable offset and lengths OK From c7c3ffa45f5cc3463f0b8983c360d950d210f320 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 13 Nov 2024 12:44:10 +1100 Subject: [PATCH 0081/2098] tests/net_hosted: Convert connect-nonblock-xfer test to use unittest. This allows it to run parts of the test on esp8266 (or any target using axTLS). Signed-off-by: Damien George --- tests/net_hosted/connect_nonblock_xfer.py | 104 +++++++++++------- tests/net_hosted/connect_nonblock_xfer.py.exp | 44 -------- 2 files changed, 62 insertions(+), 86 deletions(-) delete mode 100644 tests/net_hosted/connect_nonblock_xfer.py.exp diff --git a/tests/net_hosted/connect_nonblock_xfer.py b/tests/net_hosted/connect_nonblock_xfer.py index 23620908afb..2c8e12473fe 100644 --- a/tests/net_hosted/connect_nonblock_xfer.py +++ b/tests/net_hosted/connect_nonblock_xfer.py @@ -1,15 +1,14 @@ # test that socket.connect() on a non-blocking socket raises EINPROGRESS # and that an immediate write/send/read/recv does the right thing +import unittest import errno import select import socket import ssl # only mbedTLS supports non-blocking mode -if not hasattr(ssl, "MBEDTLS_VERSION"): - print("SKIP") - raise SystemExit +ssl_supports_nonblocking = hasattr(ssl, "MBEDTLS_VERSION") # get the name of an errno error code @@ -24,34 +23,43 @@ def errno_name(er): # do_connect establishes the socket and wraps it if tls is True. # If handshake is true, the initial connect (and TLS handshake) is # allowed to be performed before returning. -def do_connect(peer_addr, tls, handshake): +def do_connect(self, peer_addr, tls, handshake): s = socket.socket() s.setblocking(False) try: - # print("Connecting to", peer_addr) + print("Connecting to", peer_addr) s.connect(peer_addr) + self.fail() except OSError as er: print("connect:", errno_name(er.errno)) + self.assertEqual(er.errno, errno.EINPROGRESS) + # wrap with ssl/tls if desired if tls: + print("wrap socket") ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - try: - s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake) - print("wrap ok: True") - except Exception as e: - print("wrap er:", e) + s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake) + return s -# poll a socket and print out the result -def poll(s): +# poll a socket and check the result +def poll(self, s, expect_writable): poller = select.poll() poller.register(s) - print("poll: ", poller.poll(0)) + result = poller.poll(0) + print("poll:", result) + if expect_writable: + self.assertEqual(len(result), 1) + self.assertEqual(result[0][1], select.POLLOUT) + else: + self.assertEqual(result, []) + +# do_test runs the test against a specific peer address. +def do_test(self, peer_addr, tls, handshake): + print() -# test runs the test against a specific peer address. -def test(peer_addr, tls, handshake): # MicroPython plain and TLS sockets have read/write hasRW = True @@ -62,54 +70,66 @@ def test(peer_addr, tls, handshake): # connect + send # non-blocking send should raise EAGAIN if hasSR: - s = do_connect(peer_addr, tls, handshake) - poll(s) - try: + s = do_connect(self, peer_addr, tls, handshake) + poll(self, s, False) + with self.assertRaises(OSError) as ctx: ret = s.send(b"1234") - print("send ok:", ret) # shouldn't get here - except OSError as er: - print("send er:", errno_name(er.errno)) + print("send error:", errno_name(ctx.exception.errno)) + self.assertEqual(ctx.exception.errno, errno.EAGAIN) s.close() # connect + write # non-blocking write should return None if hasRW: - s = do_connect(peer_addr, tls, handshake) - poll(s) + s = do_connect(self, peer_addr, tls, handshake) + poll(self, s, tls and handshake) ret = s.write(b"1234") - print("write: ", ret) + print("write:", ret) + if tls and handshake: + self.assertEqual(ret, 4) + else: + self.assertIsNone(ret) s.close() # connect + recv # non-blocking recv should raise EAGAIN if hasSR: - s = do_connect(peer_addr, tls, handshake) - poll(s) - try: + s = do_connect(self, peer_addr, tls, handshake) + poll(self, s, False) + with self.assertRaises(OSError) as ctx: ret = s.recv(10) - print("recv ok:", ret) # shouldn't get here - except OSError as er: - print("recv er:", errno_name(er.errno)) + print("recv error:", errno_name(ctx.exception.errno)) + self.assertEqual(ctx.exception.errno, errno.EAGAIN) s.close() # connect + read # non-blocking read should return None if hasRW: - s = do_connect(peer_addr, tls, handshake) - poll(s) + s = do_connect(self, peer_addr, tls, handshake) + poll(self, s, tls and handshake) ret = s.read(10) - print("read: ", ret) + print("read:", ret) + self.assertIsNone(ret) s.close() -if __name__ == "__main__": +class Test(unittest.TestCase): # these tests use a non-existent test IP address, this way the connect takes forever and # we can see EAGAIN/None (https://tools.ietf.org/html/rfc5737) - print("--- Plain sockets to nowhere ---") - test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) - print("--- SSL sockets to nowhere ---") - test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False) - print("--- Plain sockets ---") - test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, False) - print("--- SSL sockets ---") - test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) + def test_plain_sockets_to_nowhere(self): + do_test(self, socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) + + @unittest.skipIf(not ssl_supports_nonblocking, "SSL doesn't support non-blocking") + def test_ssl_sockets_to_nowhere(self): + do_test(self, socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False) + + def test_plain_sockets(self): + do_test(self, socket.getaddrinfo("micropython.org", 80)[0][-1], False, False) + + @unittest.skipIf(not ssl_supports_nonblocking, "SSL doesn't support non-blocking") + def test_ssl_sockets(self): + do_test(self, socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/net_hosted/connect_nonblock_xfer.py.exp b/tests/net_hosted/connect_nonblock_xfer.py.exp deleted file mode 100644 index c5498a03873..00000000000 --- a/tests/net_hosted/connect_nonblock_xfer.py.exp +++ /dev/null @@ -1,44 +0,0 @@ ---- Plain sockets to nowhere --- -connect: EINPROGRESS -poll: [] -send er: EAGAIN -connect: EINPROGRESS -poll: [] -write: None -connect: EINPROGRESS -poll: [] -recv er: EAGAIN -connect: EINPROGRESS -poll: [] -read: None ---- SSL sockets to nowhere --- -connect: EINPROGRESS -wrap ok: True -poll: [] -write: None -connect: EINPROGRESS -wrap ok: True -poll: [] -read: None ---- Plain sockets --- -connect: EINPROGRESS -poll: [] -send er: EAGAIN -connect: EINPROGRESS -poll: [] -write: None -connect: EINPROGRESS -poll: [] -recv er: EAGAIN -connect: EINPROGRESS -poll: [] -read: None ---- SSL sockets --- -connect: EINPROGRESS -wrap ok: True -poll: [(, 4)] -write: 4 -connect: EINPROGRESS -wrap ok: True -poll: [(, 4)] -read: None From 2c80d369988a84d85efe0483f9cf469ff27b70f9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 3 Dec 2024 15:12:34 +1100 Subject: [PATCH 0082/2098] tests/extmod: Convert machine1.py test to use unittest. Signed-off-by: Damien George --- tests/extmod/machine1.py | 55 +++++++++++++++++------------------- tests/extmod/machine1.py.exp | 8 ------ 2 files changed, 26 insertions(+), 37 deletions(-) delete mode 100644 tests/extmod/machine1.py.exp diff --git a/tests/extmod/machine1.py b/tests/extmod/machine1.py index 90e6d17174f..2f1c0681f64 100644 --- a/tests/extmod/machine1.py +++ b/tests/extmod/machine1.py @@ -8,39 +8,36 @@ print("SKIP") raise SystemExit -print(machine.mem8) +import unittest -try: - machine.mem16[1] -except ValueError: - print("ValueError") -try: - machine.mem16[1] = 1 -except ValueError: - print("ValueError") +class Test(unittest.TestCase): + def test_mem8_print(self): + self.assertEqual(repr(machine.mem8), "<8-bit memory>") -try: - del machine.mem8[0] -except TypeError: - print("TypeError") + def test_alignment(self): + with self.assertRaises(ValueError): + machine.mem16[1] -try: - machine.mem8[0:1] -except TypeError: - print("TypeError") + with self.assertRaises(ValueError): + machine.mem16[1] = 1 -try: - machine.mem8[0:1] = 10 -except TypeError: - print("TypeError") + def test_operations(self): + with self.assertRaises(TypeError): + del machine.mem8[0] -try: - machine.mem8["hello"] -except TypeError: - print("TypeError") + with self.assertRaises(TypeError): + machine.mem8[0:1] -try: - machine.mem8["hello"] = 10 -except TypeError: - print("TypeError") + with self.assertRaises(TypeError): + machine.mem8[0:1] = 10 + + with self.assertRaises(TypeError): + machine.mem8["hello"] + + with self.assertRaises(TypeError): + machine.mem8["hello"] = 10 + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod/machine1.py.exp b/tests/extmod/machine1.py.exp deleted file mode 100644 index 25048596908..00000000000 --- a/tests/extmod/machine1.py.exp +++ /dev/null @@ -1,8 +0,0 @@ -<8-bit memory> -ValueError -ValueError -TypeError -TypeError -TypeError -TypeError -TypeError From a118cf9ab0d44a69b4af23e2f42a49c2abafca41 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 30 Nov 2024 08:31:36 +0100 Subject: [PATCH 0083/2098] extmod/extmod.mk: Fix libmetal build prefix. libmetal source files already have the build directory prefix, because they're auto-generated inside the build directory. When they're added to `SRC_THIRDPARTY_C`, another build directory prefix is added resulting in the object files being generated in a nested build directory. This patch strips the build directory prefix before adding libmetal's source files. Signed-off-by: iabdalkader --- extmod/extmod.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 514197d6b9a..f2ae3ad6923 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -616,6 +616,6 @@ $(BUILD)/$(OPENAMP_DIR)/lib/virtio_mmio/virtio_mmio_drv.o: CFLAGS += -Wno-unused # We need to have generated libmetal before compiling OpenAMP. $(addprefix $(BUILD)/, $(SRC_OPENAMP_C:.c=.o)): $(BUILD)/openamp/metal/config.h -SRC_THIRDPARTY_C += $(SRC_LIBMETAL_C) $(SRC_OPENAMP_C) +SRC_THIRDPARTY_C += $(SRC_OPENAMP_C) $(SRC_LIBMETAL_C:$(BUILD)/%=%) endif # MICROPY_PY_OPENAMP From c55202dd6390d13f77f5bd10c504300eadcfd1a7 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 14:31:18 +0100 Subject: [PATCH 0084/2098] shared/tinyusb: Set MSC max endpoint size based on device speed. Signed-off-by: iabdalkader --- shared/tinyusb/mp_usbd_descriptor.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c index be3473b6b91..d0c8845b680 100644 --- a/shared/tinyusb/mp_usbd_descriptor.c +++ b/shared/tinyusb/mp_usbd_descriptor.c @@ -34,6 +34,7 @@ #define USBD_CDC_CMD_MAX_SIZE (8) #define USBD_CDC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64) +#define USBD_MSC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64) const tusb_desc_device_t mp_usbd_builtin_desc_dev = { .bLength = sizeof(tusb_desc_device_t), @@ -61,7 +62,7 @@ const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = { USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), #endif #if CFG_TUD_MSC - TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), + TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, USBD_STR_MSC, EPNUM_MSC_OUT, EPNUM_MSC_IN, USBD_MSC_IN_OUT_MAX_SIZE), #endif }; From 67b4e6236bb769d74cf5c625da9baad13806c9e5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 16:15:21 +0100 Subject: [PATCH 0085/2098] mimxrt: Switch to shared TinyUSB descriptor. This removes duplicated TinyUSB configuration and port-specific code. Tested on RT1062, CDC+MSC still working. @robert-hh tested CDC with 1011, 1015, 1020 and 1176. Signed-off-by: iabdalkader --- ports/mimxrt/Makefile | 5 +- ports/mimxrt/mpconfigport.h | 8 ++ ports/mimxrt/tusb_port.c | 138 ------------------------- ports/mimxrt/{tusb_config.h => usbd.c} | 27 +++-- 4 files changed, 28 insertions(+), 150 deletions(-) delete mode 100644 ports/mimxrt/tusb_port.c rename ports/mimxrt/{tusb_config.h => usbd.c} (71%) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index e98073d3362..3a9550cc974 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -78,6 +78,7 @@ INC += -I$(TOP)/lib/oofatfs INC += -I$(TOP)/lib/tinyusb/hw INC += -I$(TOP)/lib/tinyusb/hw/bsp/teensy_40 INC += -I$(TOP)/lib/tinyusb/src +INC += -I$(TOP)/shared/tinyusb INC += -I. INC += -Ihal @@ -215,7 +216,7 @@ SRC_C += \ sdio.c \ systick.c \ ticks.c \ - tusb_port.c \ + usbd.c \ SHARED_SRC_C += \ shared/libc/printf.c \ @@ -234,6 +235,7 @@ SHARED_SRC_C += \ shared/timeutils/timeutils.c \ shared/tinyusb/mp_usbd.c \ shared/tinyusb/mp_usbd_cdc.c \ + shared/tinyusb/mp_usbd_descriptor.c \ # Set flash driver name, base address and internal flash flag, based on the flash type. ifeq ($(MICROPY_HW_FLASH_TYPE),$(filter $(MICROPY_HW_FLASH_TYPE),qspi_nor_flash)) @@ -343,6 +345,7 @@ CFLAGS += \ -DBOARD_$(BOARD) \ -DBOARD_FLASH_SIZE=$(MICROPY_HW_FLASH_SIZE) \ -DCFG_TUSB_MCU=OPT_MCU_MIMXRT1XXX \ + -DCFG_TUD_MAX_SPEED=OPT_MODE_HIGH_SPEED \ -DCLOCK_CONFIG_H='' \ -DCPU_$(MCU_SERIES)$(MCU_CORE) \ -DCPU_$(MCU_VARIANT) \ diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 5ef6695c142..31444e4980b 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -188,6 +188,14 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type; #define MP_STATE_PORT MP_STATE_VM // Miscellaneous settings +#ifndef MICROPY_HW_USB_VID +#define MICROPY_HW_USB_VID (0xf055) +#endif + +#ifndef MICROPY_HW_USB_PID +#define MICROPY_HW_USB_PID (0x9802) +#endif + #ifndef MICROPY_EVENT_POLL_HOOK #define MICROPY_EVENT_POLL_HOOK \ do { \ diff --git a/ports/mimxrt/tusb_port.c b/ports/mimxrt/tusb_port.c deleted file mode 100644 index f81200e2caa..00000000000 --- a/ports/mimxrt/tusb_port.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 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 "tusb.h" -#include "py/mphal.h" - -#ifndef MICROPY_HW_USB_VID -#define MICROPY_HW_USB_VID (0xf055) -#endif - -#ifndef MICROPY_HW_USB_PID -#define MICROPY_HW_USB_PID (0x9802) -#endif - -#ifndef MICROPY_HW_USB_MANUFACTURER_STRING -#define MICROPY_HW_USB_MANUFACTURER_STRING ("MicroPython") -#endif - -#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN) -#define USBD_MAX_POWER_MA (250) - -#define USBD_ITF_CDC (0) // needs 2 interfaces -#define USBD_ITF_MAX (2) - -#define USBD_CDC_EP_CMD (0x81) -#define USBD_CDC_EP_OUT (0x02) -#define USBD_CDC_EP_IN (0x82) -#define USBD_CDC_CMD_MAX_SIZE (8) -#define USBD_CDC_IN_OUT_MAX_SIZE (512) - -#define USBD_STR_0 (0x00) -#define USBD_STR_MANUF (0x01) -#define USBD_STR_PRODUCT (0x02) -#define USBD_STR_SERIAL (0x03) -#define USBD_STR_CDC (0x04) - -// Note: descriptors returned from callbacks must exist long enough for transfer to complete - -static const tusb_desc_device_t usbd_desc_device = { - .bLength = sizeof(tusb_desc_device_t), - .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .idVendor = MICROPY_HW_USB_VID, - .idProduct = MICROPY_HW_USB_PID, - .bcdDevice = 0x0100, - .iManufacturer = USBD_STR_MANUF, - .iProduct = USBD_STR_PRODUCT, - .iSerialNumber = USBD_STR_SERIAL, - .bNumConfigurations = 1, -}; - -static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN, - TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), - - TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), -}; - -static const char *const usbd_desc_str[] = { - [USBD_STR_MANUF] = MICROPY_HW_USB_MANUFACTURER_STRING, - [USBD_STR_PRODUCT] = MICROPY_HW_BOARD_NAME, - [USBD_STR_SERIAL] = "00000000000000000000", - [USBD_STR_CDC] = "Board CDC", -}; - -const uint8_t *tud_descriptor_device_cb(void) { - return (const uint8_t *)&usbd_desc_device; -} - -const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { - (void)index; - return usbd_desc_cfg; -} - -const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { - #define DESC_STR_MAX (20) - static uint16_t desc_str[DESC_STR_MAX]; - static const char hexchr[16] = "0123456789ABCDEF"; - - memset(desc_str, 0, sizeof(desc_str)); - - uint8_t len; - if (index == 0) { - desc_str[1] = 0x0409; // supported language is English - len = 1; - } else { - if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) { - return NULL; - } - if (index == USBD_STR_SERIAL) { - uint8_t uid[8]; - mp_hal_get_unique_id(uid); - // store it as a hex string - for (len = 0; len < 16; len += 2) { - desc_str[1 + len] = hexchr[uid[len / 2] >> 4]; - desc_str[1 + len + 1] = hexchr[uid[len / 2] & 0x0f]; - } - } else { - const char *str = usbd_desc_str[index]; - for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) { - desc_str[1 + len] = str[len]; - } - } - } - - // first byte is length (including header), second byte is string type - desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2); - - return desc_str; -} diff --git a/ports/mimxrt/tusb_config.h b/ports/mimxrt/usbd.c similarity index 71% rename from ports/mimxrt/tusb_config.h rename to ports/mimxrt/usbd.c index 607f36446f8..47126ddc017 100644 --- a/ports/mimxrt/tusb_config.h +++ b/ports/mimxrt/usbd.c @@ -1,7 +1,9 @@ /* + * This file is part of the MicroPython project, http://micropython.org/ + * * The MIT License (MIT) * - * Copyright (c) 2020 Jim Mussared + * 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 @@ -20,18 +22,21 @@ * 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_MIMXRT_TUSB_CONFIG_H -#define MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H -#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USBDEV -#define CFG_TUSB_OS (OPT_OS_NONE) +#include "py/mphal.h" +#include "mp_usbd.h" +#include "string.h" +#include "tusb.h" -#define CFG_TUD_CDC (1) -#define CFG_TUD_CDC_RX_BUFSIZE (512) -#define CFG_TUD_CDC_TX_BUFSIZE (512) -#define CFG_TUD_CDC_PERSISTENT_TX_BUFF (1) +void mp_usbd_port_get_serial_number(char *serial_buf) { + uint8_t uid[8]; + mp_hal_get_unique_id(uid); + mp_usbd_hex_str(serial_buf, uid, sizeof(uid)); +} -#endif // MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H +#endif From d5d366beef8d0331ee533b28d2e3024e34123ef1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 14:51:58 +0100 Subject: [PATCH 0086/2098] nrf/boards/ARDUINO_NANO_33_BLE_SENSE: Update LED and timer config. Changes: - Enable hardware timer. - Define LED pins. Signed-off-by: iabdalkader --- ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/mpconfigboard.h | 1 + ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/pins.csv | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/mpconfigboard.h b/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/mpconfigboard.h index a4bb1d899e0..65642bf2342 100644 --- a/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/mpconfigboard.h +++ b/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/mpconfigboard.h @@ -13,6 +13,7 @@ #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) +#define MICROPY_PY_MACHINE_TIMER_NRF (1) #define MICROPY_PY_MACHINE_RTCOUNTER (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/pins.csv b/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/pins.csv index 972dd9d3bdc..52e3df400e0 100644 --- a/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/pins.csv +++ b/ports/nrf/boards/ARDUINO_NANO_33_BLE_SENSE/pins.csv @@ -80,3 +80,7 @@ TX,P35 RX,P42 I2C_SCL,P2 I2C_SDA,P31 +LED_RED,P24 +LED_GREEN,P16 +LED_BLUE,P6 +LED_YELLOW,P13 From b5e80fafb2d5ef69fd80dbc6d7e839ffaa5a0f98 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 16:56:20 +0100 Subject: [PATCH 0087/2098] drivers/memory/spiflash: Add a config option to soft-reset SPI flash. Add a compile-time config option to soft-reset SPI flash on init. This puts the flash in a known state on reset. Note this option is disabled by default. Signed-off-by: iabdalkader --- drivers/memory/spiflash.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index e66153636b1..773334e167a 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -44,6 +44,8 @@ #define CMD_RD_DEVID (0x9f) #define CMD_CHIP_ERASE (0xc7) #define CMD_C4READ (0xeb) +#define CMD_RSTEN (0x66) +#define CMD_RESET (0x99) // 32 bit addressing commands #define CMD_WRITE_32 (0x12) @@ -180,6 +182,14 @@ void mp_spiflash_init(mp_spiflash_t *self) { // Ensure SPI flash is out of sleep mode mp_spiflash_deepsleep_internal(self, 0); + // Software reset. + #if MICROPY_HW_SPIFLASH_SOFT_RESET + mp_spiflash_write_cmd(self, CMD_RSTEN); + mp_spiflash_write_cmd(self, CMD_RESET); + mp_spiflash_wait_wip0(self); + mp_hal_delay_ms(1); + #endif + #if defined(CHECK_DEVID) // Validate device id uint32_t devid; From 46ca78e23fdf86d9f7d101142d5448529b0ef7be Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 16:57:38 +0100 Subject: [PATCH 0088/2098] stm32/boards: Update Arduino board configs for SPI reset and bootloader. Update ARDUINO_GIGA, ARDUINO_OPTA, ARDUINO_NICLA_VISION and ARDUINO_PORTENTA_H7 to: - Enable SPI flash soft-reset. - Disable enter bootloader via reset. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 2 ++ 4 files changed, 8 insertions(+) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index d09a074402e..2b86ce528e2 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -29,9 +29,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_TIMER (1) #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_ENABLE_MMCARD (0) +#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_BOARD_STARTUP GIGA_board_startup diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index 796a9fae923..7bb75091e8b 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -29,9 +29,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_TIMER (1) #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_ENABLE_MMCARD (0) +#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_BOARD_STARTUP NICLAV_board_startup diff --git a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h index 869522e1ee7..f52c8a26a81 100644 --- a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h @@ -25,9 +25,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_TIMER (1) +#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_BOARD_STARTUP OPTA_board_startup diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 860a0f688b2..5442f742761 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -29,9 +29,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_TIMER (1) #define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_ENABLE_MMCARD (0) +#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_BOARD_STARTUP PORTENTA_board_startup From 47d9988df2c7c6a4603f631b02a3f3a5c7e997f4 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 4 Dec 2024 09:54:13 +0100 Subject: [PATCH 0089/2098] stm32/boards: Rename SDRAM frequency config option to make units clear. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 2 +- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index 2b86ce528e2..99b92a3793f 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -233,7 +233,7 @@ extern struct _spi_bdev_t spi_bdev; // Timing configuration for 200MHz/2=100MHz (10ns) #define MICROPY_HW_SDRAM_CLOCK_PERIOD 2 #define MICROPY_HW_SDRAM_CAS_LATENCY 2 -#define MICROPY_HW_SDRAM_FREQUENCY (100000) // 100 MHz +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (100000) // 100 MHz #define MICROPY_HW_SDRAM_TIMING_TMRD (2) #define MICROPY_HW_SDRAM_TIMING_TXSR (7) #define MICROPY_HW_SDRAM_TIMING_TRAS (5) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 5442f742761..6b77b16093f 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -247,7 +247,7 @@ extern struct _spi_bdev_t spi_bdev; // Timing configuration for 200MHz/2=100MHz (10ns) #define MICROPY_HW_SDRAM_CLOCK_PERIOD 2 #define MICROPY_HW_SDRAM_CAS_LATENCY 2 -#define MICROPY_HW_SDRAM_FREQUENCY (100000) // 100 MHz +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (100000) // 100 MHz #define MICROPY_HW_SDRAM_TIMING_TMRD (2) #define MICROPY_HW_SDRAM_TIMING_TXSR (7) #define MICROPY_HW_SDRAM_TIMING_TRAS (5) From 17808e7b749b269b58dcee67df599a0c61d455bd Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 30 Nov 2024 09:56:23 +0100 Subject: [PATCH 0090/2098] stm32/sdram: Make SDRAM refresh count configurable by a board. Refresh count calculations were using a hard-coded SDRAM frequency and refresh cycles, so change them to values that can be set by a board. And set these options to their existing values on STM32F769DISC and STM32F7DISC boards. Signed-off-by: iabdalkader --- ports/stm32/boards/STM32F769DISC/mpconfigboard.h | 2 ++ ports/stm32/boards/STM32F7DISC/mpconfigboard.h | 2 ++ ports/stm32/sdram.c | 6 +++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h index f899091d201..017de9c0683 100644 --- a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h @@ -141,6 +141,7 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_SDRAM_BURST_LENGTH 1 #define MICROPY_HW_SDRAM_CAS_LATENCY 2 +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (90000) // 90 MHz #define MICROPY_HW_SDRAM_COLUMN_BITS_NUM 8 #define MICROPY_HW_SDRAM_ROW_BITS_NUM 12 #define MICROPY_HW_SDRAM_MEM_BUS_WIDTH 32 @@ -150,6 +151,7 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_SDRAM_RBURST (1) #define MICROPY_HW_SDRAM_WRITE_PROTECTION (0) #define MICROPY_HW_SDRAM_AUTOREFRESH_NUM (8) +#define MICROPY_HW_SDRAM_REFRESH_CYCLES 8192 // See pins.csv for CPU pin mapping #define MICROPY_HW_FMC_SDCKE0 (pyb_pin_FMC_SDCKE0) diff --git a/ports/stm32/boards/STM32F7DISC/mpconfigboard.h b/ports/stm32/boards/STM32F7DISC/mpconfigboard.h index cf7061902ee..d4b21484ad4 100644 --- a/ports/stm32/boards/STM32F7DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F7DISC/mpconfigboard.h @@ -111,6 +111,7 @@ void STM32F7DISC_board_early_init(void); #define MICROPY_HW_SDRAM_BURST_LENGTH 1 #define MICROPY_HW_SDRAM_CAS_LATENCY 2 +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (90000) // 90 MHz #define MICROPY_HW_SDRAM_COLUMN_BITS_NUM 8 #define MICROPY_HW_SDRAM_ROW_BITS_NUM 12 #define MICROPY_HW_SDRAM_MEM_BUS_WIDTH 16 @@ -120,6 +121,7 @@ void STM32F7DISC_board_early_init(void); #define MICROPY_HW_SDRAM_RBURST (1) #define MICROPY_HW_SDRAM_WRITE_PROTECTION (0) #define MICROPY_HW_SDRAM_AUTOREFRESH_NUM (8) +#define MICROPY_HW_SDRAM_REFRESH_CYCLES 8192 #define MICROPY_HW_FMC_SDCKE0 (pin_C3) #define MICROPY_HW_FMC_SDNE0 (pin_H3) diff --git a/ports/stm32/sdram.c b/ports/stm32/sdram.c index e99f34d2262..7104eb68e8f 100644 --- a/ports/stm32/sdram.c +++ b/ports/stm32/sdram.c @@ -245,15 +245,15 @@ static void sdram_init_seq(SDRAM_HandleTypeDef /* Send the command */ HAL_SDRAM_SendCommand(hsdram, command, 0x1000); - /* Step 8: Set the refresh rate counter + /* Step 8: Set the refresh rate counter. + Assuming 90MHz frequency, 8192 refresh cycles and 64ms refresh rate: RefreshRate = 64 ms / 8192 cyc = 7.8125 us/cyc - RefreshCycles = 7.8125 us * 90 MHz = 703 According to the formula on p.1665 of the reference manual, we also need to subtract 20 from the value, so the target refresh rate is 703 - 20 = 683. */ - #define REFRESH_COUNT (MICROPY_HW_SDRAM_REFRESH_RATE * 90000 / 8192 - 20) + #define REFRESH_COUNT (MICROPY_HW_SDRAM_REFRESH_RATE * MICROPY_HW_SDRAM_FREQUENCY_KHZ / MICROPY_HW_SDRAM_REFRESH_CYCLES - 20) HAL_SDRAM_ProgramRefreshRate(hsdram, REFRESH_COUNT); #if defined(STM32F7) || defined(STM32H7) From d42e39d87d37f20d74456222e603d0b6b409a556 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 2 Dec 2024 08:09:25 +0100 Subject: [PATCH 0091/2098] stm32/spi: Add spi_deinit_all function. SPI objects can remain active after a soft-reboot because they are statically allocated and lack a finalizer to collect and deinitialize them. This commit adds a `spi_deinit_all()` functions for SPI, similar to other peripherals such as UART, DAC, etc. Signed-off-by: iabdalkader --- ports/stm32/spi.c | 9 +++++++++ ports/stm32/spi.h | 1 + 2 files changed, 10 insertions(+) diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 607b3fe685a..aa459119c14 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -545,6 +545,15 @@ void spi_deinit(const spi_t *spi_obj) { } } +void spi_deinit_all(void) { + for (int i = 0; i < MP_ARRAY_SIZE(spi_obj); i++) { + const spi_t *spi = &spi_obj[i]; + if (spi->spi != NULL) { + spi_deinit(spi); + } + } +} + static HAL_StatusTypeDef spi_wait_dma_finished(const spi_t *spi, uint32_t t_start, uint32_t timeout) { volatile HAL_SPI_StateTypeDef *state = &spi->spi->State; for (;;) { diff --git a/ports/stm32/spi.h b/ports/stm32/spi.h index a8bc9d2cfdb..204038a92f3 100644 --- a/ports/stm32/spi.h +++ b/ports/stm32/spi.h @@ -67,6 +67,7 @@ extern const mp_obj_type_t pyb_spi_type; void spi_init0(void); int spi_init(const spi_t *spi, bool enable_nss_pin); void spi_deinit(const spi_t *spi_obj); +void spi_deinit_all(void); int spi_find_index(mp_obj_t id); void spi_set_params(const spi_t *spi_obj, uint32_t prescale, int32_t baudrate, int32_t polarity, int32_t phase, int32_t bits, int32_t firstbit); From 405aa69887558d561a993d5e9aa47b2ec7e76d9a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 2 Dec 2024 08:09:47 +0100 Subject: [PATCH 0092/2098] stm32/pyb_i2c: Add pyb_i2c_deinit_all function. I2C objects can remain active after a soft-reboot because they are statically allocated and lack a finalizer to collect and deinitialize them. This commit adds a `pyb_i2c_deinit_all()` function for I2C, similar to other peripherals such as UART, DAC, etc. Signed-off-by: iabdalkader --- ports/stm32/i2c.h | 1 + ports/stm32/pyb_i2c.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/ports/stm32/i2c.h b/ports/stm32/i2c.h index 04a7e928bdb..a48076842cb 100644 --- a/ports/stm32/i2c.h +++ b/ports/stm32/i2c.h @@ -50,6 +50,7 @@ void i2c_init0(void); int pyb_i2c_init(I2C_HandleTypeDef *i2c); int pyb_i2c_init_freq(const pyb_i2c_obj_t *self, mp_int_t freq); uint32_t pyb_i2c_get_baudrate(I2C_HandleTypeDef *i2c); +void pyb_i2c_deinit_all(void); void i2c_ev_irq_handler(mp_uint_t i2c_id); void i2c_er_irq_handler(mp_uint_t i2c_id); diff --git a/ports/stm32/pyb_i2c.c b/ports/stm32/pyb_i2c.c index 0529d3bd56e..7e1489010d0 100644 --- a/ports/stm32/pyb_i2c.c +++ b/ports/stm32/pyb_i2c.c @@ -428,6 +428,15 @@ int pyb_i2c_init_freq(const pyb_i2c_obj_t *self, mp_int_t freq) { return pyb_i2c_init(self->i2c); } +void pyb_i2c_deinit_all(void) { + for (int i = 0; i < MP_ARRAY_SIZE(pyb_i2c_obj); i++) { + const pyb_i2c_obj_t *pyb_i2c = &pyb_i2c_obj[i]; + if (pyb_i2c->i2c != NULL) { + i2c_deinit(pyb_i2c->i2c); + } + } +} + static void i2c_reset_after_error(I2C_HandleTypeDef *i2c) { // wait for bus-busy flag to be cleared, with a timeout for (int timeout = 50; timeout > 0; --timeout) { From 89191b00eae258642cd3d6458bc5440341bbc8d1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 2 Dec 2024 08:18:28 +0100 Subject: [PATCH 0093/2098] stm32/main: Deinitialize SPI and I2C on soft-reset. Following UART, CAN, Timer, etc. Signed-off-by: iabdalkader --- ports/stm32/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index df483602dc9..ea870ef0da7 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -678,6 +678,10 @@ void stm32_main(uint32_t reset_mode) { soft_timer_deinit(); timer_deinit(); uart_deinit_all(); + spi_deinit_all(); + #if MICROPY_PY_PYB_LEGACY && MICROPY_HW_ENABLE_HW_I2C + pyb_i2c_deinit_all(); + #endif #if MICROPY_HW_ENABLE_CAN can_deinit_all(); #endif From fd01cdd203b1049fec35a82ba3ddd89c4bd62a67 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 1 Dec 2024 08:55:34 +0100 Subject: [PATCH 0094/2098] stm32/mpconfigport: Switch FATFS LFN to type 2. LFN type 2 uses the stack to allocate the internal working buffer for LFN, which is thread-safe and saves about 512 bytes of BSS memory (at the expense of needing that much memory on the stack). Signed-off-by: iabdalkader --- ports/stm32/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index c6ba83d1bd2..dc0a767fb07 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -157,7 +157,7 @@ #endif // fatfs configuration used in ffconf.h -#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_ENABLE_LFN (2) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define MICROPY_FATFS_USE_LABEL (1) #define MICROPY_FATFS_RPATH (2) From 309aa268113bd1779433bb36f3a55f81fd129979 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 1 Dec 2024 08:58:02 +0100 Subject: [PATCH 0095/2098] rp2/mpconfigport: Switch FATFS LFN to type 2. LFN type 2 uses the stack to allocate the internal working buffer for LFN, which is thread-safe and saves about 512 bytes of BSS memory (at the expense of needing that much memory on the stack). Signed-off-by: iabdalkader --- ports/rp2/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 15eeab4f34b..8f4e846ba13 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -178,7 +178,7 @@ #define MICROPY_HW_SOFT_TIMER_ALARM_NUM (2) // fatfs configuration -#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_ENABLE_LFN (2) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define MICROPY_FATFS_RPATH (2) #if MICROPY_HW_USB_MSC From a2cdf9a95fea33ade5c608885424d6aae21773f5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 1 Dec 2024 08:58:24 +0100 Subject: [PATCH 0096/2098] renesas-ra/mpconfigport: Switch FATFS LFN to type 2. LFN type 2 uses the stack to allocate the internal working buffer for LFN, which is thread-safe and saves about 512 bytes of BSS memory (at the expense of needing that much memory on the stack). Signed-off-by: iabdalkader --- ports/renesas-ra/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index 307dac24ce5..bd936061fa4 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -167,7 +167,7 @@ #endif // fatfs configuration used in ffconf.h -#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_ENABLE_LFN (2) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define MICROPY_FATFS_USE_LABEL (1) #define MICROPY_FATFS_RPATH (2) From 2bee8e1b8cdaae0ac387c03e88a573e35256b092 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 1 Dec 2024 08:59:32 +0100 Subject: [PATCH 0097/2098] mimxrt/mpconfigport: Update FATFS config to align with other ports. Make this port use the same FATFS config as stm32, rp2, renesas-ra. Signed-off-by: iabdalkader --- ports/mimxrt/mpconfigport.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 31444e4980b..146c86015af 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -118,10 +118,12 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_ONEWIRE (1) // fatfs configuration used in ffconf.h -#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_ENABLE_LFN (2) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_FATFS_USE_LABEL (1) #define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MULTI_PARTITION (1) #define MICROPY_FATFS_MAX_SS (4096) -#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #ifndef MICROPY_PY_NETWORK #define MICROPY_PY_NETWORK (1) From b20687d0e71360bf76eccedbb9d2c1f73697693a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Dec 2024 10:02:46 +1100 Subject: [PATCH 0098/2098] esp32: Fix link failure due to link library order. When a wrapped symbol is provided in its own file, it's possible for the linker to skip that file entirely and not return to it depending on the order of libraries passed on the linker command line. This is because these wrapped symbols create linker cycles (libmain_espXX depends on liblwip but liblwip now also depends on libmain for the wrapped functions in lwip_patch.c, for example.) Linker failure for symbols in lwip_patch.c was reproducible if mDNS was disabled in the board configuration. This commit adds an explicit undefined symbol for each file, to ensure the linker will add the wrapped objects on its first pass. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/esp32_common.cmake | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 9d51a03aa85..0a6b29983c2 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -242,12 +242,19 @@ target_include_directories(${MICROPY_TARGET} PUBLIC target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) -# Enable the panic handler wrapper -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) - -# Patch LWIP memory pool allocators (see lwip_patch.c) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_malloc" APPEND) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_free" APPEND) +# Extra linker options +# (when wrap symbols are in standalone files, --undefined ensures +# the linker doesn't skip that file.) +target_link_options(${MICROPY_TARGET} PUBLIC + # Patch LWIP memory pool allocators (see lwip_patch.c) + -Wl,--undefined=memp_malloc + -Wl,--wrap=memp_malloc + -Wl,--wrap=memp_free + + # Enable the panic handler wrapper + -Wl,--undefined=esp_panic_handler + -Wl,--wrap=esp_panic_handler +) # Collect all of the include directories and compile definitions for the IDF components, # including those added by the IDF Component Manager via idf_components.yaml. From 5564f3042ca850e363e6644b3df0b006785fcf2d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 28 Aug 2024 09:21:34 +1000 Subject: [PATCH 0099/2098] esp32: Add basic espressif IDF v5.3 compatibility. Signed-off-by: Andrew Leech Signed-off-by: Damien George --- ports/esp32/README.md | 2 +- ports/esp32/network_wlan.c | 7 ++++++- ports/esp32/usb_serial_jtag.c | 11 +++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 4eb79138938..9f842a5a52c 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -28,7 +28,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.0.4, v5.0.5, v5.1.2, v5.2.0, v5.2.2. +Currently MicroPython supports v5.0.4, v5.0.5, v5.1.2, v5.2.0, v5.2.2, v5.3. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index 6509b4fc6aa..d52123ee0a3 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -768,6 +768,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_DPP), MP_ROM_INT(WIFI_AUTH_DPP) }, + #endif { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, @@ -775,7 +778,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +_Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) _Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) _Static_assert(WIFI_AUTH_MAX == 11, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); diff --git a/ports/esp32/usb_serial_jtag.c b/ports/esp32/usb_serial_jtag.c index 32eb806e729..c4834e56f96 100644 --- a/ports/esp32/usb_serial_jtag.c +++ b/ports/esp32/usb_serial_jtag.c @@ -35,10 +35,13 @@ #include "soc/periph_defs.h" #include "freertos/portmacro.h" -#define USB_SERIAL_JTAG_BUF_SIZE (64) +// Number of bytes in the input buffer, and number of bytes for output chunking. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#define USB_SERIAL_JTAG_PACKET_SZ_BYTES (64) +#endif static DRAM_ATTR portMUX_TYPE rx_mux = portMUX_INITIALIZER_UNLOCKED; -static uint8_t rx_buf[USB_SERIAL_JTAG_BUF_SIZE]; +static uint8_t rx_buf[USB_SERIAL_JTAG_PACKET_SZ_BYTES]; static volatile bool terminal_connected = false; static void usb_serial_jtag_handle_rx(void) { @@ -48,8 +51,8 @@ static void usb_serial_jtag_handle_rx(void) { portENTER_CRITICAL(&rx_mux); } size_t req_len = ringbuf_free(&stdin_ringbuf); - if (req_len > USB_SERIAL_JTAG_BUF_SIZE) { - req_len = USB_SERIAL_JTAG_BUF_SIZE; + if (req_len > USB_SERIAL_JTAG_PACKET_SZ_BYTES) { + req_len = USB_SERIAL_JTAG_PACKET_SZ_BYTES; } size_t len = usb_serial_jtag_ll_read_rxfifo(rx_buf, req_len); for (size_t i = 0; i < len; ++i) { From 5fb846df67189363904dcbb9c2bc93d0f287ccd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 3 Dec 2024 11:08:13 +0100 Subject: [PATCH 0100/2098] esp32: Fix machine_touchpad compiling on IDFv5.3. Signed-off-by: Damien George --- ports/esp32/machine_touchpad.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index 48250280bad..299c489f5a8 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -31,9 +31,17 @@ #if SOC_TOUCH_SENSOR_SUPPORTED -#if SOC_TOUCH_VERSION_1 // ESP32 only +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) +#if SOC_TOUCH_VERSION_1 +#define SOC_TOUCH_SENSOR_VERSION (1) +#elif SOC_TOUCH_VERSION_2 +#define SOC_TOUCH_SENSOR_VERSION (2) +#endif +#endif + +#if SOC_TOUCH_SENSOR_VERSION == 1 // ESP32 only #include "driver/touch_pad.h" -#elif SOC_TOUCH_VERSION_2 // All other SoCs with touch, to date +#elif SOC_TOUCH_SENSOR_VERSION == 2 // All other SoCs with touch, to date #include "driver/touch_sensor.h" #else #error "Unknown touch hardware version" @@ -98,13 +106,13 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); initialized = 1; } - #if SOC_TOUCH_VERSION_1 + #if SOC_TOUCH_SENSOR_VERSION == 1 esp_err_t err = touch_pad_config(self->touchpad_id, 0); - #elif SOC_TOUCH_VERSION_2 + #elif SOC_TOUCH_SENSOR_VERSION == 2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { - #if SOC_TOUCH_VERSION_2 + #if SOC_TOUCH_SENSOR_VERSION == 2 touch_pad_fsm_start(); #endif @@ -115,10 +123,10 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ static mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { mtp_obj_t *self = self_in; - #if SOC_TOUCH_VERSION_1 + #if SOC_TOUCH_SENSOR_VERSION == 1 uint16_t value = mp_obj_get_int(value_in); esp_err_t err = touch_pad_config(self->touchpad_id, value); - #elif SOC_TOUCH_VERSION_2 + #elif SOC_TOUCH_SENSOR_VERSION == 2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { @@ -130,10 +138,10 @@ MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); static mp_obj_t mtp_read(mp_obj_t self_in) { mtp_obj_t *self = self_in; - #if SOC_TOUCH_VERSION_1 + #if SOC_TOUCH_SENSOR_VERSION == 1 uint16_t value; esp_err_t err = touch_pad_read(self->touchpad_id, &value); - #elif SOC_TOUCH_VERSION_2 + #elif SOC_TOUCH_SENSOR_VERSION == 2 uint32_t value; esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); #endif From 160a4812cdd846f82f1f0bfb3798ada760d84d22 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Dec 2024 11:00:24 +1100 Subject: [PATCH 0101/2098] esp32: Pass V=1 or BUILD_VERBOSE through to idf.py when building. Allows verbose build to work the same on esp32 port as other ports. To minimise copy/paste, split the BUILD_VERBOSE section of mkenv.mk out to its own verbose.mk and include this in the port Makefile. Signed-off-by: Angus Gratton --- ports/esp32/Makefile | 6 ++++++ py/mkenv.mk | 17 +---------------- py/verbose.mk | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 py/verbose.mk diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 4ad3cf0267c..8888180766b 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -2,6 +2,8 @@ # # This is a simple, convenience wrapper around idf.py (which uses cmake). +include ../../py/verbose.mk + # Select the board to build for: ifdef BOARD_DIR # Custom board path - remove trailing slash and get the final component of @@ -61,6 +63,10 @@ ifdef MICROPY_PREVIEW_VERSION_2 IDFPY_FLAGS += -D MICROPY_PREVIEW_VERSION_2=1 endif +ifeq ($(BUILD_VERBOSE),1) + IDFPY_FLAGS += --verbose +endif + HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" define RUN_IDF_PY diff --git a/py/mkenv.mk b/py/mkenv.mk index b52dafbc9d0..4656d113665 100644 --- a/py/mkenv.mk +++ b/py/mkenv.mk @@ -12,22 +12,7 @@ endif THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) TOP := $(patsubst %/py/mkenv.mk,%,$(THIS_MAKEFILE)) -# Turn on increased build verbosity by defining BUILD_VERBOSE in your main -# Makefile or in your environment. You can also use V=1 on the make command -# line. - -ifeq ("$(origin V)", "command line") -BUILD_VERBOSE=$(V) -endif -ifndef BUILD_VERBOSE -$(info Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.) -BUILD_VERBOSE = 0 -endif -ifeq ($(BUILD_VERBOSE),0) -Q = @ -else -Q = -endif +include $(TOP)/py/verbose.mk # default settings; can be overridden in main Makefile diff --git a/py/verbose.mk b/py/verbose.mk new file mode 100644 index 00000000000..734623a21e8 --- /dev/null +++ b/py/verbose.mk @@ -0,0 +1,16 @@ +# Turn on increased build verbosity by defining BUILD_VERBOSE in your main +# Makefile or in your environment. You can also use V=1 on the make command +# line. + +ifeq ("$(origin V)", "command line") +BUILD_VERBOSE=$(V) +endif +ifndef BUILD_VERBOSE +$(info Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.) +BUILD_VERBOSE = 0 +endif +ifeq ($(BUILD_VERBOSE),0) +Q = @ +else +Q = +endif From 31a1e2b96decb9b5945461d808f725d40720d3c4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Dec 2024 11:22:49 +1100 Subject: [PATCH 0102/2098] rp2: Pass V=1 or BUILD_VERBOSE to rp2 build. Similar to esp32. Previously rp2 could build verbose by passing VERBOSE=1, which is picked up by Makefiles generated from CMake. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index f950bc7b908..afa21cc7a43 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -2,6 +2,8 @@ # # This is a simple wrapper around cmake +include ../../py/verbose.mk + # Select the board to build for: ifdef BOARD_DIR # Custom board path - remove trailing slash and get the final component of @@ -28,7 +30,9 @@ else BUILD ?= build-$(BOARD) endif -$(VERBOSE)MAKESILENT = -s +ifeq ($(BUILD_VERBOSE),1) +MAKE_ARGS += VERBOSE=1 # Picked up in Makefile generated by CMake +endif CMAKE_ARGS += -DMICROPY_BOARD=$(BOARD) -DMICROPY_BOARD_DIR="$(abspath $(BOARD_DIR))" @@ -56,7 +60,7 @@ HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wi all: [ -e $(BUILD)/Makefile ] || cmake -S . -B $(BUILD) -DPICO_BUILD_DOCS=0 ${CMAKE_ARGS} - $(MAKE) $(MAKESILENT) -C $(BUILD) || (echo -e $(HELP_BUILD_ERROR); false) + $(MAKE) $(MAKE_ARGS) -C $(BUILD) || (echo -e $(HELP_BUILD_ERROR); false) clean: $(RM) -rf $(BUILD) From 0f7d68043fce3c305ad68cba06e4126628048f3e Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 3 Dec 2024 13:24:47 +0100 Subject: [PATCH 0103/2098] py/misc: Fix msvc and C++ compatibility. Use an explicit cast to suppress the implicit conversion which started popping up in recent compiler versions (and wasn't there yet in 07bf3179). Signed-off-by: stijn --- py/misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/misc.h b/py/misc.h index 8ac80bb7b3e..2629d0c46cc 100644 --- a/py/misc.h +++ b/py/misc.h @@ -357,7 +357,7 @@ static inline uint32_t mp_clzll(unsigned long long x) { // Microsoft don't ship _BitScanReverse64 on Win32, so emulate it static inline uint32_t mp_clzll(unsigned long long x) { unsigned long h = x >> 32; - return h ? mp_clzl(h) : (mp_clzl(x) + 32); + return h ? mp_clzl(h) : (mp_clzl((unsigned long)x) + 32); } #endif From 5784714f734caf3713ff880ffe100bbbec59216e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 18 Nov 2024 22:00:31 +0100 Subject: [PATCH 0104/2098] shared/runtime/gchelper_generic: Fix AArch32 build on Clang. This commit fixes a compile error happening on Clang when building the generic gchelper code for AArch32. Clang would raise a warning regarding undefined variable access when aliasing a variable to an existing CPU register. The fix is pretty crude but it works - it simply disables the warning in question for the AArch32 gchelper collection function. Care was taken to make sure the code would also compile on GCC without warnings of sorts. Signed-off-by: Alessandro Gatti --- shared/runtime/gchelper_generic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c index 09372313740..45b2e4f7d84 100644 --- a/shared/runtime/gchelper_generic.c +++ b/shared/runtime/gchelper_generic.c @@ -101,6 +101,10 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { // Fallback implementation, prefer gchelper_thumb1.s or gchelper_thumb2.s static void gc_helper_get_regs(gc_helper_regs_t arr) { + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wuninitialized" + #endif register long r4 asm ("r4"); register long r5 asm ("r5"); register long r6 asm ("r6"); @@ -121,6 +125,9 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { arr[7] = r11; arr[8] = r12; arr[9] = r13; + #ifdef __clang__ + #pragma clang diagnostic pop + #endif } #elif defined(__aarch64__) From 2a8f6047ff5c94bba7ace738a14b572cc8553251 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 18 Nov 2024 22:04:14 +0100 Subject: [PATCH 0105/2098] py/emitglue: Fix clear cache builtin warning on Clang for AArch32. This commit fixes a warning occurring on Clang when calling `__builtin___clear_cache` with non-void pointers for its start and end memory area locations. The code now uses a char pointer for the end location, and it still builds without warnings on GCC. Signed-off-by: Alessandro Gatti --- py/emitglue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/emitglue.c b/py/emitglue.c index 444e480477e..27cbb349ef6 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -115,7 +115,7 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons #endif #elif MICROPY_EMIT_ARM #if (defined(__linux__) && defined(__GNUC__)) || __ARM_ARCH == 7 - __builtin___clear_cache((void *)fun_data, (uint8_t *)fun_data + fun_len); + __builtin___clear_cache((void *)fun_data, (char *)fun_data + fun_len); #elif defined(__arm__) // Flush I-cache and D-cache. asm volatile ( From 28b52446669b72f9863902510430bdeeaaa39fa6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 4 Dec 2024 22:45:28 +0100 Subject: [PATCH 0106/2098] extmod/modplatform: Distinguish RISC-V 64 from RISC-V 32. This commit lets the platform module report a more accurate architecture name when running on a RISC-V 64 bits platform. Signed-off-by: Alessandro Gatti --- extmod/modplatform.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extmod/modplatform.h b/extmod/modplatform.h index 5c9cbffb2a4..a155f071cba 100644 --- a/extmod/modplatform.h +++ b/extmod/modplatform.h @@ -48,7 +48,11 @@ #elif defined(__xtensa__) #define MICROPY_PLATFORM_ARCH "xtensa" #elif defined(__riscv) +#if __riscv_xlen == 64 +#define MICROPY_PLATFORM_ARCH "riscv64" +#else #define MICROPY_PLATFORM_ARCH "riscv" +#endif #else #define MICROPY_PLATFORM_ARCH "" #endif From e8c3f31ba2e5f004dfb972131d5f7d7a961974c2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 12 Nov 2024 07:16:38 +0100 Subject: [PATCH 0107/2098] mimxrt/machine_rtc: Deprecate RTC.cancel in MicroPython v2. The current documentation for the `machine.RTC` class contains information about the `RTC.cancel` method for cancelling pending alarms. However only two ports (cc3200 and mimxrt) implement this functionality but under a different name: `RTC.alarm_cancel`. The mimxrt port also implements `RTC.cancel` but it is aliased to `RTC.alarm_cancel` anyway. To maintain naming consistency, this commit updates the documentation to officially define `RTC.alarm_cancel` as the method to call to cancel pending alarms and deprecates mimxrt's `RTC.cancel` implementation. `RTC.cancel` in the mimxrt port is thus scheduled for removal in MicroPython v2. Signed-off-by: Alessandro Gatti --- docs/library/machine.RTC.rst | 5 ++++- ports/mimxrt/machine_rtc.c | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index ad0b0759efe..a457189037b 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -62,10 +62,13 @@ Methods Get the number of milliseconds left before the alarm expires. -.. method:: RTC.cancel(alarm_id=0) +.. method:: RTC.alarm_cancel(alarm_id=0) Cancel a running alarm. + The mimxrt port also exposes this function as ``RTC.cancel(alarm_id=0)``, but this is + scheduled to be removed in MicroPython 2.0. + .. method:: RTC.irq(*, trigger, handler=None, wake=machine.IDLE) Create an irq object triggered by a real time clock alarm. diff --git a/ports/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index 294942cf5a0..0d6fc54c8b6 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -394,7 +394,9 @@ static const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&machine_rtc_alarm_obj) }, { MP_ROM_QSTR(MP_QSTR_alarm_left), MP_ROM_PTR(&machine_rtc_alarm_left_obj) }, { MP_ROM_QSTR(MP_QSTR_alarm_cancel), MP_ROM_PTR(&machine_rtc_alarm_cancel_obj) }, + #if !MICROPY_PREVIEW_VERSION_2 { MP_ROM_QSTR(MP_QSTR_cancel), MP_ROM_PTR(&machine_rtc_alarm_cancel_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_rtc_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_ALARM0), MP_ROM_INT(0) }, }; From fdd606dd5395d9c474775945fa4458a27199653b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 6 Nov 2024 10:31:28 +1100 Subject: [PATCH 0108/2098] py/mkrules.mk: Use partial clone for submodules if available. MicroPython relies on a number of submodules for third party and chip vendor libraries. Users need to check these out before building their desired ports and Github Actions CI here needs to clone them all multiple times for every build. Many of these are getting significantly larger over time, slowing down usage and consuming more disk space. Newer versions of git have features to avoid pulling all historic / blob data which can have a significant impact of total data use. This commit uses a standard feature of git to do a partial clone, with automatic fallback to previous behavior on error. Signed-off-by: Andrew Leech --- py/mkrules.mk | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/py/mkrules.mk b/py/mkrules.mk index 0dc1cdfe15a..875ddee8523 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -252,7 +252,11 @@ submodules: $(ECHO) "Updating submodules: $(GIT_SUBMODULES)" ifneq ($(GIT_SUBMODULES),) $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) - $(Q)cd $(TOP) && git submodule update --init $(GIT_SUBMODULES) + # If available, do blobless partial clones of submodules to save time and space. + # A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.). + # Fallback to standard submodule update if blobless isn't available (earlier than 2.36.0) + $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) || \ + git submodule update --init $(GIT_SUBMODULES) endif .PHONY: submodules From 9441ce6e3c8296ca8e6a5e0ddbd94ee5c9f4d7d6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 5 Sep 2024 02:59:45 +1000 Subject: [PATCH 0109/2098] esp32: Use capability defines to configure features. This updates esp32 code where appropriate to replace ifdef's based on a list of specific chips with a feature SOC_* definition. This should simplify adding new esp32-* chips in future, deferring chip feature support to the IDF. Signed-off-by: Andrew Leech --- ports/esp32/adc.c | 20 +++++++++----------- ports/esp32/machine_adc.c | 16 ++++++++++++---- ports/esp32/machine_adc_block.c | 9 +++------ ports/esp32/machine_bitstream.c | 2 +- ports/esp32/machine_i2c.c | 4 ++-- ports/esp32/machine_pin.c | 2 +- ports/esp32/modmachine.c | 12 ++++++++---- 7 files changed, 36 insertions(+), 29 deletions(-) diff --git a/ports/esp32/adc.c b/ports/esp32/adc.c index ccf264e5c2a..83af6dce0b2 100644 --- a/ports/esp32/adc.c +++ b/ports/esp32/adc.c @@ -33,28 +33,26 @@ #define DEFAULT_VREF 1100 void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits) { + if (bits < SOC_ADC_RTC_MIN_BITWIDTH && bits > SOC_ADC_RTC_MAX_BITWIDTH) { + // Invalid value for the current chip, raise exception in the switch below. + bits = -1; + } switch (bits) { - #if CONFIG_IDF_TARGET_ESP32 case 9: - self->width = ADC_WIDTH_BIT_9; + self->width = ADC_BITWIDTH_9; break; case 10: - self->width = ADC_WIDTH_BIT_10; + self->width = ADC_BITWIDTH_10; break; case 11: - self->width = ADC_WIDTH_BIT_11; + self->width = ADC_BITWIDTH_11; break; - #endif - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 case 12: - self->width = ADC_WIDTH_BIT_12; + self->width = ADC_BITWIDTH_12; break; - #endif - #if CONFIG_IDF_TARGET_ESP32S2 case 13: - self->width = ADC_WIDTH_BIT_13; + self->width = ADC_BITWIDTH_13; break; - #endif default: mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); } diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index dc21b69083c..be1725c3709 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -35,7 +35,7 @@ #define ADCBLOCK1 (&madcblock_obj[0]) #define ADCBLOCK2 (&madcblock_obj[1]) -#if CONFIG_IDF_TARGET_ESP32 +#if SOC_ADC_RTC_MIN_BITWIDTH <= 9 && SOC_ADC_RTC_MAX_BITWIDTH >= 11 #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS_WIDTH_9_10_11 \ { MP_ROM_QSTR(MP_QSTR_WIDTH_9BIT), MP_ROM_INT(9) }, \ { MP_ROM_QSTR(MP_QSTR_WIDTH_10BIT), MP_ROM_INT(10) }, \ @@ -44,14 +44,14 @@ #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS_WIDTH_9_10_11 #endif -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_ADC_RTC_MIN_BITWIDTH <= 12 && SOC_ADC_RTC_MAX_BITWIDTH >= 12 #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS_WIDTH_12 \ { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(12) }, #else #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS_WIDTH_12 #endif -#if CONFIG_IDF_TARGET_ESP32S2 +#if SOC_ADC_RTC_MIN_BITWIDTH <= 13 && SOC_ADC_RTC_MAX_BITWIDTH >= 13 #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS_WIDTH_13 \ { MP_ROM_QSTR(MP_QSTR_WIDTH_13BIT), MP_ROM_INT(13) }, #else @@ -87,13 +87,21 @@ static const machine_adc_obj_t madc_obj[] = { {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_27}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_25}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_26}, - #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 + #elif CONFIG_IDF_TARGET_ESP32C3 {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_5}, + #elif CONFIG_IDF_TARGET_ESP32C6 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_5}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_6}, #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_1}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_2}, diff --git a/ports/esp32/machine_adc_block.c b/ports/esp32/machine_adc_block.c index 78c5b2491b8..a373603b7e3 100644 --- a/ports/esp32/machine_adc_block.c +++ b/ports/esp32/machine_adc_block.c @@ -32,12 +32,9 @@ #include "driver/adc.h" machine_adc_block_obj_t madcblock_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_adc_block_type}, ADC_UNIT_1, 12, -1, {0}}, - {{&machine_adc_block_type}, ADC_UNIT_2, 12, -1, {0}}, - #elif CONFIG_IDF_TARGET_ESP32S2 - {{&machine_adc_block_type}, ADC_UNIT_1, 13, -1, {0}}, - {{&machine_adc_block_type}, ADC_UNIT_2, 13, -1, {0}}, + {{&machine_adc_block_type}, ADC_UNIT_1, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, + #if SOC_ADC_PERIPH_NUM > 1 + {{&machine_adc_block_type}, ADC_UNIT_2, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, #endif }; diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index b4e58c51f4d..6296ff06708 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -42,7 +42,7 @@ // This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { uint32_t pin_mask, gpio_reg_set, gpio_reg_clear; - #if !CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 + #if SOC_GPIO_PIN_COUNT > 32 if (pin >= 32) { pin_mask = 1 << (pin - 32); gpio_reg_set = GPIO_OUT1_W1TS_REG; diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index e1e1850da57..732a62f47f1 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -49,9 +49,9 @@ #endif #endif -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_I2C_SUPPORT_XTAL #define I2C_SCLK_FREQ XTAL_CLK_FREQ -#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#elif SOC_I2C_SUPPORT_APB #define I2C_SCLK_FREQ APB_CLK_FREQ #else #error "unsupported I2C for ESP32 SoC variant" diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 687dfe1938d..74202d6f758 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -43,7 +43,7 @@ #include "modesp32.h" #include "genhdr/pins.h" -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 +#if SOC_USB_SERIAL_JTAG_SUPPORTED #include "soc/usb_serial_jtag_reg.h" #endif diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 9b58d77529b..e775b1d1469 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -45,7 +45,7 @@ #define MICROPY_PY_MACHINE_SDCARD_ENTRY #endif -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_TOUCH_SENSOR_SUPPORTED #define MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, #else #define MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY @@ -147,30 +147,34 @@ static void machine_sleep_helper(wake_type_t wake_type, size_t n_args, const mp_ esp_sleep_enable_timer_wakeup(((uint64_t)expiry) * 1000); } - #if !(CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6) - + #if SOC_PM_SUPPORT_EXT0_WAKEUP if (machine_rtc_config.ext0_pin != -1 && (machine_rtc_config.ext0_wake_types & wake_type)) { esp_sleep_enable_ext0_wakeup(machine_rtc_config.ext0_pin, machine_rtc_config.ext0_level ? 1 : 0); } + #endif + #if SOC_PM_SUPPORT_EXT1_WAKEUP if (machine_rtc_config.ext1_pins != 0) { esp_sleep_enable_ext1_wakeup( machine_rtc_config.ext1_pins, machine_rtc_config.ext1_level ? ESP_EXT1_WAKEUP_ANY_HIGH : ESP_EXT1_WAKEUP_ALL_LOW); } + #endif + #if SOC_TOUCH_SENSOR_SUPPORTED if (machine_rtc_config.wake_on_touch) { if (esp_sleep_enable_touchpad_wakeup() != ESP_OK) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_touchpad_wakeup() failed")); } } + #endif + #if SOC_ULP_SUPPORTED if (machine_rtc_config.wake_on_ulp) { if (esp_sleep_enable_ulp_wakeup() != ESP_OK) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_ulp_wakeup() failed")); } } - #endif switch (wake_type) { From 82e382a399b07e0af64516749c12882fc0804a6d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 4 Dec 2024 12:30:25 +0100 Subject: [PATCH 0110/2098] esp32/mpconfigport: Use the appropriate wait-for-interrupt opcode. When threading is disabled, the pending events handling code would wait for an incoming interrupt once there's no more work to do. This bit of code was Xtensa-specific and wouldn't compile on a RISC-V based MCU. This commit provides the RISC-V equivalent to that part of the code, allowing to make threadless MicroPython builds on RISC-V based MCUs. Signed-off-by: Alessandro Gatti --- ports/esp32/mpconfigport.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index e36d12bc056..bf1f5a18641 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -292,12 +292,17 @@ void *esp_native_code_commit(void *, size_t, void *); MP_THREAD_GIL_ENTER(); \ } while (0); #else +#if CONFIG_IDF_TARGET_ARCH_RISCV +#define MICROPY_PY_WAIT_FOR_INTERRUPT asm volatile ("wfi\n") +#else +#define MICROPY_PY_WAIT_FOR_INTERRUPT asm volatile ("waiti 0\n") +#endif #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(bool); \ mp_handle_pending(true); \ MICROPY_PY_SOCKET_EVENTS_HANDLER \ - asm ("waiti 0"); \ + MICROPY_PY_WAIT_FOR_INTERRUPT; \ } while (0); #endif From 6e5d8d009349a7d7932ab0c93a739a576a3594d3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 1 Nov 2024 16:32:48 +1100 Subject: [PATCH 0111/2098] esp32: Drop support for ESP-IDF below V5.2.0. Specifically, remove all conditional compilation for these earlier versions and change the idf_component.yml specifiers to require >=5.2.0. Signed-off-by: Angus Gratton --- ports/esp32/README.md | 2 +- ports/esp32/machine_dac.c | 27 -------------- ports/esp32/machine_pwm.c | 43 ---------------------- ports/esp32/machine_uart.c | 4 -- ports/esp32/main_esp32/idf_component.yml | 2 +- ports/esp32/main_esp32c3/idf_component.yml | 2 +- ports/esp32/main_esp32c6/idf_component.yml | 2 +- ports/esp32/main_esp32s2/idf_component.yml | 2 +- ports/esp32/main_esp32s3/idf_component.yml | 2 +- ports/esp32/modesp32.c | 1 - ports/esp32/modmachine.c | 23 +++--------- ports/esp32/modnetwork_globals.h | 6 --- ports/esp32/mpthreadport.c | 2 +- ports/esp32/network_lan.c | 2 - ports/esp32/network_wlan.c | 12 +----- ports/esp32/uart.c | 4 -- 16 files changed, 13 insertions(+), 123 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 9f842a5a52c..a8bada7510f 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -28,7 +28,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.0.4, v5.0.5, v5.1.2, v5.2.0, v5.2.2, v5.3. +Currently MicroPython supports v5.2, v5.2.2, and v5.3. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/ports/esp32/machine_dac.c b/ports/esp32/machine_dac.c index 3d1d2df8081..1c5b7c914ca 100644 --- a/ports/esp32/machine_dac.c +++ b/ports/esp32/machine_dac.c @@ -34,21 +34,13 @@ #if MICROPY_PY_MACHINE_DAC #include "driver/gpio.h" -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) #include "driver/dac_oneshot.h" -#else -#include "driver/dac.h" -#define DAC_CHAN_0 DAC_CHANNEL_1 -#define DAC_CHAN_1 DAC_CHANNEL_2 -#endif typedef struct _mdac_obj_t { mp_obj_base_t base; gpio_num_t gpio_id; dac_channel_t dac_id; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) dac_oneshot_handle_t dac_oneshot_handle; - #endif } mdac_obj_t; static mdac_obj_t mdac_obj[] = { @@ -77,21 +69,10 @@ static mp_obj_t mdac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); } - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) dac_oneshot_config_t dac_oneshot_config = {.chan_id = self->dac_id}; check_esp_err(dac_oneshot_new_channel(&dac_oneshot_config, (dac_oneshot_handle_t *)&self->dac_oneshot_handle)); check_esp_err(dac_oneshot_output_voltage(self->dac_oneshot_handle, 0)); return MP_OBJ_FROM_PTR(self); - #else - esp_err_t err = dac_output_enable(self->dac_id); - if (err == ESP_OK) { - err = dac_output_voltage(self->dac_id, 0); - } - if (err == ESP_OK) { - return MP_OBJ_FROM_PTR(self); - } - mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); - #endif } static void mdac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -106,16 +87,8 @@ static mp_obj_t mdac_write(mp_obj_t self_in, mp_obj_t value_in) { mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); } - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) check_esp_err(dac_oneshot_output_voltage(self->dac_oneshot_handle, value)); return mp_const_none; - #else - esp_err_t err = dac_output_voltage(self->dac_id, value); - if (err == ESP_OK) { - return mp_const_none; - } - mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); - #endif } MP_DEFINE_CONST_FUN_OBJ_2(mdac_write_obj, mdac_write); diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 0134fa2cc8c..6e3610b1566 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -211,39 +211,6 @@ static void configure_channel(machine_pwm_obj_t *self) { } } -// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) -static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { - // This implementation is based on the one used in Micropython v1.23 - - // Find the highest bit resolution for the requested frequency - unsigned int freq = src_clk_freq; - - int divider = (freq + timer_freq / 2) / timer_freq; // rounded - if (divider == 0) { - divider = 1; - } - float f = (float)freq / divider; // actual frequency - if (f <= 1.0) { - f = 1.0; - } - freq = (unsigned int)roundf((float)freq / f); - - unsigned int res = 0; - for (; freq > 1; freq >>= 1) { - ++res; - } - if (res == 0) { - res = 1; - } else if (res > HIGHEST_PWM_RES) { - // Limit resolution to HIGHEST_PWM_RES to match units of our duty - res = HIGHEST_PWM_RES; - } - - return res; -} -#endif - static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { esp_err_t err; if (freq != timer->freq_hz) { @@ -265,20 +232,10 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf } #endif uint32_t src_clk_freq = 0; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); if (err != ESP_OK) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); } - #else - // Simplified fallback logic for IDF V5.0.x, for targets with APB only. - src_clk_freq = APB_CLK_FREQ; // 80 MHz - #if SOC_LEDC_SUPPORT_REF_TICK - if (timer->clk_cfg == LEDC_USE_REF_TICK) { - src_clk_freq = REF_CLK_FREQ; // 1 MHz - } - #endif // SOC_LEDC_SUPPORT_REF_TICK - #endif // ESP_IDF_VERSION timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index cff942f39e7..73089ef4636 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -399,11 +399,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } self->flowcontrol = args[ARG_flow].u_int; } - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) uint8_t uart_fifo_len = UART_HW_FIFO_LEN(self->uart_num); - #else - uint8_t uart_fifo_len = UART_FIFO_LEN; - #endif check_esp_err(uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, uart_fifo_len - uart_fifo_len / 4)); } diff --git a/ports/esp32/main_esp32/idf_component.yml b/ports/esp32/main_esp32/idf_component.yml index 5dabbc00ba7..11f078f69f7 100644 --- a/ports/esp32/main_esp32/idf_component.yml +++ b/ports/esp32/main_esp32/idf_component.yml @@ -2,4 +2,4 @@ dependencies: espressif/mdns: "~1.1.0" idf: - version: ">=5.0.4" + version: ">=5.2.0" diff --git a/ports/esp32/main_esp32c3/idf_component.yml b/ports/esp32/main_esp32c3/idf_component.yml index 5dabbc00ba7..11f078f69f7 100644 --- a/ports/esp32/main_esp32c3/idf_component.yml +++ b/ports/esp32/main_esp32c3/idf_component.yml @@ -2,4 +2,4 @@ dependencies: espressif/mdns: "~1.1.0" idf: - version: ">=5.0.4" + version: ">=5.2.0" diff --git a/ports/esp32/main_esp32c6/idf_component.yml b/ports/esp32/main_esp32c6/idf_component.yml index 5bbab6d8c3b..11f078f69f7 100644 --- a/ports/esp32/main_esp32c6/idf_component.yml +++ b/ports/esp32/main_esp32c6/idf_component.yml @@ -2,4 +2,4 @@ dependencies: espressif/mdns: "~1.1.0" idf: - version: ">=5.1.0" + version: ">=5.2.0" diff --git a/ports/esp32/main_esp32s2/idf_component.yml b/ports/esp32/main_esp32s2/idf_component.yml index 05ab2f29a8d..2ee00b28778 100644 --- a/ports/esp32/main_esp32s2/idf_component.yml +++ b/ports/esp32/main_esp32s2/idf_component.yml @@ -3,4 +3,4 @@ dependencies: espressif/mdns: "~1.1.0" espressif/esp_tinyusb: "~1.0.0" idf: - version: ">=5.0.4" + version: ">=5.2.0" diff --git a/ports/esp32/main_esp32s3/idf_component.yml b/ports/esp32/main_esp32s3/idf_component.yml index 05ab2f29a8d..2ee00b28778 100644 --- a/ports/esp32/main_esp32s3/idf_component.yml +++ b/ports/esp32/main_esp32s3/idf_component.yml @@ -3,4 +3,4 @@ dependencies: espressif/mdns: "~1.1.0" espressif/esp_tinyusb: "~1.0.0" idf: - version: ">=5.0.4" + version: ">=5.2.0" diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 0a73a0faf06..164b1918261 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -53,7 +53,6 @@ static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { if (machine_rtc_config.ext0_pin != -1) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } - // mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("touchpad wakeup not available for this version of ESP-IDF")); machine_rtc_config.wake_on_touch = mp_obj_is_true(wake); return mp_const_none; diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index e775b1d1469..0c1b94d02d9 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -110,24 +110,11 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz")); #endif } - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) - esp_pm_config_t pm; - #else - #if CONFIG_IDF_TARGET_ESP32 - esp_pm_config_esp32_t pm; - #elif CONFIG_IDF_TARGET_ESP32C3 - esp_pm_config_esp32c3_t pm; - #elif CONFIG_IDF_TARGET_ESP32C6 - esp_pm_config_esp32c6_t pm; - #elif CONFIG_IDF_TARGET_ESP32S2 - esp_pm_config_esp32s2_t pm; - #elif CONFIG_IDF_TARGET_ESP32S3 - esp_pm_config_esp32s3_t pm; - #endif - #endif - pm.max_freq_mhz = freq; - pm.min_freq_mhz = freq; - pm.light_sleep_enable = false; + esp_pm_config_t pm = { + .max_freq_mhz = freq, + .min_freq_mhz = freq, + .light_sleep_enable = false, + }; esp_err_t ret = esp_pm_configure(&pm); if (ret != ESP_OK) { mp_raise_ValueError(NULL); diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 2bfc2393d66..1aad785ee3d 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -33,13 +33,9 @@ { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WAPI_PSK), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_ENT_192), MP_ROM_INT(WIFI_AUTH_WPA3_ENT_192) }, -#endif -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, -#endif { MP_ROM_QSTR(MP_QSTR_AUTH_MAX), MP_ROM_INT(WIFI_AUTH_MAX) }, #endif @@ -75,11 +71,9 @@ { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(STAT_GOT_IP)}, // Errors from the ESP-IDF { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND)}, -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND_IN_RSSI_THRESHOLD), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD)}, { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD)}, { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND_W_COMPATIBLE_SECURITY), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY)}, -#endif { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(WIFI_REASON_AUTH_FAIL)}, { MP_ROM_QSTR(MP_QSTR_STAT_BEACON_TIMEOUT), MP_ROM_INT(WIFI_REASON_BEACON_TIMEOUT)}, #if !MICROPY_PREVIEW_VERSION_2 diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index eac1e5ea366..daed1bdd0ed 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -41,7 +41,7 @@ #define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + MICROPY_STACK_CHECK_MARGIN) #define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) && !CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP +#if !CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP #define FREERTOS_TASK_DELETE_HOOK vTaskPreDeletionHook #else #define FREERTOS_TASK_DELETE_HOOK vPortCleanUpTCB diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 416e02e12db..e1a8c957857 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -30,8 +30,6 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "esp_idf_version.h" - #if MICROPY_PY_NETWORK_LAN #include "esp_eth.h" diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index d52123ee0a3..fed81d28cb6 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -113,7 +113,6 @@ static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_b // AP may not exist, or it may have momentarily dropped out; try to reconnect. message = "no AP found"; break; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: // No AP with RSSI within given threshold exists, or it may have momentarily dropped out; try to reconnect. message = "no AP with RSSI within threshold found"; @@ -126,7 +125,6 @@ static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_b // No AP with compatible security exists, or it may have momentarily dropped out; try to reconnect. message = "no AP with compatible security found"; break; - #endif case WIFI_REASON_AUTH_FAIL: // Password may be wrong, or it just failed to connect; try to reconnect. message = "authentication failed"; @@ -367,14 +365,12 @@ static mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND); - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY); - #endif } else if ((wifi_sta_disconn_reason == WIFI_REASON_AUTH_FAIL) || (wifi_sta_disconn_reason == WIFI_REASON_CONNECTION_FAIL)) { // wrong password return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_AUTH_FAIL); @@ -761,13 +757,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WAPI), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT_192), MP_ROM_INT(WIFI_AUTH_WPA3_ENT_192) }, - #endif - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, - #endif #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) { MP_ROM_QSTR(MP_QSTR_SEC_DPP), MP_ROM_INT(WIFI_AUTH_DPP) }, #endif @@ -782,10 +774,8 @@ static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); _Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) _Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); -#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) -_Static_assert(WIFI_AUTH_MAX == 11, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); #else -_Static_assert(WIFI_AUTH_MAX == 10, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#error "Error in macro logic, all supported versions should be covered." #endif MP_DEFINE_CONST_OBJ_TYPE( diff --git a/ports/esp32/uart.c b/ports/esp32/uart.c index 8336268509e..491314e04d1 100644 --- a/ports/esp32/uart.c +++ b/ports/esp32/uart.c @@ -51,11 +51,7 @@ static void uart_irq_handler(void *arg); void uart_stdout_init(void) { uart_hal_context_t repl_hal = REPL_HAL_DEFN(); - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) - uart_sclk_t sclk; - #else soc_module_clk_t sclk; - #endif uint32_t sclk_freq; uart_hal_get_sclk(&repl_hal, &sclk); // To restore SCLK after uart_hal_init() resets it From d90aff5e13ff208728ac49f88e723abb8fbff044 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 1 Nov 2024 16:29:50 +1100 Subject: [PATCH 0112/2098] esp32: Remove IDF-version-specific sdkconfig. This reverts commit 27279e69b4d74b255bd8d8c5dd6668c97c942776 (plus removes some additional references to the SDKCONFIG_IDF_VERSION_SPECIFIC CMake variable.) Relevant sdkconfig options are added into sdkconfig.base now that IDF >=5.2.0 is required. Signed-off-by: Angus Gratton --- ports/esp32/CMakeLists.txt | 10 ---------- .../boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake | 1 - ports/esp32/boards/ESP32_GENERIC/mpconfigboard.cmake | 1 - .../esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake | 1 - .../esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake | 1 - .../esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake | 1 - .../esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake | 1 - .../boards/LILYGO_TTGO_LORA32/mpconfigboard.cmake | 1 - ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake | 1 - ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake | 1 - ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake | 1 - ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake | 1 - ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake | 1 - .../esp32/boards/OLIMEX_ESP32_EVB/mpconfigboard.cmake | 1 - .../esp32/boards/OLIMEX_ESP32_POE/mpconfigboard.cmake | 1 - ports/esp32/boards/SIL_WESP32/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_PROS3/mpconfigboard.cmake | 1 - .../esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake | 1 - ports/esp32/boards/sdkconfig.base | 3 ++- ports/esp32/boards/sdkconfig.idf52 | 2 -- 31 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 ports/esp32/boards/sdkconfig.idf52 diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index d695170fa2b..c3a675eb2b7 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -5,10 +5,6 @@ # needs to be duplicated for out-of-tree builds, and can easily get out of date. cmake_minimum_required(VERSION 3.12) -# Retrieve IDF version -include($ENV{IDF_PATH}/tools/cmake/version.cmake) -set(IDF_VERSION "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}") - # Set the board if it's not already set. if(NOT MICROPY_BOARD) set(MICROPY_BOARD ESP32_GENERIC) @@ -35,12 +31,6 @@ set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) # Save the manifest file set from the cmake command line. set(MICROPY_USER_FROZEN_MANIFEST ${MICROPY_FROZEN_MANIFEST}) -# Specific options for IDF v5.2 and later -set(SDKCONFIG_IDF_VERSION_SPECIFIC "") -if (IDF_VERSION VERSION_GREATER_EQUAL "5.2.0") - set(SDKCONFIG_IDF_VERSION_SPECIFIC boards/sdkconfig.idf52) -endif() - # Include board config; this is expected to set (among other options): # - SDKCONFIG_DEFAULTS # - IDF_TARGET diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake index d7db192ca43..a3888823421 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake @@ -6,7 +6,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/ESP32_GENERIC/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC/mpconfigboard.cmake index 9d0077a15e8..ac60dc86309 100644 --- a/ports/esp32/boards/ESP32_GENERIC/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC/mpconfigboard.cmake @@ -1,5 +1,4 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake index 8c9d7c27a11..429366afac9 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/ESP32_GENERIC_C3/sdkconfig.c3usb ) diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake index 3ab98e9da1a..3060c1cfd97 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.c6 boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake index c9bc60d4cb5..76d185d90d5 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.spiram_sx ) diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake index 2bfdad21df4..9b0df3b3771 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/LILYGO_TTGO_LORA32/mpconfigboard.cmake b/ports/esp32/boards/LILYGO_TTGO_LORA32/mpconfigboard.cmake index 42c431681ef..5bbac10e22f 100644 --- a/ports/esp32/boards/LILYGO_TTGO_LORA32/mpconfigboard.cmake +++ b/ports/esp32/boards/LILYGO_TTGO_LORA32/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake index 7767e3b8497..0ce7f85e5d5 100644 --- a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/LOLIN_C3_MINI/sdkconfig.board ) diff --git a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake index 8be4a69d72e..dc9abd7478b 100644 --- a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.spiram_sx boards/sdkconfig.usb ) diff --git a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake index 8be4a69d72e..dc9abd7478b 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.spiram_sx boards/sdkconfig.usb ) diff --git a/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake index 19b55d16597..f0355e2b038 100644 --- a/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake +++ b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/sdkconfig.240mhz boards/M5STACK_ATOM/sdkconfig.board diff --git a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake index 3ab98e9da1a..3060c1cfd97 100644 --- a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake +++ b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.c6 boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/OLIMEX_ESP32_EVB/mpconfigboard.cmake b/ports/esp32/boards/OLIMEX_ESP32_EVB/mpconfigboard.cmake index d6369d9ef18..3a6323d126d 100644 --- a/ports/esp32/boards/OLIMEX_ESP32_EVB/mpconfigboard.cmake +++ b/ports/esp32/boards/OLIMEX_ESP32_EVB/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/OLIMEX_ESP32_EVB/sdkconfig.board ) diff --git a/ports/esp32/boards/OLIMEX_ESP32_POE/mpconfigboard.cmake b/ports/esp32/boards/OLIMEX_ESP32_POE/mpconfigboard.cmake index 59d2831b3e0..c460b07d5ef 100644 --- a/ports/esp32/boards/OLIMEX_ESP32_POE/mpconfigboard.cmake +++ b/ports/esp32/boards/OLIMEX_ESP32_POE/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/OLIMEX_ESP32_POE/sdkconfig.board ) diff --git a/ports/esp32/boards/SIL_WESP32/mpconfigboard.cmake b/ports/esp32/boards/SIL_WESP32/mpconfigboard.cmake index 9caef1123cc..a1460397b81 100644 --- a/ports/esp32/boards/SIL_WESP32/mpconfigboard.cmake +++ b/ports/esp32/boards/SIL_WESP32/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/sdkconfig.240mhz boards/SIL_WESP32/sdkconfig.board diff --git a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake index e3bcba9f509..5e570d513bb 100644 --- a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake @@ -1,7 +1,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.spiram_sx boards/sdkconfig.usb boards/UM_FEATHERS2/sdkconfig.board diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake index fc7f246a7ee..c98ef691769 100644 --- a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake @@ -1,7 +1,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.spiram_sx boards/sdkconfig.usb boards/UM_FEATHERS2NEO/sdkconfig.board diff --git a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake index d7fcc346948..63d1af1da76 100644 --- a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake index 2114b321b2e..1d2b887d2ce 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake b/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake index 9030d837289..6c7f34009e4 100644 --- a/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake b/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake index a6a42a262e0..aa82111d758 100644 --- a/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake b/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake index 8bd67567ffd..41a96f26e34 100644 --- a/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake index f734411616b..8b29cb344e7 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake index e2df716a16b..024b1299fa3 100644 --- a/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.c6 boards/sdkconfig.ble boards/UM_TINYC6/sdkconfig.board diff --git a/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake index c85fb9d723b..58b42b55eb7 100644 --- a/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake @@ -1,6 +1,5 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram diff --git a/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake index 1c794baeed6..70cb4a814ff 100644 --- a/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake @@ -1,7 +1,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.spiram_sx boards/sdkconfig.usb boards/UM_TINYS2/sdkconfig.board diff --git a/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake index 9030d837289..6c7f34009e4 100644 --- a/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake index e788dc965d1..089149c44c5 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - ${SDKCONFIG_IDF_VERSION_SPECIFIC} boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index c7179b6125a..e20835c70c4 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -49,7 +49,8 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # FreeRTOS CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y +CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n +CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y # UDP CONFIG_LWIP_PPP_SUPPORT=y diff --git a/ports/esp32/boards/sdkconfig.idf52 b/ports/esp32/boards/sdkconfig.idf52 deleted file mode 100644 index 49b7317d4d6..00000000000 --- a/ports/esp32/boards/sdkconfig.idf52 +++ /dev/null @@ -1,2 +0,0 @@ -CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n -CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y From d4d1d4798ccdc7077f459005727ff32208cf1c5f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Dec 2024 17:11:15 +1100 Subject: [PATCH 0113/2098] esp32: Simplify thread cleanup. Now we only support the case of !CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP, can simplify the cleanup code. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/mpthreadport.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index daed1bdd0ed..f436ebb80a7 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -41,12 +41,6 @@ #define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + MICROPY_STACK_CHECK_MARGIN) #define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) -#if !CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP -#define FREERTOS_TASK_DELETE_HOOK vTaskPreDeletionHook -#else -#define FREERTOS_TASK_DELETE_HOOK vPortCleanUpTCB -#endif - // this structure forms a linked list, one node per active thread typedef struct _mp_thread_t { TaskHandle_t id; // system id of thread @@ -76,7 +70,7 @@ void mp_thread_init(void *stack, uint32_t stack_len) { // memory barrier to ensure above data is committed __sync_synchronize(); - // FREERTOS_TASK_DELETE_HOOK needs the thread ready after thread_mutex is ready + // vTaskPreDeletionHook needs the thread ready after thread_mutex is ready thread = &thread_entry0; } @@ -180,9 +174,10 @@ void mp_thread_finish(void) { mp_thread_mutex_unlock(&thread_mutex); } -// This is called from the FreeRTOS idle task and is not within Python context, -// so MP_STATE_THREAD is not valid and it does not have the GIL. -void FREERTOS_TASK_DELETE_HOOK(void *tcb) { +// This is called either from vTaskDelete() or from the FreeRTOS idle task, so +// may not be within Python context. Therefore MP_STATE_THREAD may not be valid +// and it does not have the GIL. +void vTaskPreDeletionHook(void *tcb) { if (thread == NULL) { // threading not yet initialised return; @@ -243,7 +238,7 @@ void mp_thread_deinit(void) { // No tasks left to delete break; } else { - // Call FreeRTOS to delete the task (it will call FREERTOS_TASK_DELETE_HOOK) + // Call FreeRTOS to delete the task (it will call vTaskPreDeletionHook) vTaskDelete(id); } } @@ -251,7 +246,7 @@ void mp_thread_deinit(void) { #else -void FREERTOS_TASK_DELETE_HOOK(void *tcb) { +void vTaskPreDeletionHook(void *tcb) { } #endif // MICROPY_PY_THREAD From 94343e20e590473ff1e31bd31c654befa42307a7 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 10 Dec 2024 14:07:29 +0100 Subject: [PATCH 0114/2098] stm32/boards/STM32F429DISC: Fix SDRAM configuration. Define SDRAM frequency and refresh cycles. This was missed in commit 17808e7b749b269b58dcee67df599a0c61d455bd. Signed-off-by: iabdalkader --- ports/stm32/boards/STM32F429DISC/mpconfigboard.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/boards/STM32F429DISC/mpconfigboard.h b/ports/stm32/boards/STM32F429DISC/mpconfigboard.h index 5d9287242b7..92e8ca411b4 100644 --- a/ports/stm32/boards/STM32F429DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F429DISC/mpconfigboard.h @@ -100,6 +100,7 @@ #define MICROPY_HW_SDRAM_BURST_LENGTH 2 #define MICROPY_HW_SDRAM_CAS_LATENCY 3 +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (90000) // 90 MHz #define MICROPY_HW_SDRAM_COLUMN_BITS_NUM 8 #define MICROPY_HW_SDRAM_ROW_BITS_NUM 12 #define MICROPY_HW_SDRAM_MEM_BUS_WIDTH 16 @@ -109,6 +110,7 @@ #define MICROPY_HW_SDRAM_RBURST (0) #define MICROPY_HW_SDRAM_WRITE_PROTECTION (0) #define MICROPY_HW_SDRAM_AUTOREFRESH_NUM (4) +#define MICROPY_HW_SDRAM_REFRESH_CYCLES 8192 #define MICROPY_HW_FMC_SDCKE1 (pin_B5) #define MICROPY_HW_FMC_SDNE1 (pin_B6) From bdda91fe7414b2ff8d5259a176cc8da8b72a803f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 4 Nov 2024 16:55:16 +1100 Subject: [PATCH 0115/2098] tests/extmod_hardware: Add a test for machine.PWM freq and duty. This adds a hardware test for `machine.PWM`. It requires a jumper wire between two pins, uses `machine.PWM` to output on one of them, and `machine.time_pulse_us()` to time the PWM on the other pin (some boards test more than one pair of pins). It times both the high and low duty cycle (and hence the frequency) for a range of PWM frequencies and duty cycles (including full on and full off). Currently supported on: - esp32 (needs a minor hack for initialisation, and some tests still fail) - esp8266 (passes for frequencies 1kHz and less) - mimxrt / Teensy 4.0 (passes) - rp2 (passes) - samd21 (passes for frequencies 2kHz and less) Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 166 +++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tests/extmod_hardware/machine_pwm.py diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py new file mode 100644 index 00000000000..014030be5c6 --- /dev/null +++ b/tests/extmod_hardware/machine_pwm.py @@ -0,0 +1,166 @@ +# Test machine.PWM, frequncy and duty cycle (using machine.time_pulse_us). +# +# IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input +# pins must be wired together (see the variable `pwm_pulse_pins`). + +import sys +import time + +try: + from machine import time_pulse_us, Pin, PWM +except ImportError: + print("SKIP") + raise SystemExit + +import unittest + +pwm_freq_limit = 1000000 +freq_margin_per_thousand = 0 +duty_margin_per_thousand = 0 +timing_margin_us = 5 + +# Configure pins based on the target. +if "esp32" in sys.platform: + pwm_pulse_pins = ((4, 5),) + freq_margin_per_thousand = 2 + duty_margin_per_thousand = 1 + timing_margin_us = 20 +elif "esp8266" in sys.platform: + pwm_pulse_pins = ((4, 5),) + pwm_freq_limit = 1_000 + duty_margin_per_thousand = 3 + timing_margin_us = 50 +elif "mimxrt" in sys.platform: + if "Teensy" in sys.implementation._machine: + # Teensy 4.x + pwm_pulse_pins = ( + ("D0", "D1"), # FLEXPWM X and UART 1 + ("D2", "D3"), # FLEXPWM A/B + ("D11", "D12"), # QTMR and MOSI/MISO of SPI 0 + ) + else: + pwm_pulse_pins = (("D0", "D1"),) +elif "rp2" in sys.platform: + pwm_pulse_pins = (("GPIO0", "GPIO1"),) +elif "samd" in sys.platform: + pwm_pulse_pins = (("D0", "D1"),) + if "SAMD21" in sys.implementation._machine: + # MCU is too slow to capture short pulses. + pwm_freq_limit = 2_000 +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +# Test a specific frequency and duty cycle. +def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): + print("freq={:<5} duty_u16={:<5} :".format(freq, duty_u16), end="") + + # Check configured freq/duty_u16 is within error bound. + freq_error = abs(pwm.freq() - freq) * 1000 // freq + duty_error = abs(pwm.duty_u16() - duty_u16) * 1000 // (duty_u16 or 1) + print(" freq={} freq_er={}".format(pwm.freq(), freq_error), end="") + print(" duty={} duty_er={}".format(pwm.duty_u16(), duty_error), end="") + print(" :", end="") + self.assertLessEqual(freq_error, freq_margin_per_thousand) + self.assertLessEqual(duty_error, duty_margin_per_thousand) + + # Calculate expected timing. + expected_total_us = 1_000_000 // freq + expected_high_us = expected_total_us * duty_u16 // 65535 + expected_low_us = expected_total_us - expected_high_us + expected_us = (expected_low_us, expected_high_us) + timeout = 2 * expected_total_us + + # Wait for output to settle. + time_pulse_us(pulse_in, 0, timeout) + time_pulse_us(pulse_in, 1, timeout) + + if duty_u16 == 0 or duty_u16 == 65535: + # Expect a constant output level. + no_pulse = ( + time_pulse_us(pulse_in, 0, timeout) < 0 and time_pulse_us(pulse_in, 1, timeout) < 0 + ) + self.assertTrue(no_pulse) + if expected_high_us == 0: + # Expect a constant low level. + self.assertEqual(pulse_in(), 0) + else: + # Expect a constant high level. + self.assertEqual(pulse_in(), 1) + else: + # Test timing of low and high pulse. + n_averaging = 10 + for level in (0, 1): + t = 0 + time_pulse_us(pulse_in, level, timeout) + for _ in range(n_averaging): + t += time_pulse_us(pulse_in, level, timeout) + t //= n_averaging + expected = expected_us[level] + print(" level={} timing_er={}".format(level, abs(t - expected)), end="") + self.assertLessEqual(abs(t - expected), timing_margin_us) + + print() + + +# Test a specific frequency with multiple duty cycles. +def _test_freq(self, freq): + print() + self.pwm.freq(freq) + for duty in (0, 10, 25, 50, 75, 90, 100): + duty_u16 = duty * 65535 // 100 + if sys.platform == "esp32": + # TODO why is this bit needed to get it working on esp32? + self.pwm.init(freq=freq, duty_u16=duty_u16) + time.sleep(0.1) + self.pwm.duty_u16(duty_u16) + _test_freq_duty(self, self.pulse_in, self.pwm, freq, duty_u16) + + +# Given a set of pins, this test class will test multiple frequencies and duty cycles. +class TestBase: + @classmethod + def setUpClass(cls): + print("set up pins:", cls.pwm_pin, cls.pulse_pin) + cls.pwm = PWM(cls.pwm_pin) + cls.pulse_in = Pin(cls.pulse_pin, Pin.IN) + + @classmethod + def tearDownClass(cls): + cls.pwm.deinit() + + def test_freq_50(self): + _test_freq(self, 50) + + def test_freq_100(self): + _test_freq(self, 100) + + def test_freq_500(self): + _test_freq(self, 500) + + def test_freq_1000(self): + _test_freq(self, 1000) + + @unittest.skipIf(pwm_freq_limit < 2000, "frequency too high") + def test_freq_2000(self): + _test_freq(self, 2000) + + @unittest.skipIf(pwm_freq_limit < 5000, "frequency too high") + def test_freq_5000(self): + _test_freq(self, 5000) + + @unittest.skipIf(pwm_freq_limit < 10000, "frequency too high") + def test_freq_10000(self): + _test_freq(self, 10000) + + +# Generate test classes, one for each set of pins to test. +for pwm, pulse in pwm_pulse_pins: + cls_name = "Test_{}_{}".format(pwm, pulse) + globals()[cls_name] = type( + cls_name, (TestBase, unittest.TestCase), {"pwm_pin": pwm, "pulse_pin": pulse} + ) + +if __name__ == "__main__": + unittest.main() From 5f2d05d41755990c1e14223475e19c7d204d9db2 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 4 Dec 2024 16:30:56 +0100 Subject: [PATCH 0116/2098] esp32: Enable machine.USBDevice to configure USB at runtime. This adds support for `machine.USBDevice` to S2 and S3 boards. Signed-off-by: Sebastian Romero --- ports/esp32/esp32_common.cmake | 1 + ports/esp32/main.c | 4 ++++ ports/esp32/mpconfigport.h | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 0a6b29983c2..b6bfcdacc3c 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -78,6 +78,7 @@ if(MICROPY_PY_TINYUSB) ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) list(APPEND MICROPY_INC_TINYUSB diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 18ef9d73547..4f0c27ee078 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -183,6 +183,10 @@ void mp_task(void *pvParameter) { mp_thread_deinit(); #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif + gc_sweep_all(); // Free any native code pointers that point to iRAM. diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index bf1f5a18641..b5b7d63a563 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -215,6 +215,10 @@ #else #define MICROPY_HW_USB_VID (CONFIG_TINYUSB_DESC_CUSTOM_VID) #endif + +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +#endif #endif #ifndef MICROPY_HW_USB_PID From 8e4c8096940c83c49c3ac07acc96b0133f65ec8e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 16 Dec 2024 11:56:03 +1100 Subject: [PATCH 0117/2098] unix,windows: Force _FILE_OFFSET_BITS=64 to fix 32-bit file ABI. On 64-bit systems this should have no effect. On 32-bit systems it will force 64-bit file sizes and fixes directory listing on certain 32-bit ports. This option should work on pretty much all 32-bit systems these days. Signed-off-by: Damien George --- ports/unix/Makefile | 4 ++++ ports/windows/Makefile | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index f02e6c6355a..88fa1af0454 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -48,6 +48,10 @@ CWARN = -Wall -Werror CWARN += -Wextra -Wno-unused-parameter -Wpointer-arith -Wdouble-promotion -Wfloat-conversion CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) +# Force the use of 64-bits for file sizes in C library functions on 32-bit platforms. +# This option has no effect on 64-bit builds. +CFLAGS += -D_FILE_OFFSET_BITS=64 + # Debugging/Optimization ifdef DEBUG COPT ?= -Og diff --git a/ports/windows/Makefile b/ports/windows/Makefile index cf0a927014b..115d1a61ef5 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -42,6 +42,10 @@ INC += -I$(VARIANT_DIR) CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Werror -std=gnu99 -DUNIX -D__USE_MINGW_ANSI_STDIO=1 $(COPT) $(CFLAGS_EXTRA) LDFLAGS += -lm -lbcrypt $(LDFLAGS_EXTRA) +# Force the use of 64-bits for file sizes in C library functions on 32-bit platforms. +# This option has no effect on 64-bit builds. +CFLAGS += -D_FILE_OFFSET_BITS=64 + # Debugging/Optimization ifdef DEBUG CFLAGS += -g From ee8d8b3448f26abafbfe1ed7f8970e02f4f8d232 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 16 Dec 2024 11:58:43 +1100 Subject: [PATCH 0118/2098] tools/ci.sh: Re-enable vfs_posix tests on unix qemu MIPS CI. These work now that _FILE_OFFSET_BITS=64. Signed-off-by: Damien George --- tools/ci.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index b9f9dec8bc2..33559d33916 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -682,10 +682,8 @@ function ci_unix_qemu_mips_build { } function ci_unix_qemu_mips_run_tests { - # Issues with MIPS tests: - # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py --exclude 'vfs_posix.*\.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py) } function ci_unix_qemu_arm_setup { From 46a37a09fdf469958c14a64ebdab974a4ca87a21 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Nov 2024 23:57:50 +1100 Subject: [PATCH 0119/2098] qemu/mpconfigport: Enable VFS reader, loading .mpy files and io.IOBase. This allows importing from the VFS, and enables the following 7 additional tests: builtin_execfile, io_iobase, json_dump_iobase, import_mpy_invalid, import_mpy_native, import_mpy_native_gc, vfs_userfs. Signed-off-by: Damien George --- ports/qemu/main.c | 4 ---- ports/qemu/mpconfigport.h | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ports/qemu/main.c b/ports/qemu/main.c index 04210658040..dff55058e16 100644 --- a/ports/qemu/main.c +++ b/ports/qemu/main.c @@ -71,10 +71,6 @@ void gc_collect(void) { gc_collect_end(); } -mp_lexer_t *mp_lexer_new_from_file(qstr filename) { - mp_raise_OSError(MP_ENOENT); -} - void nlr_jump_fail(void *val) { mp_printf(&mp_plat_print, "uncaught NLR\n"); exit(1); diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index f3dd8c37508..f95b58ba4d4 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -42,13 +42,14 @@ #endif #define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_MEM_STATS (1) +#define MICROPY_READER_VFS (1) #define MICROPY_ENABLE_GC (1) #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_IO_IOBASE (0) #define MICROPY_PY_SYS_PLATFORM "qemu" #define MICROPY_PY_SYS_STDIO_BUFFER (0) #define MICROPY_PY_SELECT (0) From bd6edf00bba5dd5f64f54946cce4e396ed841209 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Dec 2024 23:29:30 +1100 Subject: [PATCH 0120/2098] stm32/pin: Add option to exclude legacy Pin methods and constants. This is enabled by default, but disabled when MICROPY_PREVIEW_VERSION_2 is enabled. The intention is that these methods and constants are deprecated in MicroPython 2.x. Signed-off-by: Damien George --- ports/stm32/mpconfigboard_common.h | 5 ++++ ports/stm32/pin.c | 42 ++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index c1584194833..f9fe1396a37 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -52,6 +52,11 @@ #define MICROPY_PY_PYB_LEGACY (1) #endif +// Whether to include legacy methods and constants in machine.Pin (which is also pyb.Pin). +#ifndef MICROPY_PY_MACHINE_PIN_LEGACY +#define MICROPY_PY_MACHINE_PIN_LEGACY (!MICROPY_PREVIEW_VERSION_2) +#endif + // Whether machine.bootloader() will enter the bootloader via reset, or direct jump. #ifndef MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (1) diff --git a/ports/stm32/pin.c b/ports/stm32/pin.c index 117a8366dbf..08597154651 100644 --- a/ports/stm32/pin.c +++ b/ports/stm32/pin.c @@ -280,6 +280,8 @@ static mp_obj_t pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_ } } +#if MICROPY_PY_MACHINE_PIN_LEGACY + /// \classmethod mapper([fun]) /// Get or set the pin mapper function. static mp_obj_t pin_mapper(size_t n_args, const mp_obj_t *args) { @@ -304,20 +306,6 @@ static mp_obj_t pin_map_dict(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_map_dict_fun_obj, 1, 2, pin_map_dict); static MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_map_dict_obj, MP_ROM_PTR(&pin_map_dict_fun_obj)); -/// \classmethod af_list() -/// Returns an array of alternate functions available for this pin. -static mp_obj_t pin_af_list(mp_obj_t self_in) { - machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t result = mp_obj_new_list(0, NULL); - - const pin_af_obj_t *af = self->af; - for (mp_uint_t i = 0; i < self->num_af; i++, af++) { - mp_obj_list_append(result, MP_OBJ_FROM_PTR(af)); - } - return result; -} -static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_list_obj, pin_af_list); - /// \classmethod debug([state]) /// Get or set the debugging state (`True` or `False` for on or off). static mp_obj_t pin_debug(size_t n_args, const mp_obj_t *args) { @@ -330,6 +318,8 @@ static mp_obj_t pin_debug(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_debug_fun_obj, 1, 2, pin_debug); static MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_debug_obj, MP_ROM_PTR(&pin_debug_fun_obj)); +#endif // MICROPY_PY_MACHINE_PIN_LEGACY + // init(mode, pull=None, alt=-1, *, value, alt) static mp_obj_t pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { @@ -442,6 +432,8 @@ static mp_obj_t pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } static MP_DEFINE_CONST_FUN_OBJ_KW(pin_irq_obj, 1, pin_irq); +#if MICROPY_PY_MACHINE_PIN_LEGACY + /// \method name() /// Get the pin name. static mp_obj_t pin_name(mp_obj_t self_in) { @@ -469,6 +461,20 @@ static mp_obj_t pin_names(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(pin_names_obj, pin_names); +/// \classmethod af_list() +/// Returns an array of alternate functions available for this pin. +static mp_obj_t pin_af_list(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t result = mp_obj_new_list(0, NULL); + + const pin_af_obj_t *af = self->af; + for (mp_uint_t i = 0; i < self->num_af; i++, af++) { + mp_obj_list_append(result, MP_OBJ_FROM_PTR(af)); + } + return result; +} +static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_list_obj, pin_af_list); + /// \method port() /// Get the pin port. static mp_obj_t pin_port(mp_obj_t self_in) { @@ -520,6 +526,8 @@ static mp_obj_t pin_af(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_obj, pin_af); +#endif // MICROPY_PY_MACHINE_PIN_LEGACY + static const mp_rom_map_elem_t pin_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pin_init_obj) }, @@ -531,6 +539,7 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { // Legacy names as used by pyb.Pin { MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&pin_on_obj) }, + #if MICROPY_PY_MACHINE_PIN_LEGACY { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&pin_name_obj) }, { MP_ROM_QSTR(MP_QSTR_names), MP_ROM_PTR(&pin_names_obj) }, { MP_ROM_QSTR(MP_QSTR_af_list), MP_ROM_PTR(&pin_af_list_obj) }, @@ -540,11 +549,14 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&pin_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_pull), MP_ROM_PTR(&pin_pull_obj) }, { MP_ROM_QSTR(MP_QSTR_af), MP_ROM_PTR(&pin_af_obj) }, + #endif + #if MICROPY_PY_MACHINE_PIN_LEGACY // class methods { MP_ROM_QSTR(MP_QSTR_mapper), MP_ROM_PTR(&pin_mapper_obj) }, { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&pin_map_dict_obj) }, { MP_ROM_QSTR(MP_QSTR_debug), MP_ROM_PTR(&pin_debug_obj) }, + #endif // class attributes { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&pin_board_pins_obj_type) }, @@ -562,12 +574,14 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_MODE_IT_RISING) }, { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_MODE_IT_FALLING) }, + #if MICROPY_PY_MACHINE_PIN_LEGACY // legacy class constants { MP_ROM_QSTR(MP_QSTR_OUT_PP), MP_ROM_INT(GPIO_MODE_OUTPUT_PP) }, { MP_ROM_QSTR(MP_QSTR_OUT_OD), MP_ROM_INT(GPIO_MODE_OUTPUT_OD) }, { MP_ROM_QSTR(MP_QSTR_AF_PP), MP_ROM_INT(GPIO_MODE_AF_PP) }, { MP_ROM_QSTR(MP_QSTR_AF_OD), MP_ROM_INT(GPIO_MODE_AF_OD) }, { MP_ROM_QSTR(MP_QSTR_PULL_NONE), MP_ROM_INT(GPIO_NOPULL) }, + #endif #include "genhdr/pins_af_const.h" }; From 1c2cdf9f6dc649280ecfcc114100b3c2443c9402 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Dec 2024 23:38:38 +1100 Subject: [PATCH 0121/2098] stm32/pin: Add config option to exclude Pin alternate function. Signed-off-by: Damien George --- ports/stm32/mpconfigboard_common.h | 5 ++++ ports/stm32/pin.c | 40 ++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index f9fe1396a37..5c66b804b2f 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -57,6 +57,11 @@ #define MICROPY_PY_MACHINE_PIN_LEGACY (!MICROPY_PREVIEW_VERSION_2) #endif +// Whether to include support for alternate function selection in machine.Pin (and pyb.Pin). +#ifndef MICROPY_PY_MACHINE_PIN_ALT_SUPPORT +#define MICROPY_PY_MACHINE_PIN_ALT_SUPPORT (1) +#endif + // Whether machine.bootloader() will enter the bootloader via reset, or direct jump. #ifndef MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (1) diff --git a/ports/stm32/pin.c b/ports/stm32/pin.c index 08597154651..1430c7c64a3 100644 --- a/ports/stm32/pin.c +++ b/ports/stm32/pin.c @@ -320,14 +320,29 @@ static MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_debug_obj, MP_ROM_PTR(&pin_debug_fun_ #endif // MICROPY_PY_MACHINE_PIN_LEGACY -// init(mode, pull=None, alt=-1, *, value, alt) +// init(mode, pull=None, af=-1, *, value, alt) static mp_obj_t pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_mode, + ARG_pull, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT + ARG_af, + #endif + ARG_value, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT + ARG_alt, + #endif + }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_pull, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT { MP_QSTR_af, MP_ARG_INT, {.u_int = -1}}, // legacy + #endif { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT { MP_QSTR_alt, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, + #endif }; // parse args @@ -335,35 +350,38 @@ static mp_obj_t pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // get io mode - uint mode = args[0].u_int; + uint mode = args[ARG_mode].u_int; if (!IS_GPIO_MODE(mode)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin mode: %d"), mode); } // get pull mode uint pull = GPIO_NOPULL; - if (args[1].u_obj != mp_const_none) { - pull = mp_obj_get_int(args[1].u_obj); + if (args[ARG_pull].u_obj != mp_const_none) { + pull = mp_obj_get_int(args[ARG_pull].u_obj); } if (!IS_GPIO_PULL(pull)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin pull: %d"), pull); } + mp_int_t af = -1; + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT // get af (alternate function); alt-arg overrides af-arg - mp_int_t af = args[4].u_int; + af = args[ARG_alt].u_int; if (af == -1) { - af = args[2].u_int; + af = args[ARG_af].u_int; } if ((mode == GPIO_MODE_AF_PP || mode == GPIO_MODE_AF_OD) && !IS_GPIO_AF(af)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin af: %d"), af); } + #endif // enable the peripheral clock for the port of this pin mp_hal_gpio_clock_enable(self->gpio); // if given, set the pin value before initialising to prevent glitches - if (args[3].u_obj != MP_OBJ_NULL) { - mp_hal_pin_write(self, mp_obj_is_true(args[3].u_obj)); + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + mp_hal_pin_write(self, mp_obj_is_true(args[ARG_value].u_obj)); } // configure the GPIO as requested @@ -566,8 +584,10 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_INPUT) }, { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_MODE_OUTPUT_PP) }, { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_OUTPUT_OD) }, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT { MP_ROM_QSTR(MP_QSTR_ALT), MP_ROM_INT(GPIO_MODE_AF_PP) }, { MP_ROM_QSTR(MP_QSTR_ALT_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_AF_OD) }, + #endif { MP_ROM_QSTR(MP_QSTR_ANALOG), MP_ROM_INT(GPIO_MODE_ANALOG) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULLUP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULLDOWN) }, @@ -578,12 +598,16 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { // legacy class constants { MP_ROM_QSTR(MP_QSTR_OUT_PP), MP_ROM_INT(GPIO_MODE_OUTPUT_PP) }, { MP_ROM_QSTR(MP_QSTR_OUT_OD), MP_ROM_INT(GPIO_MODE_OUTPUT_OD) }, + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT { MP_ROM_QSTR(MP_QSTR_AF_PP), MP_ROM_INT(GPIO_MODE_AF_PP) }, { MP_ROM_QSTR(MP_QSTR_AF_OD), MP_ROM_INT(GPIO_MODE_AF_OD) }, + #endif { MP_ROM_QSTR(MP_QSTR_PULL_NONE), MP_ROM_INT(GPIO_NOPULL) }, #endif + #if MICROPY_PY_MACHINE_PIN_ALT_SUPPORT #include "genhdr/pins_af_const.h" + #endif }; static MP_DEFINE_CONST_DICT(pin_locals_dict, pin_locals_dict_table); From 3203e950fc23b31f8a702babe4c220a248763804 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Dec 2024 00:04:34 +1100 Subject: [PATCH 0122/2098] tools/boardgen.py: Provide macro defns for number of cpu/board pins. So a port can use them if needed to exclude the Pin.cpu/Pin.board objects. Signed-off-by: Damien George --- tools/boardgen.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/boardgen.py b/tools/boardgen.py index 41a8e9f2e59..39bedf71cd0 100644 --- a/tools/boardgen.py +++ b/tools/boardgen.py @@ -172,6 +172,8 @@ def __init__(self, pin_type, enable_af=False): self._pins = [] self._pin_type = pin_type self._enable_af = enable_af + self._pin_cpu_num_entries = 0 + self._pin_board_num_entries = 0 # Allows a port to define a known cpu pin (without relying on it being in the # csv file). @@ -298,6 +300,9 @@ def print_board_locals_dict(self, out_source): # Don't include hidden pins in Pins.board. continue + # Keep track of the total number of Pin.board entries. + self._pin_board_num_entries += 1 + # We don't use the enable macro for board pins, because they # shouldn't be referenced in pins.csv unless they're # available. @@ -322,6 +327,9 @@ def print_cpu_locals_dict(self, out_source): file=out_source, ) for pin in self.available_pins(exclude_hidden=True): + # Keep track of the total number of Pin.cpu entries. + self._pin_cpu_num_entries += 1 + m = pin.enable_macro() if m: print(" #if {}".format(m), file=out_source) @@ -351,6 +359,20 @@ def board_name_define_prefix(self): # Print the pin_CPUNAME and pin_BOARDNAME macros. def print_defines(self, out_header, cpu=True, board=True): + # Provide #defines for the number of cpu and board pins. + print( + "#define MICROPY_PY_MACHINE_PIN_CPU_NUM_ENTRIES ({})".format( + self._pin_cpu_num_entries + ), + file=out_header, + ) + print( + "#define MICROPY_PY_MACHINE_PIN_BOARD_NUM_ENTRIES ({})".format( + self._pin_board_num_entries + ), + file=out_header, + ) + # Provide #defines for each cpu pin. for pin in self.available_pins(): print(file=out_header) From a2efafcce3a03e2fd972e2521cae2c2bec2e88ff Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Dec 2024 00:05:19 +1100 Subject: [PATCH 0123/2098] stm32/pin: Exclude Pin.cpu/Pin.board if they contain no entries. Signed-off-by: Damien George --- ports/stm32/pin.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/stm32/pin.c b/ports/stm32/pin.c index 1430c7c64a3..7de87f2c7be 100644 --- a/ports/stm32/pin.c +++ b/ports/stm32/pin.c @@ -577,8 +577,12 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { #endif // class attributes + #if MICROPY_PY_MACHINE_PIN_BOARD_NUM_ENTRIES > 0 { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&pin_board_pins_obj_type) }, + #endif + #if MICROPY_PY_MACHINE_PIN_CPU_NUM_ENTRIES > 0 { MP_ROM_QSTR(MP_QSTR_cpu), MP_ROM_PTR(&pin_cpu_pins_obj_type) }, + #endif // class constants { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_INPUT) }, From 23bfa95d34e9b6f836ee67a1564e04f7b86eaa36 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Sep 2024 13:06:30 +1000 Subject: [PATCH 0124/2098] stm32/extint: Fix EXTI IRQ handlers for H5 MCUs. The existing EXTI IRQ handlers are moved from `stm32_it.c` to `extint.c` to keep them with related code. A macro is defined to make it easier to define the handler function that handles one line, and correct handlers added for STM32H5xx MCUs. Also, to prevent errors in the future, `MP_STATIC_ASSERT( > 0)` is added to each handler function to check that the correct `IRQn` constant is used, which corresponds to the handler function name. Signed-off-by: Damien George --- ports/stm32/extint.c | 91 ++++++++++++++++++++++++++++++++++++++++++ ports/stm32/stm32_it.c | 78 ------------------------------------ 2 files changed, 91 insertions(+), 78 deletions(-) diff --git a/ports/stm32/extint.c b/ports/stm32/extint.c index 2a088101255..5b5658809bc 100644 --- a/ports/stm32/extint.c +++ b/ports/stm32/extint.c @@ -241,6 +241,97 @@ static const uint8_t nvic_irq_channel[EXTI_NUM_VECTORS] = { #endif }; +#define DEFINE_EXTI_IRQ_HANDLER(line) \ + void EXTI##line##_IRQHandler(void) { \ + MP_STATIC_ASSERT(EXTI##line##_IRQn > 0); \ + IRQ_ENTER(EXTI##line##_IRQn); \ + Handle_EXTI_Irq(line); \ + IRQ_EXIT(EXTI##line##_IRQn); \ + } + +#if defined(STM32F0) || defined(STM32L0) || defined(STM32G0) + +void EXTI0_1_IRQHandler(void) { + MP_STATIC_ASSERT(EXTI0_1_IRQn > 0); + IRQ_ENTER(EXTI0_1_IRQn); + Handle_EXTI_Irq(0); + Handle_EXTI_Irq(1); + IRQ_EXIT(EXTI0_1_IRQn); +} + +void EXTI2_3_IRQHandler(void) { + MP_STATIC_ASSERT(EXTI2_3_IRQn > 0); + IRQ_ENTER(EXTI2_3_IRQn); + Handle_EXTI_Irq(2); + Handle_EXTI_Irq(3); + IRQ_EXIT(EXTI2_3_IRQn); +} + +void EXTI4_15_IRQHandler(void) { + MP_STATIC_ASSERT(EXTI4_15_IRQn > 0); + IRQ_ENTER(EXTI4_15_IRQn); + for (int i = 4; i <= 15; ++i) { + Handle_EXTI_Irq(i); + } + IRQ_EXIT(EXTI4_15_IRQn); +} + +#elif defined(STM32F4) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32L1) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + +DEFINE_EXTI_IRQ_HANDLER(0) +DEFINE_EXTI_IRQ_HANDLER(1) +DEFINE_EXTI_IRQ_HANDLER(2) +DEFINE_EXTI_IRQ_HANDLER(3) +DEFINE_EXTI_IRQ_HANDLER(4) + +void EXTI9_5_IRQHandler(void) { + MP_STATIC_ASSERT(EXTI9_5_IRQn > 0); + IRQ_ENTER(EXTI9_5_IRQn); + Handle_EXTI_Irq(5); + Handle_EXTI_Irq(6); + Handle_EXTI_Irq(7); + Handle_EXTI_Irq(8); + Handle_EXTI_Irq(9); + IRQ_EXIT(EXTI9_5_IRQn); +} + +void EXTI15_10_IRQHandler(void) { + MP_STATIC_ASSERT(EXTI15_10_IRQn > 0); + IRQ_ENTER(EXTI15_10_IRQn); + Handle_EXTI_Irq(10); + Handle_EXTI_Irq(11); + Handle_EXTI_Irq(12); + Handle_EXTI_Irq(13); + Handle_EXTI_Irq(14); + Handle_EXTI_Irq(15); + IRQ_EXIT(EXTI15_10_IRQn); +} + +#elif defined(STM32H5) + +DEFINE_EXTI_IRQ_HANDLER(0) +DEFINE_EXTI_IRQ_HANDLER(1) +DEFINE_EXTI_IRQ_HANDLER(2) +DEFINE_EXTI_IRQ_HANDLER(3) +DEFINE_EXTI_IRQ_HANDLER(4) +DEFINE_EXTI_IRQ_HANDLER(5) +DEFINE_EXTI_IRQ_HANDLER(6) +DEFINE_EXTI_IRQ_HANDLER(7) +DEFINE_EXTI_IRQ_HANDLER(8) +DEFINE_EXTI_IRQ_HANDLER(9) +DEFINE_EXTI_IRQ_HANDLER(10) +DEFINE_EXTI_IRQ_HANDLER(11) +DEFINE_EXTI_IRQ_HANDLER(12) +DEFINE_EXTI_IRQ_HANDLER(13) +DEFINE_EXTI_IRQ_HANDLER(14) +DEFINE_EXTI_IRQ_HANDLER(15) + +#else + +#error Unsupported processor + +#endif + // Set override_callback_obj to true if you want to unconditionally set the // callback function. uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t callback_obj, bool override_callback_obj) { diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index e05dbe91bc1..2e625a5894d 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -452,62 +452,6 @@ void OTG_HS_WKUP_IRQHandler(void) { { }*/ -/** - * @brief These functions handle the EXTI interrupt requests. - * @param None - * @retval None - */ -void EXTI0_IRQHandler(void) { - IRQ_ENTER(EXTI0_IRQn); - Handle_EXTI_Irq(0); - IRQ_EXIT(EXTI0_IRQn); -} - -void EXTI1_IRQHandler(void) { - IRQ_ENTER(EXTI1_IRQn); - Handle_EXTI_Irq(1); - IRQ_EXIT(EXTI1_IRQn); -} - -void EXTI2_IRQHandler(void) { - IRQ_ENTER(EXTI2_IRQn); - Handle_EXTI_Irq(2); - IRQ_EXIT(EXTI2_IRQn); -} - -void EXTI3_IRQHandler(void) { - IRQ_ENTER(EXTI3_IRQn); - Handle_EXTI_Irq(3); - IRQ_EXIT(EXTI3_IRQn); -} - -void EXTI4_IRQHandler(void) { - IRQ_ENTER(EXTI4_IRQn); - Handle_EXTI_Irq(4); - IRQ_EXIT(EXTI4_IRQn); -} - -void EXTI9_5_IRQHandler(void) { - IRQ_ENTER(EXTI9_5_IRQn); - Handle_EXTI_Irq(5); - Handle_EXTI_Irq(6); - Handle_EXTI_Irq(7); - Handle_EXTI_Irq(8); - Handle_EXTI_Irq(9); - IRQ_EXIT(EXTI9_5_IRQn); -} - -void EXTI15_10_IRQHandler(void) { - IRQ_ENTER(EXTI15_10_IRQn); - Handle_EXTI_Irq(10); - Handle_EXTI_Irq(11); - Handle_EXTI_Irq(12); - Handle_EXTI_Irq(13); - Handle_EXTI_Irq(14); - Handle_EXTI_Irq(15); - IRQ_EXIT(EXTI15_10_IRQn); -} - void PVD_IRQHandler(void) { IRQ_ENTER(PVD_IRQn); Handle_EXTI_Irq(EXTI_PVD_OUTPUT); @@ -605,28 +549,6 @@ void RTC_IRQHandler(void) { } #endif -void EXTI0_1_IRQHandler(void) { - IRQ_ENTER(EXTI0_1_IRQn); - Handle_EXTI_Irq(0); - Handle_EXTI_Irq(1); - IRQ_EXIT(EXTI0_1_IRQn); -} - -void EXTI2_3_IRQHandler(void) { - IRQ_ENTER(EXTI2_3_IRQn); - Handle_EXTI_Irq(2); - Handle_EXTI_Irq(3); - IRQ_EXIT(EXTI2_3_IRQn); -} - -void EXTI4_15_IRQHandler(void) { - IRQ_ENTER(EXTI4_15_IRQn); - for (int i = 4; i <= 15; ++i) { - Handle_EXTI_Irq(i); - } - IRQ_EXIT(EXTI4_15_IRQn); -} - void TIM1_BRK_UP_TRG_COM_IRQHandler(void) { IRQ_ENTER(TIM1_BRK_UP_TRG_COM_IRQn); timer_irq_handler(1); From 8970ede7cccee57032b547b8a97539b64e61102a Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 24 Nov 2024 15:06:30 +1100 Subject: [PATCH 0125/2098] extmod/moductypes: Fix large return values of addressof and INT_MAYBE. So they don't return a negative number for an address (prior to this fix they would return negative addresses for values that were larger than the maximum small-int value). Signed-off-by: Damien George --- extmod/moductypes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 00a69a275a0..bf45797658f 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -602,7 +602,7 @@ static mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in) { uint agg_type = GET_TYPE(offset, AGG_TYPE_BITS); if (agg_type == PTR) { byte *p = *(void **)self->addr; - return mp_obj_new_int((mp_int_t)(uintptr_t)p); + return mp_obj_new_int_from_uint((uintptr_t)p); } } MP_FALLTHROUGH @@ -629,7 +629,7 @@ static mp_int_t uctypes_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, static mp_obj_t uctypes_struct_addressof(mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); - return mp_obj_new_int((mp_int_t)(uintptr_t)bufinfo.buf); + return mp_obj_new_int_from_uint((uintptr_t)bufinfo.buf); } MP_DEFINE_CONST_FUN_OBJ_1(uctypes_struct_addressof_obj, uctypes_struct_addressof); From d10cda66dcff1b760ebc550c7f9352220fe4163f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 18 Dec 2024 10:15:01 +1100 Subject: [PATCH 0126/2098] tests/extmod: Add test for uctypes.addressof function. Signed-off-by: Damien George --- tests/extmod/uctypes_addressof.py | 16 ++++++++++++++++ tests/extmod/uctypes_addressof.py.exp | 9 +++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/extmod/uctypes_addressof.py create mode 100644 tests/extmod/uctypes_addressof.py.exp diff --git a/tests/extmod/uctypes_addressof.py b/tests/extmod/uctypes_addressof.py new file mode 100644 index 00000000000..c83089d0f72 --- /dev/null +++ b/tests/extmod/uctypes_addressof.py @@ -0,0 +1,16 @@ +# Test uctypes.addressof(). + +try: + from sys import maxsize + import uctypes +except ImportError: + print("SKIP") + raise SystemExit + +# Test small addresses. +for i in range(8): + print(uctypes.addressof(uctypes.bytearray_at(1 << i, 8))) + +# Test address that is bigger than the greatest small-int but still within the address range. +large_addr = maxsize + 1 +print(uctypes.addressof(uctypes.bytearray_at(large_addr, 8)) == large_addr) diff --git a/tests/extmod/uctypes_addressof.py.exp b/tests/extmod/uctypes_addressof.py.exp new file mode 100644 index 00000000000..5b48569d018 --- /dev/null +++ b/tests/extmod/uctypes_addressof.py.exp @@ -0,0 +1,9 @@ +1 +2 +4 +8 +16 +32 +64 +128 +True From 92a5ea51b456e96332f703aded4be2fbc22982fc Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Dec 2024 15:34:12 +1100 Subject: [PATCH 0127/2098] ci: Cache Zephyr workspace installation. Can save several minutes downloading the Zephyr docker image and/or cloning repo from GitHub. Cache keyed on the Zephyr version, which AFAIK is the only determinant for the workspace contents. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_zephyr.yml | 13 +++++++++++++ tools/ci.sh | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 444b0973daf..ff3e19c7ea1 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -30,9 +30,22 @@ jobs: docker-images: false swap-storage: false - uses: actions/checkout@v4 + - id: versions + name: Read Zephyr version + run: source tools/ci.sh && echo "ZEPHYR=$ZEPHYR_VERSION" | tee "$GITHUB_OUTPUT" + - name: Cached Zephyr Workspace + id: cache_workspace + uses: actions/cache@v4 + with: + # note that the Zephyr CI docker image is 15GB. At time of writing + # GitHub caches are limited to 10GB total for a project. So we only + # cache the "workspace" + path: ./zephyrproject + key: zephyr-workspace-${{ steps.versions.outputs.ZEPHYR }} - name: Install packages run: source tools/ci.sh && ci_zephyr_setup - name: Install Zephyr + if: steps.cache_workspace.outputs.cache-hit != 'true' run: source tools/ci.sh && ci_zephyr_install - name: Build run: source tools/ci.sh && ci_zephyr_build diff --git a/tools/ci.sh b/tools/ci.sh index 33559d33916..bc013f53241 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -747,14 +747,24 @@ ZEPHYR_SDK_VERSION=0.16.8 ZEPHYR_VERSION=v3.7.0 function ci_zephyr_setup { - docker pull zephyrprojectrtos/ci:${ZEPHYR_DOCKER_VERSION} + IMAGE=zephyrprojectrtos/ci:${ZEPHYR_DOCKER_VERSION} + + docker pull ${IMAGE} + + # Directories cached by GitHub Actions, mounted + # into the container + ZEPHYRPROJECT_DIR="$(pwd)/zephyrproject" + + mkdir -p "${ZEPHYRPROJECT_DIR}" + docker run --name zephyr-ci -d -it \ -v "$(pwd)":/micropython \ + -v "${ZEPHYRPROJECT_DIR}":/zephyrproject \ -e ZEPHYR_SDK_INSTALL_DIR=/opt/toolchains/zephyr-sdk-${ZEPHYR_SDK_VERSION} \ -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr \ -e ZEPHYR_BASE=/zephyrproject/zephyr \ -w /micropython/ports/zephyr \ - zephyrprojectrtos/ci:${ZEPHYR_DOCKER_VERSION} + ${IMAGE} docker ps -a # qemu-system-arm is needed to run the test suite. From db4b095644e646f0a700076e4d2c51512eed4d30 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Dec 2024 16:54:35 +1100 Subject: [PATCH 0128/2098] ci: Pull the Zephyr CI docker image from GitHub container reg. This image is 15GB so in theory this may be faster, although in testing the improvement is either non-existent or marginal. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index bc013f53241..d4bfe493369 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -747,7 +747,7 @@ ZEPHYR_SDK_VERSION=0.16.8 ZEPHYR_VERSION=v3.7.0 function ci_zephyr_setup { - IMAGE=zephyrprojectrtos/ci:${ZEPHYR_DOCKER_VERSION} + IMAGE=ghcr.io/zephyrproject-rtos/ci:${ZEPHYR_DOCKER_VERSION} docker pull ${IMAGE} From a9945fc528c950659793b6fb4979586297ea1497 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 11 Dec 2024 14:59:39 +1100 Subject: [PATCH 0129/2098] ci: Add caching of ccache for Zephyr. Similar to the ESP32 builds, but needs additional step to pass the ccache directory through to the Zephyr container. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_zephyr.yml | 4 ++++ tools/ci.sh | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index ff3e19c7ea1..eb85af6a361 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -42,6 +42,10 @@ jobs: # cache the "workspace" path: ./zephyrproject key: zephyr-workspace-${{ steps.versions.outputs.ZEPHYR }} + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: zephyr - name: Install packages run: source tools/ci.sh && ci_zephyr_setup - name: Install Zephyr diff --git a/tools/ci.sh b/tools/ci.sh index d4bfe493369..c67aeed0cec 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -754,12 +754,15 @@ function ci_zephyr_setup { # Directories cached by GitHub Actions, mounted # into the container ZEPHYRPROJECT_DIR="$(pwd)/zephyrproject" + CCACHE_DIR="$(pwd)/.ccache" mkdir -p "${ZEPHYRPROJECT_DIR}" + mkdir -p "${CCACHE_DIR}" docker run --name zephyr-ci -d -it \ -v "$(pwd)":/micropython \ -v "${ZEPHYRPROJECT_DIR}":/zephyrproject \ + -v "${CCACHE_DIR}":/root/.cache/ccache \ -e ZEPHYR_SDK_INSTALL_DIR=/opt/toolchains/zephyr-sdk-${ZEPHYR_SDK_VERSION} \ -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr \ -e ZEPHYR_BASE=/zephyrproject/zephyr \ From 22804fccf312030104adf48f836dd8667411cad1 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 13 Aug 2024 10:54:36 +1000 Subject: [PATCH 0130/2098] stm32/boards/WEACT_F411_BLACKPILL: Add WeAct F411 'blackpill' boards. Adds board profile for the WeAct F411 'blackpill' which is a quite popular low cost ST dev board. This board also has optional spiflash so can be purchased in a few different configurations. Builds for v3.1 with no SPI Flash by default. Includes variants for different board versions and spi flash sizes. Signed-off-by: Andrew Leech --- .../boards/WEACT_F411_BLACKPILL/README.md | 55 +++++++++ .../stm32/boards/WEACT_F411_BLACKPILL/bdev.c | 45 +++++++ .../boards/WEACT_F411_BLACKPILL/board.json | 22 ++++ .../WEACT_F411_BLACKPILL/mpconfigboard.h | 112 ++++++++++++++++++ .../WEACT_F411_BLACKPILL/mpconfigboard.mk | 38 ++++++ .../mpconfigvariant_V13.mk | 1 + .../mpconfigvariant_V13_FLASH_4M.mk | 2 + .../mpconfigvariant_V20_FLASH_4M.mk | 2 + .../mpconfigvariant_V31_FLASH_8M.mk | 2 + .../mpconfigvariant_V31_XTAL_8M.mk | 2 + .../boards/WEACT_F411_BLACKPILL/pins.csv | 45 +++++++ .../WEACT_F411_BLACKPILL/stm32f4xx_hal_conf.h | 19 +++ 12 files changed, 345 insertions(+) create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/README.md create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/bdev.c create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/board.json create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13_FLASH_4M.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V20_FLASH_4M.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_FLASH_8M.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_XTAL_8M.mk create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/pins.csv create mode 100644 ports/stm32/boards/WEACT_F411_BLACKPILL/stm32f4xx_hal_conf.h diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/README.md b/ports/stm32/boards/WEACT_F411_BLACKPILL/README.md new file mode 100644 index 00000000000..9af29e6b1e4 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/README.md @@ -0,0 +1,55 @@ +WeAct F411 'blackpill' +====================== + +The WeAct F411 blackpill board comes in a number of versions and variants. +All have a footprint for a SPI flash chip on the back, though the board is +often sold without any flash chip loaded. + +At the time of writing (Sep 2024) v3.1 is the current version. +This version is sold with both 25Mhz HSE crystal (same as previous versions) and also +with a 8Mhz crystal. The end user should be careful to confirm which variant is +purchased and/or read the markings on the crystal to know which variant build to load. + +The previous v2.0 boards had changed the MCU pinout for the spi flash chip so requires +soft-spi support enabled in the variant settings, unlike v3.1 or v1.3 which is +compatible with the hardware spi peripheral. + +The original v1.3 boards did not include a user switch on the top, it only has +"BOOT0" and "NRST" switches to load bootloader and reset the board respectively. + +For more information see: https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1 + +Note: The pins used by features like spiflash and USB are also broken out to the +gpio headers on the sides of the board. +If these peripherals / features are enabled then these external pins must be avoided to ensure +there are no conflicts. [pins.csv](pins.csv) should be consulted to check all pins assigned +to alternate functions on the board. + +Customising the build +--------------------- + +After purchasing a board without any spiflash chip loaded the user can solder on +their own of any desired size. Most brands of spiflash in SO8 pinout are compatible +however some do have a slightly different protocol so may not work out of the box +with micropython. Brand compatibility is outide the scope of this doc. + +Once a custom spiflash chip has been loaded onto the board micropython should +be built with the flash size specified. After doing so the spiflash chip will +be used for the FAT/LFS main filesystem. + +Examples: + +For a v3.1 / 25Mhz (default version) board with 16MiB flash chip loaded: +``` bash +make -C ports/stm32 BOARD=WEACT_F411_BLACKPILL SPI_FLASH_SIZE_MB=16 +``` + +For a v3.1 / 8Mhz board with 4MiB flash chip loaded: +``` bash +make -C ports/stm32 BOARD=WEACT_F411_BLACKPILL BOARD_VARIANT=V31_XTAL_8M SPI_FLASH_SIZE_MB=4 +``` + +For a v1.3 board with 2MiB flash chip loaded and XTAL manually replaced with 8Mhz: +``` bash +make -C ports/stm32 BOARD=WEACT_F411_BLACKPILL BOARD_VARIANT=V13 SPI_FLASH_SIZE_MB=2 XTAL_FREQ_MHZ=8 +``` diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/bdev.c b/ports/stm32/boards/WEACT_F411_BLACKPILL/bdev.c new file mode 100644 index 00000000000..fe349e0c8a4 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/bdev.c @@ -0,0 +1,45 @@ +#include "storage.h" +#include "spi.h" +#include "py/mpconfig.h" + +#if !MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE + +#if WEACT_F411_V20 +// External SPI flash uses SPI interface, but not on all HW spi pins. +static const mp_soft_spi_obj_t spi_bus = { + .delay_half = MICROPY_HW_SOFTSPI_MIN_DELAY, + .polarity = 0, + .phase = 0, + .sck = MICROPY_HW_SPIFLASH_SCK, + .mosi = MICROPY_HW_SPIFLASH_MOSI, + .miso = MICROPY_HW_SPIFLASH_MISO, +}; + +#else // WEACT_F411_V1.3 or WEACT_F411_V3.1 +static const spi_proto_cfg_t spi_bus = { + .spi = &spi_obj[0], // SPI1 + .baudrate = 25000000, + .polarity = 0, + .phase = 0, + .bits = 8, + .firstbit = SPI_FIRSTBIT_MSB, +}; +#endif + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +static mp_spiflash_cache_t spi_bdev_cache; +#endif + +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_SPI, + .bus.u_spi.cs = MICROPY_HW_SPIFLASH_CS, + .bus.u_spi.data = (void *)&spi_bus, + .bus.u_spi.proto = &spi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + .cache = &spi_bdev_cache, + #endif +}; + +spi_bdev_t spi_bdev; + +#endif diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/board.json b/ports/stm32/boards/WEACT_F411_BLACKPILL/board.json new file mode 100644 index 00000000000..5b107872169 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../PYBV10/deploy.md" + ], + "docs": "", + "features": [], + "images": [ + "WEACTV20_F411.jpg" + ], + "mcu": "stm32f411", + "product": "WeAct F411 'blackpill'. Default variant is v3.1 with no SPI Flash.", + "thumbnail": "", + "url": "https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1", + "variants": { + "V13": "v1.3 board with no SPI Flash", + "V13_FLASH_4M": "v1.3 board with 4MB SPI Flash", + "V20_FLASH_4M": "v2.0 board with 4MB SPI Flash", + "V31_FLASH_8M": "v3.1 board with 8MB SPI Flash", + "V31_XTAL_8M": "v3.1 board with 8MHz crystal" + }, + "vendor": "WeAct Studio" +} diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h new file mode 100644 index 00000000000..561c1a9f151 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h @@ -0,0 +1,112 @@ +// based off the following two repositories: +// * https://github.com/mcauser/WEACT_F411CEU6 +// * https://github.com/YXZhu/micropython + +#define MICROPY_HW_BOARD_NAME "WEACT_F411_BLACKPILL" +#define MICROPY_HW_MCU_NAME "STM32F411CE" +#define MICROPY_HW_FLASH_FS_LABEL "WEACT_F411_BLACKPILL" + +// some users having issues with FLASH_LATENCY_2, so set to 3 +// from https://forum.micropython.org/viewtopic.php?t=7154 +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_3 + +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_USB_FS (1) + +// Switch +#if WEACT_F411_V13 +#define MICROPY_HW_HAS_SWITCH (0) +#else +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_USRSW_PIN (pyb_pin_SW) +#define MICROPY_HW_USRSW_PULL (GPIO_PULLUP) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_FALLING) +#define MICROPY_HW_USRSW_PRESSED (0) +#endif + +// LEDs +#define MICROPY_HW_LED1 (pyb_pin_LED_BLUE) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) + +// RTC +#define MICROPY_HW_RTC_USE_LSE (1) +#define MICROPY_HW_RTC_USE_US (0) +#define MICROPY_HW_RTC_USE_CALOUT (1) + +// Set PLLM same as HSE in MHZ. +#define MICROPY_HW_CLK_PLLM (MICROPY_HW_HSE_SPEED_MHZ) +// Configure PLL for final CPU freq of 96MHz +#define MICROPY_HW_CLK_PLLN (192) +#define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2) +#define MICROPY_HW_CLK_PLLQ (4) + +// UART config +#define MICROPY_HW_UART1_TX (pin_A9) +#define MICROPY_HW_UART1_RX (pin_A10) + +#define MICROPY_HW_UART2_TX (pin_A2) +#define MICROPY_HW_UART2_RX (pin_A3) + +#define MICROPY_HW_UART_REPL (PYB_UART_1) +#define MICROPY_HW_UART_REPL_BAUD (115200) + +// I2C bus +#define MICROPY_HW_I2C1_SCL (pin_B6) +#define MICROPY_HW_I2C1_SDA (pin_B7) +#define MICROPY_HW_I2C2_SCL (pin_B10) +#define MICROPY_HW_I2C2_SDA (pin_B9) +#define MICROPY_HW_I2C3_SCL (pin_A8) +#define MICROPY_HW_I2C3_SDA (pin_B8) + +// SPI bus +// SPI 1 is generally used for the SPI flash if enabled below. +#define MICROPY_HW_SPI1_NSS (pin_A4) +#define MICROPY_HW_SPI1_SCK (pin_A5) +#define MICROPY_HW_SPI1_MISO (pin_A6) +#define MICROPY_HW_SPI1_MOSI (pin_A7) + +#define MICROPY_HW_SPI2_NSS (pin_B12) +#define MICROPY_HW_SPI2_SCK (pin_B13) +#define MICROPY_HW_SPI2_MISO (pin_B14) +#define MICROPY_HW_SPI2_MOSI (pin_B15) + +// SPI 3 is not accessible if SPI flash module is used on V2.0 (PB4 conflict) +#define MICROPY_HW_SPI3_NSS (pin_A15) +#define MICROPY_HW_SPI3_SCK (pin_B3) +#define MICROPY_HW_SPI3_MISO (pin_B4) +#define MICROPY_HW_SPI3_MOSI (pin_B5) + +// External SPI Flash configuration + +#if !MICROPY_HW_SPIFLASH_SIZE_BYTES +// Use internal filesystem if spiflash not enabled. +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1) + +#else +// Disable internal filesystem to use spiflash. +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) + +// SPI flash pins +#define MICROPY_HW_SPIFLASH_CS (pyb_pin_FLASH_CS) +#define MICROPY_HW_SPIFLASH_SCK (pyb_pin_FLASH_SCK) +#define MICROPY_HW_SPIFLASH_MOSI (pyb_pin_FLASH_MOSI) +#if WEACT_F411_V13 +#define MICROPY_HW_SPIFLASH_MISO (pyb_pin_FLASH_MISO_V13) +#elif WEACT_F411_V20 +#define MICROPY_HW_SPIFLASH_MISO (pyb_pin_FLASH_MISO_V20) +#else +#define MICROPY_HW_SPIFLASH_MISO (pyb_pin_FLASH_MISO_V31) +#endif + +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) // for extended block protocol +#define MICROPY_HW_SPIFLASH_SIZE_BITS (MICROPY_HW_SPIFLASH_SIZE_BYTES * 8) +#endif diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.mk new file mode 100644 index 00000000000..3ae152294d2 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.mk @@ -0,0 +1,38 @@ +MCU_SERIES = f4 +CMSIS_MCU = STM32F411xE +AF_FILE = boards/stm32f411_af.csv +LD_FILES = boards/stm32f411.ld boards/common_ifs.ld +TEXT0_ADDR = 0x08000000 +TEXT1_ADDR = 0x08020000 + +MICROPY_VFS_LFS2 = 1 + +# Settings for version, spi flash and HSE xtal. +# These are used in variant configs and/or on make command line. +# If provided on make command line the build folder name will be updated to match +# When set in variant they're included after this file so the following ifdef blocks are ignored. + +ifdef BOARD_VERSION +BUILD := $(BUILD)_V$(BOARD_VERSION) +endif + +ifdef SPI_FLASH_SIZE_MB +BUILD := $(BUILD)_FLASH_$(SPI_FLASH_SIZE_MB)M +endif + +ifdef XTAL_FREQ_MHZ +BUILD := $(BUILD)_XTAL_$(XTAL_FREQ_MHZ)M +endif + +# Blackpill v3.1 board by default +BOARD_VERSION ?= 31 + +# No flash chip in default build - use internal flash +SPI_FLASH_SIZE_MB ?= 0 + +# 25Mhz HSE crystal by default. +XTAL_FREQ_MHZ ?= 25 + +CFLAGS += -DWEACT_F411_V$(BOARD_VERSION)=1 +CFLAGS += -DMICROPY_HW_SPIFLASH_SIZE_BYTES="($(SPI_FLASH_SIZE_MB) * 1024 * 1024)" +CFLAGS += -DMICROPY_HW_HSE_SPEED_MHZ="($(XTAL_FREQ_MHZ))" diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13.mk new file mode 100644 index 00000000000..47c94faee40 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13.mk @@ -0,0 +1 @@ +BOARD_VERSION = 13 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13_FLASH_4M.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13_FLASH_4M.mk new file mode 100644 index 00000000000..ae7aeaaf52d --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V13_FLASH_4M.mk @@ -0,0 +1,2 @@ +BOARD_VERSION = 13 +SPI_FLASH_SIZE_MB = 4 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V20_FLASH_4M.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V20_FLASH_4M.mk new file mode 100644 index 00000000000..21ce84ceaff --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V20_FLASH_4M.mk @@ -0,0 +1,2 @@ +BOARD_VERSION = 20 +SPI_FLASH_SIZE_MB = 4 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_FLASH_8M.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_FLASH_8M.mk new file mode 100644 index 00000000000..87867b1fedf --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_FLASH_8M.mk @@ -0,0 +1,2 @@ +BOARD_VERSION = 31 +SPI_FLASH_SIZE_MB = 8 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_XTAL_8M.mk b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_XTAL_8M.mk new file mode 100644 index 00000000000..5eb05e49fe9 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigvariant_V31_XTAL_8M.mk @@ -0,0 +1,2 @@ +BOARD_VERSION = 31 +XTAL_FREQ_MHZ = 8 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/pins.csv b/ports/stm32/boards/WEACT_F411_BLACKPILL/pins.csv new file mode 100644 index 00000000000..eabf753de85 --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/pins.csv @@ -0,0 +1,45 @@ +,PA0 +,PA1 +,PA2 +,PA3 +,PA4 +,PA5 +,PA6 +,PA7 +,PA8 +,PA9 +,PA10 +,PA11 +,PA12 +,PA15 +,PB0 +,PB1 +,PB2 +,PB3 +,PB4 +,PB5 +,PB6 +,PB7 +,PB8 +,PB9 +,PB10 +,PB12 +,PB13 +,PB14 +,PB15 +,PC14 +,PC15 +LED_BLUE,PC13 +SW,PA0 +-SWDIO,PA13 +-SWCLK,PA14 +-OSC32_IN,PH0 +-OSC32_OUT,PH1 +-USB_DM,PA11 +-USB_DP,PA12 +-FLASH_CS,PA4 +-FLASH_SCK,PA5 +-FLASH_MOSI,PA7 +-FLASH_MISO_V13,PA6 +-FLASH_MISO_V20,PB4 +-FLASH_MISO_V31,PA6 diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/stm32f4xx_hal_conf.h b/ports/stm32/boards/WEACT_F411_BLACKPILL/stm32f4xx_hal_conf.h new file mode 100644 index 00000000000..c11d72a213f --- /dev/null +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/stm32f4xx_hal_conf.h @@ -0,0 +1,19 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2024 Andrew Leech + */ +#ifndef MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H + +#include "boards/stm32f4xx_hal_conf_base.h" + +// Oscillator values in Hz +#define HSE_VALUE (MICROPY_HW_HSE_SPEED_MHZ * 1000000) +#define LSE_VALUE (32768) +#define EXTERNAL_CLOCK_VALUE (12288000) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (100) +#define LSE_STARTUP_TIMEOUT (5000) + +#endif // MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H From 7924b31050271c76ff6cf26c6fda5fe3ba615d08 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 3 Sep 2024 10:41:25 +1000 Subject: [PATCH 0131/2098] stm32: Generate PLL tables from pre-processed headers. Allows boards to configure their HSE and PLL values in variants. Signed-off-by: Andrew Leech --- ports/stm32/Makefile | 12 +++---- ports/stm32/boards/plli2svalues.py | 55 +++++++++++++++--------------- ports/stm32/boards/pllvalues.py | 30 ++++++++-------- ports/stm32/machine_i2s.c | 2 ++ ports/stm32/powerctrl.c | 17 ++++++++- 5 files changed, 67 insertions(+), 49 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 806added49e..a9612f1a579 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -640,15 +640,15 @@ $(BUILD)/%_$(BOARD).c $(HEADER_BUILD)/%.h $(HEADER_BUILD)/%_af_const.h $(HEADER_ --output-source $(GEN_PINS_SRC) --output-header $(GEN_PINS_HDR) \ --output-af-const $(GEN_PINS_AF_CONST) --output-af-defs $(GEN_PINS_AF_DEFS) -powerctrl.c: $(GEN_PLLFREQTABLE_HDR) -$(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD) +$(BUILD)/powerctrl.o: $(GEN_PLLFREQTABLE_HDR) +$(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD)/qstr.i.last $(ECHO) "GEN $@" - $(Q)$(PYTHON) $(PLLVALUES) -c -m $(CMSIS_MCU_LOWER) file:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h > $@ + $(Q)$(PYTHON) $(PLLVALUES) -c -m $(CMSIS_MCU_LOWER) file:$(HEADER_BUILD)/qstr.i.last > $@ -$(TOP)/extmod/machine_i2s.c: $(GEN_PLLI2STABLE_HDR) -$(GEN_PLLI2STABLE_HDR): $(PLLI2SVALUES) | $(HEADER_BUILD) +$(BUILD)/extmod/machine_i2s.o: $(GEN_PLLI2STABLE_HDR) +$(GEN_PLLI2STABLE_HDR): $(PLLI2SVALUES) | $(HEADER_BUILD)/qstr.i.last $(ECHO) "GEN $@" - $(Q)$(PYTHON) $(PLLI2SVALUES) -c -m $(CMSIS_MCU_LOWER) hse:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h pllm:$(BOARD_DIR)/mpconfigboard.h > $@ + $(Q)$(PYTHON) $(PLLI2SVALUES) -c -m $(CMSIS_MCU_LOWER) file:$(HEADER_BUILD)/qstr.i.last > $@ $(BUILD)/modstm.o: $(GEN_STMCONST_HDR) $(HEADER_BUILD)/modstm_const.h: $(CMSIS_MCU_HDR) make-stmconst.py | $(HEADER_BUILD) diff --git a/ports/stm32/boards/plli2svalues.py b/ports/stm32/boards/plli2svalues.py index 8ff7f0ff8fc..436dcee7444 100644 --- a/ports/stm32/boards/plli2svalues.py +++ b/ports/stm32/boards/plli2svalues.py @@ -123,22 +123,21 @@ def generate_c_table(plli2s_table, hse, pllm): print("}") -def search_header(filename, re_include, re_define, lookup, val): - regex_include = re.compile(re_include) +def search_header(filename, re_define, lookup): regex_define = re.compile(re_define) + val = None with open(filename) as f: for line in f: line = line.strip() - m = regex_include.match(line) - if m: - # Search included file - search_header(m.group(1), re_include, re_define, lookup, val) - continue m = regex_define.match(line) if m: # found lookup value + found = m.group(3) + if "*" in found or "/" in found: + # process define using multiply or divide to calculate value + found = eval(found) if m.group(1) == lookup: - val[0] = int(m.group(3)) + val = int(found) return val @@ -166,35 +165,37 @@ def main(): break if mcu_series in mcu_support_plli2s: - if len(argv) != 2: + if len(argv) not in (1, 2): print("usage: pllvalues.py [-c] [-m ] ") sys.exit(1) if argv[0].startswith("hse:"): - # extract HSE_VALUE from header file - (hse,) = search_header( - argv[0][len("hse:") :], - r'#include "(boards/[A-Za-z0-9_./]+)"', - r"#define +(HSE_VALUE) +\((\(uint32_t\))?([0-9]+)\)", - "HSE_VALUE", - [None], + hse = int(argv[0][len("hse:") :]) + + if argv[0].startswith("pllm:"): + pllm = int(argv[0][len("pllm:") :]) + + if argv[0].startswith("file:"): + # extract hse value from processed header files + hse = search_header( + argv[0][len("file:") :], + r"static.* (micropy_hw_hse_value) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + "micropy_hw_hse_value", ) if hse is None: - raise ValueError("%s does not contain a definition of HSE_VALUE" % argv[0]) - argv.pop(0) + raise ValueError( + "%s does not contain a definition of micropy_hw_hse_value" % argv[0] + ) - if argv[0].startswith("pllm:"): - # extract MICROPY_HW_CLK_PLLM from header file - (pllm,) = search_header( - argv[0][len("pllm:") :], - r'#include "(boards/[A-Za-z0-9_./]+)"', - r"#define +(MICROPY_HW_CLK_PLLM) +\((\(uint32_t\))?([0-9]+)\)", - "MICROPY_HW_CLK_PLLM", - [None], + # extract pllm value from processed header files + pllm = search_header( + argv[0][len("file:") :], + r"static.* (micropy_hw_clk_pllm) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + "micropy_hw_clk_pllm", ) if pllm is None: raise ValueError( - "%s does not contain a definition of MICROPY_HW_CLK_PLLM" % argv[0] + "%s does not contain a definition of micropy_hw_clk_pllm" % argv[0] ) argv.pop(0) diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py index 2db6b5f2570..5edc1d51fd6 100644 --- a/ports/stm32/boards/pllvalues.py +++ b/ports/stm32/boards/pllvalues.py @@ -228,26 +228,26 @@ def print_table(hse, valid_plls): print("found %u valid configurations" % len(valid_plls)) -def search_header_for_hsx_values(filename, vals): - regex_inc = re.compile(r'#include "(boards/[A-Za-z0-9_./]+)"') - regex_def = re.compile(r"#define +(HSE_VALUE|HSI_VALUE) +\((\(uint32_t\))?([0-9]+)\)") +def search_header_for_hsx_values(filename): + hse = hsi = None + regex_def = re.compile( + r"static.* +(micropy_hw_hs[ei]_value) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + ) with open(filename) as f: for line in f: line = line.strip() - m = regex_inc.match(line) - if m: - # Search included file - search_header_for_hsx_values(m.group(1), vals) - continue m = regex_def.match(line) if m: # Found HSE_VALUE or HSI_VALUE - val = int(m.group(3)) // 1000000 - if m.group(1) == "HSE_VALUE": - vals[0] = val + found = m.group(3) + if "*" in found or "/" in found: + found = eval(found) + val = int(found) // 1000000 + if m.group(1) == "micropy_hw_hse_value": + hse = val else: - vals[1] = val - return vals + hsi = val + return hse, hsi def main(): @@ -280,9 +280,9 @@ def main(): if argv[0].startswith("file:"): # extract HSE_VALUE, and optionally HSI_VALUE, from header file - hse, hsi = search_header_for_hsx_values(argv[0][5:], [None, None]) + hse, hsi = search_header_for_hsx_values(argv[0][5:]) if hse is None: - raise ValueError("%s does not contain a definition of HSE_VALUE" % argv[0]) + raise ValueError("%s does not contain a definition of micropy_hw_hse_value" % argv[0]) else: # HSE given directly as an integer hse = int(argv[0]) diff --git a/ports/stm32/machine_i2s.c b/ports/stm32/machine_i2s.c index a2a0a829100..d7d4dc14b98 100644 --- a/ports/stm32/machine_i2s.c +++ b/ports/stm32/machine_i2s.c @@ -33,7 +33,9 @@ #include "py/mphal.h" #include "pin.h" #include "dma.h" +#ifndef NO_QSTR #include "genhdr/plli2stable.h" +#endif // Notes on this port's specific implementation of I2S: // - the DMA callbacks (1/2 complete and complete) are used to implement the asynchronous background operations diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 9d3f374f937..eea009e2d78 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -28,8 +28,23 @@ #include "py/mphal.h" #include "powerctrl.h" #include "rtc.h" -#include "genhdr/pllfreqtable.h" #include "extmod/modbluetooth.h" +#include "py/mpconfig.h" +#ifndef NO_QSTR +#include "genhdr/pllfreqtable.h" +#endif + +// These will be defined / expanded in pre-processor output for use in the +// boards/pllvalues.py script, then generally stripped from final firmware. +#ifdef HSI_VALUE +static uint32_t __attribute__((unused)) micropy_hw_hsi_value = HSI_VALUE; +#endif +#ifdef HSE_VALUE +static uint32_t __attribute__((unused)) micropy_hw_hse_value = HSE_VALUE; +#endif +#ifdef MICROPY_HW_CLK_PLLM +static uint32_t __attribute__((unused)) micropy_hw_clk_pllm = MICROPY_HW_CLK_PLLM; +#endif #if defined(STM32H5) || defined(STM32H7) #define RCC_SR RSR From 3c1722e705002b2182f138a114f4408ae7ebac38 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 19 Dec 2024 00:55:31 +1100 Subject: [PATCH 0132/2098] stm32: Fix extraction of hse/hsi/pllm values from preprocessed source. The expressions for the `micropy_hw_hse_value` etc variables may contain parenthesis, eg `micropy_hw_hse_value = ((25) * 1000000)`. To handle such a case, simplify the regex and always use `eval(found)` to evaluate the expression. Signed-off-by: Damien George --- ports/stm32/boards/plli2svalues.py | 11 ++++------- ports/stm32/boards/pllvalues.py | 8 +++----- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/ports/stm32/boards/plli2svalues.py b/ports/stm32/boards/plli2svalues.py index 436dcee7444..e5ea4e8dfd4 100644 --- a/ports/stm32/boards/plli2svalues.py +++ b/ports/stm32/boards/plli2svalues.py @@ -132,12 +132,9 @@ def search_header(filename, re_define, lookup): m = regex_define.match(line) if m: # found lookup value - found = m.group(3) - if "*" in found or "/" in found: - # process define using multiply or divide to calculate value - found = eval(found) + found = m.group(2) if m.group(1) == lookup: - val = int(found) + val = eval(found) return val @@ -179,7 +176,7 @@ def main(): # extract hse value from processed header files hse = search_header( argv[0][len("file:") :], - r"static.* (micropy_hw_hse_value) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + r"static.* (micropy_hw_hse_value) = +([0-9 +-/\*()]+);", "micropy_hw_hse_value", ) if hse is None: @@ -190,7 +187,7 @@ def main(): # extract pllm value from processed header files pllm = search_header( argv[0][len("file:") :], - r"static.* (micropy_hw_clk_pllm) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + r"static.* (micropy_hw_clk_pllm) = +([0-9 +-/\*()]+);", "micropy_hw_clk_pllm", ) if pllm is None: diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py index 5edc1d51fd6..d8856bfecdb 100644 --- a/ports/stm32/boards/pllvalues.py +++ b/ports/stm32/boards/pllvalues.py @@ -231,7 +231,7 @@ def print_table(hse, valid_plls): def search_header_for_hsx_values(filename): hse = hsi = None regex_def = re.compile( - r"static.* +(micropy_hw_hs[ei]_value) = +\(*(\(uint32_t\))?([0-9 +-/\*]+)\)*;", + r"static.* +(micropy_hw_hs[ei]_value) = +([0-9 +-/\*()]+);", ) with open(filename) as f: for line in f: @@ -239,10 +239,8 @@ def search_header_for_hsx_values(filename): m = regex_def.match(line) if m: # Found HSE_VALUE or HSI_VALUE - found = m.group(3) - if "*" in found or "/" in found: - found = eval(found) - val = int(found) // 1000000 + found = m.group(2) + val = eval(found) // 1000000 if m.group(1) == "micropy_hw_hse_value": hse = val else: From e323da729101c9fe626b5b08cafef2dbdbb70441 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 17 Dec 2024 15:15:59 +1100 Subject: [PATCH 0133/2098] tests/run-tests.py: Set name of injected test module to '__main__'. Running unittest-based tests with --via-mpy is currently broken, because the unittest test needs the module to be named `__main__`, whereas it's actually called `__injected_test`. Fix this by changing the name when the file is opened. Signed-off-by: Damien George --- tests/run-tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 6fe45707bb6..c72178cd760 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -59,10 +59,13 @@ def base_path(*p): os.environ["PYTHONIOENCODING"] = "utf-8" # Code to allow a target MicroPython to import an .mpy from RAM +# Note: the module is named `__injected_test` but it needs to have `__name__` set to +# `__main__` so that the test sees itself as the main module, eg so unittest works. injected_import_hook_code = """\ import sys, os, io, vfs class __File(io.IOBase): def __init__(self): + sys.modules['__injected_test'].__name__ = '__main__' self.off = 0 def ioctl(self, request, arg): return 0 From b5de529ffc17dd02e24c3f2b14e230eefe42dc7a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 28 Jan 2023 10:12:49 +0100 Subject: [PATCH 0134/2098] docs: Fix the quickref documentation of rtc.datetime(). Such that it matches the implementation and the documentation of the `machine.RTC` class. Signed-off-by: robert-hh --- docs/esp32/quickref.rst | 4 +++- docs/esp8266/quickref.rst | 4 +++- docs/mimxrt/quickref.rst | 4 +++- docs/pyboard/quickref.rst | 4 +++- docs/renesas-ra/quickref.rst | 5 +++-- docs/rp2/quickref.rst | 3 ++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 5cce96d6878..c5c2dfb38e5 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -577,7 +577,9 @@ See :ref:`machine.RTC ` :: from machine import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and + # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time WDT (Watchdog timer) diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 635f1f834bb..e17b60f6761 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -284,7 +284,9 @@ See :ref:`machine.RTC ` :: from machine import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and + # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time # synchronize with ntp diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index 34e0aa79f13..49d7befc745 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -429,7 +429,9 @@ See :ref:`machine.RTC `:: from machine import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and + # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time rtc.now() # return date and time in CPython format. diff --git a/docs/pyboard/quickref.rst b/docs/pyboard/quickref.rst index 62157bff0a4..52ddc29b193 100644 --- a/docs/pyboard/quickref.rst +++ b/docs/pyboard/quickref.rst @@ -138,7 +138,9 @@ See :ref:`pyb.RTC ` :: from pyb import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and + # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time PWM (pulse width modulation) diff --git a/docs/renesas-ra/quickref.rst b/docs/renesas-ra/quickref.rst index ea9a38db15f..b5283707fc8 100644 --- a/docs/renesas-ra/quickref.rst +++ b/docs/renesas-ra/quickref.rst @@ -206,8 +206,9 @@ See :ref:`machine.RTC ` :: from machine import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time - # time, eg 2017/8/23 1:12:48 + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and + # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time Following functions are not supported at the present:: diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst index 9b82ea5dc67..6be31805007 100644 --- a/docs/rp2/quickref.rst +++ b/docs/rp2/quickref.rst @@ -310,8 +310,9 @@ See :ref:`machine.RTC ` :: from machine import RTC rtc = RTC() - rtc.datetime((2017, 8, 23, 2, 12, 48, 0, 0)) # set a specific date and + rtc.datetime((2017, 8, 23, 0, 1, 12, 48, 0)) # set a specific date and # time, eg. 2017/8/23 1:12:48 + # the day-of-week value is ignored rtc.datetime() # get date and time WDT (Watchdog timer) From 0302cd65e85cb255670cf6eb32d9e6cee241128c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 28 Jan 2023 14:57:22 +0100 Subject: [PATCH 0135/2098] mimxrt/machine_rtc: Drop machine.RTC.now() method. This is not part of the common machine API. It's dropped on the mimxrt port and kept only on the cc3200 port for legacy. Also show the port availability of `RTC.now()` in the documentation. Signed-off-by: robert-hh --- docs/library/machine.RTC.rst | 2 ++ ports/mimxrt/machine_rtc.c | 20 -------------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index a457189037b..ed93c5fff85 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -48,6 +48,8 @@ Methods Get get the current datetime tuple. + Availability: WiPy. + .. method:: RTC.deinit() Resets the RTC to the time of January 1, 2015 and starts running it again. diff --git a/ports/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index 0d6fc54c8b6..91769ea3341 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -230,25 +230,6 @@ static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); -static mp_obj_t machine_rtc_now(mp_obj_t self_in) { - // Get date and time in CPython order. - snvs_lp_srtc_datetime_t srtc_date; - SNVS_LP_SRTC_GetDatetime(SNVS, &srtc_date); - - mp_obj_t tuple[8] = { - mp_obj_new_int(srtc_date.year), - mp_obj_new_int(srtc_date.month), - mp_obj_new_int(srtc_date.day), - mp_obj_new_int(srtc_date.hour), - mp_obj_new_int(srtc_date.minute), - mp_obj_new_int(srtc_date.second), - mp_obj_new_int(0), - mp_const_none, - }; - return mp_obj_new_tuple(8, tuple); -} -static MP_DEFINE_CONST_FUN_OBJ_1(machine_rtc_now_obj, machine_rtc_now); - static mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { mp_obj_t args[2] = {self_in, date}; machine_rtc_datetime_helper(2, args); @@ -389,7 +370,6 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_irq_obj, 1, machine_rtc_irq); static const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_rtc_init_obj) }, { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, - { MP_ROM_QSTR(MP_QSTR_now), MP_ROM_PTR(&machine_rtc_now_obj) }, { MP_ROM_QSTR(MP_QSTR_calibration), MP_ROM_PTR(&machine_rtc_calibration_obj) }, { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&machine_rtc_alarm_obj) }, { MP_ROM_QSTR(MP_QSTR_alarm_left), MP_ROM_PTR(&machine_rtc_alarm_left_obj) }, From f4e45995232ad52aeb4da270e6ca7571695e339b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 28 Jan 2023 15:19:37 +0100 Subject: [PATCH 0136/2098] ports: Fix machine.RTC.init() method so argument order matches the docs. This commit makes the argument ordering of `machine.RTC.init()` the same for all the ports that implement arguments to this method: cc3200, esp32, mimxrt and samd. The cc3200 argument ordering is used, which matches the documentation. Also document the availability and the differing semantics for the stm32 and renesas-ra port. Signed-off-by: robert-hh --- docs/library/machine.RTC.rst | 9 ++++++++- ports/esp32/machine_rtc.c | 15 +++++++++++---- ports/mimxrt/machine_rtc.c | 12 ++++++------ ports/samd/machine_rtc.c | 12 ++++++------ 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index ed93c5fff85..e2ddd728bde 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -42,7 +42,14 @@ Methods Initialise the RTC. Datetime is a tuple of the form: - ``(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])`` + ``(year, month, day, hour, minute, second, microsecond, tzinfo)`` + + All eight arguments must be present. The ``microsecond`` and ``tzinfo`` + values are currently ignored but might be used in the future. + + Availability: CC3200, ESP32, MIMXRT, SAMD. The rtc.init() method on + the stm32 and renesas-ra ports just (re-)starts the RTC and does not + accept arguments. .. method:: RTC.now() diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index 087ba9d69ef..a2f4bb132d4 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -101,7 +101,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s return (mp_obj_t)&machine_rtc_obj; } -static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args) { +static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args, int hour_index) { if (n_args == 1) { // Get time @@ -131,7 +131,14 @@ static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar mp_obj_get_array_fixed_n(args[1], 8, &items); struct timeval tv = {0}; - tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); + tv.tv_sec = timeutils_seconds_since_epoch( + mp_obj_get_int(items[0]), + mp_obj_get_int(items[1]), + mp_obj_get_int(items[2]), + mp_obj_get_int(items[hour_index]), + mp_obj_get_int(items[hour_index + 1]), + mp_obj_get_int(items[hour_index + 2]) + ); tv.tv_usec = mp_obj_get_int(items[7]); settimeofday(&tv, NULL); @@ -139,13 +146,13 @@ static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar } } static mp_obj_t machine_rtc_datetime(size_t n_args, const mp_obj_t *args) { - return machine_rtc_datetime_helper(n_args, args); + return machine_rtc_datetime_helper(n_args, args, 4); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); static mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { mp_obj_t args[2] = {self_in, date}; - machine_rtc_datetime_helper(2, args); + machine_rtc_datetime_helper(2, args, 3); return mp_const_none; } diff --git a/ports/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index 91769ea3341..5feeb0da096 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -185,7 +185,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s return (mp_obj_t)&machine_rtc_obj; } -static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) { +static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args, int hour_index) { if (n_args == 1) { // Get date and time. snvs_lp_srtc_datetime_t srtc_date; @@ -214,9 +214,9 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) srtc_date.month = mp_obj_get_int(items[1]); srtc_date.day = mp_obj_get_int(items[2]); // Ignore weekday at items[3] - srtc_date.hour = mp_obj_get_int(items[4]); - srtc_date.minute = mp_obj_get_int(items[5]); - srtc_date.second = mp_obj_get_int(items[6]); + srtc_date.hour = mp_obj_get_int(items[hour_index]); + srtc_date.minute = mp_obj_get_int(items[hour_index + 1]); + srtc_date.second = mp_obj_get_int(items[hour_index + 2]); if (SNVS_LP_SRTC_SetDatetime(SNVS, &srtc_date) != kStatus_Success) { mp_raise_ValueError(NULL); } @@ -226,13 +226,13 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) } static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { - return machine_rtc_datetime_helper(n_args, args); + return machine_rtc_datetime_helper(n_args, args, 4); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); static mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { mp_obj_t args[2] = {self_in, date}; - machine_rtc_datetime_helper(2, args); + machine_rtc_datetime_helper(2, args, 3); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_init_obj, machine_rtc_init); diff --git a/ports/samd/machine_rtc.c b/ports/samd/machine_rtc.c index a906f9176f9..74c2266d609 100644 --- a/ports/samd/machine_rtc.c +++ b/ports/samd/machine_rtc.c @@ -93,7 +93,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s return (mp_obj_t)&machine_rtc_obj; } -static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) { +static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args, int hour_index) { // Rtc *rtc = RTC; if (n_args == 1) { // Get date and time. @@ -120,9 +120,9 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) RTC_MODE2_CLOCK_YEAR(mp_obj_get_int(items[0]) % 100) | RTC_MODE2_CLOCK_MONTH(mp_obj_get_int(items[1])) | RTC_MODE2_CLOCK_DAY(mp_obj_get_int(items[2])) | - RTC_MODE2_CLOCK_HOUR(mp_obj_get_int(items[4])) | - RTC_MODE2_CLOCK_MINUTE(mp_obj_get_int(items[5])) | - RTC_MODE2_CLOCK_SECOND(mp_obj_get_int(items[6])); + RTC_MODE2_CLOCK_HOUR(mp_obj_get_int(items[hour_index])) | + RTC_MODE2_CLOCK_MINUTE(mp_obj_get_int(items[hour_index + 1])) | + RTC_MODE2_CLOCK_SECOND(mp_obj_get_int(items[hour_index + 2])); RTC->MODE2.CLOCK.reg = date; #if defined(MCU_SAMD21) @@ -138,13 +138,13 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) } static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { - return machine_rtc_datetime_helper(n_args, args); + return machine_rtc_datetime_helper(n_args, args, 4); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); static mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { mp_obj_t args[2] = {self_in, date}; - machine_rtc_datetime_helper(2, args); + machine_rtc_datetime_helper(2, args, 3); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_init_obj, machine_rtc_init); From 32b98d3e66c46b83dc0030e1fa679923557748bd Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 21 Oct 2024 15:32:06 +0100 Subject: [PATCH 0137/2098] lib/pico-sdk: Update to version 2.1.0. Brings in support for Pico 2 W, among other things. Signed-off-by: Peter Harper --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index efe2103f9b2..95ea6acad13 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit efe2103f9b28458a1615ff096054479743ade236 +Subproject commit 95ea6acad131124694cda1c162c52cd30e0aece0 From a93762a7623c235d24a3fe806774078c50067555 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 1 Oct 2024 15:35:43 +0100 Subject: [PATCH 0138/2098] rp2/modmachine: Fix USB sleep on RP2350 MCUs. Signed-off-by: Peter Harper --- ports/rp2/modmachine.c | 2 +- tests/ports/rp2/rp2_lightsleep.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index cd73552dd98..3229aed277b 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -228,7 +228,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #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; + clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_BITS; #else #error Unknown processor #endif diff --git a/tests/ports/rp2/rp2_lightsleep.py b/tests/ports/rp2/rp2_lightsleep.py index 5ce5696e0e1..63f31940ad0 100644 --- a/tests/ports/rp2/rp2_lightsleep.py +++ b/tests/ports/rp2/rp2_lightsleep.py @@ -16,12 +16,6 @@ except ImportError: print("SKIP") raise SystemExit - -# RP2350 currently fails this test, needs further investigation. -if "RP2350" in sys.implementation._machine: - print("SKIP") - raise SystemExit - try: led = Pin(Pin.board.LED, Pin.OUT) except AttributeError: From 929d7a75265e7f5ed898560b065fd33af5a18264 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Sep 2024 09:58:07 +0100 Subject: [PATCH 0139/2098] rp2/CMakeLists.txt: Add components required by bootrom.h. Signed-off-by: Peter Harper --- ports/rp2/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 4baaf7debf6..4c85b83440e 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -196,6 +196,7 @@ set(MICROPY_SOURCE_QSTR ) set(PICO_SDK_COMPONENTS + boot_bootrom_headers hardware_adc hardware_base hardware_boot_lock @@ -222,6 +223,7 @@ set(PICO_SDK_COMPONENTS pico_base_headers pico_binary_info pico_bootrom + pico_flash pico_multicore pico_platform pico_platform_compiler From 30163e0ae483a3c04e57f8346e903a6bc2e0e2cd Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 23 Oct 2024 16:41:54 +0100 Subject: [PATCH 0140/2098] rp2/cyw43_configport: Define cyw43 pins. The cyw43 pins used to be hardcoded and `CYW43_PIN_WL_HOST_WAKE` and `CYW43_PIN_WL_REG_ON` were in the `pico_w.h` board header. This has been changed so the board header just defines the "default version of the pins, e.g. `CYW43_DEFAULT_PIN_WL_HOST_WAKE`, `CYW43_DEFAULT_PIN_WL_REG_ON` etc. Set the pin values in `cyw43_configport.`h so `cyw43-driver` sees them and allow them to be changed at runtime (dynamic) if required. Signed-off-by: Peter Harper --- ports/rp2/cyw43_configport.h | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 4b012ce17e9..4294691c666 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -53,6 +53,41 @@ #define CYW43_HOST_NAME mod_network_hostname_data +#if CYW43_PIN_WL_DYNAMIC + +// Dynamic pins can be changed at runtime before initialising the CYW43 + +typedef enum cyw43_pin_index_t { + CYW43_PIN_INDEX_WL_REG_ON, + CYW43_PIN_INDEX_WL_DATA_OUT, + CYW43_PIN_INDEX_WL_DATA_IN, + CYW43_PIN_INDEX_WL_HOST_WAKE, + CYW43_PIN_INDEX_WL_CLOCK, + CYW43_PIN_INDEX_WL_CS, + CYW43_PIN_INDEX_WL_COUNT // last +} cyw43_pin_index_t; + +// Function to retrieve a cyw43 dynamic pin +uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); + +#define CYW43_PIN_WL_REG_ON cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_REG_ON) +#define CYW43_PIN_WL_DATA_OUT cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_DATA_OUT) +#define CYW43_PIN_WL_DATA_IN cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_DATA_IN) +#define CYW43_PIN_WL_HOST_WAKE cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_HOST_WAKE) +#define CYW43_PIN_WL_CLOCK cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_CLOCK) +#define CYW43_PIN_WL_CS cyw43_get_pin_wl(CYW43_PIN_INDEX_WL_CS) + +#else + +#define CYW43_PIN_WL_REG_ON CYW43_DEFAULT_PIN_WL_REG_ON +#define CYW43_PIN_WL_DATA_OUT CYW43_DEFAULT_PIN_WL_DATA_OUT +#define CYW43_PIN_WL_DATA_IN CYW43_DEFAULT_PIN_WL_DATA_IN +#define CYW43_PIN_WL_HOST_WAKE CYW43_DEFAULT_PIN_WL_HOST_WAKE +#define CYW43_PIN_WL_CLOCK CYW43_DEFAULT_PIN_WL_CLOCK +#define CYW43_PIN_WL_CS CYW43_DEFAULT_PIN_WL_CS + +#endif + #define CYW43_SDPCM_SEND_COMMON_WAIT \ if (get_core_num() == 0) { \ cyw43_yield(); \ From 4a6c2460083607ba05ab8af13a31889767c2ce98 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 9 Oct 2024 18:03:55 +0100 Subject: [PATCH 0141/2098] rp2/mphalport: Add mp_hal_is_pin_reserved() function. As cyw43 pins might be dynamic, add a function that returns if a pin is reserved. This is used by `MICROPY_HW_PIN_RESERVED` to prevent the pin IRQ from being reset across a soft-reset. Signed-off-by: Peter Harper --- ports/rp2/boards/RPI_PICO_W/mpconfigboard.h | 4 +++- ports/rp2/mphalport.c | 8 ++++++++ ports/rp2/mphalport.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ports/rp2/boards/RPI_PICO_W/mpconfigboard.h b/ports/rp2/boards/RPI_PICO_W/mpconfigboard.h index ef812b63013..45ad6107ba1 100644 --- a/ports/rp2/boards/RPI_PICO_W/mpconfigboard.h +++ b/ports/rp2/boards/RPI_PICO_W/mpconfigboard.h @@ -20,4 +20,6 @@ #define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT -#define MICROPY_HW_PIN_RESERVED(i) ((i) == CYW43_PIN_WL_HOST_WAKE || (i) == CYW43_PIN_WL_REG_ON) +// If this returns true for a pin then its irq will not be disabled on a soft reboot +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 3f50151620a..caecb695099 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -278,3 +278,11 @@ void mp_wfe_or_timeout(uint32_t timeout_ms) { // Clean up the timer node if it's not already soft_timer_remove(&timer); } + +int mp_hal_is_pin_reserved(int n) { + #if MICROPY_PY_NETWORK_CYW43 + return n == CYW43_PIN_WL_HOST_WAKE; + #else + return false; + #endif +} diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index da865fb7e85..33a1073e1df 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -210,5 +210,6 @@ enum { void mp_hal_get_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); +int mp_hal_is_pin_reserved(int n); #endif // MICROPY_INCLUDED_RP2_MPHALPORT_H From 1b611dab039c063165fe58c476c2ab9e97804010 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 9 Oct 2024 18:05:11 +0100 Subject: [PATCH 0142/2098] rp2/boards/RPI_PICO2_W: Add new Pico 2 W board definition. Signed-off-by: Peter Harper --- ports/rp2/boards/RPI_PICO2_W/board.json | 21 +++++++++++++ ports/rp2/boards/RPI_PICO2_W/manifest.py | 6 ++++ .../boards/RPI_PICO2_W/mpconfigboard.cmake | 17 +++++++++++ ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h | 22 ++++++++++++++ .../boards/RPI_PICO2_W/mpconfigvariant.cmake | 1 + ports/rp2/boards/RPI_PICO2_W/pins.csv | 30 +++++++++++++++++++ 6 files changed, 97 insertions(+) create mode 100644 ports/rp2/boards/RPI_PICO2_W/board.json create mode 100644 ports/rp2/boards/RPI_PICO2_W/manifest.py create mode 100644 ports/rp2/boards/RPI_PICO2_W/mpconfigboard.cmake create mode 100644 ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h create mode 100644 ports/rp2/boards/RPI_PICO2_W/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/RPI_PICO2_W/pins.csv diff --git a/ports/rp2/boards/RPI_PICO2_W/board.json b/ports/rp2/boards/RPI_PICO2_W/board.json new file mode 100644 index 00000000000..f35ea940a26 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Dual-core", + "External Flash", + "USB", + "WiFi" + ], + "images": [ + "rp2-pico2-w.jpg" + ], + "mcu": "rp2350", + "product": "Pico 2 W", + "thumbnail": "", + "url": "https://www.raspberrypi.com/products/raspberry-pi-pico-2/", + "vendor": "Raspberry Pi" +} diff --git a/ports/rp2/boards/RPI_PICO2_W/manifest.py b/ports/rp2/boards/RPI_PICO2_W/manifest.py new file mode 100644 index 00000000000..4e38f09cdee --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +# Bluetooth +require("aioble") diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.cmake b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.cmake new file mode 100644 index 00000000000..1d9b7fc448a --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.cmake @@ -0,0 +1,17 @@ +# cmake file for Raspberry Pi Pico 2 W + +set(PICO_BOARD "pico2_w") + +# To change the gpio count for QFN-80 +# set(PICO_NUM_GPIOS 48) + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h new file mode 100644 index 00000000000..fe688c28031 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigboard.h @@ -0,0 +1,22 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico 2 W" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1536 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "Pico2W" + +// CYW43 driver configuration. +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +// For debugging mbedtls - also set +// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose +// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant.cmake b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant.cmake new file mode 100644 index 00000000000..6fe039ba51b --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/RPI_PICO2_W/pins.csv b/ports/rp2/boards/RPI_PICO2_W/pins.csv new file mode 100644 index 00000000000..8debb6326e0 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/pins.csv @@ -0,0 +1,30 @@ +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 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 +LED,EXT_GPIO0 From a70a90ccfc6584f51f39a7c6bf43f19695f7da51 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Fri, 22 Nov 2024 17:19:26 +0000 Subject: [PATCH 0143/2098] rp2/boards/RPI_PICO2_W: Add RISCV variant for Pico 2 W. Build with: make BOARD=RPI_PICO2_W BOARD_VARIANT=RISCV Signed-off-by: Peter Harper --- ports/rp2/CMakeLists.txt | 1 - ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_RISCV.cmake | 1 + ports/rp2/mpnetworkport.c | 4 ++++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_RISCV.cmake diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 4c85b83440e..8a4092529b1 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -407,7 +407,6 @@ if (MICROPY_PY_NETWORK_CYW43) target_link_libraries(${MICROPY_TARGET} cyw43_driver_picow - cmsis_core ) target_include_directories(${MICROPY_TARGET} PRIVATE ${MICROPY_DIR}/lib/cyw43-driver/ diff --git a/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..65a97fc3350 --- /dev/null +++ b/ports/rp2/boards/RPI_PICO2_W/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index fcc60b3fe57..af2cabb3bff 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -44,6 +44,7 @@ static soft_timer_entry_t mp_network_soft_timer; #include "lib/cyw43-driver/src/cyw43_stats.h" #include "hardware/irq.h" +#if !defined(__riscv) #if PICO_RP2040 #include "RP2040.h" // cmsis, for NVIC_SetPriority and PendSV_IRQn #elif PICO_RP2350 @@ -51,6 +52,7 @@ static soft_timer_entry_t mp_network_soft_timer; #else #error Unknown processor #endif +#endif #define CYW43_IRQ_LEVEL GPIO_IRQ_LEVEL_HIGH #define CYW43_SHARED_IRQ_HANDLER_PRIORITY PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY @@ -74,7 +76,9 @@ static void gpio_irq_handler(void) { void cyw43_irq_init(void) { gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); irq_set_enabled(IO_IRQ_BANK0, true); + #if !defined(__riscv) NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); + #endif } void cyw43_post_poll_hook(void) { From 39538e4c9a54e44ea27bcc374e17774ac2b9796d Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 15 Nov 2024 13:21:04 -0700 Subject: [PATCH 0144/2098] rp2/boards/SPARKFUN_PROMICRO: Fix SparkFun Pro Micro RP2040 image. Signed-off-by: Dryw Wade --- ports/rp2/boards/SPARKFUN_PROMICRO/board.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO/board.json b/ports/rp2/boards/SPARKFUN_PROMICRO/board.json index 46878a29cd8..b8c8afc914e 100644 --- a/ports/rp2/boards/SPARKFUN_PROMICRO/board.json +++ b/ports/rp2/boards/SPARKFUN_PROMICRO/board.json @@ -11,7 +11,7 @@ "USB-C" ], "images": [ - "17745-SparkFun_Thing_Plus_-_RP2040-01a.jpg" + "18288-SparkFun_Pro_Micro_-_RP2040-01.jpg" ], "mcu": "rp2040", "product": "Pro Micro RP2040", From 4e76acc88d8673ce4f43698fbb4c2913e63b0135 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Dec 2024 17:23:45 +1100 Subject: [PATCH 0145/2098] doc: Note that machine.USBDevice is now available on esp32 port. Support was added in commit 5f2d05d, this updates the docs to match. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.USBDevice.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst index 5c18c49a75b..5291679ac12 100644 --- a/docs/library/machine.USBDevice.rst +++ b/docs/library/machine.USBDevice.rst @@ -4,8 +4,9 @@ class USBDevice -- USB Device driver ==================================== -.. note:: ``machine.USBDevice`` is currently only supported on the rp2 and samd - ports. +.. note:: ``machine.USBDevice`` is currently only supported for esp32, rp2 and + samd ports. Native USB support is also required, and not every board + supports native USB. USBDevice provides a low-level Python API for implementing USB device functions using Python code. From a3128f89ccb1d24d0ddaec7164d2fa8a101b8ac3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Nov 2024 13:52:04 +1100 Subject: [PATCH 0146/2098] tests: Fix all file ioctl's to support only MP_STREAM_CLOSE. A return value of 0 from Python-level `ioctl()` means success, but if that's returned unconditionally it means that the method supports all ioctl calls, which is not true. Returning 0 without doing anything can potentially lead to a crash, eg for MP_STREAM_SEEK which requires returning a value in the passed-in struct pointer. This commit makes it so that all `ioctl()` methods respond only to MP_STREAM_CLOSE, ie they return -1 (indicating error) for all other ioctl calls. Signed-off-by: Damien George --- tests/micropython/builtin_execfile.py | 4 +++- tests/micropython/import_mpy_invalid.py | 4 +++- tests/micropython/import_mpy_native.py | 4 +++- tests/micropython/import_mpy_native_gc.py | 4 +++- tests/perf_bench/core_import_mpy_multi.py | 4 +++- tests/perf_bench/core_import_mpy_single.py | 4 +++- tests/run-natmodtests.py | 4 +++- tests/run-tests.py | 4 +++- 8 files changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/micropython/builtin_execfile.py b/tests/micropython/builtin_execfile.py index a905521c662..75a867bb940 100644 --- a/tests/micropython/builtin_execfile.py +++ b/tests/micropython/builtin_execfile.py @@ -16,7 +16,9 @@ def __init__(self, data): self.off = 0 def ioctl(self, request, arg): - return 0 + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 def readinto(self, buf): buf[:] = memoryview(self.data)[self.off : self.off + len(buf)] diff --git a/tests/micropython/import_mpy_invalid.py b/tests/micropython/import_mpy_invalid.py index 89fdf44839b..f928d45c791 100644 --- a/tests/micropython/import_mpy_invalid.py +++ b/tests/micropython/import_mpy_invalid.py @@ -22,7 +22,9 @@ def readinto(self, buf): return n def ioctl(self, req, arg): - return 0 + if req == 4: # MP_STREAM_CLOSE + return 0 + return -1 class UserFS: diff --git a/tests/micropython/import_mpy_native.py b/tests/micropython/import_mpy_native.py index 8f5de25a0b2..ac5e724e8d4 100644 --- a/tests/micropython/import_mpy_native.py +++ b/tests/micropython/import_mpy_native.py @@ -28,7 +28,9 @@ def readinto(self, buf): return n def ioctl(self, req, arg): - return 0 + if req == 4: # MP_STREAM_CLOSE + return 0 + return -1 class UserFS: diff --git a/tests/micropython/import_mpy_native_gc.py b/tests/micropython/import_mpy_native_gc.py index bdeb612b492..851eb392295 100644 --- a/tests/micropython/import_mpy_native_gc.py +++ b/tests/micropython/import_mpy_native_gc.py @@ -22,7 +22,9 @@ def readinto(self, buf): return n def ioctl(self, req, arg): - return 0 + if req == 4: # MP_STREAM_CLOSE + return 0 + return -1 class UserFS: diff --git a/tests/perf_bench/core_import_mpy_multi.py b/tests/perf_bench/core_import_mpy_multi.py index 33437f9da80..8affa157fa0 100644 --- a/tests/perf_bench/core_import_mpy_multi.py +++ b/tests/perf_bench/core_import_mpy_multi.py @@ -31,7 +31,9 @@ def __init__(self): self.off = 0 def ioctl(self, request, arg): - return 0 + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 def readinto(self, buf): buf[:] = memoryview(file_data)[self.off : self.off + len(buf)] diff --git a/tests/perf_bench/core_import_mpy_single.py b/tests/perf_bench/core_import_mpy_single.py index 18454b8fd5e..4d9aa67bf2f 100644 --- a/tests/perf_bench/core_import_mpy_single.py +++ b/tests/perf_bench/core_import_mpy_single.py @@ -86,7 +86,9 @@ def __init__(self): self.off = 0 def ioctl(self, request, arg): - return 0 + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 def readinto(self, buf): buf[:] = memoryview(file_data)[self.off : self.off + len(buf)] diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index f1a2a974690..1fe44bec161 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -35,7 +35,9 @@ class __File(io.IOBase): def __init__(self): self.off = 0 def ioctl(self, request, arg): - return 0 + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 def readinto(self, buf): buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] self.off += len(buf) diff --git a/tests/run-tests.py b/tests/run-tests.py index c72178cd760..a609a1fcbbe 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -68,7 +68,9 @@ def __init__(self): sys.modules['__injected_test'].__name__ = '__main__' self.off = 0 def ioctl(self, request, arg): - return 0 + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 def readinto(self, buf): buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] self.off += len(buf) From 8b6bd43eaba005c24d277fa581c945b40213059d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Nov 2024 11:16:04 +1100 Subject: [PATCH 0147/2098] extmod/vfs: Guard mutating fs functions with MICROPY_VFS_WRITABLE. Enabled by default. Useful for ports that need the VFS but don't have any writable filesystems. Signed-off-by: Damien George --- extmod/modos.c | 4 +++- extmod/vfs.c | 4 ++++ extmod/vfs.h | 4 ++++ py/mpconfig.h | 5 +++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/extmod/modos.c b/extmod/modos.c index e7f7fc818cf..69fdc3fac0a 100644 --- a/extmod/modos.c +++ b/extmod/modos.c @@ -171,13 +171,15 @@ static const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + #if MICROPY_VFS_WRITABLE { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mp_vfs_rename_obj) }, { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove + #endif { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, - { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove #endif // The following are MicroPython extensions. diff --git a/extmod/vfs.c b/extmod/vfs.c index e545c9af936..b9c5ab0fc3a 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -443,6 +443,8 @@ mp_obj_t mp_vfs_listdir(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_listdir_obj, 0, 1, mp_vfs_listdir); +#if MICROPY_VFS_WRITABLE + mp_obj_t mp_vfs_mkdir(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); @@ -479,6 +481,8 @@ mp_obj_t mp_vfs_rmdir(mp_obj_t path_in) { } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_rmdir_obj, mp_vfs_rmdir); +#endif // MICROPY_VFS_WRITABLE + mp_obj_t mp_vfs_stat(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); diff --git a/extmod/vfs.h b/extmod/vfs.h index f577d3e337c..626e25a3511 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -95,10 +95,12 @@ mp_obj_t mp_vfs_chdir(mp_obj_t path_in); mp_obj_t mp_vfs_getcwd(void); mp_obj_t mp_vfs_ilistdir(size_t n_args, const mp_obj_t *args); mp_obj_t mp_vfs_listdir(size_t n_args, const mp_obj_t *args); +#if MICROPY_VFS_WRITABLE mp_obj_t mp_vfs_mkdir(mp_obj_t path_in); mp_obj_t mp_vfs_remove(mp_obj_t path_in); mp_obj_t mp_vfs_rename(mp_obj_t old_path_in, mp_obj_t new_path_in); mp_obj_t mp_vfs_rmdir(mp_obj_t path_in); +#endif mp_obj_t mp_vfs_stat(mp_obj_t path_in); mp_obj_t mp_vfs_statvfs(mp_obj_t path_in); @@ -111,10 +113,12 @@ MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_chdir_obj); MP_DECLARE_CONST_FUN_OBJ_0(mp_vfs_getcwd_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_ilistdir_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_listdir_obj); +#if MICROPY_VFS_WRITABLE MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_mkdir_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_remove_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_vfs_rename_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_rmdir_obj); +#endif MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_stat_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj); diff --git a/py/mpconfig.h b/py/mpconfig.h index b31da2a37c0..a330cadc4ef 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -991,6 +991,11 @@ typedef double mp_float_t; #define MICROPY_VFS (0) #endif +// Whether to include support for writable filesystems. +#ifndef MICROPY_VFS_WRITABLE +#define MICROPY_VFS_WRITABLE (1) +#endif + // Support for VFS POSIX component, to mount a POSIX filesystem within VFS #ifndef MICROPY_VFS_POSIX #define MICROPY_VFS_POSIX (0) From 136058496f05b4b2ab5e63829b301671dc8b9a59 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 17 Dec 2024 20:06:02 +0100 Subject: [PATCH 0148/2098] esp32/machine_timer: Restrict timer numbers for ESP32C6 to 0 and 1. The ESP32C6 has only one timer in each of the two groups. Also add a check for valid timer numbers. Addresses issue #16438. Signed-off-by: robert-hh --- ports/esp32/machine_timer.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 30e64eb334d..82b432bbacd 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -66,7 +66,7 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr machine_timer_obj_t *self = self_in; qstr mode = self->repeat ? MP_QSTR_PERIODIC : MP_QSTR_ONE_SHOT; uint64_t period = self->period / (TIMER_SCALE / 1000); // convert to ms - #if CONFIG_IDF_TARGET_ESP32C3 + #if SOC_TIMER_GROUP_TIMERS_PER_GROUP == 1 mp_printf(print, "Timer(%u, mode=%q, period=%lu)", self->group, mode, period); #else mp_printf(print, "Timer(%u, mode=%q, period=%lu)", (self->group << 1) | self->index, mode, period); @@ -76,7 +76,7 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr machine_timer_obj_t *machine_timer_create(mp_uint_t timer) { machine_timer_obj_t *self = NULL; - #if CONFIG_IDF_TARGET_ESP32C3 + #if SOC_TIMER_GROUP_TIMERS_PER_GROUP == 1 mp_uint_t group = timer & 1; mp_uint_t index = 0; #else @@ -108,7 +108,11 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_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])); + uint32_t timer_number = mp_obj_get_int(args[0]); + if (timer_number >= SOC_TIMER_GROUP_TOTAL_TIMERS) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid Timer number")); + } + machine_timer_obj_t *self = machine_timer_create(timer_number); if (n_args > 1 || n_kw > 0) { mp_map_t kw_args; From 6760e00817ff6cb449134418411850fc577d0f9c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 23 Jun 2024 08:18:04 +0200 Subject: [PATCH 0149/2098] tools/mpy_ld.py: Add native modules support for RV32 code. This commit adds support for RV32IMC native modules, as in embedding native code into a self-contained MPY module and and make its exported functions available to the MicroPython environment. Signed-off-by: Alessandro Gatti --- docs/develop/natmod.rst | 5 +- examples/natmod/README.md | 2 +- examples/natmod/btree/Makefile | 2 +- examples/natmod/deflate/Makefile | 2 +- examples/natmod/features0/Makefile | 3 +- examples/natmod/features1/Makefile | 2 +- examples/natmod/features3/Makefile | 2 +- examples/natmod/features4/Makefile | 2 +- examples/natmod/heapq/Makefile | 2 +- examples/natmod/random/Makefile | 2 +- examples/natmod/re/Makefile | 2 +- py/dynruntime.mk | 16 ++ tools/mpy_ld.py | 399 ++++++++++++++++++++++++++++- 13 files changed, 425 insertions(+), 16 deletions(-) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index 502ea1c4c68..ba45e4305c6 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -39,7 +39,8 @@ options for the ``ARCH`` variable, see below): * ``armv7emsp`` (ARM Thumb 2, single precision float, eg Cortex-M4F, Cortex-M7) * ``armv7emdp`` (ARM Thumb 2, double precision float, eg Cortex-M7) * ``xtensa`` (non-windowed, eg ESP8266) -* ``xtensawin`` (windowed with window size 8, eg ESP32) +* ``xtensawin`` (windowed with window size 8, eg ESP32, ESP32S3) +* ``rv32imc`` (RISC-V 32 bits with compressed instructions, eg ESP32C3, ESP32C6) When compiling and linking the native .mpy file the architecture must be chosen and the corresponding file can only be imported on that architecture. For more @@ -172,7 +173,7 @@ The file ``Makefile`` contains: # Source files (.c or .py) SRC = factorial.c - # Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin) + # Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/README.md b/examples/natmod/README.md index f72d1c049a0..ca6b887d034 100644 --- a/examples/natmod/README.md +++ b/examples/natmod/README.md @@ -65,7 +65,7 @@ your system package manager or installed from PyPI in a virtual environment with `pip`. Each example provides a Makefile. You should specify the `ARCH` argument to -make (one of x86, x64, armv6m, armv7m, xtensa, xtensawin): +make (one of x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc): ``` $ cd features0 diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index b5846f90065..ff130d61b37 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -7,7 +7,7 @@ MOD = btree_$(ARCH) # Source files (.c or .py) SRC = btree_c.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx diff --git a/examples/natmod/deflate/Makefile b/examples/natmod/deflate/Makefile index 86ef29b6324..504130d5723 100644 --- a/examples/natmod/deflate/Makefile +++ b/examples/natmod/deflate/Makefile @@ -7,7 +7,7 @@ MOD = deflate_$(ARCH) # Source files (.c or .py) SRC = deflate.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features0/Makefile b/examples/natmod/features0/Makefile index 57490df90ab..fb01b8d031a 100644 --- a/examples/natmod/features0/Makefile +++ b/examples/natmod/features0/Makefile @@ -7,8 +7,9 @@ MOD = features0 # Source files (.c or .py) SRC = features0.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 # Include to get the rules for compiling and linking the module include $(MPY_DIR)/py/dynruntime.mk + diff --git a/examples/natmod/features1/Makefile b/examples/natmod/features1/Makefile index 010640daf97..49040511020 100644 --- a/examples/natmod/features1/Makefile +++ b/examples/natmod/features1/Makefile @@ -7,7 +7,7 @@ MOD = features1 # Source files (.c or .py) SRC = features1.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/features3/Makefile b/examples/natmod/features3/Makefile index 4a5f71b8f25..3573f41caca 100644 --- a/examples/natmod/features3/Makefile +++ b/examples/natmod/features3/Makefile @@ -7,7 +7,7 @@ MOD = features3 # Source files (.c or .py) SRC = features3.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/features4/Makefile b/examples/natmod/features4/Makefile index f76a31a7cc1..34fc3a7ef7b 100644 --- a/examples/natmod/features4/Makefile +++ b/examples/natmod/features4/Makefile @@ -7,7 +7,7 @@ MOD = features4 # Source files (.c or .py) SRC = features4.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/heapq/Makefile b/examples/natmod/heapq/Makefile index af45b472da1..61e2fc8fcc0 100644 --- a/examples/natmod/heapq/Makefile +++ b/examples/natmod/heapq/Makefile @@ -7,7 +7,7 @@ MOD = heapq_$(ARCH) # Source files (.c or .py) SRC = heapq.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/random/Makefile b/examples/natmod/random/Makefile index 5c50227b15f..8abdb66dc87 100644 --- a/examples/natmod/random/Makefile +++ b/examples/natmod/random/Makefile @@ -7,7 +7,7 @@ MOD = random_$(ARCH) # Source files (.c or .py) SRC = random.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/re/Makefile b/examples/natmod/re/Makefile index 1ba54011065..56b08b98868 100644 --- a/examples/natmod/re/Makefile +++ b/examples/natmod/re/Makefile @@ -7,7 +7,7 @@ MOD = re_$(ARCH) # Source files (.c or .py) SRC = re.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 include $(MPY_DIR)/py/dynruntime.mk diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 62db43ad149..71679954eab 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -99,6 +99,22 @@ CROSS = xtensa-esp32-elf- CFLAGS += MICROPY_FLOAT_IMPL ?= float +else ifeq ($(ARCH),rv32imc) + +# rv32imc +CROSS = riscv64-unknown-elf- +CFLAGS += -march=rv32imac -mabi=ilp32 -mno-relax +# If Picolibc is available then select it explicitly. Ubuntu 22.04 ships its +# bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default +# is "nosys" so a value must be provided. To avoid having per-distro +# workarounds, always select Picolibc if available. +PICOLIBC_SPECS = $(shell $(CROSS)gcc --print-file-name=picolibc.specs) +ifneq ($(PICOLIBC_SPECS),picolibc.specs) +CFLAGS += --specs=$(PICOLIBC_SPECS) +endif + +MICROPY_FLOAT_IMPL ?= none + else $(error architecture '$(ARCH)' not supported) endif diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index c77c46d44e0..54295208f1a 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -47,6 +47,7 @@ MP_NATIVE_ARCH_ARMV7EMDP = 8 MP_NATIVE_ARCH_XTENSA = 9 MP_NATIVE_ARCH_XTENSAWIN = 10 +MP_NATIVE_ARCH_RV32IMC = 11 MP_PERSISTENT_OBJ_STR = 5 MP_SCOPE_FLAG_VIPERRELOC = 0x10 MP_SCOPE_FLAG_VIPERRODATA = 0x20 @@ -56,6 +57,7 @@ # ELF constants R_386_32 = 1 +R_RISCV_32 = 1 R_X86_64_64 = 1 R_XTENSA_32 = 1 R_386_PC32 = 2 @@ -70,15 +72,57 @@ R_386_GOTPC = 10 R_ARM_THM_CALL = 10 R_XTENSA_ASM_EXPAND = 11 +R_RISCV_BRANCH = 16 +R_RISCV_JAL = 17 +R_RISCV_CALL = 18 +R_RISCV_CALL_PLT = 19 R_XTENSA_DIFF32 = 19 R_XTENSA_SLOT0_OP = 20 +R_RISCV_GOT_HI20 = 20 +R_RISCV_TLS_GD_HI20 = 22 +R_RISCV_PCREL_HI20 = 23 +R_RISCV_PCREL_LO12_I = 24 +R_RISCV_PCREL_LO12_S = 25 R_ARM_BASE_PREL = 25 # aka R_ARM_GOTPC R_ARM_GOT_BREL = 26 # aka R_ARM_GOT32 R_ARM_THM_JUMP24 = 30 +R_RISCV_HI20 = 26 +R_RISCV_LO12_I = 27 +R_RISCV_LO12_S = 28 +R_RISCV_TPREL_HI20 = 29 +R_RISCV_TPREL_LO12_I = 30 +R_RISCV_TPREL_LO12_S = 31 +R_RISCV_TPREL_ADD = 32 +R_RISCV_ADD8 = 33 +R_RISCV_ADD16 = 34 +R_RISCV_ADD32 = 35 +R_RISCV_ADD64 = 36 +R_RISCV_SUB8 = 37 +R_RISCV_SUB16 = 38 +R_RISCV_SUB32 = 39 +R_RISCV_SUB64 = 40 +R_RISCV_GOT32_PCREL = 41 R_X86_64_GOTPCREL = 9 R_X86_64_REX_GOTPCRELX = 42 R_386_GOT32X = 43 +R_RISCV_ALIGN = 43 +R_RISCV_RVC_BRANCH = 44 +R_RISCV_RVC_JUMP = 45 +R_RISCV_RELAX = 51 +R_RISCV_SUB6 = 52 +R_RISCV_SET6 = 53 +R_RISCV_SET8 = 54 +R_RISCV_SET16 = 55 +R_RISCV_SET32 = 56 +R_RISCV_32_PCREL = 57 +R_RISCV_PLT32 = 59 R_XTENSA_PDIFF32 = 59 +R_RISCV_SET_ULEB128 = 60 +R_RISCV_SUB_ULEB128 = 61 +R_RISCV_TLSDESC_HI20 = 62 +R_RISCC_TLSDESC_LOAD_LO12 = 63 +R_RISCV_TLSDESC_ADD_LO12 = 64 +R_RISCV_TLSDESC_CALL = 65 ################################################################################ # Architecture configuration @@ -130,6 +174,18 @@ def asm_jump_xtensa(entry): return struct.pack("> 8) +def asm_jump_rv32(entry): + # This could be 6 bytes shorter, but the code currently cannot + # support a trampoline with varying length depending on the offset. + + # auipc t6, HI(entry) + # jalr zero, t6, LO(entry) + upper, lower = split_riscv_address(entry) + return struct.pack( + "> 16 & 0xFF +def split_riscv_address(value): + # The address can be represented with just the lowest 12 bits + if value < 0 and value > -2048: + value = 4096 + value + return 0, value + # 2s complement + if value < 0: + value = 0x100000000 + value + upper, lower = (value & 0xFFFFF000), (value & 0xFFF) + if lower & 0x800 != 0: + # Reverse lower part sign extension + upper += 0x1000 + return upper & 0xFFFFFFFF, lower & 0xFFFFFFFF + + def xxd(text): for i in range(0, len(text), 16): print("{:08x}:".format(i), end="") @@ -346,7 +424,7 @@ def build_got_generic(env): for r in sec.reloc: s = r.sym if not ( - s.entry["st_info"]["bind"] == "STB_GLOBAL" + s.entry["st_info"]["bind"] in ("STB_GLOBAL", "STB_WEAK") and r["r_info_type"] in env.arch.arch_got ): continue @@ -487,6 +565,8 @@ def do_relocation_text(env, text_addr, r): # Default relocation type and name for logging reloc_type = "le32" log_name = None + addr = None + value = None if ( env.arch.name == "EM_386" @@ -590,12 +670,46 @@ def do_relocation_text(env, text_addr, r): return assert 0 + elif env.arch.name == "EM_RISCV" and r_info_type in ( + R_RISCV_TLS_GD_HI20, + R_RISCV_TLSDESC_HI20, + R_RISCV_TLSDESC_ADD_LO12, + R_RISCV_TLSDESC_CALL, + ): + # TLS relocations are not supported. + raise LinkError("{}: RISC-V TLS relocation: {}".format(s.filename, s.name)) + + elif env.arch.name == "EM_RISCV" and r_info_type in ( + R_RISCV_TPREL_HI20, + R_RISCV_TPREL_LO12_I, + R_RISCV_TPREL_LO12_S, + R_RISCV_TPREL_ADD, + ): + # ThreadPointer-relative relocations are not supported. + raise LinkError("{}: RISC-V TP-relative relocation: {}".format(s.filename, s.name)) + + elif env.arch.name == "EM_RISCV" and r_info_type in (R_RISCV_SET_ULEB128, R_RISCV_SUB_ULEB128): + # 128-bit value relocations are not supported + raise LinkError("{}: RISC-V ULEB128 relocation: {}".format(s.filename, s.name)) + + elif env.arch.name == "EM_RISCV" and r_info_type in (R_RISCV_RELAX, R_RISCV_ALIGN): + # To keep things simple, no relocations are relaxed and thus no + # size optimisation is performed even if there is the chance, along + # with no offsets to fix up. + return + + elif env.arch.name == "EM_RISCV": + (addr, value) = process_riscv32_relocation(env, text_addr, r) + else: # Unknown/unsupported relocation assert 0, r_info_type # Write relocation - if reloc_type == "le32": + if env.arch.name == "EM_RISCV": + # This case is already handled by `process_riscv_relocation`. + pass + elif reloc_type == "le32": (existing,) = struct.unpack_from(" {:08x}".format(r_offset, log_name, addr)) + if addr is not None: + log(LOG_LEVEL_3, " {:08x} {} -> {:08x}".format(r_offset, log_name, addr)) + else: + log(LOG_LEVEL_3, " {:08x} {} == {:08x}".format(r_offset, log_name, value)) def do_relocation_data(env, text_addr, r): @@ -646,12 +763,16 @@ def do_relocation_data(env, text_addr, r): and r_info_type == R_ARM_ABS32 or env.arch.name == "EM_XTENSA" and r_info_type == R_XTENSA_32 + or env.arch.name == "EM_RISCV" + and r_info_type == R_RISCV_32 ): # Relocation in data.rel.ro to internal/external symbol if env.arch.word_size == 4: struct_type = "> 1) + | ((reloc & 0x20) >> 3) + | ((reloc & 0x18) << 7) + | ((reloc & 0x06) << 2) + ) + & 0xFFFF, + ) + elif reloc_type == "riscv_cj": + # Patch the target of a compressed jump opcode + (existing,) = struct.unpack_from("> 2) + | ((reloc & 0x300) << 1) + | ((reloc & 0x80) >> 1) + | ((reloc & 0x40) << 1) + | ((reloc & 0x20) >> 3) + | ((reloc & 0x10) << 7) + | ((reloc & 0x0E) << 2) + ) + & 0xFFFF, + ) + elif reloc_type == "riscv_call": + # Patch a pair of opcodes forming a call operation + upper, lower = split_riscv_address(reloc) + (existing,) = struct.unpack_from("> 4) + | ((reloc & 0x7E0) << 20) + | ((reloc & 0x1E) << 7) + ) + & 0xFFFFFFFF, + ) + elif reloc_type == "riscv_j": + # Patch a jump/jump with link opcode + (existing,) = struct.unpack_from(" Date: Wed, 2 Oct 2024 23:46:14 +0200 Subject: [PATCH 0150/2098] examples/natmod/re: Fix build on RV32 with alloca. This fixes compilation of the `re` natmod example when built with Picolibc in the CI environment. Ubuntu 22.04's combination of its bare metal RISC-V toolchain and its version of Picolibc makes the `alloca` symbol more elusive than it should be. This commit makes the `re` natmod try harder to get an `alloca` implementation. Signed-off-by: Alessandro Gatti --- examples/natmod/re/re.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/natmod/re/re.c b/examples/natmod/re/re.c index eb6d13778c3..c0279ee7e81 100644 --- a/examples/natmod/re/re.c +++ b/examples/natmod/re/re.c @@ -4,7 +4,20 @@ #define MICROPY_PY_RE_MATCH_SPAN_START_END (1) #define MICROPY_PY_RE_SUB (0) // requires vstr interface +#if defined __has_builtin +#if __has_builtin(__builtin_alloca) +#define alloca __builtin_alloca +#endif +#endif + +#if !defined(alloca) +#if defined(_PICOLIBC__) && !defined(HAVE_BUILTIN_ALLOCA) +#define alloca(n) m_malloc(n) +#else #include +#endif +#endif + #include "py/dynruntime.h" #define STACK_LIMIT (2048) From 4bf087b272856482c055a6df66ec008f2ca846d0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 11 Dec 2024 13:36:32 +0100 Subject: [PATCH 0151/2098] py/dynruntime.mk: Delete compiled module file on clean. This commit adds the compiled native module file to the list of files to remove when `make clean` is issued in a native module source directory. Signed-off-by: Alessandro Gatti --- py/dynruntime.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 71679954eab..68d5e2e268f 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -38,6 +38,8 @@ MPY_CROSS_FLAGS += -march=$(ARCH) SRC_O += $(addprefix $(BUILD)/, $(patsubst %.c,%.o,$(filter %.c,$(SRC))) $(patsubst %.S,%.o,$(filter %.S,$(SRC)))) SRC_MPY += $(addprefix $(BUILD)/, $(patsubst %.py,%.mpy,$(filter %.py,$(SRC)))) +CLEAN_EXTRA += $(MOD).mpy + ################################################################################ # Architecture configuration From 7ca6e5eb6815de6f7c2792969b0f66cf306f5b91 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 22 Dec 2024 00:27:03 +0100 Subject: [PATCH 0152/2098] qemu: Add test_natmod target for RV32 and use as part of CI pipeline. This commit brings the natmod tests in the CI build process for the RV32 platform. Not all example natmods are tested at the moment, as `features` requires soft-float support, and `btree` needs thread-local storage support in `mpy_ld.py` when built with the CI's toolchain. Co-authored-by: Damien George Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 6 ++++++ ports/qemu/README.md | 18 +++++++++++++++--- ports/qemu/boards/VIRT_RV32.mk | 2 ++ tools/ci.sh | 13 +++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 237e1e9514c..b85ff2896a1 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -168,6 +168,12 @@ test: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && ./run-tests.py -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_ARGS) $(RUN_TESTS_EXTRA) +.PHONY: test_natmod +test_natmod: $(BUILD)/firmware.elf + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + # "btree" cannot build against Picolibc right now. + cd $(TOP)/tests && ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_NATMODTESTS_ARGS) extmod/{deflate,framebuf,heapq,random_basic,re}*.py + $(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ) $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) $(Q)$(SIZE) $@ diff --git a/ports/qemu/README.md b/ports/qemu/README.md index 984faf87047..9274bcc4e75 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -71,8 +71,9 @@ 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: +This will start `qemu-system-arm` (or `qemu-system-riscv32`) with the UART +redirected to stdio. It's also possible to redirect the UART to a pty device +using: $ make run @@ -84,7 +85,7 @@ for example `mpremote`: 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). +was started (or execute `import machine; 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: @@ -97,6 +98,17 @@ tests against the serial device, for example: $ cd ../../tests $ ./run-tests.py -t /dev/pts/1 +Selected native modules that come as examples with the MicroPython source tree +can also be tested with this command (this is currently supported only for the +`VIRT_RV32` board): + + $ make test_natmod + +The same remarks about manually running the tests apply for native modules, but +`run-natmodtests.py` should be run instead of `run-tests.py`. In this case you +also have to explicitly pass the architecture you are running native modules to +`run-natmodtests.py` ("--arch rv32imc" for the `VIRT_RV32` board). + Extra make options ------------------ diff --git a/ports/qemu/boards/VIRT_RV32.mk b/ports/qemu/boards/VIRT_RV32.mk index a61b659fa6d..355a09c3d52 100644 --- a/ports/qemu/boards/VIRT_RV32.mk +++ b/ports/qemu/boards/VIRT_RV32.mk @@ -12,3 +12,5 @@ MPY_CROSS_FLAGS += -march=rv32imc # These Thumb tests don't run on RV32, so exclude them. RUN_TESTS_ARGS = --exclude 'inlineasm|qemu/asm_test' + +RUN_NATMODTESTS_ARGS = --arch rv32imc diff --git a/tools/ci.sh b/tools/ci.sh index c67aeed0cec..8d75d7cecf4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -273,6 +273,7 @@ function ci_qemu_setup_arm { } function ci_qemu_setup_rv32 { + ci_mpy_format_setup ci_gcc_riscv_setup sudo apt-get update sudo apt-get install qemu-system @@ -292,6 +293,10 @@ function ci_qemu_build_rv32 { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 submodules make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 test + + # Test building and running native .mpy with rv32imc architecture. + ci_native_mpy_modules_build rv32imc + make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 test_natmod } ######################################################################################## @@ -476,10 +481,14 @@ function ci_native_mpy_modules_build { arch=$1 fi make -C examples/natmod/features1 ARCH=$arch - make -C examples/natmod/features2 ARCH=$arch + if [ $arch != rv32imc ]; then + # This requires soft-float support on rv32imc. + make -C examples/natmod/features2 ARCH=$arch + # This requires thread local storage support on rv32imc. + make -C examples/natmod/btree ARCH=$arch + fi make -C examples/natmod/features3 ARCH=$arch make -C examples/natmod/features4 ARCH=$arch - make -C examples/natmod/btree ARCH=$arch make -C examples/natmod/deflate ARCH=$arch make -C examples/natmod/framebuf ARCH=$arch make -C examples/natmod/heapq ARCH=$arch From 4729a895046908414b239f02256ac3334d1b2c15 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 20 Dec 2024 22:43:14 +0100 Subject: [PATCH 0153/2098] tools/verifygitlog.py: Show invalid commit subjects in quotes. If a commit subject line has any trailing whitespace it won't match the repository validation rules, and the line will show up as part of the relevant error message. However, since there's no quotation marks around the offending text, the trailing whitespace may go unnoticed, and given that the commit message is then discarded when the commit operation is retried this can get fairly annoying. This commit simply modifies the error output for invalid subject lines to add quotation marks around the offending text, so trailing whitespace is much easier to see. Signed-off-by: Alessandro Gatti --- tools/verifygitlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index ad9385e7ac5..67215d5c5d0 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -47,7 +47,7 @@ def git_log(pretty_format, *args): def diagnose_subject_line(subject_line, subject_line_format, err): - err.error("Subject line: " + subject_line) + err.error('Subject line: "' + subject_line + '"') if not subject_line.endswith("."): err.error('* must end with "."') if not re.match(r"^[^!]+: ", subject_line): From 50637ff239cb6a09294f49fe2d2534c2d7ae8b76 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:52:35 +1100 Subject: [PATCH 0154/2098] extmod/vfs_rom: Add VfsRom filesystem object. This commit defines a new ROMFS filesystem for storing read-only files that can be memory mapped, and a new VfsRom driver. Files opened from this filesystem support the buffer protocol. This allows naturally getting the memory-mapped address of the file using: - memoryview(file) - uctypes.addressof(file) Furthermore, if these files are .mpy files then their content can be referenced in-place when importing. Such imports take up a lot less RAM than importing from a normal filesystem. This is essentially dynamically frozen .mpy files, building on the revamped v6 .mpy file format. Signed-off-by: Damien George --- extmod/extmod.cmake | 2 + extmod/extmod.mk | 2 + extmod/modvfs.c | 4 + extmod/vfs_rom.c | 399 ++++++++++++++++++++++++++++++++++++++++++ extmod/vfs_rom.h | 47 +++++ extmod/vfs_rom_file.c | 180 +++++++++++++++++++ py/mpconfig.h | 5 + 7 files changed, 639 insertions(+) create mode 100644 extmod/vfs_rom.c create mode 100644 extmod/vfs_rom.h create mode 100644 extmod/vfs_rom_file.c diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 98e8a84608a..154931a6644 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -58,6 +58,8 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/vfs_fat_diskio.c ${MICROPY_EXTMOD_DIR}/vfs_fat_file.c ${MICROPY_EXTMOD_DIR}/vfs_lfs.c + ${MICROPY_EXTMOD_DIR}/vfs_rom.c + ${MICROPY_EXTMOD_DIR}/vfs_rom_file.c ${MICROPY_EXTMOD_DIR}/vfs_posix.c ${MICROPY_EXTMOD_DIR}/vfs_posix_file.c ${MICROPY_EXTMOD_DIR}/vfs_reader.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index f2ae3ad6923..c8bc74d268e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -61,6 +61,8 @@ SRC_EXTMOD_C += \ extmod/vfs_fat_diskio.c \ extmod/vfs_fat_file.c \ extmod/vfs_lfs.c \ + extmod/vfs_rom.c \ + extmod/vfs_rom_file.c \ extmod/vfs_posix.c \ extmod/vfs_posix_file.c \ extmod/vfs_reader.c \ diff --git a/extmod/modvfs.c b/extmod/modvfs.c index 32dc0e5d08b..df422365b75 100644 --- a/extmod/modvfs.c +++ b/extmod/modvfs.c @@ -32,6 +32,7 @@ #include "extmod/vfs_fat.h" #include "extmod/vfs_lfs.h" #include "extmod/vfs_posix.h" +#include "extmod/vfs_rom.h" #if !MICROPY_VFS #error "MICROPY_PY_VFS requires MICROPY_VFS" @@ -51,6 +52,9 @@ static const mp_rom_map_elem_t vfs_module_globals_table[] = { #if MICROPY_VFS_LFS2 { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, #endif + #if MICROPY_VFS_ROM + { MP_ROM_QSTR(MP_QSTR_VfsRom), MP_ROM_PTR(&mp_type_vfs_rom) }, + #endif #if MICROPY_VFS_POSIX { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, #endif diff --git a/extmod/vfs_rom.c b/extmod/vfs_rom.c new file mode 100644 index 00000000000..b8b6f8e40d1 --- /dev/null +++ b/extmod/vfs_rom.c @@ -0,0 +1,399 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 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. + */ + +// ROMFS filesystem format +// ======================= +// +// ROMFS is a flexible and extensible filesystem format designed to represent a +// directory hierarchy with files, where those files are read-only and their data +// can be memory mapped. +// +// Concepts: +// - varuint: An unsigned integer that is encoded in a variable number of bytes. It is +// stored big-endian with the high bit of the byte set if there are following bytes. +// - record: A variable sized element with a type. It is stored as two varuint's and then +// a payload. The first varuint is the record kind and the second varuint is the +// payload length (which may be zero bytes long). +// +// A ROMFS filesystem is a record with record kind 0x14a6b1, chosen so the encoded value +// is 0xd2-0xcd-0x31 which is "RM1" with the first two bytes having their high bit set. +// If the ROMFS record's payload is non-empty then it contains records. +// +// Record types: +// - 0 = unused, can be used to detect corruption of the filesystem. +// - 1 = padding/comments, can contain any data in their payload. +// - 2 = verbatim data, used to store file data. +// - 3 = indirect data, pointer to offset within the ROMFS payload. +// - 4 = a directory: payload contains a varuint which is the length of the directory +// name in bytes, then the name, then optional nested records for the contents +// of the directory (including optional metadata). +// - 5 = a file: payload contains a varuint which is the length of the filename in bytes +// then the name, then optional nested records. +// +// Remarks: +// - A varuint can be padded if needed by prepending with one or more 0x80 bytes. This +// padding does not change any semantics. +// - The size of the ROMFS record (including kind and length and payload) must be a +// multiple of 2 (because it's not possible to add a padding record of one byte). +// - File data can be optionally aligned using padding records and/or indirect data +// records. +// - There is no limit to the size of directory/file names or file data. +// +// Unknown record types must be skipped over. They may in the future add optional +// features, while still retaining backwards compatibility. Such features may be: +// - Alignment requirements of the ROMFS record. +// - Timestamps on directories/files. +// - A precomputed hash of a file, or other metadata. +// - An optimised lookup table indexing the directory hierarchy. + +#include + +#include "py/bc.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "extmod/vfs_rom.h" + +#if MICROPY_VFS_ROM + +#define ROMFS_SIZE_MIN (4) +#define ROMFS_HEADER_BYTE0 (0x80 | 'R') +#define ROMFS_HEADER_BYTE1 (0x80 | 'M') +#define ROMFS_HEADER_BYTE2 (0x00 | '1') + +// Values for `record_kind_t`. +#define ROMFS_RECORD_KIND_UNUSED (0) +#define ROMFS_RECORD_KIND_PADDING (1) +#define ROMFS_RECORD_KIND_DATA_VERBATIM (2) +#define ROMFS_RECORD_KIND_DATA_POINTER (3) +#define ROMFS_RECORD_KIND_DIRECTORY (4) +#define ROMFS_RECORD_KIND_FILE (5) + +typedef mp_uint_t record_kind_t; + +struct _mp_obj_vfs_rom_t { + mp_obj_base_t base; + mp_obj_t memory; + const uint8_t *filesystem; + const uint8_t *filesystem_end; +}; + +static record_kind_t extract_record(const uint8_t **fs, const uint8_t **fs_next) { + record_kind_t record_kind = mp_decode_uint(fs); + mp_uint_t record_len = mp_decode_uint(fs); + *fs_next = *fs + record_len; + return record_kind; +} + +static void extract_data(mp_obj_vfs_rom_t *self, const uint8_t *fs, const uint8_t *fs_top, size_t *size_out, const uint8_t **data_out) { + *size_out = 0; + *data_out = NULL; + while (fs < fs_top) { + const uint8_t *fs_next; + record_kind_t record_kind = extract_record(&fs, &fs_next); + if (record_kind == ROMFS_RECORD_KIND_DATA_VERBATIM) { + // Verbatim data. + *size_out = fs_next - fs; + *data_out = fs; + break; + } else if (record_kind == ROMFS_RECORD_KIND_DATA_POINTER) { + // Pointer to data. + *size_out = mp_decode_uint(&fs); + *data_out = self->filesystem + mp_decode_uint(&fs); + break; + } else { + // Skip this record. + fs = fs_next; + } + } +} + +// Searches for `path` in the filesystem. +// `path` must be null-terminated. +mp_import_stat_t mp_vfs_rom_search_filesystem(mp_obj_vfs_rom_t *self, const char *path, size_t *size_out, const uint8_t **data_out) { + const uint8_t *fs = self->filesystem; + const uint8_t *fs_top = self->filesystem_end; + size_t path_len = strlen(path); + if (*path == '/') { + // An optional slash at the start of the path enters the top-level filesystem. + ++path; + --path_len; + } + while (path_len > 0 && fs < fs_top) { + const uint8_t *fs_next; + record_kind_t record_kind = extract_record(&fs, &fs_next); + if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { + // A directory or file record. + mp_uint_t name_len = mp_decode_uint(&fs); + if ((name_len == path_len + || (name_len < path_len && path[name_len] == '/')) + && memcmp(path, fs, name_len) == 0) { + // Name matches, so enter this record. + fs += name_len; + fs_top = fs_next; + path += name_len; + path_len -= name_len; + if (record_kind == ROMFS_RECORD_KIND_DIRECTORY) { + // Continue searching in this directory. + if (*path == '/') { + ++path; + --path_len; + } + } else { + // Return this file. + if (path_len != 0) { + return MP_IMPORT_STAT_NO_EXIST; + } + if (size_out != NULL) { + extract_data(self, fs, fs_top, size_out, data_out); + } + return MP_IMPORT_STAT_FILE; + } + } else { + // Skip this directory/file record. + fs = fs_next; + } + } else { + // Skip this record. + fs = fs_next; + } + } + if (path_len == 0) { + if (size_out != NULL) { + *size_out = fs_top - fs; + *data_out = fs; + } + return MP_IMPORT_STAT_DIR; + } + return MP_IMPORT_STAT_NO_EXIST; +} + +static mp_obj_t vfs_rom_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, 1, false); + + mp_obj_vfs_rom_t *self = m_new_obj(mp_obj_vfs_rom_t); + self->base.type = type; + self->memory = args[0]; + + mp_buffer_info_t bufinfo; + if (mp_get_buffer(self->memory, &bufinfo, MP_BUFFER_READ)) { + if (bufinfo.len < ROMFS_SIZE_MIN) { + mp_raise_OSError(MP_ENODEV); + } + self->filesystem = bufinfo.buf; + } else { + self->filesystem = (const uint8_t *)(uintptr_t)mp_obj_get_int_truncated(self->memory); + } + + // Verify it is a ROMFS. + if (!(self->filesystem[0] == ROMFS_HEADER_BYTE0 + && self->filesystem[1] == ROMFS_HEADER_BYTE1 + && self->filesystem[2] == ROMFS_HEADER_BYTE2)) { + mp_raise_OSError(MP_ENODEV); + } + + // The ROMFS is a record itself, so enter into it and compute its limit. + extract_record(&self->filesystem, &self->filesystem_end); + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t vfs_rom_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) { + (void)self_in; + (void)readonly; + if (mp_obj_is_true(mkfs)) { + mp_raise_OSError(MP_EPERM); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(vfs_rom_mount_obj, vfs_rom_mount); + +// mp_vfs_rom_file_open is implemented in vfs_rom_file.c. +static MP_DEFINE_CONST_FUN_OBJ_3(vfs_rom_open_obj, mp_vfs_rom_file_open); + +static mp_obj_t vfs_rom_chdir(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_vfs_rom_get_path_str(self, path_in); + if (path[0] == '/' && path[1] == '\0') { + // Allow chdir to the root of the filesystem. + } else { + // Don't allow chdir to any subdirectory (not currently implemented). + mp_raise_OSError(MP_EOPNOTSUPP); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_chdir_obj, vfs_rom_chdir); + +typedef struct _vfs_rom_ilistdir_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_vfs_rom_t *vfs_rom; + bool is_str; + const uint8_t *index; + const uint8_t *index_top; +} vfs_rom_ilistdir_it_t; + +static mp_obj_t vfs_rom_ilistdir_it_iternext(mp_obj_t self_in) { + vfs_rom_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + + while (self->index < self->index_top) { + const uint8_t *index_next; + record_kind_t record_kind = extract_record(&self->index, &index_next); + uint32_t type; + mp_uint_t name_len; + size_t data_len; + if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { + // A directory or file record. + name_len = mp_decode_uint(&self->index); + if (record_kind == ROMFS_RECORD_KIND_DIRECTORY) { + // A directory. + type = MP_S_IFDIR; + data_len = index_next - self->index - name_len; + } else { + // A file. + type = MP_S_IFREG; + const uint8_t *data_value; + extract_data(self->vfs_rom, self->index + name_len, index_next, &data_len, &data_value); + } + } else { + // Skip this record. + self->index = index_next; + continue; + } + + const uint8_t *name_str = self->index; + self->index = index_next; + + // Make 4-tuple with info about this entry: (name, attr, inode, size) + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL)); + + if (self->is_str) { + t->items[0] = mp_obj_new_str((const char *)name_str, name_len); + } else { + t->items[0] = mp_obj_new_bytes(name_str, name_len); + } + + t->items[1] = MP_OBJ_NEW_SMALL_INT(type); + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); + t->items[3] = mp_obj_new_int(data_len); + + return MP_OBJ_FROM_PTR(t); + } + + return MP_OBJ_STOP_ITERATION; +} + +static mp_obj_t vfs_rom_ilistdir(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + vfs_rom_ilistdir_it_t *iter = m_new_obj(vfs_rom_ilistdir_it_t); + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = vfs_rom_ilistdir_it_iternext; + iter->vfs_rom = self; + iter->is_str = mp_obj_get_type(path_in) == &mp_type_str; + const char *path = mp_vfs_rom_get_path_str(self, path_in); + size_t size; + if (mp_vfs_rom_search_filesystem(self, path, &size, &iter->index) != MP_IMPORT_STAT_DIR) { + mp_raise_OSError(MP_ENOENT); + } + iter->index_top = iter->index + size; + return MP_OBJ_FROM_PTR(iter); +} +static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_ilistdir_obj, vfs_rom_ilistdir); + +static mp_obj_t vfs_rom_stat(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_vfs_rom_get_path_str(self, path_in); + size_t file_size; + const uint8_t *file_data; + mp_import_stat_t stat = mp_vfs_rom_search_filesystem(self, path, &file_size, &file_data); + if (stat == MP_IMPORT_STAT_NO_EXIST) { + mp_raise_OSError(MP_ENOENT); + } + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(stat == MP_IMPORT_STAT_FILE ? MP_S_IFREG : MP_S_IFDIR); // st_mode + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev + t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink + t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid + t->items[6] = MP_OBJ_NEW_SMALL_INT(file_size); // st_size + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime + t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_stat_obj, vfs_rom_stat); + +static mp_obj_t vfs_rom_statvfs(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + (void)path_in; + size_t filesystem_len = self->filesystem_end - self->filesystem; + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(1); // f_bsize + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // f_frsize + t->items[2] = mp_obj_new_int_from_uint(filesystem_len); // f_blocks + t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // f_bfree + t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // f_bavail + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files + t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags + t->items[9] = MP_OBJ_NEW_SMALL_INT(32767); // f_namemax + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_statvfs_obj, vfs_rom_statvfs); + +static const mp_rom_map_elem_t vfs_rom_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_rom_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&vfs_rom_open_obj) }, + + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&vfs_rom_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&vfs_rom_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_rom_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_rom_statvfs_obj) }, +}; +static MP_DEFINE_CONST_DICT(vfs_rom_locals_dict, vfs_rom_locals_dict_table); + +static mp_import_stat_t mp_vfs_rom_import_stat(void *self_in, const char *path) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + return mp_vfs_rom_search_filesystem(self, path, NULL, NULL); +} + +static const mp_vfs_proto_t vfs_rom_proto = { + .import_stat = mp_vfs_rom_import_stat, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_vfs_rom, + MP_QSTR_VfsRom, + MP_TYPE_FLAG_NONE, + make_new, vfs_rom_make_new, + protocol, &vfs_rom_proto, + locals_dict, &vfs_rom_locals_dict + ); + +#endif // MICROPY_VFS_ROM diff --git a/extmod/vfs_rom.h b/extmod/vfs_rom.h new file mode 100644 index 00000000000..d8e2b911ba0 --- /dev/null +++ b/extmod/vfs_rom.h @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 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. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_VFS_ROM_H +#define MICROPY_INCLUDED_EXTMOD_VFS_ROM_H + +#include "py/builtin.h" +#include "py/obj.h" + +#if MICROPY_VFS_ROM + +typedef struct _mp_obj_vfs_rom_t mp_obj_vfs_rom_t; + +extern const mp_obj_type_t mp_type_vfs_rom; + +static inline const char *mp_vfs_rom_get_path_str(mp_obj_vfs_rom_t *self, mp_obj_t path) { + return mp_obj_str_get_str(path); +} + +mp_import_stat_t mp_vfs_rom_search_filesystem(mp_obj_vfs_rom_t *self, const char *path, size_t *size_out, const uint8_t **data_out); +mp_obj_t mp_vfs_rom_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); + +#endif // MICROPY_VFS_ROM + +#endif // MICROPY_INCLUDED_EXTMOD_VFS_ROM_H diff --git a/extmod/vfs_rom_file.c b/extmod/vfs_rom_file.c new file mode 100644 index 00000000000..57aca8c5dc7 --- /dev/null +++ b/extmod/vfs_rom_file.c @@ -0,0 +1,180 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 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 + +#include "py/reader.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs_rom.h" + +#if MICROPY_VFS_ROM + +typedef struct _mp_obj_vfs_rom_file_t { + mp_obj_base_t base; + size_t file_size; + size_t file_offset; + const uint8_t *file_data; +} mp_obj_vfs_rom_file_t; + +static const mp_obj_type_t mp_type_vfs_rom_fileio; +static const mp_obj_type_t mp_type_vfs_rom_textio; + +mp_obj_t mp_vfs_rom_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) { + mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in); + + const char *mode_s = mp_obj_str_get_str(mode_in); + const mp_obj_type_t *type = &mp_type_vfs_rom_textio; + while (*mode_s) { + switch (*mode_s++) { + case 'r': + break; + case 'w': + case 'a': + case '+': + mp_raise_OSError(MP_EROFS); + case 'b': + type = &mp_type_vfs_rom_fileio; + break; + case 't': + type = &mp_type_vfs_rom_textio; + break; + } + } + + mp_obj_vfs_rom_file_t *o = m_new_obj(mp_obj_vfs_rom_file_t); + o->base.type = type; + o->file_offset = 0; + + const char *path = mp_vfs_rom_get_path_str(self, path_in); + mp_import_stat_t stat = mp_vfs_rom_search_filesystem(self, path, &o->file_size, &o->file_data); + if (stat == MP_IMPORT_STAT_NO_EXIST) { + mp_raise_OSError(MP_ENOENT); + } else if (stat == MP_IMPORT_STAT_DIR) { + mp_raise_OSError(MP_EISDIR); + } + + return MP_OBJ_FROM_PTR(o); +} + +static mp_int_t vfs_rom_file_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mp_obj_vfs_rom_file_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)self->file_data; + bufinfo->len = self->file_size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Can't write to a ROM file. + return 1; + } +} + +static mp_uint_t vfs_rom_file_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_vfs_rom_file_t *self = MP_OBJ_TO_PTR(o_in); + size_t remain = self->file_size - self->file_offset; + if (size > remain) { + size = remain; + } + memcpy(buf, self->file_data + self->file_offset, size); + self->file_offset += size; + return size; +} + +static mp_uint_t vfs_rom_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_vfs_rom_file_t *self = MP_OBJ_TO_PTR(o_in); + + switch (request) { + case MP_STREAM_SEEK: { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; + if (s->whence == 0) { // SEEK_SET + self->file_offset = (size_t)s->offset; + } else if (s->whence == 1) { // SEEK_CUR + self->file_offset += s->offset; + } else { // SEEK_END + self->file_offset = self->file_size + s->offset; + } + if (self->file_offset > self->file_size) { + if (s->offset < 0) { + // Seek to before the start of the file. + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } + self->file_offset = self->file_size; + } + s->offset = self->file_offset; + return 0; + } + case MP_STREAM_CLOSE: + return 0; + default: + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +static const mp_rom_map_elem_t vfs_rom_rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) }, + { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, + { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj) }, +}; +static MP_DEFINE_CONST_DICT(vfs_rom_rawfile_locals_dict, vfs_rom_rawfile_locals_dict_table); + +static const mp_stream_p_t vfs_rom_fileio_stream_p = { + .read = vfs_rom_file_read, + .ioctl = vfs_rom_file_ioctl, +}; + +static MP_DEFINE_CONST_OBJ_TYPE( + mp_type_vfs_rom_fileio, + MP_QSTR_FileIO, + MP_TYPE_FLAG_ITER_IS_STREAM, + buffer, vfs_rom_file_get_buffer, + protocol, &vfs_rom_fileio_stream_p, + locals_dict, &vfs_rom_rawfile_locals_dict + ); + +static const mp_stream_p_t vfs_rom_textio_stream_p = { + .read = vfs_rom_file_read, + .ioctl = vfs_rom_file_ioctl, + .is_text = true, +}; + +static MP_DEFINE_CONST_OBJ_TYPE( + mp_type_vfs_rom_textio, + MP_QSTR_TextIOWrapper, + MP_TYPE_FLAG_ITER_IS_STREAM, + protocol, &vfs_rom_textio_stream_p, + locals_dict, &vfs_rom_rawfile_locals_dict + ); + +#endif // MICROPY_VFS_ROM diff --git a/py/mpconfig.h b/py/mpconfig.h index a330cadc4ef..8598eaa5bb6 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1016,6 +1016,11 @@ typedef double mp_float_t; #define MICROPY_VFS_LFS2 (0) #endif +// Support for ROMFS. +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (0) +#endif + /*****************************************************************************/ /* Fine control over Python builtins, classes, modules, etc */ From a1c32101ac3bc70ed2774f461c69548ce8dae23d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:40:15 +1100 Subject: [PATCH 0155/2098] py/qstr: Add qstr_from_strn_static() helper function. Allows an interned string to reference static/ROM data, instead of allocating it on the GC heap. Signed-off-by: Damien George --- py/qstr.c | 24 ++++++++++++++++++++++-- py/qstr.h | 3 +++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/py/qstr.c b/py/qstr.c index fea7f44fe13..7902646a92d 100644 --- a/py/qstr.c +++ b/py/qstr.c @@ -309,7 +309,7 @@ qstr qstr_from_str(const char *str) { return qstr_from_strn(str, strlen(str)); } -qstr qstr_from_strn(const char *str, size_t len) { +static qstr qstr_from_strn_helper(const char *str, size_t len, bool data_is_static) { QSTR_ENTER(); qstr q = qstr_find_strn(str, len); if (q == 0) { @@ -321,6 +321,12 @@ qstr qstr_from_strn(const char *str, size_t len) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); } + if (data_is_static) { + // Given string data will be forever available so use it directly. + assert(str[len] == '\0'); + goto add; + } + // compute number of bytes needed to intern this string size_t n_bytes = len + 1; @@ -364,12 +370,26 @@ qstr qstr_from_strn(const char *str, size_t len) { // store the interned strings' data memcpy(q_ptr, str, len); q_ptr[len] = '\0'; - q = qstr_add(len, q_ptr); + str = q_ptr; + + add: + q = qstr_add(len, str); } QSTR_EXIT(); return q; } +qstr qstr_from_strn(const char *str, size_t len) { + return qstr_from_strn_helper(str, len, false); +} + +#if MICROPY_VFS_ROM +// Create a new qstr that can forever reference the given string data. +qstr qstr_from_strn_static(const char *str, size_t len) { + return qstr_from_strn_helper(str, len, true); +} +#endif + mp_uint_t qstr_hash(qstr q) { const qstr_pool_t *pool = find_qstr(&q); #if MICROPY_QSTR_BYTES_IN_HASH diff --git a/py/qstr.h b/py/qstr.h index 58ce285fd2c..0cde6062eb1 100644 --- a/py/qstr.h +++ b/py/qstr.h @@ -101,6 +101,9 @@ qstr qstr_find_strn(const char *str, size_t str_len); // returns MP_QSTRnull if qstr qstr_from_str(const char *str); qstr qstr_from_strn(const char *str, size_t len); +#if MICROPY_VFS_ROM +qstr qstr_from_strn_static(const char *str, size_t len); +#endif mp_uint_t qstr_hash(qstr q); const char *qstr_str(qstr q); From f870e8d2d406e809b4494e6d029fee0269b1ea05 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:54:05 +1100 Subject: [PATCH 0156/2098] py/reader: Provide mp_reader_try_read_rom() function. This allows accessing data directly in ROM if the reader supports it. Signed-off-by: Damien George --- py/reader.c | 15 ++++++++++++++- py/reader.h | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/py/reader.c b/py/reader.c index 151e04cac2c..8feb6d75275 100644 --- a/py/reader.c +++ b/py/reader.c @@ -50,7 +50,7 @@ static mp_uint_t mp_reader_mem_readbyte(void *data) { static void mp_reader_mem_close(void *data) { mp_reader_mem_t *reader = (mp_reader_mem_t *)data; - if (reader->free_len > 0) { + if (reader->free_len > 0 && reader->free_len != MP_READER_IS_ROM) { m_del(char, (char *)reader->beg, reader->free_len); } m_del_obj(mp_reader_mem_t, reader); @@ -67,6 +67,19 @@ void mp_reader_new_mem(mp_reader_t *reader, const byte *buf, size_t len, size_t reader->close = mp_reader_mem_close; } +const uint8_t *mp_reader_try_read_rom(mp_reader_t *reader, size_t len) { + if (reader->readbyte != mp_reader_mem_readbyte) { + return NULL; + } + mp_reader_mem_t *m = reader->data; + if (m->free_len != MP_READER_IS_ROM) { + return NULL; + } + const uint8_t *data = m->cur; + m->cur += len; + return data; +} + #if MICROPY_READER_POSIX #include diff --git a/py/reader.h b/py/reader.h index 5cb1e67966c..301c70ab3f0 100644 --- a/py/reader.h +++ b/py/reader.h @@ -28,6 +28,10 @@ #include "py/obj.h" +// Pass to the `free_len` argument to `mp_reader_new_mem` to indicate that the data is in ROM. +// This means that the data is addressable and will remain valid at least until a soft reset. +#define MP_READER_IS_ROM ((size_t)-1) + // the readbyte function must return the next byte in the input stream // it must return MP_READER_EOF if end of stream // it can be called again after returning MP_READER_EOF, and in that case must return MP_READER_EOF @@ -43,4 +47,9 @@ void mp_reader_new_mem(mp_reader_t *reader, const byte *buf, size_t len, size_t void mp_reader_new_file(mp_reader_t *reader, qstr filename); void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd); +// Try to efficiently read the given number of bytes from a ROM-based reader. +// Returns a valid, non-NULL pointer to the requseted data if the reader points to ROM. +// Returns NULL if the reader does not point to ROM. +const uint8_t *mp_reader_try_read_rom(mp_reader_t *reader, size_t len); + #endif // MICROPY_INCLUDED_PY_READER_H From 5e9dd4b6a2ba58bc61ffc0d0a34a03c0382fcc72 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 18 Dec 2024 11:30:33 +1100 Subject: [PATCH 0157/2098] extmod/vfs_reader: Add support for opening a memory-mappable file. If the file can be memory mapped (because it responds to the buffer protocol) then return a memory-reader that directly references the ROM data of the file. Signed-off-by: Damien George --- extmod/vfs_reader.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extmod/vfs_reader.c b/extmod/vfs_reader.c index 80d0fa6344a..de5c4e03d3c 100644 --- a/extmod/vfs_reader.c +++ b/extmod/vfs_reader.c @@ -85,6 +85,17 @@ void mp_reader_new_file(mp_reader_t *reader, qstr filename) { const mp_stream_p_t *stream_p = mp_get_stream(file); int errcode = 0; + + #if MICROPY_VFS_ROM + // Check if the stream can be memory mapped. + mp_buffer_info_t bufinfo; + if (mp_get_buffer(file, &bufinfo, MP_BUFFER_READ)) { + mp_reader_new_mem(reader, bufinfo.buf, bufinfo.len, MP_READER_IS_ROM); + return; + } + #endif + + // Determine how big the input buffer should be, if the stream requests a certain size or not. mp_uint_t bufsize = stream_p->ioctl(file, MP_STREAM_GET_BUFFER_SIZE, 0, &errcode); if (bufsize == MP_STREAM_ERROR || bufsize == 0) { // bufsize == 0 is included here to support mpremote v1.21 and older where mount file ioctl @@ -94,6 +105,7 @@ void mp_reader_new_file(mp_reader_t *reader, qstr filename) { bufsize = MIN(MICROPY_READER_VFS_MAX_BUFFER_SIZE, MAX(MICROPY_READER_VFS_MIN_BUFFER_SIZE, bufsize)); } + // Create the reader. mp_reader_vfs_t *rf = m_new_obj_var(mp_reader_vfs_t, buf, byte, bufsize); rf->file = file; rf->bufsize = bufsize; From 97518064cd5fadf6e57e9b989958c9f36fe94e47 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:54:05 +1100 Subject: [PATCH 0158/2098] py/persistentcode: Add support for loading .mpy files from a ROM reader. This adds an optimisation for loading .mpy files from a reader that points to ROM. In such a case qstr, str and bytes data, along with bytecode, are all referenced in-place in ROM. Signed-off-by: Damien George --- py/persistentcode.c | 70 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index be93eaa5b45..0843d1a2c52 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -181,6 +181,15 @@ static qstr load_qstr(mp_reader_t *reader) { return len >> 1; } len >>= 1; + + #if MICROPY_VFS_ROM + // If possible, create the qstr from the memory-mapped string data. + const uint8_t *memmap = mp_reader_try_read_rom(reader, len + 1); + if (memmap != NULL) { + return qstr_from_strn_static((const char *)memmap, len); + } + #endif + char *str = m_new(char, len); read_bytes(reader, (byte *)str, len); read_byte(reader); // read and discard null terminator @@ -189,6 +198,24 @@ static qstr load_qstr(mp_reader_t *reader) { return qst; } +#if MICROPY_VFS_ROM +// Create a str/bytes object that can forever reference the given data. +static mp_obj_t mp_obj_new_str_static(const mp_obj_type_t *type, const byte *data, size_t len) { + if (type == &mp_type_str) { + qstr q = qstr_find_strn((const char *)data, len); + if (q != MP_QSTRnull) { + return MP_OBJ_NEW_QSTR(q); + } + } + assert(data[len] == '\0'); + mp_obj_str_t *o = mp_obj_malloc(mp_obj_str_t, type); + o->len = len; + o->hash = qstr_compute_hash(data, len); + o->data = data; + return MP_OBJ_FROM_PTR(o); +} +#endif + static mp_obj_t load_obj(mp_reader_t *reader) { byte obj_type = read_byte(reader); #if MICROPY_EMIT_MACHINE_CODE @@ -206,6 +233,8 @@ static mp_obj_t load_obj(mp_reader_t *reader) { return MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj); } else { size_t len = read_uint(reader); + + // Handle empty bytes object, and tuple objects. if (len == 0 && obj_type == MP_PERSISTENT_OBJ_BYTES) { read_byte(reader); // skip null terminator return mp_const_empty_bytes; @@ -216,11 +245,31 @@ static mp_obj_t load_obj(mp_reader_t *reader) { } return MP_OBJ_FROM_PTR(tuple); } + + // Read in the object's data, either from ROM or into RAM. + const uint8_t *memmap = NULL; vstr_t vstr; - vstr_init_len(&vstr, len); - read_bytes(reader, (byte *)vstr.buf, len); + #if MICROPY_VFS_ROM + memmap = mp_reader_try_read_rom(reader, len); + vstr.buf = (void *)memmap; + vstr.len = len; + #endif + if (memmap == NULL) { + // Data could not be memory-mapped, so allocate it in RAM and read it in. + vstr_init_len(&vstr, len); + read_bytes(reader, (byte *)vstr.buf, len); + } + + // Create and return the object. if (obj_type == MP_PERSISTENT_OBJ_STR || obj_type == MP_PERSISTENT_OBJ_BYTES) { - read_byte(reader); // skip null terminator + read_byte(reader); // skip null terminator (it needs to be there for ROM str objects) + #if MICROPY_VFS_ROM + if (memmap != NULL) { + // Create a str/bytes that references the memory-mapped data. + const mp_obj_type_t *t = obj_type == MP_PERSISTENT_OBJ_STR ? &mp_type_str : &mp_type_bytes; + return mp_obj_new_str_static(t, memmap, len); + } + #endif if (obj_type == MP_PERSISTENT_OBJ_STR) { return mp_obj_new_str_from_utf8_vstr(&vstr); } else { @@ -257,10 +306,17 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co #endif if (kind == MP_CODE_BYTECODE) { - // Allocate memory for the bytecode - fun_data = m_new(uint8_t, fun_data_len); - // Load bytecode - read_bytes(reader, fun_data, fun_data_len); + #if MICROPY_VFS_ROM + // Try to reference memory-mapped data for the bytecode. + fun_data = (uint8_t *)mp_reader_try_read_rom(reader, fun_data_len); + #endif + + if (fun_data == NULL) { + // Allocate memory for the bytecode. + fun_data = m_new(uint8_t, fun_data_len); + // Load bytecode. + read_bytes(reader, fun_data, fun_data_len); + } #if MICROPY_EMIT_MACHINE_CODE } else { From d9378c9287f87c0214dc4a33206dfd047f798240 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:56:27 +1100 Subject: [PATCH 0159/2098] unix: Enable VfsRom on standard and coverage variants. Signed-off-by: Damien George --- ports/unix/variants/mpconfigvariant_common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index cea03974143..093477af0ad 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -121,3 +121,5 @@ #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_BASE (1) + +#define MICROPY_VFS_ROM (1) From def9a37994897341ee9250b39851f9cf53a748de Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Nov 2024 23:58:01 +1100 Subject: [PATCH 0160/2098] qemu/mpconfigport: Enable VfsRom. Signed-off-by: Damien George --- ports/qemu/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index f95b58ba4d4..d3e3c0a3f37 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -60,6 +60,7 @@ #define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS (1) +#define MICROPY_VFS_ROM (1) // type definitions for the specific machine From c73204128e16cfa62abd42b07981bd8e5adeb321 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Nov 2024 10:35:31 +1100 Subject: [PATCH 0161/2098] tests/extmod: Add VfsRom test. Provides full coverage of the VfsRom driver. Signed-off-by: Damien George --- tests/extmod/vfs_rom.py | 412 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 tests/extmod/vfs_rom.py diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py new file mode 100644 index 00000000000..0382c84c558 --- /dev/null +++ b/tests/extmod/vfs_rom.py @@ -0,0 +1,412 @@ +# Test VfsRom filesystem. + +try: + import sys, struct, os, uctypes, vfs + + vfs.VfsRom +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +try: + import select +except ImportError: + select = None + +import unittest + +IFDIR = 0x4000 +IFREG = 0x8000 + +SEEK_SET = 0 +SEEK_CUR = 1 +SEEK_END = 2 + +# An mpy file with four constant objects: str, bytes, long-int, float. +test_mpy = ( + # header + b"M\x06\x00\x1f" # mpy file header + b"\x06" # n_qstr + b"\x05" # n_obj + # qstrs + b"\x0etest.py\x00" # qstr0 = "test.py" + b"\x0f" # qstr1 = "" + b"\x0estr_obj\x00" # qstr2 = "str_obj" + b"\x12bytes_obj\x00" # qstr3 = "bytes_obj" + b"\x0eint_obj\x00" # qstr4 = "int_obj" + b"\x12float_obj\x00" # qstr5 = "float_obj" + # objects + b"\x05\x14this is a str object\x00" + b"\x06\x16this is a bytes object\x00" + b"\x07\x0a1234567890" # long-int object + b"\x08\x041.23" # float object + b"\x05\x07str_obj\x00" # str object of existing qstr + # bytecode + b"\x81\x28" # 21 bytes, no children, bytecode + b"\x00\x02" # prelude + b"\x01" # simple name () + b"\x23\x00" # LOAD_CONST_OBJ(0) + b"\x16\x02" # STORE_NAME(str_obj) + b"\x23\x01" # LOAD_CONST_OBJ(1) + b"\x16\x03" # STORE_NAME(bytes_obj) + b"\x23\x02" # LOAD_CONST_OBJ(2) + b"\x16\x04" # STORE_NAME(int_obj) + b"\x23\x03" # LOAD_CONST_OBJ(3) + b"\x16\x05" # STORE_NAME(float_obj) + b"\x51" # LOAD_CONST_NONE + b"\x63" # RETURN_VALUE +) + + +class VfsRomWriter: + ROMFS_HEADER = b"\xd2\xcd\x31" + + ROMFS_RECORD_KIND_UNUSED = 0 + ROMFS_RECORD_KIND_PADDING = 1 + ROMFS_RECORD_KIND_DATA_VERBATIM = 2 + ROMFS_RECORD_KIND_DATA_POINTER = 3 + ROMFS_RECORD_KIND_DIRECTORY = 4 + ROMFS_RECORD_KIND_FILE = 5 + + def __init__(self): + self._dir_stack = [(None, bytearray())] + + def _encode_uint(self, value): + encoded = [value & 0x7F] + value >>= 7 + while value != 0: + encoded.insert(0, 0x80 | (value & 0x7F)) + value >>= 7 + return bytes(encoded) + + def _pack(self, kind, payload): + return self._encode_uint(kind) + self._encode_uint(len(payload)) + payload + + def _extend(self, data): + buf = self._dir_stack[-1][1] + buf.extend(data) + return len(buf) + + def finalise(self): + _, data = self._dir_stack.pop() + encoded_kind = VfsRomWriter.ROMFS_HEADER + encoded_len = self._encode_uint(len(data)) + if (len(encoded_kind) + len(encoded_len) + len(data)) % 2 == 1: + encoded_len = b"\x80" + encoded_len + data = encoded_kind + encoded_len + data + return data + + def opendir(self, dirname): + self._dir_stack.append((dirname, bytearray())) + + def closedir(self): + dirname, dirdata = self._dir_stack.pop() + dirdata = self._encode_uint(len(dirname)) + bytes(dirname, "ascii") + dirdata + self._extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DIRECTORY, dirdata)) + + def mkdata(self, data): + assert len(self._dir_stack) == 1 + return self._extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_VERBATIM, data)) - len( + data + ) + + def mkfile(self, filename, filedata, extra_payload=b""): + filename = bytes(filename, "ascii") + payload = self._encode_uint(len(filename)) + payload += filename + payload += extra_payload + if isinstance(filedata, tuple): + sub_payload = self._encode_uint(filedata[0]) + sub_payload += self._encode_uint(filedata[1]) + payload += self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_POINTER, sub_payload) + else: + payload += self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_VERBATIM, filedata) + self._dir_stack[-1][1].extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_FILE, payload)) + + +def _make_romfs(fs, files, data_map): + for filename, contents in files: + if isinstance(contents, tuple): + fs.opendir(filename) + _make_romfs(fs, contents, data_map) + fs.closedir() + elif isinstance(contents, int): + fs.mkfile(filename, data_map[contents]) + else: + fs.mkfile(filename, contents) + + +def make_romfs(files, data=None): + fs = VfsRomWriter() + data_map = {} + if data: + for k, v in data.items(): + data_map[k] = len(v), fs.mkdata(v) + _make_romfs(fs, files, data_map) + return fs.finalise() + + +# A class to test if a value is within a range, needed because MicroPython's range +# doesn't support arbitrary objects. +class Range: + def __init__(self, lower, upper): + self._lower = lower + self._upper = upper + + def __repr__(self): + return "Range({}, {})".format(self._lower, self._upper) + + def __contains__(self, value): + return self._lower <= value < self._upper + + +class TestBase(unittest.TestCase): + @classmethod + def setUpClass(cls): + fs_inner = make_romfs((("test_inner.txt", b"contents_inner"), ("c.py", b""))) + cls.romfs = make_romfs( + ( + ("fs.romfs", 0), + ("test.txt", b"contents"), + ( + "dir", + ( + ("a.py", b"x = 1"), + ("b.py", b"x = 2"), + ("test.mpy", test_mpy), + ), + ), + ), + {0: fs_inner}, + ) + cls.romfs_ilistdir = [ + ("fs.romfs", IFREG, 0, 46), + ("test.txt", IFREG, 0, 8), + ("dir", IFDIR, 0, 198), + ] + cls.romfs_listdir = [x[0] for x in cls.romfs_ilistdir] + cls.romfs_listdir_dir = ["a.py", "b.py", "test.mpy"] + cls.romfs_listdir_bytes = [bytes(x, "ascii") for x in cls.romfs_listdir] + cls.romfs_addr = uctypes.addressof(cls.romfs) + cls.romfs_addr_range = Range(cls.romfs_addr, cls.romfs_addr + len(cls.romfs)) + + +class TestEdgeCases(unittest.TestCase): + def test_empty_romfs(self): + empty_romfs = make_romfs(()) + self.assertEqual(empty_romfs, bytes([0x80 | ord("R"), 0x80 | ord("M"), ord("1"), 0])) + fs = vfs.VfsRom(empty_romfs) + self.assertIsInstance(fs, vfs.VfsRom) + fs.mount(True, False) + self.assertEqual(list(fs.ilistdir("")), []) + self.assertEqual(fs.stat(""), (IFDIR, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + self.assertEqual(fs.statvfs(""), (1, 0, 0, 0, 0, 0, 0, 0, 0, 32767)) + + def test_error(self): + for bad_romfs in (b"", b"xxx", b"not a romfs"): + with self.assertRaises(OSError) as ctx: + vfs.VfsRom(bad_romfs) + self.assertEqual(ctx.exception.errno, errno.ENODEV) + + def test_unknown_record(self): + fs = VfsRomWriter() + fs._extend(fs._pack(VfsRomWriter.ROMFS_RECORD_KIND_PADDING, b"payload")) + fs.mkfile( + "test", + b"contents", + extra_payload=fs._pack(VfsRomWriter.ROMFS_RECORD_KIND_PADDING, b"pad"), + ) + romfs = fs.finalise() + fs = vfs.VfsRom(romfs) + self.assertEqual(list(fs.ilistdir("")), [("test", IFREG, 0, 8)]) + with fs.open("test", "rb") as f: + self.assertEqual(f.read(), b"contents") + + +class TestStandalone(TestBase): + def test_constructor(self): + self.assertIsInstance(vfs.VfsRom(self.romfs), vfs.VfsRom) + self.assertIsInstance(vfs.VfsRom(self.romfs_addr), vfs.VfsRom) + + def test_mount(self): + vfs.VfsRom(self.romfs).mount(True, False) + with self.assertRaises(OSError): + vfs.VfsRom(self.romfs).mount(True, True) + + def test_ilistdir(self): + fs = vfs.VfsRom(self.romfs) + self.assertEqual(list(fs.ilistdir("")), self.romfs_ilistdir) + self.assertEqual(list(fs.ilistdir("/")), self.romfs_ilistdir) + with self.assertRaises(OSError): + fs.ilistdir("does not exist") + + def test_stat(self): + fs = vfs.VfsRom(self.romfs) + self.assertEqual(fs.stat(""), (IFDIR, 0, 0, 0, 0, 0, 289, 0, 0, 0)) + self.assertEqual(fs.stat("/"), (IFDIR, 0, 0, 0, 0, 0, 289, 0, 0, 0)) + self.assertEqual(fs.stat("/test.txt"), (IFREG, 0, 0, 0, 0, 0, 8, 0, 0, 0)) + self.assertEqual(fs.stat("/dir"), (IFDIR, 0, 0, 0, 0, 0, 198, 0, 0, 0)) + with self.assertRaises(OSError): + fs.stat("/does-not-exist") + + def test_statvfs(self): + fs = vfs.VfsRom(self.romfs) + self.assertEqual(fs.statvfs(""), (1, 0, 289, 0, 0, 0, 0, 0, 0, 32767)) + + def test_open(self): + fs = vfs.VfsRom(self.romfs) + + with fs.open("/test.txt", "") as f: + self.assertEqual(f.read(), "contents") + with fs.open("/test.txt", "rt") as f: + self.assertEqual(f.read(), "contents") + with fs.open("/test.txt", "rb") as f: + self.assertEqual(f.read(), b"contents") + + with self.assertRaises(OSError) as ctx: + fs.open("/file-does-not-exist", "") + self.assertEqual(ctx.exception.errno, errno.ENOENT) + + with self.assertRaises(OSError) as ctx: + fs.open("/dir", "rb") + self.assertEqual(ctx.exception.errno, errno.EISDIR) + + with self.assertRaises(OSError): + fs.open("/test.txt", "w") + with self.assertRaises(OSError): + fs.open("/test.txt", "a") + with self.assertRaises(OSError): + fs.open("/test.txt", "+") + + def test_file_seek(self): + fs = vfs.VfsRom(self.romfs) + with fs.open("/test.txt", "") as f: + self.assertEqual(f.seek(0, SEEK_SET), 0) + self.assertEqual(f.seek(3, SEEK_SET), 3) + self.assertEqual(f.read(), "tents") + self.assertEqual(f.seek(0, SEEK_SET), 0) + self.assertEqual(f.seek(100, SEEK_CUR), 8) + self.assertEqual(f.seek(-1, SEEK_END), 7) + self.assertEqual(f.read(), "s") + self.assertEqual(f.seek(1, SEEK_END), 8) + with self.assertRaises(OSError): + f.seek(-1, SEEK_SET) + f.seek(0, SEEK_SET) + with self.assertRaises(OSError): + f.seek(-1, SEEK_CUR) + with self.assertRaises(OSError): + f.seek(-100, SEEK_END) + + @unittest.skipIf(select is None, "no select module") + def test_file_ioctl_invalid(self): + fs = vfs.VfsRom(self.romfs) + with fs.open("/test.txt", "") as f: + p = select.poll() + p.register(f) + with self.assertRaises(OSError): + p.poll(0) + + def test_memory_mapping(self): + fs = vfs.VfsRom(self.romfs) + with fs.open("/test.txt", "rb") as f: + addr = uctypes.addressof(f) + data = memoryview(f) + self.assertIn(addr, self.romfs_addr_range) + self.assertIn(addr + len(data), self.romfs_addr_range) + self.assertEqual(bytes(data), b"contents") + + +class TestMounted(TestBase): + def setUp(self): + self.orig_sys_path = list(sys.path) + self.orig_cwd = os.getcwd() + vfs.mount(vfs.VfsRom(self.romfs), "/test_rom") + + def tearDown(self): + vfs.umount("/test_rom") + os.chdir(self.orig_cwd) + sys.path = self.orig_sys_path + + def test_listdir(self): + self.assertEqual(os.listdir("/test_rom"), self.romfs_listdir) + self.assertEqual(os.listdir("/test_rom/dir"), self.romfs_listdir_dir) + self.assertEqual(os.listdir(b"/test_rom"), self.romfs_listdir_bytes) + + def test_chdir(self): + os.chdir("/test_rom") + self.assertEqual(os.listdir(), self.romfs_listdir) + + os.chdir("/test_rom/") + self.assertEqual(os.listdir(), self.romfs_listdir) + + # chdir within the romfs is not implemented. + with self.assertRaises(OSError): + os.chdir("/test_rom/dir") + + def test_stat(self): + self.assertEqual(os.stat("/test_rom"), (IFDIR, 0, 0, 0, 0, 0, 289, 0, 0, 0)) + self.assertEqual(os.stat("/test_rom/"), (IFDIR, 0, 0, 0, 0, 0, 289, 0, 0, 0)) + self.assertEqual(os.stat("/test_rom/test.txt"), (IFREG, 0, 0, 0, 0, 0, 8, 0, 0, 0)) + self.assertEqual(os.stat("/test_rom/dir"), (IFDIR, 0, 0, 0, 0, 0, 198, 0, 0, 0)) + with self.assertRaises(OSError): + os.stat("/test_rom/does-not-exist") + + def test_open(self): + with open("/test_rom/test.txt") as f: + self.assertEqual(f.read(), "contents") + with open("/test_rom/test.txt", "b") as f: + self.assertEqual(f.read(), b"contents") + + with self.assertRaises(OSError) as ctx: + open("/test_rom/file-does-not-exist") + self.assertEqual(ctx.exception.errno, errno.ENOENT) + + with self.assertRaises(OSError) as ctx: + open("/test_rom/dir") + self.assertEqual(ctx.exception.errno, errno.EISDIR) + + def test_import_py(self): + sys.path.append("/test_rom/dir") + a = __import__("a") + b = __import__("b") + self.assertEqual(a.__file__, "/test_rom/dir/a.py") + self.assertEqual(a.x, 1) + self.assertEqual(b.__file__, "/test_rom/dir/b.py") + self.assertEqual(b.x, 2) + + def test_import_mpy(self): + sys.path.append("/test_rom/dir") + test = __import__("test") + self.assertEqual(test.__file__, "/test_rom/dir/test.mpy") + self.assertEqual(test.str_obj, "this is a str object") + self.assertEqual(test.bytes_obj, b"this is a bytes object") + self.assertEqual(test.int_obj, 1234567890) + self.assertEqual(test.float_obj, 1.23) + self.assertIn(uctypes.addressof(test.str_obj), self.romfs_addr_range) + self.assertIn(uctypes.addressof(test.bytes_obj), self.romfs_addr_range) + + def test_romfs_inner(self): + with open("/test_rom/fs.romfs", "rb") as f: + romfs_inner = vfs.VfsRom(memoryview(f)) + + vfs.mount(romfs_inner, "/test_rom2") + + self.assertEqual(os.listdir("/test_rom2"), ["test_inner.txt", "c.py"]) + + sys.path.append("/test_rom2") + self.assertEqual(__import__("c").__file__, "/test_rom2/c.py") + + with open("/test_rom2/test_inner.txt") as f: + self.assertEqual(f.read(), "contents_inner") + + with open("/test_rom2/test_inner.txt", "rb") as f: + addr = uctypes.addressof(f) + data = memoryview(f) + self.assertIn(addr, self.romfs_addr_range) + self.assertIn(addr + len(data), self.romfs_addr_range) + + vfs.umount("/test_rom2") + + +if __name__ == "__main__": + unittest.main() From a5270c84cfdab6136d0dd4e63d41007f9a37b108 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 20 Aug 2024 07:21:55 +0200 Subject: [PATCH 0162/2098] tests/inlineasm: Make room for RV32IMC inline asm tests. Thumb/Thumb2 tests are now into their own subdirectory, as RV32IMC-specific tests will be added as part of the RV32 inline assembler support. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/SABRELITE.mk | 2 +- ports/qemu/boards/VIRT_RV32.mk | 2 +- .../{frozen_asm.py => frozen_asm_thumb.py} | 0 tests/inlineasm/{ => thumb}/asmargs.py | 0 tests/inlineasm/{ => thumb}/asmargs.py.exp | 0 tests/inlineasm/{ => thumb}/asmbcc.py | 0 tests/inlineasm/{ => thumb}/asmbcc.py.exp | 0 tests/inlineasm/{ => thumb}/asmbitops.py | 0 tests/inlineasm/{ => thumb}/asmbitops.py.exp | 0 tests/inlineasm/{ => thumb}/asmblbx.py | 0 tests/inlineasm/{ => thumb}/asmblbx.py.exp | 0 tests/inlineasm/{ => thumb}/asmconst.py | 0 tests/inlineasm/{ => thumb}/asmconst.py.exp | 0 tests/inlineasm/{ => thumb}/asmdata.py | 0 tests/inlineasm/{ => thumb}/asmdata.py.exp | 0 tests/inlineasm/{ => thumb}/asmdiv.py | 0 tests/inlineasm/{ => thumb}/asmdiv.py.exp | 0 tests/inlineasm/{ => thumb}/asmfpaddsub.py | 0 .../inlineasm/{ => thumb}/asmfpaddsub.py.exp | 0 tests/inlineasm/{ => thumb}/asmfpcmp.py | 0 tests/inlineasm/{ => thumb}/asmfpcmp.py.exp | 0 tests/inlineasm/{ => thumb}/asmfpldrstr.py | 0 .../inlineasm/{ => thumb}/asmfpldrstr.py.exp | 0 tests/inlineasm/{ => thumb}/asmfpmuldiv.py | 0 .../inlineasm/{ => thumb}/asmfpmuldiv.py.exp | 0 tests/inlineasm/{ => thumb}/asmfpsqrt.py | 0 tests/inlineasm/{ => thumb}/asmfpsqrt.py.exp | 0 tests/inlineasm/{ => thumb}/asmit.py | 0 tests/inlineasm/{ => thumb}/asmit.py.exp | 0 tests/inlineasm/{ => thumb}/asmpushpop.py | 0 tests/inlineasm/{ => thumb}/asmpushpop.py.exp | 0 tests/inlineasm/{ => thumb}/asmrettype.py | 0 tests/inlineasm/{ => thumb}/asmrettype.py.exp | 0 tests/inlineasm/{ => thumb}/asmshift.py | 0 tests/inlineasm/{ => thumb}/asmshift.py.exp | 0 tests/inlineasm/{ => thumb}/asmspecialregs.py | 0 .../{ => thumb}/asmspecialregs.py.exp | 0 tests/inlineasm/{ => thumb}/asmsum.py | 0 tests/inlineasm/{ => thumb}/asmsum.py.exp | 0 tests/ports/qemu/asm_test.py | 2 +- tests/run-tests.py | 42 +++++++++---------- 41 files changed, 24 insertions(+), 24 deletions(-) rename ports/qemu/test-frzmpy/{frozen_asm.py => frozen_asm_thumb.py} (100%) rename tests/inlineasm/{ => thumb}/asmargs.py (100%) rename tests/inlineasm/{ => thumb}/asmargs.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmbcc.py (100%) rename tests/inlineasm/{ => thumb}/asmbcc.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmbitops.py (100%) rename tests/inlineasm/{ => thumb}/asmbitops.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmblbx.py (100%) rename tests/inlineasm/{ => thumb}/asmblbx.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmconst.py (100%) rename tests/inlineasm/{ => thumb}/asmconst.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmdata.py (100%) rename tests/inlineasm/{ => thumb}/asmdata.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmdiv.py (100%) rename tests/inlineasm/{ => thumb}/asmdiv.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmfpaddsub.py (100%) rename tests/inlineasm/{ => thumb}/asmfpaddsub.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmfpcmp.py (100%) rename tests/inlineasm/{ => thumb}/asmfpcmp.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmfpldrstr.py (100%) rename tests/inlineasm/{ => thumb}/asmfpldrstr.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmfpmuldiv.py (100%) rename tests/inlineasm/{ => thumb}/asmfpmuldiv.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmfpsqrt.py (100%) rename tests/inlineasm/{ => thumb}/asmfpsqrt.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmit.py (100%) rename tests/inlineasm/{ => thumb}/asmit.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmpushpop.py (100%) rename tests/inlineasm/{ => thumb}/asmpushpop.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmrettype.py (100%) rename tests/inlineasm/{ => thumb}/asmrettype.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmshift.py (100%) rename tests/inlineasm/{ => thumb}/asmshift.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmspecialregs.py (100%) rename tests/inlineasm/{ => thumb}/asmspecialregs.py.exp (100%) rename tests/inlineasm/{ => thumb}/asmsum.py (100%) rename tests/inlineasm/{ => thumb}/asmsum.py.exp (100%) diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE.mk index f1945c10fc2..839b3d6ac4f 100644 --- a/ports/qemu/boards/SABRELITE.mk +++ b/ports/qemu/boards/SABRELITE.mk @@ -16,4 +16,4 @@ SRC_BOARD_O = shared/runtime/gchelper_generic.o MPY_CROSS_FLAGS += -march=armv6 # These tests don't work on Cortex-A9, so exclude them. -RUN_TESTS_ARGS = --exclude '(asmdiv|asmspecialregs).py' +RUN_TESTS_ARGS = --exclude 'inlineasm/thumb/(asmdiv|asmspecialregs).py' diff --git a/ports/qemu/boards/VIRT_RV32.mk b/ports/qemu/boards/VIRT_RV32.mk index 355a09c3d52..e166165a199 100644 --- a/ports/qemu/boards/VIRT_RV32.mk +++ b/ports/qemu/boards/VIRT_RV32.mk @@ -11,6 +11,6 @@ SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o MPY_CROSS_FLAGS += -march=rv32imc # These Thumb tests don't run on RV32, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm|qemu/asm_test' +RUN_TESTS_ARGS = --exclude 'inlineasm/thumb|qemu/asm_test' RUN_NATMODTESTS_ARGS = --arch rv32imc diff --git a/ports/qemu/test-frzmpy/frozen_asm.py b/ports/qemu/test-frzmpy/frozen_asm_thumb.py similarity index 100% rename from ports/qemu/test-frzmpy/frozen_asm.py rename to ports/qemu/test-frzmpy/frozen_asm_thumb.py diff --git a/tests/inlineasm/asmargs.py b/tests/inlineasm/thumb/asmargs.py similarity index 100% rename from tests/inlineasm/asmargs.py rename to tests/inlineasm/thumb/asmargs.py diff --git a/tests/inlineasm/asmargs.py.exp b/tests/inlineasm/thumb/asmargs.py.exp similarity index 100% rename from tests/inlineasm/asmargs.py.exp rename to tests/inlineasm/thumb/asmargs.py.exp diff --git a/tests/inlineasm/asmbcc.py b/tests/inlineasm/thumb/asmbcc.py similarity index 100% rename from tests/inlineasm/asmbcc.py rename to tests/inlineasm/thumb/asmbcc.py diff --git a/tests/inlineasm/asmbcc.py.exp b/tests/inlineasm/thumb/asmbcc.py.exp similarity index 100% rename from tests/inlineasm/asmbcc.py.exp rename to tests/inlineasm/thumb/asmbcc.py.exp diff --git a/tests/inlineasm/asmbitops.py b/tests/inlineasm/thumb/asmbitops.py similarity index 100% rename from tests/inlineasm/asmbitops.py rename to tests/inlineasm/thumb/asmbitops.py diff --git a/tests/inlineasm/asmbitops.py.exp b/tests/inlineasm/thumb/asmbitops.py.exp similarity index 100% rename from tests/inlineasm/asmbitops.py.exp rename to tests/inlineasm/thumb/asmbitops.py.exp diff --git a/tests/inlineasm/asmblbx.py b/tests/inlineasm/thumb/asmblbx.py similarity index 100% rename from tests/inlineasm/asmblbx.py rename to tests/inlineasm/thumb/asmblbx.py diff --git a/tests/inlineasm/asmblbx.py.exp b/tests/inlineasm/thumb/asmblbx.py.exp similarity index 100% rename from tests/inlineasm/asmblbx.py.exp rename to tests/inlineasm/thumb/asmblbx.py.exp diff --git a/tests/inlineasm/asmconst.py b/tests/inlineasm/thumb/asmconst.py similarity index 100% rename from tests/inlineasm/asmconst.py rename to tests/inlineasm/thumb/asmconst.py diff --git a/tests/inlineasm/asmconst.py.exp b/tests/inlineasm/thumb/asmconst.py.exp similarity index 100% rename from tests/inlineasm/asmconst.py.exp rename to tests/inlineasm/thumb/asmconst.py.exp diff --git a/tests/inlineasm/asmdata.py b/tests/inlineasm/thumb/asmdata.py similarity index 100% rename from tests/inlineasm/asmdata.py rename to tests/inlineasm/thumb/asmdata.py diff --git a/tests/inlineasm/asmdata.py.exp b/tests/inlineasm/thumb/asmdata.py.exp similarity index 100% rename from tests/inlineasm/asmdata.py.exp rename to tests/inlineasm/thumb/asmdata.py.exp diff --git a/tests/inlineasm/asmdiv.py b/tests/inlineasm/thumb/asmdiv.py similarity index 100% rename from tests/inlineasm/asmdiv.py rename to tests/inlineasm/thumb/asmdiv.py diff --git a/tests/inlineasm/asmdiv.py.exp b/tests/inlineasm/thumb/asmdiv.py.exp similarity index 100% rename from tests/inlineasm/asmdiv.py.exp rename to tests/inlineasm/thumb/asmdiv.py.exp diff --git a/tests/inlineasm/asmfpaddsub.py b/tests/inlineasm/thumb/asmfpaddsub.py similarity index 100% rename from tests/inlineasm/asmfpaddsub.py rename to tests/inlineasm/thumb/asmfpaddsub.py diff --git a/tests/inlineasm/asmfpaddsub.py.exp b/tests/inlineasm/thumb/asmfpaddsub.py.exp similarity index 100% rename from tests/inlineasm/asmfpaddsub.py.exp rename to tests/inlineasm/thumb/asmfpaddsub.py.exp diff --git a/tests/inlineasm/asmfpcmp.py b/tests/inlineasm/thumb/asmfpcmp.py similarity index 100% rename from tests/inlineasm/asmfpcmp.py rename to tests/inlineasm/thumb/asmfpcmp.py diff --git a/tests/inlineasm/asmfpcmp.py.exp b/tests/inlineasm/thumb/asmfpcmp.py.exp similarity index 100% rename from tests/inlineasm/asmfpcmp.py.exp rename to tests/inlineasm/thumb/asmfpcmp.py.exp diff --git a/tests/inlineasm/asmfpldrstr.py b/tests/inlineasm/thumb/asmfpldrstr.py similarity index 100% rename from tests/inlineasm/asmfpldrstr.py rename to tests/inlineasm/thumb/asmfpldrstr.py diff --git a/tests/inlineasm/asmfpldrstr.py.exp b/tests/inlineasm/thumb/asmfpldrstr.py.exp similarity index 100% rename from tests/inlineasm/asmfpldrstr.py.exp rename to tests/inlineasm/thumb/asmfpldrstr.py.exp diff --git a/tests/inlineasm/asmfpmuldiv.py b/tests/inlineasm/thumb/asmfpmuldiv.py similarity index 100% rename from tests/inlineasm/asmfpmuldiv.py rename to tests/inlineasm/thumb/asmfpmuldiv.py diff --git a/tests/inlineasm/asmfpmuldiv.py.exp b/tests/inlineasm/thumb/asmfpmuldiv.py.exp similarity index 100% rename from tests/inlineasm/asmfpmuldiv.py.exp rename to tests/inlineasm/thumb/asmfpmuldiv.py.exp diff --git a/tests/inlineasm/asmfpsqrt.py b/tests/inlineasm/thumb/asmfpsqrt.py similarity index 100% rename from tests/inlineasm/asmfpsqrt.py rename to tests/inlineasm/thumb/asmfpsqrt.py diff --git a/tests/inlineasm/asmfpsqrt.py.exp b/tests/inlineasm/thumb/asmfpsqrt.py.exp similarity index 100% rename from tests/inlineasm/asmfpsqrt.py.exp rename to tests/inlineasm/thumb/asmfpsqrt.py.exp diff --git a/tests/inlineasm/asmit.py b/tests/inlineasm/thumb/asmit.py similarity index 100% rename from tests/inlineasm/asmit.py rename to tests/inlineasm/thumb/asmit.py diff --git a/tests/inlineasm/asmit.py.exp b/tests/inlineasm/thumb/asmit.py.exp similarity index 100% rename from tests/inlineasm/asmit.py.exp rename to tests/inlineasm/thumb/asmit.py.exp diff --git a/tests/inlineasm/asmpushpop.py b/tests/inlineasm/thumb/asmpushpop.py similarity index 100% rename from tests/inlineasm/asmpushpop.py rename to tests/inlineasm/thumb/asmpushpop.py diff --git a/tests/inlineasm/asmpushpop.py.exp b/tests/inlineasm/thumb/asmpushpop.py.exp similarity index 100% rename from tests/inlineasm/asmpushpop.py.exp rename to tests/inlineasm/thumb/asmpushpop.py.exp diff --git a/tests/inlineasm/asmrettype.py b/tests/inlineasm/thumb/asmrettype.py similarity index 100% rename from tests/inlineasm/asmrettype.py rename to tests/inlineasm/thumb/asmrettype.py diff --git a/tests/inlineasm/asmrettype.py.exp b/tests/inlineasm/thumb/asmrettype.py.exp similarity index 100% rename from tests/inlineasm/asmrettype.py.exp rename to tests/inlineasm/thumb/asmrettype.py.exp diff --git a/tests/inlineasm/asmshift.py b/tests/inlineasm/thumb/asmshift.py similarity index 100% rename from tests/inlineasm/asmshift.py rename to tests/inlineasm/thumb/asmshift.py diff --git a/tests/inlineasm/asmshift.py.exp b/tests/inlineasm/thumb/asmshift.py.exp similarity index 100% rename from tests/inlineasm/asmshift.py.exp rename to tests/inlineasm/thumb/asmshift.py.exp diff --git a/tests/inlineasm/asmspecialregs.py b/tests/inlineasm/thumb/asmspecialregs.py similarity index 100% rename from tests/inlineasm/asmspecialregs.py rename to tests/inlineasm/thumb/asmspecialregs.py diff --git a/tests/inlineasm/asmspecialregs.py.exp b/tests/inlineasm/thumb/asmspecialregs.py.exp similarity index 100% rename from tests/inlineasm/asmspecialregs.py.exp rename to tests/inlineasm/thumb/asmspecialregs.py.exp diff --git a/tests/inlineasm/asmsum.py b/tests/inlineasm/thumb/asmsum.py similarity index 100% rename from tests/inlineasm/asmsum.py rename to tests/inlineasm/thumb/asmsum.py diff --git a/tests/inlineasm/asmsum.py.exp b/tests/inlineasm/thumb/asmsum.py.exp similarity index 100% rename from tests/inlineasm/asmsum.py.exp rename to tests/inlineasm/thumb/asmsum.py.exp diff --git a/tests/ports/qemu/asm_test.py b/tests/ports/qemu/asm_test.py index 26c7efe4273..57238c62945 100644 --- a/tests/ports/qemu/asm_test.py +++ b/tests/ports/qemu/asm_test.py @@ -1,4 +1,4 @@ -import frozen_asm +import frozen_asm_thumb as frozen_asm print(frozen_asm.asm_add(1, 2)) print(frozen_asm.asm_add1(3)) diff --git a/tests/run-tests.py b/tests/run-tests.py index a609a1fcbbe..f00510f2da2 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -138,11 +138,11 @@ def open(self, path, mode): ), "qemu": ( # Skip tests that require Cortex-M4. - "inlineasm/asmfpaddsub.py", - "inlineasm/asmfpcmp.py", - "inlineasm/asmfpldrstr.py", - "inlineasm/asmfpmuldiv.py", - "inlineasm/asmfpsqrt.py", + "inlineasm/thumb/asmfpaddsub.py", + "inlineasm/thumb/asmfpcmp.py", + "inlineasm/thumb/asmfpldrstr.py", + "inlineasm/thumb/asmfpmuldiv.py", + "inlineasm/thumb/asmfpsqrt.py", ), "webassembly": ( "basics/string_format_modulo.py", # can't print nulls to stdout @@ -664,17 +664,17 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Check if @micropython.asm_thumb supports Thumb2 instructions, and skip such tests if it doesn't output = run_feature_check(pyb, args, "inlineasm_thumb2.py") if output != b"thumb2\n": - skip_tests.add("inlineasm/asmbcc.py") - skip_tests.add("inlineasm/asmbitops.py") - skip_tests.add("inlineasm/asmconst.py") - skip_tests.add("inlineasm/asmdiv.py") - skip_tests.add("inlineasm/asmfpaddsub.py") - skip_tests.add("inlineasm/asmfpcmp.py") - skip_tests.add("inlineasm/asmfpldrstr.py") - skip_tests.add("inlineasm/asmfpmuldiv.py") - skip_tests.add("inlineasm/asmfpsqrt.py") - skip_tests.add("inlineasm/asmit.py") - skip_tests.add("inlineasm/asmspecialregs.py") + skip_tests.add("inlineasm/thumb/asmbcc.py") + skip_tests.add("inlineasm/thumb/asmbitops.py") + skip_tests.add("inlineasm/thumb/asmconst.py") + skip_tests.add("inlineasm/thumb/asmdiv.py") + skip_tests.add("inlineasm/thumb/asmfpaddsub.py") + skip_tests.add("inlineasm/thumb/asmfpcmp.py") + skip_tests.add("inlineasm/thumb/asmfpldrstr.py") + skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") + skip_tests.add("inlineasm/thumb/asmfpsqrt.py") + skip_tests.add("inlineasm/thumb/asmit.py") + skip_tests.add("inlineasm/thumb/asmspecialregs.py") # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") @@ -1215,15 +1215,15 @@ def main(): ) if args.platform == "pyboard": # run pyboard tests - test_dirs += ("float", "stress", "inlineasm", "ports/stm32") + test_dirs += ("float", "stress", "inlineasm/thumb", "ports/stm32") elif args.platform == "mimxrt": - test_dirs += ("float", "stress", "inlineasm") + test_dirs += ("float", "stress", "inlineasm/thumb") elif args.platform == "renesas-ra": - test_dirs += ("float", "inlineasm", "ports/renesas-ra") + test_dirs += ("float", "inlineasm/thumb", "ports/renesas-ra") elif args.platform == "rp2": test_dirs += ("float", "stress", "thread", "ports/rp2") if "arm" in args.mpy_cross_flags: - test_dirs += ("inlineasm",) + test_dirs += ("inlineasm/thumb",) elif args.platform == "esp32": test_dirs += ("float", "stress", "thread") elif args.platform in ("esp8266", "minimal", "samd", "nrf"): @@ -1245,7 +1245,7 @@ def main(): elif args.platform == "qemu": test_dirs += ( "float", - "inlineasm", + "inlineasm/thumb", "ports/qemu", ) elif args.platform == "webassembly": From 3044233ea3726e9d8727d8f6a76f32c48e6fae5e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 Aug 2024 00:48:55 +0200 Subject: [PATCH 0163/2098] py/misc: Add a popcount(uint32_t) implementation. This makes the existing popcount(uint32_t) implementation found in the RV32 emitter available to the rest of the codebase. This version of popcount will use intrinsic or builtin implementations if they are available, falling back to a generic implementation if that is not the case. Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 24 ++---------------------- py/misc.h | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 3f3395842ef..6aa3ec16541 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -29,6 +29,7 @@ #include #include "py/emit.h" +#include "py/misc.h" #include "py/mpconfig.h" // wrapper around everything in this file @@ -43,27 +44,6 @@ #define DEBUG_printf(...) (void)0 #endif -#ifndef MP_POPCOUNT -#ifdef _MSC_VER -#include -#define MP_POPCOUNT __popcnt -#else -#if defined __has_builtin -#if __has_builtin(__builtin_popcount) -#define MP_POPCOUNT __builtin_popcount -#endif -#else -static uint32_t fallback_popcount(uint32_t value) { - value = value - ((value >> 1) & 0x55555555); - value = (value & 0x33333333) + ((value >> 2) & 0x33333333); - value = (value + (value >> 4)) & 0x0F0F0F0F; - return value * 0x01010101; -} -#define MP_POPCOUNT fallback_popcount -#endif -#endif -#endif - #define INTERNAL_TEMPORARY ASM_RV32_REG_S0 #define AVAILABLE_REGISTERS_COUNT 32 @@ -249,7 +229,7 @@ static void adjust_stack(asm_rv32_t *state, mp_int_t stack_size) { // stack to hold all the tainted registers and an arbitrary amount of space // for locals. static void emit_function_prologue(asm_rv32_t *state, mp_uint_t registers) { - mp_uint_t registers_count = MP_POPCOUNT(registers); + mp_uint_t registers_count = mp_popcount(registers); state->stack_size = (registers_count + state->locals_count) * sizeof(uint32_t); mp_uint_t old_saved_registers_mask = state->saved_registers_mask; // Move stack pointer up. diff --git a/py/misc.h b/py/misc.h index 2629d0c46cc..e05fbe61a9d 100644 --- a/py/misc.h +++ b/py/misc.h @@ -370,12 +370,29 @@ static inline uint32_t mp_ctz(uint32_t x) { static inline bool mp_check(bool value) { return value; } + +static inline uint32_t mp_popcount(uint32_t x) { + return __popcnt(x); +} #else #define mp_clz(x) __builtin_clz(x) #define mp_clzl(x) __builtin_clzl(x) #define mp_clzll(x) __builtin_clzll(x) #define mp_ctz(x) __builtin_ctz(x) #define mp_check(x) (x) +#if defined __has_builtin +#if __has_builtin(__builtin_popcount) +#define mp_popcount(x) __builtin_popcount(x) +#endif +#endif +#if !defined(mp_popcount) +static inline uint32_t mp_popcount(uint32_t x) { + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + return x * 0x01010101; +} +#endif #endif // mp_int_t can be larger than long, i.e. Windows 64-bit, nan-box variants From 268acb714dd79fa5eeeb82c1fca022bc4ea126b7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 25 Aug 2024 16:28:35 +0200 Subject: [PATCH 0164/2098] py/emitinlinerv32: Add inline assembler support for RV32. This commit adds support for writing inline assembler functions when targeting a RV32IMC processor. Given that this takes up a bit of rodata space due to its large instruction decoding table and its extensive error messages, it is enabled by default only on offline targets such as mpy-cross and the qemu port. Signed-off-by: Alessandro Gatti --- mpy-cross/mpconfigport.h | 1 + ports/qemu/Makefile | 4 +- ports/qemu/boards/MICROBIT.mk | 3 + ports/qemu/boards/MPS2_AN385.mk | 3 + ports/qemu/boards/NETDUINO2.mk | 3 + ports/qemu/boards/SABRELITE.mk | 2 +- ports/qemu/boards/VIRT_RV32.mk | 2 +- ports/qemu/mpconfigport.h | 1 + ports/qemu/test-frzmpy/frozen_asm_rv32.py | 31 + py/asmrv32.c | 34 +- py/asmrv32.h | 302 +++++++- py/compile.c | 6 + py/emit.h | 3 + py/emitinlinerv32.c | 818 ++++++++++++++++++++++ py/mpconfig.h | 7 +- py/py.cmake | 1 + py/py.mk | 1 + tests/inlineasm/rv32/asmargs.py | 44 ++ tests/inlineasm/rv32/asmargs.py.exp | 5 + tests/inlineasm/rv32/asmarith.py | 79 +++ tests/inlineasm/rv32/asmarith.py.exp | 14 + tests/inlineasm/rv32/asmbranch.py | 161 +++++ tests/inlineasm/rv32/asmbranch.py.exp | 32 + tests/inlineasm/rv32/asmconst.py | 49 ++ tests/inlineasm/rv32/asmconst.py.exp | 5 + tests/inlineasm/rv32/asmcsr.py | 65 ++ tests/inlineasm/rv32/asmcsr.py.exp | 2 + tests/inlineasm/rv32/asmdata.py | 33 + tests/inlineasm/rv32/asmdata.py.exp | 10 + tests/inlineasm/rv32/asmdivmul.py | 63 ++ tests/inlineasm/rv32/asmdivmul.py.exp | 18 + tests/inlineasm/rv32/asmjump.py | 115 +++ tests/inlineasm/rv32/asmjump.py.exp | 6 + tests/inlineasm/rv32/asmloadstore.py | 86 +++ tests/inlineasm/rv32/asmloadstore.py.exp | 3 + tests/inlineasm/rv32/asmrettype.py | 33 + tests/inlineasm/rv32/asmrettype.py.exp | 4 + tests/inlineasm/rv32/asmsanity.py | 204 ++++++ tests/inlineasm/rv32/asmsanity.py.exp | 162 +++++ tests/inlineasm/rv32/asmshift.py | 121 ++++ tests/inlineasm/rv32/asmshift.py.exp | 15 + tests/inlineasm/rv32/asmstack.py | 65 ++ tests/inlineasm/rv32/asmstack.py.exp | 3 + tests/inlineasm/rv32/asmsum.py | 59 ++ tests/inlineasm/rv32/asmsum.py.exp | 3 + tests/ports/qemu/asm_test.py | 8 +- tests/run-tests.py | 5 +- 47 files changed, 2649 insertions(+), 45 deletions(-) create mode 100644 ports/qemu/test-frzmpy/frozen_asm_rv32.py create mode 100644 py/emitinlinerv32.c create mode 100644 tests/inlineasm/rv32/asmargs.py create mode 100644 tests/inlineasm/rv32/asmargs.py.exp create mode 100644 tests/inlineasm/rv32/asmarith.py create mode 100644 tests/inlineasm/rv32/asmarith.py.exp create mode 100644 tests/inlineasm/rv32/asmbranch.py create mode 100644 tests/inlineasm/rv32/asmbranch.py.exp create mode 100644 tests/inlineasm/rv32/asmconst.py create mode 100644 tests/inlineasm/rv32/asmconst.py.exp create mode 100644 tests/inlineasm/rv32/asmcsr.py create mode 100644 tests/inlineasm/rv32/asmcsr.py.exp create mode 100644 tests/inlineasm/rv32/asmdata.py create mode 100644 tests/inlineasm/rv32/asmdata.py.exp create mode 100644 tests/inlineasm/rv32/asmdivmul.py create mode 100644 tests/inlineasm/rv32/asmdivmul.py.exp create mode 100644 tests/inlineasm/rv32/asmjump.py create mode 100644 tests/inlineasm/rv32/asmjump.py.exp create mode 100644 tests/inlineasm/rv32/asmloadstore.py create mode 100644 tests/inlineasm/rv32/asmloadstore.py.exp create mode 100644 tests/inlineasm/rv32/asmrettype.py create mode 100644 tests/inlineasm/rv32/asmrettype.py.exp create mode 100644 tests/inlineasm/rv32/asmsanity.py create mode 100644 tests/inlineasm/rv32/asmsanity.py.exp create mode 100644 tests/inlineasm/rv32/asmshift.py create mode 100644 tests/inlineasm/rv32/asmshift.py.exp create mode 100644 tests/inlineasm/rv32/asmstack.py create mode 100644 tests/inlineasm/rv32/asmstack.py.exp create mode 100644 tests/inlineasm/rv32/asmsum.py create mode 100644 tests/inlineasm/rv32/asmsum.py.exp diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 8723472eb71..d3805f03073 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -47,6 +47,7 @@ #define MICROPY_EMIT_INLINE_XTENSA (1) #define MICROPY_EMIT_XTENSAWIN (1) #define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_INLINE_RV32 (1) #define MICROPY_EMIT_NATIVE_DEBUG (1) #define MICROPY_EMIT_NATIVE_DEBUG_PRINTER (&mp_stdout_print) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index b85ff2896a1..ea0bef81486 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -19,10 +19,10 @@ QSTR_DEFS = qstrdefsport.h MICROPY_ROM_TEXT_COMPRESSION ?= 1 ifeq ($(QEMU_ARCH),arm) -FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy')" +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_thumb.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif ifeq ($(QEMU_ARCH),riscv32) -FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif # include py core make definitions diff --git a/ports/qemu/boards/MICROBIT.mk b/ports/qemu/boards/MICROBIT.mk index 02eb0576c34..d6cfd7e2243 100644 --- a/ports/qemu/boards/MICROBIT.mk +++ b/ports/qemu/boards/MICROBIT.mk @@ -11,3 +11,6 @@ LDSCRIPT = mcu/arm/nrf51.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb1.o MPY_CROSS_FLAGS += -march=armv7m + +# These RV32 tests don't run on Thumb, so exclude them. +RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/MPS2_AN385.mk b/ports/qemu/boards/MPS2_AN385.mk index 182d076eb35..8d5c27df57c 100644 --- a/ports/qemu/boards/MPS2_AN385.mk +++ b/ports/qemu/boards/MPS2_AN385.mk @@ -10,3 +10,6 @@ LDSCRIPT = mcu/arm/mps2.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m + +# These RV32 tests don't run on Thumb, so exclude them. +RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/NETDUINO2.mk b/ports/qemu/boards/NETDUINO2.mk index ffa781f3399..f88ea32e7e5 100644 --- a/ports/qemu/boards/NETDUINO2.mk +++ b/ports/qemu/boards/NETDUINO2.mk @@ -10,3 +10,6 @@ LDSCRIPT = mcu/arm/stm32.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m + +# These RV32 tests don't run on Thumb, so exclude them. +RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE.mk index 839b3d6ac4f..d3f4e14d480 100644 --- a/ports/qemu/boards/SABRELITE.mk +++ b/ports/qemu/boards/SABRELITE.mk @@ -16,4 +16,4 @@ SRC_BOARD_O = shared/runtime/gchelper_generic.o MPY_CROSS_FLAGS += -march=armv6 # These tests don't work on Cortex-A9, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/thumb/(asmdiv|asmspecialregs).py' +RUN_TESTS_ARGS = --exclude 'inlineasm/rv32|inlineasm/thumb/(asmdiv|asmspecialregs).py' diff --git a/ports/qemu/boards/VIRT_RV32.mk b/ports/qemu/boards/VIRT_RV32.mk index e166165a199..80dd5d56fdc 100644 --- a/ports/qemu/boards/VIRT_RV32.mk +++ b/ports/qemu/boards/VIRT_RV32.mk @@ -11,6 +11,6 @@ SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o MPY_CROSS_FLAGS += -march=rv32imc # These Thumb tests don't run on RV32, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/thumb|qemu/asm_test' +RUN_TESTS_ARGS = --exclude 'inlineasm/thumb' RUN_NATMODTESTS_ARGS = --arch rv32imc diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index d3e3c0a3f37..8f55d284f93 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -39,6 +39,7 @@ #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #elif defined(__riscv) #define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_INLINE_RV32 (1) #endif #define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) diff --git a/ports/qemu/test-frzmpy/frozen_asm_rv32.py b/ports/qemu/test-frzmpy/frozen_asm_rv32.py new file mode 100644 index 00000000000..ce1bd428c03 --- /dev/null +++ b/ports/qemu/test-frzmpy/frozen_asm_rv32.py @@ -0,0 +1,31 @@ +# Test freezing inline-asm code. + +# ruff: noqa: F821 - @asm_rv32 decorator adds names to function scope + +import micropython + + +@micropython.asm_rv32 +def asm_add(a0, a1): + add(a0, a0, a1) + + +@micropython.asm_rv32 +def asm_add1(a0) -> object: + slli(a0, a0, 1) + addi(a0, a0, 3) + + +@micropython.asm_rv32 +def asm_cast_bool(a0) -> bool: + pass + + +@micropython.asm_rv32 +def asm_shift_int(a0) -> int: + slli(a0, a0, 29) + + +@micropython.asm_rv32 +def asm_shift_uint(a0) -> uint: + slli(a0, a0, 29) diff --git a/py/asmrv32.c b/py/asmrv32.c index 6aa3ec16541..c24d05a1384 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -45,12 +45,6 @@ #endif #define INTERNAL_TEMPORARY ASM_RV32_REG_S0 -#define AVAILABLE_REGISTERS_COUNT 32 - -#define IS_IN_C_REGISTER_WINDOW(register_number) \ - (((register_number) >= ASM_RV32_REG_X8) && ((register_number) <= ASM_RV32_REG_X15)) -#define MAP_IN_C_REGISTER_WINDOW(register_number) \ - ((register_number) - ASM_RV32_REG_X8) #define FIT_UNSIGNED(value, bits) (((value) & ~((1U << (bits)) - 1)) == 0) #define FIT_SIGNED(value, bits) \ @@ -106,7 +100,6 @@ static void split_immediate(mp_int_t immediate, mp_uint_t *upper, mp_uint_t *low // Turn the lower half from unsigned to signed. if ((*lower & 0x800) != 0) { *upper += 0x1000; - *lower -= 0x1000; } } @@ -180,7 +173,7 @@ void asm_rv32_emit_optimised_load_immediate(asm_rv32_t *state, mp_uint_t rd, mp_ static void emit_registers_store(asm_rv32_t *state, mp_uint_t registers_mask) { mp_uint_t offset = 0; - for (mp_uint_t register_index = 0; register_index < AVAILABLE_REGISTERS_COUNT; register_index++) { + for (mp_uint_t register_index = 0; register_index < RV32_AVAILABLE_REGISTERS_COUNT; register_index++) { if (registers_mask & (1U << register_index)) { assert(FIT_UNSIGNED(offset >> 2, 6) && "Registers save stack offset out of range."); // c.swsp register, offset @@ -192,7 +185,7 @@ static void emit_registers_store(asm_rv32_t *state, mp_uint_t registers_mask) { static void emit_registers_load(asm_rv32_t *state, mp_uint_t registers_mask) { mp_uint_t offset = 0; - for (mp_uint_t register_index = 0; register_index < AVAILABLE_REGISTERS_COUNT; register_index++) { + for (mp_uint_t register_index = 0; register_index < RV32_AVAILABLE_REGISTERS_COUNT; register_index++) { if (registers_mask & (1U << register_index)) { assert(FIT_UNSIGNED(offset >> 2, 6) && "Registers load stack offset out of range."); // c.lwsp register, offset @@ -262,7 +255,7 @@ static bool calculate_displacement_for_label(asm_rv32_t *state, mp_uint_t label, void asm_rv32_entry(asm_rv32_t *state, mp_uint_t locals) { state->saved_registers_mask |= (1U << REG_FUN_TABLE) | (1U << REG_LOCAL_1) | \ - (1U << REG_LOCAL_2) | (1U << REG_LOCAL_3) | (1U << INTERNAL_TEMPORARY); + (1U << REG_LOCAL_2) | (1U << REG_LOCAL_3); state->locals_count = locals; emit_function_prologue(state, state->saved_registers_mask); } @@ -281,10 +274,11 @@ void asm_rv32_emit_call_ind(asm_rv32_t *state, mp_uint_t index) { mp_uint_t offset = index * ASM_WORD_SIZE; state->saved_registers_mask |= (1U << ASM_RV32_REG_RA); - if (IS_IN_C_REGISTER_WINDOW(REG_FUN_TABLE) && IS_IN_C_REGISTER_WINDOW(INTERNAL_TEMPORARY) && FIT_UNSIGNED(offset, 6)) { + if (RV32_IS_IN_C_REGISTER_WINDOW(REG_FUN_TABLE) && RV32_IS_IN_C_REGISTER_WINDOW(INTERNAL_TEMPORARY) && FIT_UNSIGNED(offset, 6)) { + state->saved_registers_mask |= (1U << INTERNAL_TEMPORARY); // c.lw temporary, offset(fun_table) // c.jalr temporary - asm_rv32_opcode_clw(state, MAP_IN_C_REGISTER_WINDOW(INTERNAL_TEMPORARY), MAP_IN_C_REGISTER_WINDOW(REG_FUN_TABLE), offset); + asm_rv32_opcode_clw(state, RV32_MAP_IN_C_REGISTER_WINDOW(INTERNAL_TEMPORARY), RV32_MAP_IN_C_REGISTER_WINDOW(REG_FUN_TABLE), offset); asm_rv32_opcode_cjalr(state, INTERNAL_TEMPORARY); return; } @@ -341,9 +335,9 @@ void asm_rv32_emit_jump_if_reg_nonzero(asm_rv32_t *state, mp_uint_t rs, mp_uint_ ptrdiff_t displacement = 0; bool can_emit_short_jump = calculate_displacement_for_label(state, label, &displacement); - if (can_emit_short_jump && FIT_SIGNED(displacement, 8) && IS_IN_C_REGISTER_WINDOW(rs)) { + if (can_emit_short_jump && FIT_SIGNED(displacement, 8) && RV32_IS_IN_C_REGISTER_WINDOW(rs)) { // c.bnez rs', displacement - asm_rv32_opcode_cbnez(state, MAP_IN_C_REGISTER_WINDOW(rs), displacement); + asm_rv32_opcode_cbnez(state, RV32_MAP_IN_C_REGISTER_WINDOW(rs), displacement); return; } @@ -364,8 +358,8 @@ void asm_rv32_emit_jump_if_reg_nonzero(asm_rv32_t *state, mp_uint_t rs, mp_uint_ // jalr zero, temporary, LO(displacement) ; PC + 8 // ... ; PC + 12 - if (can_emit_short_jump && IS_IN_C_REGISTER_WINDOW(rs)) { - asm_rv32_opcode_cbeqz(state, MAP_IN_C_REGISTER_WINDOW(rs), 10); + if (can_emit_short_jump && RV32_IS_IN_C_REGISTER_WINDOW(rs)) { + asm_rv32_opcode_cbeqz(state, RV32_MAP_IN_C_REGISTER_WINDOW(rs), 10); // Compensate for the C.BEQZ opcode. displacement -= ASM_HALFWORD_SIZE; } else { @@ -438,9 +432,9 @@ void asm_rv32_emit_mov_reg_local(asm_rv32_t *state, mp_uint_t rd, mp_uint_t loca void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local) { mp_uint_t offset = state->locals_stack_offset + (local * ASM_WORD_SIZE); - if (FIT_UNSIGNED(offset, 10) && offset != 0 && IS_IN_C_REGISTER_WINDOW(rd)) { + if (FIT_UNSIGNED(offset, 10) && offset != 0 && RV32_IS_IN_C_REGISTER_WINDOW(rd)) { // c.addi4spn rd', offset - asm_rv32_opcode_caddi4spn(state, MAP_IN_C_REGISTER_WINDOW(rd), offset); + asm_rv32_opcode_caddi4spn(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), offset); return; } @@ -459,9 +453,9 @@ void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { mp_int_t scaled_offset = offset * sizeof(ASM_WORD_SIZE); - if (scaled_offset >= 0 && IS_IN_C_REGISTER_WINDOW(rd) && IS_IN_C_REGISTER_WINDOW(rs) && FIT_UNSIGNED(scaled_offset, 6)) { + if (scaled_offset >= 0 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs) && FIT_UNSIGNED(scaled_offset, 6)) { // c.lw rd', offset(rs') - asm_rv32_opcode_clw(state, MAP_IN_C_REGISTER_WINDOW(rd), MAP_IN_C_REGISTER_WINDOW(rs), scaled_offset); + asm_rv32_opcode_clw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs), scaled_offset); return; } diff --git a/py/asmrv32.h b/py/asmrv32.h index 775cf1ffc96..584e3ffd224 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -74,9 +74,6 @@ #define ASM_RV32_REG_SP (ASM_RV32_REG_X2) #define ASM_RV32_REG_GP (ASM_RV32_REG_X3) #define ASM_RV32_REG_TP (ASM_RV32_REG_X4) -#define ASM_RV32_REG_T0 (ASM_RV32_REG_X5) -#define ASM_RV32_REG_T1 (ASM_RV32_REG_X6) -#define ASM_RV32_REG_T2 (ASM_RV32_REG_X7) #define ASM_RV32_REG_A0 (ASM_RV32_REG_X10) #define ASM_RV32_REG_A1 (ASM_RV32_REG_X11) #define ASM_RV32_REG_A2 (ASM_RV32_REG_X12) @@ -85,6 +82,9 @@ #define ASM_RV32_REG_A5 (ASM_RV32_REG_X15) #define ASM_RV32_REG_A6 (ASM_RV32_REG_X16) #define ASM_RV32_REG_A7 (ASM_RV32_REG_X17) +#define ASM_RV32_REG_T0 (ASM_RV32_REG_X5) +#define ASM_RV32_REG_T1 (ASM_RV32_REG_X6) +#define ASM_RV32_REG_T2 (ASM_RV32_REG_X7) #define ASM_RV32_REG_T3 (ASM_RV32_REG_X28) #define ASM_RV32_REG_T4 (ASM_RV32_REG_X29) #define ASM_RV32_REG_T5 (ASM_RV32_REG_X30) @@ -103,6 +103,12 @@ #define ASM_RV32_REG_S10 (ASM_RV32_REG_X26) #define ASM_RV32_REG_S11 (ASM_RV32_REG_X27) +#define RV32_AVAILABLE_REGISTERS_COUNT 32 +#define RV32_MAP_IN_C_REGISTER_WINDOW(register_number) \ + ((register_number) - ASM_RV32_REG_X8) +#define RV32_IS_IN_C_REGISTER_WINDOW(register_number) \ + (((register_number) >= ASM_RV32_REG_X8) && ((register_number) <= ASM_RV32_REG_X15)) + typedef struct _asm_rv32_t { // Opaque emitter state. mp_asm_base_t base; @@ -127,6 +133,10 @@ void asm_rv32_end_pass(asm_rv32_t *state); ((imm & 0x1E) << 7) | ((rs1 & 0x1F) << 15) | ((rs2 & 0x1F) << 20) | \ ((imm & 0x7E0) << 20) | ((imm & 0x1000) << 19)) +#define RV32_ENCODE_TYPE_CSRI(op, ft3, rd, csr, imm) \ + ((op & 0x7F) | ((rd & 0x1F) << 7) | ((ft3 & 0x07) << 12) | \ + ((csr & 0xFFF) << 20) | ((imm & 0x1F) << 15)) + #define RV32_ENCODE_TYPE_I(op, ft3, rd, rs, imm) \ ((op & 0x7F) | ((rd & 0x1F) << 7) | ((ft3 & 0x07) << 12) | \ ((rs & 0x1F) << 15) | ((imm & 0xFFF) << 20)) @@ -143,13 +153,16 @@ void asm_rv32_end_pass(asm_rv32_t *state); ((op & 0x7F) | ((imm & 0x1F) << 7) | ((ft3 & 0x07) << 12) | \ ((rs1 & 0x1F) << 15) | ((rs2 & 0x1F) << 20) | ((imm & 0xFE0) << 20)) +#define RV32_ENCODE_TYPE_CA(op, ft6, ft2, rd, rs) \ + ((op & 0x03) | ((ft6 & 0x3F) << 10) | ((ft2 & 0x03) << 5) | \ + ((rd & 0x03) << 7) | ((rs & 0x03) << 2)) + #define RV32_ENCODE_TYPE_U(op, rd, imm) \ ((op & 0x7F) | ((rd & 0x1F) << 7) | (imm & 0xFFFFF000)) #define RV32_ENCODE_TYPE_CB(op, ft3, rs, imm) \ ((op & 0x03) | ((ft3 & 0x07) << 13) | ((rs & 0x07) << 7) | \ - (((imm) & 0x100) << 4) | (((imm) & 0xC0) >> 1) | (((imm) & 0x20) >> 3) | \ - (((imm) & 0x18) << 7) | (((imm) & 0x06) << 2)) + (((imm) & 0xE0) << 5) | (((imm) & 0x1F) << 2)) #define RV32_ENCODE_TYPE_CI(op, ft3, rd, imm) \ ((op & 0x03) | ((ft3 & 0x07) << 13) | ((rd & 0x1F) << 7) | \ @@ -174,6 +187,11 @@ void asm_rv32_end_pass(asm_rv32_t *state); #define RV32_ENCODE_TYPE_CR(op, ft4, rs1, rs2) \ ((op & 0x03) | ((rs2 & 0x1F) << 2) | ((rs1 & 0x1F) << 7) | ((ft4 & 0x0F) << 12)) +#define RV32_ENCODE_TYPE_CS(op, ft3, rd, rs, imm) \ + ((op & 0x03) | ((ft3 & 0x07) << 13) | ((rd & 0x07) << 2) | \ + ((rs & 0x07) << 7) | ((imm & 0x40) >> 1) | ((imm & 0x38) << 7) | \ + ((imm & 0x04) << 4)) + #define RV32_ENCODE_TYPE_CSS(op, ft3, rs, imm) \ ((op & 0x03) | ((ft3 & 0x07) << 13) | ((rs & 0x1F) << 2) | ((imm) & 0x3F) << 7) @@ -198,6 +216,12 @@ static inline void asm_rv32_opcode_and(asm_rv32_t *state, mp_uint_t rd, mp_uint_ asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x07, 0x00, rd, rs1, rs2)); } +// ANDI RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_andi(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 111 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x07, rd, rs, immediate)); +} + // AUIPC RD, offset static inline void asm_rv32_opcode_auipc(asm_rv32_t *state, mp_uint_t rd, mp_int_t offset) { // U: .................... ..... 0010111 @@ -210,6 +234,30 @@ static inline void asm_rv32_opcode_beq(asm_rv32_t *state, mp_uint_t rs1, mp_uint asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_B(0x63, 0x00, rs1, rs2, offset)); } +// BGE RS1, RS2, OFFSET +static inline void asm_rv32_opcode_bge(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { + // B: . ...... ..... ..... 101 .... . 1100011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_B(0x63, 0x05, rs1, rs2, offset)); +} + +// BGEU RS1, RS2, OFFSET +static inline void asm_rv32_opcode_bgeu(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { + // B: . ...... ..... ..... 111 .... . 1100011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_B(0x63, 0x07, rs1, rs2, offset)); +} + +// BLT RS1, RS2, OFFSET +static inline void asm_rv32_opcode_blt(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { + // B: . ...... ..... ..... 100 .... . 1100011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_B(0x63, 0x04, rs1, rs2, offset)); +} + +// BLTU RS1, RS2, OFFSET +static inline void asm_rv32_opcode_bltu(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { + // B: . ...... ..... ..... 110 .... . 1100011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_B(0x63, 0x06, rs1, rs2, offset)); +} + // BNE RS1, RS2, OFFSET static inline void asm_rv32_opcode_bne(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { // B: . ...... ..... ..... 001 .... . 1100011 @@ -234,16 +282,39 @@ static inline void asm_rv32_opcode_caddi4spn(asm_rv32_t *state, mp_uint_t rd, mp asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CIW(0x00, 0x00, rd, immediate)); } +// C.AND RD', RS' +static inline void asm_rv32_opcode_cand(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) { + // CA: 100011 ... 11 ... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x03, rd, rs)); +} + +// C.ANDI RD', IMMEDIATE +static inline void asm_rv32_opcode_candi(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate) { + // CB: 100 . 10 ... ..... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x04, rd, + (((immediate & 0x20) << 2) | (immediate & 0x1F) | 0x40))); +} + // C.BEQZ RS', IMMEDIATE static inline void asm_rv32_opcode_cbeqz(asm_rv32_t *state, mp_uint_t rs, mp_int_t offset) { // CB: 110 ... ... ..... 01 - asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x06, rs, offset)); + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x06, rs, + (((offset & 0x100) >> 1) | ((offset & 0xC0) >> 3) | ((offset & 0x20) >> 5) | + ((offset & 0x18) << 2) | (offset & 0x06)))); } // C.BNEZ RS', IMMEDIATE static inline void asm_rv32_opcode_cbnez(asm_rv32_t *state, mp_uint_t rs, mp_int_t offset) { // CB: 111 ... ... ..... 01 - asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x07, rs, offset)); + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x07, rs, + (((offset & 0x100) >> 1) | ((offset & 0xC0) >> 3) | ((offset & 0x20) >> 5) | + ((offset & 0x18) << 2) | (offset & 0x06)))); +} + +// C.EBREAK +static inline void asm_rv32_opcode_cebreak(asm_rv32_t *state) { + // CA: 100 1 00000 00000 10 + asm_rv32_emit_halfword_opcode(state, 0x9002); } // C.J OFFSET @@ -252,6 +323,12 @@ static inline void asm_rv32_opcode_cj(asm_rv32_t *state, mp_int_t offset) { asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CJ(0x01, 0x05, offset)); } +// C.JAL OFFSET +static inline void asm_rv32_opcode_cjal(asm_rv32_t *state, mp_int_t offset) { + // CJ: 001 ........... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CJ(0x01, 0x01, offset)); +} + // C.JALR RS static inline void asm_rv32_opcode_cjalr(asm_rv32_t *state, mp_uint_t rs) { // CR: 1001 ..... 00000 10 @@ -294,31 +371,159 @@ static inline void asm_rv32_opcode_cmv(asm_rv32_t *state, mp_uint_t rd, mp_uint_ asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CR(0x02, 0x08, rd, rs)); } +// C.NOP +static inline void asm_rv32_opcode_cnop(asm_rv32_t *state) { + // CI: 000 . 00000 ..... 01 + asm_rv32_emit_halfword_opcode(state, 0x0001); +} + +// C.OR RD', RS' +static inline void asm_rv32_opcode_cor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) { + // CA: 100011 ... 10 ... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x02, rd, rs)); +} + +// C.SLLI RD, IMMEDIATE +static inline void asm_rv32_opcode_cslli(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate) { + // CI: 000 . ..... ..... 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CI(0x02, 0x00, rd, immediate)); +} + +// C.SRAI RD, IMMEDIATE +static inline void asm_rv32_opcode_csrai(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate) { + // CB: 100 . 01 ... ..... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x04, rd, + (((immediate & 0x20) << 2) | (immediate & 0x1F) | 0x20))); +} + +// C.SRLI RD, IMMEDIATE +static inline void asm_rv32_opcode_csrli(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate) { + // CB: 100 . 00 ... ..... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CB(0x01, 0x04, rd, + (((immediate & 0x20) << 2) | (immediate & 0x1F)))); +} + +// C.SUB RD', RS' +static inline void asm_rv32_opcode_csub(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) { + // CA: 100011 ... 00 ... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x00, rd, rs)); +} + +// C.SW RS1', OFFSET(RS2') +static inline void asm_rv32_opcode_csw(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_int_t offset) { + // CS: 110 ... ... .. ... 00 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CL(0x00, 0x06, rs1, rs2, offset)); +} + // C.SWSP RS, OFFSET static inline void asm_rv32_opcode_cswsp(asm_rv32_t *state, mp_uint_t rs, mp_uint_t offset) { // CSS: 010 ...... ..... 10 asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CSS(0x02, 0x06, rs, ((offset & 0xC0) >> 6) | (offset & 0x3C))); } -// JALR RD, RS, offset +// C.XOR RD', RS' +static inline void asm_rv32_opcode_cxor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) { + // CA: 100011 ... 01 ... 01 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x01, rd, rs)); +} + +// CSRRC RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_csrrc(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 011 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x73, 0x03, rd, rs, immediate)); +} + +// CSRRS RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_csrrs(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 010 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x73, 0x02, rd, rs, immediate)); +} + +// CSRRW RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_csrrw(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 001 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x73, 0x01, rd, rs, immediate)); +} + +// CSRRCI RD, CSR, IMMEDIATE +static inline void asm_rv32_opcode_csrrci(asm_rv32_t *state, mp_uint_t rd, mp_uint_t csr, mp_int_t immediate) { + // CSRI: ............ ..... 111 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_CSRI(0x73, 0x07, rd, csr, immediate)); +} + +// CSRRSI RD, CSR, IMMEDIATE +static inline void asm_rv32_opcode_csrrsi(asm_rv32_t *state, mp_uint_t rd, mp_uint_t csr, mp_int_t immediate) { + // CSRI: ............ ..... 110 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_CSRI(0x73, 0x06, rd, csr, immediate)); +} + +// CSRRWI RD, CSR, IMMEDIATE +static inline void asm_rv32_opcode_csrrwi(asm_rv32_t *state, mp_uint_t rd, mp_uint_t csr, mp_int_t immediate) { + // CSRI: ............ ..... 101 ..... 1110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_CSRI(0x73, 0x05, rd, csr, immediate)); +} + +// DIV RD, RS1, RS2 +static inline void asm_rv32_opcode_div(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 100 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x04, 0x01, rd, rs1, rs2)); +} + +// DIVU RD, RS1, RS2 +static inline void asm_rv32_opcode_divu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 101 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x05, 0x01, rd, rs1, rs2)); +} + +// EBREAK +static inline void asm_rv32_opcode_ebreak(asm_rv32_t *state) { + // I: 000000000001 00000 000 00000 1110011 + asm_rv32_emit_word_opcode(state, 0x100073); +} + +// ECALL +static inline void asm_rv32_opcode_ecall(asm_rv32_t *state) { + // I: 000000000000 00000 000 00000 1110011 + asm_rv32_emit_word_opcode(state, 0x73); +} + +// JAL RD, OFFSET +static inline void asm_rv32_opcode_jal(asm_rv32_t *state, mp_uint_t rd, mp_int_t offset) { + // J: ......................... 1101111 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_J(0x6F, rd, offset)); +} + +// JALR RD, RS, OFFSET static inline void asm_rv32_opcode_jalr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { // I: ............ ..... 000 ..... 1100111 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x67, 0x00, rd, rs, offset)); } +// LB RD, OFFSET(RS) +static inline void asm_rv32_opcode_lb(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { + // I: ............ ..... 000 ..... 0000011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, 0x00, rd, rs, offset)); +} + // LBU RD, OFFSET(RS) static inline void asm_rv32_opcode_lbu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { // I: ............ ..... 100 ..... 0000011 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, 0x04, rd, rs, offset)); } +// LH RD, OFFSET(RS) +static inline void asm_rv32_opcode_lh(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { + // I: ............ ..... 001 ..... 0000011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, 0x01, rd, rs, offset)); +} + // LHU RD, OFFSET(RS) static inline void asm_rv32_opcode_lhu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { // I: ............ ..... 101 ..... 0000011 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, 0x05, rd, rs, offset)); } -// LUI RD, immediate +// LUI RD, IMMEDIATE static inline void asm_rv32_opcode_lui(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate) { // U: .................... ..... 0110111 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_U(0x37, rd, immediate)); @@ -336,12 +541,48 @@ static inline void asm_rv32_opcode_mul(asm_rv32_t *state, mp_uint_t rd, mp_uint_ asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x00, 0x01, rd, rs1, rs2)); } +// MULH RD, RS1, RS2 +static inline void asm_rv32_opcode_mulh(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 001 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x01, 0x01, rd, rs1, rs2)); +} + +// MULHSU RD, RS1, RS2 +static inline void asm_rv32_opcode_mulhsu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 010 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x02, 0x01, rd, rs1, rs2)); +} + +// MULHU RD, RS1, RS2 +static inline void asm_rv32_opcode_mulhu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 011 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x03, 0x01, rd, rs1, rs2)); +} + // OR RD, RS1, RS2 static inline void asm_rv32_opcode_or(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0000000 ..... ..... 110 ..... 0110011 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x06, 0x00, rd, rs1, rs2)); } +// ORI RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_ori(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 110 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x06, rd, rs, immediate)); +} + +// REM RD, RS1, RS2 +static inline void asm_rv32_opcode_rem(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 110 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x06, 0x01, rd, rs1, rs2)); +} + +// REMU RD, RS1, RS2 +static inline void asm_rv32_opcode_remu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000001 ..... ..... 111 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x07, 0x01, rd, rs1, rs2)); +} + // SLL RD, RS1, RS2 static inline void asm_rv32_opcode_sll(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0000000 ..... ..... 001 ..... 0110011 @@ -349,23 +590,29 @@ static inline void asm_rv32_opcode_sll(asm_rv32_t *state, mp_uint_t rd, mp_uint_ } // SLLI RD, RS, IMMEDIATE -static inline void asm_rv32_opcode_slli(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_uint_t immediate) { +static inline void asm_rv32_opcode_slli(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { // I: 0000000..... ..... 001 ..... 0010011 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x01, rd, rs, immediate & 0x1F)); } -// SRL RD, RS1, RS2 -static inline void asm_rv32_opcode_srl(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { - // R: 0000000 ..... ..... 101 ..... 0110011 - asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x05, 0x00, rd, rs1, rs2)); -} - // SLT RD, RS1, RS2 static inline void asm_rv32_opcode_slt(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0000000 ..... ..... 010 ..... 0110011 asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x02, 0x00, rd, rs1, rs2)); } +// SLTI RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_slti(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 010 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x02, rd, rs, immediate)); +} + +// SLTIU RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_sltiu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: ............ ..... 011 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x03, rd, rs, immediate)); +} + // SLTU RD, RS1, RS2 static inline void asm_rv32_opcode_sltu(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0000000 ..... ..... 011 ..... 0110011 @@ -378,6 +625,24 @@ static inline void asm_rv32_opcode_sra(asm_rv32_t *state, mp_uint_t rd, mp_uint_ asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x05, 0x20, rd, rs1, rs2)); } +// SRAI RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_srai(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: 0100000..... ..... 101 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x05, rd, rs, ((immediate & 0x1F) | 0x400))); +} + +// SRL RD, RS1, RS2 +static inline void asm_rv32_opcode_srl(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0000000 ..... ..... 101 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x05, 0x00, rd, rs1, rs2)); +} + +// SRLI RD, RS, IMMEDIATE +static inline void asm_rv32_opcode_srli(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { + // I: 0000000..... ..... 101 ..... 0010011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x05, rd, rs, immediate & 0x1F)); +} + // SUB RD, RS1, RS2 static inline void asm_rv32_opcode_sub(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0100000 ..... ..... 000 ..... 0110011 @@ -435,6 +700,8 @@ void asm_rv32_meta_comparison_ne(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 void asm_rv32_meta_comparison_lt(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison); void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison); +void asm_rv32_emit_optimised_load_immediate(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); + #ifdef GENERIC_ASM_API void asm_rv32_emit_call_ind(asm_rv32_t *state, mp_uint_t index); @@ -444,10 +711,9 @@ void asm_rv32_emit_jump_if_reg_nonzero(asm_rv32_t *state, mp_uint_t rs, mp_uint_ void asm_rv32_emit_load16_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset); void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset); void asm_rv32_emit_mov_local_reg(asm_rv32_t *state, mp_uint_t local, mp_uint_t rs); -void asm_rv32_emit_mov_reg_local(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local); void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local); +void asm_rv32_emit_mov_reg_local(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local); void asm_rv32_emit_mov_reg_pcrel(asm_rv32_t *state, mp_uint_t rd, mp_uint_t label); -void asm_rv32_emit_optimised_load_immediate(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs); void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_uint_t base, mp_int_t offset); diff --git a/py/compile.c b/py/compile.c index d2af6aaf2cb..60f06d7773e 100644 --- a/py/compile.c +++ b/py/compile.c @@ -147,6 +147,7 @@ static const emit_inline_asm_method_table_t *emit_asm_table[] = { &emit_inline_thumb_method_table, &emit_inline_xtensa_method_table, NULL, + &emit_inline_rv32_method_table, }; #elif MICROPY_EMIT_INLINE_ASM @@ -157,6 +158,9 @@ static const emit_inline_asm_method_table_t *emit_asm_table[] = { #elif MICROPY_EMIT_INLINE_XTENSA #define ASM_DECORATOR_QSTR MP_QSTR_asm_xtensa #define ASM_EMITTER(f) emit_inline_xtensa_##f +#elif MICROPY_EMIT_INLINE_RV32 +#define ASM_DECORATOR_QSTR MP_QSTR_asm_rv32 +#define ASM_EMITTER(f) emit_inline_rv32_##f #else #error "unknown asm emitter" #endif @@ -855,6 +859,8 @@ static bool compile_built_in_decorator(compiler_t *comp, size_t name_len, mp_par *emit_options = MP_EMIT_OPT_ASM; } else if (attr == MP_QSTR_asm_xtensa) { *emit_options = MP_EMIT_OPT_ASM; + } else if (attr == MP_QSTR_asm_rv32) { + *emit_options = MP_EMIT_OPT_ASM; #else } else if (attr == ASM_DECORATOR_QSTR) { *emit_options = MP_EMIT_OPT_ASM; diff --git a/py/emit.h b/py/emit.h index 623b1634901..033ac9c763b 100644 --- a/py/emit.h +++ b/py/emit.h @@ -307,12 +307,15 @@ typedef struct _emit_inline_asm_method_table_t { void (*op)(emit_inline_asm_t *emit, qstr op, mp_uint_t n_args, mp_parse_node_t *pn_args); } emit_inline_asm_method_table_t; +extern const emit_inline_asm_method_table_t emit_inline_rv32_method_table; extern const emit_inline_asm_method_table_t emit_inline_thumb_method_table; extern const emit_inline_asm_method_table_t emit_inline_xtensa_method_table; +emit_inline_asm_t *emit_inline_rv32_new(mp_uint_t max_num_labels); emit_inline_asm_t *emit_inline_thumb_new(mp_uint_t max_num_labels); emit_inline_asm_t *emit_inline_xtensa_new(mp_uint_t max_num_labels); +void emit_inline_rv32_free(emit_inline_asm_t *emit); void emit_inline_thumb_free(emit_inline_asm_t *emit); void emit_inline_xtensa_free(emit_inline_asm_t *emit); diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c new file mode 100644 index 00000000000..179e289c67b --- /dev/null +++ b/py/emitinlinerv32.c @@ -0,0 +1,818 @@ +/* + * This file is part of the MicroPython project, https://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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 +#include +#include +#include + +#include "py/asmrv32.h" +#include "py/emit.h" +#include "py/misc.h" + +#if MICROPY_EMIT_INLINE_RV32 + +typedef enum { +// define rules with a compile function +#define DEF_RULE(rule, comp, kind, ...) PN_##rule, +#define DEF_RULE_NC(rule, kind, ...) + #include "py/grammar.h" +#undef DEF_RULE +#undef DEF_RULE_NC + PN_const_object, // special node for a constant, generic Python object +// define rules without a compile function +#define DEF_RULE(rule, comp, kind, ...) +#define DEF_RULE_NC(rule, kind, ...) PN_##rule, + #include "py/grammar.h" +#undef DEF_RULE +#undef DEF_RULE_NC +} pn_kind_t; + +struct _emit_inline_asm_t { + asm_rv32_t as; + uint16_t pass; + mp_obj_t *error_slot; + mp_uint_t max_num_labels; + qstr *label_lookup; +}; + +static const qstr REGISTERS_QSTR_TABLE[] = { + MP_QSTR_zero, MP_QSTR_ra, MP_QSTR_sp, MP_QSTR_gp, MP_QSTR_tp, MP_QSTR_t0, MP_QSTR_t1, MP_QSTR_t2, + MP_QSTR_s0, MP_QSTR_s1, MP_QSTR_a0, MP_QSTR_a1, MP_QSTR_a2, MP_QSTR_a3, MP_QSTR_a4, MP_QSTR_a5, + MP_QSTR_a6, MP_QSTR_a7, MP_QSTR_s2, MP_QSTR_s3, MP_QSTR_s4, MP_QSTR_s5, MP_QSTR_s6, MP_QSTR_s7, + MP_QSTR_s8, MP_QSTR_s9, MP_QSTR_s10, MP_QSTR_s11, MP_QSTR_t3, MP_QSTR_t4, MP_QSTR_t5, MP_QSTR_t6, + MP_QSTR_x0, MP_QSTR_x1, MP_QSTR_x2, MP_QSTR_x3, MP_QSTR_x4, MP_QSTR_x5, MP_QSTR_x6, MP_QSTR_x7, + MP_QSTR_x8, MP_QSTR_x9, MP_QSTR_x10, MP_QSTR_x11, MP_QSTR_x12, MP_QSTR_x13, MP_QSTR_x14, MP_QSTR_x15, + MP_QSTR_x16, MP_QSTR_x17, MP_QSTR_x18, MP_QSTR_x19, MP_QSTR_x20, MP_QSTR_x21, MP_QSTR_x22, MP_QSTR_x23, + MP_QSTR_x24, MP_QSTR_x25, MP_QSTR_x26, MP_QSTR_x27, MP_QSTR_x28, MP_QSTR_x29, MP_QSTR_x30, MP_QSTR_x31, +}; + +//////////////////////////////////////////////////////////////////////////////// + +static inline void emit_inline_rv32_error_msg(emit_inline_asm_t *emit, mp_rom_error_text_t msg) { + *emit->error_slot = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg); +} + +static inline void emit_inline_rv32_error_exc(emit_inline_asm_t *emit, mp_obj_t exc) { + *emit->error_slot = exc; +} + +emit_inline_asm_t *emit_inline_rv32_new(mp_uint_t max_num_labels) { + emit_inline_asm_t *emit = m_new_obj(emit_inline_asm_t); + memset(&emit->as, 0, sizeof(emit->as)); + mp_asm_base_init(&emit->as.base, max_num_labels); + emit->max_num_labels = max_num_labels; + emit->label_lookup = m_new(qstr, max_num_labels); + return emit; +} + +void emit_inline_rv32_free(emit_inline_asm_t *emit) { + m_del(qstr, emit->label_lookup, emit->max_num_labels); + mp_asm_base_deinit(&emit->as.base, false); + m_del_obj(emit_inline_asm_t, emit); +} + +static void emit_inline_rv32_start_pass(emit_inline_asm_t *emit, pass_kind_t pass, mp_obj_t *error_slot) { + emit->pass = pass; + emit->error_slot = error_slot; + if (emit->pass == MP_PASS_CODE_SIZE) { + memset(emit->label_lookup, 0, emit->max_num_labels * sizeof(qstr)); + } + mp_asm_base_start_pass(&emit->as.base, pass == MP_PASS_EMIT ? MP_ASM_PASS_EMIT : MP_ASM_PASS_COMPUTE); +} + +static void emit_inline_rv32_end_pass(emit_inline_asm_t *emit, mp_uint_t type_sig) { + // c.jr ra + asm_rv32_opcode_cjr(&emit->as, ASM_RV32_REG_RA); + asm_rv32_end_pass(&emit->as); +} + +static bool parse_register_node(mp_parse_node_t node, mp_uint_t *register_number, bool compressed) { + assert(register_number != NULL && "Register number pointer is NULL."); + + if (!MP_PARSE_NODE_IS_ID(node)) { + return false; + } + + qstr node_qstr = MP_PARSE_NODE_LEAF_ARG(node); + for (mp_uint_t index = 0; index < MP_ARRAY_SIZE(REGISTERS_QSTR_TABLE); index++) { + if (node_qstr == REGISTERS_QSTR_TABLE[index]) { + mp_uint_t number = index % RV32_AVAILABLE_REGISTERS_COUNT; + if (!compressed || (compressed && RV32_IS_IN_C_REGISTER_WINDOW(number))) { + *register_number = compressed ? RV32_MAP_IN_C_REGISTER_WINDOW(number) : number; + return true; + } + break; + } + } + + return false; +} + +static mp_uint_t lookup_label(emit_inline_asm_t *emit, mp_parse_node_t node, qstr *qstring) { + assert(qstring && "qstring pointer is NULL"); + + *qstring = MP_PARSE_NODE_LEAF_ARG(node); + for (mp_uint_t label = 0; label < emit->max_num_labels; label++) { + if (emit->label_lookup[label] == *qstring) { + return label; + } + } + + return emit->max_num_labels; +} + +static inline ptrdiff_t label_code_offset(emit_inline_asm_t *emit, mp_uint_t label_index) { + return emit->as.base.label_offsets[label_index] - emit->as.base.code_offset; +} + +static mp_uint_t emit_inline_rv32_count_params(emit_inline_asm_t *emit, mp_uint_t parameters_count, mp_parse_node_t *parameter_nodes) { + // TODO: Raise this up to 8? RV32I has 8 A-registers that are meant to + // be used for passing arguments. + + if (parameters_count > 4) { + emit_inline_rv32_error_msg(emit, MP_ERROR_TEXT("can only have up to 4 parameters for RV32 assembly")); + return 0; + } + + mp_uint_t register_index = 0; + for (mp_uint_t index = 0; index < parameters_count; index++) { + bool valid_register = parse_register_node(parameter_nodes[index], ®ister_index, false); + if (!valid_register || (register_index != (ASM_RV32_REG_A0 + index))) { + emit_inline_rv32_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence a0 to a3")); + return 0; + } + } + + return parameters_count; +} + +static bool emit_inline_rv32_label(emit_inline_asm_t *emit, mp_uint_t label_num, qstr label_id) { + assert(label_num < emit->max_num_labels); + if (emit->pass == MP_PASS_CODE_SIZE) { + for (mp_uint_t index = 0; index < emit->max_num_labels; index++) { + if (emit->label_lookup[index] == label_id) { + return false; + } + } + } + emit->label_lookup[label_num] = label_id; + mp_asm_base_label_assign(&emit->as.base, label_num); + return true; +} + +#define CALL_RRR 0 // Register, Register, Register +#define CALL_RR 1 // Register, Register +#define CALL_RRI 2 // Register, Register, Immediate +#define CALL_RRL 3 // Register, Register, Label +#define CALL_RI 4 // Register, Immediate +#define CALL_L 5 // Label +#define CALL_R 6 // Register +#define CALL_RL 7 // Register, Label +#define CALL_N 8 // No arguments +#define CALL_I 9 // Immediate +#define CALL_RII 10 // Register, Immediate, Immediate +#define CALL_RIR 11 // Register, Immediate(Register) + +#define N 0 // No argument +#define R 1 // Register +#define I 2 // Immediate +#define L 3 // Label +#define C (1 << 2) // Compressed register +#define U (1 << 2) // Unsigned immediate +#define Z (1 << 3) // Non-zero + +typedef void (*call_l_t)(asm_rv32_t *state, mp_uint_t label_index); +typedef void (*call_ri_t)(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); +typedef void (*call_rri_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_int_t immediate); +typedef void (*call_rii_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t immediate1, mp_int_t immediate2); +typedef void (*call_rrr_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2); +typedef void (*call_rr_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs); +typedef void (*call_i_t)(asm_rv32_t *state, mp_int_t immediate); +typedef void (*call_r_t)(asm_rv32_t *state, mp_uint_t rd); +typedef void (*call_n_t)(asm_rv32_t *state); + +typedef struct _opcode_t { + qstr qstring; + void *emitter; + uint32_t calling_convention : 4; + uint32_t argument1_kind : 4; + uint32_t argument1_shift : 5; + uint32_t argument2_kind : 4; + uint32_t argument2_shift : 5; + uint32_t argument3_kind : 4; + uint32_t argument3_shift : 5; + uint32_t argument1_mask; + uint32_t argument2_mask; + uint32_t argument3_mask; +} opcode_t; + +#define opcode_li asm_rv32_emit_optimised_load_immediate + +static void opcode_la(asm_rv32_t *state, mp_uint_t rd, mp_int_t displacement) { + // This cannot be optimised for size, otherwise label addresses would move around. + mp_uint_t upper = (mp_uint_t)displacement & 0xFFFFF000; + mp_uint_t lower = (mp_uint_t)displacement & 0x00000FFF; + if ((lower & 0x800) != 0) { + upper += 0x1000; + } + asm_rv32_opcode_auipc(state, rd, upper); + asm_rv32_opcode_addi(state, rd, rd, lower); +} + +#define RC (R | C) +#define IU (I | U) +#define IZ (I | Z) +#define IUZ (I | U | Z) + +static const opcode_t OPCODES[] = { + { MP_QSTR_add, asm_rv32_opcode_add, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_addi, asm_rv32_opcode_addi, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_and_, asm_rv32_opcode_and, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_andi, asm_rv32_opcode_andi, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_auipc, asm_rv32_opcode_auipc, CALL_RI, R, 0, I, 12, N, 0, 0xFFFFFFFF, 0xFFFFF000, 0x00000000 }, + { MP_QSTR_beq, asm_rv32_opcode_beq, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_bge, asm_rv32_opcode_bge, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_bgeu, asm_rv32_opcode_bgeu, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_blt, asm_rv32_opcode_blt, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_bltu, asm_rv32_opcode_bltu, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_bne, asm_rv32_opcode_bne, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, + { MP_QSTR_csrrc, asm_rv32_opcode_csrrc, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_csrrs, asm_rv32_opcode_csrrs, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_csrrw, asm_rv32_opcode_csrrw, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_csrrci, asm_rv32_opcode_csrrci, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, + { MP_QSTR_csrrsi, asm_rv32_opcode_csrrsi, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, + { MP_QSTR_csrrwi, asm_rv32_opcode_csrrwi, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, + { MP_QSTR_c_add, asm_rv32_opcode_cadd, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFE, 0xFFFFFFFE, 0x00000000 }, + { MP_QSTR_c_addi, asm_rv32_opcode_caddi, CALL_RI, R, 0, IZ, 0, N, 0, 0xFFFFFFFE, 0x0000003F, 0x00000000 }, + { MP_QSTR_c_addi4spn, asm_rv32_opcode_caddi4spn, CALL_RI, R, 0, IUZ, 0, N, 0, 0x0000FF00, 0x000003FC, 0x00000000 }, + { MP_QSTR_c_and, asm_rv32_opcode_cand, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, + { MP_QSTR_c_andi, asm_rv32_opcode_candi, CALL_RI, RC, 0, I, 0, N, 0, 0x0000FF00, 0x0000003F, 0x00000000 }, + { MP_QSTR_c_beqz, asm_rv32_opcode_cbeqz, CALL_RL, RC, 0, L, 0, N, 0, 0x0000FF00, 0x000001FE, 0x00000000 }, + { MP_QSTR_c_bnez, asm_rv32_opcode_cbnez, CALL_RL, RC, 0, L, 0, N, 0, 0x0000FF00, 0x000001FE, 0x00000000 }, + { MP_QSTR_c_ebreak, asm_rv32_opcode_cebreak, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, + { MP_QSTR_c_j, asm_rv32_opcode_cj, CALL_L, L, 0, N, 0, N, 0, 0x00000FFE, 0x00000000, 0x00000000 }, + { MP_QSTR_c_jal, asm_rv32_opcode_cjal, CALL_L, L, 0, N, 0, N, 0, 0x00000FFE, 0x00000000, 0x00000000 }, + { MP_QSTR_c_jalr, asm_rv32_opcode_cjalr, CALL_R, R, 0, N, 0, N, 0, 0xFFFFFFFE, 0x00000000, 0x00000000 }, + { MP_QSTR_c_jr, asm_rv32_opcode_cjr, CALL_R, R, 0, N, 0, N, 0, 0xFFFFFFFE, 0x00000000, 0x00000000 }, + { MP_QSTR_c_li, asm_rv32_opcode_cli, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFE, 0x0000003F, 0x00000000 }, + { MP_QSTR_c_lui, asm_rv32_opcode_clui, CALL_RI, R, 0, IUZ, 12, N, 0, 0xFFFFFFFA, 0x0001F800, 0x00000000 }, + { MP_QSTR_c_lw, asm_rv32_opcode_clw, CALL_RIR, RC, 0, I, 0, RC, 0, 0x0000FF00, 0x0000007C, 0x0000FF00 }, + { MP_QSTR_c_lwsp, asm_rv32_opcode_clwsp, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFE, 0x000000FC, 0x00000000 }, + { MP_QSTR_c_mv, asm_rv32_opcode_cmv, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFE, 0xFFFFFFFE, 0x00000000 }, + { MP_QSTR_c_nop, asm_rv32_opcode_cnop, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, + { MP_QSTR_c_or, asm_rv32_opcode_cor, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, + { MP_QSTR_c_slli, asm_rv32_opcode_cslli, CALL_RI, R, 0, IU, 0, N, 0, 0xFFFFFFFE, 0x0000001F, 0x00000000 }, + { MP_QSTR_c_srai, asm_rv32_opcode_csrai, CALL_RI, RC, 0, IU, 0, N, 0, 0x0000FF00, 0x0000001F, 0x00000000 }, + { MP_QSTR_c_srli, asm_rv32_opcode_csrli, CALL_RI, RC, 0, IU, 0, N, 0, 0x0000FF00, 0x0000001F, 0x00000000 }, + { MP_QSTR_c_sub, asm_rv32_opcode_csub, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, + { MP_QSTR_c_sw, asm_rv32_opcode_csw, CALL_RIR, RC, 0, I, 0, RC, 0, 0x0000FF00, 0x0000007C, 0x0000FF00 }, + { MP_QSTR_c_swsp, asm_rv32_opcode_cswsp, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFF, 0x000000FC, 0x00000000 }, + { MP_QSTR_c_xor, asm_rv32_opcode_cxor, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, + { MP_QSTR_div, asm_rv32_opcode_div, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_divu, asm_rv32_opcode_divu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_ebreak, asm_rv32_opcode_ebreak, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, + { MP_QSTR_ecall, asm_rv32_opcode_ecall, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, + { MP_QSTR_jal, asm_rv32_opcode_jal, CALL_RL, R, 0, L, 0, N, 0, 0xFFFFFFFF, 0x001FFFFE, 0x00000000 }, + { MP_QSTR_jalr, asm_rv32_opcode_jalr, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_la, opcode_la, CALL_RL, R, 0, L, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, + { MP_QSTR_lb, asm_rv32_opcode_lb, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_lbu, asm_rv32_opcode_lbu, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_lh, asm_rv32_opcode_lh, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_lhu, asm_rv32_opcode_lhu, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_li, opcode_li, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, + { MP_QSTR_lui, asm_rv32_opcode_lui, CALL_RI, R, 0, I, 12, N, 0, 0xFFFFFFFF, 0xFFFFF000, 0x00000000 }, + { MP_QSTR_lw, asm_rv32_opcode_lw, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_mv, asm_rv32_opcode_cmv, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, + { MP_QSTR_mul, asm_rv32_opcode_mul, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_mulh, asm_rv32_opcode_mulh, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_mulhsu, asm_rv32_opcode_mulhsu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_mulhu, asm_rv32_opcode_mulhu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_or_, asm_rv32_opcode_or, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_ori, asm_rv32_opcode_ori, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_rem, asm_rv32_opcode_rem, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_remu, asm_rv32_opcode_remu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_sb, asm_rv32_opcode_sb, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_sh, asm_rv32_opcode_sh, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_sll, asm_rv32_opcode_sll, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_slli, asm_rv32_opcode_slli, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, + { MP_QSTR_slt, asm_rv32_opcode_slt, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_slti, asm_rv32_opcode_slti, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_sltiu, asm_rv32_opcode_sltiu, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_sltu, asm_rv32_opcode_sltu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_sra, asm_rv32_opcode_sra, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_srai, asm_rv32_opcode_srai, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, + { MP_QSTR_srl, asm_rv32_opcode_srl, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_srli, asm_rv32_opcode_srli, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, + { MP_QSTR_sub, asm_rv32_opcode_sub, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_sw, asm_rv32_opcode_sw, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, + { MP_QSTR_xor, asm_rv32_opcode_xor, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, + { MP_QSTR_xori, asm_rv32_opcode_xori, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, +}; + +#undef RC +#undef IU +#undef IZ +#undef IUZ + +// These two checks assume the bitmasks are contiguous. + +static bool is_in_signed_mask(mp_uint_t mask, mp_uint_t value) { + mp_uint_t leading_zeroes = mp_clz(mask); + if (leading_zeroes == 0 || leading_zeroes > 32) { + return true; + } + mp_uint_t positive_mask = ~(mask & ~(1U << (31 - leading_zeroes))); + if ((value & positive_mask) == 0) { + return true; + } + mp_uint_t negative_mask = ~(mask >> 1); + mp_uint_t trailing_zeroes = mp_ctz(mask); + if (trailing_zeroes > 0) { + mp_uint_t trailing_mask = (1U << trailing_zeroes) - 1; + if ((value & trailing_mask) != 0) { + return false; + } + negative_mask &= ~trailing_mask; + } + return (value & negative_mask) == negative_mask; +} + +static inline bool is_in_unsigned_mask(mp_uint_t mask, mp_uint_t value) { + return (value & ~mask) == 0; +} + +static bool validate_integer(mp_uint_t value, mp_uint_t mask, mp_uint_t flags) { + if (flags & U) { + if (!is_in_unsigned_mask(mask, value)) { + return false; + } + } else { + if (!is_in_signed_mask(mask, value)) { + return false; + } + } + + if ((flags & Z) && (value == 0)) { + return false; + } + + return true; +} + +static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, + const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index) { + assert((node_index < 3) && "Invalid argument node number."); + + uint32_t kind = 0; + uint32_t shift = 0; + uint32_t mask = 0; + + switch (node_index) { + case 0: + kind = opcode->argument1_kind; + shift = opcode->argument1_shift; + mask = opcode->argument1_mask; + break; + + case 1: + kind = opcode->argument2_kind; + shift = opcode->argument2_shift; + mask = opcode->argument2_mask; + break; + + case 2: + kind = opcode->argument3_kind; + shift = opcode->argument3_shift; + mask = opcode->argument3_mask; + break; + + default: + break; + } + + switch (kind & 0x03) { + case N: + return true; + + case R: { + mp_uint_t register_index; + if (!parse_register_node(node, ®ister_index, false)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must be a register"), + opcode_qstr, node_index + 1)); + return false; + } + + if ((mask & (1U << register_index)) == 0) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d is an invalid register"), + opcode_qstr, node_index + 1)); + return false; + } + + return true; + } + break; + + case I: { + mp_obj_t object; + if (!mp_parse_node_get_int_maybe(node, &object)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must be an integer"), + opcode_qstr, node_index + 1)); + return false; + } + + mp_uint_t immediate = mp_obj_get_int_truncated(object) << shift; + if (kind & U) { + if (!is_in_unsigned_mask(mask, immediate)) { + goto out_of_range; + } + } else { + if (!is_in_signed_mask(mask, immediate)) { + goto out_of_range; + } + } + + if ((kind & Z) && (immediate == 0)) { + goto zero_immediate; + } + + return true; + } + break; + + case L: { + if (!MP_PARSE_NODE_IS_ID(node)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must be a label"), + opcode_qstr, node_index + 1)); + return false; + } + + qstr qstring; + mp_uint_t label_index = lookup_label(emit, node, &qstring); + if (label_index >= emit->max_num_labels && emit->pass == MP_PASS_EMIT) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d label '%q' is undefined"), + opcode_qstr, node_index + 1, qstring)); + return false; + } + + mp_uint_t displacement = (mp_uint_t)(label_code_offset(emit, label_index)); + if (kind & U) { + if (!is_in_unsigned_mask(mask, displacement)) { + goto out_of_range; + } + } else { + if (!is_in_signed_mask(mask, displacement)) { + goto out_of_range; + } + } + return true; + } + break; + + default: + assert(!"Unknown argument kind"); + break; + } + + return false; + +out_of_range: + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), + opcode_qstr, node_index + 1)); + return false; + +zero_immediate: + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must not be zero"), + opcode_qstr, node_index + 1)); + return false; +} + +static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr, const opcode_t *opcode_data, mp_parse_node_t node, mp_uint_t node_index, mp_parse_node_t *register_node, mp_parse_node_t *offset_node, bool *negative) { + assert(register_node != NULL && "Register node pointer is NULL."); + assert(offset_node != NULL && "Offset node pointer is NULL."); + assert(negative != NULL && "Negative pointer is NULL."); + + if (!MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_atom_expr_normal) && !MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_factor_2)) { + goto invalid_structure; + } + mp_parse_node_struct_t *node_struct = (mp_parse_node_struct_t *)node; + *negative = false; + if (MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_factor_2)) { + if (MP_PARSE_NODE_IS_TOKEN_KIND(node_struct->nodes[0], MP_TOKEN_OP_MINUS)) { + *negative = true; + } else { + if (!MP_PARSE_NODE_IS_TOKEN_KIND(node_struct->nodes[0], MP_TOKEN_OP_PLUS)) { + goto invalid_structure; + } + } + if (!MP_PARSE_NODE_IS_STRUCT_KIND(node_struct->nodes[1], PN_atom_expr_normal)) { + goto invalid_structure; + } + node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; + } + + if (*negative) { + // If the value is negative, RULE_atom_expr_normal's first token will be the + // offset stripped of its negative marker; range check will then fail if the + // default method is used, so a custom check is used instead. + mp_obj_t object; + if (!mp_parse_node_get_int_maybe(node_struct->nodes[0], &object)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must be an integer"), + opcode_qstr, 2)); + return false; + } + mp_uint_t value = mp_obj_get_int_truncated(object); + value = (~value + 1) & (mp_uint_t)-1; + if (!validate_integer(value << opcode_data->argument2_shift, opcode_data->argument2_mask, opcode_data->argument2_kind)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), + opcode_qstr, 2)); + return false; + } + } else { + if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 1)) { + return false; + } + } + + *offset_node = node_struct->nodes[0]; + node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; + if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 2)) { + return false; + } + *register_node = node_struct->nodes[0]; + return true; + +invalid_structure: + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d must be an integer offset to a register"), + opcode_qstr, node_index + 1)); + return false; +} + +static void handle_opcode(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *arguments) { + mp_uint_t rd = 0; + mp_uint_t rs1 = 0; + mp_uint_t rs2 = 0; + + switch (opcode_data->calling_convention) { + case CALL_RRR: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); + parse_register_node(arguments[2], &rs2, opcode_data->argument3_kind & C); + ((call_rrr_t)opcode_data->emitter)(&emit->as, rd, rs1, rs2); + break; + } + + case CALL_RR: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); + ((call_rr_t)opcode_data->emitter)(&emit->as, rd, rs1); + break; + } + + case CALL_RRI: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); + mp_obj_t object; + mp_parse_node_get_int_maybe(arguments[2], &object); + mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift; + ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, immediate); + break; + } + + case CALL_RI: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + mp_obj_t object; + mp_parse_node_get_int_maybe(arguments[1], &object); + mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; + ((call_ri_t)opcode_data->emitter)(&emit->as, rd, immediate); + break; + } + + case CALL_R: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + ((call_r_t)opcode_data->emitter)(&emit->as, rd); + break; + } + + case CALL_RRL: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); + qstr qstring; + mp_uint_t label_index = lookup_label(emit, arguments[2], &qstring); + ptrdiff_t displacement = label_code_offset(emit, label_index); + ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, displacement); + break; + } + + case CALL_RL: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + qstr qstring; + mp_uint_t label_index = lookup_label(emit, arguments[1], &qstring); + ptrdiff_t displacement = label_code_offset(emit, label_index); + ((call_ri_t)opcode_data->emitter)(&emit->as, rd, displacement); + break; + } + + case CALL_L: { + qstr qstring; + mp_uint_t label_index = lookup_label(emit, arguments[0], &qstring); + ptrdiff_t displacement = label_code_offset(emit, label_index); + ((call_i_t)opcode_data->emitter)(&emit->as, displacement); + break; + } + + case CALL_N: + ((call_n_t)opcode_data->emitter)(&emit->as); + break; + + case CALL_I: { + mp_obj_t object; + mp_parse_node_get_int_maybe(arguments[0], &object); + mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument1_shift; + ((call_i_t)opcode_data->emitter)(&emit->as, immediate); + break; + } + + case CALL_RII: { + parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); + mp_obj_t object; + mp_parse_node_get_int_maybe(arguments[1], &object); + mp_uint_t immediate1 = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; + mp_parse_node_get_int_maybe(arguments[2], &object); + mp_uint_t immediate2 = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift; + ((call_rii_t)opcode_data->emitter)(&emit->as, rd, immediate1, immediate2); + break; + } + + case CALL_RIR: + assert(!"Should not get here."); + break; + + default: + assert(!"Unhandled call convention."); + break; + } +} + +static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *argument_nodes) { + mp_parse_node_t nodes[3] = {0}; + if (!validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { + return false; + } + nodes[0] = argument_nodes[0]; + bool negative = false; + if (!parse_register_offset_node(emit, opcode, opcode_data, argument_nodes[1], 1, &nodes[1], &nodes[2], &negative)) { + return false; + } + + mp_uint_t rd; + mp_uint_t rs1; + parse_register_node(nodes[0], &rd, opcode_data->argument1_kind & C); + if (!parse_register_node(nodes[1], &rs1, opcode_data->argument3_kind & C)) { + return false; + } + + mp_obj_t object; + mp_parse_node_get_int_maybe(nodes[2], &object); + mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; + if (negative) { + immediate = (~immediate + 1) & (mp_uint_t)-1; + } + if (!is_in_signed_mask(opcode_data->argument2_mask, immediate)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), + opcode, 2)); + return false; + } + + ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, immediate); + return true; +} + +static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uint_t arguments_count, mp_parse_node_t *argument_nodes) { + const opcode_t *opcode_data = NULL; + for (mp_uint_t index = 0; index < MP_ARRAY_SIZE(OPCODES); index++) { + if (OPCODES[index].qstring == opcode) { + opcode_data = &OPCODES[index]; + break; + } + } + + if (!opcode_data) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("unsupported or unknown RV32 instruction '%q'."), opcode)); + return; + } + + size_t required_arguments = 0; + if (opcode_data->argument1_kind != N) { + required_arguments++; + } + if (opcode_data->argument2_kind != N) { + required_arguments++; + } + if (opcode_data->argument3_kind != N) { + required_arguments++; + } + + if (opcode_data->calling_convention != CALL_RIR) { + if (required_arguments != arguments_count) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("RV32 instruction '%q' requires %d arguments."), opcode, required_arguments)); + return; + } + if (required_arguments >= 1 && !validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { + return; + } + if (required_arguments >= 2 && !validate_argument(emit, opcode, opcode_data, argument_nodes[1], 1)) { + return; + } + if (required_arguments >= 3 && !validate_argument(emit, opcode, opcode_data, argument_nodes[2], 2)) { + return; + } + handle_opcode(emit, opcode, opcode_data, argument_nodes); + return; + } + + assert(required_arguments == 3 && "Invalid arguments count for calling convention."); + assert((opcode_data->argument2_kind & U) == 0 && "Offset must not be unsigned."); + assert((opcode_data->argument2_kind & Z) == 0 && "Offset can be zero."); + + if (arguments_count != 2) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("RV32 instruction '%q' requires %d arguments."), opcode, 2)); + return; + } + + handle_load_store_opcode_with_offset(emit, opcode, opcode_data, argument_nodes); +} + +#undef N +#undef R +#undef I +#undef L +#undef C +#undef U + +const emit_inline_asm_method_table_t emit_inline_rv32_method_table = { + #if MICROPY_DYNAMIC_COMPILER + emit_inline_rv32_new, + emit_inline_rv32_free, + #endif + + emit_inline_rv32_start_pass, + emit_inline_rv32_end_pass, + emit_inline_rv32_count_params, + emit_inline_rv32_label, + emit_inline_rv32_opcode, +}; + +#endif // MICROPY_EMIT_INLINE_RV32 diff --git a/py/mpconfig.h b/py/mpconfig.h index 8598eaa5bb6..e84d258a122 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -411,6 +411,11 @@ #define MICROPY_EMIT_RV32 (0) #endif +// Whether to enable the RISC-V RV32 inline assembler +#ifndef MICROPY_EMIT_INLINE_RV32 +#define MICROPY_EMIT_INLINE_RV32 (0) +#endif + // Convenience definition for whether any native emitter is enabled #define MICROPY_EMIT_NATIVE (MICROPY_EMIT_X64 || MICROPY_EMIT_X86 || MICROPY_EMIT_THUMB || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN || MICROPY_EMIT_RV32 || MICROPY_EMIT_NATIVE_DEBUG) @@ -420,7 +425,7 @@ #define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN) // Convenience definition for whether any inline assembler emitter is enabled -#define MICROPY_EMIT_INLINE_ASM (MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) +#define MICROPY_EMIT_INLINE_ASM (MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_INLINE_RV32) // Convenience definition for whether any native or inline assembler emitter is enabled #define MICROPY_EMIT_MACHINE_CODE (MICROPY_EMIT_NATIVE || MICROPY_EMIT_INLINE_ASM) diff --git a/py/py.cmake b/py/py.cmake index dd94f6a59e3..0fee74ddcb4 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -24,6 +24,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/emitbc.c ${MICROPY_PY_DIR}/emitcommon.c ${MICROPY_PY_DIR}/emitglue.c + ${MICROPY_PY_DIR}/emitinlinerv32.c ${MICROPY_PY_DIR}/emitinlinethumb.c ${MICROPY_PY_DIR}/emitinlinextensa.c ${MICROPY_PY_DIR}/emitnarm.c diff --git a/py/py.mk b/py/py.mk index 9592fbb9170..c0b7e1ac8b9 100644 --- a/py/py.mk +++ b/py/py.mk @@ -123,6 +123,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ emitnxtensawin.o \ asmrv32.o \ emitnrv32.o \ + emitinlinerv32.o \ emitndebug.o \ formatfloat.o \ parsenumbase.o \ diff --git a/tests/inlineasm/rv32/asmargs.py b/tests/inlineasm/rv32/asmargs.py new file mode 100644 index 00000000000..78afd511150 --- /dev/null +++ b/tests/inlineasm/rv32/asmargs.py @@ -0,0 +1,44 @@ +# test passing arguments + + +@micropython.asm_rv32 +def arg0(): + c_li(a0, 1) + + +print(arg0()) + + +@micropython.asm_rv32 +def arg1(a0): + addi(a0, a0, 1) + + +print(arg1(1)) + + +@micropython.asm_rv32 +def arg2(a0, a1): + add(a0, a0, a1) + + +print(arg2(1, 2)) + + +@micropython.asm_rv32 +def arg3(a0, a1, a2): + add(a0, a0, a1) + add(a0, a0, a2) + + +print(arg3(1, 2, 3)) + + +@micropython.asm_rv32 +def arg4(a0, a1, a2, a3): + add(a0, a0, a1) + add(a0, a0, a2) + add(a0, a0, a3) + + +print(arg4(1, 2, 3, 4)) diff --git a/tests/inlineasm/rv32/asmargs.py.exp b/tests/inlineasm/rv32/asmargs.py.exp new file mode 100644 index 00000000000..e33a6964f46 --- /dev/null +++ b/tests/inlineasm/rv32/asmargs.py.exp @@ -0,0 +1,5 @@ +1 +2 +3 +6 +10 diff --git a/tests/inlineasm/rv32/asmarith.py b/tests/inlineasm/rv32/asmarith.py new file mode 100644 index 00000000000..8b864c0b3b2 --- /dev/null +++ b/tests/inlineasm/rv32/asmarith.py @@ -0,0 +1,79 @@ +# test arithmetic opcodes + + +@micropython.asm_rv32 +def f1(): + li(a0, 0x100) + li(a1, 1) + add(a0, a0, a1) + addi(a0, a0, 1) + addi(a0, a0, -2) + sub(a0, a0, a1) + c_add(a0, a1) + c_addi(a0, -1) + c_sub(a0, a1) + + +print(hex(f1())) + + +@micropython.asm_rv32 +def f2(): + li(a0, 0x10FF) + li(a1, 1) + and_(a2, a0, a1) + andi(a3, a0, 0x10) + or_(a2, a2, a3) + ori(a2, a2, 8) + li(a1, 0x200) + c_or(a2, a1) + li(a1, 0xF0) + mv(a0, a2) + c_and(a0, a1) + li(a1, 0x101) + xor(a0, a0, a1) + xori(a0, a0, 0x101) + c_xor(a0, a1) + + +print(hex(f2())) + + +@micropython.asm_rv32 +def f3(a0, a1): + slt(a0, a0, a1) + + +print(f3(0xFFFFFFF0, 0xFFFFFFF1)) +print(f3(0x0, 0xFFFFFFF1)) +print(f3(0xFFFFFFF1, 0xFFFFFFF1)) +print(f3(0xFFFFFFF1, 0xFFFFFFF0)) + + +@micropython.asm_rv32 +def f4(a0, a1): + sltu(a0, a0, a1) + + +print(f3(0xFFFFFFF0, 0xFFFFFFF1)) +print(f3(0x0, 0xFFFFFFF1)) +print(f3(0xFFFFFFF1, 0xFFFFFFF1)) +print(f3(0xFFFFFFF1, 0xFFFFFFF0)) + + +@micropython.asm_rv32 +def f5(a0): + slti(a0, a0, -2) + + +print(f5(-1)) +print(f5(-3)) + + +@micropython.asm_rv32 +def f6(a0): + sltiu(a0, a0, -2) + + +print(f6(-1)) +print(f6(-3)) diff --git a/tests/inlineasm/rv32/asmarith.py.exp b/tests/inlineasm/rv32/asmarith.py.exp new file mode 100644 index 00000000000..7da4dd5c93c --- /dev/null +++ b/tests/inlineasm/rv32/asmarith.py.exp @@ -0,0 +1,14 @@ +0xfe +0x111 +1 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +1 diff --git a/tests/inlineasm/rv32/asmbranch.py b/tests/inlineasm/rv32/asmbranch.py new file mode 100644 index 00000000000..d7d059d4067 --- /dev/null +++ b/tests/inlineasm/rv32/asmbranch.py @@ -0,0 +1,161 @@ +# test branch instructions + + +@micropython.asm_rv32 +def tbeq(a0): + mv(a1, a0) + + li(a0, 10) + li(a2, 1) + beq(a1, a2, end) + + li(a0, 20) + li(a2, 2) + beq(a1, a2, end) + + li(a0, 30) + li(a2, 3) + beq(a1, a2, end) + + li(a0, 0) + + label(end) + + +print(tbeq(0)) +print(tbeq(1)) +print(tbeq(2)) +print(tbeq(3)) + + +@micropython.asm_rv32 +def tbne(a0): + mv(a1, a0) + + li(a0, 10) + li(a2, 1) + bne(a1, a2, end) + + li(a0, 20) + li(a2, 2) + bne(a1, a2, end) + + li(a0, 30) + li(a2, 3) + bne(a1, a2, end) + + li(a0, 0) + + label(end) + + +print(tbne(0)) +print(tbne(1)) +print(tbne(2)) +print(tbne(3)) + + +@micropython.asm_rv32 +def tbgeu(a0): + mv(a1, a0) + + li(a0, 1) + li(a2, 2) + bgeu(a1, a2, end) + li(a0, 0) + + label(end) + + +print(tbgeu(0)) +print(tbgeu(1)) +print(tbgeu(2)) +print(tbgeu(3)) + + +@micropython.asm_rv32 +def tbltu(a0): + mv(a1, a0) + + li(a0, 1) + li(a2, 2) + bltu(a1, a2, end) + li(a0, 0) + + label(end) + + +print(tbltu(0)) +print(tbltu(1)) +print(tbltu(2)) +print(tbltu(3)) + + +@micropython.asm_rv32 +def tbge(a0): + mv(a1, a0) + + li(a0, 1) + li(a2, -2) + bge(a1, a2, end) + li(a0, 0) + + label(end) + + +print(tbge(-3)) +print(tbge(-2)) +print(tbge(-1)) +print(tbge(0)) + + +@micropython.asm_rv32 +def tblt(a0): + mv(a1, a0) + + li(a0, 1) + li(a2, -2) + blt(a1, a2, end) + li(a0, 0) + + label(end) + + +print(tblt(-3)) +print(tblt(-2)) +print(tblt(-1)) +print(tblt(0)) + + +@micropython.asm_rv32 +def tcbeqz(a0): + mv(a1, a0) + + li(a0, 1) + c_beqz(a1, end) + li(a0, 0) + + label(end) + + +print(tcbeqz(0)) +print(tcbeqz(1)) +print(tcbeqz(2)) +print(tcbeqz(3)) + + +@micropython.asm_rv32 +def tcbnez(a0): + mv(a1, a0) + + li(a0, 1) + c_bnez(a1, end) + li(a0, 0) + + label(end) + + +print(tcbnez(0)) +print(tcbnez(1)) +print(tcbnez(2)) +print(tcbnez(3)) diff --git a/tests/inlineasm/rv32/asmbranch.py.exp b/tests/inlineasm/rv32/asmbranch.py.exp new file mode 100644 index 00000000000..baae6914953 --- /dev/null +++ b/tests/inlineasm/rv32/asmbranch.py.exp @@ -0,0 +1,32 @@ +0 +10 +20 +30 +10 +20 +10 +10 +0 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +1 diff --git a/tests/inlineasm/rv32/asmconst.py b/tests/inlineasm/rv32/asmconst.py new file mode 100644 index 00000000000..2b6363a43db --- /dev/null +++ b/tests/inlineasm/rv32/asmconst.py @@ -0,0 +1,49 @@ +# test constants in assembler + + +@micropython.asm_rv32 +def c1(): + li(a0, 0xFFFFFFFF) + li(a1, 0xF0000000) + sub(a0, a0, a1) + + +print(hex(c1())) + + +@micropython.asm_rv32 +def c2(): + lui(a0, 0x12345) + li(a1, 0x678) + add(a0, a0, a1) + + +print(hex(c2())) + + +@micropython.asm_rv32 +def c3() -> uint: + lui(a0, 0) + addi(a0, a0, 0x7FF) + + +print(hex(c3())) + + +@micropython.asm_rv32 +def c4() -> uint: + lui(a0, 0) + addi(a0, a0, -1) + + +print(hex(c4())) + + +@micropython.asm_rv32 +def c5(): + c_lui(a0, 1) + c_li(a1, 1) + c_add(a0, a1) + + +print(hex(c5())) diff --git a/tests/inlineasm/rv32/asmconst.py.exp b/tests/inlineasm/rv32/asmconst.py.exp new file mode 100644 index 00000000000..0c713a84148 --- /dev/null +++ b/tests/inlineasm/rv32/asmconst.py.exp @@ -0,0 +1,5 @@ +0xfffffff +0x12345678 +0x7ff +0xffffffff +0x1001 diff --git a/tests/inlineasm/rv32/asmcsr.py b/tests/inlineasm/rv32/asmcsr.py new file mode 100644 index 00000000000..f27e2aa5e34 --- /dev/null +++ b/tests/inlineasm/rv32/asmcsr.py @@ -0,0 +1,65 @@ +# test csr instructions + +# CSR 0x340 is `mscratch`. This test suite is only safe to run on a system +# where it is known that there is no other code that can read from or write +# to that register. The qemu port is one such system, as the CSR is only +# accessed when a machine exception occurs, and at that point it doesn't matter +# anymore whether these tests are running or not. + + +@micropython.asm_rv32 +def csr(): + li(a0, 0) + csrrw(zero, zero, 0x340) # All zeroes + csrrs(a1, zero, 0x340) # Read zeroes + c_bnez(a1, end) + addi(a0, a0, 1) + li(a1, 0xA5A5A5A5) + li(a2, 0x5A5A5A5A) + csrrs(a2, a1, 0x340) # Read zeroes, set 0xA5A5A5A5 + c_bnez(a2, end) + addi(a0, a0, 1) + csrrs(a3, zero, 0x340) # Read 0xA5A5A5A5 + bne(a3, a1, end) + addi(a0, a0, 1) + li(a2, 0xF0F0F0F0) + csrrc(zero, a2, 0x340) # Clear upper half + csrrs(a3, zero, 0x340) # Read 0x05050505 + xori(a2, a2, -1) + and_(a2, a1, a2) + bne(a2, a3, end) + addi(a0, a0, 1) + label(end) + + +print(csr()) + + +@micropython.asm_rv32 +def csri(): + li(a0, 0) + csrrwi(zero, 0x340, 15) # Write 0xF + csrrs(a1, zero, 0x340) # Read 0xF + csrrsi(a2, 0x340, 0) # Read + bne(a1, a2, end) + addi(a0, a0, 1) + csrrci(a2, 0x340, 0) # Read + bne(a1, a2, end) + addi(a0, a0, 1) + li(a2, 15) + bne(a1, a2, end) + addi(a0, a0, 1) + csrrci(zero, 0x340, 1) # Clear bit 1 + csrrs(a1, zero, 0x340) # Read 0xE + li(a2, 14) + bne(a1, a2, end) + addi(a0, a0, 1) + csrrsi(zero, 0x340, 1) # Set bit 1 + csrrs(a1, zero, 0x340) # Read 0xF + li(a2, 15) + bne(a1, a2, end) + addi(a0, a0, 1) + label(end) + + +print(csri()) diff --git a/tests/inlineasm/rv32/asmcsr.py.exp b/tests/inlineasm/rv32/asmcsr.py.exp new file mode 100644 index 00000000000..61c83cba41c --- /dev/null +++ b/tests/inlineasm/rv32/asmcsr.py.exp @@ -0,0 +1,2 @@ +4 +5 diff --git a/tests/inlineasm/rv32/asmdata.py b/tests/inlineasm/rv32/asmdata.py new file mode 100644 index 00000000000..5e555ef4bf4 --- /dev/null +++ b/tests/inlineasm/rv32/asmdata.py @@ -0,0 +1,33 @@ +# test the "data" directive + + +@micropython.asm_rv32 +def ret_num(a0) -> uint: + slli(a0, a0, 2) + addi(a0, a0, 16) + auipc(a1, 0) + add(a1, a1, a0) + lw(a0, 0(a1)) + jal(zero, HERE) + data(4, 0x12345678, 0x20000000, 0x40000000, 0x7FFFFFFF + 1, (1 << 32) - 2) + label(HERE) + + +for i in range(5): + print(hex(ret_num(i))) + + +@micropython.asm_rv32 +def ret_num_la(a0) -> uint: + slli(a0, a0, 2) + la(a1, DATA) + add(a1, a1, a0) + lw(a0, 0(a1)) + jal(zero, HERE) + label(DATA) + data(4, 0x12345678, 0x20000000, 0x40000000, 0x7FFFFFFF + 1, (1 << 32) - 2) + label(HERE) + + +for i in range(5): + print(hex(ret_num_la(i))) diff --git a/tests/inlineasm/rv32/asmdata.py.exp b/tests/inlineasm/rv32/asmdata.py.exp new file mode 100644 index 00000000000..79e92bdfa5d --- /dev/null +++ b/tests/inlineasm/rv32/asmdata.py.exp @@ -0,0 +1,10 @@ +0x12345678 +0x20000000 +0x40000000 +0x80000000 +0xfffffffe +0x12345678 +0x20000000 +0x40000000 +0x80000000 +0xfffffffe diff --git a/tests/inlineasm/rv32/asmdivmul.py b/tests/inlineasm/rv32/asmdivmul.py new file mode 100644 index 00000000000..e1120c6f63c --- /dev/null +++ b/tests/inlineasm/rv32/asmdivmul.py @@ -0,0 +1,63 @@ +@micropython.asm_rv32 +def sdiv(a0, a1): + div(a0, a0, a1) + + +@micropython.asm_rv32 +def udiv(a0, a1): + divu(a0, a0, a1) + + +@micropython.asm_rv32 +def srem(a0, a1): + rem(a0, a0, a1) + + +@micropython.asm_rv32 +def urem(a0, a1): + remu(a0, a0, a1) + + +print(sdiv(1234, 3)) +print(sdiv(-1234, 3)) +print(sdiv(1234, -3)) +print(sdiv(-1234, -3)) + +print(udiv(1234, 3)) +print(udiv(0xFFFFFFFF, 0x7FFFFFFF)) +print(udiv(0xFFFFFFFF, 0xFFFFFFFF)) + +print(srem(1234, 3)) +print(srem(-1234, 3)) +print(srem(1234, -3)) +print(srem(-1234, -3)) + +print(urem(1234, 3)) +print(urem(0xFFFFFFFF, 0x7FFFFFFF)) +print(urem(0xFFFFFFFF, 0xFFFFFFFF)) + + +@micropython.asm_rv32 +def m1(a0, a1): + mul(a0, a0, a1) + + +@micropython.asm_rv32 +def m2(a0, a1): + mulh(a0, a0, a1) + + +@micropython.asm_rv32 +def m3(a0, a1): + mulhu(a0, a0, a1) + + +@micropython.asm_rv32 +def m4(a0, a1): + mulhsu(a0, a0, a1) + + +print(m1(0xFFFFFFFF, 2)) +print(m2(0xFFFFFFFF, 0xFFFFFFF0)) +print(m3(0xFFFFFFFF, 0xFFFFFFF0)) +print(m4(0xFFFFFFFF, 0xFFFFFFF0)) diff --git a/tests/inlineasm/rv32/asmdivmul.py.exp b/tests/inlineasm/rv32/asmdivmul.py.exp new file mode 100644 index 00000000000..60d28635f79 --- /dev/null +++ b/tests/inlineasm/rv32/asmdivmul.py.exp @@ -0,0 +1,18 @@ +411 +-411 +-411 +411 +411 +2 +1 +1 +-1 +1 +-1 +1 +1 +0 +-2 +0 +-17 +-1 diff --git a/tests/inlineasm/rv32/asmjump.py b/tests/inlineasm/rv32/asmjump.py new file mode 100644 index 00000000000..fe87d3f968b --- /dev/null +++ b/tests/inlineasm/rv32/asmjump.py @@ -0,0 +1,115 @@ +@micropython.asm_rv32 +def f1(): + li(a0, 0) + la(a1, END) + c_jr(a1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + label(END) + + +print(f1()) + + +@micropython.asm_rv32 +def f2(): + addi(sp, sp, -4) + c_swsp(ra, 0) + li(ra, 0) + li(a0, 0) + c_jal(END) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + label(END) + bne(ra, zero, SUCCESS) + c_addi(a0, 2) + label(SUCCESS) + c_lwsp(ra, 0) + addi(sp, sp, 4) + + +print(f2()) + + +@micropython.asm_rv32 +def f3(): + li(a0, 0) + c_j(END) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + label(END) + + +print(f3()) + + +@micropython.asm_rv32 +def f4(): + addi(sp, sp, -4) + c_swsp(ra, 0) + li(ra, 0) + li(a0, 0) + la(a1, END) + c_jalr(a1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + label(END) + bne(ra, zero, SUCCESS) + c_addi(a0, 2) + label(SUCCESS) + c_lwsp(ra, 0) + addi(sp, sp, 4) + + +print(f4()) + + +@micropython.asm_rv32 +def f5(): + li(a0, 0) + li(a1, 0) + jal(a1, END) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + label(END) + bne(a1, zero, SUCCESS) + c_addi(a0, 2) + label(SUCCESS) + + +print(f5()) + + +@micropython.asm_rv32 +def f6(): + li(a0, 0) + la(a1, JUMP) + li(a2, 0) + jalr(a2, a1, 10) + label(JUMP) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + c_addi(a0, 1) + bne(a2, zero, SUCCESS) + c_addi(a0, 2) + label(SUCCESS) + + +print(f6()) diff --git a/tests/inlineasm/rv32/asmjump.py.exp b/tests/inlineasm/rv32/asmjump.py.exp new file mode 100644 index 00000000000..f7eb44d66e0 --- /dev/null +++ b/tests/inlineasm/rv32/asmjump.py.exp @@ -0,0 +1,6 @@ +0 +0 +0 +0 +0 +0 diff --git a/tests/inlineasm/rv32/asmloadstore.py b/tests/inlineasm/rv32/asmloadstore.py new file mode 100644 index 00000000000..2c49e07b41a --- /dev/null +++ b/tests/inlineasm/rv32/asmloadstore.py @@ -0,0 +1,86 @@ +# test load/store opcodes + + +@micropython.asm_rv32 +def l(): + li(a5, 4) + addi(sp, sp, -12) + li(a0, 0x123) + c_swsp(a0, 0) + addi(a1, a0, 0x111) + c_swsp(a1, 4) + addi(a2, a1, 0x111) + c_swsp(a2, 8) + mv(a4, sp) + c_lw(a3, 0(a4)) + bne(a3, a0, END) + addi(a5, a5, -1) + lw(a3, 4(a4)) + bne(a3, a1, END) + addi(a5, a5, -1) + lhu(a3, 8(a4)) + bne(a3, a2, END) + addi(a5, a5, -1) + lbu(a0, 8(a4)) + addi(a0, a0, 0x300) + bne(a0, a2, END) + addi(a5, a5, -1) + label(END) + addi(sp, sp, 12) + mv(a0, a5) + + +print(l()) + + +@micropython.asm_rv32 +def s(): + li(a5, 4) + addi(sp, sp, -12) + c_swsp(zero, 0) + c_swsp(zero, 4) + c_swsp(zero, 8) + li(a0, 0x12345) + mv(a4, sp) + c_sw(a0, 0(a4)) + sh(a0, 4(a4)) + sb(a0, 8(a4)) + li(a1, 0xFFFF) + and_(a1, a0, a1) + andi(a2, a0, 0xFF) + lw(a3, 0(sp)) + bne(a3, a0, END) + addi(a5, a5, -1) + lw(a3, 4(sp)) + bne(a3, a1, END) + addi(a5, a5, -1) + lw(a3, 8(sp)) + bne(a3, a2, END) + addi(a5, a5, -1) + label(END) + addi(sp, sp, 12) + mv(a0, a5) + + +print(s()) + + +@micropython.asm_rv32 +def lu(): + li(a5, 4) + addi(sp, sp, -8) + li(a0, 0xF1234567) + c_swsp(a0, 0) + c_swsp(a0, 4) + lh(a1, 0(sp)) + blt(a1, zero, END) + addi(a5, a5, -1) + lb(a2, 4(sp)) + blt(a2, zero, END) + addi(a5, a5, -1) + label(END) + addi(sp, sp, 8) + mv(a0, a5) + + +print(lu()) diff --git a/tests/inlineasm/rv32/asmloadstore.py.exp b/tests/inlineasm/rv32/asmloadstore.py.exp new file mode 100644 index 00000000000..4539bbf2d22 --- /dev/null +++ b/tests/inlineasm/rv32/asmloadstore.py.exp @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/tests/inlineasm/rv32/asmrettype.py b/tests/inlineasm/rv32/asmrettype.py new file mode 100644 index 00000000000..fc7ae61d152 --- /dev/null +++ b/tests/inlineasm/rv32/asmrettype.py @@ -0,0 +1,33 @@ +# test return type of inline asm + + +@micropython.asm_rv32 +def ret_obj(a0) -> object: + pass + + +ret_obj(print)(1) + + +@micropython.asm_rv32 +def ret_bool(a0) -> bool: + pass + + +print(ret_bool(0), ret_bool(1)) + + +@micropython.asm_rv32 +def ret_int(a0) -> int: + slli(a0, a0, 29) + + +print(ret_int(0), hex(ret_int(1)), hex(ret_int(2)), hex(ret_int(4))) + + +@micropython.asm_rv32 +def ret_uint(a0) -> uint: + slli(a0, a0, 29) + + +print(ret_uint(0), hex(ret_uint(1)), hex(ret_uint(2)), hex(ret_uint(4))) diff --git a/tests/inlineasm/rv32/asmrettype.py.exp b/tests/inlineasm/rv32/asmrettype.py.exp new file mode 100644 index 00000000000..cbb49d24724 --- /dev/null +++ b/tests/inlineasm/rv32/asmrettype.py.exp @@ -0,0 +1,4 @@ +1 +False True +0 0x20000000 0x40000000 -0x80000000 +0 0x20000000 0x40000000 0x80000000 diff --git a/tests/inlineasm/rv32/asmsanity.py b/tests/inlineasm/rv32/asmsanity.py new file mode 100644 index 00000000000..1a16d3504db --- /dev/null +++ b/tests/inlineasm/rv32/asmsanity.py @@ -0,0 +1,204 @@ +TEMPLATE3 = """ +@micropython.asm_rv32 +def f(): + {}({}, {}, {}) +""" + +TEMPLATE2 = """ +@micropython.asm_rv32 +def f(): + {}({}, {}) +""" + +TEMPLATE1 = """ +@micropython.asm_rv32 +def f(): + {}({}) +""" + + +REGISTERS = [ + "zero", + "s0", + "s1", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "tp", + "gp", + "sp", + "ra", + "t0", + "t1", + "t2", + "t3", + "t4", + "t5", + "t6", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + "x6", + "x7", + "x8", + "x9", + "x10", + "x11", + "x12", + "x13", + "x14", + "x15", + "x16", + "x17", + "x18", + "x19", + "x20", + "x21", + "x22", + "x23", + "x24", + "x25", + "x26", + "x27", + "x28", + "x29", + "x30", + "x31", +] + + +def harness(opcode, fragment, tag): + try: + exec(fragment) + except SyntaxError: + print(tag, opcode) + + +for opcode in ("slli", "srli", "srai"): + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", -1), "-") + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", 33), "+") + +for opcode in ("c_slli", "c_srli", "c_srai"): + harness(opcode, TEMPLATE2.format(opcode, "a0", -1), "-") + harness(opcode, TEMPLATE2.format(opcode, "a0", 33), "+") + +harness("c_slli", TEMPLATE2.format("c_slli", "zero", 0), "0") +harness("c_slli", TEMPLATE2.format("c_slli", "x0", 0), "0") + +for opcode in ("c_srli", "c_srai"): + for register in REGISTERS: + harness(opcode, TEMPLATE2.format(opcode, register, 0), register) + +for opcode in ("c_mv", "c_add"): + harness(opcode, TEMPLATE2.format(opcode, "a0", "zero"), "0l") + harness(opcode, TEMPLATE2.format(opcode, "zero", "a0"), "0r") + harness(opcode, TEMPLATE2.format(opcode, "zero", "zero"), "0b") + +harness("c_jr", TEMPLATE1.format("c_jr", "zero"), "0") + +for opcode in ("addi", "andi", "ori", "slti", "sltiu", "xori"): + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", 0x7FF), ">=s") + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", 0x800), ">s") + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", -2048), "<=s") + harness(opcode, TEMPLATE3.format(opcode, "a0", "a0", -2049), "=s") + harness(opcode, TEMPLATE.format(opcode, 0x800), ">s") + harness(opcode, TEMPLATE.format(opcode, -2048), "<=s") + harness(opcode, TEMPLATE.format(opcode, -2049), "0") +harness("c_addi", TEMPLATE2.format("c_andi", "zero", -512), "<0") +harness("c_addi", TEMPLATE2.format("c_andi", "s0", 0), "s0") +harness("c_addi", TEMPLATE2.format("c_andi", "s0", -100), "s") + +harness("c_andi", TEMPLATE2.format("c_andi", "zero", 0), "00") +harness("c_andi", TEMPLATE2.format("c_andi", "zero", 512), ">0") +harness("c_andi", TEMPLATE2.format("c_andi", "zero", -512), "<0") +harness("c_andi", TEMPLATE2.format("c_andi", "s0", 0), "s0") +harness("c_andi", TEMPLATE2.format("c_andi", "s0", -100), "s") + +C_REGISTERS = ( + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "s0", + "s1", + "x8", + "x9", + "x10", + "x11", + "x12", + "x13", + "x14", + "x15", +) + +for opcode in ("c_and", "c_or", "c_xor"): + for source in REGISTERS: + for destination in REGISTERS: + if source in C_REGISTERS and destination in C_REGISTERS: + try: + exec( + """ +@micropython.asm_rv32 +def f(): + {}({}, {}) +""".format(opcode, source, destination) + ) + except SyntaxError: + print(source, destination, opcode) + else: + try: + exec( + """ +@micropython.asm_rv32 +def f(): + {}({}, {}) +""".format(opcode, source, destination) + ) + print(source, destination, opcode) + except SyntaxError: + pass + print(opcode) + +for opcode in ("c_lw", "c_sw"): + TEMPLATE = """ +@micropython.asm_rv32 +def f(): + {}(a0, {}(a0)) +""" + harness(opcode, TEMPLATE.format(opcode, 60), ">=s") + harness(opcode, TEMPLATE.format(opcode, 61), ">s") + harness(opcode, TEMPLATE.format(opcode, -60), "<=s") + harness(opcode, TEMPLATE.format(opcode, -61), "s addi +s andi +s ori +s slti +s sltiu +s xori +s lb +s lbu +s lh +s lhu +s lw +s sb +s sh +s sw +0 c_addi +<0 c_addi +s c_addi +00 c_andi +>0 c_andi +<0 c_andi +s c_andi +c_and +c_or +c_xor +>s c_lw +s c_sw + Date: Fri, 6 Sep 2024 12:43:54 +0200 Subject: [PATCH 0165/2098] qemu/main: Make GC heap size configurable on a per-arch basis. In certain circumstances depending on the code size, the `deflate_decompress` test fails on both ARM and RV32 with a memory allocation failure error. The issue is mitigated by having a larger GC heap, in this case around 20 KBytes more than the original 100 KBytes default. This commit makes the GC heap size configurable on a per-arch basis, with both ARM and RV32 using the enlarged 120 KBytes heap. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 4 ++++ ports/qemu/README.md | 2 ++ ports/qemu/main.c | 8 +++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index ea0bef81486..c8c07965f9c 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -19,9 +19,11 @@ QSTR_DEFS = qstrdefsport.h MICROPY_ROM_TEXT_COMPRESSION ?= 1 ifeq ($(QEMU_ARCH),arm) +MICROPY_HEAP_SIZE ?= 122880 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_thumb.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif ifeq ($(QEMU_ARCH),riscv32) +MICROPY_HEAP_SIZE ?= 122880 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif @@ -29,6 +31,8 @@ endif include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk +CFLAGS += -DMICROPY_HEAP_SIZE=$(MICROPY_HEAP_SIZE) + ################################################################################ # ARM specific settings diff --git a/ports/qemu/README.md b/ports/qemu/README.md index 9274bcc4e75..70edf97f58e 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -122,3 +122,5 @@ The following options can be specified on the `make` command line: - `QEMU_DEBUG_ARGS`: defaults to `-s` (gdb on TCP port 1234), but can be overridden with different qemu gdb arguments. - `QEMU_DEBUG_EXTRA`: extra options to pass to qemu when `QEMU_DEBUG=1` is used. +- `MICROPY_HEAP_SIZE`: pass in an optional value (in bytes) for overriding the GC + heap size used by the port. diff --git a/ports/qemu/main.c b/ports/qemu/main.c index dff55058e16..75c6fe4cdc0 100644 --- a/ports/qemu/main.c +++ b/ports/qemu/main.c @@ -34,14 +34,16 @@ #include "shared/runtime/gchelper.h" #include "shared/runtime/pyexec.h" -#define HEAP_SIZE (100 * 1024) +#if MICROPY_HEAP_SIZE <= 0 +#error MICROPY_HEAP_SIZE must be a positive integer. +#endif -static uint32_t gc_heap[HEAP_SIZE / sizeof(uint32_t)]; +static uint32_t gc_heap[MICROPY_HEAP_SIZE / sizeof(uint32_t)]; int main(int argc, char **argv) { mp_stack_ctrl_init(); mp_stack_set_limit(10240); - gc_init(gc_heap, (char *)gc_heap + HEAP_SIZE); + gc_init(gc_heap, (char *)gc_heap + MICROPY_HEAP_SIZE); for (;;) { mp_init(); From 24482a93ef50dc0df1a704922f465d9c23e143c6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Nov 2024 10:16:53 +0100 Subject: [PATCH 0166/2098] rp2/mpconfigport: Enable RV32 inline assembly support. This commit enables by default inline assembly support for the RP2 target when it is operating in RISC-V mode. This brings the feature set when in RISC-V mode to parity with what's available in ARM mode. Signed-off-by: Alessandro Gatti --- ports/rp2/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8f4e846ba13..bc289bcbf97 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -87,6 +87,7 @@ #endif #elif PICO_RISCV #define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_INLINE_RV32 (1) #endif // Optimisations From 931a768f5529649c43a65e999ab28f2820002c38 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Nov 2024 11:55:54 +0100 Subject: [PATCH 0167/2098] tests/run-tests.py: Detect inlineasm support and add tests if needed. This commit implements a method to detect at runtime if inline assembler support is enabled, and if so which platform it targets. This allows clean test runs even on modified version of ARM-based ports where inline assembler support is disabled, running inline assembler tests on ports that have such feature not enabled by default and manually enabled, and allows to always run the correct inlineasm tests for ports that support more than one architecture (esp32, qemu, rp2). Signed-off-by: Alessandro Gatti --- ports/qemu/boards/MICROBIT.mk | 3 -- ports/qemu/boards/MPS2_AN385.mk | 3 -- ports/qemu/boards/NETDUINO2.mk | 3 -- ports/qemu/boards/SABRELITE.mk | 2 +- ports/qemu/boards/VIRT_RV32.mk | 3 -- tests/feature_check/inlineasm_rv32.py | 9 ++++ tests/feature_check/inlineasm_rv32.py.exp | 1 + tests/feature_check/inlineasm_thumb.py | 9 ++++ tests/feature_check/inlineasm_thumb.py.exp | 1 + tests/feature_check/inlineasm_xtensa.py | 9 ++++ tests/feature_check/inlineasm_xtensa.py.exp | 1 + tests/run-tests.py | 60 +++++++++++++-------- 12 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 tests/feature_check/inlineasm_rv32.py create mode 100644 tests/feature_check/inlineasm_rv32.py.exp create mode 100644 tests/feature_check/inlineasm_thumb.py create mode 100644 tests/feature_check/inlineasm_thumb.py.exp create mode 100644 tests/feature_check/inlineasm_xtensa.py create mode 100644 tests/feature_check/inlineasm_xtensa.py.exp diff --git a/ports/qemu/boards/MICROBIT.mk b/ports/qemu/boards/MICROBIT.mk index d6cfd7e2243..02eb0576c34 100644 --- a/ports/qemu/boards/MICROBIT.mk +++ b/ports/qemu/boards/MICROBIT.mk @@ -11,6 +11,3 @@ LDSCRIPT = mcu/arm/nrf51.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb1.o MPY_CROSS_FLAGS += -march=armv7m - -# These RV32 tests don't run on Thumb, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/MPS2_AN385.mk b/ports/qemu/boards/MPS2_AN385.mk index 8d5c27df57c..182d076eb35 100644 --- a/ports/qemu/boards/MPS2_AN385.mk +++ b/ports/qemu/boards/MPS2_AN385.mk @@ -10,6 +10,3 @@ LDSCRIPT = mcu/arm/mps2.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m - -# These RV32 tests don't run on Thumb, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/NETDUINO2.mk b/ports/qemu/boards/NETDUINO2.mk index f88ea32e7e5..ffa781f3399 100644 --- a/ports/qemu/boards/NETDUINO2.mk +++ b/ports/qemu/boards/NETDUINO2.mk @@ -10,6 +10,3 @@ LDSCRIPT = mcu/arm/stm32.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o MPY_CROSS_FLAGS += -march=armv7m - -# These RV32 tests don't run on Thumb, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/rv32' diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE.mk index d3f4e14d480..839b3d6ac4f 100644 --- a/ports/qemu/boards/SABRELITE.mk +++ b/ports/qemu/boards/SABRELITE.mk @@ -16,4 +16,4 @@ SRC_BOARD_O = shared/runtime/gchelper_generic.o MPY_CROSS_FLAGS += -march=armv6 # These tests don't work on Cortex-A9, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/rv32|inlineasm/thumb/(asmdiv|asmspecialregs).py' +RUN_TESTS_ARGS = --exclude 'inlineasm/thumb/(asmdiv|asmspecialregs).py' diff --git a/ports/qemu/boards/VIRT_RV32.mk b/ports/qemu/boards/VIRT_RV32.mk index 80dd5d56fdc..dd926480052 100644 --- a/ports/qemu/boards/VIRT_RV32.mk +++ b/ports/qemu/boards/VIRT_RV32.mk @@ -10,7 +10,4 @@ SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o MPY_CROSS_FLAGS += -march=rv32imc -# These Thumb tests don't run on RV32, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/thumb' - RUN_NATMODTESTS_ARGS = --arch rv32imc diff --git a/tests/feature_check/inlineasm_rv32.py b/tests/feature_check/inlineasm_rv32.py new file mode 100644 index 00000000000..21dd103b6c3 --- /dev/null +++ b/tests/feature_check/inlineasm_rv32.py @@ -0,0 +1,9 @@ +# check if RISC-V 32 inline asm is supported + + +@micropython.asm_rv32 +def f(): + add(a0, a0, a0) + + +print("rv32") diff --git a/tests/feature_check/inlineasm_rv32.py.exp b/tests/feature_check/inlineasm_rv32.py.exp new file mode 100644 index 00000000000..5eecf09c224 --- /dev/null +++ b/tests/feature_check/inlineasm_rv32.py.exp @@ -0,0 +1 @@ +rv32 diff --git a/tests/feature_check/inlineasm_thumb.py b/tests/feature_check/inlineasm_thumb.py new file mode 100644 index 00000000000..321eab0e2f8 --- /dev/null +++ b/tests/feature_check/inlineasm_thumb.py @@ -0,0 +1,9 @@ +# check if Thumb inline asm is supported + + +@micropython.asm_thumb +def f(): + nop() + + +print("thumb") diff --git a/tests/feature_check/inlineasm_thumb.py.exp b/tests/feature_check/inlineasm_thumb.py.exp new file mode 100644 index 00000000000..bb48e1a2f03 --- /dev/null +++ b/tests/feature_check/inlineasm_thumb.py.exp @@ -0,0 +1 @@ +thumb diff --git a/tests/feature_check/inlineasm_xtensa.py b/tests/feature_check/inlineasm_xtensa.py new file mode 100644 index 00000000000..2a24d39973c --- /dev/null +++ b/tests/feature_check/inlineasm_xtensa.py @@ -0,0 +1,9 @@ +# check if Xtensa inline asm is supported + + +@micropython.asm_xtensa +def f(): + ret_n() + + +print("xtensa") diff --git a/tests/feature_check/inlineasm_xtensa.py.exp b/tests/feature_check/inlineasm_xtensa.py.exp new file mode 100644 index 00000000000..036142c5097 --- /dev/null +++ b/tests/feature_check/inlineasm_xtensa.py.exp @@ -0,0 +1 @@ +xtensa diff --git a/tests/run-tests.py b/tests/run-tests.py index db5ebe34cc3..3dc0df029e7 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -232,6 +232,14 @@ def get_test_instance(test_instance, baudrate, user, password): return pyb +def detect_inline_asm_arch(pyb, args): + for arch in ("rv32", "thumb", "xtensa"): + output = run_feature_check(pyb, args, "inlineasm_{}.py".format(arch)) + if output.strip() == arch.encode(): + return arch + return None + + def detect_test_platform(pyb, args): # Run a script to detect various bits of information about the target test instance. output = run_feature_check(pyb, args, "target_info.py") @@ -240,15 +248,19 @@ def detect_test_platform(pyb, args): platform, arch = str(output, "ascii").strip().split() if arch == "None": arch = None + inlineasm_arch = detect_inline_asm_arch(pyb, args) args.platform = platform args.arch = arch if arch and not args.mpy_cross_flags: args.mpy_cross_flags = "-march=" + arch + args.inlineasm_arch = inlineasm_arch print("platform={}".format(platform), end="") if arch: print(" arch={}".format(arch), end="") + if inlineasm_arch: + print(" inlineasm={}".format(inlineasm_arch), end="") print() @@ -601,6 +613,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_io_module = False skip_fstring = False skip_endian = False + skip_inlineasm = False has_complex = True has_coverage = False @@ -661,20 +674,21 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if output != b"a=1\n": skip_fstring = True - # Check if @micropython.asm_thumb supports Thumb2 instructions, and skip such tests if it doesn't - output = run_feature_check(pyb, args, "inlineasm_thumb2.py") - if output != b"thumb2\n": - skip_tests.add("inlineasm/thumb/asmbcc.py") - skip_tests.add("inlineasm/thumb/asmbitops.py") - skip_tests.add("inlineasm/thumb/asmconst.py") - skip_tests.add("inlineasm/thumb/asmdiv.py") - skip_tests.add("inlineasm/thumb/asmfpaddsub.py") - skip_tests.add("inlineasm/thumb/asmfpcmp.py") - skip_tests.add("inlineasm/thumb/asmfpldrstr.py") - skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") - skip_tests.add("inlineasm/thumb/asmfpsqrt.py") - skip_tests.add("inlineasm/thumb/asmit.py") - skip_tests.add("inlineasm/thumb/asmspecialregs.py") + if args.inlineasm_arch == "thumb": + # Check if @micropython.asm_thumb supports Thumb2 instructions, and skip such tests if it doesn't + output = run_feature_check(pyb, args, "inlineasm_thumb2.py") + if output != b"thumb2\n": + skip_tests.add("inlineasm/thumb/asmbcc.py") + skip_tests.add("inlineasm/thumb/asmbitops.py") + skip_tests.add("inlineasm/thumb/asmconst.py") + skip_tests.add("inlineasm/thumb/asmdiv.py") + skip_tests.add("inlineasm/thumb/asmfpaddsub.py") + skip_tests.add("inlineasm/thumb/asmfpcmp.py") + skip_tests.add("inlineasm/thumb/asmfpldrstr.py") + skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") + skip_tests.add("inlineasm/thumb/asmfpsqrt.py") + skip_tests.add("inlineasm/thumb/asmit.py") + skip_tests.add("inlineasm/thumb/asmspecialregs.py") # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") @@ -699,6 +713,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): ) skip_endian = upy_byteorder != cpy_byteorder + skip_inlineasm = args.inlineasm_arch is None + # These tests don't test slice explicitly but rather use it to perform the test misc_slice_tests = ( "builtin_range", @@ -852,6 +868,7 @@ def run_one_test(test_file): is_const = test_name.startswith("const") is_io_module = test_name.startswith("io_") is_fstring = test_name.startswith("string_fstring") + is_inlineasm = test_name.startswith("asm") skip_it = test_file in skip_tests skip_it |= skip_native and is_native @@ -865,6 +882,7 @@ def run_one_test(test_file): skip_it |= skip_revops and "reverse_op" in test_name skip_it |= skip_io_module and is_io_module skip_it |= skip_fstring and is_fstring + skip_it |= skip_inlineasm and is_inlineasm if skip_it: print("skip ", test_file) @@ -1213,17 +1231,17 @@ def main(): "misc", "extmod", ) + if args.inlineasm_arch is not None: + test_dirs += ("inlineasm/{}".format(args.inlineasm_arch),) if args.platform == "pyboard": # run pyboard tests - test_dirs += ("float", "stress", "inlineasm/thumb", "ports/stm32") + test_dirs += ("float", "stress", "ports/stm32") elif args.platform == "mimxrt": - test_dirs += ("float", "stress", "inlineasm/thumb") + test_dirs += ("float", "stress") elif args.platform == "renesas-ra": - test_dirs += ("float", "inlineasm/thumb", "ports/renesas-ra") + test_dirs += ("float", "ports/renesas-ra") elif args.platform == "rp2": test_dirs += ("float", "stress", "thread", "ports/rp2") - if "arm" in args.mpy_cross_flags: - test_dirs += ("inlineasm/thumb",) elif args.platform == "esp32": test_dirs += ("float", "stress", "thread") elif args.platform in ("esp8266", "minimal", "samd", "nrf"): @@ -1247,10 +1265,6 @@ def main(): "float", "ports/qemu", ) - if args.arch == "rv32imc": - test_dirs += ("inlineasm/rv32",) - else: - test_dirs += ("inlineasm/thumb",) elif args.platform == "webassembly": test_dirs += ("float", "ports/webassembly") else: From 61e2931f864129524e0707c7487f804cfe59e84e Mon Sep 17 00:00:00 2001 From: Victor Rajewski Date: Wed, 4 Dec 2024 10:48:29 +1100 Subject: [PATCH 0168/2098] stm32/mboot: Add mboot version string. Adds a configurable version string to a known location at the end of mboot flash section. Also stores the options mboot was built with, eg usb and which filesystems are supported. A board can override the defaults, or disable the version string entirely by setting MBOOT_VERSION_ALLOCATED_BYTES=0. Signed-off-by: Victor Rajewski --- ports/stm32/mboot/Makefile | 20 +++++++++- ports/stm32/mboot/README.md | 16 ++++++++ ports/stm32/mboot/fwupdate.py | 22 ++++++++++ ports/stm32/mboot/stm32_sections.ld | 6 +++ ports/stm32/mboot/version.c | 62 +++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 ports/stm32/mboot/version.c diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 07053b32939..87bced1aee7 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -36,6 +36,12 @@ include $(BOARD_DIR)/mpconfigboard.mk # A board can set MBOOT_TEXT0_ADDR to a custom location where mboot should reside. MBOOT_TEXT0_ADDR ?= 0x08000000 +# The string in MBOOT_VERSION (default defined in version.c if not defined by a +# board) will be stored in the final MBOOT_VERSION_ALLOCATED_BYTES bytes of mboot flash. +# A board can change the size of this region by defining MBOOT_VERSION_ALLOCATED_BYTES. +MBOOT_VERSION_ALLOCATED_BYTES ?= 64 +MBOOT_VERSION_INCLUDE_OPTIONS ?= 1 # if set to 1, this will append build options to version string (see version.c) + USBDEV_DIR=usbdev DFU=$(TOP)/tools/dfu.py PYDFU ?= $(TOP)/tools/pydfu.py @@ -78,9 +84,14 @@ CFLAGS += -DBUILDING_MBOOT=$(BUILDING_MBOOT) CFLAGS += -DMICROPY_HW_STM32WB_FLASH_SYNCRONISATION=0 CFLAGS += -DUSBD_ENABLE_VENDOR_DEVICE_REQUESTS=1 CFLAGS += -DBOOTLOADER_DFU_USB_VID=$(BOOTLOADER_DFU_USB_VID) -DBOOTLOADER_DFU_USB_PID=$(BOOTLOADER_DFU_USB_PID) +ifdef MBOOT_VERSION +CFLAGS += -DMBOOT_VERSION=\"$(MBOOT_VERSION)\" +endif +CFLAGS += -DMBOOT_VERSION_ALLOCATED_BYTES=$(MBOOT_VERSION_ALLOCATED_BYTES) -DMBOOT_VERSION_INCLUDE_OPTIONS=$(MBOOT_VERSION_INCLUDE_OPTIONS) MBOOT_LD_FILES ?= stm32_memory.ld stm32_sections.ld LDFLAGS += -nostdlib -L . $(addprefix -T,$(MBOOT_LD_FILES)) -Map=$(@:.elf=.map) --cref +LDFLAGS += --defsym mboot_version_len=$(MBOOT_VERSION_ALLOCATED_BYTES) LIBS += $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) # Remove uncalled code from the final image. @@ -121,6 +132,7 @@ SRC_C += \ vfs_fat.c \ vfs_lfs.c \ vfs_raw.c \ + version.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ @@ -206,7 +218,7 @@ deploy-stlink: $(BUILD)/firmware.dfu $(BUILD)/firmware.dfu: $(BUILD)/firmware.elf $(ECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $(BUILD)/firmware.bin + $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .mboot_version_text $^ $(BUILD)/firmware.bin $(Q)$(PYTHON) $(DFU) -b $(MBOOT_TEXT0_ADDR):$(BUILD)/firmware.bin $@ $(BUILD)/firmware.hex: $(BUILD)/firmware.elf @@ -231,8 +243,9 @@ GEN_PINS_SRC = $(BUILD)/pins_$(BOARD).c GEN_PINS_HDR = $(HEADER_BUILD)/pins.h GEN_PINS_AF_CONST = $(HEADER_BUILD)/pins_af_const.h GEN_PINS_AF_DEFS = $(HEADER_BUILD)/pins_af_defs.h +GEN_MPVERSION = $(HEADER_BUILD)/mpversion.h -$(OBJ): $(GEN_QSTRDEFS_GENERATED) $(GEN_ROOT_POINTERS) $(GEN_PINS_AF_DEFS) +$(OBJ): $(GEN_QSTRDEFS_GENERATED) $(GEN_ROOT_POINTERS) $(GEN_PINS_AF_DEFS) $(GEN_MPVERSION) $(HEADER_BUILD): $(MKDIR) -p $(BUILD)/genhdr @@ -250,6 +263,9 @@ $(GEN_PINS_AF_DEFS): $(BOARD_PINS) $(MAKE_PINS) ../$(AF_FILE) $(PREFIX_FILE) | $ --output-af-const $(GEN_PINS_AF_CONST) --output-af-defs $(GEN_PINS_AF_DEFS) \ --mboot-mode +$(GEN_MPVERSION): | $(HEADER_BUILD) + $(PYTHON) ../../../py/makeversionhdr.py $@ + ######################################### vpath %.S . $(TOP) diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 221e3a7c3ac..c294041a519 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -89,6 +89,17 @@ How to use the beginning of the chunk when the end is reached. Then use a split raw filesystem to inform mboot of this wrapping. + The version and config options that mboot was built with are stored in a + small, fixed section of bytes at the end of the flash region allocated + for mboot. The length of the fixed section defaults to 64 bytes, but can + be overridden by setting MBOOT_VERSION_ALLOCATED_BYTES. If running + low on flash for the mboot build, this can be reduced or even set to 0. + The version string stored defaults to the micropython git version as + generated by makeversionhdr.py. The default version string can be + overridden by setting MBOOT_VERSION in a board's build files. The version + string is appended with options mboot was built with - see version.c for + details. This can be prevented by setting MBOOT_VERSION_INCLUDE_OPTIONS to 0. + 2. Build the board's main application firmware as usual. 3. Build mboot via: @@ -209,6 +220,11 @@ and signed firmware, and can be deployed via USB DFU, or by copying it to the de internal filesystem (if `MBOOT_FSLOAD` is enabled). `firmware.dfu` is still unencrypted and can be directly flashed with jtag etc. +Retrieving the mboot version in micropython +------------------------------------------- +The function `get_mboot_version` in `fwupdate.py` returns the version mboot was built with, +optionally with build options. + Example: Mboot on PYBv1.x ------------------------- diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index 8578ff4fc90..b28ba2a7821 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -281,3 +281,25 @@ def update_mpy(*args, **kwargs): elems = update_app_elements(*args, **kwargs) if elems: machine.bootloader(elems) + + +def get_mboot_version( + mboot_base=0x0800_0000, # address of start of mboot flash section + mboot_len=0x8000, # length of mboot flash section + mboot_ver_len=64, # length of mboot version section (defined in mboot/Makefile or in board dir) + valid_prefix="mboot-", # prefix that the version string was defined with + include_opts=True, # return the options mboot was built with (set False for just the version) +): + s = "" + for i in range(mboot_ver_len): + c = stm.mem8[mboot_base + mboot_len - mboot_ver_len + i] + if c == 0x00 or c == 0xFF: # have hit padding or empty flash + break + s += chr(c) + if s.startswith(valid_prefix): + if include_opts: + return s + else: + return s.split("+")[0] # optional mboot config info stored after "+" + else: # version hasn't been set, so on the original mboot (i.e. mboot-v1.0.0) + return None diff --git a/ports/stm32/mboot/stm32_sections.ld b/ports/stm32/mboot/stm32_sections.ld index 3302c5f9729..43511f08397 100644 --- a/ports/stm32/mboot/stm32_sections.ld +++ b/ports/stm32/mboot/stm32_sections.ld @@ -47,6 +47,12 @@ SECTIONS _edata = .; } >RAM AT> FLASH_BL + /* Final section of mboot flash reserved for mboot version */ + .mboot_version_text (ORIGIN(FLASH_BL) + LENGTH(FLASH_BL) - mboot_version_len) : + { + KEEP(*(.mboot_version)); + } >FLASH_BL + /* Zeroed-out data section */ .bss : { diff --git a/ports/stm32/mboot/version.c b/ports/stm32/mboot/version.c new file mode 100644 index 00000000000..b9912f61fcb --- /dev/null +++ b/ports/stm32/mboot/version.c @@ -0,0 +1,62 @@ +#include "mboot.h" +#include "genhdr/mpversion.h" + +#if defined(MBOOT_VERSION_ALLOCATED_BYTES) && MBOOT_VERSION_ALLOCATED_BYTES > 0 + +#ifndef MBOOT_VERSION +#define MBOOT_VERSION "mboot-" MICROPY_GIT_TAG +#endif + +#if MBOOT_VERSION_INCLUDE_OPTIONS // if this is defined, append a list of build options e.g. fat.lfs2 +#define MBOOT_VERSION_USB MBOOT_VERSION "+usb" // USB is always included + +#if defined(MBOOT_I2C_SCL) +#define MBOOT_VERSION_I2C MBOOT_VERSION_USB ".i2c" +#else +#define MBOOT_VERSION_I2C MBOOT_VERSION_USB +#endif + +#if MBOOT_ADDRESS_SPACE_64BIT +#define MBOOT_VERSION_64BIT MBOOT_VERSION_I2C ".64" +#else +#define MBOOT_VERSION_64BIT MBOOT_VERSION_I2C +#endif + +#if MBOOT_VFS_FAT +#define MBOOT_VERSION_FAT MBOOT_VERSION_64BIT ".fat" +#else +#define MBOOT_VERSION_FAT MBOOT_VERSION_64BIT +#endif + +#if MBOOT_VFS_LFS1 +#define MBOOT_VERSION_LFS1 MBOOT_VERSION_FAT ".lfs1" +#else +#define MBOOT_VERSION_LFS1 MBOOT_VERSION_FAT +#endif + +#if MBOOT_VFS_LFS2 +#define MBOOT_VERSION_LFS2 MBOOT_VERSION_LFS1 ".lfs2" +#else +#define MBOOT_VERSION_LFS2 MBOOT_VERSION_LFS1 +#endif + +#if MBOOT_VFS_RAW +#define MBOOT_VERSION_RAW MBOOT_VERSION_LFS2 ".raw" +#else +#define MBOOT_VERSION_RAW MBOOT_VERSION_LFS2 +#endif + +#define MBOOT_VERSION_FINAL MBOOT_VERSION_RAW + +#else // MBOOT_VERSION_INCLUDE_OPTIONS + +#define MBOOT_VERSION_FINAL MBOOT_VERSION + +#endif // MBOOT_VERSION_INCLUDE_OPTIONS + +// Ensure we don't overrun the allocated space +_Static_assert(sizeof(MBOOT_VERSION_FINAL) <= MBOOT_VERSION_ALLOCATED_BYTES + 1, "mboot version string is too long"); +// Cuts off the null terminator +const char mboot_version[sizeof(MBOOT_VERSION_FINAL) - 1] __attribute__((section(".mboot_version"))) __attribute__ ((__used__)) = MBOOT_VERSION_FINAL; + +#endif From 3dd605e7de0dc8a04aac77c49709e8acf2ea2dd2 Mon Sep 17 00:00:00 2001 From: Yoctopuce Date: Mon, 13 May 2024 12:00:08 +0200 Subject: [PATCH 0169/2098] shared/timeutils: Add missing mp_uint_t casts. To prevent compiler warnings. Signed-off-by: Yoctopuce --- shared/timeutils/timeutils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/timeutils/timeutils.h b/shared/timeutils/timeutils.h index 82032600f3a..874d16e9764 100644 --- a/shared/timeutils/timeutils.h +++ b/shared/timeutils/timeutils.h @@ -62,7 +62,7 @@ mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, static inline void timeutils_seconds_since_epoch_to_struct_time(uint64_t t, timeutils_struct_time_t *tm) { // TODO this will give incorrect results for dates before 2000/1/1 - timeutils_seconds_since_2000_to_struct_time(t - TIMEUTILS_SECONDS_1970_TO_2000, tm); + timeutils_seconds_since_2000_to_struct_time((mp_uint_t)(t - TIMEUTILS_SECONDS_1970_TO_2000), tm); } // Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based. @@ -78,7 +78,7 @@ static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t m } static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { - return ns / 1000000000ULL; + return (mp_uint_t)(ns / 1000000000ULL); } static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) { From b323b427702a26b655183c00e20ef49e3612f9a2 Mon Sep 17 00:00:00 2001 From: Yoctopuce Date: Mon, 13 May 2024 12:04:16 +0200 Subject: [PATCH 0170/2098] extmod/modsocket: Add missing static in private function definitions. Signed-off-by: Yoctopuce --- extmod/modsocket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/modsocket.c b/extmod/modsocket.c index eedc17b2ffc..dd288b641a0 100644 --- a/extmod/modsocket.c +++ b/extmod/modsocket.c @@ -470,7 +470,7 @@ static const mp_rom_map_elem_t socket_locals_dict_table[] = { static MP_DEFINE_CONST_DICT(socket_locals_dict, socket_locals_dict_table); -mp_uint_t socket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { +static mp_uint_t socket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { mod_network_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->nic == MP_OBJ_NULL) { return MP_STREAM_ERROR; @@ -482,7 +482,7 @@ mp_uint_t socket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) return ret; } -mp_uint_t socket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { +static mp_uint_t socket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { mod_network_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->nic == MP_OBJ_NULL) { return MP_STREAM_ERROR; @@ -494,7 +494,7 @@ mp_uint_t socket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *e return ret; } -mp_uint_t socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { +static mp_uint_t socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { mod_network_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); if (request == MP_STREAM_CLOSE) { if (self->nic != MP_OBJ_NULL) { From 61995b53c349d7ac3f3be08a5d6440de6614e0a8 Mon Sep 17 00:00:00 2001 From: Yoctopuce Date: Mon, 13 May 2024 12:11:31 +0200 Subject: [PATCH 0171/2098] extmod/moddeflate: Add missing size_t cast. To prevent compiler warnings. Signed-off-by: Yoctopuce --- extmod/moddeflate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/moddeflate.c b/extmod/moddeflate.c index c0c3bb26a8b..4afad01618c 100644 --- a/extmod/moddeflate.c +++ b/extmod/moddeflate.c @@ -142,7 +142,7 @@ static bool deflateio_init_read(mp_obj_deflateio_t *self) { } } - size_t window_len = 1 << wbits; + size_t window_len = (size_t)1 << wbits; self->read->window = m_new(uint8_t, window_len); uzlib_uncompress_init(&self->read->decomp, self->read->window, window_len); From 9bb6b50693fe9d813e95af01f1af4d850ebb0b07 Mon Sep 17 00:00:00 2001 From: Yoctopuce Date: Mon, 13 May 2024 12:01:31 +0200 Subject: [PATCH 0172/2098] py/obj: Cast float literals to 64-bit to prevent overflow warning. Fixes compilation warning C4307: '+': integral constant overflow. Signed-off-by: Yoctopuce --- py/obj.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/py/obj.h b/py/obj.h index d942e1bbf7b..8a32bfcf0f1 100644 --- a/py/obj.h +++ b/py/obj.h @@ -184,12 +184,13 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) << 1) | 1)) #if MICROPY_PY_BUILTINS_FLOAT -#define mp_const_float_e MP_ROM_PTR((mp_obj_t)(((0x402df854 & ~3) | 2) + 0x80800000)) -#define mp_const_float_pi MP_ROM_PTR((mp_obj_t)(((0x40490fdb & ~3) | 2) + 0x80800000)) +#define MP_OBJ_NEW_CONST_FLOAT(f) MP_ROM_PTR((mp_obj_t)((((((uint64_t)f) & ~3) | 2) + 0x80800000) & 0xffffffff)) +#define mp_const_float_e MP_OBJ_NEW_CONST_FLOAT(0x402df854) +#define mp_const_float_pi MP_OBJ_NEW_CONST_FLOAT(0x40490fdb) #if MICROPY_PY_MATH_CONSTANTS -#define mp_const_float_tau MP_ROM_PTR((mp_obj_t)(((0x40c90fdb & ~3) | 2) + 0x80800000)) -#define mp_const_float_inf MP_ROM_PTR((mp_obj_t)(((0x7f800000 & ~3) | 2) + 0x80800000)) -#define mp_const_float_nan MP_ROM_PTR((mp_obj_t)(((0xffc00000 & ~3) | 2) + 0x80800000)) +#define mp_const_float_tau MP_OBJ_NEW_CONST_FLOAT(0x40c90fdb) +#define mp_const_float_inf MP_OBJ_NEW_CONST_FLOAT(0x7f800000) +#define mp_const_float_nan MP_OBJ_NEW_CONST_FLOAT(0xffc00000) #endif static inline bool mp_obj_is_float(mp_const_obj_t o) { From 5d12df51fc431ecb159112bf4a40983c2ccba165 Mon Sep 17 00:00:00 2001 From: Yoctopuce Date: Fri, 24 May 2024 11:56:40 +0200 Subject: [PATCH 0173/2098] py/obj: Make literals unsigned in float get/new functions. Fixes gcc warning when -Wsign-conversion is on. Signed-off-by: Yoctopuce --- py/obj.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/obj.h b/py/obj.h index 8a32bfcf0f1..bd31ef11d09 100644 --- a/py/obj.h +++ b/py/obj.h @@ -203,7 +203,7 @@ static inline mp_float_t mp_obj_float_get(mp_const_obj_t o) { union { mp_float_t f; mp_uint_t u; - } num = {.u = ((mp_uint_t)o - 0x80800000) & ~3}; + } num = {.u = ((mp_uint_t)o - 0x80800000u) & ~3u}; return num.f; } static inline mp_obj_t mp_obj_new_float(mp_float_t f) { @@ -211,7 +211,7 @@ static inline mp_obj_t mp_obj_new_float(mp_float_t f) { mp_float_t f; mp_uint_t u; } num = {.f = f}; - return (mp_obj_t)(((num.u & ~0x3) | 2) + 0x80800000); + return (mp_obj_t)(((num.u & ~0x3u) | 2u) + 0x80800000u); } #endif From 91e30df5f2c9e8c2c84277363a3266d09bc7ae49 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 20 Dec 2024 22:34:42 +1100 Subject: [PATCH 0174/2098] tests/run-tests.py: Set __main__ module to __injected_test. When using unittest (for example) with injected mpy files, not only does the name of the main test module need to be `__main__`, but also the `__main__` module should correspond to this injected module. Otherwise the unittest test won't be detected. Signed-off-by: Damien George --- tests/run-tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 3dc0df029e7..6af70cb8368 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -65,7 +65,9 @@ def base_path(*p): import sys, os, io, vfs class __File(io.IOBase): def __init__(self): - sys.modules['__injected_test'].__name__ = '__main__' + module = sys.modules['__injected_test'] + module.__name__ = '__main__' + sys.modules['__main__'] = module self.off = 0 def ioctl(self, request, arg): if request == 4: # MP_STREAM_CLOSE From 6a90e513dea2267372d8e94f1b83f9260141907d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 27 Dec 2024 16:13:59 +1100 Subject: [PATCH 0175/2098] py/asmarm: Fix asm_arm_ldrh_reg_reg_offset to emit correct machine code. Prior to this fix, the assembler generated `LDRH Rd, [Rn, #imm]!`, so the second `LDRH` from the same origin would load from the wrong base. Co-authored-by: Alessandro Gatti Signed-off-by: Damien George --- py/asmarm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/asmarm.c b/py/asmarm.c index 60064907012..634f2db4f6a 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -328,7 +328,7 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { // ldrh rd, [rn, #off] - emit_al(as, 0x1f000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); + emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); } void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { From 510e055c71afd60be1aaae4594ec8f97cf8b9fa9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Jan 2025 13:31:18 +1100 Subject: [PATCH 0176/2098] py/asmarm: Allow function state to be larger than 255. Co-authored-by: Alessandro Gatti Signed-off-by: Damien George --- py/asmarm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index 634f2db4f6a..b20bdebde5b 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -168,13 +168,23 @@ void asm_arm_entry(asm_arm_t *as, int num_locals) { emit_al(as, asm_arm_op_push(as->push_reglist | 1 << ASM_ARM_REG_LR)); if (as->stack_adjust > 0) { - emit_al(as, asm_arm_op_sub_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); + if (as->stack_adjust < 0x100) { + emit_al(as, asm_arm_op_sub_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); + } else { + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, as->stack_adjust); + emit_al(as, asm_arm_op_sub_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + } } } void asm_arm_exit(asm_arm_t *as) { if (as->stack_adjust > 0) { - emit_al(as, asm_arm_op_add_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); + if (as->stack_adjust < 0x100) { + emit_al(as, asm_arm_op_add_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); + } else { + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, as->stack_adjust); + emit_al(as, asm_arm_op_add_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + } } emit_al(as, asm_arm_op_pop(as->push_reglist | (1 << ASM_ARM_REG_PC))); From 966eb003946cdddb0e9236a70c432890117f70ab Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Jan 2025 14:11:28 +1100 Subject: [PATCH 0177/2098] tests/run-tests.py: Implement getcwd on __FS hook filesystem. This method is needed by tests like `extmod/vfs_rom.py`. Signed-off-by: Damien George --- tests/run-tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 6af70cb8368..1df1b35d23b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -84,6 +84,8 @@ def umount(self): pass def chdir(self, path): pass + def getcwd(self): + return "" def stat(self, path): if path == '__injected_test.mpy': return tuple(0 for _ in range(10)) From fca8ea6c3994c5d9ffe088a678d01ec5279d5b6c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Jan 2025 14:11:57 +1100 Subject: [PATCH 0178/2098] tests/extmod/vfs_rom.py: Import errno for test. It's needed by the test. This previously passed because the compiler (actually parser) optimises away errno constants. Signed-off-by: Damien George --- tests/extmod/vfs_rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index 0382c84c558..d416d838705 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -1,7 +1,7 @@ # Test VfsRom filesystem. try: - import sys, struct, os, uctypes, vfs + import errno, sys, struct, os, uctypes, vfs vfs.VfsRom except (ImportError, AttributeError): From bb1e7de5c625e32d10a25fe594ece73f3daadd28 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Jan 2025 15:47:06 +1100 Subject: [PATCH 0179/2098] qemu/boards: Exclude Thumb2 tests and tests failing with native emitter. The `asmbcc`, `asmbitops`, `asmconst` and `asmit` tests fail to compile with mpy-cross on armv6 architecture (used by SABRELITE), so explicitly exclude them. The `math_domain` and `vfs_rom` tests fail when compiled to native machine code, so also exclude those unconditionally. Signed-off-by: Damien George --- ports/qemu/boards/SABRELITE.mk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE.mk index 839b3d6ac4f..98756678302 100644 --- a/ports/qemu/boards/SABRELITE.mk +++ b/ports/qemu/boards/SABRELITE.mk @@ -16,4 +16,7 @@ SRC_BOARD_O = shared/runtime/gchelper_generic.o MPY_CROSS_FLAGS += -march=armv6 # These tests don't work on Cortex-A9, so exclude them. -RUN_TESTS_ARGS = --exclude 'inlineasm/thumb/(asmdiv|asmspecialregs).py' +RUN_TESTS_ARGS += --exclude 'inlineasm/thumb/(asmbcc|asmbitops|asmconst|asmdiv|asmit|asmspecialregs).py' + +# These tests fail with via-mpy and the native (armv6) emitter, so exclude them. +RUN_TESTS_ARGS += --exclude 'extmod/vfs_rom.py|float/math_domain.py' From b2a45014612beb0f1a79342d1921d5ea0fb2af2d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Sep 2024 11:55:23 +1000 Subject: [PATCH 0180/2098] qemu/Makefile: Add test_full target to run a comprehensive test suite. The tests now include `--via-mpy` and `--via-mpy --emit native`, which will test more cases of the native emitter under both ARM and RISC-V. Signed-off-by: Damien George --- ports/qemu/Makefile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index c8c07965f9c..b792ee8ca9c 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -128,6 +128,8 @@ CFLAGS += $(SPECS_FRAGMENT) LDFLAGS += $(SPECS_FRAGMENT) endif +RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../ports/qemu/$<" $(RUN_TESTS_ARGS) + ################################################################################ # Source files and libraries @@ -169,8 +171,13 @@ run: $(BUILD)/firmware.elf .PHONY: test test: $(BUILD)/firmware.elf - $(eval DIRNAME=ports/$(notdir $(CURDIR))) - cd $(TOP)/tests && ./run-tests.py -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_ARGS) $(RUN_TESTS_EXTRA) + cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) $(RUN_TESTS_EXTRA) + +.PHONY: test_full +test_full: $(BUILD)/firmware.elf + cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) + cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy + cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy --emit native .PHONY: test_natmod test_natmod: $(BUILD)/firmware.elf From 495ce91caefda4623b026520e2b8d8709e274843 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Sep 2024 11:56:40 +1000 Subject: [PATCH 0181/2098] tools/ci.sh: Run test_full for qemu port CI. Signed-off-by: Damien George --- tools/ci.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 8d75d7cecf4..374395abd2c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -285,14 +285,14 @@ function ci_qemu_build_arm { make ${MAKEOPTS} -C ports/qemu submodules make ${MAKEOPTS} -C ports/qemu CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 make ${MAKEOPTS} -C ports/qemu clean - make ${MAKEOPTS} -C ports/qemu test - make ${MAKEOPTS} -C ports/qemu BOARD=SABRELITE test + make ${MAKEOPTS} -C ports/qemu test_full + make ${MAKEOPTS} -C ports/qemu BOARD=SABRELITE test_full } function ci_qemu_build_rv32 { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 submodules - make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 test + make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 test_full # Test building and running native .mpy with rv32imc architecture. ci_native_mpy_modules_build rv32imc From 065d45f9ec7d5597bfcfe075d45abb76197c1319 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 8 Nov 2024 12:35:03 -0700 Subject: [PATCH 0182/2098] rp2/mphalport: Fix mp_hal_pin_low/high() for pin>=32. Fixes issue #16190. Signed-off-by: Dryw Wade --- ports/rp2/mphalport.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 33a1073e1df..956db3ec702 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -182,11 +182,11 @@ static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { } static inline void mp_hal_pin_low(mp_hal_pin_obj_t pin) { - gpio_clr_mask(1 << pin); + gpio_clr_mask64(UINT64_C(1) << pin); } static inline void mp_hal_pin_high(mp_hal_pin_obj_t pin) { - gpio_set_mask(1 << pin); + gpio_set_mask64(UINT64_C(1) << pin); } enum mp_hal_pin_interrupt_trigger { From 7964a435ea8cafed185e41eeb6dc24b269773650 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Sat, 9 Nov 2024 15:04:35 -0700 Subject: [PATCH 0183/2098] rp2/machine_bitstream: Tweak MP_HAL_BITSTREAM_NS_OVERHEAD for RP2350. See https://github.com/micropython/micropython/issues/16190#issuecomment-2466155919 Signed-off-by: Dryw Wade --- 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 4ef97f0c868..8411179f1ae 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -32,7 +32,11 @@ #if MICROPY_PY_MACHINE_BITSTREAM +#if PICO_RP2350 +#define MP_HAL_BITSTREAM_NS_OVERHEAD (5) +#else #define MP_HAL_BITSTREAM_NS_OVERHEAD (9) +#endif #if PICO_RISCV From 99ac8193ea299e6d836b076149f561e58226ede6 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 14 Nov 2024 15:02:16 -0700 Subject: [PATCH 0184/2098] rp2/boards/SPARKFUN_PROMICRO_RP2350: Add SparkFun Pro Micro RP2350. Signed-off-by: Dryw Wade --- .../SPARKFUN_PROMICRO_RP2350/board.json | 22 +++++++++++++ .../mpconfigboard.cmake | 4 +++ .../SPARKFUN_PROMICRO_RP2350/mpconfigboard.h | 26 ++++++++++++++++ .../boards/SPARKFUN_PROMICRO_RP2350/pins.csv | 31 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json create mode 100644 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/pins.csv diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json new file mode 100644 index 00000000000..d2bfa80a348 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "External RAM", + "JST-SH", + "RGB LED", + "USB-C" + ], + "images": [ + "DEV-24870-Pro-Micro-RP2350-Feature.jpg" + ], + "mcu": "rp2350", + "product": "Pro Micro RP2350", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/24870", + "vendor": "Sparkfun" +} diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake new file mode 100644 index 00000000000..16f34c68027 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.cmake @@ -0,0 +1,4 @@ +# cmake file for SparkFun Pro Micro RP2350 + +set(PICO_BOARD "sparkfun_promicro_rp2350") +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h new file mode 100644 index 00000000000..e01467d71ab --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/mpconfigboard.h @@ -0,0 +1,26 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun Pro Micro RP2350" +#define MICROPY_HW_FLASH_STORAGE_BYTES (14 * 1024 * 1024) + +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0039) + +#define MICROPY_HW_UART0_TX (0) +#define MICROPY_HW_UART0_RX (1) +#define MICROPY_HW_UART0_CTS (2) +#define MICROPY_HW_UART0_RTS (3) + +#define MICROPY_HW_UART1_TX (8) +#define MICROPY_HW_UART1_RX (9) +#define MICROPY_HW_UART1_CTS (6) +#define MICROPY_HW_UART1_RTS (7) + +#define MICROPY_HW_I2C0_SDA (16) +#define MICROPY_HW_I2C0_SCL (17) + +#define MICROPY_HW_SPI0_SCK (22) +#define MICROPY_HW_SPI0_MOSI (23) +#define MICROPY_HW_SPI0_MISO (20) + +#define MICROPY_HW_PSRAM_CS_PIN (19) +#define MICROPY_HW_ENABLE_PSRAM (1) diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/pins.csv b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/pins.csv new file mode 100644 index 00000000000..07e1b094e91 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/pins.csv @@ -0,0 +1,31 @@ +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 +LED_RGB,GPIO25 +RGB_LED,GPIO25 +NEOPIXEL,GPIO25 From 5a5f0cb462e643a6ca4d5811f803ffad0c2e28d2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 10 Jan 2025 17:11:51 +1100 Subject: [PATCH 0185/2098] github/workflows: Use Python 3.11 for unix settrace jobs. GitHub Actions has updated ubuntu-latest to 24.04, which now defaults CPython to 3.12, which has a known regression with settrace. Fix that by explicitly using CPython 3.11. Signed-off-by: Damien George --- .github/workflows/ports_unix.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 64fd5c902a0..cb7abf06aac 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -173,6 +173,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Build run: source tools/ci.sh && ci_unix_settrace_build - name: Run main test suite @@ -185,6 +190,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Build run: source tools/ci.sh && ci_unix_settrace_stackless_build - name: Run main test suite From 790986b3eda7702fb48502270d90a9dfc164832f Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 10 Jan 2025 22:27:12 +1100 Subject: [PATCH 0186/2098] github/workflows: Use ubuntu-22.04 for unix qemu CI. To use an older version of libffi. Signed-off-by: Damien George --- .github/workflows/ports_unix.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index cb7abf06aac..a095136b4a6 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -219,7 +219,8 @@ jobs: run: tests/run-tests.py --print-failures qemu_mips: - runs-on: ubuntu-latest + # ubuntu-22.04 is needed for older libffi. + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install packages @@ -233,7 +234,8 @@ jobs: run: tests/run-tests.py --print-failures qemu_arm: - runs-on: ubuntu-latest + # ubuntu-22.04 is needed for older libffi. + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install packages @@ -247,7 +249,8 @@ jobs: run: tests/run-tests.py --print-failures qemu_riscv64: - runs-on: ubuntu-latest + # ubuntu-22.04 is needed for older libffi. + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install packages From 6dbb7ab18f4b4c39baf2028e927177c5b2e1b14e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Jan 2025 14:34:13 +1100 Subject: [PATCH 0187/2098] tests/README: Update TLS certificate generation instructions. Fix the command that converts `ec_key.pem` to `ec_key.der`, and increase the certificate validity to 10 years. Signed-off-by: Damien George --- tests/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/README.md b/tests/README.md index 28c1b3a0852..891668c8b87 100644 --- a/tests/README.md +++ b/tests/README.md @@ -222,7 +222,7 @@ need to be re-created by end users. This section is included here for reference A new self-signed RSA key/cert pair can be created with openssl: ``` -$ openssl req -x509 -newkey rsa:2048 -keyout rsa_key.pem -out rsa_cert.pem -days 365 -nodes -subj '/CN=micropython.local/O=MicroPython/C=AU' +$ openssl req -x509 -newkey rsa:2048 -keyout rsa_key.pem -out rsa_cert.pem -days 3650 -nodes -subj '/CN=micropython.local/O=MicroPython/C=AU' ``` In this case CN is: micropython.local @@ -235,6 +235,6 @@ $ openssl x509 -in rsa_cert.pem -out rsa_cert.der -outform DER For elliptic curve tests using key/cert pairs, create a key then a certificate using: ``` $ openssl ecparam -name prime256v1 -genkey -noout -out ec_key.pem -$ openssl x509 -in ec_key.pem -out ec_key.der -outform DER -$ openssl req -new -x509 -key ec_key.pem -out ec_cert.der -outform DER -days 365 -nodes -subj '/CN=micropython.local/O=MicroPython/C=AU' +$ openssl pkey -in ec_key.pem -out ec_key.der -outform DER +$ openssl req -new -x509 -key ec_key.pem -out ec_cert.der -outform DER -days 3650 -nodes -subj '/CN=micropython.local/O=MicroPython/C=AU' ``` From 1b4c969ce0c2ed83bcf366e1b6d977df3d72a9d2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Jan 2025 14:35:54 +1100 Subject: [PATCH 0188/2098] tests/multi_net: Update TLS test certificates and keys. They expired in early January 2025. Signed-off-by: Damien George --- tests/multi_net/ec_cert.der | Bin 471 -> 470 bytes tests/multi_net/ec_key.der | Bin 121 -> 121 bytes tests/multi_net/rsa_cert.der | Bin 867 -> 867 bytes tests/multi_net/rsa_key.der | Bin 1193 -> 1191 bytes tests/multi_net/sslcontext_getpeercert.py.exp | 2 +- .../sslcontext_verify_callback.py.exp | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/multi_net/ec_cert.der b/tests/multi_net/ec_cert.der index a503a39dfe49443dfdecfd79af0eab9f7cc2337d..a13f1c984a6400b7f6c9aef3b23c7f17c5a9c425 100644 GIT binary patch delta 300 zcmcc4e2rP(po#Gk5LYf>W@2Pw5@BBbK`mP-zVF1Xtu-dsLSK44xw+ebi;Y98&EuRc z3p3Ni2~Kiy;=D$t28M2*TrSBJarXUseP>V{LJ zn}}IStn|c^MaBHu$9;av$vo5bx|J?8uTyF}U-4q^6mJIJUO69UiCM-I6!~2WGZzON z1RBV)F^9_Xv52vV2%NhzeV+xx^NkZY0{s4R-Lc&NK+8ZLB(2OMVIbCku7DM+VsZha zoQFGufeVv@x2UZ~;PaN`Knv?v2d{3PKCzK8Y{SoV1&7O?^F&xAGno{GAL~D2zr5#k d{fx-XB7DW{{;4_4Zk57ufz@(zHbgz$1OTp$Y*YXM delta 301 zcmcb{e4Sa}po#G^5LYf>W@2Pw5^)w?ezrgJ-##@5L%*8o*SJ)b*NPf&v2kd%d7QIl zVP={*!AVX|oY%<2z|hddz{J$p)Hn*rH3f2wA>4`mvGtzYzy8Xo*%UP;&dt#pD7; zIZqD;16L-6i2B6C?v~enZEes>_dOHhV)gPw?cx&l9>0fYo4>3To5G~XaQ;Z_c{PuY fW4m1_&yK zNX|V20SBQ(13~}exboMie%5T^o^fqH_-`$4vPi9Of-b+Gi4; bi5j;KbE8XSO8^ax9!x_Rnlq3g4@7ZknF}pC literal 121 zcmV-<0EYiCcLD(c1R$DG+%@_9vL<@B;!Ue6N^g&V-V~d-iN0|&7;OcczVo081_&yK zNX|V20SBQ(13~});(-;eB7fZ5nWl4BUKtT zFflPSFf%hXFfdvd4Kp<`F)=bQGcz?XFp-jDf9?^F=O21pd|LuZnZODyu6IibE#z1s z`nKde31!0ty&(xnjABv~E!X`aBVtlk`>Sx#b)96LjDoRlntv(%?Sq{w4Zf*z=*wE1swL@^; zh0YR31aN#aDagXg2%@)*E{1|3v#$CXf#jCH&W}x7)#_wNS%Xa5GmeYNZ;w@u{w{tc zMO=!epbp6=tf;bOa4Cs0|5X5qf;f6jE0x;t6;lPx2_s?r`dJlRg3^ ze;C_TRaP5zU5RLB0>NjJ0tey0aV+F`0tAIkhC?2`3ZYY#fe zwMflD>q4+n_w{#Z68VRuQ;d7*kG1^4nhD4<|F1G;?NQt-5Whm}VYT0w|}Q9`flSr0q)yf2c89O(is_hBDxF3G;ylM`7gy=8ct78jZarEM&*7270%N!+ C-Y=*C delta 668 zcmV;N0%QH-2IB@JFoFYLFoFU{paTK{0s<5wuk(~YwW*=sF0&t&g6?5ev6!}zBUKtS zFflPTFf=nUGBa8i4Kg(_F)=hSG&3nNd82vUF~b`nsyBcwx1)S%*tF7fz+r5gcg?}?-0W=JLh^VS{n_}5ndgbkjNM#E zeXkTsSPqik{X%6K!NaalOsVJMLA%K#gCLq;Hp8#t=#c(J6S7)!7Q>8tU=dSEbe|0X ze^eMH;6&(TV$iJnzUEGqV6Ytn_D@&do~I zzBDFGklJ|6Hup)hOX8p@CS_01=pxLJV+ZQaTrW_#nJR;9r%0PtlU%ypTOwWppeb1< zX!=G4>wV2{pEqi7s3}RHZsYR$G49>Se_n3zc#@ICJ&P}bGJ_E2Tym)z2%A>9MdM@- zrAVWpJC~*qJq!RvTt*gC42pRvA6fPq?cCpoJYn-VOIm*oMb&h*Zx(Nl4^JbrF;z%P1i=L|5k_i(Ld+c4i{H3Lx8ZCjZR6!qR_Efg@0-J`` CrZBkx diff --git a/tests/multi_net/rsa_key.der b/tests/multi_net/rsa_key.der index c9c535ae639557e4e0023490a8f491a8ebf8985d..658af9bd89923af4381500f78b87cdb212c88301 100644 GIT binary patch literal 1191 zcmV;Y1X%kpf&`-i0RRGm0RaH+5s&8|dR%;40!f*`3N5a8O9?IHSRwkhpl&NF6Z>Ow*yf?Y!(0LY# zX`tqaN3J!rTR)G=>*dvW4ztHlqU(5jY*`M>&u(rA5ZKVs5Vb>a-i6K*M+9(uGbzZz z$_S#jjV^|QA+xUf8G+=MzRr(LTh;1hM_GeR+cS=f$#0KUj{YuwCFSv^Z5Zg!P(iqa zXzDA*VSZN15$Hx;L;gPk0|5X50)hbmLBpst_=Oy|U5u;-Yx1`&a5L^4n)aay#HR>t1#vT0*bMa8NW%yG^KctSGgn0LjRpWj0t0)c@5`$h#`7Dyp&r1oHuim%&fz9TwH_cGOBQ>apf zCXy9~qMYr&Py9+PYug>3Glk3EJ59f&^ayJllI%_dnRr zQKQmgq3OZ}U^RD#H7sLEx0)gDsWylK5i#j>}#l|$@4atQA?uE?0*jB4JV2csvRpjqjQ z%N1zj#^!@i)eNK(clSqCm9>f-JE9ox0)c@5!;F){%syycha&>EVWUWLw0?i^HGfAH z$0b+0^4l@fq-v~=HD69Ml_;!lV1a9?}K@Iiv_OU zTRu=@TQcg>%5A~x;2hY*W9gTuMfjFmPYh|s$*?s1&q0~VaE^=nR!$$2KC8=pHHPo$ zF&LbnnnK3<6+A-LCp=!}1lwt@`DNpN1!WHJdT=b2pllYMi{hlwxoiV9gft`Mzgl+m FrDD&ROFsYr literal 1193 zcmV;a1XlYnf&`@k0RRGm0RaHcHA#8tD#$+6Jr!l6keA;P+b`4E2bpr*i#&OwdE7C> z8zHJUfGxM9d}-LU(sRIJY@2t@!SCGcY}`Wfcp3fK_=%b4iOr1NTt|Jc6iQeQlHdJ8 zWf{T4u24*==i))T$s>axnqM}d^wrK$Gt8E=lU1?~MLOMAX;eyY8>*9M~gtF4&OyIORggw2ogTl5C5%vA;j zf7?)gKYoC()^HO_P(@37TSaQ3@_Nw!v1W}hJqbGm4LYeBKEo{H*;m@(K5Rr9p44n~ z`<|q72dsN@Hp_dFsGC&+0|5X50)hbmJHn%_st#2IC|n(U88#6q^z+{lg8A_8DqM zM9TUR8ky8G9CW)L%&Nd1j=qNlEP$|)U3%`uxu%BvV^-xowxm+&4}4UxaGS#U7Uimh z3Qj$y>E+E`?pGOWI19{1fg*x~LZsS-li6Vjd!SOf$?%fxLE*Ur4}R;11W>?`_D&Wm zR1{Gf)N1K#^D#3p^RU`kI*3;Xt)j&8;P8I(Td0h#3R;SeIhat1+Q=%?RQnRwDTn%C z&W>s?JM{}k2Z!{{b=YJKF!R7o0)c@5>hgq)q8a96ry81^*EVOYrOU6BaXowMHDP@n zUdk|WHeLHi+ENYyvaob_#M4-My~dV5W!@VZf)t5i#E;s;uq5tSZJI zjPERM%2Q*UQwB;t!VjUk_LQ-sM1Qi#cJb6qThdD#7+()3p_k@;6g-;tb&5M@0)c@5 z;ID+9HImxTl$yoIaK;Vb&W-^p_&mUT--XY)t2;BBI}dc-;70)c@5(+c;WulSwl1M#%dkINDRmWD}qh~w7H zC=DYj-@COL?B-UF6u)w>$UJH3iOv+WuVJSqMhgl49CgQiv){ux&BPV05N`nKCB))q z8w%|n=p^fY&yZ?Zz=u@l+`rf2E}qbqJn)P%;0;JiGV98PQ#J@u>?oUL-D$Tq4bB#y z0)c@5ti=jD1%n9^Ie5{)Ol@A;k>S~%AvXZPexb=Rt?|ME^e diff --git a/tests/multi_net/sslcontext_getpeercert.py.exp b/tests/multi_net/sslcontext_getpeercert.py.exp index e7a0ab0b462..a0dfaa4f567 100644 --- a/tests/multi_net/sslcontext_getpeercert.py.exp +++ b/tests/multi_net/sslcontext_getpeercert.py.exp @@ -1,5 +1,5 @@ --- instance0 --- b'client to server' --- instance1 --- -308201d330820179a00302010202144315a7cd8f69febe2640314e7c97d60a2523ad15300a06082a8648ce3d040302303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b3009060355040613024155301e170d3234303131343034353335335a170d3235303131333034353335335a303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b30090603550406130241553059301306072a8648ce3d020106082a8648ce3d0301070342000449b7f5fa687cb25a9464c397508149992f445c860bcf7002958eb4337636c6af840cd4c8cf3b96f2384860d8ae3ee3fa135dba051e8605e62bd871689c6af43ca3533051301d0603551d0e0416041441b3ae171d91e330411d8543ba45e0f2d5b2951b301f0603551d2304183016801441b3ae171d91e330411d8543ba45e0f2d5b2951b300f0603551d130101ff040530030101ff300a06082a8648ce3d04030203480030450220587f61c34739d6fab5802a674dcc54443ae9c87da374078c4ee1cd83f4ad1694022100cfc45dcf264888c6ba2c36e78bd27bb67856d7879a052dd7aa7ecf7215f7b992 +308201d230820179a003020102021403abf0266b125f8ec8dab57c34d655e94ae4d9bb300a06082a8648ce3d040302303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b3009060355040613024155301e170d3235303131343033333531375a170d3335303131323033333531375a303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b30090603550406130241553059301306072a8648ce3d020106082a8648ce3d030107034200042882eb6055f540ddef989ec7ead84281461436745d1b9174a2730f2bc74cf91e1ce62d4ada67129e891ab70e73a34b644b000d8d1e4c43189a3390210f447169a3533051301d0603551d0e0416041410ced497be3800e7b19008504efd0adc39bfe02a301f0603551d2304183016801410ced497be3800e7b19008504efd0adc39bfe02a300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034700304402204b153d2851e7846351383beac1d5b39791810156b0f9672040d3499e14041969022013e32fe207d3bccb7f9859b3140e73074f656c034679135e517b1e9cb05ae5b2 b'server to client' diff --git a/tests/multi_net/sslcontext_verify_callback.py.exp b/tests/multi_net/sslcontext_verify_callback.py.exp index e7a0ab0b462..a0dfaa4f567 100644 --- a/tests/multi_net/sslcontext_verify_callback.py.exp +++ b/tests/multi_net/sslcontext_verify_callback.py.exp @@ -1,5 +1,5 @@ --- instance0 --- b'client to server' --- instance1 --- -308201d330820179a00302010202144315a7cd8f69febe2640314e7c97d60a2523ad15300a06082a8648ce3d040302303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b3009060355040613024155301e170d3234303131343034353335335a170d3235303131333034353335335a303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b30090603550406130241553059301306072a8648ce3d020106082a8648ce3d0301070342000449b7f5fa687cb25a9464c397508149992f445c860bcf7002958eb4337636c6af840cd4c8cf3b96f2384860d8ae3ee3fa135dba051e8605e62bd871689c6af43ca3533051301d0603551d0e0416041441b3ae171d91e330411d8543ba45e0f2d5b2951b301f0603551d2304183016801441b3ae171d91e330411d8543ba45e0f2d5b2951b300f0603551d130101ff040530030101ff300a06082a8648ce3d04030203480030450220587f61c34739d6fab5802a674dcc54443ae9c87da374078c4ee1cd83f4ad1694022100cfc45dcf264888c6ba2c36e78bd27bb67856d7879a052dd7aa7ecf7215f7b992 +308201d230820179a003020102021403abf0266b125f8ec8dab57c34d655e94ae4d9bb300a06082a8648ce3d040302303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b3009060355040613024155301e170d3235303131343033333531375a170d3335303131323033333531375a303f311a301806035504030c116d6963726f707974686f6e2e6c6f63616c31143012060355040a0c0b4d6963726f507974686f6e310b30090603550406130241553059301306072a8648ce3d020106082a8648ce3d030107034200042882eb6055f540ddef989ec7ead84281461436745d1b9174a2730f2bc74cf91e1ce62d4ada67129e891ab70e73a34b644b000d8d1e4c43189a3390210f447169a3533051301d0603551d0e0416041410ced497be3800e7b19008504efd0adc39bfe02a301f0603551d2304183016801410ced497be3800e7b19008504efd0adc39bfe02a300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034700304402204b153d2851e7846351383beac1d5b39791810156b0f9672040d3499e14041969022013e32fe207d3bccb7f9859b3140e73074f656c034679135e517b1e9cb05ae5b2 b'server to client' From e73cf71a246ee456aac0f4d16167e0856846db6b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 4 Jan 2025 15:00:28 +0100 Subject: [PATCH 0189/2098] tests/extmod/re_sub.py: Fix test execution on Python 3.13. This commit fixes a test failure for `extmod/re_sub.py` where the code, whilst being correct, would not make the test pass due to a newer Python version than expected. On Python 3.13, running `tests/extmod/re_sub.py` would yield a deprecation warning about `re.sub` not providing the match count as a keyword parameter. This warning would be embedded in the expected test result and thus the test would always fail. Co-authored-by: stijn Signed-off-by: Alessandro Gatti --- tests/extmod/re_sub.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/extmod/re_sub.py b/tests/extmod/re_sub.py index 2c7c6c10f1a..ecaa66d83d8 100644 --- a/tests/extmod/re_sub.py +++ b/tests/extmod/re_sub.py @@ -10,6 +10,8 @@ print("SKIP") raise SystemExit +import sys + def multiply(m): return str(int(m.group(0)) * 2) @@ -47,7 +49,11 @@ def A(): print(re.sub("a", "b", "c")) # with maximum substitution count specified -print(re.sub("a", "b", "1a2a3a", 2)) +if sys.implementation.name != "micropython": + # On CPython 3.13 and later the substitution count must be a keyword argument. + print(re.sub("a", "b", "1a2a3a", count=2)) +else: + print(re.sub("a", "b", "1a2a3a", 2)) # invalid group try: From 0a9cc9014ac228e5c51a067b7d83a97a539899da Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 13 Nov 2024 16:42:30 -0700 Subject: [PATCH 0190/2098] rp2/boards/SPARKFUN_THINGPLUS_RP2350: Add SparkFun Thing Plus RP2350. Signed-off-by: Dryw Wade --- .../SPARKFUN_THINGPLUS_RP2350/board.json | 27 +++++++++++ .../SPARKFUN_THINGPLUS_RP2350/manifest.py | 6 +++ .../mpconfigboard.cmake | 15 ++++++ .../SPARKFUN_THINGPLUS_RP2350/mpconfigboard.h | 48 +++++++++++++++++++ .../boards/SPARKFUN_THINGPLUS_RP2350/pins.csv | 33 +++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json create mode 100644 ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/manifest.py create mode 100644 ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/pins.csv diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json new file mode 100644 index 00000000000..09bbefe04dc --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json @@ -0,0 +1,27 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Battery Charging", + "Dual-core", + "External Flash", + "External RAM", + "Feather", + "JST-SH", + "RGB LED", + "USB-C", + "WiFi", + "microSD" + ], + "images": [ + "25134-Thing-Plus-RP2350-Feature.jpg" + ], + "mcu": "rp2350", + "product": "Thing Plus RP2350", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/25134", + "vendor": "Sparkfun" +} diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/manifest.py b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/manifest.py new file mode 100644 index 00000000000..4e38f09cdee --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +# Bluetooth +require("aioble") diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.cmake new file mode 100644 index 00000000000..12b51f32a21 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.cmake @@ -0,0 +1,15 @@ +# cmake file for SparkFun Thing Plus RP2350 + +set(PICO_BOARD "sparkfun_thingplus_rp2350") +set(PICO_PLATFORM "rp2350") + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.h new file mode 100644 index 00000000000..f86faa76198 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/mpconfigboard.h @@ -0,0 +1,48 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun Thing Plus RP2350" +#define MICROPY_HW_FLASH_STORAGE_BYTES (14 * 1024 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "ThingPlusRP2350" + +// CYW43 driver configuration. +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) + +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0038) + +#define MICROPY_HW_I2C0_SDA (20) +#define MICROPY_HW_I2C0_SCL (21) + +#define MICROPY_HW_I2C1_SDA (6) +#define MICROPY_HW_I2C1_SCL (7) + +#define MICROPY_HW_SPI0_SCK (2) +#define MICROPY_HW_SPI0_MOSI (3) +#define MICROPY_HW_SPI0_MISO (4) + +#define MICROPY_HW_SPI1_SCK (26) +#define MICROPY_HW_SPI1_MOSI (27) +#define MICROPY_HW_SPI1_MISO (28) + +#define MICROPY_HW_UART0_TX (0) +#define MICROPY_HW_UART0_RX (1) +#define MICROPY_HW_UART0_CTS (18) +#define MICROPY_HW_UART0_RTS (19) + +#define MICROPY_HW_UART1_TX (4) +#define MICROPY_HW_UART1_RX (5) +#define MICROPY_HW_UART1_CTS (6) +#define MICROPY_HW_UART1_RTS (7) + +#define MICROPY_HW_PSRAM_CS_PIN (8) +#define MICROPY_HW_ENABLE_PSRAM (1) diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/pins.csv b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/pins.csv new file mode 100644 index 00000000000..d11ab42cd50 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/pins.csv @@ -0,0 +1,33 @@ +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 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 +LED,EXT_GPIO0 +LED_RGB,GPIO14 +RGB_LED,GPIO14 +NEOPIXEL,GPIO14 From ca71df0081ea634105beebaea063cdca8ef6a9a3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Jan 2025 16:44:23 +1100 Subject: [PATCH 0191/2098] esp32/boards: Remove remaining "id" entries from board.json. This entry was originally used to override the firmware filenames generated by the build server, but these days all filenames should match the board directory name. So, remove the "id" entry and let the default be used. This is a follow-up to 1a99f74063569df0927e1ada0256059fcdef128c (these three boards were added after that change). Signed-off-by: Damien George --- ports/esp32/boards/ESP32_GENERIC_C6/board.json | 1 - ports/esp32/boards/UM_NANOS3/board.json | 1 - ports/esp32/boards/UM_OMGS3/board.json | 1 - 3 files changed, 3 deletions(-) diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/board.json b/ports/esp32/boards/ESP32_GENERIC_C6/board.json index 963d9515be8..cf644f9038a 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_C6/board.json @@ -7,7 +7,6 @@ "BLE", "WiFi" ], - "id": "esp32c6", "images": [ "esp32c6_devkitmini.jpg" ], diff --git a/ports/esp32/boards/UM_NANOS3/board.json b/ports/esp32/boards/UM_NANOS3/board.json index b213a8ad6bd..958f9adf514 100644 --- a/ports/esp32/boards/UM_NANOS3/board.json +++ b/ports/esp32/boards/UM_NANOS3/board.json @@ -13,7 +13,6 @@ "features_non_filterable": [ "TinyPICO Nano Compatible" ], - "id": "nanos3", "images": [ "unexpectedmaker_nanos3.jpg" ], diff --git a/ports/esp32/boards/UM_OMGS3/board.json b/ports/esp32/boards/UM_OMGS3/board.json index e4cb8739148..7e976ad6303 100644 --- a/ports/esp32/boards/UM_OMGS3/board.json +++ b/ports/esp32/boards/UM_OMGS3/board.json @@ -13,7 +13,6 @@ "features_non_filterable": [ "I2C BAT Fuel Gauge" ], - "id": "omgs3", "images": [ "unexpectedmaker_omgs3.jpg" ], From d533c9067a3a2709d56304944e5142ff71830c8d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Jan 2025 17:17:22 +1100 Subject: [PATCH 0192/2098] tools/autobuild: Don't allow a board to change its ID. All board IDs are now the board directory name. Signed-off-by: Damien George --- tools/autobuild/build-downloads.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/autobuild/build-downloads.py b/tools/autobuild/build-downloads.py index c03d98aa5de..d6f6d032469 100755 --- a/tools/autobuild/build-downloads.py +++ b/tools/autobuild/build-downloads.py @@ -65,9 +65,8 @@ def main(repo_path, output_path): ) sys.exit(1) - # Use "id" if specified, otherwise default to board dir (e.g. "PYBV11"). - # We allow boards to override ID for the historical build names. - blob["id"] = blob.get("id", os.path.basename(board_dir)) + # The ID of a board is the board directory (e.g. "PYBV11"). + blob["id"] = os.path.basename(board_dir) # Check for duplicate board IDs. if blob["id"] in board_ids: From 0950f65ac48bb8315a9ae51be0d4099432cad548 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 6 Sep 2024 15:50:35 +0200 Subject: [PATCH 0193/2098] tools/pyboard.py: Wait a bit before accessing the PTY serial port. Some PTY targets, namely `NETDUINO2` and `MICROBIT` under Qemu, take a bit more time to present a REPL than usual. The pyboard tool is a bit too impatient and would bail out before any of those targets had a chance to respond to the raw REPL request. Co-authored-by: Damien George Signed-off-by: Alessandro Gatti --- tools/pyboard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/pyboard.py b/tools/pyboard.py index 8459bcf6556..dac51e7d6b8 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -244,6 +244,8 @@ def __init__(self, cmd): self.close() sys.exit(1) pty = m.group() + # Compensate for some boards taking a bit longer to start + time.sleep(0.1) # rtscts, dsrdtr params are to workaround pyserial bug: # http://stackoverflow.com/questions/34831131/pyserial-does-not-play-well-with-virtual-port self.serial = serial.Serial(pty, interCharTimeout=1, rtscts=True, dsrdtr=True) From 1232a83555968e04ec8e2ceb0d49435aa18de9f0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 6 Sep 2024 18:40:34 +0200 Subject: [PATCH 0194/2098] qemu/mcu/arm: Dump exception cause and registers on machine error. When a CPU exception is raised when emulating a Thumb-capable processor, the default exception handler would simply enter in an endless loop without providing any further information. This commit adds a more complete exception handler that dumps to STDOUT the exception cause and the status of the registers at the moment of the exception. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 1 + ports/qemu/mcu/arm/errorhandler.c | 176 ++++++++++++++++++++++++++++++ ports/qemu/mcu/arm/startup.c | 32 ++++-- 3 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 ports/qemu/mcu/arm/errorhandler.c diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index b792ee8ca9c..f555ca50dea 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -44,6 +44,7 @@ LDFLAGS += -nostdlib LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) SRC_C += \ + mcu/arm/errorhandler.c \ mcu/arm/startup.c \ shared/runtime/semihosting_arm.c \ diff --git a/ports/qemu/mcu/arm/errorhandler.c b/ports/qemu/mcu/arm/errorhandler.c new file mode 100644 index 00000000000..a0e7ef930f0 --- /dev/null +++ b/ports/qemu/mcu/arm/errorhandler.c @@ -0,0 +1,176 @@ +/* + * This file is part of the MicroPython project, https://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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 +#include + +#include "py/runtime.h" + +#if defined(__ARM_ARCH_ISA_THUMB) + +typedef enum _exception_kind_t { + RESET = 1, + NMI = 2, + HARD_FAULT = 3, + MEM_MANAGE = 4, + BUS_FAULT = 5, + USAGE_FAULT = 6, + SV_CALL = 11, + DEBUG_MONITOR = 12, + PENDING_SV = 13, + SYSTEM_TICK = 14, +} exception_kind_t; + +static const char *EXCEPTION_NAMES_TABLE[] = { + "Reserved", + "Reset", + "NMI", + "HardFault", + "MemManage", + "BusFault", + "UsageFault", + "SVCall", + "DebugMonitor", + "PendSV", + "SysTick", + "External interrupt" +}; + +// R0-R15, PSR, Kind +uintptr_t registers_copy[18] = { 0 }; + +__attribute__((naked)) NORETURN void exception_handler(uintptr_t kind) { + // Save registers + __asm volatile ( + "ldr r1, =registers_copy \n" + "str r0, [r1, #68] \n" // Kind + "ldr r0, [sp, #0] \n" // R0 + "str r0, [r1, #0] \n" + "ldr r0, [sp, #4] \n" // R1 + "str r0, [r1, #4] \n" + "ldr r0, [sp, #8] \n" // R2 + "str r0, [r1, #8] \n" + "ldr r0, [sp, #12] \n" // R3 + "str r0, [r1, #12] \n" + "str r4, [r1, #16] \n" + "str r5, [r1, #20] \n" + "str r6, [r1, #24] \n" + "str r7, [r1, #28] \n" + "mov r0, r8 \n" + "str r0, [r1, #32] \n" + "mov r0, r9 \n" + "str r0, [r1, #36] \n" + "mov r0, r10 \n" + "str r0, [r1, #40] \n" + "mov r0, r11 \n" + "str r0, [r1, #44] \n" + "ldr r0, [sp, #16] \n" // R12 + "str r0, [r1, #48] \n" + "mov r0, sp \n" + "sub r0, #32 \n" + "str r0, [r1, #52] \n" + "ldr r0, [sp, #20] \n" // R14 + "str r0, [r1, #56] \n" + "ldr r0, [sp, #24] \n" // R15 + "str r0, [r1, #60] \n" + "ldr r0, [sp, #28] \n" // xPSR + "str r0, [r1, #64] \n" + : + : + : "memory" + ); + + uintptr_t saved_kind = registers_copy[17]; + + switch (saved_kind) { + case RESET: + case NMI: + case HARD_FAULT: + case MEM_MANAGE: + case BUS_FAULT: + case USAGE_FAULT: + case SV_CALL: + case DEBUG_MONITOR: + case PENDING_SV: + case SYSTEM_TICK: + printf(EXCEPTION_NAMES_TABLE[saved_kind]); + break; + default: + if (saved_kind >= 16) { + printf("%s %d", EXCEPTION_NAMES_TABLE[11], saved_kind - 16); + } else { + printf(EXCEPTION_NAMES_TABLE[0]); + } + break; + } + printf(" exception caught:\n"); + printf("R0: %08X R1: %08X R2: %08X R3: %08X\n", registers_copy[0], registers_copy[1], registers_copy[2], registers_copy[3]); + printf("R4: %08X R5: %08X R6: %08X R7: %08X\n", registers_copy[4], registers_copy[5], registers_copy[6], registers_copy[7]); + printf("R8: %08X R9: %08X R10: %08X R11: %08X\n", registers_copy[8], registers_copy[9], registers_copy[10], registers_copy[11]); + printf("R12: %08X R13: %08X R14: %08X R15: %08X\n", registers_copy[12], registers_copy[13], registers_copy[14], registers_copy[15]); + printf("xPSR: %08X\n", registers_copy[16]); + + for (;;) {} +} + +__attribute__((naked)) NORETURN void NMI_Handler(void) { + exception_handler(NMI); +} + +__attribute__((naked)) NORETURN void HardFault_Handler(void) { + exception_handler(HARD_FAULT); +} + +__attribute__((naked)) NORETURN void MemManage_Handler(void) { + exception_handler(MEM_MANAGE); +} + +__attribute__((naked)) NORETURN void BusFault_Handler(void) { + exception_handler(BUS_FAULT); +} + +__attribute__((naked)) NORETURN void UsageFault_Handler(void) { + exception_handler(USAGE_FAULT); +} + +__attribute__((naked)) NORETURN void SVC_Handler(void) { + exception_handler(SV_CALL); +} + +__attribute__((naked)) NORETURN void DebugMon_Handler(void) { + exception_handler(DEBUG_MONITOR); +} + +__attribute__((naked)) NORETURN void PendSV_Handler(void) { + exception_handler(PENDING_SV); +} + +__attribute__((naked)) NORETURN void SysTick_Handler(void) { + exception_handler(SYSTEM_TICK); +} + +#endif diff --git a/ports/qemu/mcu/arm/startup.c b/ports/qemu/mcu/arm/startup.c index 118a5b8006f..dbb75e33922 100644 --- a/ports/qemu/mcu/arm/startup.c +++ b/ports/qemu/mcu/arm/startup.c @@ -28,6 +28,8 @@ #include #include +#include "py/runtime.h" + #include "shared/runtime/semihosting_arm.h" #include "uart.h" @@ -50,7 +52,7 @@ __attribute__((naked)) void Reset_Handler(void) { _start(); } -void Default_Handler(void) { +NORETURN void Default_Handler(void) { for (;;) { } } @@ -74,25 +76,35 @@ __attribute__((naked, section(".isr_vector"))) void isr_vector(void) { #elif defined(__ARM_ARCH_ISA_THUMB) +extern void NMI_Handler(void); +extern void HardFault_Handler(void); +extern void MemManage_Handler(void); +extern void BusFault_Handler(void); +extern void UsageFault_Handler(void); +extern void SVC_Handler(void); +extern void DebugMon_Handler(void); +extern void PendSV_Handler(void); +extern void SysTick_Handler(void); + // ARM architecture with Thumb-only ISA. const uint32_t isr_vector[] __attribute__((section(".isr_vector"))) = { (uint32_t)&_estack, (uint32_t)&Reset_Handler, - (uint32_t)&Default_Handler, // NMI_Handler - (uint32_t)&Default_Handler, // HardFault_Handler - (uint32_t)&Default_Handler, // MemManage_Handler - (uint32_t)&Default_Handler, // BusFault_Handler - (uint32_t)&Default_Handler, // UsageFault_Handler + (uint32_t)&NMI_Handler, + (uint32_t)&HardFault_Handler, + (uint32_t)&MemManage_Handler, + (uint32_t)&BusFault_Handler, + (uint32_t)&UsageFault_Handler, 0, 0, 0, 0, - (uint32_t)&Default_Handler, // SVC_Handler - (uint32_t)&Default_Handler, // DebugMon_Handler + (uint32_t)&SVC_Handler, + (uint32_t)&DebugMon_Handler, 0, - (uint32_t)&Default_Handler, // PendSV_Handler - (uint32_t)&Default_Handler, // SysTick_Handler + (uint32_t)&PendSV_Handler, + (uint32_t)&SysTick_Handler, }; #endif From 3225c1bc66a3cf21c3fc48917123c24131bdbf8b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 6 Sep 2024 20:32:04 +0200 Subject: [PATCH 0195/2098] qemu: Disable native emitter for the MICROBIT board. The Micro:Bit machine definition in Qemu has soft MMU support enabled, which is currently not compatible with the way MicroPython generates code that needs to call back into non-emitted code. As a stop-gap solution, the native code emitter for the MICROBIT board is turned off. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 4 ++++ ports/qemu/mpconfigport.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index f555ca50dea..1b77b179059 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -20,8 +20,12 @@ MICROPY_ROM_TEXT_COMPRESSION ?= 1 ifeq ($(QEMU_ARCH),arm) MICROPY_HEAP_SIZE ?= 122880 +ifeq ($(BOARD),MICROBIT) +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_const.py'))" +else FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_thumb.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif +endif ifeq ($(QEMU_ARCH),riscv32) MICROPY_HEAP_SIZE ?= 122880 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 8f55d284f93..0a25df4d0ac 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -34,8 +34,10 @@ #define MICROPY_EMIT_ARM (1) #define MICROPY_EMIT_INLINE_THUMB (1) #elif defined(__ARM_ARCH_ISA_THUMB) +#if !defined(QEMU_SOC_NRF51) #define MICROPY_EMIT_THUMB (1) #define MICROPY_EMIT_INLINE_THUMB (1) +#endif #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #elif defined(__riscv) #define MICROPY_EMIT_RV32 (1) From e84c9abfc21f57fe93b4d9a05c1d123e3f333880 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 14 Jan 2025 02:26:16 +0100 Subject: [PATCH 0196/2098] qemu/Makefile: Increase GC heap size to 140KiB. This commit increases the GC heap size from 120KiB to 140KiB, as it is needed to make the full test suite pass on SABRELITE when ran through the armv6 native emitter. This is needed as the code output by the armv6 native emitter is limited to 4-bytes opcodes and thus takes more space than other ARM emitters. To keep things aligned, the RV32 port also got its heap size increased even though it is not needed on that platform right now. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 1b77b179059..bf9addc6f32 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -19,7 +19,7 @@ QSTR_DEFS = qstrdefsport.h MICROPY_ROM_TEXT_COMPRESSION ?= 1 ifeq ($(QEMU_ARCH),arm) -MICROPY_HEAP_SIZE ?= 122880 +MICROPY_HEAP_SIZE ?= 143360 ifeq ($(BOARD),MICROBIT) FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_const.py'))" else @@ -27,7 +27,7 @@ FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_thum endif endif ifeq ($(QEMU_ARCH),riscv32) -MICROPY_HEAP_SIZE ?= 122880 +MICROPY_HEAP_SIZE ?= 143360 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif From 928c71638cf13eb0b47aebb917913820b889403f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 14 Jan 2025 01:09:40 +0100 Subject: [PATCH 0197/2098] py/asmarm: Fix locals address loading code generation with large imm. This commit fixes code generation for loading a local's address if its index is greater than 63. The old code blindly encoded the offset into an `ADD Rd, Rn, #imm` opcode, but only the lowest 8 bits would be put into the opcode itself. This commit instead generates a two-opcodes sequence, a constant load into R8, and then an `ADD Rd, Rn, R8` opcode. This fixes `tests/float/math_domain.py` for the qemu/SABRELITE board. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index b20bdebde5b..a1c15d0fa60 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -292,8 +292,15 @@ void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { } void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num) { - // add rd, sp, #local_num*4 - emit_al(as, asm_arm_op_add_imm(rd, ASM_ARM_REG_SP, local_num << 2)); + if (local_num >= 0x40) { + // mov r8, #local_num*4 + // add rd, sp, r8 + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, local_num << 2); + emit_al(as, asm_arm_op_add_reg(rd, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + } else { + // add rd, sp, #local_num*4 + emit_al(as, asm_arm_op_add_imm(rd, ASM_ARM_REG_SP, local_num << 2)); + } } void asm_arm_mov_reg_pcrel(asm_arm_t *as, uint reg_dest, uint label) { From c610199f2d679ae0c8860d149edfece4fc8d49e5 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 14 Jan 2025 01:28:20 +0100 Subject: [PATCH 0198/2098] py/asmarm: Fix halfword loads with larger offsets. This commit fixes code generation for loading halfwords using an offset greater than 255. The old code blindly encoded the offset into a `LDRH Rd, [Rn, #imm]` opcode, but only the lowest 8 bits would be put into the opcode itself. This commit instead generates a two-opcodes sequence, a constant load into R8, and then `LDRH Rd, [Rn, R8]`. This fixes `tests/extmod/vfs_rom.py` for the qemu/SABRELITE board. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index a1c15d0fa60..6fa751b32eb 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -344,8 +344,15 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { } void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { - // ldrh rd, [rn, #off] - emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); + if (byte_offset < 0x100) { + // ldrh rd, [rn, #off] + emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); + } else { + // mov r8, #off + // ldrh rd, [rn, r8] + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); + emit_al(as, 0x19000b0 | (rn << 16) | (rd << 12) | ASM_ARM_REG_R8); + } } void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { From a46e84280774a358eb7419de2c07a1ab2868d853 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 14 Jan 2025 01:30:00 +0100 Subject: [PATCH 0199/2098] qemu/boards/SABRELITE.mk: Remove exception for omitted tests. This commit re-introduces `tests/extmod/vfs_rom.py` and `tests/float/math_domain.py` to the test suite, as the issues that made them fail have now been addressed. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/SABRELITE.mk | 3 --- 1 file changed, 3 deletions(-) diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE.mk index 98756678302..80ea08f12cf 100644 --- a/ports/qemu/boards/SABRELITE.mk +++ b/ports/qemu/boards/SABRELITE.mk @@ -17,6 +17,3 @@ MPY_CROSS_FLAGS += -march=armv6 # These tests don't work on Cortex-A9, so exclude them. RUN_TESTS_ARGS += --exclude 'inlineasm/thumb/(asmbcc|asmbitops|asmconst|asmdiv|asmit|asmspecialregs).py' - -# These tests fail with via-mpy and the native (armv6) emitter, so exclude them. -RUN_TESTS_ARGS += --exclude 'extmod/vfs_rom.py|float/math_domain.py' From a82fc718a74a75d778e485548330414bad85f070 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 14 Jan 2025 09:40:16 +0100 Subject: [PATCH 0200/2098] stm32/mpconfigboard_common: Add MICROPY_HW_SPI_IS_STATIC macro. A board should make this return true if the specified SPI instances should not be deinitialized on soft-reboot. Signed-off-by: iabdalkader --- ports/stm32/mpconfigboard_common.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 5c66b804b2f..e1c9c159ecf 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -187,6 +187,12 @@ #define MICROPY_HW_SPI_IS_RESERVED(spi_id) (false) #endif +// Function to determine if the given spi_id is static or not. +// Static SPI instances can be accessed by the user but are not deinit'd on soft reset. +#ifndef MICROPY_HW_SPI_IS_STATIC +#define MICROPY_HW_SPI_IS_STATIC(spi_id) (false) +#endif + // Function to determine if the given tim_id is reserved for system use or not. #ifndef MICROPY_HW_TIM_IS_RESERVED #define MICROPY_HW_TIM_IS_RESERVED(tim_id) (false) From 5fa960c71912b0c5dace6ccd60a8dba48dc9e2da Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 19 Dec 2024 10:58:21 +0100 Subject: [PATCH 0201/2098] stm32/spi: Retain the state of special SPI buses on soft reboot. Reserved and static SPI buses must remain initialized during a soft reboot as they may be used for SPI flash storage or XIP. Signed-off-by: iabdalkader --- ports/stm32/spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index aa459119c14..96dd170652e 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -548,7 +548,7 @@ void spi_deinit(const spi_t *spi_obj) { void spi_deinit_all(void) { for (int i = 0; i < MP_ARRAY_SIZE(spi_obj); i++) { const spi_t *spi = &spi_obj[i]; - if (spi->spi != NULL) { + if (spi->spi != NULL && !MICROPY_HW_SPI_IS_RESERVED(i + 1) && !MICROPY_HW_SPI_IS_STATIC(i + 1)) { spi_deinit(spi); } } From b79ceeca8fc006c14fb1be5c9be3962f09269a85 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 19 Dec 2024 11:22:01 +0100 Subject: [PATCH 0202/2098] stm32/boards: Reserve SPI bus when it's used for external flash storage. Reserve SPI flash bus used for storage or XIP. Signed-off-by: iabdalkader --- ports/stm32/boards/ADAFRUIT_F405_EXPRESS/mpconfigboard.h | 1 + ports/stm32/boards/LEGO_HUB_NO6/mpconfigboard.h | 1 + ports/stm32/boards/MIKROE_QUAIL/mpconfigboard.h | 1 + ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h | 3 +++ 4 files changed, 6 insertions(+) diff --git a/ports/stm32/boards/ADAFRUIT_F405_EXPRESS/mpconfigboard.h b/ports/stm32/boards/ADAFRUIT_F405_EXPRESS/mpconfigboard.h index a7bfc37df29..c573f942a0a 100644 --- a/ports/stm32/boards/ADAFRUIT_F405_EXPRESS/mpconfigboard.h +++ b/ports/stm32/boards/ADAFRUIT_F405_EXPRESS/mpconfigboard.h @@ -28,6 +28,7 @@ // External SPI Flash config #if !MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE +#define MICROPY_HW_SPI_IS_RESERVED(id) (id == 1) // Reserve SPI flash bus. #define MICROPY_HW_SPIFLASH_SIZE_BITS (16 * 1024 * 1024) // 16 Mbit (2 MByte) #define MICROPY_HW_SPIFLASH_CS (MICROPY_HW_SPI1_NSS) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/mpconfigboard.h b/ports/stm32/boards/LEGO_HUB_NO6/mpconfigboard.h index ffc53fa6fc5..9878b1533ee 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/mpconfigboard.h +++ b/ports/stm32/boards/LEGO_HUB_NO6/mpconfigboard.h @@ -65,6 +65,7 @@ #define MICROPY_HW_SPI2_SCK (pyb_pin_FLASH_SCK) #define MICROPY_HW_SPI2_MISO (pyb_pin_FLASH_MISO) #define MICROPY_HW_SPI2_MOSI (pyb_pin_FLASH_MOSI) +#define MICROPY_HW_SPI_IS_RESERVED(id) (id == 2) // Reserve SPI flash bus. // USB config #define MICROPY_HW_USB_VBUS_DETECT_PIN (pyb_pin_USB_VBUS) diff --git a/ports/stm32/boards/MIKROE_QUAIL/mpconfigboard.h b/ports/stm32/boards/MIKROE_QUAIL/mpconfigboard.h index 4c3fbba0521..d7b21d801f8 100644 --- a/ports/stm32/boards/MIKROE_QUAIL/mpconfigboard.h +++ b/ports/stm32/boards/MIKROE_QUAIL/mpconfigboard.h @@ -71,6 +71,7 @@ // External SPI Flash config (Cypress S25FL164K) #if !MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE +#define MICROPY_HW_SPI_IS_STATIC(id) (id == 3) // Shared with SPIFLASH. #define MICROPY_HW_SPIFLASH_SIZE_BITS (64 * 1024 * 1024) // 64 Mbit (8 MByte) #define MICROPY_HW_SPIFLASH_CS (pin_A13) diff --git a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h index 561c1a9f151..98d6dc6a0b8 100644 --- a/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h +++ b/ports/stm32/boards/WEACT_F411_BLACKPILL/mpconfigboard.h @@ -86,6 +86,9 @@ #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1) #else +// Reserve SPI flash bus. +#define MICROPY_HW_SPI_IS_RESERVED(id) (id == 1) + // Disable internal filesystem to use spiflash. #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) From 624bd48d2fd90fd9e6cd95b82b8caef4121cb1c7 Mon Sep 17 00:00:00 2001 From: peterhinch Date: Sun, 29 Jan 2023 18:09:51 +0000 Subject: [PATCH 0203/2098] docs/reference/isr_rules: Describe issue with hard ISRs and globals. Signed-off-by: Damien George --- docs/reference/isr_rules.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/reference/isr_rules.rst b/docs/reference/isr_rules.rst index ea330acad3a..14010fb207f 100644 --- a/docs/reference/isr_rules.rst +++ b/docs/reference/isr_rules.rst @@ -170,11 +170,17 @@ is partially updated. When the ISR tries to read the object, a crash results. Be on rare, random occasions they can be hard to diagnose. There are ways to circumvent this issue, described in :ref:`Critical Sections ` below. -It is important to be clear about what constitutes the modification of an object. An alteration to a built-in type -such as a dictionary is problematic. Altering the contents of an array or bytearray is not. This is because bytes -or words are written as a single machine code instruction which is not interruptible: in the parlance of real time -programming the write is atomic. A user defined object might instantiate an integer, array or bytearray. It is valid -for both the main loop and the ISR to alter the contents of these. +It is important to be clear about what constitutes the modification of an object. Altering the contents of an array +or bytearray is safe. This is because bytes or words are written as a single machine code instruction which is not +interruptible: in the parlance of real time programming the write is atomic. The same is true of updating a +dictionary item because items are machine words, being integers or pointers to objects. A user defined object might +instantiate an array or bytearray. It is valid for both the main loop and the ISR to alter the contents of these. + +The hazard arises when the structure of an object is altered, notably in the case of dictionaries. Adding or deleting +keys can trigger a rehash. If a hard ISR runs while a rehash is in progress and attempts to access an item, a crash +may occur. Internally globals are implemented as a dictionary. Consequently the main program should create all +necessary globals before starting a process that generates hard interrupts. Application code should also avoid +deleting globals. MicroPython supports integers of arbitrary precision. Values between 2**30 -1 and -2**30 will be stored in a single machine word. Larger values are stored as Python objects. Consequently changes to long integers cannot From f2cd1a3db6d0e9f3cd0025a3167822356f8c1614 Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Thu, 25 Jul 2024 03:45:09 -0400 Subject: [PATCH 0204/2098] lib/mbedtls: Update to mbedtls v3.6.2. Signed-off-by: Glenn Strauss --- extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/mbedtls/mbedtls_config_common.h | 1 + lib/mbedtls | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 154931a6644..532ce83f907 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -219,6 +219,7 @@ if(MICROPY_SSL_MBEDTLS) ${MICROPY_LIB_MBEDTLS_DIR}/library/pkcs12.c ${MICROPY_LIB_MBEDTLS_DIR}/library/pkcs5.c ${MICROPY_LIB_MBEDTLS_DIR}/library/pkparse.c + ${MICROPY_LIB_MBEDTLS_DIR}/library/pk_ecc.c ${MICROPY_LIB_MBEDTLS_DIR}/library/pk_wrap.c ${MICROPY_LIB_MBEDTLS_DIR}/library/pkwrite.c ${MICROPY_LIB_MBEDTLS_DIR}/library/platform.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index c8bc74d268e..6d54ae2222e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -294,6 +294,7 @@ SRC_THIRDPARTY_C += $(addprefix $(MBEDTLS_DIR)/library/,\ pkcs12.c \ pkcs5.c \ pkparse.c \ + pk_ecc.c \ pk_wrap.c \ pkwrite.c \ platform.c \ diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 4028bdf56d2..6ea8540af99 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -79,6 +79,7 @@ #define MBEDTLS_OID_C #define MBEDTLS_PKCS5_C #define MBEDTLS_PK_C +#define MBEDTLS_PK_HAVE_ECC_KEYS #define MBEDTLS_PK_PARSE_C #define MBEDTLS_PLATFORM_C #define MBEDTLS_RSA_C diff --git a/lib/mbedtls b/lib/mbedtls index edb8fec9882..107ea89daae 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit edb8fec9882084344a314368ac7fd957a187519c +Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98 From 8e2da5a519c3c68f0e9a04613e9d3e2a48ec44bb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 9 Oct 2024 14:53:37 +1100 Subject: [PATCH 0205/2098] stm32/boards: Support 'FDCAN' in board pin CSVs. Previously micros with the 'FDCAN' peripheral (as opposed to the older 'CAN' peripheral) needed to rename these pins in the CSVs for the CAN driver to work. The following CSVs in MicroPython still had FDCAN in them: $ rg -t csv -l FDCAN boards boards/stm32h7b3_af.csv boards/stm32h743_af.csv boards/stm32h573_af.csv boards/stm32h723_af.csv boards/stm32g0b1_af.csv Confirmed that this allows CAN to work on NUCLEO_H723ZG board, and that at least one board based on each of the other chips can still compile. Some of these boards could possibly have MICROPY_HW_ENABLE_CAN set and work, now. Signed-off-by: Angus Gratton --- ports/stm32/boards/make-pins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/boards/make-pins.py b/ports/stm32/boards/make-pins.py index 8fb442153fa..1b89fd64154 100755 --- a/ports/stm32/boards/make-pins.py +++ b/ports/stm32/boards/make-pins.py @@ -122,6 +122,9 @@ def add_af(self, af_idx, af_name, af): if af_ext: af_pin = "EXT" + af_pin + # Special case: FDCAN peripheral is named CAN in MicroPython, same as bxCAN + af_fn = af_fn.replace("FDCAN", "CAN") + af_supported = af_fn in SUPPORTED_AF and af_pin in SUPPORTED_AF[af_fn] self._afs.append(PinAf(af_idx, af_fn, af_unit, af_pin, af_supported, af_name)) From 1d8943ac7b57b89efa5400d966957e6613a7604d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Jan 2025 12:05:45 +1100 Subject: [PATCH 0206/2098] stm32/pyb_can: Fix CAN-FD BRS baud initialisation. Was initialising using the Classic CAN bs1/bs2 value, incorrectly. Signed-off-by: Angus Gratton --- ports/stm32/pyb_can.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 563758831cc..dc05f965d81 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -269,8 +269,8 @@ static mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp // Set BRS bit timings. self->can.Init.DataPrescaler = args[ARG_brs_prescaler].u_int; self->can.Init.DataSyncJumpWidth = args[ARG_brs_sjw].u_int; - self->can.Init.DataTimeSeg1 = args[ARG_bs1].u_int; // DataTimeSeg1 = Propagation_segment + Phase_segment_1 - self->can.Init.DataTimeSeg2 = args[ARG_bs2].u_int; + self->can.Init.DataTimeSeg1 = args[ARG_brs_bs1].u_int; // DataTimeSeg1 = Propagation_segment + Phase_segment_1 + self->can.Init.DataTimeSeg2 = args[ARG_brs_bs2].u_int; #else // Init filter banks for classic CAN. can2_start_bank = args[ARG_num_filter_banks].u_int; From e8d3df51dc036337803fbe047fff77266b3b69f4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Jan 2025 12:07:41 +1100 Subject: [PATCH 0207/2098] stm32/pyb_can: Make pyb.CAN baud calculation a little more forgiving. Not every baudrate or sample point combination has an exact match, but getting within 1% on sample point and .1% on baud rate should always be good enough. Because the search goes from shorter bit periods (lowest brp) and increases, the first match which meets this criteria should still mostly be the best available. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/pyb.CAN.rst | 21 ++++++++++++++------- ports/stm32/pyb_can.c | 12 ++++++++++-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index 57a85d54b77..eb21d8223f2 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -67,11 +67,17 @@ Methods :meth:`~CAN.restart()` can be used to leave the bus-off state - *baudrate* if a baudrate other than 0 is provided, this function will try to automatically calculate the CAN nominal bit time (overriding *prescaler*, *bs1* and *bs2*) that satisfies - both the baudrate and the desired *sample_point*. - - *sample_point* given in a percentage of the nominal bit time, the *sample_point* specifies the position - of the bit sample with respect to the whole nominal bit time. The default *sample_point* is 75%. + both the *baudrate* (within .1%) and the desired *sample_point* (to the nearest 1%). For more precise + control over the CAN timing, set the *prescaler*, *bs1* and *bs2* parameters directly. + - *sample_point* specifies the position of the bit sample with respect to the whole nominal bit time, + expressed as an integer percentage of the nominal bit time. The default *sample_point* is 75%. + This parameter is ignored unless *baudrate* is set. - *num_filter_banks* for classic CAN, this is the number of banks that will be assigned to CAN(1), the rest of the 28 are assigned to CAN(2). + + The remaining parameters are only present on boards with CAN FD support, and configure the optional CAN FD + Bit Rate Switch (BRS) feature: + - *brs_prescaler* is the value by which the CAN FD input clock is divided to generate the data bit time quanta. The prescaler can be a value between 1 and 32 inclusive. - *brs_sjw* is the resynchronisation jump width in units of time quanta for data bits; @@ -82,10 +88,11 @@ Methods it can be a value between 1 and 16 inclusive - *brs_baudrate* if a baudrate other than 0 is provided, this function will try to automatically calculate the CAN data bit time (overriding *brs_prescaler*, *brs_bs1* and *brs_bs2*) that satisfies - both the baudrate and the desired *brs_sample_point*. - - *brs_sample_point* given in a percentage of the data bit time, the *brs_sample_point* specifies the position - of the bit sample with respect to the whole data bit time. The default *brs_sample_point* is 75%. - + both the *brs_baudrate* (within .1%) and the desired *brs_sample_point* (to the nearest 1%). For more + precise control over the BRS timing, set the *brs_prescaler*, *brs_bs1* and *brs_bs2* parameters directly. + - *brs_sample_point* specifies the position of the bit sample with respect to the whole nominal bit time, + expressed as an integer percentage of the nominal bit time. The default *brs_sample_point* is 75%. + This parameter is ignored unless *brs_baudrate* is set. The time quanta tq is the basic unit of time for the CAN bus. tq is the CAN prescaler value divided by PCLK1 (the frequency of internal peripheral bus 1); diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index dc05f965d81..723ff6f9dfa 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -25,6 +25,7 @@ */ #include +#include #include "py/objarray.h" #include "py/runtime.h" @@ -199,12 +200,19 @@ static void pyb_can_get_bit_timing(mp_uint_t baudrate, mp_uint_t sample_point, uint32_t max_brp, uint32_t max_bs1, uint32_t max_bs2, uint32_t min_tseg, mp_int_t *bs1_out, mp_int_t *bs2_out, mp_int_t *prescaler_out) { uint32_t can_kern_clk = pyb_can_get_source_freq(); + mp_uint_t max_baud_error = baudrate / 1000; // Allow .1% deviation + const mp_uint_t MAX_SAMPLE_ERROR = 5; // round to nearest 1%, which is the param resolution + sample_point *= 10; // Calculate CAN bit timing. for (uint32_t brp = 1; brp < max_brp; brp++) { for (uint32_t bs1 = min_tseg; bs1 < max_bs1; bs1++) { for (uint32_t bs2 = min_tseg; bs2 < max_bs2; bs2++) { - if ((baudrate == (can_kern_clk / (brp * (1 + bs1 + bs2)))) && - ((sample_point * 10) == (((1 + bs1) * 1000) / (1 + bs1 + bs2)))) { + mp_int_t calc_baud = can_kern_clk / (brp * (1 + bs1 + bs2)); + mp_int_t calc_sample = ((1 + bs1) * 1000) / (1 + bs1 + bs2); + mp_int_t baud_err = baudrate - calc_baud; + mp_int_t sample_err = sample_point - calc_sample; + if (abs(baud_err) < max_baud_error && + abs(sample_err) < MAX_SAMPLE_ERROR) { *bs1_out = bs1; *bs2_out = bs2; *prescaler_out = brp; From 221a4ecf300d903d6043418a9980aa239dcd2879 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Jan 2025 12:11:34 +1100 Subject: [PATCH 0208/2098] stm32/pyb_can: Include requested CAN baudrate in matching error. This is redundant for bxCAN, but for CAN-FD with BRS it's otherwise unclear which set of parameters (baudrate & sample_point or brs_baudrate & brs_sample_point) failed to match. This makes finding a valid combination extra annoying. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/pyb_can.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 723ff6f9dfa..181b3173866 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -222,7 +222,7 @@ static void pyb_can_get_bit_timing(mp_uint_t baudrate, mp_uint_t sample_point, } } - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("couldn't match baudrate and sample point")); + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("couldn't match baudrate %u and sample point %u"), baudrate, sample_point / 10); } // init(mode, prescaler=100, *, sjw=1, bs1=6, bs2=8) From 3a60f32c9db15060a95db69a3d2ca5df33a36f1b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 9 Oct 2024 15:50:39 +1100 Subject: [PATCH 0209/2098] stm32/can: Fix clearing filters on CAN3 (bxCAN). HAL argument is ignored for CAN1, CAN2 but needed for CAN3. Signed-off-by: Angus Gratton --- ports/stm32/can.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/can.c b/ports/stm32/can.c index 27d29207227..490faeba709 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -174,7 +174,7 @@ void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { filter.FilterActivation = DISABLE; filter.BankNumber = bank; - HAL_CAN_ConfigFilter(NULL, &filter); + HAL_CAN_ConfigFilter(&self->can, &filter); } int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms) { From 21b7bd9f44a1e9dff2881b8aa1c72f400d3c7c39 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 May 2024 17:46:47 +1000 Subject: [PATCH 0210/2098] stm32/fdcan: Fix extended CAN ID filtering for stm32g4. The memory bank addresses used for these are independent, can (and must) enable both. Also looks like no need to shrink these if FDCAN2 is added, the Reference Manual is a bit unclear but looks like the peripheral's RAM multiplies out for each additional controller. Signed-off-by: Angus Gratton --- ports/stm32/fdcan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index 26a88609884..04d8135c081 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -86,8 +86,8 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ init->DataSyncJumpWidth = 1; init->DataTimeSeg1 = 1; init->DataTimeSeg2 = 1; - init->StdFiltersNbr = 28; // /2 ? if FDCAN2 is used !!? - init->ExtFiltersNbr = 0; // Not used + init->StdFiltersNbr = 28; + init->ExtFiltersNbr = 8; init->TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; #elif defined(STM32H7) // The dedicated FDCAN RAM is 2560 32-bit words and shared between the FDCAN instances. From 05eb1d8e1535b310712a532c254e28161694a9b9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 13 Aug 2024 09:48:11 +1000 Subject: [PATCH 0211/2098] stm32/boards/ARDUINO_NICLA_VISION: Fix CAN pin assignment. The only STM32H747 pins with CAN function that are also broken out on the board are PB8 and PB9. Signed-off-by: Angus Gratton --- ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index 7bb75091e8b..c845bc91f97 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -186,8 +186,8 @@ extern struct _spi_bdev_t spi_bdev; // FDCAN bus #define MICROPY_HW_CAN1_NAME "FDCAN1" -#define MICROPY_HW_CAN1_TX (pin_A10) -#define MICROPY_HW_CAN1_RX (pin_A9) +#define MICROPY_HW_CAN1_TX (pin_B9) +#define MICROPY_HW_CAN1_RX (pin_B8) #define MICROPY_HW_CAN_IS_RESERVED(id) (id != PYB_CAN_1) // LEDs From b6649b922e0ff7fa263b05d560f4c11a3910d1d9 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 16 Jan 2025 13:47:50 +0100 Subject: [PATCH 0212/2098] stm32/boards: Update Arduino boards to reserve timers and fix USB PID. Reserve timers used for the camera, and fix USB PID because 0x055F is reserved for MicroPython. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 2 +- ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 3 ++- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index 99b92a3793f..fdf3128a360 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -30,6 +30,7 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_ENABLE_MMCARD (0) #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) +#define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) @@ -42,7 +43,6 @@ void GIGA_board_startup(void); #define MICROPY_BOARD_EARLY_INIT GIGA_board_early_init void GIGA_board_early_init(void); -#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) #define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) GIGA_board_enter_bootloader() void GIGA_board_enter_bootloader(void); diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index c845bc91f97..505f8b68901 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -30,6 +30,7 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_ENABLE_MMCARD (0) #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) +#define MICROPY_HW_TIM_IS_RESERVED(id) (id == 3) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) @@ -225,7 +226,7 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE (3000000) #define MICROPY_HW_USB_VID 0x2341 -#define MICROPY_HW_USB_PID 0x045F +#define MICROPY_HW_USB_PID 0x055F #define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 6b77b16093f..900bf186737 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -30,6 +30,7 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_ENABLE_MMCARD (0) #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) +#define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) From d89e71e6c08b7dff21967868887f4503fc8ac5aa Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 7 Jan 2025 14:19:11 +1100 Subject: [PATCH 0213/2098] tools/autobuild,esp32: Template the generation of esp32 port deploy.md. Allows two source files (ports/esp32/boards/deploy.md and deploy_nativeusb.md for boards with only native USB) for all esp32 installation steps, with templated chip name and flash offset inserted via string formatting. The new files add more text to explain the esptool.py port auto-detection, remove the unnecessary -z feature (already enabled by default), and add a bit of troubleshooting and port detection info. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/README.md | 9 ++++ .../boards/ARDUINO_NANO_ESP32/board.json | 3 ++ .../esp32/boards/ARDUINO_NANO_ESP32/deploy.md | 2 + ports/esp32/boards/ESP32_GENERIC/board.json | 3 ++ .../esp32/boards/ESP32_GENERIC_C3/board.json | 5 +- .../esp32/boards/ESP32_GENERIC_C6/board.json | 5 +- .../esp32/boards/ESP32_GENERIC_S2/board.json | 5 +- .../esp32/boards/ESP32_GENERIC_S3/board.json | 5 +- .../boards/LILYGO_TTGO_LORA32/board.json | 3 ++ ports/esp32/boards/LOLIN_C3_MINI/board.json | 5 +- ports/esp32/boards/LOLIN_S2_MINI/board.json | 5 +- ports/esp32/boards/LOLIN_S2_PICO/board.json | 5 +- ports/esp32/boards/M5STACK_ATOM/board.json | 3 ++ .../boards/M5STACK_ATOMS3_LITE/board.json | 5 +- .../boards/M5STACK_ATOMS3_LITE/deploy.md | 22 -------- ports/esp32/boards/M5STACK_NANOC6/board.json | 5 +- .../boards/M5STACK_NANOC6/deploy_nanoc6.md | 18 ------- .../esp32/boards/OLIMEX_ESP32_EVB/board.json | 3 ++ .../esp32/boards/OLIMEX_ESP32_POE/board.json | 3 ++ ports/esp32/boards/SIL_WESP32/board.json | 3 ++ ports/esp32/boards/UM_FEATHERS2/board.json | 6 ++- ports/esp32/boards/UM_FEATHERS2/deploy.md | 50 ----------------- ports/esp32/boards/UM_FEATHERS2NEO/board.json | 6 ++- ports/esp32/boards/UM_FEATHERS2NEO/deploy.md | 50 ----------------- ports/esp32/boards/UM_FEATHERS3/board.json | 6 ++- ports/esp32/boards/UM_FEATHERS3/deploy.md | 52 ------------------ ports/esp32/boards/UM_FEATHERS3NEO/board.json | 6 ++- ports/esp32/boards/UM_FEATHERS3NEO/deploy.md | 52 ------------------ ports/esp32/boards/UM_NANOS3/board.json | 6 ++- ports/esp32/boards/UM_NANOS3/deploy.md | 53 ------------------- .../boards/UM_NANOS3/deploy_flashmode.md | 9 ++++ ports/esp32/boards/UM_OMGS3/board.json | 6 ++- ports/esp32/boards/UM_OMGS3/deploy.md | 53 ------------------- .../esp32/boards/UM_OMGS3/deploy_flashmode.md | 8 +++ ports/esp32/boards/UM_PROS3/board.json | 6 ++- ports/esp32/boards/UM_PROS3/deploy.md | 52 ------------------ .../esp32/boards/UM_RGBTOUCH_MINI/board.json | 6 ++- ports/esp32/boards/UM_RGBTOUCH_MINI/deploy.md | 52 ------------------ ports/esp32/boards/UM_TINYC6/board.json | 6 ++- .../boards/UM_TINYC6/deploy_flashmode.md | 2 + ports/esp32/boards/UM_TINYC6/deploy_tinyc6.md | 18 ------- ports/esp32/boards/UM_TINYPICO/board.json | 5 +- ports/esp32/boards/UM_TINYPICO/deploy.md | 46 ---------------- ports/esp32/boards/UM_TINYS2/board.json | 6 ++- ports/esp32/boards/UM_TINYS2/deploy.md | 50 ----------------- ports/esp32/boards/UM_TINYS3/board.json | 6 ++- ports/esp32/boards/UM_TINYS3/deploy.md | 52 ------------------ ports/esp32/boards/UM_TINYWATCHS3/board.json | 6 ++- ports/esp32/boards/UM_TINYWATCHS3/deploy.md | 52 ------------------ ports/esp32/boards/deploy.md | 48 +++++++++++++++-- ports/esp32/boards/deploy_c3.md | 14 ----- ports/esp32/boards/deploy_c6.md | 14 ----- ports/esp32/boards/deploy_flashmode.md | 9 ++++ ports/esp32/boards/deploy_nativeusb.md | 51 ++++++++++++++++++ ports/esp32/boards/deploy_s2.md | 14 ----- ports/esp32/boards/deploy_s3.md | 14 ----- tools/autobuild/build-downloads.py | 13 ++++- 57 files changed, 267 insertions(+), 755 deletions(-) delete mode 100644 ports/esp32/boards/M5STACK_ATOMS3_LITE/deploy.md delete mode 100644 ports/esp32/boards/M5STACK_NANOC6/deploy_nanoc6.md delete mode 100644 ports/esp32/boards/UM_FEATHERS2/deploy.md delete mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/deploy.md delete mode 100644 ports/esp32/boards/UM_FEATHERS3/deploy.md delete mode 100644 ports/esp32/boards/UM_FEATHERS3NEO/deploy.md delete mode 100644 ports/esp32/boards/UM_NANOS3/deploy.md create mode 100644 ports/esp32/boards/UM_NANOS3/deploy_flashmode.md delete mode 100644 ports/esp32/boards/UM_OMGS3/deploy.md create mode 100644 ports/esp32/boards/UM_OMGS3/deploy_flashmode.md delete mode 100644 ports/esp32/boards/UM_PROS3/deploy.md delete mode 100644 ports/esp32/boards/UM_RGBTOUCH_MINI/deploy.md create mode 100644 ports/esp32/boards/UM_TINYC6/deploy_flashmode.md delete mode 100644 ports/esp32/boards/UM_TINYC6/deploy_tinyc6.md delete mode 100644 ports/esp32/boards/UM_TINYPICO/deploy.md delete mode 100644 ports/esp32/boards/UM_TINYS2/deploy.md delete mode 100644 ports/esp32/boards/UM_TINYS3/deploy.md delete mode 100644 ports/esp32/boards/UM_TINYWATCHS3/deploy.md delete mode 100644 ports/esp32/boards/deploy_c3.md delete mode 100644 ports/esp32/boards/deploy_c6.md create mode 100644 ports/esp32/boards/deploy_flashmode.md create mode 100644 ports/esp32/boards/deploy_nativeusb.md delete mode 100644 ports/esp32/boards/deploy_s2.md delete mode 100644 ports/esp32/boards/deploy_s3.md diff --git a/ports/esp32/README.md b/ports/esp32/README.md index a8bada7510f..bc24e5cd71a 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -229,6 +229,15 @@ files that configure ESP-IDF settings. Some standard `sdkconfig` files are provided in the `boards/` directory, like `boards/sdkconfig.ble`. You can also define custom ones in your board directory. +Deployment instructions usually invoke the `boards/deploy.md` file (for boards +with a USB/Serial converter connection), or the `boards/deploy_nativeusb.md` +file (for boards with only a native USB port connection). These files are +formatted for each board using template strings found in the `boards.json` +files. You can also include the common `boards/deploy_flashmode.md` file if you +have a board which requires manual resetting via the RESET and BOOT buttons. +Boards with unique flashing steps can include custom `deploy.md` file(s). +Existing `board.json` files contain examples of all of these combinations. + See existing board definitions for further examples of configuration. Configuration diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/board.json b/ports/esp32/boards/ARDUINO_NANO_ESP32/board.json index 77aa1929599..9d0016017fc 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/board.json +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/board.json @@ -2,6 +2,9 @@ "deploy": [ "deploy.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/deploy.md b/ports/esp32/boards/ARDUINO_NANO_ESP32/deploy.md index bb32ba75593..36836da9199 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/deploy.md +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/deploy.md @@ -11,3 +11,5 @@ Please note that the DFU bootloader comes factory flashed. Should you for any re entire flash, the DFU bootloader will have to be re-installed. Please follow the instructions [here](https://support.arduino.cc/hc/en-us/articles/9810414060188-Reset-the-Arduino-bootloader-on-the-Nano-ESP32) to do so. + +**Important** From the options below, download the `.app-bin` file for your board. diff --git a/ports/esp32/boards/ESP32_GENERIC/board.json b/ports/esp32/boards/ESP32_GENERIC/board.json index 130f6b88c88..81c38a6ad48 100644 --- a/ports/esp32/boards/ESP32_GENERIC/board.json +++ b/ports/esp32/boards/ESP32_GENERIC/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/board.json b/ports/esp32/boards/ESP32_GENERIC_C3/board.json index 4a81d227a02..fd20cb51bcb 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_C3/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_c3.md" + "../deploy.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/board.json b/ports/esp32/boards/ESP32_GENERIC_C6/board.json index cf644f9038a..7363333f86e 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_C6/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_c6.md" + "../deploy.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/ESP32_GENERIC_S2/board.json b/ports/esp32/boards/ESP32_GENERIC_S2/board.json index 6ebf16be1a2..fde04d9c4b9 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S2/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_S2/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_s2.md" + "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "External Flash", diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/board.json b/ports/esp32/boards/ESP32_GENERIC_S3/board.json index c9794dba861..fd0c9edce09 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_S3/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_s3.md" + "../deploy.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/LILYGO_TTGO_LORA32/board.json b/ports/esp32/boards/LILYGO_TTGO_LORA32/board.json index 525551df3b1..d68a9baadda 100644 --- a/ports/esp32/boards/LILYGO_TTGO_LORA32/board.json +++ b/ports/esp32/boards/LILYGO_TTGO_LORA32/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/LOLIN_C3_MINI/board.json b/ports/esp32/boards/LOLIN_C3_MINI/board.json index c9b91641e52..dd47862d9f7 100644 --- a/ports/esp32/boards/LOLIN_C3_MINI/board.json +++ b/ports/esp32/boards/LOLIN_C3_MINI/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_c3.md" + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/LOLIN_S2_MINI/board.json b/ports/esp32/boards/LOLIN_S2_MINI/board.json index a45d0434d99..a7098c9afa3 100644 --- a/ports/esp32/boards/LOLIN_S2_MINI/board.json +++ b/ports/esp32/boards/LOLIN_S2_MINI/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_s2.md" + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "External Flash", diff --git a/ports/esp32/boards/LOLIN_S2_PICO/board.json b/ports/esp32/boards/LOLIN_S2_PICO/board.json index 6c72e60f805..724f47e1a9d 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/board.json +++ b/ports/esp32/boards/LOLIN_S2_PICO/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "../deploy_s2.md" + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "Display", diff --git a/ports/esp32/boards/M5STACK_ATOM/board.json b/ports/esp32/boards/M5STACK_ATOM/board.json index 4b6c09db3a7..3a1e7ce359c 100644 --- a/ports/esp32/boards/M5STACK_ATOM/board.json +++ b/ports/esp32/boards/M5STACK_ATOM/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "External Flash", diff --git a/ports/esp32/boards/M5STACK_ATOMS3_LITE/board.json b/ports/esp32/boards/M5STACK_ATOMS3_LITE/board.json index d00bb673be8..fe0e97f9f9c 100644 --- a/ports/esp32/boards/M5STACK_ATOMS3_LITE/board.json +++ b/ports/esp32/boards/M5STACK_ATOMS3_LITE/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "deploy.md" + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "https://docs.m5stack.com/en/core/AtomS3%20Lite", "features": [ "BLE", diff --git a/ports/esp32/boards/M5STACK_ATOMS3_LITE/deploy.md b/ports/esp32/boards/M5STACK_ATOMS3_LITE/deploy.md deleted file mode 100644 index 9a20a6a43dc..00000000000 --- a/ports/esp32/boards/M5STACK_ATOMS3_LITE/deploy.md +++ /dev/null @@ -1,22 +0,0 @@ -Program your board using the `esptool.py` program, found -[here](https://github.com/espressif/esptool). - -To place the board in _bootloader mode_ - so `esptool`` can be used - press and -hold the reset button for two seconds. A green LED will flash behind the reset -button when the bootloader mode has been activated. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -From then on program the firmware starting at address 0: - -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0 board-20240105-v1.22.1.bin -``` - -After the firmware has been deployed, press the reset button to reset the device -and start the new firmware. diff --git a/ports/esp32/boards/M5STACK_NANOC6/board.json b/ports/esp32/boards/M5STACK_NANOC6/board.json index 15654341b44..087851ae5ee 100644 --- a/ports/esp32/boards/M5STACK_NANOC6/board.json +++ b/ports/esp32/boards/M5STACK_NANOC6/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "deploy_nanoc6.md" + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/M5STACK_NANOC6/deploy_nanoc6.md b/ports/esp32/boards/M5STACK_NANOC6/deploy_nanoc6.md deleted file mode 100644 index d8c212c5d29..00000000000 --- a/ports/esp32/boards/M5STACK_NANOC6/deploy_nanoc6.md +++ /dev/null @@ -1,18 +0,0 @@ -Program your board using the esptool.py program, found -[here](https://github.com/espressif/esptool). - -To put the NanoC6 into 'update mode', hold the button while connecting the USB -cable. It can be released after the connection is made. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 erase_flash -``` - -From then on program the firmware starting at address 0x0: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 M5STACK_NANOC6-20240602-v1.24.0.bin -``` diff --git a/ports/esp32/boards/OLIMEX_ESP32_EVB/board.json b/ports/esp32/boards/OLIMEX_ESP32_EVB/board.json index de7a74d8a8f..3eb9a5e0a71 100644 --- a/ports/esp32/boards/OLIMEX_ESP32_EVB/board.json +++ b/ports/esp32/boards/OLIMEX_ESP32_EVB/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/OLIMEX_ESP32_POE/board.json b/ports/esp32/boards/OLIMEX_ESP32_POE/board.json index e62b960ccd6..cda26178d75 100644 --- a/ports/esp32/boards/OLIMEX_ESP32_POE/board.json +++ b/ports/esp32/boards/OLIMEX_ESP32_POE/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/SIL_WESP32/board.json b/ports/esp32/boards/SIL_WESP32/board.json index 50dd2cc660d..53a50f3286d 100644 --- a/ports/esp32/boards/SIL_WESP32/board.json +++ b/ports/esp32/boards/SIL_WESP32/board.json @@ -2,6 +2,9 @@ "deploy": [ "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_FEATHERS2/board.json b/ports/esp32/boards/UM_FEATHERS2/board.json index 4de9a7d4f71..b6cacf8c5c7 100644 --- a/ports/esp32/boards/UM_FEATHERS2/board.json +++ b/ports/esp32/boards/UM_FEATHERS2/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_FEATHERS2/deploy.md b/ports/esp32/boards/UM_FEATHERS2/deploy.md deleted file mode 100644 index 24bced3cda9..00000000000 --- a/ports/esp32/boards/UM_FEATHERS2/deploy.md +++ /dev/null @@ -1,50 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your FeatherS2, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash -``` - -### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options -below, then use the following command to program the firmware starting at address -0x1000, remembering to replace `feathers2-micropython-firmware-version.bin` with the -name of the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 feathers2-micropython-firmware-version.bin -``` - -### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 feathers2-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 feathers2-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/board.json b/ports/esp32/boards/UM_FEATHERS2NEO/board.json index 5b79d3195a7..5b9a1935c97 100644 --- a/ports/esp32/boards/UM_FEATHERS2NEO/board.json +++ b/ports/esp32/boards/UM_FEATHERS2NEO/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md b/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md deleted file mode 100644 index b1a116e8074..00000000000 --- a/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md +++ /dev/null @@ -1,50 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your FeatherS2 Neo, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash -``` - -### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x1000, -remembering to replace `feathers2neo-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 feathers2neo-micropython-firmware-version.bin -``` - -### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 feathers2neo-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 feathers2-feathers2neo-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_FEATHERS3/board.json b/ports/esp32/boards/UM_FEATHERS3/board.json index 235d52a12d4..19d2eb105f3 100644 --- a/ports/esp32/boards/UM_FEATHERS3/board.json +++ b/ports/esp32/boards/UM_FEATHERS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_FEATHERS3/deploy.md b/ports/esp32/boards/UM_FEATHERS3/deploy.md deleted file mode 100644 index 3a6a21a5228..00000000000 --- a/ports/esp32/boards/UM_FEATHERS3/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your FeatherS3, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `feathers3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 feathers3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 feathers3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 feathers3-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/board.json b/ports/esp32/boards/UM_FEATHERS3NEO/board.json index 05315820072..178219ce251 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/board.json +++ b/ports/esp32/boards/UM_FEATHERS3NEO/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/deploy.md b/ports/esp32/boards/UM_FEATHERS3NEO/deploy.md deleted file mode 100644 index ea9bb56dbfe..00000000000 --- a/ports/esp32/boards/UM_FEATHERS3NEO/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your FeatherS3 Neo, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `feathers3neo-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 feathers3neo-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 feathers3neo-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 feathers3neo-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_NANOS3/board.json b/ports/esp32/boards/UM_NANOS3/board.json index 958f9adf514..3da4b33c12e 100644 --- a/ports/esp32/boards/UM_NANOS3/board.json +++ b/ports/esp32/boards/UM_NANOS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "./deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_NANOS3/deploy.md b/ports/esp32/boards/UM_NANOS3/deploy.md deleted file mode 100644 index 4285b027538..00000000000 --- a/ports/esp32/boards/UM_NANOS3/deploy.md +++ /dev/null @@ -1,53 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your NANOS3, you have to first put it into download mode. -NANOS3 doesn't include buttons for RESET and IO0, which should be provided by adding external buttons via a carrier board or other method. -To put the NANOS3 into download, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `nanos3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 nanos3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 nanos3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 nanos3-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_NANOS3/deploy_flashmode.md b/ports/esp32/boards/UM_NANOS3/deploy_flashmode.md new file mode 100644 index 00000000000..b200970409f --- /dev/null +++ b/ports/esp32/boards/UM_NANOS3/deploy_flashmode.md @@ -0,0 +1,9 @@ +To flash or erase your NANOS3, you have to first put it into download mode. +NANOS3 doesn't include buttons for RESET and IO0, which should be provided by adding external buttons via a carrier board or other method. +To put the NANOS3 into download, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button + +Now the board is in download mode and the native USB will have enumerated as a serial device. diff --git a/ports/esp32/boards/UM_OMGS3/board.json b/ports/esp32/boards/UM_OMGS3/board.json index 7e976ad6303..4b3cd9b8faf 100644 --- a/ports/esp32/boards/UM_OMGS3/board.json +++ b/ports/esp32/boards/UM_OMGS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "./deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_OMGS3/deploy.md b/ports/esp32/boards/UM_OMGS3/deploy.md deleted file mode 100644 index 09f2ef8c232..00000000000 --- a/ports/esp32/boards/UM_OMGS3/deploy.md +++ /dev/null @@ -1,53 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your OMGS3, you have to first put it into download mode. -OMGS3 doesn't include buttons for RESET and IO0, which should be provided by adding external buttons via a carrier board or other method. -To put the OMGS3 into download, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `omgs3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 omgs3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 omgs3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 omgs3-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_OMGS3/deploy_flashmode.md b/ports/esp32/boards/UM_OMGS3/deploy_flashmode.md new file mode 100644 index 00000000000..93e28d2275b --- /dev/null +++ b/ports/esp32/boards/UM_OMGS3/deploy_flashmode.md @@ -0,0 +1,8 @@ +To flash or erase your OMGS3, you have to first put it into download mode. + +OMGS3 doesn't include buttons for RESET and IO0, which should be provided by adding external buttons via a carrier board or other method. +To put the OMGS3 into download, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button diff --git a/ports/esp32/boards/UM_PROS3/board.json b/ports/esp32/boards/UM_PROS3/board.json index 8efc4e5ab55..3d168b2505a 100644 --- a/ports/esp32/boards/UM_PROS3/board.json +++ b/ports/esp32/boards/UM_PROS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_PROS3/deploy.md b/ports/esp32/boards/UM_PROS3/deploy.md deleted file mode 100644 index d35d7a02fe3..00000000000 --- a/ports/esp32/boards/UM_PROS3/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your ProS3, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `pros3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 pros3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 pros3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 pros3-pros3-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/board.json b/ports/esp32/boards/UM_RGBTOUCH_MINI/board.json index 6b3ec0f0673..4e3940005ef 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/board.json +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/deploy.md b/ports/esp32/boards/UM_RGBTOUCH_MINI/deploy.md deleted file mode 100644 index afe1ff1de87..00000000000 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your RGB Touch Mini, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `rgbtouch_mini-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 rgbtouch_mini-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 rgbtouch_mini-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 rgbtouch_mini-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_TINYC6/board.json b/ports/esp32/boards/UM_TINYC6/board.json index 122b411a304..7bf920d64c1 100644 --- a/ports/esp32/boards/UM_TINYC6/board.json +++ b/ports/esp32/boards/UM_TINYC6/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy_tinyc6.md" + "deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_TINYC6/deploy_flashmode.md b/ports/esp32/boards/UM_TINYC6/deploy_flashmode.md new file mode 100644 index 00000000000..5dc696a24f9 --- /dev/null +++ b/ports/esp32/boards/UM_TINYC6/deploy_flashmode.md @@ -0,0 +1,2 @@ +To put the TinyC6 into 'download mode', hold the _BOOT_ button while connecting +the USB cable. It can be released after the connection is made. diff --git a/ports/esp32/boards/UM_TINYC6/deploy_tinyc6.md b/ports/esp32/boards/UM_TINYC6/deploy_tinyc6.md deleted file mode 100644 index 4e0603a1077..00000000000 --- a/ports/esp32/boards/UM_TINYC6/deploy_tinyc6.md +++ /dev/null @@ -1,18 +0,0 @@ -Program your board using the esptool.py program, found -[here](https://github.com/espressif/esptool). - -To put the TinyC6 into 'download mode', hold the _BOOT_ button while connecting -the USB cable. It can be released after the connection is made. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 erase_flash -``` - -From then on program the firmware starting at address 0x0: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 UM_TINYC6-20240602-v1.24.0.bin -``` diff --git a/ports/esp32/boards/UM_TINYPICO/board.json b/ports/esp32/boards/UM_TINYPICO/board.json index 5ec7913c42e..06584832bb0 100644 --- a/ports/esp32/boards/UM_TINYPICO/board.json +++ b/ports/esp32/boards/UM_TINYPICO/board.json @@ -1,7 +1,10 @@ { "deploy": [ - "deploy.md" + "../deploy.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_TINYPICO/deploy.md b/ports/esp32/boards/UM_TINYPICO/deploy.md deleted file mode 100644 index 8c15d4d4135..00000000000 --- a/ports/esp32/boards/UM_TINYPICO/deploy.md +++ /dev/null @@ -1,46 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -Your TinyPICO has an auto-reset circuit on it, so there is no need to put it into a -download mode first to erase or flash it. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash -``` - -### Mac -```bash -esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x1000, -remembering to replace `tinypico-micropython-firmware-version.bin` with the name of the -firmware you just downloaded: - -From then on program the firmware starting at address 0x1000: - -### Linux -```bash -esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin -``` - -### Mac -```bash -esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 921600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32 --port COM(X) --baud 921600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_TINYS2/board.json b/ports/esp32/boards/UM_TINYS2/board.json index 9bbf9058bb8..9077ca6bb4b 100644 --- a/ports/esp32/boards/UM_TINYS2/board.json +++ b/ports/esp32/boards/UM_TINYS2/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0x1000" + }, "docs": "", "features": [ "Battery Charging", diff --git a/ports/esp32/boards/UM_TINYS2/deploy.md b/ports/esp32/boards/UM_TINYS2/deploy.md deleted file mode 100644 index a46bc9bd1ae..00000000000 --- a/ports/esp32/boards/UM_TINYS2/deploy.md +++ /dev/null @@ -1,50 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your TinyS2, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash -``` - -### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash -``` - -#### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x1000, -remembering to replace `tinys2-micropython-firmware-version.bin` with the name of the -firmware you just downloaded: - -#### Linux -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 tinys2-micropython-firmware-version.bin -``` - -#### Mac -```bash -esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 tinys2-micropython-firmware-version.bin -``` - -#### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 tinys2-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_TINYS3/board.json b/ports/esp32/boards/UM_TINYS3/board.json index 27ae46a249a..5fea9e3a462 100644 --- a/ports/esp32/boards/UM_TINYS3/board.json +++ b/ports/esp32/boards/UM_TINYS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_TINYS3/deploy.md b/ports/esp32/boards/UM_TINYS3/deploy.md deleted file mode 100644 index d65014e0124..00000000000 --- a/ports/esp32/boards/UM_TINYS3/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your TinyS3, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `tinys3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 tinys3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 tinys3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 tinys3-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/UM_TINYWATCHS3/board.json b/ports/esp32/boards/UM_TINYWATCHS3/board.json index 61dadcfb64b..d1330239f1a 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/board.json +++ b/ports/esp32/boards/UM_TINYWATCHS3/board.json @@ -1,7 +1,11 @@ { "deploy": [ - "deploy.md" + "../deploy_flashmode.md", + "../deploy_nativeusb.md" ], + "deploy_options": { + "flash_offset": "0" + }, "docs": "", "features": [ "BLE", diff --git a/ports/esp32/boards/UM_TINYWATCHS3/deploy.md b/ports/esp32/boards/UM_TINYWATCHS3/deploy.md deleted file mode 100644 index bad7d7c7d89..00000000000 --- a/ports/esp32/boards/UM_TINYWATCHS3/deploy.md +++ /dev/null @@ -1,52 +0,0 @@ -Program your board using the latest version of the esptool.py program, found [here](https://github.com/espressif/esptool). - -To flash or erase your TinyWATCH S3, you have to first put it into download mode. -To do this, follow these steps: - -- Press and hold the [BOOT] button -- Press and release the [RESET] button -- Release the [BOOT] button - -Now the board is in download mode and the native USB will have enumerated as a serial device. - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 erase_flash -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) erase_flash -``` - -Now download the version of the firmware you would like to install from the options below, -then use the following command to program the firmware starting at address 0x0, -remembering to replace `tinywatchs3-micropython-firmware-version.bin` with the name of -the firmware you just downloaded: - -### Linux -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0x0 tinywatchs3-micropython-firmware-version.bin -``` - -### Mac -Please do a `ls /dev/cu.usbm*` to determine the port your board has enumerated as. -```bash -esptool.py --chip esp32s3 --port /dev/cu.usbmodem01 write_flash -z 0x0 tinywatchs3-micropython-firmware-version.bin -``` - -### Windows -Change (X) to whatever COM port is being used by the board -```bash -esptool --chip esp32s3 --port COM(X) write_flash -z 0x0 tinywatchs3-micropython-firmware-version.bin -``` diff --git a/ports/esp32/boards/deploy.md b/ports/esp32/boards/deploy.md index 64e683edfc6..aa86a372159 100644 --- a/ports/esp32/boards/deploy.md +++ b/ports/esp32/boards/deploy.md @@ -1,14 +1,54 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). +Program your board using the esptool.py program, found [here](https://docs.espressif.com/projects/esptool/en/latest/{mcu}/). + +*Windows users:* You may find the installed program is called `esptool` instead of `esptool.py`. + +### Erasing If you are putting MicroPython on your board for the first time then you should first erase the entire flash using: ```bash -esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash +esptool.py erase_flash ``` -From then on program the firmware starting at address 0x1000: +`esptool.py` will try to detect the serial port with the ESP32 automatically, +but if this fails or there might be more than one Espressif-based device +attached to your computer then pass the `--port` option with the name of the +target serial port. For example: ```bash -esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20190125-v1.10.bin +esptool.py --port PORTNAME erase_flash ``` + +* On Linux, the port name is usually similar to `/dev/ttyUSB` or `/dev/ttyACM0`. +* On Mac, the port name is usually similar to `/dev/cu.usbmodem01`. +* On Windows, the port name is usually similar to `COM4`. + +### Flashing + +Then deploy the firmware to the board, starting at address {deploy_options[flash_offset]}: + +```bash +esptool.py --baud 460800 write_flash {deploy_options[flash_offset]} ESP32_BOARD_NAME-DATE-VERSION.bin +``` + +Replace `ESP32_BOARD_NAME-DATE-VERSION.bin` with the `.bin` file downloaded from this page. + +As above, if `esptool.py` can't automatically detect the serial port +then you can pass it explicitly on the command line instead. For example: + +```bash +esptool.py --port PORTNAME --baud 460800 write_flash {deploy_options[flash_offset]} ESP32_BOARD_NAME-DATE-VERSION.bin +``` + +### Troubleshooting + +If flashing starts and then fails partway through, try removing the `--baud +460800` option to flash at the slower default speed. + +If these steps don't work, consult the [MicroPython ESP32 Troubleshooting +steps](https://docs.micropython.org/en/latest/esp32/tutorial/intro.html#troubleshooting-installation-problems) +and the [esptool +documentation](https://docs.espressif.com/projects/esptool/en/latest/{mcu}/esptool/basic-options.html). + +**Important**: From the options below, download the ``.bin`` file for your board. diff --git a/ports/esp32/boards/deploy_c3.md b/ports/esp32/boards/deploy_c3.md deleted file mode 100644 index 016ba7cabb3..00000000000 --- a/ports/esp32/boards/deploy_c3.md +++ /dev/null @@ -1,14 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32c3 --port /dev/ttyUSB0 erase_flash -``` - -From then on program the firmware starting at address 0x0: - -```bash -esptool.py --chip esp32c3 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 esp32c3-20220117-v1.18.bin -``` diff --git a/ports/esp32/boards/deploy_c6.md b/ports/esp32/boards/deploy_c6.md deleted file mode 100644 index 941c69cdefb..00000000000 --- a/ports/esp32/boards/deploy_c6.md +++ /dev/null @@ -1,14 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 erase_flash -``` - -From then on program the firmware starting at address 0x0: - -```bash -esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 ESP32_GENERIC_C6-20240602-v1.24.0.bin -``` diff --git a/ports/esp32/boards/deploy_flashmode.md b/ports/esp32/boards/deploy_flashmode.md new file mode 100644 index 00000000000..ff7cbf9a463 --- /dev/null +++ b/ports/esp32/boards/deploy_flashmode.md @@ -0,0 +1,9 @@ +To flash or erase your {product}, you have to first put it into download mode. + +To put the {product} into download, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button + +Now the board is in download mode and the native USB will have enumerated as a serial device. diff --git a/ports/esp32/boards/deploy_nativeusb.md b/ports/esp32/boards/deploy_nativeusb.md new file mode 100644 index 00000000000..c653330ca8d --- /dev/null +++ b/ports/esp32/boards/deploy_nativeusb.md @@ -0,0 +1,51 @@ +Program your board using the esptool.py program, found [here](https://docs.espressif.com/projects/esptool/en/latest/{mcu}/). + +*Windows users:* You may find the installed program is called `esptool` instead of `esptool.py`. + +### Erasing + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py erase_flash +``` + +`esptool.py` will try to detect the serial port with the ESP32 automatically, +but if this fails or there might be more than one Espressif-based device +attached to your computer then pass the `--port` option with the name of the +target serial port. For example: + +```bash +esptool.py --port PORTNAME erase_flash +``` + +* On Linux, the port name is usually similar to `/dev/ttyACM0`. +* On Mac, the port name is usually similar to `/dev/cu.usbmodem01`. +* On Windows, the port name is usually similar to `COM4`. + +### Flashing + +Then deploy the firmware to the board, starting at address {deploy_options[flash_offset]}: + +```bash +esptool.py write_flash {deploy_options[flash_offset]} ESP32_BOARD_NAME-DATE-VERSION.bin +``` + +Replace `ESP32_BOARD_NAME-DATE-VERSION.bin` with the `.bin` file downloaded from this page. + +As above, if `esptool.py` can't automatically detect the serial port +then you can pass it explicitly on the command line instead. For example: + +```bash +esptool.py --port PORTNAME write_flash {deploy_options[flash_offset]} ESP32_BOARD_NAME-DATE-VERSION.bin +``` + +### Troubleshooting + +If these steps don't work, consult the [MicroPython ESP32 Troubleshooting +steps](https://docs.micropython.org/en/latest/esp32/tutorial/intro.html#troubleshooting-installation-problems) +and the [esptool +documentation](https://docs.espressif.com/projects/esptool/en/latest/{mcu}/esptool/basic-options.html). + +**Important**: From the options below, download the ``.bin`` file for your board. diff --git a/ports/esp32/boards/deploy_s2.md b/ports/esp32/boards/deploy_s2.md deleted file mode 100644 index 5b3057087d1..00000000000 --- a/ports/esp32/boards/deploy_s2.md +++ /dev/null @@ -1,14 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash -``` - -From then on program the firmware starting at address 0x1000: - -```bash -esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 board-20210902-v1.17.bin -``` diff --git a/ports/esp32/boards/deploy_s3.md b/ports/esp32/boards/deploy_s3.md deleted file mode 100644 index 7f564a95e31..00000000000 --- a/ports/esp32/boards/deploy_s3.md +++ /dev/null @@ -1,14 +0,0 @@ -Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). - -If you are putting MicroPython on your board for the first time then you should -first erase the entire flash using: - -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash -``` - -From then on program the firmware starting at address 0: - -```bash -esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0 board-20210902-v1.17.bin -``` diff --git a/tools/autobuild/build-downloads.py b/tools/autobuild/build-downloads.py index d6f6d032469..f7411f5980f 100755 --- a/tools/autobuild/build-downloads.py +++ b/tools/autobuild/build-downloads.py @@ -91,7 +91,18 @@ def main(repo_path, output_path): f.write("\n\n## Installation instructions\n") for deploy in blob["deploy"]: with open(os.path.join(board_dir, deploy), "r") as fin: - f.write(fin.read()) + body = fin.read() + # any key in the board.json file can be substituted via Python str.format() + try: + body = body.format(**blob) + except Exception as e: + raise RuntimeError( + "Failed to format deploy file {} according to {}: {}".format( + fin.name, board_json, e + ) + ) + f.write(body) + f.write("\n") # Write the full index for the website to load. with open(os.path.join(output_path, "index.json"), "w") as f: From 600e46800d9f2f7ec27df6e6851a408b9860c7f4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 7 Jan 2025 12:14:30 +1100 Subject: [PATCH 0214/2098] docs/esp32: Update tutorial flashing steps to match deploy.md. Includes fixing the flashing address for newer SoCs, as reported in discussion https://github.com/orgs/micropython/discussions/16417 Also removes some redundant or out of date information, and adds links to the Espressif esptool docs which are quite comprehensive. Information about ESP32_GENERIC variants is moved to the board page, as it only applies to that board. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/tutorial/intro.rst | 125 +++++++++++++--------- ports/esp32/boards/ESP32_GENERIC/board.md | 19 +++- 2 files changed, 86 insertions(+), 58 deletions(-) diff --git a/docs/esp32/tutorial/intro.rst b/docs/esp32/tutorial/intro.rst index cf4d0bcbd2f..660af87448a 100644 --- a/docs/esp32/tutorial/intro.rst +++ b/docs/esp32/tutorial/intro.rst @@ -36,19 +36,26 @@ Getting the firmware The first thing you need to do is download the most recent MicroPython firmware .bin file to load onto your ESP32 device. You can download it from the -`MicroPython downloads page `_. -From here, you have 3 main choices: +`MicroPython download page`_. Search for your particular board on this page. -* Stable firmware builds -* Daily firmware builds -* Daily firmware builds with SPIRAM support +.. note:: If you don't see your specific board on the download page, then it's + very likely that one of the generic firmwares will work. These are + listed at the top of the download page and have names matching the + onboard Espressif chip (i.e. `ESP32 / WROOM`_, `ESP32-C3`_, + `ESP32-S3`_, etc). -If you are just starting with MicroPython, the best bet is to go for the Stable -firmware builds. If you are an advanced, experienced MicroPython ESP32 user -who would like to follow development closely and help with testing new -features, there are daily builds. If your board has SPIRAM support you can -use either the standard firmware or the firmware with SPIRAM support, and in -the latter case you will have access to more RAM for Python objects. + However, you may need to double check with the vendor you purchased + the board from. + +From here, you have a choice to make: + +* Download a stable firmware release. +* Download a daily firmware "Preview" build. + +If you are just starting with MicroPython, the best bet is to go for the stable +Release firmware builds. If you are an advanced, experienced MicroPython ESP32 +user who would like to follow development closely and help with testing new +features, then you may find the Preview builds useful. .. _esp32_flashing: @@ -71,71 +78,83 @@ For best results it is recommended to first erase the entire flash of your device before putting on new MicroPython firmware. Currently we only support esptool.py to copy across the firmware. You can find -this tool here: ``__, or install it +this tool here: ``__, or install it using pip:: pip install esptool -Versions starting with 1.3 support both Python 2.7 and Python 3.4 (or newer). -An older version (at least 1.2.1 is needed) works fine but will require Python -2.7. - Using esptool.py you can erase the flash with the command:: - esptool.py --port /dev/ttyUSB0 erase_flash + esptool.py erase_flash + +.. note:: On Windows, the command may be named ``esptool`` not ``esptool.py`` + +Then deploy the new firmware. Use this command for original ESP32 and ESP32-S2 SoCs:: + + esptool.py --baud 460800 write_flash 0x1000 ESP32_BOARD_NAME-DATE-VERSION.bin -And then deploy the new firmware using:: +Use this command for all other SoCs (including ESP32-S3, ESP32-C3, and all newer chips):: - esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 esp32-20180511-v1.9.4.bin + esptool.py --baud 460800 write_flash 0 ESP32_BOARD_NAME-DATE-VERSION.bin -Notes: +Replace ``ESP32_BOARD_NAME-DATE-VERSION.bin`` with the name of your firmware .bin file. -* You might need to change the "port" setting to something else relevant for your - PC -* You may need to reduce the baudrate if you get errors when flashing - (eg down to 115200 by adding ``--baud 115200`` into the command) -* For some boards with a particular FlashROM configuration you may need to - change the flash mode (eg by adding ``-fm dio`` into the command) -* The filename of the firmware should match the file that you have +.. note:: If you're unsure which command line to use, check the `MicroPython + download page`_ for your board. Each download page shows an accurate command + line example for that board and its SoC. If the above commands run without error then MicroPython should be installed on -your board! +your board! If the command fails, see :ref:`esp32_troubleshooting_install` below. Serial prompt ------------- Once you have the firmware on the device you can access the REPL (Python prompt) -over UART0 (GPIO1=TX, GPIO3=RX), which might be connected to a USB-serial -converter, depending on your board. The baudrate is 115200. +over either UART0, which might be connected to a USB-serial converter depending +on your board, or the chip's built-in USB device. The baudrate is 115200. From here you can now follow the ESP8266 tutorial, because these two Espressif chips are very similar when it comes to using MicroPython on them. The ESP8266 tutorial is found at :ref:`esp8266_tutorial` (but skip the Introduction section). +.. _esp32_troubleshooting_install: + Troubleshooting installation problems ------------------------------------- If you experience problems during flashing or with running firmware immediately -after it, here are troubleshooting recommendations: - -* Be aware of and try to exclude hardware problems. There are 2 common - problems: bad power source quality, and worn-out/defective FlashROM. - Speaking of power source, not just raw amperage is important, but also low - ripple and noise/EMI in general. The most reliable and convenient power - source is a USB port. - -* The flashing instructions above use flashing speed of 460800 baud, which is - good compromise between speed and stability. However, depending on your - module/board, USB-UART converter, cables, host OS, etc., the above baud - rate may be too high and lead to errors. Try a more common 115200 baud - rate instead in such cases. - -* To catch incorrect flash content (e.g. from a defective sector on a chip), - add ``--verify`` switch to the commands above. - -* If you still experience problems with flashing the firmware please - refer to esptool.py project page, https://github.com/espressif/esptool - for additional documentation and a bug tracker where you can report problems. - -* If you are able to flash the firmware but the ``--verify`` option returns - errors even after multiple retries the you may have a defective FlashROM chip. +after flashing, here are some troubleshooting recommendations: + +* Esptool will try to detect the serial port where your ESP32 is connected. If + this doesn't work, or you have multiple serial ports, then you may need to + manually specify the port by adding the ``--port`` option to the start of the + ``esptool.py`` command line. For example, ``esptool.py --port /dev/ttyUSB0 + `` for Linux or ``esptool --port COM4 `` for + Windows. +* If the board isn't responding to esptool at all, it may need to be manually + reset into the bootloader download mode. Look for a button marked "BOOT" or + "IO0" on your board and a second button marked "RESET" or "RST". If you have + both buttons, try these steps: + + 1. Press "BOOT" (or "IO0") and hold it down. + 2. Press "RESET" (or "RST") and immediately release it. + 3. Release "BOOT" (or "IO0"). + 4. Re-run the flashing steps from the download page. + + If your board doesn't have these buttons, consult the board manufacturer's + documentation about entering bootloader download mode. +* If you get errors part-way through the flashing process then try reducing the + speed of data transfer by removing the ``--baud 460800`` argument. +* Hardware problems can cause flashing to fail. There are two common problems: + bad power source quality, and defective hardware (especially very low cost + unbranded development boards). Speaking of power source, not just raw amperage + is important, but also low ripple and noise/EMI in general. The most reliable + and convenient power source is a USB port. +* If you still experience problems with flashing the firmware then please also + refer to the `esptool Troubleshooting documentation`_. + +.. _esptool Troubleshooting documentation: https://docs.espressif.com/projects/esptool/en/latest/esp32/troubleshooting.html +.. _MicroPython download page: https://micropython.org/download/?port=esp32 +.. _ESP32 / WROOM: https://micropython.org/download/ESP32_GENERIC +.. _ESP32-C3: https://micropython.org/download/ESP32_GENERIC_C3 +.. _ESP32-S3: https://micropython.org/download/ESP32_GENERIC_S3 diff --git a/ports/esp32/boards/ESP32_GENERIC/board.md b/ports/esp32/boards/ESP32_GENERIC/board.md index 8a669411d06..9346d18d84a 100644 --- a/ports/esp32/boards/ESP32_GENERIC/board.md +++ b/ports/esp32/boards/ESP32_GENERIC/board.md @@ -1,9 +1,18 @@ The following files are firmware that should work on most ESP32-based boards with 4MiB of flash, including WROOM WROVER, SOLO, PICO, and MINI modules. -If your board is based on a WROVER module, or otherwise has SPIRAM (also known -as PSRAM), then use the "spiram" variant. +This board has multiple variants available: -The "d2wd" variant is for ESP32-D2WD chips (with 2MiB flash), and "unicore" is -for single-core ESP32 chips (e.g. the "SOLO" modules). The "ota" variant sets -up the partition table to allow for Over-the-Air updates. +* If your board is based on a WROVER module, or otherwise has SPIRAM (also known + as PSRAM) then it's recommended to use the "spiram" variant. Look for heading + **Support for SPIRAM / WROVER)**. +* If your board has a ESP32-D2WD chip (with only 2MiB flash) then use the "d2wd" + variant. Look for heading **ESP32 D2WD**. +* If your board has a single-core ESP32 (e.g. the "SOLO" modules) then choose + the "unicore" variant. Look for heading **ESP32 Unicore**. +* If you'd like to perform Over-the-Air updates of the MicroPython firmware, + then choose the "ota" variant. This variant has less room in the flash for + Python files as a result of supporting OTA. Look for heading **Support for + OTA**. + +Otherwise, download the generic variant (under the first heading below). From 6fc18ec647c6c9f399b433aa53b07ee04132d4a8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 7 Jan 2025 17:29:56 +1100 Subject: [PATCH 0215/2098] esp32/boards: Update the product name for some UM boards. The previous deploy.md refactors revealed that these boards had a different "product" entry in boards.json compared to the name given in the board.md file. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/UM_FEATHERS2/board.json | 2 +- ports/esp32/boards/UM_FEATHERS2NEO/board.json | 2 +- ports/esp32/boards/UM_TINYS2/board.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/esp32/boards/UM_FEATHERS2/board.json b/ports/esp32/boards/UM_FEATHERS2/board.json index b6cacf8c5c7..4a59fbe70c2 100644 --- a/ports/esp32/boards/UM_FEATHERS2/board.json +++ b/ports/esp32/boards/UM_FEATHERS2/board.json @@ -24,7 +24,7 @@ "unexpectedmaker_feathers2.jpg" ], "mcu": "esp32s2", - "product": "Feather S2", + "product": "FeatherS2", "thumbnail": "", "url": "https://feathers2.io/", "vendor": "Unexpected Maker" diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/board.json b/ports/esp32/boards/UM_FEATHERS2NEO/board.json index 5b9a1935c97..a8271dfd3df 100644 --- a/ports/esp32/boards/UM_FEATHERS2NEO/board.json +++ b/ports/esp32/boards/UM_FEATHERS2NEO/board.json @@ -24,7 +24,7 @@ "unexpectedmaker_feathers2neo.jpg" ], "mcu": "esp32s2", - "product": "Feather S2 Neo", + "product": "FeatherS2 Neo", "thumbnail": "", "url": "https://unexpectedmaker.com/feathers2-neo", "vendor": "Unexpected Maker" diff --git a/ports/esp32/boards/UM_TINYS2/board.json b/ports/esp32/boards/UM_TINYS2/board.json index 9077ca6bb4b..dc788a4bc04 100644 --- a/ports/esp32/boards/UM_TINYS2/board.json +++ b/ports/esp32/boards/UM_TINYS2/board.json @@ -23,7 +23,7 @@ "unexpectedmaker_tinys2.jpg" ], "mcu": "esp32s2", - "product": "Tiny S2", + "product": "TinyS2", "thumbnail": "", "url": "https://unexpectedmaker.com/tinys2", "vendor": "Unexpected Maker" From 84e0aca0fb3b95a4f6c3a46e1a40894b488ba655 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 15 Jan 2025 11:17:07 +1100 Subject: [PATCH 0216/2098] docs/esp32: Defer to the download page for flashing steps. The user already has it open, and its customised for their particular board. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/tutorial/intro.rst | 72 ++++++++++------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/docs/esp32/tutorial/intro.rst b/docs/esp32/tutorial/intro.rst index 660af87448a..599731ad755 100644 --- a/docs/esp32/tutorial/intro.rst +++ b/docs/esp32/tutorial/intro.rst @@ -62,60 +62,17 @@ features, then you may find the Preview builds useful. Deploying the firmware ---------------------- -Once you have the MicroPython firmware you need to load it onto your ESP32 device. -There are two main steps to do this: first you need to put your device in -bootloader mode, and second you need to copy across the firmware. The exact -procedure for these steps is highly dependent on the particular board and you will -need to refer to its documentation for details. +Once you have the MicroPython firmware you need to load it onto your ESP32 +device. There are two main steps to do this: first you need to put your device +in bootloader mode, and second you need to copy across the firmware. The exact +procedure for these steps is highly dependent on the particular board. -Fortunately, most boards have a USB connector, a USB-serial converter, and the DTR -and RTS pins wired in a special way then deploying the firmware should be easy as -all steps can be done automatically. Boards that have such features -include the Adafruit Feather HUZZAH32, M5Stack, Wemos LOLIN32, and TinyPICO -boards, along with the Espressif DevKitC, PICO-KIT, WROVER-KIT dev-kits. - -For best results it is recommended to first erase the entire flash of your -device before putting on new MicroPython firmware. - -Currently we only support esptool.py to copy across the firmware. You can find -this tool here: ``__, or install it -using pip:: - - pip install esptool - -Using esptool.py you can erase the flash with the command:: - - esptool.py erase_flash - -.. note:: On Windows, the command may be named ``esptool`` not ``esptool.py`` - -Then deploy the new firmware. Use this command for original ESP32 and ESP32-S2 SoCs:: - - esptool.py --baud 460800 write_flash 0x1000 ESP32_BOARD_NAME-DATE-VERSION.bin - -Use this command for all other SoCs (including ESP32-S3, ESP32-C3, and all newer chips):: - - esptool.py --baud 460800 write_flash 0 ESP32_BOARD_NAME-DATE-VERSION.bin - -Replace ``ESP32_BOARD_NAME-DATE-VERSION.bin`` with the name of your firmware .bin file. - -.. note:: If you're unsure which command line to use, check the `MicroPython - download page`_ for your board. Each download page shows an accurate command - line example for that board and its SoC. +Detailed steps can be found on the same `MicroPython download page`_ for your +board. It's recommended that you follow the steps on the download page, as they +are customised for your particular board. If the above commands run without error then MicroPython should be installed on -your board! If the command fails, see :ref:`esp32_troubleshooting_install` below. - -Serial prompt -------------- - -Once you have the firmware on the device you can access the REPL (Python prompt) -over either UART0, which might be connected to a USB-serial converter depending -on your board, or the chip's built-in USB device. The baudrate is 115200. - -From here you can now follow the ESP8266 tutorial, because these two Espressif chips -are very similar when it comes to using MicroPython on them. The ESP8266 tutorial -is found at :ref:`esp8266_tutorial` (but skip the Introduction section). +your board! Skip ahead to :ref:`esp32_serial_prompt`. .. _esp32_troubleshooting_install: @@ -153,6 +110,19 @@ after flashing, here are some troubleshooting recommendations: * If you still experience problems with flashing the firmware then please also refer to the `esptool Troubleshooting documentation`_. +.. _esp32_serial_prompt: + +Serial prompt +------------- + +Once you have the firmware on the device you can access the REPL (Python prompt) +over either UART0, which might be connected to a USB-serial converter depending +on your board, or the chip's built-in USB device. The baudrate is 115200. + +From here you can now follow the ESP8266 tutorial, because these two Espressif chips +are very similar when it comes to using MicroPython on them. The ESP8266 tutorial +is found at :ref:`esp8266_tutorial` (but skip the Introduction section). + .. _esptool Troubleshooting documentation: https://docs.espressif.com/projects/esptool/en/latest/esp32/troubleshooting.html .. _MicroPython download page: https://micropython.org/download/?port=esp32 .. _ESP32 / WROOM: https://micropython.org/download/ESP32_GENERIC From 6db29978ac8954f3686f9eb59dd71b55c3495456 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Jan 2025 15:54:47 +1100 Subject: [PATCH 0217/2098] py/mkrules.mk: Move comment about partial clones outside make rule. Otherwise the comment is printed each time the rule is run. Follow up to fdd606dd5395d9c474775945fa4458a27199653b. Signed-off-by: Damien George --- py/mkrules.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/mkrules.mk b/py/mkrules.mk index 875ddee8523..74978b0d15b 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -248,13 +248,13 @@ clean-prog: .PHONY: clean-prog endif +# If available, do blobless partial clones of submodules to save time and space. +# A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.). +# Fallback to standard submodule update if blobless isn't available (earlier than 2.36.0) submodules: $(ECHO) "Updating submodules: $(GIT_SUBMODULES)" ifneq ($(GIT_SUBMODULES),) $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) - # If available, do blobless partial clones of submodules to save time and space. - # A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.). - # Fallback to standard submodule update if blobless isn't available (earlier than 2.36.0) $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) || \ git submodule update --init $(GIT_SUBMODULES) endif From b4f53a0e51a7bb098f67a747fb1c62c21492a1dd Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Jan 2025 15:16:36 +1100 Subject: [PATCH 0218/2098] qemu/boards: Change boards to use a subdirectory like other ports. This commit moves `.mk` to `/mpconfigboard.mk` for all qemu boards, making it the same as other bare-metal ports. Signed-off-by: Damien George --- ports/qemu/Makefile | 7 ++++++- .../qemu/boards/{MICROBIT.mk => MICROBIT/mpconfigboard.mk} | 0 .../boards/{MPS2_AN385.mk => MPS2_AN385/mpconfigboard.mk} | 0 .../boards/{NETDUINO2.mk => NETDUINO2/mpconfigboard.mk} | 0 .../boards/{SABRELITE.mk => SABRELITE/mpconfigboard.mk} | 0 .../boards/{VIRT_RV32.mk => VIRT_RV32/mpconfigboard.mk} | 0 6 files changed, 6 insertions(+), 1 deletion(-) rename ports/qemu/boards/{MICROBIT.mk => MICROBIT/mpconfigboard.mk} (100%) rename ports/qemu/boards/{MPS2_AN385.mk => MPS2_AN385/mpconfigboard.mk} (100%) rename ports/qemu/boards/{NETDUINO2.mk => NETDUINO2/mpconfigboard.mk} (100%) rename ports/qemu/boards/{SABRELITE.mk => SABRELITE/mpconfigboard.mk} (100%) rename ports/qemu/boards/{VIRT_RV32.mk => VIRT_RV32/mpconfigboard.mk} (100%) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index bf9addc6f32..d2550cf883b 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -2,6 +2,11 @@ # Initial setup of Makefile environment BOARD ?= MPS2_AN385 +BOARD_DIR ?= boards/$(BOARD) + +ifeq ($(wildcard $(BOARD_DIR)/.),) +$(error Invalid BOARD specified: $(BOARD_DIR)) +endif # Make the build directory reflect the board. BUILD ?= build-$(BOARD) @@ -10,7 +15,7 @@ include ../../py/mkenv.mk -include mpconfigport.mk # Include board specific .mk file. -include boards/$(BOARD).mk +include $(BOARD_DIR)/mpconfigboard.mk # qstr definitions (must come before including py.mk) QSTR_DEFS = qstrdefsport.h diff --git a/ports/qemu/boards/MICROBIT.mk b/ports/qemu/boards/MICROBIT/mpconfigboard.mk similarity index 100% rename from ports/qemu/boards/MICROBIT.mk rename to ports/qemu/boards/MICROBIT/mpconfigboard.mk diff --git a/ports/qemu/boards/MPS2_AN385.mk b/ports/qemu/boards/MPS2_AN385/mpconfigboard.mk similarity index 100% rename from ports/qemu/boards/MPS2_AN385.mk rename to ports/qemu/boards/MPS2_AN385/mpconfigboard.mk diff --git a/ports/qemu/boards/NETDUINO2.mk b/ports/qemu/boards/NETDUINO2/mpconfigboard.mk similarity index 100% rename from ports/qemu/boards/NETDUINO2.mk rename to ports/qemu/boards/NETDUINO2/mpconfigboard.mk diff --git a/ports/qemu/boards/SABRELITE.mk b/ports/qemu/boards/SABRELITE/mpconfigboard.mk similarity index 100% rename from ports/qemu/boards/SABRELITE.mk rename to ports/qemu/boards/SABRELITE/mpconfigboard.mk diff --git a/ports/qemu/boards/VIRT_RV32.mk b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk similarity index 100% rename from ports/qemu/boards/VIRT_RV32.mk rename to ports/qemu/boards/VIRT_RV32/mpconfigboard.mk From 87f04d5935ef4e190873f4f31afdc25a58647976 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 18 Jan 2025 12:06:33 +0100 Subject: [PATCH 0219/2098] esp8266/Makefile: Fix local toolchain builds on recent Linux systems. This commit fixes compilation for the ESP8266 port when using a local toolchain on relatively recent Linux systems. The documentation asks the user to delete the esptool instance that comes with the toolchain, in favour of using the one provided by the system. On Linux systems that are at least two years old (looking at the CI Ubuntu image as an example), the version of esptool installed with the package manager isn't called `esptool.py` but just `esptool`. The Makefile didn't take that into account and used `esptool.py` without checking if such a command exists, making builds fail. Now preference is given to the `esptool` command, falling back to `esptool.py` only if the former command does not exist or it is not available to the current user, to maintain compatibility with old setups. Signed-off-by: Alessandro Gatti --- ports/esp8266/Makefile | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index cd94a38a726..1721075eae1 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -185,6 +185,11 @@ else ifneq ($(shell cat $(CONFVARS_FILE)), $(FROZEN_MANIFEST) $(UART_OS)) $(shell echo $(FROZEN_MANIFEST) $(UART_OS) > $(CONFVARS_FILE)) endif +ESPTOOL := $(shell command -v esptool 2> /dev/null) +ifndef ESPTOOL +ESPTOOL = esptool.py +endif + $(BUILD)/uart.o: $(CONFVARS_FILE) FROZEN_EXTRA_DEPS = $(CONFVARS_FILE) @@ -193,11 +198,11 @@ FROZEN_EXTRA_DEPS = $(CONFVARS_FILE) deploy: $(FWBIN) $(ECHO) "Writing $< to the board" - $(Q)esptool.py --port $(PORT) --baud $(BAUD) write_flash --verify --flash_size=$(FLASH_SIZE) --flash_mode=$(FLASH_MODE) 0 $< + $(Q)$(ESPTOOL) --port $(PORT) --baud $(BAUD) write_flash --verify --flash_size=$(FLASH_SIZE) --flash_mode=$(FLASH_MODE) 0 $< erase: $(ECHO) "Erase flash" - $(Q)esptool.py --port $(PORT) --baud $(BAUD) erase_flash + $(Q)$(ESPTOOL) --port $(PORT) --baud $(BAUD) erase_flash reset: echo -e "\r\nimport machine; machine.reset()\r\n" >$(PORT) @@ -205,7 +210,7 @@ reset: ifeq ($(BOARD_VARIANT),OTA) $(FWBIN): $(BUILD)/firmware.elf $(ECHO) "Create $@" - $(Q)esptool.py elf2image $^ + $(Q)$(ESPTOOL) elf2image $^ $(Q)$(PYTHON) makeimg.py $(BUILD)/firmware.elf-0x00000.bin $(BUILD)/firmware.elf-0x[0-5][1-f]000.bin $(BUILD)/firmware-ota.bin $(Q)cat $(YAOTA8266)/yaota8266.bin $(BUILD)/firmware-ota.bin > $@ @@ -213,7 +218,7 @@ $(FWBIN): $(BUILD)/firmware.elf else $(FWBIN): $(BUILD)/firmware.elf $(ECHO) "Create $@" - $(Q)esptool.py elf2image $^ + $(Q)$(ESPTOOL) elf2image $^ $(Q)$(PYTHON) makeimg.py $(BUILD)/firmware.elf-0x00000.bin $(BUILD)/firmware.elf-0x[0-5][1-f]000.bin $@ endif From 1100aa63c908f60d03fa5b1d1b3abae3a53050f8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Jan 2025 17:09:43 +1100 Subject: [PATCH 0220/2098] LICENSE,docs: Update copyright year range to include 2025. Signed-off-by: Damien George --- LICENSE | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 550ed9574d0..929a2e97de7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2024 Damien P. George +Copyright (c) 2013-2025 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 diff --git a/docs/conf.py b/docs/conf.py index e5c6ba98090..eb614875820 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -71,7 +71,7 @@ # General information about the project. project = "MicroPython" -copyright = "- The MicroPython Documentation is Copyright © 2014-2024, " + micropy_authors +copyright = "- The MicroPython Documentation is Copyright © 2014-2025, " + micropy_authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 70b95d8f9314ebcddee15e5ac2f4bdb03143846d Mon Sep 17 00:00:00 2001 From: Graeme Winter Date: Thu, 23 Mar 2023 05:20:46 +0000 Subject: [PATCH 0221/2098] samd/machine_dac: Fix SAMD51 DAC for two channels. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improvements to DAC support for SAMD51: - properly validate DAC id - correctly use dac_init flag, as a 2-ple for A0, A1 channels - disable DAC before adjusting settings, see SAMD5x data sheet §47.6.2.3 Co-authored-by: robert-hh Signed-off-by: Graeme Winter --- ports/samd/machine_dac.c | 52 +++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index c611f95e653..7dcc654644d 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -72,7 +72,8 @@ static uint8_t dac_vref_table[] = { #define MAX_DAC_VALUE (4095) #define DEFAULT_DAC_VREF (2) #define MAX_DAC_VREF (3) -static bool dac_init = false; +static bool dac_init[2] = {false, false}; + #endif @@ -91,10 +92,10 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ uint8_t id = args[ARG_id].u_int; dac_obj_t *self = NULL; - if (0 <= id && id <= MP_ARRAY_SIZE(dac_obj)) { + if (0 <= id && id < MP_ARRAY_SIZE(dac_obj)) { self = &dac_obj[id]; } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid id for DAC")); } uint8_t vref = args[ARG_vref].u_int; @@ -102,9 +103,10 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } - Dac *dac = dac_bases[0]; // Just one DAC + Dac *dac = dac_bases[0]; // Just one DAC register block + + // initialize DAC - // Init DAC #if defined(MCU_SAMD21) // Configuration SAMD21 @@ -127,21 +129,39 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // Configuration SAMD51 // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz - dac_init = true; - MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; - GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN; - // Reset DAC registers - dac->CTRLA.bit.SWRST = 1; - while (dac->CTRLA.bit.SWRST) { + if (!(dac_init[0] | dac_init[1])) { + MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | \ + GCLK_PCHCTRL_CHEN; + + // Reset DAC registers + dac->CTRLA.bit.SWRST = 1; + while (dac->CTRLA.bit.SWRST) { + } + dac->CTRLB.reg = DAC_CTRLB_REFSEL(dac_vref_table[self->vref]); + } - dac->CTRLB.reg = DAC_CTRLB_REFSEL(dac_vref_table[self->vref]); - dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; - // Enable DAC and wait to be ready - dac->CTRLA.bit.ENABLE = 1; - while (dac->SYNCBUSY.bit.ENABLE) { + // Modify DAC config - requires disabling see Section 47.6.2.3 of data sheet + if (!dac_init[self->id]) { + // Disable DAC and wait + dac->CTRLA.bit.ENABLE = 0; + while (dac->SYNCBUSY.bit.ENABLE) { + } + + // Modify configuration + dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | \ + DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; + dac->DATA[self->id].reg = 0; + dac_init[self->id] = true; + + // Enable DAC and wait + dac->CTRLA.bit.ENABLE = 1; + while (dac->SYNCBUSY.bit.ENABLE) { + } } + #endif // Set the port as given in self->gpio_id as DAC From c69f0e4eeeb1058ac4a4b1b5b5d1896371e0ecd2 Mon Sep 17 00:00:00 2001 From: "I. Tomita" Date: Thu, 19 Oct 2023 12:54:37 +0300 Subject: [PATCH 0222/2098] samd/samd_qspiflash: Correct QSPI baud calculation. The QSPI baud is derived from the AHB clock, not from the APB (peripheral) clock. Datasheet: The QSPI Baud rate clock is generated by dividing the module clock (CLK_QSPI_AHB) by a value between 1 and 255. As previously implemented, all baudrates are 2.5 times greater than expected. Signed-off-by: I. Tomita --- ports/samd/samd_qspiflash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c index d027a0495f3..e25ddf21efe 100644 --- a/ports/samd/samd_qspiflash.c +++ b/ports/samd/samd_qspiflash.c @@ -238,7 +238,7 @@ static void wait_for_flash_ready(void) { } static uint8_t get_baud(int32_t freq_mhz) { - int baud = get_peripheral_freq() / (freq_mhz * 1000000) - 1; + int baud = get_cpu_freq() / (freq_mhz * 1000000) - 1; if (baud < 1) { baud = 1; } From 865a4c8bf6868ce488347a76f5f3db8e0dabd11d Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 21 Jan 2025 08:48:25 +0200 Subject: [PATCH 0223/2098] esp32: Add support for IDF v5.4. Add WIFI_AUTH_WPA3_ENTERPRISE and WIFI_AUTH_WPA2_WPA3_ENTERPRISE, and update PPP callback signature for latest lwIP. Co-authored-by: Daniel van de Giessen Signed-off-by: IhorNehrutsa --- ports/esp32/README.md | 2 +- ports/esp32/network_ppp.c | 9 +++++++-- ports/esp32/network_wlan.c | 8 +++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index bc24e5cd71a..8597c85ec31 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -28,7 +28,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.2, v5.2.2, and v5.3. +Currently MicroPython supports v5.2, v5.2.2, v5.3 and v5.4. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 5c41cf948c1..4dd5a3718c2 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -100,7 +100,12 @@ static mp_obj_t ppp_make_new(mp_obj_t stream) { } MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); -static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +static u32_t ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) +#else +static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) +#endif +{ ppp_if_obj_t *self = ctx; mp_obj_t stream = self->stream; @@ -109,7 +114,7 @@ static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) } int err; - return mp_stream_rw(stream, data, len, &err, MP_STREAM_RW_WRITE); + return mp_stream_rw(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); } static void pppos_client_task(void *self_in) { diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index fed81d28cb6..e85d1328fdc 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -763,6 +763,10 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) { MP_ROM_QSTR(MP_QSTR_SEC_DPP), MP_ROM_INT(WIFI_AUTH_DPP) }, #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA3_ENTERPRISE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, + #endif { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, @@ -770,7 +774,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +_Static_assert(WIFI_AUTH_MAX == 16, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) _Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) _Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); From a4ab847688406bb31eb531e07155bf173a8785aa Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 21 Jan 2025 09:55:55 +0200 Subject: [PATCH 0224/2098] py/persistentcode: Initialize prelude_ptr to prevent compiler warning. The esp32 IDF toolchain can give a "may be used uninitialized" warning, at least for ESP32-S3 with gcc 14.2.0. Silence that warning by initializing the variable with NULL. Co-authored-by: Daniel van de Giessen Signed-off-by: IhorNehrutsa --- py/persistentcode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index 0843d1a2c52..840ee49d3e4 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -402,7 +402,7 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co #if MICROPY_EMIT_MACHINE_CODE } else { - const uint8_t *prelude_ptr; + const uint8_t *prelude_ptr = NULL; #if MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE if (kind == MP_CODE_NATIVE_PY) { // Executable code cannot be accessed byte-wise on this architecture, so copy From 7b3f189b1723fe642f122a3b7826d16fe32f801a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 8 Feb 2024 16:56:52 -0800 Subject: [PATCH 0225/2098] tests/basics/nanbox_smallint.py: Fix incorrect use of int() in test. The literal is in base 16 but int()'s default radix in CPython is 10, not 0. Signed-off-by: Jeff Epler --- tests/basics/nanbox_smallint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/basics/nanbox_smallint.py b/tests/basics/nanbox_smallint.py index b3a502e447e..9451ab32846 100644 --- a/tests/basics/nanbox_smallint.py +++ b/tests/basics/nanbox_smallint.py @@ -23,17 +23,17 @@ raise SystemExit micropython.heap_lock() -print(int("0x80000000")) +print(int("0x80000000", 16)) micropython.heap_unlock() # This is the most positive small integer. micropython.heap_lock() -print(int("0x3fffffffffff")) +print(int("0x3fffffffffff", 16)) micropython.heap_unlock() # This is the most negative small integer. micropython.heap_lock() -print(int("-0x3fffffffffff") - 1) +print(int("-0x3fffffffffff", 16) - 1) micropython.heap_unlock() x = 1 From 13b13d1fdd05549d504eeded0b5aa8871d5e5dcf Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 3 Jan 2024 19:31:35 -0600 Subject: [PATCH 0226/2098] py/parsenum: Throw an exception for invalid int literals like "01". This includes making int("01") parse in base 10 like standard Python. When a base of 0 is specified it means auto-detect based on the prefix, and literals begining with 0 (except when the literal is all 0's) like "01" are then invalid and now throw an exception. The new error message is different from CPython. It says e.g., `SyntaxError: invalid syntax for integer with base 0: '09'` Additional test cases were added to cover the changed & added code. Co-authored-by: Damien George Signed-off-by: Jeff Epler --- py/objint.c | 2 +- py/parsenum.c | 4 ++-- py/parsenumbase.c | 29 +++++++++++------------------ tests/basics/int1.py | 15 +++++++++++++++ tests/basics/lexer.py | 8 ++++++++ 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/py/objint.c b/py/objint.c index 773e180343a..4be6009a440 100644 --- a/py/objint.c +++ b/py/objint.c @@ -55,7 +55,7 @@ static mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, return o; } else if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) { // a textual representation, parse it - return mp_parse_num_integer(bufinfo.buf, bufinfo.len, 0, NULL); + return mp_parse_num_integer(bufinfo.buf, bufinfo.len, 10, NULL); #if MICROPY_PY_BUILTINS_FLOAT } else if (mp_obj_is_float(args[0])) { return mp_obj_new_int_from_float(mp_obj_float_get(args[0])); diff --git a/py/parsenum.c b/py/parsenum.c index b33ffb6ff23..27d66411987 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -151,13 +151,13 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m raise_exc(exc, lex); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_obj_t exc = mp_obj_new_exception_msg_varg(&mp_type_ValueError, - MP_ERROR_TEXT("invalid syntax for integer with base %d"), base); + MP_ERROR_TEXT("invalid syntax for integer with base %d"), base == 1 ? 0 : base); raise_exc(exc, lex); #else vstr_t vstr; mp_print_t print; vstr_init_print(&vstr, 50, &print); - mp_printf(&print, "invalid syntax for integer with base %d: ", base); + mp_printf(&print, "invalid syntax for integer with base %d: ", base == 1 ? 0 : base); mp_str_print_quoted(&print, str_val_start, top - str_val_start, true); mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_ValueError, mp_obj_new_str_from_utf8_vstr(&vstr)); diff --git a/py/parsenumbase.c b/py/parsenumbase.c index 94523a666d3..cc3275c456e 100644 --- a/py/parsenumbase.c +++ b/py/parsenumbase.c @@ -30,35 +30,28 @@ // find real radix base, and strip preceding '0x', '0o' and '0b' // puts base in *base, and returns number of bytes to skip the prefix +// in base-0, puts 1 in *base to indicate a number that starts with 0, to provoke a +// ValueError if it's not all-digits-zero. size_t mp_parse_num_base(const char *str, size_t len, int *base) { const byte *p = (const byte *)str; if (len <= 1) { goto no_prefix; } unichar c = *(p++); - if ((*base == 0 || *base == 16) && c == '0') { - c = *(p++); - if ((c | 32) == 'x') { + if (c == '0') { + c = *(p++) | 32; + int b = *base; + if (c == 'x' && !(b & ~16)) { *base = 16; - } else if (*base == 0 && (c | 32) == 'o') { + } else if (c == 'o' && !(b & ~8)) { *base = 8; - } else if (*base == 0 && (c | 32) == 'b') { + } else if (c == 'b' && !(b & ~2)) { *base = 2; } else { - if (*base == 0) { - *base = 10; - } - p -= 2; - } - } else if (*base == 8 && c == '0') { - c = *(p++); - if ((c | 32) != 'o') { - p -= 2; - } - } else if (*base == 2 && c == '0') { - c = *(p++); - if ((c | 32) != 'b') { p -= 2; + if (b == 0) { + *base = 1; + } } } else { p--; diff --git a/tests/basics/int1.py b/tests/basics/int1.py index 2d92105c73e..94723af4d00 100644 --- a/tests/basics/int1.py +++ b/tests/basics/int1.py @@ -13,6 +13,7 @@ print(int('+1')) print(int('-1')) print(int('01')) +print(int('00')) print(int('9')) print(int('10')) print(int('+10')) @@ -31,6 +32,7 @@ print(int('0', 10)) print(int('1', 10)) print(int(' \t 1 \t ', 10)) +print(int(' \t 00 \t ', 10)) print(int('11', 10)) print(int('11', 16)) print(int('11', 8)) @@ -52,6 +54,17 @@ print(int('0o12 \t ', 8)) print(int(b"12", 10)) print(int(b"12")) +print(int('000 ', 0)) +print(int('000 ', 2)) +print(int('000 ', 8)) +print(int('000 ', 10)) +print(int('000 ', 16)) +print(int('000 ', 36)) +print(int('010 ', 2)) +print(int('010 ', 8)) +print(int('010 ', 10)) +print(int('010 ', 16)) +print(int('010 ', 36)) def test(value, base): @@ -79,6 +92,8 @@ def test(value, base): test('0xg', 16) test('1 1', 16) test('123', 37) +test('01', 0) +test('01 ', 0) # check that we don't parse this as a floating point number print(0x1e+1) diff --git a/tests/basics/lexer.py b/tests/basics/lexer.py index 181d62db1aa..addb8a13df3 100644 --- a/tests/basics/lexer.py +++ b/tests/basics/lexer.py @@ -83,3 +83,11 @@ def a(x): exec(r"'\U0000000'") except SyntaxError: print("SyntaxError") + +# Properly formed integer literals +print(eval("00")) +# badly formed integer literals +try: + eval("01") +except SyntaxError: + print("SyntaxError") From bfc0d7b0b97cbf52db892775b0f371dd06d2ba08 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 23 Jan 2025 23:55:57 +0100 Subject: [PATCH 0227/2098] py/emitnative: Optimise Viper register offset load/stores on Xtensa. This commit improves the emitted code sequences for address generation in the Viper subsystem when loading/storing 16 and 32 bit values via a register offset. The Xtensa opcodes ADDX2 and ADDX4 are used to avoid performing the extra shifts to align the final operation offset. Those opcodes are available on both xtensa and xtensawin MicroPython architectures. Signed-off-by: Alessandro Gatti --- py/asmxtensa.h | 8 ++++++++ py/emitnative.c | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/py/asmxtensa.h b/py/asmxtensa.h index f226624a826..d2f37bf828e 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -143,6 +143,14 @@ static inline void asm_xtensa_op_addi(asm_xtensa_t *as, uint reg_dest, uint reg_ asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, 12, reg_src, reg_dest, imm8 & 0xff)); } +static inline void asm_xtensa_op_addx2(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 9, reg_dest, reg_src_a, reg_src_b)); +} + +static inline void asm_xtensa_op_addx4(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 10, reg_dest, reg_src_a, reg_src_b)); +} + static inline void asm_xtensa_op_and(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) { asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 1, reg_dest, reg_src_a, reg_src_b)); } diff --git a/py/emitnative.c b/py/emitnative.c index 66c345b233d..7d856e13f75 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1625,6 +1625,11 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory + #if N_XTENSA || N_XTENSAWIN + asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); + asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); + break; + #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD16_REG_REG(emit->as, REG_RET, REG_ARG_1); // load from (base+2*index) @@ -1637,6 +1642,10 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); asm_rv32_opcode_lw(emit->as, REG_RET, REG_ARG_1, 0); break; + #elif N_XTENSA || N_XTENSAWIN + asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); + asm_xtensa_op_l32i_n(emit->as, REG_RET, REG_ARG_1, 0); + break; #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base @@ -1900,6 +1909,10 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_XTENSA || N_XTENSAWIN + asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); + asm_xtensa_op_s16i(emit->as, reg_value, REG_ARG_1, 0); + break; #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base @@ -1916,6 +1929,10 @@ static void emit_native_store_subscr(emit_t *emit) { asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); asm_rv32_opcode_sw(emit->as, reg_value, REG_ARG_1, 0); break; + #elif N_XTENSA || N_XTENSAWIN + asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); + asm_xtensa_op_s32i_n(emit->as, reg_value, REG_ARG_1, 0); + break; #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base From 40585eaa8f1b603f0094b73764e8ce5623812ecf Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 24 Jan 2025 00:20:39 +0100 Subject: [PATCH 0228/2098] py/emitnative: Emit shorter exception handler entry code on RV32. This commit improves the RV32 code sequence that is emitted if a function needs to set up an exception handler as its prologue. The old code would clear a temporary register and then copy that value to places that needed to be initialised with zero values. On RV32 there's a dedicated register that's hardwired to be equal to zero, which allows us to bypass the extra register clear and use the zero register to initialise values. Signed-off-by: Alessandro Gatti --- py/asmrv32.h | 2 ++ py/emitnative.c | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/py/asmrv32.h b/py/asmrv32.h index 584e3ffd224..b09f48eb12f 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -694,6 +694,7 @@ static inline void asm_rv32_opcode_xori(asm_rv32_t *state, mp_uint_t rd, mp_uint #define REG_LOCAL_1 ASM_RV32_REG_S3 #define REG_LOCAL_2 ASM_RV32_REG_S4 #define REG_LOCAL_3 ASM_RV32_REG_S5 +#define REG_ZERO ASM_RV32_REG_ZERO void asm_rv32_meta_comparison_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd); void asm_rv32_meta_comparison_ne(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd); @@ -756,6 +757,7 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_STORE_REG_REG(state, rs1, rs2) ASM_STORE32_REG_REG(state, rs1, rs2) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) +#define ASM_CLR_REG(state, rd) #endif diff --git a/py/emitnative.c b/py/emitnative.c index 7d856e13f75..f846e8bb4e2 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -288,6 +288,11 @@ struct _emit_t { ASM_T *as; }; +#ifndef REG_ZERO +#define REG_ZERO REG_TEMP0 +#define ASM_CLR_REG(state, rd) ASM_XOR_REG_REG(state, rd, rd) +#endif + static void emit_load_reg_with_object(emit_t *emit, int reg, mp_obj_t obj); static void emit_native_global_exc_entry(emit_t *emit); static void emit_native_global_exc_exit(emit_t *emit); @@ -1200,12 +1205,12 @@ static void emit_native_global_exc_entry(emit_t *emit) { ASM_JUMP_IF_REG_ZERO(emit->as, REG_RET, start_label, true); } else { // Clear the unwind state - ASM_XOR_REG_REG(emit->as, REG_TEMP0, REG_TEMP0); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_TEMP0); + ASM_CLR_REG(emit->as, REG_ZERO); + ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_ZERO); // clear nlr.ret_val, because it's passed to mp_native_raise regardless // of whether there was an exception or not - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); + ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_ZERO); // Put PC of start code block into REG_LOCAL_1 ASM_MOV_REG_PCREL(emit->as, REG_LOCAL_1, start_label); @@ -1221,8 +1226,8 @@ static void emit_native_global_exc_entry(emit_t *emit) { ASM_JUMP_IF_REG_NONZERO(emit->as, REG_RET, global_except_label, true); // Clear PC of current code block, and jump there to resume execution - ASM_XOR_REG_REG(emit->as, REG_TEMP0, REG_TEMP0); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_TEMP0); + ASM_CLR_REG(emit->as, REG_ZERO); + ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_ZERO); ASM_JUMP_REG(emit->as, REG_LOCAL_1); // Global exception handler: check for valid exception handler From 55ca3fd67512555707304c6b68b836eb89f09d1c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 24 Jan 2025 09:22:36 +0100 Subject: [PATCH 0229/2098] py/emitnative: Optimise Viper immediate offset load/stores on Xtensa. This commit introduces the ability to emit optimised code paths on Xtensa for load/store operations indexed via an immediate offset. If an immediate offset for a load/store operation is within a certain range that allows it to be embedded into an available opcode then said opcode is emitted instead of the generic code sequence. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/py/emitnative.c b/py/emitnative.c index f846e8bb4e2..82ee729d3d5 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1550,6 +1550,11 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); + break; + } #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value); @@ -1573,6 +1578,11 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); + break; + } #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); @@ -1596,6 +1606,10 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); + } #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); @@ -1812,6 +1826,11 @@ static void emit_native_store_subscr(emit_t *emit) { asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_op_s8i(emit->as, REG_RET, reg_base, index_value); + break; + } #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value); #if N_ARM @@ -1838,6 +1857,11 @@ static void emit_native_store_subscr(emit_t *emit) { asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_op_s16i(emit->as, REG_RET, reg_base, index_value); + break; + } #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 2*index to base @@ -1860,6 +1884,10 @@ static void emit_native_store_subscr(emit_t *emit) { asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); break; } + #elif N_XTENSA || N_XTENSAWIN + if (index_value > 0 && index_value < 256) { + asm_xtensa_s32i_optimised(emit->as, REG_RET, reg_base, index_value); + } #elif N_ARM ASM_MOV_REG_IMM(emit->as, reg_index, index_value); asm_arm_str_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); From 0d46e45a1f72ee61a153c41aaaf6c63818ccffb0 Mon Sep 17 00:00:00 2001 From: Hans Maerki Date: Sun, 26 Jan 2025 18:00:38 +0100 Subject: [PATCH 0230/2098] tools/mpremote: Avoid initial blocking read in read_until(). If the target does not return any data then `read_until()` will block indefinitely. Fix this by making the initial read part of the general read look, which always checks `inWaiting() > 0` before reading from the serial device. Also added the UART timeout to the constructor. This is not currently used but may be used as an additional safeguard. Signed-off-by: Hans Maerki --- tools/mpremote/mpremote/transport_serial.py | 30 ++++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index 28ccaf6d8c9..449708a5ee4 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -42,7 +42,7 @@ class SerialTransport(Transport): - def __init__(self, device, baudrate=115200, wait=0, exclusive=True): + def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None): self.in_raw_repl = False self.use_raw_paste = True self.device_name = device @@ -52,7 +52,11 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True): import serial.tools.list_ports # Set options, and exclusive if pyserial supports it - serial_kwargs = {"baudrate": baudrate, "interCharTimeout": 1} + serial_kwargs = { + "baudrate": baudrate, + "timeout": timeout, + "interCharTimeout": 1, + } if serial.__version__ >= "3.3": serial_kwargs["exclusive"] = exclusive @@ -95,13 +99,20 @@ def close(self): self.serial.close() def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): - # if data_consumer is used then data is not accumulated and the ending must be 1 byte long + """ + min_num_bytes: Obsolete. + ending: Return if 'ending' matches. + timeout [s]: Return if timeout between characters. None: Infinite timeout. + data_consumer: Use callback for incoming characters. + If data_consumer is used then data is not accumulated and the ending must be 1 byte long + + It is not visible to the caller why the function returned. It could be ending or timeout. + """ assert data_consumer is None or len(ending) == 1 + assert isinstance(timeout, (type(None), int, float)) - data = self.serial.read(min_num_bytes) - if data_consumer: - data_consumer(data) - timeout_count = 0 + data = b"" + begin_char_s = time.monotonic() while True: if data.endswith(ending): break @@ -112,10 +123,9 @@ def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): data = new_data else: data = data + new_data - timeout_count = 0 + begin_char_s = time.monotonic() else: - timeout_count += 1 - if timeout is not None and timeout_count >= 100 * timeout: + if timeout is not None and time.monotonic() >= begin_char_s + timeout: break time.sleep(0.01) return data From 03fe9c55ea8a3ef8bede5547476f37d7fca20119 Mon Sep 17 00:00:00 2001 From: Hans Maerki Date: Mon, 27 Jan 2025 07:29:44 +0100 Subject: [PATCH 0231/2098] tools/mpremote: Introduce timeout_overall for read_until(). And use it in `enter_raw_repl()`. This prevents waiting forever for a serial device that does not respond to the Ctrl-C/Ctrl-D/etc commands and is constantly outputting data. Signed-off-by: Hans Maerki --- tools/mpremote/mpremote/transport_serial.py | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index 449708a5ee4..d3148f9ace3 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -98,11 +98,14 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None def close(self): self.serial.close() - def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): + def read_until( + self, min_num_bytes, ending, timeout=10, data_consumer=None, timeout_overall=None + ): """ min_num_bytes: Obsolete. ending: Return if 'ending' matches. timeout [s]: Return if timeout between characters. None: Infinite timeout. + timeout_overall [s]: Return not later than timeout_overall. None: Infinite timeout. data_consumer: Use callback for incoming characters. If data_consumer is used then data is not accumulated and the ending must be 1 byte long @@ -110,9 +113,10 @@ def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): """ assert data_consumer is None or len(ending) == 1 assert isinstance(timeout, (type(None), int, float)) + assert isinstance(timeout_overall, (type(None), int, float)) data = b"" - begin_char_s = time.monotonic() + begin_overall_s = begin_char_s = time.monotonic() while True: if data.endswith(ending): break @@ -127,10 +131,15 @@ def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): else: if timeout is not None and time.monotonic() >= begin_char_s + timeout: break + if ( + timeout_overall is not None + and time.monotonic() >= begin_overall_s + timeout_overall + ): + break time.sleep(0.01) return data - def enter_raw_repl(self, soft_reset=True): + def enter_raw_repl(self, soft_reset=True, timeout_overall=10): self.serial.write(b"\r\x03") # ctrl-C: interrupt any running program # flush input (without relying on serial.flushInput()) @@ -142,7 +151,9 @@ def enter_raw_repl(self, soft_reset=True): self.serial.write(b"\r\x01") # ctrl-A: enter raw REPL if soft_reset: - data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>") + data = self.read_until( + 1, b"raw REPL; CTRL-B to exit\r\n>", timeout_overall=timeout_overall + ) if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"): print(data) raise TransportError("could not enter raw repl") @@ -152,12 +163,12 @@ def enter_raw_repl(self, soft_reset=True): # Waiting for "soft reboot" independently to "raw REPL" (done below) # allows boot.py to print, which will show up after "soft reboot" # and before "raw REPL". - data = self.read_until(1, b"soft reboot\r\n") + data = self.read_until(1, b"soft reboot\r\n", timeout_overall=timeout_overall) if not data.endswith(b"soft reboot\r\n"): print(data) raise TransportError("could not enter raw repl") - data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n") + data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n", timeout_overall=timeout_overall) if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"): print(data) raise TransportError("could not enter raw repl") From f29bd5a65e9688222474e54b4f5d0fb127a23db3 Mon Sep 17 00:00:00 2001 From: Jared Hancock Date: Sun, 19 Jan 2025 15:37:55 -0600 Subject: [PATCH 0232/2098] extmod/modlwip: Fix incorrect peer address for IPv6. For IPv6 connections, the peer address was previously defined as only the first four bytes of the IP address. For IPv6 addresses, this resulted in an incorrect IPv4 address. For instance, receiving a packet via `::recvfrom` from `'fe80::87:e7ff:fe48:629a'` is returned as having a peer address of `'254.128.0.0'` Signed-off-by: Jared Hancock --- extmod/modlwip.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index ce70627c31c..bcccd363e67 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -37,7 +37,6 @@ #if MICROPY_PY_LWIP -#include "shared/netutils/netutils.h" #include "modnetwork.h" #include "lwip/init.h" @@ -308,7 +307,7 @@ typedef struct _lwip_socket_obj_t { } connection; } incoming; mp_obj_t callback; - byte peer[4]; + ip_addr_t peer; mp_uint_t peer_port; mp_uint_t timeout; uint16_t recv_offset; @@ -638,7 +637,7 @@ static mp_uint_t lwip_raw_udp_send(lwip_socket_obj_t *socket, const byte *buf, m } // Helper function for recv/recvfrom to handle raw/UDP packets -static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, byte *ip, mp_uint_t *port, int *_errno) { +static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, ip_addr_t *ip, mp_uint_t *port, int *_errno) { if (socket->incoming.pbuf == NULL) { if (socket->timeout == 0) { @@ -1132,7 +1131,7 @@ static mp_obj_t lwip_socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { mp_raise_OSError(error_lookup_table[-err]); } socket->peer_port = (mp_uint_t)port; - memcpy(socket->peer, &dest, sizeof(socket->peer)); + memcpy(&socket->peer, &dest, sizeof(socket->peer)); MICROPY_PY_LWIP_EXIT // And now we wait... @@ -1294,13 +1293,13 @@ static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { mp_int_t len = mp_obj_get_int(len_in); vstr_t vstr; vstr_init_len(&vstr, len); - byte ip[4]; + ip_addr_t ip; mp_uint_t port; mp_uint_t ret = 0; switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: { - memcpy(ip, &socket->peer, sizeof(socket->peer)); + memcpy(&ip, &socket->peer, sizeof(socket->peer)); port = (mp_uint_t)socket->peer_port; ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, &_errno); break; @@ -1309,7 +1308,7 @@ static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { #if MICROPY_PY_LWIP_SOCK_RAW case MOD_NETWORK_SOCK_RAW: #endif - ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, ip, &port, &_errno); + ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, &ip, &port, &_errno); break; } if (ret == -1) { @@ -1323,7 +1322,8 @@ static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { vstr.len = ret; tuple[0] = mp_obj_new_bytes_from_vstr(&vstr); } - tuple[1] = netutils_format_inet_addr(ip, port, NETUTILS_BIG); + tuple[1] = lwip_format_inet_addr(&ip, port); + return mp_obj_new_tuple(2, tuple); } static MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_recvfrom_obj, lwip_socket_recvfrom); From ec527a11136e1c83ddf51099a418ea785215284f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 14 Jan 2025 11:38:43 +1100 Subject: [PATCH 0233/2098] esp32: Disable component manager when running 'make submodules'. - ECHO_SUBMODULES=1 exits CMake early. With idf_component_manager 1.x this seems to leave the managed_components directory in a state that causes later builds to fail. - Looks like the component manager isn't needed for this step, so disable it. This invocation logs a warning (not visible in normal output) but completes successfully and returns the correct list of submodules. Signed-off-by: Angus Gratton --- ports/esp32/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 8888180766b..1c2fa82ce12 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -112,6 +112,6 @@ size-files: # output and passes the list of submodules to py/mkrules.mk which does the # `git submodule init` on each. submodules: - @GIT_SUBMODULES=$$(idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D ECHO_SUBMODULES=1 build 2>&1 | \ + @GIT_SUBMODULES=$$(IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D ECHO_SUBMODULES=1 build 2>&1 | \ grep '^GIT_SUBMODULES=' | cut -d= -f2); \ $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" submodules From 43e3ab6131b11d7fb7409a35fe5127317ba27397 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 14 Jan 2025 11:39:32 +1100 Subject: [PATCH 0234/2098] esp32: Don't add TinyUSB files to an ECHO_SUBMODULES build. Similar to other places, CMake will error out if this file doesn't exist yet but we don't want this if we're only getting the list of submodules. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/esp32_common.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index b6bfcdacc3c..e03d2ff11aa 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -60,7 +60,7 @@ list(APPEND MICROPY_SOURCE_DRIVERS ) string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) -if(MICROPY_PY_TINYUSB) +if(MICROPY_PY_TINYUSB AND NOT ECHO_SUBMODULES) set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) From 22353e9e1ed5bf8bc5bad179896ca2877f7511e2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 14 Jan 2025 11:50:27 +1100 Subject: [PATCH 0235/2098] py/mkrules: Add GIT_SUBMODULES_FAIL_IF_EMPTY flag for CMake ports. The way CMake gathers the submodule list, it can quietly be empty if the previous step fails. This makes it an explicit error. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/Makefile | 2 +- ports/rp2/Makefile | 2 +- py/mkrules.mk | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 1c2fa82ce12..1ce4d97208d 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -114,4 +114,4 @@ size-files: submodules: @GIT_SUBMODULES=$$(IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D ECHO_SUBMODULES=1 build 2>&1 | \ grep '^GIT_SUBMODULES=' | cut -d= -f2); \ - $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" submodules + $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" GIT_SUBMODULES_FAIL_IF_EMPTY=1 submodules diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index afa21cc7a43..200899d338e 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -76,4 +76,4 @@ submodules: $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="lib/pico-sdk" submodules @GIT_SUBMODULES=$$(cmake -B $(BUILD)/submodules -DECHO_SUBMODULES=1 ${CMAKE_ARGS} -S . 2>&1 | \ grep '^GIT_SUBMODULES=' | cut -d= -f2); \ - $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" submodules + $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" GIT_SUBMODULES_FAIL_IF_EMPTY=1 submodules diff --git a/py/mkrules.mk b/py/mkrules.mk index 74978b0d15b..373bda8996f 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -257,6 +257,13 @@ ifneq ($(GIT_SUBMODULES),) $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) || \ git submodule update --init $(GIT_SUBMODULES) +else +ifeq ($(GIT_SUBMODULES_FAIL_IF_EMPTY),1) + # If you see this error, it may mean the internal step run by the port's build + # system to find git submodules has failed. Double-check dependencies are set correctly. + $(ECHO) "Internal build error: The submodule list should not be empty." + exit 1 +endif endif .PHONY: submodules From abb13b1e1e37c42876f69c83b96c9ead3285a9a7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 25 Jan 2025 17:45:40 +1100 Subject: [PATCH 0236/2098] extmod/lwip-include: Factor common lwIP config into lwipopts_common.h. This lwIP configuration file has options that are common to all ports, and the ports are updated to use this file. This change is a no-op, the lwIP configuration remains the same for the four ports using this common file. This reduces code duplication, keeps the ports in sync, and makes it easier to update the configuration for all ports at once. Signed-off-by: Damien George --- extmod/lwip-include/lwipopts_common.h | 109 ++++++++++++++++++++++++++ ports/mimxrt/lwip_inc/lwipopts.h | 47 +---------- ports/renesas-ra/lwip_inc/lwipopts.h | 42 +--------- ports/rp2/lwip_inc/lwipopts.h | 56 +------------ ports/stm32/lwip_inc/lwipopts.h | 77 +----------------- 5 files changed, 122 insertions(+), 209 deletions(-) create mode 100644 extmod/lwip-include/lwipopts_common.h diff --git a/extmod/lwip-include/lwipopts_common.h b/extmod/lwip-include/lwipopts_common.h new file mode 100644 index 00000000000..8688db064ef --- /dev/null +++ b/extmod/lwip-include/lwipopts_common.h @@ -0,0 +1,109 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ +#ifndef MICROPY_INCLUDED_LWIPOPTS_COMMON_H +#define MICROPY_INCLUDED_LWIPOPTS_COMMON_H + +#include "py/mpconfig.h" + +// This sys-arch protection is not needed. +// Ports either protect lwIP code with flags, or run it at PendSV priority. +#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) +#define SYS_ARCH_PROTECT(lev) do { } while (0) +#define SYS_ARCH_UNPROTECT(lev) do { } while (0) + +#define NO_SYS 1 +#define SYS_LIGHTWEIGHT_PROT 1 +#define MEM_ALIGNMENT 4 + +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 + +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_RAW 1 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_STATS 0 +#define LWIP_NETIF_HOSTNAME 1 + +#define LWIP_DHCP 1 +#define LWIP_DHCP_CHECK_LINK_UP 1 +#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up +#define LWIP_DNS 1 +#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 +#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) + +#define SO_REUSE 1 +#define TCP_LISTEN_BACKLOG 1 + +// TCP memory settings. +// Default lwIP settings takes 15800 bytes; TCP d/l: 380k/s local, 7.2k/s remote; TCP u/l is very slow. +#ifndef MEM_SIZE + +#if 0 +// lwIP takes 19159 bytes; TCP d/l and u/l are around 320k/s on local network. +#define MEM_SIZE (5000) +#define TCP_WND (4 * TCP_MSS) +#define TCP_SND_BUF (4 * TCP_MSS) +#endif + +#if 1 +// lwIP takes 26700 bytes; TCP dl/ul are around 750/600 k/s on local network. +#define MEM_SIZE (8000) +#define TCP_MSS (800) +#define TCP_WND (8 * TCP_MSS) +#define TCP_SND_BUF (8 * TCP_MSS) +#define MEMP_NUM_TCP_SEG (32) +#endif + +#if 0 +// lwIP takes 45600 bytes; TCP dl/ul are around 1200/1000 k/s on local network. +#define MEM_SIZE (16000) +#define TCP_MSS (1460) +#define TCP_WND (8 * TCP_MSS) +#define TCP_SND_BUF (8 * TCP_MSS) +#define MEMP_NUM_TCP_SEG (32) +#endif + +#endif // MEM_SIZE + +// Needed for PPP. +#define sys_jiffies sys_now + +typedef uint32_t sys_prot_t; + +#endif // MICROPY_INCLUDED_LWIPOPTS_COMMON_H diff --git a/ports/mimxrt/lwip_inc/lwipopts.h b/ports/mimxrt/lwip_inc/lwipopts.h index 411ca4b60f3..cf25597f95c 100644 --- a/ports/mimxrt/lwip_inc/lwipopts.h +++ b/ports/mimxrt/lwip_inc/lwipopts.h @@ -1,56 +1,15 @@ #ifndef MICROPY_INCLUDED_MIMXRT_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_MIMXRT_LWIP_LWIPOPTS_H -#include - -// This protection is not needed, instead we execute all lwIP code at PendSV priority -#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) -#define SYS_ARCH_PROTECT(lev) do { } while (0) -#define SYS_ARCH_UNPROTECT(lev) do { } while (0) - -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 1 -#define MEM_ALIGNMENT 4 - -#define LWIP_CHKSUM_ALGORITHM 3 -// The checksum flags are set in eth.c -#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 - -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 -#define LWIP_STATS 0 -#define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_EXT_STATUS_CALLBACK 1 #define LWIP_IPV6 0 -#define LWIP_DHCP 1 -#define LWIP_DHCP_CHECK_LINK_UP 1 -#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up -#define LWIP_DNS 1 -#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 -#define LWIP_MDNS_RESPONDER 1 -#define LWIP_IGMP 1 -#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) - -#define SO_REUSE 1 -#define TCP_LISTEN_BACKLOG 1 - -extern uint32_t trng_random_u32(void); #define LWIP_RAND() trng_random_u32() -// lwip takes 26700 bytes -#define MEM_SIZE (8000) -#define TCP_MSS (800) -#define TCP_WND (8 * TCP_MSS) -#define TCP_SND_BUF (8 * TCP_MSS) -#define MEMP_NUM_TCP_SEG (32) +// Include common lwIP configuration. +#include "extmod/lwip-include/lwipopts_common.h" -typedef uint32_t sys_prot_t; +extern uint32_t trng_random_u32(void); #endif // MICROPY_INCLUDED_MIMXRT_LWIP_LWIPOPTS_H diff --git a/ports/renesas-ra/lwip_inc/lwipopts.h b/ports/renesas-ra/lwip_inc/lwipopts.h index 74e39022a6a..77645ae6c3c 100644 --- a/ports/renesas-ra/lwip_inc/lwipopts.h +++ b/ports/renesas-ra/lwip_inc/lwipopts.h @@ -1,45 +1,8 @@ #ifndef MICROPY_INCLUDED_RA_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_RA_LWIP_LWIPOPTS_H -#include - -// This protection is not needed, instead protect lwIP code with flags -#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) -#define SYS_ARCH_PROTECT(lev) do { } while (0) -#define SYS_ARCH_UNPROTECT(lev) do { } while (0) - -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 1 -#define MEM_ALIGNMENT 4 - -#define LWIP_CHKSUM_ALGORITHM 3 -#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 - -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 -#define LWIP_STATS 0 -#define LWIP_NETIF_HOSTNAME 1 - #define LWIP_IPV6 0 -#define LWIP_DHCP 1 -#define LWIP_DHCP_CHECK_LINK_UP 1 -#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up -#define LWIP_DNS 1 -#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 -#define LWIP_MDNS_RESPONDER 1 -#define LWIP_IGMP 1 -#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) - -#define SO_REUSE 1 -#define TCP_LISTEN_BACKLOG 1 - -extern uint32_t rng_read(void); #define LWIP_RAND() rng_read() #define MEM_SIZE (16 * 1024) @@ -51,6 +14,9 @@ extern uint32_t rng_read(void); #define TCP_QUEUE_OOSEQ (1) #define MEMP_NUM_TCP_SEG (2 * TCP_SND_QUEUELEN) -typedef uint32_t sys_prot_t; +// Include common lwIP configuration. +#include "extmod/lwip-include/lwipopts_common.h" + +extern uint32_t rng_read(void); #endif // MICROPY_INCLUDED_RA_LWIP_LWIPOPTS_H diff --git a/ports/rp2/lwip_inc/lwipopts.h b/ports/rp2/lwip_inc/lwipopts.h index 15679e0cf01..da45a5735b2 100644 --- a/ports/rp2/lwip_inc/lwipopts.h +++ b/ports/rp2/lwip_inc/lwipopts.h @@ -1,28 +1,6 @@ #ifndef MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H -#include "py/mpconfig.h" - -// This protection is not needed, instead protect lwIP code with flags -#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) -#define SYS_ARCH_PROTECT(lev) do { } while (0) -#define SYS_ARCH_UNPROTECT(lev) do { } while (0) - -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 1 -#define MEM_ALIGNMENT 4 - -#define LWIP_CHKSUM_ALGORITHM 3 -// The checksum flags are set in eth.c -#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 - -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 -#define LWIP_STATS 0 -#define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_EXT_STATUS_CALLBACK 1 #define LWIP_NETIF_STATUS_CALLBACK 1 @@ -30,40 +8,12 @@ #define LWIP_IPV6 1 #define LWIP_ND6_NUM_DESTINATIONS 4 #define LWIP_ND6_QUEUEING 0 -#define LWIP_DHCP 1 -#define LWIP_DHCP_CHECK_LINK_UP 1 -#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up -#define LWIP_DNS 1 -#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 -#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) - -#define SO_REUSE 1 -#define TCP_LISTEN_BACKLOG 1 - -extern uint32_t rosc_random_u32(void); #define LWIP_RAND() rosc_random_u32() -// lwip takes 26700 bytes -#define MEM_SIZE (8000) -#define TCP_MSS (800) -#define TCP_WND (8 * TCP_MSS) -#define TCP_SND_BUF (8 * TCP_MSS) -#define MEMP_NUM_TCP_SEG (32) +// Include common lwIP configuration. +#include "extmod/lwip-include/lwipopts_common.h" -typedef uint32_t sys_prot_t; - -// Needed for PPP. -#define sys_jiffies sys_now +extern uint32_t rosc_random_u32(void); #endif // MICROPY_INCLUDED_RP2_LWIP_LWIPOPTS_H diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index 9e1aa8d0f14..9e2402c8dc5 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -1,89 +1,18 @@ #ifndef MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H -#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) -#define SYS_ARCH_PROTECT(lev) do { } while (0) -#define SYS_ARCH_UNPROTECT(lev) do { } while (0) - -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 1 -#define MEM_ALIGNMENT 4 - -#define LWIP_CHKSUM_ALGORITHM 3 -#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 - -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 -#define LWIP_STATS 0 -#define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_EXT_STATUS_CALLBACK 1 #define LWIP_LOOPIF_MULTICAST 1 #define LWIP_LOOPBACK_MAX_PBUFS 8 #define LWIP_IPV6 0 -#define LWIP_DHCP 1 -#define LWIP_DHCP_CHECK_LINK_UP 1 -#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up -#define LWIP_DNS 1 -#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 -#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) - -#define SO_REUSE 1 -#define TCP_LISTEN_BACKLOG 1 -extern uint32_t rng_get(void); #define LWIP_RAND() rng_get() -// default -// lwip takes 15800 bytes; TCP d/l: 380k/s local, 7.2k/s remote -// TCP u/l is very slow - -#if 0 -// lwip takes 19159 bytes; TCP d/l and u/l are around 320k/s on local network -#define MEM_SIZE (5000) -#define TCP_WND (4 * TCP_MSS) -#define TCP_SND_BUF (4 * TCP_MSS) -#endif - -#if 1 -// lwip takes 26700 bytes; TCP dl/ul are around 750/600 k/s on local network -#define MEM_SIZE (8000) -#define TCP_MSS (800) -#define TCP_WND (8 * TCP_MSS) -#define TCP_SND_BUF (8 * TCP_MSS) -#define MEMP_NUM_TCP_SEG (32) -#endif +// Include common lwIP configuration. +#include "extmod/lwip-include/lwipopts_common.h" -#if 0 -// lwip takes 45600 bytes; TCP dl/ul are around 1200/1000 k/s on local network -#define MEM_SIZE (16000) -#define TCP_MSS (1460) -#define TCP_WND (8 * TCP_MSS) -#define TCP_SND_BUF (8 * TCP_MSS) -#define MEMP_NUM_TCP_SEG (32) -#endif - -typedef uint32_t sys_prot_t; - -// Needed for PPP. -#define sys_jiffies sys_now +extern uint32_t rng_get(void); #endif // MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H From bfb1bee6feba9f06452a0e4b572ec4d15df325cf Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 26 Jan 2025 20:54:14 -0600 Subject: [PATCH 0237/2098] py/parsenumbase: Favor clarity of code over manual optimisation. Follow up to 13b13d1fdd05549d504eeded0b5aa8871d5e5dcf, based on some testing on godbolt, the manual code optimisation seems unnecessary for code size, at least on gcc x86_64 and ARM, and it's definitely not good for clarity. Signed-off-by: Jeff Epler --- py/parsenumbase.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/parsenumbase.c b/py/parsenumbase.c index cc3275c456e..fbf07a11958 100644 --- a/py/parsenumbase.c +++ b/py/parsenumbase.c @@ -41,11 +41,11 @@ size_t mp_parse_num_base(const char *str, size_t len, int *base) { if (c == '0') { c = *(p++) | 32; int b = *base; - if (c == 'x' && !(b & ~16)) { + if (c == 'x' && (b == 0 || b == 16)) { *base = 16; - } else if (c == 'o' && !(b & ~8)) { + } else if (c == 'o' && (b == 0 || b == 8)) { *base = 8; - } else if (c == 'b' && !(b & ~2)) { + } else if (c == 'b' && (b == 0 || b == 2)) { *base = 2; } else { p -= 2; From 195bf051153cff1d49b3645c51099e9ff6e1d80d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 15 Oct 2024 12:23:28 +1100 Subject: [PATCH 0238/2098] tests: Add a test for SSL socket memory leaks. Test is for an issue reported on the micropython-lib Discord as effecting the rp2 port umqtt.simple interface when reconnecting with TLS, however it's a more generic problem. Currently this test fails on RPI_PICO_W and ESP32_GENERIC_C3 (and no doubt others). Fixes are in the subsequent commits. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/extmod/ssl_noleak.py | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/extmod/ssl_noleak.py diff --git a/tests/extmod/ssl_noleak.py b/tests/extmod/ssl_noleak.py new file mode 100644 index 00000000000..870032d58e6 --- /dev/null +++ b/tests/extmod/ssl_noleak.py @@ -0,0 +1,50 @@ +# Ensure that SSLSockets can be allocated sequentially +# without running out of available memory. +try: + import io + import tls +except ImportError: + print("SKIP") + raise SystemExit + +import unittest + + +class TestSocket(io.IOBase): + def write(self, buf): + return len(buf) + + def readinto(self, buf): + return 0 + + def ioctl(self, cmd, arg): + return 0 + + def setblocking(self, value): + pass + + +ITERS = 128 + + +class TLSNoLeaks(unittest.TestCase): + def test_unique_context(self): + for n in range(ITERS): + print(n) + s = TestSocket() + ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + ctx.verify_mode = tls.CERT_NONE + s = ctx.wrap_socket(s, do_handshake_on_connect=False) + + def test_shared_context(self): + # Single SSLContext, multiple sockets + ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + ctx.verify_mode = tls.CERT_NONE + for n in range(ITERS): + print(n) + s = TestSocket() + s = ctx.wrap_socket(s, do_handshake_on_connect=False) + + +if __name__ == "__main__": + unittest.main() From 97f444bfa04408dd510f118baaea5760b93bd892 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 28 Jan 2025 15:31:18 +1100 Subject: [PATCH 0239/2098] extmod/mbedtls: Try GC before failing to setup socket on esp32, unix. On mbedTLS ports with non-baremetal configs (mostly esp32, technically also unix port), mbedTLS memory is allocated from the libc heap. This means an old SSL socket may be holding large SSL buffers and preventing a new SSL socket from being allocated. As a workaround, trigger a GC pass and retry before failing outright. This was originally implemented as a global mbedTLS calloc function, but there is complexity around the possibility of C user modules calling into mbedTLS without holding the GIL. It would be interesting to try making a generic version for any malloc which fails, but this would require checking for a Python thread and probably making the GIL recursive. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/modtls_mbedtls.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 0a1b8828af5..3fd416d72f5 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -37,6 +37,7 @@ #include "py/stream.h" #include "py/objstr.h" #include "py/reader.h" +#include "py/gc.h" #include "extmod/vfs.h" // mbedtls_time_t @@ -58,6 +59,10 @@ #include "mbedtls/asn1.h" #endif +#ifndef MICROPY_MBEDTLS_CONFIG_BARE_METAL +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (0) +#endif + #define MP_STREAM_POLL_RDWR (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR) // This corresponds to an SSLContext object. @@ -545,6 +550,16 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t mbedtls_ssl_init(&o->ssl); ret = mbedtls_ssl_setup(&o->ssl, &ssl_context->conf); + #if !MICROPY_MBEDTLS_CONFIG_BARE_METAL + if (ret == MBEDTLS_ERR_SSL_ALLOC_FAILED) { + // If mbedTLS relies on platform libc heap for buffers (i.e. esp32 + // port), then run a GC pass and then try again. This is useful because + // it may free a Python object (like an old SSL socket) whose finaliser + // frees some platform-level heap. + gc_collect(); + ret = mbedtls_ssl_setup(&o->ssl, &ssl_context->conf); + } + #endif if (ret != 0) { goto cleanup; } From d642cce27a9a07e043211f099c31fca390f96f1a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 16 Oct 2024 16:43:13 +1100 Subject: [PATCH 0240/2098] unix: Use the bare metal mbedTLS config in the coverage buiid. This allows coverage to test MicroPython-specific features such as the tracked alloc cleanups added in the parent commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/unix/mbedtls/mbedtls_config_port.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ports/unix/mbedtls/mbedtls_config_port.h b/ports/unix/mbedtls/mbedtls_config_port.h index c619de9b8b1..aec65e6581e 100644 --- a/ports/unix/mbedtls/mbedtls_config_port.h +++ b/ports/unix/mbedtls/mbedtls_config_port.h @@ -32,7 +32,18 @@ // Enable mbedtls modules #define MBEDTLS_TIMING_C +#if defined(MICROPY_UNIX_COVERAGE) +// Test the "bare metal" memory management in the coverage build +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) +#endif + // Include common mbedtls configuration. #include "extmod/mbedtls/mbedtls_config_common.h" +#if defined(MICROPY_UNIX_COVERAGE) +// See comment above, but fall back to the default platform entropy functions +#undef MBEDTLS_ENTROPY_HARDWARE_ALT +#undef MBEDTLS_NO_PLATFORM_ENTROPY +#endif + #endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ From 8a2ff2ca7366f605dd55c93f6b393552b365cd10 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Dec 2024 10:53:08 +1100 Subject: [PATCH 0241/2098] py/gc: Split out running finalizers to a separate pass. Currently a finalizer may run and access memory which has already been freed. (This happens mostly during gc_sweep_all() but could happen during any garbage collection pass.) Includes some speed improvement tweaks to skip empty FTB blocks. These help compensate for the inherent slowdown of having to walk the heap twice. Signed-off-by: Angus Gratton --- py/gc.c | 67 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/py/gc.c b/py/gc.c index bee44925076..297f4f85d3f 100644 --- a/py/gc.c +++ b/py/gc.c @@ -477,29 +477,20 @@ static void gc_deal_with_stack_overflow(void) { } } -static void gc_sweep(void) { - #if MICROPY_PY_GC_COLLECT_RETVAL - MP_STATE_MEM(gc_collected) = 0; - #endif - // free unmarked heads and their tails - int free_tail = 0; - #if MICROPY_GC_SPLIT_HEAP_AUTO - mp_state_mem_area_t *prev_area = NULL; - #endif - for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { - size_t end_block = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; - if (area->gc_last_used_block < end_block) { - end_block = area->gc_last_used_block + 1; - } - - size_t last_used_block = 0; - - for (size_t block = 0; block < end_block; block++) { - MICROPY_GC_HOOK_LOOP(block); - switch (ATB_GET_KIND(area, block)) { - case AT_HEAD: - #if MICROPY_ENABLE_FINALISER - if (FTB_GET(area, block)) { +// Run finalisers for all to-be-freed blocks +static void gc_sweep_run_finalisers(void) { + #if MICROPY_ENABLE_FINALISER + for (const mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { + assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB); + // Small speed optimisation: skip over empty FTB blocks + size_t ftb_end = area->gc_last_used_block / BLOCKS_PER_FTB; // index is inclusive + for (size_t ftb_idx = 0; ftb_idx <= ftb_end; ftb_idx++) { + byte ftb = area->gc_finaliser_table_start[ftb_idx]; + size_t block = ftb_idx * BLOCKS_PER_FTB; + while (ftb) { + MICROPY_GC_HOOK_LOOP(block); + if (ftb & 1) { // FTB_GET(area, block) shortcut + if (ATB_GET_KIND(area, block) == AT_HEAD) { mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(area, block); if (obj->type != NULL) { // if the object has a type then see if it has a __del__ method @@ -519,7 +510,35 @@ static void gc_sweep(void) { // clear finaliser flag FTB_CLEAR(area, block); } - #endif + } + ftb >>= 1; + block++; + } + } + } + #endif // MICROPY_ENABLE_FINALISER +} + +static void gc_sweep(void) { + #if MICROPY_PY_GC_COLLECT_RETVAL + MP_STATE_MEM(gc_collected) = 0; + #endif + // free unmarked heads and their tails + int free_tail = 0; + #if MICROPY_GC_SPLIT_HEAP_AUTO + mp_state_mem_area_t *prev_area = NULL; + #endif + + gc_sweep_run_finalisers(); + + for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { + size_t last_used_block = 0; + assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB); + + for (size_t block = 0; block <= area->gc_last_used_block; block++) { + MICROPY_GC_HOOK_LOOP(block); + switch (ATB_GET_KIND(area, block)) { + case AT_HEAD: free_tail = 1; DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(area, block)); #if MICROPY_PY_GC_COLLECT_RETVAL From 40e1c111e17864044190596dff6d32955d11280c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Dec 2024 10:58:06 +1100 Subject: [PATCH 0242/2098] py/gc: Allow gc_free from inside a gc_sweep finalizer. Do this by tracking being inside gc collection with a separate flag, GC_COLLECT_FLAG. In gc_free(), ignore this flag when determining if the heap is locked. * For finalisers calling gc_free() when heap is otherwise unlocked, this allows memory to be immediately freed (potentially avoiding a MemoryError). * Hard IRQs still can't call gc_free(), as heap will be locked via gc_lock(). * If finalisers are disabled then all of this code can be compiled out to save some code size. Signed-off-by: Angus Gratton --- py/gc.c | 35 +++++++++++++++++++++-------------- py/modmicropython.c | 4 ++-- py/mpstate.h | 13 +++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/py/gc.c b/py/gc.c index 297f4f85d3f..3d48e062494 100644 --- a/py/gc.c +++ b/py/gc.c @@ -334,12 +334,12 @@ void gc_lock(void) { // - each thread has its own gc_lock_depth so there are no races between threads; // - a hard interrupt will only change gc_lock_depth during its execution, and // upon return will restore the value of gc_lock_depth. - MP_STATE_THREAD(gc_lock_depth)++; + MP_STATE_THREAD(gc_lock_depth) += (1 << GC_LOCK_DEPTH_SHIFT); } void gc_unlock(void) { // This does not need to be atomic, See comment above in gc_lock. - MP_STATE_THREAD(gc_lock_depth)--; + MP_STATE_THREAD(gc_lock_depth) -= (1 << GC_LOCK_DEPTH_SHIFT); } bool gc_is_locked(void) { @@ -581,13 +581,18 @@ static void gc_sweep(void) { } } -void gc_collect_start(void) { +static void gc_collect_start_common(void) { GC_ENTER(); - MP_STATE_THREAD(gc_lock_depth)++; + assert((MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG) == 0); + MP_STATE_THREAD(gc_lock_depth) |= GC_COLLECT_FLAG; + MP_STATE_MEM(gc_stack_overflow) = 0; +} + +void gc_collect_start(void) { + gc_collect_start_common(); #if MICROPY_GC_ALLOC_THRESHOLD MP_STATE_MEM(gc_alloc_amount) = 0; #endif - MP_STATE_MEM(gc_stack_overflow) = 0; // Trace root pointers. This relies on the root pointers being organised // correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals, @@ -658,14 +663,12 @@ void gc_collect_end(void) { for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { area->gc_last_free_atb_index = 0; } - MP_STATE_THREAD(gc_lock_depth)--; + MP_STATE_THREAD(gc_lock_depth) &= ~GC_COLLECT_FLAG; GC_EXIT(); } void gc_sweep_all(void) { - GC_ENTER(); - MP_STATE_THREAD(gc_lock_depth)++; - MP_STATE_MEM(gc_stack_overflow) = 0; + gc_collect_start_common(); gc_collect_end(); } @@ -902,10 +905,13 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { // force the freeing of a piece of memory // TODO: freeing here does not call finaliser void gc_free(void *ptr) { - if (MP_STATE_THREAD(gc_lock_depth) > 0) { - // Cannot free while the GC is locked. However free is an optimisation - // to reclaim the memory immediately, this means it will now be left - // until the next collection. + // Cannot free while the GC is locked, unless we're only doing a gc sweep. + // However free is an optimisation to reclaim the memory immediately, this + // means it will now be left until the next collection. + // + // (We have the optimisation to free immediately from inside a gc sweep so + // that finalisers can free more memory when trying to avoid MemoryError.) + if (MP_STATE_THREAD(gc_lock_depth) & ~GC_COLLECT_FLAG) { return; } @@ -930,7 +936,8 @@ void gc_free(void *ptr) { #endif size_t block = BLOCK_FROM_PTR(area, ptr); - assert(ATB_GET_KIND(area, block) == AT_HEAD); + assert(ATB_GET_KIND(area, block) == AT_HEAD + || (ATB_GET_KIND(area, block) == AT_MARK && (MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG))); #if MICROPY_ENABLE_FINALISER FTB_CLEAR(area, block); diff --git a/py/modmicropython.c b/py/modmicropython.c index 1bf0a000c20..d1a687f10e1 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -132,13 +132,13 @@ static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_lock_obj, mp_micropython_he static mp_obj_t mp_micropython_heap_unlock(void) { gc_unlock(); - return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); + return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth) >> GC_LOCK_DEPTH_SHIFT); } static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_unlock_obj, mp_micropython_heap_unlock); #if MICROPY_PY_MICROPYTHON_HEAP_LOCKED static mp_obj_t mp_micropython_heap_locked(void) { - return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); + return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth) >> GC_LOCK_DEPTH_SHIFT); } static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_locked_obj, mp_micropython_heap_locked); #endif diff --git a/py/mpstate.h b/py/mpstate.h index 54eca596daa..51f290b55b5 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -77,6 +77,18 @@ typedef struct _mp_sched_item_t { mp_obj_t arg; } mp_sched_item_t; +// gc_lock_depth field is a combination of the GC_COLLECT_FLAG +// bit and a lock depth shifted GC_LOCK_DEPTH_SHIFT bits left. +#if MICROPY_ENABLE_FINALISER +#define GC_COLLECT_FLAG 1 +#define GC_LOCK_DEPTH_SHIFT 1 +#else +// If finalisers are disabled then this check doesn't matter, as gc_lock() +// is called anywhere else that heap can't be changed. So save some code size. +#define GC_COLLECT_FLAG 0 +#define GC_LOCK_DEPTH_SHIFT 0 +#endif + // This structure holds information about a single contiguous area of // memory reserved for the memory manager. typedef struct _mp_state_mem_area_t { @@ -268,6 +280,7 @@ typedef struct _mp_state_thread_t { #endif // Locking of the GC is done per thread. + // See GC_LOCK_DEPTH_SHIFT for an explanation of this field. uint16_t gc_lock_depth; //////////////////////////////////////////////////////////// From 4bcbe88e74de245fdee029b6a0746e3485d82a7e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Dec 2024 14:50:42 +1100 Subject: [PATCH 0243/2098] py: Add optional support for recursive mutexes, use for gc mutex. Enabled by default if using threading and no GIL This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/gc.c | 11 ++++++----- py/mpconfig.h | 5 +++++ py/mpstate.h | 2 +- py/mpthread.h | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/py/gc.c b/py/gc.c index 3d48e062494..6a3f48ac3c0 100644 --- a/py/gc.c +++ b/py/gc.c @@ -113,9 +113,12 @@ #endif #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL -#define GC_ENTER() mp_thread_mutex_lock(&MP_STATE_MEM(gc_mutex), 1) -#define GC_EXIT() mp_thread_mutex_unlock(&MP_STATE_MEM(gc_mutex)) +#define GC_MUTEX_INIT() mp_thread_recursive_mutex_init(&MP_STATE_MEM(gc_mutex)) +#define GC_ENTER() mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1) +#define GC_EXIT() mp_thread_recursive_mutex_unlock(&MP_STATE_MEM(gc_mutex)) #else +// Either no threading, or assume callers to gc_collect() hold the GIL +#define GC_MUTEX_INIT() #define GC_ENTER() #define GC_EXIT() #endif @@ -210,9 +213,7 @@ void gc_init(void *start, void *end) { MP_STATE_MEM(gc_alloc_amount) = 0; #endif - #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL - mp_thread_mutex_init(&MP_STATE_MEM(gc_mutex)); - #endif + GC_MUTEX_INIT(); } #if MICROPY_GC_SPLIT_HEAP diff --git a/py/mpconfig.h b/py/mpconfig.h index e84d258a122..64138a9ea7a 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1629,6 +1629,11 @@ typedef double mp_float_t; #define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) #endif +// Is a recursive mutex type in use? +#ifndef MICROPY_PY_THREAD_RECURSIVE_MUTEX +#define MICROPY_PY_THREAD_RECURSIVE_MUTEX (MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL) +#endif + // Extended modules #ifndef MICROPY_PY_ASYNCIO diff --git a/py/mpstate.h b/py/mpstate.h index 51f290b55b5..138c5617300 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -145,7 +145,7 @@ typedef struct _mp_state_mem_t { #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL // This is a global mutex used to make the GC thread-safe. - mp_thread_mutex_t gc_mutex; + mp_thread_recursive_mutex_t gc_mutex; #endif } mp_state_mem_t; diff --git a/py/mpthread.h b/py/mpthread.h index f335cc02911..795f230bb4a 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -48,6 +48,12 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex); int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); +#if MICROPY_PY_THREAD_RECURSIVE_MUTEX +void mp_thread_recursive_mutex_init(mp_thread_recursive_mutex_t *mutex); +int mp_thread_recursive_mutex_lock(mp_thread_recursive_mutex_t *mutex, int wait); +void mp_thread_recursive_mutex_unlock(mp_thread_recursive_mutex_t *mutex); +#endif + #endif // MICROPY_PY_THREAD #if MICROPY_PY_THREAD && MICROPY_PY_THREAD_GIL From 3bfedd0f4a9e8b11ff97851615ec4761663b0e94 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Dec 2024 14:51:10 +1100 Subject: [PATCH 0244/2098] rp2: Migrate to the new mp_thread_recursive_mutex_t. Necessary for GC support, also refactored pendsv usage. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/mpthreadport.c | 4 ++-- ports/rp2/mpthreadport.h | 20 +++++++++++++++++++- ports/rp2/pendsv.c | 14 +++++++------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ports/rp2/mpthreadport.c b/ports/rp2/mpthreadport.c index 0d3b343cab0..8fd5e5d7902 100644 --- a/ports/rp2/mpthreadport.c +++ b/ports/rp2/mpthreadport.c @@ -84,10 +84,10 @@ void mp_thread_deinit(void) { assert(get_core_num() == 0); // Must ensure that core1 is not currently holding the GC lock, otherwise // it will be terminated while holding the lock. - mp_thread_mutex_lock(&MP_STATE_MEM(gc_mutex), 1); + mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1); multicore_reset_core1(); core1_entry = NULL; - mp_thread_mutex_unlock(&MP_STATE_MEM(gc_mutex)); + mp_thread_recursive_mutex_unlock(&MP_STATE_MEM(gc_mutex)); } void mp_thread_gc_others(void) { diff --git a/ports/rp2/mpthreadport.h b/ports/rp2/mpthreadport.h index 67a0da0e937..f2f2e17bb07 100644 --- a/ports/rp2/mpthreadport.h +++ b/ports/rp2/mpthreadport.h @@ -26,9 +26,10 @@ #ifndef MICROPY_INCLUDED_RP2_MPTHREADPORT_H #define MICROPY_INCLUDED_RP2_MPTHREADPORT_H -#include "pico/mutex.h" +#include "mutex_extra.h" typedef struct mutex mp_thread_mutex_t; +typedef recursive_mutex_nowait_t mp_thread_recursive_mutex_t; extern void *core_state[2]; @@ -65,4 +66,21 @@ static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *m) { mutex_exit(m); } +static inline void mp_thread_recursive_mutex_init(mp_thread_recursive_mutex_t *m) { + recursive_mutex_nowait_init(m); +} + +static inline int mp_thread_recursive_mutex_lock(mp_thread_recursive_mutex_t *m, int wait) { + if (wait) { + recursive_mutex_nowait_enter_blocking(m); + return 1; + } else { + return recursive_mutex_nowait_try_enter(m, NULL); + } +} + +static inline void mp_thread_recursive_mutex_unlock(mp_thread_recursive_mutex_t *m) { + recursive_mutex_nowait_exit(m); +} + #endif // MICROPY_INCLUDED_RP2_MPTHREADPORT_H diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 905a5aa162e..4ba1e81604b 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -26,7 +26,7 @@ #include #include "py/mpconfig.h" -#include "mutex_extra.h" +#include "py/mpthread.h" #include "pendsv.h" #if PICO_RP2040 @@ -47,21 +47,21 @@ 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; +static mp_thread_recursive_mutex_t pendsv_mutex; void pendsv_init(void) { - recursive_mutex_nowait_init(&pendsv_mutex); + mp_thread_recursive_mutex_init(&pendsv_mutex); } void pendsv_suspend(void) { // Recursive Mutex here as either core may call pendsv_suspend() and expect // both mutual exclusion (other core can't enter pendsv_suspend() at the // same time), and that no PendSV handler will run. - recursive_mutex_nowait_enter_blocking(&pendsv_mutex); + mp_thread_recursive_mutex_lock(&pendsv_mutex, 1); } void pendsv_resume(void) { - recursive_mutex_nowait_exit(&pendsv_mutex); + mp_thread_recursive_mutex_unlock(&pendsv_mutex); // Run pendsv if needed. Find an entry with a dispatch and call pendsv dispatch // with it. If pendsv runs it will service all slots. @@ -97,7 +97,7 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { // PendSV interrupt handler to perform background processing. void PendSV_Handler(void) { - if (!recursive_mutex_nowait_try_enter(&pendsv_mutex, NULL)) { + if (!mp_thread_recursive_mutex_lock(&pendsv_mutex, 0)) { // Failure here means core 1 holds pendsv_mutex. ISR will // run again after core 1 calls pendsv_resume(). return; @@ -117,5 +117,5 @@ void PendSV_Handler(void) { } } - recursive_mutex_nowait_exit(&pendsv_mutex); + mp_thread_recursive_mutex_unlock(&pendsv_mutex); } From fd0e529a476650a070046a38b7ba7e253b65a77a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 9 Jan 2025 14:38:31 +1100 Subject: [PATCH 0245/2098] unix: Add recursive mutex support. Allows refactoring the existing thread_mutex atomic section support to use the new recursive mutex type. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/unix/mpthreadport.c | 31 ++++++++++++++++++++++++------- ports/unix/mpthreadport.h | 1 + 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 16ac4da8bf5..5172645bc14 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -65,7 +65,7 @@ static pthread_key_t tls_key; // The mutex is used for any code in this port that needs to be thread safe. // Specifically for thread management, access to the linked list is one example. // But also, e.g. scheduler state. -static pthread_mutex_t thread_mutex; +static mp_thread_recursive_mutex_t thread_mutex; static mp_thread_t *thread; // this is used to synchronise the signal handler of the thread @@ -78,11 +78,11 @@ static sem_t thread_signal_done; #endif void mp_thread_unix_begin_atomic_section(void) { - pthread_mutex_lock(&thread_mutex); + mp_thread_recursive_mutex_lock(&thread_mutex, true); } void mp_thread_unix_end_atomic_section(void) { - pthread_mutex_unlock(&thread_mutex); + mp_thread_recursive_mutex_unlock(&thread_mutex); } // this signal handler is used to scan the regs and stack of a thread @@ -113,10 +113,7 @@ void mp_thread_init(void) { // Needs to be a recursive mutex to emulate the behavior of // BEGIN_ATOMIC_SECTION on bare metal. - pthread_mutexattr_t thread_mutex_attr; - pthread_mutexattr_init(&thread_mutex_attr); - pthread_mutexattr_settype(&thread_mutex_attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&thread_mutex, &thread_mutex_attr); + mp_thread_recursive_mutex_init(&thread_mutex); // create first entry in linked list of all threads thread = malloc(sizeof(mp_thread_t)); @@ -321,6 +318,26 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { // TODO check return value } +#if MICROPY_PY_THREAD_RECURSIVE_MUTEX + +void mp_thread_recursive_mutex_init(mp_thread_recursive_mutex_t *mutex) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mutex, &attr); + pthread_mutexattr_destroy(&attr); +} + +int mp_thread_recursive_mutex_lock(mp_thread_recursive_mutex_t *mutex, int wait) { + return mp_thread_mutex_lock(mutex, wait); +} + +void mp_thread_recursive_mutex_unlock(mp_thread_recursive_mutex_t *mutex) { + mp_thread_mutex_unlock(mutex); +} + +#endif // MICROPY_PY_THREAD_RECURSIVE_MUTEX + #endif // MICROPY_PY_THREAD // this is used even when MICROPY_PY_THREAD is disabled diff --git a/ports/unix/mpthreadport.h b/ports/unix/mpthreadport.h index b365f200edf..a38223e720b 100644 --- a/ports/unix/mpthreadport.h +++ b/ports/unix/mpthreadport.h @@ -28,6 +28,7 @@ #include typedef pthread_mutex_t mp_thread_mutex_t; +typedef pthread_mutex_t mp_thread_recursive_mutex_t; void mp_thread_init(void); void mp_thread_deinit(void); From 990f50fbb829e41ce275d942c056bcd3f4b857df Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 29 Jan 2025 10:10:10 +1100 Subject: [PATCH 0246/2098] py/gc: Reorder static functions for clarity. - Renamed gc_sweep to gc_sweep_free_blocks. - Call gc_sweep_run_finalisers from top level. - Reordered the gc static functions to be in approximate runtime sequence (with forward declarations) rather than in declaration order. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/gc.c | 175 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 93 insertions(+), 82 deletions(-) diff --git a/py/gc.c b/py/gc.c index 6a3f48ac3c0..eda63187b20 100644 --- a/py/gc.c +++ b/py/gc.c @@ -123,6 +123,18 @@ #define GC_EXIT() #endif +// Static functions for individual steps of the GC mark/sweep sequence +static void gc_collect_start_common(void); +static void *gc_get_ptr(void **ptrs, int i); +#if MICROPY_GC_SPLIT_HEAP +static void gc_mark_subtree(mp_state_mem_area_t *area, size_t block); +#else +static void gc_mark_subtree(size_t block); +#endif +static void gc_deal_with_stack_overflow(void); +static void gc_sweep_run_finalisers(void); +static void gc_sweep_free_blocks(void); + // TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) { // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes): @@ -379,6 +391,64 @@ static inline mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) { #endif #endif +void gc_collect_start(void) { + gc_collect_start_common(); + #if MICROPY_GC_ALLOC_THRESHOLD + MP_STATE_MEM(gc_alloc_amount) = 0; + #endif + + // Trace root pointers. This relies on the root pointers being organised + // correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals, + // dict_globals, then the root pointer section of mp_state_vm. + void **ptrs = (void **)(void *)&mp_state_ctx; + size_t root_start = offsetof(mp_state_ctx_t, thread.dict_locals); + size_t root_end = offsetof(mp_state_ctx_t, vm.qstr_last_chunk); + gc_collect_root(ptrs + root_start / sizeof(void *), (root_end - root_start) / sizeof(void *)); + + #if MICROPY_ENABLE_PYSTACK + // Trace root pointers from the Python stack. + ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start); + gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *)); + #endif +} + +static void gc_collect_start_common(void) { + GC_ENTER(); + assert((MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG) == 0); + MP_STATE_THREAD(gc_lock_depth) |= GC_COLLECT_FLAG; + MP_STATE_MEM(gc_stack_overflow) = 0; +} + +void gc_collect_root(void **ptrs, size_t len) { + #if !MICROPY_GC_SPLIT_HEAP + mp_state_mem_area_t *area = &MP_STATE_MEM(area); + #endif + for (size_t i = 0; i < len; i++) { + MICROPY_GC_HOOK_LOOP(i); + void *ptr = gc_get_ptr(ptrs, i); + #if MICROPY_GC_SPLIT_HEAP + mp_state_mem_area_t *area = gc_get_ptr_area(ptr); + if (!area) { + continue; + } + #else + if (!VERIFY_PTR(ptr)) { + continue; + } + #endif + size_t block = BLOCK_FROM_PTR(area, ptr); + if (ATB_GET_KIND(area, block) == AT_HEAD) { + // An unmarked head: mark it, and mark all its children + ATB_HEAD_TO_MARK(area, block); + #if MICROPY_GC_SPLIT_HEAP + gc_mark_subtree(area, block); + #else + gc_mark_subtree(block); + #endif + } + } +} + // Take the given block as the topmost block on the stack. Check all it's // children: mark the unmarked child blocks and put those newly marked // blocks on the stack. When all children have been checked, pop off the @@ -457,6 +527,25 @@ static void gc_mark_subtree(size_t block) } } +void gc_sweep_all(void) { + gc_collect_start_common(); + gc_collect_end(); +} + +void gc_collect_end(void) { + gc_deal_with_stack_overflow(); + gc_sweep_run_finalisers(); + gc_sweep_free_blocks(); + #if MICROPY_GC_SPLIT_HEAP + MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area); + #endif + for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { + area->gc_last_free_atb_index = 0; + } + MP_STATE_THREAD(gc_lock_depth) &= ~GC_COLLECT_FLAG; + GC_EXIT(); +} + static void gc_deal_with_stack_overflow(void) { while (MP_STATE_MEM(gc_stack_overflow)) { MP_STATE_MEM(gc_stack_overflow) = 0; @@ -520,18 +609,16 @@ static void gc_sweep_run_finalisers(void) { #endif // MICROPY_ENABLE_FINALISER } -static void gc_sweep(void) { +// Free unmarked heads and their tails +static void gc_sweep_free_blocks(void) { #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected) = 0; #endif - // free unmarked heads and their tails int free_tail = 0; #if MICROPY_GC_SPLIT_HEAP_AUTO mp_state_mem_area_t *prev_area = NULL; #endif - gc_sweep_run_finalisers(); - for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { size_t last_used_block = 0; assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB); @@ -541,7 +628,7 @@ static void gc_sweep(void) { switch (ATB_GET_KIND(area, block)) { case AT_HEAD: free_tail = 1; - DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(area, block)); + DEBUG_printf("gc_sweep_free_blocks(%p)\n", (void *)PTR_FROM_BLOCK(area, block)); #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected)++; #endif @@ -572,7 +659,7 @@ static void gc_sweep(void) { #if MICROPY_GC_SPLIT_HEAP_AUTO // Free any empty area, aside from the first one if (last_used_block == 0 && prev_area != NULL) { - DEBUG_printf("gc_sweep free empty area %p\n", area); + DEBUG_printf("gc_sweep_free_blocks free empty area %p\n", area); NEXT_AREA(prev_area) = NEXT_AREA(area); MP_PLAT_FREE_HEAP(area); area = prev_area; @@ -582,34 +669,6 @@ static void gc_sweep(void) { } } -static void gc_collect_start_common(void) { - GC_ENTER(); - assert((MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG) == 0); - MP_STATE_THREAD(gc_lock_depth) |= GC_COLLECT_FLAG; - MP_STATE_MEM(gc_stack_overflow) = 0; -} - -void gc_collect_start(void) { - gc_collect_start_common(); - #if MICROPY_GC_ALLOC_THRESHOLD - MP_STATE_MEM(gc_alloc_amount) = 0; - #endif - - // Trace root pointers. This relies on the root pointers being organised - // correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals, - // dict_globals, then the root pointer section of mp_state_vm. - void **ptrs = (void **)(void *)&mp_state_ctx; - size_t root_start = offsetof(mp_state_ctx_t, thread.dict_locals); - size_t root_end = offsetof(mp_state_ctx_t, vm.qstr_last_chunk); - gc_collect_root(ptrs + root_start / sizeof(void *), (root_end - root_start) / sizeof(void *)); - - #if MICROPY_ENABLE_PYSTACK - // Trace root pointers from the Python stack. - ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start); - gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *)); - #endif -} - // Address sanitizer needs to know that the access to ptrs[i] must always be // considered OK, even if it's a load from an address that would normally be // prohibited (due to being undefined, in a red zone, etc). @@ -625,54 +684,6 @@ static void *gc_get_ptr(void **ptrs, int i) { return ptrs[i]; } -void gc_collect_root(void **ptrs, size_t len) { - #if !MICROPY_GC_SPLIT_HEAP - mp_state_mem_area_t *area = &MP_STATE_MEM(area); - #endif - for (size_t i = 0; i < len; i++) { - MICROPY_GC_HOOK_LOOP(i); - void *ptr = gc_get_ptr(ptrs, i); - #if MICROPY_GC_SPLIT_HEAP - mp_state_mem_area_t *area = gc_get_ptr_area(ptr); - if (!area) { - continue; - } - #else - if (!VERIFY_PTR(ptr)) { - continue; - } - #endif - size_t block = BLOCK_FROM_PTR(area, ptr); - if (ATB_GET_KIND(area, block) == AT_HEAD) { - // An unmarked head: mark it, and mark all its children - ATB_HEAD_TO_MARK(area, block); - #if MICROPY_GC_SPLIT_HEAP - gc_mark_subtree(area, block); - #else - gc_mark_subtree(block); - #endif - } - } -} - -void gc_collect_end(void) { - gc_deal_with_stack_overflow(); - gc_sweep(); - #if MICROPY_GC_SPLIT_HEAP - MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area); - #endif - for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { - area->gc_last_free_atb_index = 0; - } - MP_STATE_THREAD(gc_lock_depth) &= ~GC_COLLECT_FLAG; - GC_EXIT(); -} - -void gc_sweep_all(void) { - gc_collect_start_common(); - gc_collect_end(); -} - void gc_info(gc_info_t *info) { GC_ENTER(); info->total = 0; From 112f65776588ef0cc3307f8bef208a480037d7d0 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Thu, 23 Jan 2025 23:17:00 -0800 Subject: [PATCH 0247/2098] stm32/eth: Make ETH DMA buffer attributes configurable. Signed-off-by: Kwabena W. Agyeman --- ports/stm32/eth.c | 2 +- ports/stm32/mpconfigboard_common.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index fd46bde23cb..9f655306816 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -121,7 +121,7 @@ typedef struct _eth_t { int16_t (*phy_get_link_status)(uint32_t phy_addr); } eth_t; -static eth_dma_t eth_dma __attribute__((aligned(16384))); +static eth_dma_t eth_dma MICROPY_HW_ETH_DMA_ATTRIBUTE; eth_t eth_instance; diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index e1c9c159ecf..4ce0a75b87e 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -669,3 +669,7 @@ #endif #define MICROPY_HW_USES_BOOTLOADER (MICROPY_HW_VTOR != 0x08000000) + +#ifndef MICROPY_HW_ETH_DMA_ATTRIBUTE +#define MICROPY_HW_ETH_DMA_ATTRIBUTE __attribute__((aligned(16384))); +#endif From 55ae597bb6044c392e9264f1af09ddcc19901f44 Mon Sep 17 00:00:00 2001 From: Carl Pottle Date: Sun, 29 Dec 2024 15:20:37 -0800 Subject: [PATCH 0248/2098] rp2/modmachine: Make lightsleep preserve SLEEP_EN0 and SLEEP_EN1. The problem was introduced in d1423ef7a23793de3777e84d985f9902241e788e, calling `machine.lightsleep()` overwrites RP2xxx registers `SLEEP_EN0` and `SLEEP_EN1` with their power on default values. Prior to that commit the register values were saved on entry to lightsleep and restored before returning. These changes restores the earlier behavior. Fixes issue #16502. Signed-off-by: Carl Pottle --- ports/rp2/modmachine.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 3229aed277b..954ea216497 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -196,6 +196,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif xosc_dormant(); } else { + uint32_t save_sleep_en0 = clocks_hw->sleep_en0; + uint32_t save_sleep_en1 = clocks_hw->sleep_en1; bool timer3_enabled = irq_is_enabled(3); const uint32_t alarm_num = 3; @@ -251,8 +253,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { if (!timer3_enabled) { irq_set_enabled(irq_num, false); } - clocks_hw->sleep_en0 |= ~(0u); - clocks_hw->sleep_en1 |= ~(0u); + clocks_hw->sleep_en0 = save_sleep_en0; + clocks_hw->sleep_en1 = save_sleep_en1; } // Enable ROSC. From 81ab49a60759376271ad1e1fbf05bae0ffe9e7b1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 23 Jan 2025 16:37:33 +1100 Subject: [PATCH 0249/2098] tests/ports/rp2: Add test for SLEEP_ENx registers over lightsleep. Signed-off-by: Damien George --- tests/ports/rp2/rp2_lightsleep_regs.py | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/ports/rp2/rp2_lightsleep_regs.py diff --git a/tests/ports/rp2/rp2_lightsleep_regs.py b/tests/ports/rp2/rp2_lightsleep_regs.py new file mode 100644 index 00000000000..4a833d0a9c3 --- /dev/null +++ b/tests/ports/rp2/rp2_lightsleep_regs.py @@ -0,0 +1,58 @@ +# Test that SLEEP_ENx registers are preserved over a call to machine.lightsleep(). + +import sys +from machine import mem32, lightsleep +import unittest + +is_rp2350 = "RP2350" in sys.implementation._machine + +if is_rp2350: + CLOCK_BASE = 0x40010000 + SLEEP_EN0 = CLOCK_BASE + 0xB4 + SLEEP_EN1 = CLOCK_BASE + 0xB8 + TO_DISABLE_EN0 = 1 << 30 # SHA256 + TO_DISABLE_EN1 = 1 << 4 # SRAM0 +else: + CLOCK_BASE = 0x40008000 + SLEEP_EN0 = CLOCK_BASE + 0xA8 + SLEEP_EN1 = CLOCK_BASE + 0xAC + TO_DISABLE_EN0 = 1 << 28 # SRAM0 + TO_DISABLE_EN1 = 1 << 0 # SRAM4 + + +class Test(unittest.TestCase): + def setUp(self): + self.orig_sleep_en0 = mem32[SLEEP_EN0] + self.orig_sleep_en1 = mem32[SLEEP_EN1] + + def tearDown(self): + mem32[SLEEP_EN0] = self.orig_sleep_en0 + mem32[SLEEP_EN1] = self.orig_sleep_en1 + + def test_sleep_en_regs(self): + print() + + # Disable some bits so the registers aren't just 0xffff. + mem32[SLEEP_EN0] &= ~TO_DISABLE_EN0 + mem32[SLEEP_EN1] &= ~TO_DISABLE_EN1 + + # Get the registers before the lightsleep. + sleep_en0_before = mem32[SLEEP_EN0] & 0xFFFFFFFF + sleep_en1_before = mem32[SLEEP_EN1] & 0xFFFFFFFF + print(hex(sleep_en0_before), hex(sleep_en1_before)) + + # Do a lightsleep. + lightsleep(100) + + # Get the registers after a lightsleep. + sleep_en0_after = mem32[SLEEP_EN0] & 0xFFFFFFFF + sleep_en1_after = mem32[SLEEP_EN1] & 0xFFFFFFFF + print(hex(sleep_en0_after), hex(sleep_en1_after)) + + # Check the registers have not changed. + self.assertEqual(sleep_en0_before, sleep_en0_after) + self.assertEqual(sleep_en1_before, sleep_en1_after) + + +if __name__ == "__main__": + unittest.main() From 3699cf5f38736b670a26786af4025584dcb5aef2 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Tue, 21 Jan 2025 21:35:08 +0000 Subject: [PATCH 0250/2098] rp2/rp2_flash: Workaround multicore lockout not being reset. With regression test. See upstream bug https://github.com/raspberrypi/pico-sdk/issues/2201 Tested-by: Angus Gratton Signed-off-by: Mike Bell --- ports/rp2/rp2_flash.c | 14 ++++++++++++-- tests/ports/rp2/rp2_thread_reset_part1.py | 18 ++++++++++++++++++ tests/ports/rp2/rp2_thread_reset_part1.py.exp | 1 + tests/ports/rp2/rp2_thread_reset_part2.py | 12 ++++++++++++ tests/ports/rp2/rp2_thread_reset_part2.py.exp | 3 +++ 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/ports/rp2/rp2_thread_reset_part1.py create mode 100644 tests/ports/rp2/rp2_thread_reset_part1.py.exp create mode 100644 tests/ports/rp2/rp2_thread_reset_part2.py create mode 100644 tests/ports/rp2/rp2_thread_reset_part2.py.exp diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index c1acb54e757..a487fb1633b 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -70,10 +70,20 @@ bi_decl(bi_block_device( BINARY_INFO_BLOCK_DEV_FLAG_WRITE | BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); +// This is a workaround to pico-sdk #2201: https://github.com/raspberrypi/pico-sdk/issues/2201 +// which means the multicore_lockout_victim_is_initialized returns true even after core1 is reset. +static bool use_multicore_lockout(void) { + return multicore_lockout_victim_is_initialized(1 - get_core_num()) + #if MICROPY_PY_THREAD + && core1_entry != NULL + #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) { - if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + if (use_multicore_lockout()) { multicore_lockout_start_blocking(); } return save_and_disable_interrupts(); @@ -81,7 +91,7 @@ static uint32_t begin_critical_flash_section(void) { static void end_critical_flash_section(uint32_t state) { restore_interrupts(state); - if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + if (use_multicore_lockout()) { multicore_lockout_end_blocking(); } } diff --git a/tests/ports/rp2/rp2_thread_reset_part1.py b/tests/ports/rp2/rp2_thread_reset_part1.py new file mode 100644 index 00000000000..d43868113f5 --- /dev/null +++ b/tests/ports/rp2/rp2_thread_reset_part1.py @@ -0,0 +1,18 @@ +# This is a regression test for https://github.com/micropython/micropython/issues/16619 +# it runs in two parts by necessity: +# +# - This "part1" creates a non-terminating thread. +# - The test runner issues a soft reset, which will terminate that thread. +# - "part2" is the actual test, which is whether flash access works correctly +# after the thread was terminated by soft reset. + +import _thread + + +def infinite(): + while True: + pass + + +_thread.start_new_thread(infinite, ()) +print("Part 1 complete...") diff --git a/tests/ports/rp2/rp2_thread_reset_part1.py.exp b/tests/ports/rp2/rp2_thread_reset_part1.py.exp new file mode 100644 index 00000000000..48ea7efad97 --- /dev/null +++ b/tests/ports/rp2/rp2_thread_reset_part1.py.exp @@ -0,0 +1 @@ +Part 1 complete... diff --git a/tests/ports/rp2/rp2_thread_reset_part2.py b/tests/ports/rp2/rp2_thread_reset_part2.py new file mode 100644 index 00000000000..15f0eaab8f8 --- /dev/null +++ b/tests/ports/rp2/rp2_thread_reset_part2.py @@ -0,0 +1,12 @@ +# This is part2 of a two-part regression test, see part1 +# for details of what's expected. +import os + +FILENAME = "/rp2_thread_reset_test.txt" + +print("Starting") +with open(FILENAME, "w") as f: + f.write("test") +print("Written") +os.unlink(FILENAME) +print("Removed") diff --git a/tests/ports/rp2/rp2_thread_reset_part2.py.exp b/tests/ports/rp2/rp2_thread_reset_part2.py.exp new file mode 100644 index 00000000000..d7581c7d260 --- /dev/null +++ b/tests/ports/rp2/rp2_thread_reset_part2.py.exp @@ -0,0 +1,3 @@ +Starting +Written +Removed From 4bed77cc233a84846a0c47347b520ce5b816758b Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Fri, 19 May 2023 17:51:52 +1000 Subject: [PATCH 0251/2098] esp8266/network_wlan: Make WLAN.config('channel') use wifi_get_channel. Prior to this fix, `WLAN.config('channel')` would return an incorrect channel for AP_IF if STA has connected to an external AP running on a different channel. The esp8266 now has the same behaviour as for esp32 per commit 98d1c50159fe9427d72ec358ba0219eaebb1d991. Fixes issue #11463. Signed-off-by: Glenn Moloney --- ports/esp8266/network_wlan.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ports/esp8266/network_wlan.c b/ports/esp8266/network_wlan.c index 44154ff6d8e..2e43ebfb2b7 100644 --- a/ports/esp8266/network_wlan.c +++ b/ports/esp8266/network_wlan.c @@ -641,8 +641,7 @@ static mp_obj_t esp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); break; case MP_QSTR_channel: - req_if = SOFTAP_IF; - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.channel); + val = MP_OBJ_NEW_SMALL_INT(wifi_get_channel()); break; case MP_QSTR_hostname: case MP_QSTR_dhcp_hostname: { From b1e6c2b655209022fb1dd1e7b3271af010fcccb5 Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Mon, 26 Jun 2023 18:05:37 +1000 Subject: [PATCH 0252/2098] esp8266/network_wlan: Make WLAN.config(channel=x) use wifi_set_channel. Also permits channel option to be used for STA_IF interface. This provides compatibility with esp32 code, especially for espnow users. Signed-off-by: Glenn Moloney --- ports/esp8266/network_wlan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp8266/network_wlan.c b/ports/esp8266/network_wlan.c index 2e43ebfb2b7..7131b88bac9 100644 --- a/ports/esp8266/network_wlan.c +++ b/ports/esp8266/network_wlan.c @@ -565,8 +565,8 @@ static mp_obj_t esp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs break; } case MP_QSTR_channel: { - req_if = SOFTAP_IF; cfg.ap.channel = mp_obj_get_int(kwargs->table[i].value); + error_check(wifi_set_channel(cfg.ap.channel), "can't set channel"); break; } case MP_QSTR_hostname: From 921f19fc9d697488f4f86bf8db73d43b04b781a6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Feb 2025 15:14:51 +1100 Subject: [PATCH 0253/2098] tests/multi_wlan: Remove esp8266 port workaround. Not needed due to parent commit. Signed-off-by: Angus Gratton --- tests/multi_wlan/01_ap_sta.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/multi_wlan/01_ap_sta.py b/tests/multi_wlan/01_ap_sta.py index 1a8e80cd33a..368addf1393 100644 --- a/tests/multi_wlan/01_ap_sta.py +++ b/tests/multi_wlan/01_ap_sta.py @@ -96,15 +96,7 @@ def instance1(): print("STA connected") - # Print the current channel, if the port support this - try: - print("channel", sta.config("channel")) - except OSError as e: - if "AP" in str(e): - # ESP8266 only supports reading channel on the AP interface, so fake this result - print("channel", CHANNEL) - else: - raise + print("channel", sta.config("channel")) print("STA waiting for disconnect...") From 71e8b27b26f46f11f642381d4f6699ee1fc073f6 Mon Sep 17 00:00:00 2001 From: eggfly Date: Mon, 23 Dec 2024 19:40:06 +0800 Subject: [PATCH 0254/2098] esp32/README: Fix board in octal-SPIRAM example make command. Signed-off-by: eggfly --- ports/esp32/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 8597c85ec31..6ed7eddb8b8 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -152,7 +152,7 @@ $ make BOARD=ESP32_GENERIC BOARD_VARIANT=OTA or to enable octal-SPIRAM support for the `ESP32_GENERIC_S3` board: ```bash -$ make BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM_OCT +$ make BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT ``` From b603fa38b25c5136d36cdbfff91b69f96efacd69 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 3 Feb 2025 16:03:22 +1100 Subject: [PATCH 0255/2098] py/mkrules.mk: Reset USER_C_MODULES when building mpy-cross dependency. When a port automatically compiles `mpy-cross`, if `USER_C_MODULES` is provided by the user on the command line then it is also applied to the `mpy-cross` build. That can lead to build errors if the path is relative and not found when building `mpy-cross`. Fix that by explicitly resetting `USER_C_MODULES` when invoking the `mpy-cross` build. Signed-off-by: Andrew Leech --- py/mkrules.cmake | 2 +- py/mkrules.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index bfc56abfe80..3ee4c4c31aa 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -211,7 +211,7 @@ if(MICROPY_FROZEN_MANIFEST) endif() add_custom_command( OUTPUT ${MICROPY_MPYCROSS_DEPENDENCY} - COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross + COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross USER_C_MODULES= ) endif() diff --git a/py/mkrules.mk b/py/mkrules.mk index 373bda8996f..e9504ce39f8 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -176,7 +176,7 @@ $(HEADER_BUILD): ifneq ($(MICROPY_MPYCROSS_DEPENDENCY),) # to automatically build mpy-cross, if needed $(MICROPY_MPYCROSS_DEPENDENCY): - $(MAKE) -C "$(abspath $(dir $@)..)" + $(MAKE) -C "$(abspath $(dir $@)..)" USER_C_MODULES= endif ifneq ($(FROZEN_DIR),) From dfd1d69a72956e650af5cb071e4d9be61b5d322a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 24 Jan 2025 16:26:55 +0100 Subject: [PATCH 0256/2098] tests/run-natmodtests.py: Autodetect the test target architecture. This commit lets the natmod tests runner to automatically detect the architecture of the test target. This allows to avoid to explicitly pass the architecture name to the runner in test scripts. However, the ability to manually specify a target was not removed but it was made optional. This way the user is able to override the architecture name if needed (like if one wants to test an armv6 MPY on an armv7 board). Signed-off-by: Alessandro Gatti --- ports/qemu/README.md | 4 +- ports/qemu/boards/VIRT_RV32/mpconfigboard.mk | 2 - tests/run-natmodtests.py | 58 ++++++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/ports/qemu/README.md b/ports/qemu/README.md index 70edf97f58e..c7d0dc1f4ea 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -105,9 +105,7 @@ can also be tested with this command (this is currently supported only for the $ make test_natmod The same remarks about manually running the tests apply for native modules, but -`run-natmodtests.py` should be run instead of `run-tests.py`. In this case you -also have to explicitly pass the architecture you are running native modules to -`run-natmodtests.py` ("--arch rv32imc" for the `VIRT_RV32` board). +`run-natmodtests.py` should be run instead of `run-tests.py`. Extra make options ------------------ diff --git a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk index dd926480052..ce12720928e 100644 --- a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk +++ b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk @@ -9,5 +9,3 @@ LDSCRIPT = mcu/rv32/virt.ld SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o MPY_CROSS_FLAGS += -march=rv32imc - -RUN_NATMODTESTS_ARGS = --arch rv32imc diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index 1fe44bec161..b858989daa4 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -28,6 +28,23 @@ "re": "re/re_$(ARCH).mpy", } +# Supported architectures for native mpy modules +AVAILABLE_ARCHS = ( + "x86", + "x64", + "armv6", + "armv6m", + "armv7m", + "armv7em", + "armv7emsp", + "armv7emdp", + "xtensa", + "xtensawin", + "rv32imc", +) + +ARCH_MAPPINGS = {"armv7em": "armv7m"} + # Code to allow a target MicroPython to import an .mpy from RAM injected_import_hook_code = """\ import sys, io, vfs @@ -96,14 +113,33 @@ def run_script(self, script): return b"", er -def run_tests(target_truth, target, args, stats): +def detect_architecture(target): + with open("./feature_check/target_info.py", "rb") as f: + target_info_data = f.read() + result_out, error = target.run_script(target_info_data) + if error is not None: + return None, None, error + info = result_out.split(b" ") + if len(info) < 2: + return None, None, "unexpected target info: {}".format(info) + platform = info[0].strip().decode() + arch = info[1].strip().decode() + if arch not in AVAILABLE_ARCHS: + if arch == "None": + return None, None, "the target does not support dynamic modules" + else: + return None, None, "{} is not a supported architecture".format(arch) + return platform, arch, None + + +def run_tests(target_truth, target, args, stats, resolved_arch): for test_file in args.files: # Find supported test test_file_basename = os.path.basename(test_file) for k, v in TEST_MAPPINGS.items(): if test_file_basename.startswith(k): test_module = k - test_mpy = v.replace("$(ARCH)", args.arch) + test_mpy = v.replace("$(ARCH)", resolved_arch) break else: print("---- {} - no matching mpy".format(test_file)) @@ -174,7 +210,7 @@ def main(): "-d", "--device", default="/dev/ttyACM0", help="the device for pyboard.py" ) cmd_parser.add_argument( - "-a", "--arch", default="x64", help="native architecture of the target" + "-a", "--arch", choices=AVAILABLE_ARCHS, help="override native architecture of the target" ) cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() @@ -186,8 +222,22 @@ def main(): else: target = TargetSubprocess([MICROPYTHON]) + if hasattr(args, "arch") and args.arch is not None: + target_arch = args.arch + target_platform = None + else: + target_platform, target_arch, error = detect_architecture(target) + if error: + print("Cannot run tests: {}".format(error)) + sys.exit(1) + target_arch = ARCH_MAPPINGS.get(target_arch, target_arch) + + if target_platform: + print("platform={} ".format(target_platform), end="") + print("arch={}".format(target_arch)) + stats = {"total": 0, "pass": 0, "fail": 0, "skip": 0} - run_tests(target_truth, target, args, stats) + run_tests(target_truth, target, args, stats, target_arch) target.close() target_truth.close() From ca3090a33f06da57221827ad506d80cb1f826b99 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 24 Jan 2025 11:18:39 +0100 Subject: [PATCH 0257/2098] qemu/Makefile: Fix shell interpolation for automated natmod tests. This commit fixes the command used to run natmod tests, as it relied on a string interpolation feature of the POSIX shell that was not working as expected inside a makefile. The interpolation was not performed from inside the makefile and the raw command string was sent to the operating system for execution. Now the command is run by using a different type of string substitution, which explicitly performs the interpolation using a POSIX shell for-loop. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index d2550cf883b..befef5d5200 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -189,11 +189,14 @@ test_full: $(BUILD)/firmware.elf cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy --emit native +# "btree" currently does not build for rv32imc (Picolibc TLS incompatibility). .PHONY: test_natmod test_natmod: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) - # "btree" cannot build against Picolibc right now. - cd $(TOP)/tests && ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_NATMODTESTS_ARGS) extmod/{deflate,framebuf,heapq,random_basic,re}*.py + cd $(TOP)/tests && \ + for natmod in deflate framebuf heapq random_basic re; do \ + ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" extmod/$$natmod*.py; \ + done $(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ) $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) From f594c6f66ec49e02ba06165d1f1db202d82575ec Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 24 Jan 2025 18:00:05 +0100 Subject: [PATCH 0258/2098] tools/ci.sh: Add natmod tests for QEMU/Arm. This commit adds the natmod tests for the MPS2_AN385 board running inside QEMU to the CI pipeline. Now natmod tests capabilities are equal between the Arm and RV32 platforms for the QEMU port. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 2 ++ tools/ci.sh | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index befef5d5200..e9e1e0f957e 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -40,6 +40,8 @@ endif include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk +GIT_SUBMODULES += lib/berkeley-db-1.xx + CFLAGS += -DMICROPY_HEAP_SIZE=$(MICROPY_HEAP_SIZE) ################################################################################ diff --git a/tools/ci.sh b/tools/ci.sh index 374395abd2c..2c647012f14 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -266,6 +266,7 @@ function ci_powerpc_build { # ports/qemu function ci_qemu_setup_arm { + ci_mpy_format_setup ci_gcc_arm_setup sudo apt-get update sudo apt-get install qemu-system @@ -287,6 +288,10 @@ function ci_qemu_build_arm { make ${MAKEOPTS} -C ports/qemu clean make ${MAKEOPTS} -C ports/qemu test_full make ${MAKEOPTS} -C ports/qemu BOARD=SABRELITE test_full + + # Test building and running native .mpy with armv7m architecture. + ci_native_mpy_modules_build armv7m + make ${MAKEOPTS} -C ports/qemu test_natmod } function ci_qemu_build_rv32 { @@ -480,20 +485,18 @@ function ci_native_mpy_modules_build { else arch=$1 fi - make -C examples/natmod/features1 ARCH=$arch + for natmod in features1 features3 features4 deflate framebuf heapq random re + do + make -C examples/natmod/$natmod ARCH=$arch + done + # btree requires thread local storage support on rv32imc. if [ $arch != rv32imc ]; then - # This requires soft-float support on rv32imc. - make -C examples/natmod/features2 ARCH=$arch - # This requires thread local storage support on rv32imc. make -C examples/natmod/btree ARCH=$arch fi - make -C examples/natmod/features3 ARCH=$arch - make -C examples/natmod/features4 ARCH=$arch - make -C examples/natmod/deflate ARCH=$arch - make -C examples/natmod/framebuf ARCH=$arch - make -C examples/natmod/heapq ARCH=$arch - make -C examples/natmod/random ARCH=$arch - make -C examples/natmod/re ARCH=$arch + # features2 requires soft-float on armv7m and rv32imc. + if [ $arch != rv32imc ] && [ $arch != armv7m ]; then + make -C examples/natmod/features2 ARCH=$arch + fi } function ci_native_mpy_modules_32bit_build { From e37d498cc0891b67ea6b782fc1e9b1b7dab3c740 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 4 Feb 2025 00:02:49 +0100 Subject: [PATCH 0259/2098] py/emitnative: Mark condition code tables as const. This commit marks as const the condition code tables used when figuring out which opcode sequence must be emitted depending on the requested comparison type. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index 82ee729d3d5..dd064479323 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -2541,7 +2541,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { #if N_X64 asm_x64_xor_r64_r64(emit->as, REG_RET, REG_RET); asm_x64_cmp_r64_with_r64(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6 + 6] = { + static const byte ops[6 + 6] = { // unsigned ASM_X64_CC_JB, ASM_X64_CC_JA, @@ -2561,7 +2561,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { #elif N_X86 asm_x86_xor_r32_r32(emit->as, REG_RET, REG_RET); asm_x86_cmp_r32_with_r32(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6 + 6] = { + static const byte ops[6 + 6] = { // unsigned ASM_X86_CC_JB, ASM_X86_CC_JA, @@ -2581,7 +2581,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { #elif N_THUMB asm_thumb_cmp_rlo_rlo(emit->as, REG_ARG_2, reg_rhs); if (asm_thumb_allow_armv7m(emit->as)) { - static uint16_t ops[6 + 6] = { + static const uint16_t ops[6 + 6] = { // unsigned ASM_THUMB_OP_ITE_CC, ASM_THUMB_OP_ITE_HI, @@ -2601,7 +2601,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); } else { - static uint16_t ops[6 + 6] = { + static const uint16_t ops[6 + 6] = { // unsigned ASM_THUMB_CC_CC, ASM_THUMB_CC_HI, @@ -2624,7 +2624,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { } #elif N_ARM asm_arm_cmp_reg_reg(emit->as, REG_ARG_2, reg_rhs); - static uint ccs[6 + 6] = { + static const uint ccs[6 + 6] = { // unsigned ASM_ARM_CC_CC, ASM_ARM_CC_HI, @@ -2642,7 +2642,7 @@ static void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { }; asm_arm_setcc_reg(emit->as, REG_RET, ccs[op_idx]); #elif N_XTENSA || N_XTENSAWIN - static uint8_t ccs[6 + 6] = { + static const uint8_t ccs[6 + 6] = { // unsigned ASM_XTENSA_CC_LTU, 0x80 | ASM_XTENSA_CC_LTU, // for GTU we'll swap args From 44a7731669f1c8429bb61b790a7e8bf8a86b045d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 4 Feb 2025 00:05:44 +0100 Subject: [PATCH 0260/2098] py/emitnative: Load and store words just once for Viper code. This commit fixes two Xtensa sequences in order to terminate early when loading and storing word values via an immediate index. This was meant to be part of 55ca3fd67512555707304c6b68b836eb89f09d1c but whilst it was part of the code being tested, it didn't end up in the commit. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/emitnative.c b/py/emitnative.c index dd064479323..1aab0a9eb78 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1609,6 +1609,7 @@ static void emit_native_load_subscr(emit_t *emit) { #elif N_XTENSA || N_XTENSAWIN if (index_value > 0 && index_value < 256) { asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); + break; } #endif need_reg_single(emit, reg_index, 0); @@ -1887,6 +1888,7 @@ static void emit_native_store_subscr(emit_t *emit) { #elif N_XTENSA || N_XTENSAWIN if (index_value > 0 && index_value < 256) { asm_xtensa_s32i_optimised(emit->as, REG_RET, reg_base, index_value); + break; } #elif N_ARM ASM_MOV_REG_IMM(emit->as, reg_index, index_value); From 304467518fbe9fc679dbba087fba7931c51655fb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 13:34:47 +0100 Subject: [PATCH 0261/2098] samd/boards: Add generic SAMD21x18 board definitions. The definition uses the internal oscillator for clock and only internal flash for the file system. It works at SAMD21G18 and SAMD21E18 devices as well, only that fewer pins are accessible. Tested with a SAMD21E18, SAM21G18 and SAMD21J18 board. Signed-off-by: robert-hh --- ports/samd/boards/SAMD_GENERIC_D21X18/board.json | 16 ++++++++++++++++ ports/samd/boards/SAMD_GENERIC_D21X18/board.md | 4 ++++ .../boards/SAMD_GENERIC_D21X18/mpconfigboard.h | 4 ++++ .../boards/SAMD_GENERIC_D21X18/mpconfigboard.mk | 4 ++++ ports/samd/boards/SAMD_GENERIC_D21X18/pins.csv | 10 ++++++++++ 5 files changed, 38 insertions(+) create mode 100644 ports/samd/boards/SAMD_GENERIC_D21X18/board.json create mode 100644 ports/samd/boards/SAMD_GENERIC_D21X18/board.md create mode 100644 ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.h create mode 100644 ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.mk create mode 100644 ports/samd/boards/SAMD_GENERIC_D21X18/pins.csv diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/board.json b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json new file mode 100644 index 00000000000..a14730bd1bf --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json @@ -0,0 +1,16 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "USB" + ], + "images": [ + "generic_board.jpg" + ], + "mcu": "samd21", + "vendor": "Microchip", + "product": "Generic SAMD21J18", + "thumbnail": "" +} diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/board.md b/ports/samd/boards/SAMD_GENERIC_D21X18/board.md new file mode 100644 index 00000000000..a10b08f2f1c --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/board.md @@ -0,0 +1,4 @@ +The following firmware should work on most boards with a SAMD21E18, +SAMD21G18 and SAMD21J18 MCU. It uses only the features built into +the MCU. Additional devices at the board like external flash +are not supported. diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.h b/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.h new file mode 100644 index 00000000000..945975ab238 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.h @@ -0,0 +1,4 @@ +#define MICROPY_HW_BOARD_NAME "Generic SAMD21J18" +#define MICROPY_HW_MCU_NAME "SAMD21J18A" + +#define MICROPY_HW_DFLL_USB_SYNC (1) diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.mk new file mode 100644 index 00000000000..f95c6549381 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/mpconfigboard.mk @@ -0,0 +1,4 @@ +MCU_SERIES = SAMD21 +CMSIS_MCU = SAMD21J18A +LD_FILES = boards/samd21x18a.ld sections.ld +TEXT0 = 0x2000 diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/pins.csv b/ports/samd/boards/SAMD_GENERIC_D21X18/pins.csv new file mode 100644 index 00000000000..d35bc9d8ea4 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/pins.csv @@ -0,0 +1,10 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +USB_DM,PA24 +USB_DP,PA25 + +SWCLK,PA30 +SWDIO,PA31 From 9ced693ade1a1aca1e76d1eb6f11094e8aa3bfc9 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 13:42:18 +0100 Subject: [PATCH 0262/2098] samd/boards: Add generic SAMD51x19 board definitions. The definition uses the internal oscillator for clock and only internal flash for the file system. It works at SAMD51G19 and SAMD51J19 devices as well, only that fewer pins are accessible. Tested with a SAMD51G19 and SAMD51J9 board. Signed-off-by: robert-hh --- .../samd/boards/SAMD_GENERIC_D51X19/board.json | 16 ++++++++++++++++ ports/samd/boards/SAMD_GENERIC_D51X19/board.md | 4 ++++ .../boards/SAMD_GENERIC_D51X19/mpconfigboard.h | 2 ++ .../SAMD_GENERIC_D51X19/mpconfigboard.mk | 11 +++++++++++ ports/samd/boards/SAMD_GENERIC_D51X19/pins.csv | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X19/board.json create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X19/board.md create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.h create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X19/pins.csv diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/board.json b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json new file mode 100644 index 00000000000..21cb114bf65 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json @@ -0,0 +1,16 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "USB" + ], + "images": [ + "generic_board.jpg" + ], + "mcu": "samd51", + "vendor": "Microchip", + "product": "Generic SAMD51P19", + "thumbnail": "" +} diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/board.md b/ports/samd/boards/SAMD_GENERIC_D51X19/board.md new file mode 100644 index 00000000000..b4694cc2857 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/board.md @@ -0,0 +1,4 @@ +The following firmware should work on most boards with a SAMD51G19, +SAMD51J19 and SAMD51P19 MCU. It uses only the features built into +the MCU. Additional devices at the board like external flash +are not supported. diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.h b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.h new file mode 100644 index 00000000000..cce157f9e0f --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.h @@ -0,0 +1,2 @@ +#define MICROPY_HW_BOARD_NAME "Generic SAMD51P19" +#define MICROPY_HW_MCU_NAME "SAMD51P19A" diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk new file mode 100644 index 00000000000..1a20643214f --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk @@ -0,0 +1,11 @@ +MCU_SERIES = SAMD51 +CMSIS_MCU = SAMD51P19A +LD_FILES = boards/samd51x19a.ld sections.ld +TEXT0 = 0x4000 + +# The ?='s allow overriding in mpconfigboard.mk. +# MicroPython settings +# The size of a MCU flash filesystem will be +# 496k - MICROPY_HW_CODESIZE - MICROPY_HW_VFSROMSIZE +# The default for MICROPY_HW_VFSROMSIZE is 64K +MICROPY_HW_CODESIZE ?= 368K diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/pins.csv b/ports/samd/boards/SAMD_GENERIC_D51X19/pins.csv new file mode 100644 index 00000000000..76e38a98467 --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/pins.csv @@ -0,0 +1,18 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +USB_DM,PA24 +USB_DP,PA25 +USB_SOF,PA23 + +QSPI_CS,PB11 +QSPI_SCK,PB10 +QSPI_D0,PA08 +QSPI_D1,PA09 +QSPI_D2,PA10 +QSPI_D3,PA11 + +SWCLK,PA30 +SWDIO,PA31 From 6b2e359076ea394d8a20f2d17b1cd2c68e3b5c74 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 13:54:28 +0100 Subject: [PATCH 0263/2098] samd/boards: Add generic SAMD51x20 board definitions. The definition uses the internal oscillator for clock and only internal flash for the file system. It works at SAMD51J20 device as well, only that fewer pins are accessible. Tested with a SAMD51J20 board. Signed-off-by: robert-hh --- .../samd/boards/SAMD_GENERIC_D51X20/board.json | 16 ++++++++++++++++ ports/samd/boards/SAMD_GENERIC_D51X20/board.md | 4 ++++ .../boards/SAMD_GENERIC_D51X20/mpconfigboard.h | 2 ++ .../boards/SAMD_GENERIC_D51X20/mpconfigboard.mk | 13 +++++++++++++ ports/samd/boards/SAMD_GENERIC_D51X20/pins.csv | 17 +++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X20/board.json create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X20/board.md create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.h create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk create mode 100644 ports/samd/boards/SAMD_GENERIC_D51X20/pins.csv diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/board.json b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json new file mode 100644 index 00000000000..ae4f83472ec --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json @@ -0,0 +1,16 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "USB" + ], + "images": [ + "generic_board.jpg" + ], + "mcu": "samd51", + "vendor": "Microchip", + "product": "Generic SAMD51P20", + "thumbnail": "" +} diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/board.md b/ports/samd/boards/SAMD_GENERIC_D51X20/board.md new file mode 100644 index 00000000000..4e75f901e3c --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/board.md @@ -0,0 +1,4 @@ +The following firmware should work on most boards with a +SAMD51J20 and SAMD51P20 MCU. It uses only the features built into +the MCU. Additional devices at the board like external flash +are not supported. diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.h b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.h new file mode 100644 index 00000000000..30e5fa9e6eb --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.h @@ -0,0 +1,2 @@ +#define MICROPY_HW_BOARD_NAME "Generic SAMD51P20" +#define MICROPY_HW_MCU_NAME "SAMD51P20A" diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk new file mode 100644 index 00000000000..ddba3dcbbad --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk @@ -0,0 +1,13 @@ +MCU_SERIES = SAMD51 +CMSIS_MCU = SAMD51P20A +LD_FILES = boards/samd51x19a.ld sections.ld +TEXT0 = 0x4000 + + +# The ?='s allow overriding in mpconfigboard.mk. +# MicroPython settings +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_VFSROMSIZE +# The default for MICROPY_HW_VFSROMSIZE is 64K +MICROPY_HW_CODESIZE ?= 752K +MICROPY_HW_VFSROMSIZE ?= 128K diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/pins.csv b/ports/samd/boards/SAMD_GENERIC_D51X20/pins.csv new file mode 100644 index 00000000000..24820c25bbd --- /dev/null +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/pins.csv @@ -0,0 +1,17 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +USB_DM,PA24 +USB_DP,PA25 + +QSPI_CS,PB11 +QSPI_SCK,PB10 +QSPI_D0,PA08 +QSPI_D1,PA09 +QSPI_D2,PA10 +QSPI_D3,PA11 + +SWCLK,PA30 +SWDIO,PA31 From ff9c6da88c82e8423e577e131d91c5708b788f26 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 14:16:13 +0100 Subject: [PATCH 0264/2098] samd/Makefile: Add support for board variants. Tested with a Adafruit SAMD QT board, which may optionally be equipped with SPIFLASH memory. Signed-off-by: robert-hh --- ports/samd/Makefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 75a4d9b1de3..005664f178d 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -13,7 +13,19 @@ ifeq ($(wildcard $(BOARD_DIR)/.),) $(error Invalid BOARD specified: $(BOARD_DIR)) endif +ifneq ($(BOARD_VARIANT),) +ifeq ($(wildcard $(BOARD_DIR)/mpconfigvariant_$(BOARD_VARIANT).mk),) +$(error Invalid BOARD_VARIANT specified: $(BOARD_VARIANT)) +endif +endif + +# If the build directory is not given, make it reflect the board name (and +# optionally the board variant). +ifneq ($(BOARD_VARIANT),) +BUILD ?= build-$(BOARD)-$(BOARD_VARIANT) +else BUILD ?= build-$(BOARD) +endif CROSS_COMPILE ?= arm-none-eabi- UF2CONV ?= $(TOP)/tools/uf2conv.py @@ -21,7 +33,13 @@ UF2CONV ?= $(TOP)/tools/uf2conv.py MCU_SERIES_LOWER = $(shell echo $(MCU_SERIES) | tr '[:upper:]' '[:lower:]') include ../../py/mkenv.mk +# Include board specific .mk file, and optional board variant .mk file. include $(BOARD_DIR)/mpconfigboard.mk +ifeq ($(BOARD_VARIANT),) +-include $(BOARD_DIR)/mpconfigvariant.mk +else +include $(BOARD_DIR)/mpconfigvariant_$(BOARD_VARIANT).mk +endif include mcu/$(MCU_SERIES_LOWER)/mpconfigmcu.mk # Qstr definitions (must come before including py.mk) From 6cbe145ca8b46857cd2ba5289021022bb92cb02e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 14:19:07 +0100 Subject: [PATCH 0265/2098] samd/boards: Add support for the Adafruit QT Py board. Supporting a variant with an optional SPIFLASH device as well. Tested both variants with a QT Py board. Signed-off-by: robert-hh --- .../boards/ADAFRUIT_QTPY_SAMD21/board.json | 20 +++++++++++++++ .../ADAFRUIT_QTPY_SAMD21/mpconfigboard.h | 6 +++++ .../ADAFRUIT_QTPY_SAMD21/mpconfigboard.mk | 4 +++ .../mpconfigvariant_SPIFLASH.mk | 2 ++ .../samd/boards/ADAFRUIT_QTPY_SAMD21/pins.csv | 25 +++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 ports/samd/boards/ADAFRUIT_QTPY_SAMD21/board.json create mode 100644 ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h create mode 100644 ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.mk create mode 100644 ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk create mode 100644 ports/samd/boards/ADAFRUIT_QTPY_SAMD21/pins.csv diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/board.json b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/board.json new file mode 100644 index 00000000000..f48416dd02a --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/board.json @@ -0,0 +1,20 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "USB-C" + ], + "images": [ + "qt_py_samd21.jpg" + ], + "mcu": "samd21", + "product": "QT Py - SAMD21", + "thumbnail": "", + "url": "https://www.adafruit.com/product/4600", + "variants": { + "SPIFLASH": "Support for an external Flash chip" + }, + "vendor": "Adafruit" +} diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h new file mode 100644 index 00000000000..0acf28afbfe --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h @@ -0,0 +1,6 @@ +#define MICROPY_HW_BOARD_NAME "QT Py" +#define MICROPY_HW_MCU_NAME "SAMD21E18A" + +#define MICROPY_HW_DFLL_USB_SYNC (1) + +#define MICROPY_HW_SPIFLASH_ID (3) diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.mk new file mode 100644 index 00000000000..5b4d0b63e7e --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.mk @@ -0,0 +1,4 @@ +MCU_SERIES = SAMD21 +CMSIS_MCU = SAMD21E18A +LD_FILES = boards/samd21x18a.ld sections.ld +TEXT0 = 0x2000 diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk new file mode 100644 index 00000000000..69537d5bf3c --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk @@ -0,0 +1,2 @@ +CFLAGS += -DMICROPY_HW_SPIFLASH=1 +MICROPY_HW_CODESIZE ?= 232K diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/pins.csv b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/pins.csv new file mode 100644 index 00000000000..c3ada219f16 --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/pins.csv @@ -0,0 +1,25 @@ +A0,PA02 +A1,PA03 +A2,PA04 +A3,PA05 +SDA,PA16 +SCL,PA17 +TX,PA06 +RX,PA07 +SCK,PA11 +MISO,PA09 +MOSI,PA10 + +NEO_PWR,PA15 +NEOPIX,PA18 + +FLASH_MOSI,PA22 +FLASH_MISO,PA19 +FLASH_SCK,PA23 +FLASH_CS,PA08 + +USB_DM,PA24 +USB_DP,PA25 + +SWCLK,PA30 +SWDIO,PA31 From 36a0a839974c6e49723794421f46f6282e60e56f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 14:21:52 +0100 Subject: [PATCH 0266/2098] samd/boards: Add support for the Adafruit NeoKey Trinkey board. Tested with that board. Signed-off-by: robert-hh --- .../boards/ADAFRUIT_NEOKEY_TRINKEY/board.json | 18 ++++++++++++++++++ .../ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h | 14 ++++++++++++++ .../ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.mk | 4 ++++ .../boards/ADAFRUIT_NEOKEY_TRINKEY/pins.csv | 14 ++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/board.json create mode 100644 ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h create mode 100644 ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.mk create mode 100644 ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/pins.csv diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/board.json b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/board.json new file mode 100644 index 00000000000..9b0c307d679 --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "RGB LED", + "USB" + ], + "images": [ + "neokey_trinkey.jpg" + ], + "mcu": "samd21", + "product": "NeoKey Trinkey", + "thumbnail": "", + "url": "https://www.adafruit.com/product/5020", + "vendor": "Adafruit" +} diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h new file mode 100644 index 00000000000..eb4704ff8cb --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h @@ -0,0 +1,14 @@ +#define MICROPY_HW_BOARD_NAME "NeoKey Trinkey" +#define MICROPY_HW_MCU_NAME "SAMD21E18A" + +#define MICROPY_HW_DFLL_USB_SYNC (1) + +// The NEOKEY board has just two accessible GPIO pins. +// So many classes and modules are useless. +#define MICROPY_PY_MACHINE_SOFTI2C (0) +#define MICROPY_PY_MACHINE_SOFTSPI (0) +#define MICROPY_PY_MACHINE_I2C (0) +#define MICROPY_PY_MACHINE_SPI (0) +#define MICROPY_PY_MACHINE_UART (0) +#define MICROPY_PY_MACHINE_ADC (0) +#define MICROPY_PY_MACHINE_DAC (0) diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.mk new file mode 100644 index 00000000000..5b4d0b63e7e --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.mk @@ -0,0 +1,4 @@ +MCU_SERIES = SAMD21 +CMSIS_MCU = SAMD21E18A +LD_FILES = boards/samd21x18a.ld sections.ld +TEXT0 = 0x2000 diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/pins.csv b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/pins.csv new file mode 100644 index 00000000000..c979f5b9bc1 --- /dev/null +++ b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/pins.csv @@ -0,0 +1,14 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +NEOPIXEL,PA15 +SWITCH,PA18 +TOUCH,PA07 + +USB_DM,PA24 +USB_DP,PA25 + +SWCLK,PA30 +SWDIO,PA31 From a84143d2230daa3843f00dd1f09474441810f08d Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 17:38:40 +0100 Subject: [PATCH 0267/2098] docs/samd/pinout: Add pinout for Adafruit NeoKey Trinkey and QT Py. Only pins accessible at the board are shown. Signed-off-by: robert-hh --- docs/samd/pinout.rst | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 5da4ae78d45..f6473e5495f 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -567,6 +567,66 @@ The default devices at the board are: - SPI 0 at pins PA06/PA09/PA08, labelled D4, D2 and D0 - DAC output on pin PA02, labelled D1 +Adafruit QT PY pin assignment table +----------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 2 PA02 A0 2 0 - - - - + 3 PA03 A1 3 1 - - - - + 4 PA04 A2 4 4 - 0/0 0/0 - + 5 PA05 A3 5 5 - 0/1 0/1 - + 7 PA07 RX 7 7 - 0/3 1/1 - + 6 PA06 TX 6 6 - 0/2 1/0 - + 8 PA08 FLASH_CS - 16 0/0 2/0 0/0 1/2 + 19 PA19 FLASH_MISO 3 - 1/3 3/3 3/1 0/3 + 22 PA22 FLASH_MOSI 6 - 3/0 5/0 4/0 0/4 + 23 PA23 FLASH_SCK 7 - 3/1 5/1 4/1 0/5 + 9 PA09 MISO 9 17 0/1 2/1 0/1 1/3 + 10 PA10 MOSI 10 18 0/2 2/2 1/0 0/2 + 18 PA18 NEOPIX 2 - 1/2 3/2 3/0 0/2 + 15 PA15 NEO_PWR 15 - 2/3 4/3 3/1 0/5 + 11 PA11 SCK 11 19 0/3 2/3 1/1 0/3 + 17 PA17 SCL 1 - 1/1 3/1 2/1 0/7 + 16 PA16 SDA 0 - 1/0 3/0 2/0 0/6 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 0 at pins PA07/PA06, labelled RX/TX +- I2C 1 at pins PA16/PA17, labelled SDA/SCL +- SPI 0 at pins PA09/PA10/PA11, labelled MISO, MOSI and SCK +- DAC output on pin PA02, labelled A0 + +Adafruit NeoKey Trinkey pin assignment table +-------------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 15 PA15 NEOPIXEL 15 - 2/3 4/3 3/1 0/5 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 18 PA18 SWITCH 2 - 1/2 3/2 3/0 0/2 + 7 PA07 TOUCH 7 7 - 0/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The board does not provide access to UART, I2C, SPI or DAC. + + SAMD21 Xplained PRO pin assignment table ---------------------------------------- From e44a2c6921919d0d19356a70a23bf21cd301856c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 23 Jan 2025 20:14:27 +0100 Subject: [PATCH 0268/2098] docs/samd/pinout: Add pinout for the Generic SAMD board types. The table shows the devices available at the pin and the respective package letter. Signed-off-by: robert-hh --- docs/samd/pinout.rst | 182 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index f6473e5495f..776ab74bcdb 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -935,6 +935,188 @@ The default devices at the board are: - SPI 4 at pins PB12/PB11/PB13, labelled MOSI, MISO and SCK - DAC output on pins PA02 and PA05, labelled A0 and A4 +Generic SAMD21x18 pin assignment table +-------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Name/Package IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 0 PA00 EGJ 0 - - 1/0 2/0 - + 1 PA01 EGJ 1 - - 1/1 2/1 - + 2 PA02 EGJ 2 0 - - - - + 3 PA03 EGJ 3 1 - - - - + 4 PA04 EGJ 4 4 - 0/0 0/0 - + 5 PA05 EGJ 5 5 - 0/1 0/1 - + 6 PA06 EGJ 6 6 - 0/2 1/0 - + 7 PA07 EGJ 7 7 - 0/3 1/1 - + 8 PA08 EGJ - 16 0/0 2/0 0/0 1/2 + 9 PA09 EGJ 9 17 0/1 2/1 0/1 1/3 + 10 PA10 EGJ 10 18 0/2 2/2 1/0 0/2 + 11 PA11 EGJ 11 19 0/3 2/3 1/1 0/3 + 12 PA12 GJ 12 - 2/0 4/0 2/0 0/6 + 13 PA13 GJ 13 - 2/1 4/1 2/0 0/7 + 14 PA14 EGJ 14 - 2/2 4/2 3/0 0/4 + 15 PA15 EGJ 15 - 2/3 4/3 3/1 0/5 + 16 PA16 EGJ 0 - 1/0 3/0 2/0 0/6 + 17 PA17 EGJ 1 - 1/1 3/1 2/1 0/7 + 18 PA18 EGJ 2 - 1/2 3/2 3/0 0/2 + 19 PA19 EGJ 3 - 1/3 3/3 3/1 0/3 + 20 PA20 GJ 4 - 5/2 3/2 7/0 0/4 + 21 PA21 GJ 5 - 5/3 3/3 7/1 0/7 + 22 PA22 EGJ 6 - 3/0 5/0 4/0 0/4 + 23 PA23 EGJ 7 - 3/1 5/1 4/1 0/5 + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 27 PA27 EGJ 15 - - - - - + 28 PA28 EGJ 8 - - - - - + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 32 PB00 J 0 8 - 5/2 7/0 - + 33 PB01 J 1 9 - 5/3 7/1 - + 34 PB02 GJ 2 10 - 5/0 6/0 - + 35 PB03 GJ 3 11 - 5/1 6/1 - + 36 PB04 J 4 12 - - - - + 37 PB05 J 5 13 - - - - + 38 PB06 J 6 14 - - - - + 39 PB07 J 7 15 - - - - + 40 PB08 GJ 8 2 - 4/0 4/0 - + 41 PB09 GJ 9 3 - 4/1 4/1 - + 42 PB10 GJ 10 - - 4/2 5/0 0/4 + 43 PB11 GJ 11 - - 4/3 5/1 0/5 + 44 PB12 J 12 - 4/0 - 4/0 0/6 + 45 PB13 J 13 - 4/1 - 4/1 0/7 + 46 PB14 J 14 - 4/2 - 5/0 - + 47 PB15 J 15 - 4/3 - 5/1 - + 48 PB16 J 0 - 5/0 - 6/0 0/4 + 49 PB17 J 1 - 5/1 - 6/1 0/5 + 54 PB22 GJ 6 - - 5/2 7/0 - + 55 PB23 GJ 7 - - 5/3 7/1 - + 62 PB30 J 14 - - 5/0 0/0 1/2 + 63 PB31 J 15 - - 5/1 0/1 1/3 +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The Package column indicates the package letter providing this pin. An entry +EGJ tells for instance, that the pin is available for SAMD21E18, SAMD21G18 and +SAMD21J18. + + +Generic SAMD51x19 and SAM51x20 pin assignment table +--------------------------------------------------- + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Name/Package IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 QSPI_D2 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 QSPI_D3 11 11 - 0/3 2/3 1/1 0/3 1/7 + 42 PB10 QSPI_SCK 10 - - - 4/2 5/0 0/4 1/0 + 23 PA23 USB_SOF 7 - - 3/1 5/0 4/1 1/7 0/3 + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 0 PA00 GJP 0 - - - 1/0 2/0 - - + 1 PA01 GJP 1 - - - 1/1 2/1 - - + 2 PA02 GJP 2 0 - - - - - - + 3 PA03 GJP 3 10 - - - - - - + 4 PA04 GJP 4 4 - - 0/0 0/0 - - + 5 PA05 GJP 5 5 - - 0/1 0/1 - - + 6 PA06 GJP 6 6 - - 0/2 1/0 - - + 7 PA07 GJP 7 7 - - 0/3 1/1 - - + 12 PA12 GJP 12 - - 2/0 4/1 2/0 0/6 1/2 + 13 PA13 GJP 13 - - 2/1 4/0 2/1 0/7 1/3 + 14 PA14 GJP 14 - - 2/2 4/2 3/0 2/0 1/2 + 15 PA15 GJP 15 - - 2/3 4/3 3/1 2/1 1/3 + 16 PA16 GJP 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 GJP 1 - - 1/1 3/0 2/1 1/1 0/5 + 18 PA18 GJP 2 - - 1/2 3/2 3/0 1/2 0/6 + 19 PA19 GJP 3 - - 1/3 3/3 3/1 1/3 0/7 + 20 PA20 GJP 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 GJP 5 - - 5/3 3/3 7/1 1/5 0/1 + 22 PA22 GJP 6 - - 3/0 5/1 4/0 1/6 0/2 + 27 PA27 GJP 11 - - - - - - - + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - + 32 PB00 JP 0 12 - - 5/2 7/0 - - + 33 PB01 JP 1 13 - - 5/3 7/1 - - + 34 PB02 GJP 2 14 - - 5/0 6/0 2/2 - + 35 PB03 GJP 3 15 - - 5/1 6/1 - - + 36 PB04 JP 4 - 6 - - - - - + 37 PB05 JP 5 - 7 - - - - - + 38 PB06 JP 6 - 8 - - - - - + 39 PB07 JP 7 - 9 - - - - - + 40 PB08 GJP 8 2 0 - 4/0 4/0 - - + 41 PB09 GJP 9 3 1 - 4/1 4/1 - - + 44 PB12 JP 12 - - 4/0 - 4/0 3/0 0/0 + 45 PB13 JP 13 - - 4/1 - 4/1 3/1 0/1 + 46 PB14 JP 14 - - 4/2 - 5/0 4/0 0/2 + 47 PB15 JP 15 - - 4/3 - 5/1 4/1 0/3 + 48 PB16 JP 0 - - 5/0 - 6/0 3/0 0/4 + 49 PB17 JP 1 - - 5/1 - 6/1 3/1 0/5 + 50 PB18 P 2 - - 5/2 7/2 - 1/0 - + 51 PB19 P 3 - - 5/3 7/3 - 1/1 - + 52 PB20 P 4 - - 3/0 7/1 - 1/2 - + 53 PB21 P 5 - - 3/1 7/0 - 1/3 - + 54 PB22 GJP 6 - - 1/2 5/2 7/0 - - + 55 PB23 GJP 7 - - 1/3 5/3 7/1 - - + 56 PB24 P 8 - - 0/0 2/1 - - - + 57 PB25 P 9 - - 0/1 2/0 - - - + 58 PB26 P 12 - - 2/0 4/1 - 1/2 - + 59 PB27 P 13 - - 2/1 4/0 - 1/3 - + 60 PB28 P 14 - - 2/2 4/2 - 1/4 - + 61 PB29 P 15 - - 2/3 4/3 - 1/5 - + 62 PB30 JP 14 - - 7/0 5/1 0/0 4/0 0/6 + 63 PB31 JP 15 - - 7/1 5/0 0/1 4/1 0/7 + 64 PC00 P 0 - 10 - - - - - + 65 PC01 P 1 - 11 - - - - - + 66 PC02 P 2 - 4 - - - - - + 67 PC03 P 3 - 5 - - - - - + 68 PC04 P 4 - - 6/0 - - 0/0 - + 69 PC05 P 5 - - 6/1 - - - - + 70 PC06 P 6 - - 6/2 - - - - + 71 PC07 P 9 - - 6/3 - - - - + 74 PC10 P 10 - - 6/2 7/2 - 0/0 1/4 + 75 PC11 P 11 - - 6/3 7/3 - 0/1 1/5 + 76 PC12 P 12 - - 7/0 6/1 - 0/2 1/6 + 77 PC13 P 13 - - 7/1 6/0 - 0/3 1/7 + 78 PC14 P 14 - - 7/2 6/2 - 0/4 1/0 + 79 PC15 P 15 - - 7/3 6/3 - 0/5 1/1 + 80 PC16 P 0 - - 6/0 0/1 - 0/0 - + 81 PC17 P 1 - - 6/1 0/0 - 0/1 - + 82 PC18 P 2 - - 6/2 0/2 - 0/2 - + 83 PC19 P 3 - - 6/3 0/3 - 0/3 - + 84 PC20 P 4 - - - - - 0/4 - + 85 PC21 P 5 - - - - - 0/5 - + 86 PC22 P 6 - - 1/0 3/1 - 0/5 - + 87 PC23 P 7 - - 1/1 3/0 - 0/7 - + 88 PC24 P 8 - - 0/2 2/2 - - - + 89 PC25 P 9 - - 0/3 2/3 - - - + 90 PC26 P 10 - - - - - - - + 91 PC27 P 11 - - 1/0 - - - - + 92 PC28 P 12 - - 1/1 - - - - + 94 PC30 P 14 - 12 - - - - - + 95 PC31 P 15 - 13 - - - - - + 96 PD00 P 0 - 14 - - - - - + 97 PD01 P 1 - 15 - - - - - +104 PD08 P 3 - - 7/0 6/1 - 0/1 - +105 PD09 P 4 - - 7/1 6/0 - 0/2 - +106 PD10 P 5 - - 7/2 6/2 - 0/3 - +107 PD11 P 6 - - 7/3 6/3 - 0/4 - +108 PD12 P 7 - - - - - 0/5 - +116 PD20 P 10 - - 1/2 3/2 - 1/0 - +117 PD21 P 11 - - 1/3 3/3 - 1/1 - +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + + +The Package column indicates the package letter providing this pin. An entry +GJP tells for instance, that the pin is available for SAMD51G19, SAMD51J19/-J20 and +SAMD51P19/-P20. + Scripts for creating the pin assignment tables ---------------------------------------------- From e176fea95cda017cf351e779d1aa323da52f1151 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 25 Jan 2025 08:50:45 +0100 Subject: [PATCH 0269/2098] mimxrt/irq: Add CSI IRQ. Signed-off-by: iabdalkader --- ports/mimxrt/irq.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/mimxrt/irq.h b/ports/mimxrt/irq.h index b83eb49ffab..8202a98e874 100644 --- a/ports/mimxrt/irq.h +++ b/ports/mimxrt/irq.h @@ -69,6 +69,8 @@ static inline void restore_irq_pri(uint32_t basepri) { #define IRQ_PRI_SYSTICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 0, 0) +#define IRQ_PRI_CSI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 3, 0) + #define IRQ_PRI_OTG_HS NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 6, 0) #define IRQ_PRI_EXTINT NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 14, 0) From 67ebc537c3d0095d3b6a0ef8a7160ba06ea95dff Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 25 Jan 2025 08:59:47 +0100 Subject: [PATCH 0270/2098] mimxrt/machine_rtc: Fix build with new SDKs. In more recent SDKs, this feature is actually disabled for the MIMXRT1062. Signed-off-by: iabdalkader --- ports/mimxrt/machine_rtc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index 5feeb0da096..e6c51999198 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -156,8 +156,10 @@ void machine_rtc_start(void) { SNVS->HPCOMR |= SNVS_HPCOMR_NPSWA_EN_MASK; // Do a basic init. SNVS_LP_Init(SNVS); + #if FSL_FEATURE_SNVS_HAS_MULTIPLE_TAMPER // Disable all external Tamper SNVS_LP_DisableAllExternalTamper(SNVS); + #endif SNVS_LP_SRTC_StartTimer(SNVS); // If the date is not set, set it to a more recent start date, From d76733d05856d389f216d60c71d1a7043d78c510 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 25 Jan 2025 13:42:24 +0100 Subject: [PATCH 0271/2098] mimxrt/mpconfigport: Remove hard-coded CMSIS header. Signed-off-by: iabdalkader --- ports/mimxrt/mpconfigport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 146c86015af..923eff4dc70 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -29,7 +29,6 @@ // Board specific definitions #include "mpconfigboard.h" #include "fsl_common.h" -#include "lib/nxp_driver/sdk/CMSIS/Include/core_cm7.h" uint32_t trng_random_u32(void); From 9d0a5ac7e9ac8b90d1bbea402fd16a410aa4008f Mon Sep 17 00:00:00 2001 From: StrayCat Date: Thu, 2 Jan 2025 23:46:44 +1300 Subject: [PATCH 0272/2098] esp32/boards: Enable I2S on ESP32C3 boards. Signed-off-by: StrayCat --- ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h | 1 - ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h | 1 - 2 files changed, 2 deletions(-) diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h index 0dbfae03a36..a6cebdf6ee6 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h @@ -4,7 +4,6 @@ #define MICROPY_HW_MCU_NAME "ESP32C3" #define MICROPY_HW_ENABLE_SDCARD (0) -#define MICROPY_PY_MACHINE_I2S (0) // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h index 9b304b69f6f..46d720edc75 100644 --- a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h +++ b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-c3-mini" #define MICROPY_HW_ENABLE_SDCARD (0) -#define MICROPY_PY_MACHINE_I2S (0) #define MICROPY_HW_I2C0_SCL (10) #define MICROPY_HW_I2C0_SDA (8) From 0662c551216db1a3ab40dafbeffcc3ee929d7690 Mon Sep 17 00:00:00 2001 From: Markus Gyger Date: Tue, 31 Dec 2024 15:34:17 +0700 Subject: [PATCH 0273/2098] rp2/rp2_pio: Add side_pindir support for PIO. Side-setting can also be used to change pin directions instead of pin values. This adds a parameter `side_pindir` to decorator `asm_pio()` to configure it. Also replaces a few close-by 0s with corresponding PIO.* constants. Addresses issue #10027. Signed-off-by: Markus Gyger --- docs/library/rp2.rst | 8 +++++--- ports/rp2/modules/rp2.py | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index f0189327dfb..a215e98b51e 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -23,7 +23,7 @@ The ``rp2`` module includes functions for assembling PIO programs. For running PIO programs, see :class:`rp2.StateMachine`. -.. function:: asm_pio(*, out_init=None, set_init=None, sideset_init=None, in_shiftdir=0, out_shiftdir=0, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE) +.. function:: asm_pio(*, out_init=None, set_init=None, sideset_init=None, side_pindir=False, in_shiftdir=PIO.SHIFT_LEFT, out_shiftdir=PIO.SHIFT_LEFT, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE) Assemble a PIO program. @@ -35,8 +35,10 @@ For running PIO programs, see :class:`rp2.StateMachine`. - *out_init* configures the pins used for ``out()`` instructions. - *set_init* configures the pins used for ``set()`` instructions. There can be at most 5. - - *sideset_init* configures the pins used side-setting. There can be at - most 5. + - *sideset_init* configures the pins used for ``.side()`` modifiers. There + can be at most 5. + - *side_pindir* when set to ``True`` configures ``.side()`` modifiers to be + used for pin directions, instead of pin values (the default, when ``False``). The following parameters are used by default, but can be overridden in `StateMachine.init()`: diff --git a/ports/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py index 9d13bf1b52c..e9be7dac8d5 100644 --- a/ports/rp2/modules/rp2.py +++ b/ports/rp2/modules/rp2.py @@ -26,20 +26,21 @@ def __init__( out_init=None, set_init=None, sideset_init=None, - in_shiftdir=0, - out_shiftdir=0, + side_pindir=False, + in_shiftdir=PIO.SHIFT_LEFT, + out_shiftdir=PIO.SHIFT_LEFT, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, - fifo_join=0, + fifo_join=PIO.JOIN_NONE, ): # array is a built-in module so importing it here won't require # scanning the filesystem. from array import array self.labels = {} - execctrl = 0 + execctrl = side_pindir << 29 shiftctrl = ( fifo_join << 30 | (pull_thresh & 0x1F) << 25 From b2ce9b6fb03ed27943733235eac2f3f5e0d5b97d Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 4 Dec 2024 16:32:42 -0700 Subject: [PATCH 0274/2098] rp2/boards: Add SparkFun IoT Node LoRaWAN board. Signed-off-by: Dryw Wade --- .../board.json | 25 ++++++ .../mpconfigboard.cmake | 7 ++ .../mpconfigboard.h | 33 +++++++ .../SPARKFUN_IOTNODE_LORAWAN_RP2350/pins.csv | 34 +++++++ .../sparkfun_iotnode_lorawan_rp2350.h | 88 +++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/pins.csv create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/sparkfun_iotnode_lorawan_rp2350.h diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json new file mode 100644 index 00000000000..4ddcd999fe5 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json @@ -0,0 +1,25 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "Dual-core", + "External Flash", + "External RAM", + "JST-SH", + "LoRa", + "RGB LED", + "USB-C", + "microSD" + ], + "images": [ + "26060-IoT-Node-LoRaWAN-Feature-new.jpg" + ], + "mcu": "rp2350", + "product": "IoT Node LoRaWAN RP2350", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/26060", + "vendor": "Sparkfun" +} diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake new file mode 100644 index 00000000000..11fe1ee28fc --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake @@ -0,0 +1,7 @@ +# cmake file for SparkFun IoT Node LoRaWAN RP2350 + +# TODO: DELETE THIS LINE ONCE PICO SDK 2.1.1 IS RELEASED +set(PICO_BOARD_HEADER_DIRS ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) + +set(PICO_BOARD "sparkfun_iotnode_lorawan_rp2350") +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.h new file mode 100644 index 00000000000..106ec95aa25 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.h @@ -0,0 +1,33 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun IoT Node LoRaWAN" +#define MICROPY_HW_FLASH_STORAGE_BYTES (14 * 1024 * 1024) + +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0044) + +#define MICROPY_HW_I2C0_SDA (20) +#define MICROPY_HW_I2C0_SCL (21) + +#define MICROPY_HW_I2C1_SDA (6) +#define MICROPY_HW_I2C1_SCL (7) + +#define MICROPY_HW_SPI0_SCK (2) +#define MICROPY_HW_SPI0_MOSI (3) +#define MICROPY_HW_SPI0_MISO (4) + +#define MICROPY_HW_SPI1_SCK (14) +#define MICROPY_HW_SPI1_MOSI (15) +#define MICROPY_HW_SPI1_MISO (12) + +#define MICROPY_HW_UART0_TX (18) +#define MICROPY_HW_UART0_RX (19) +#define MICROPY_HW_UART0_CTS (2) +#define MICROPY_HW_UART0_RTS (3) + +#define MICROPY_HW_UART1_TX (4) +#define MICROPY_HW_UART1_RX (5) +#define MICROPY_HW_UART1_CTS (6) +#define MICROPY_HW_UART1_RTS (7) + +#define MICROPY_HW_PSRAM_CS_PIN (0) +#define MICROPY_HW_ENABLE_PSRAM (1) diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/pins.csv b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/pins.csv new file mode 100644 index 00000000000..2526b36d10e --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/pins.csv @@ -0,0 +1,34 @@ +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 +GP23,GPIO23 +GP24,GPIO24 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +GP29,GPIO29 +LED,GPIO25 +LED_RGB,GPIO25 +RGB_LED,GPIO25 +NEOPIXEL,GPIO25 diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/sparkfun_iotnode_lorawan_rp2350.h b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/sparkfun_iotnode_lorawan_rp2350.h new file mode 100644 index 00000000000..5bea33cc4c2 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/sparkfun_iotnode_lorawan_rp2350.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// ----------------------------------------------------- +// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO +// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES +// ----------------------------------------------------- +// Board definition for the SparkFun IoT Node LoRaWAN +// +// This header may be included by other board headers as "boards/sparkfun_iotnode_lorawan_rp2350.h" + +// pico_cmake_set PICO_PLATFORM=rp2350 + +#ifndef _BOARDS_SPARKFUN_IOTNODE_LORAWAN_RP2350_H +#define _BOARDS_SPARKFUN_IOTNODE_LORAWAN_RP2350_H + +// For board detection +#define SPARKFUN_IOTNODE_LORAWAN_RP2350 + +// --- RP2350 VARIANT --- +#define PICO_RP2350A 1 + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 18 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 19 +#endif + +// --- LED --- +#ifndef PICO_DEFAULT_WS2812_PIN +#define PICO_DEFAULT_WS2812_PIN 25 +#endif + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 20 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 21 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 1 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 14 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 15 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 12 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 13 +#endif + +// --- FLASH --- + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +// pico_cmake_set_default PICO_FLASH_SIZE_BYTES = (16 * 1024 * 1024) +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) +#endif + +// pico_cmake_set_default PICO_RP2350_A2_SUPPORTED = 1 +#ifndef PICO_RP2350_A2_SUPPORTED +#define PICO_RP2350_A2_SUPPORTED 1 +#endif + +#endif From e574f6882082058b868b2f81955d8204fa1c852d Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 3 Oct 2024 09:07:48 +0200 Subject: [PATCH 0275/2098] mimxrt: Add support for a UF2 bootloader. Allowing to use e.g. the Adafruit bootloaders with MicroPython. The .uf2 file is created in addition to the .bin and .hex files allowing to use the latter ones without the bootloader for debugging and testing. Changes: - Set the location of the ISR Vector and .text segment to 0x6000C000 and 0x6000C400. - Reserve an area at the start of ITCM for a copy of the interrupt vector table and copy the table on reset to this place. - Extend `machine.bootloader()` by setting the magic number to enable the bootloader on reset. - Create a .uf2 file which skips the segments below 0x6000C000. The bootloader has to be installed as a preparation step using the board specific methods, but then the firmware's .uf2 file version can be installed using the bootloader. The bootloader can be invoked with: - double reset - calling machine.bootloader() - Using the touch1200 method Double reset is hard to achieve on MIMXRT boards, since there is no clean reset pin. Some MIMXRT boards provide it by switching the power. Some boards are excluded from the .uf2 build: - MIMXRT1050_EVK: The uf2 bootloader is built for the QSPI version of the board. MicroPython supports the Hyperflash version. - MIMXRT1176_EVK: No support for this board yet, but it should be possible. Signed-off-by: robert-hh --- ports/mimxrt/Makefile | 21 +++++++++++++++++++ .../boards/ADAFRUIT_METRO_M7/mpconfigboard.mk | 2 ++ .../boards/MIMXRT1010_EVK/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/MIMXRT1011.ld | 4 ++-- ports/mimxrt/boards/MIMXRT1015.ld | 4 ++-- .../boards/MIMXRT1015_EVK/mpconfigboard.mk | 2 ++ .../boards/MIMXRT1020_EVK/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/MIMXRT1021.ld | 4 ++-- ports/mimxrt/boards/MIMXRT1052.ld | 4 ++-- .../boards/MIMXRT1060_EVK/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/MIMXRT1062.ld | 4 ++-- ports/mimxrt/boards/MIMXRT1064.ld | 4 ++-- .../boards/MIMXRT1064_EVK/mpconfigboard.mk | 2 ++ .../boards/OLIMEX_RT1010/mpconfigboard.mk | 2 ++ .../boards/SEEED_ARCH_MIX/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/TEENSY40/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/TEENSY41/mpconfigboard.mk | 2 ++ ports/mimxrt/boards/common.ld | 11 +++++----- ports/mimxrt/hal/resethandler_MIMXRT10xx.S | 13 ++++++++++++ ports/mimxrt/modmachine.c | 14 +++++++++++-- ports/mimxrt/mpconfigport.h | 2 ++ tools/autobuild/build-boards.sh | 2 +- 22 files changed, 87 insertions(+), 20 deletions(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 3a9550cc974..ad7d853b677 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -21,6 +21,7 @@ BUILD ?= build-$(BOARD) PORT ?= /dev/ttyACM0 CROSS_COMPILE ?= arm-none-eabi- GIT_SUBMODULES += lib/tinyusb lib/nxp_driver +UF2CONV ?= $(TOP)/tools/uf2conv.py # MicroPython feature configurations MICROPY_VFS_LFS2 ?= 1 @@ -162,6 +163,13 @@ SRC_HAL_IMX_C += \ $(MCU_DIR)/drivers/fsl_romapi.c endif +# If not empty, then it is 10xx. +ifneq ($(findstring MIMXRT10, $(MCU_SERIES)),) +APPLICATION_ADDR := 0x6000C000 +else +APPLICATION_ADDR := 0x3000C000 +endif + ifeq ($(MCU_SERIES), MIMXRT1176) INC += -I$(TOP)/$(MCU_DIR)/drivers/cm7 @@ -253,6 +261,11 @@ else $(error Error: Unknown board flash type $(MICROPY_HW_FLASH_TYPE)) endif +# Set a flag if the UF2 bootloader is used +ifeq ($(USE_UF2_BOOTLOADER),1) + CFLAGS += -DMICROPY_MACHINE_UF2_BOOTLOADER=1 +endif + # Add sources for respective board flash type # Add hal/flexspi_nor_flash.c or hal/flashspi_hyper_flash.c respectively SRC_HAL_C += hal/flexspi_$(subst qspi_,,$(FLEXSPI_FLASH_TYPE)).c @@ -470,7 +483,11 @@ $(BUILD)/lib/tinyusb/src/device/usbd.o: CFLAGS += -Wno-missing-braces # Build targets # ============================================================================= +ifeq ($(USE_UF2_BOOTLOADER),1) +all: $(BUILD)/firmware.hex $(BUILD)/firmware.bin $(BUILD)/firmware.uf2 +else all: $(BUILD)/firmware.hex $(BUILD)/firmware.bin +endif # Process linker scripts with C preprocessor to exchange LDDEFINES and # aggregate output of preprocessor in a single linker script `link.ld` @@ -487,6 +504,10 @@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(BUILD)/firmware.hex: $(BUILD)/firmware.elf $(Q)$(OBJCOPY) -O ihex -R .eeprom $< $@ +$(BUILD)/firmware.uf2: $(BUILD)/firmware.elf + $(Q)$(OBJCOPY) -O binary -R .stack -R .ivt -R .flash_config $^ $@-binpart + $(Q)$(PYTHON) $(UF2CONV) -b $(APPLICATION_ADDR) -f MIMXRT10XX -c -o $@ $@-binpart + # Making OBJ use an order-only dependency on the generated pins.h file # has the side effect of making the pins.h file before we actually compile # any of the objects. The normal dependency generation will deal with the diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk index a8f7add6d26..74d3a6f0f10 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk @@ -8,3 +8,5 @@ MICROPY_HW_FLASH_SIZE ?= 0x800000 # 8MB MICROPY_PY_NETWORK_NINAW10 ?= 1 MICROPY_PY_SSL ?= 1 MICROPY_SSL_MBEDTLS ?= 1 + +USE_UF2_BOOTLOADER = 1 diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk index dd525859063..1aef451e2a6 100644 --- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk @@ -5,6 +5,8 @@ MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB +USE_UF2_BOOTLOADER = 1 + JLINK_PATH ?= /media/RT1010-EVK/ JLINK_COMMANDER_SCRIPT = $(BUILD)/script.jlink diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index 908eefffd64..ab363bd56a0 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -14,9 +14,9 @@ flash_config_start = flash_start + 0x00000400; flash_config_size = 0x00000C00; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1015.ld b/ports/mimxrt/boards/MIMXRT1015.ld index 90336a24371..0237d348c24 100644 --- a/ports/mimxrt/boards/MIMXRT1015.ld +++ b/ports/mimxrt/boards/MIMXRT1015.ld @@ -14,9 +14,9 @@ flash_config_start = flash_start; flash_config_size = 0x00001000; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk index 34e5cdee511..0f719ac0a97 100644 --- a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk @@ -6,3 +6,5 @@ MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB MICROPY_BOOT_BUFFER_SIZE = (32 * 1024) + +USE_UF2_BOOTLOADER = 1 diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk index c98843a1a3c..7715e669f8d 100644 --- a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk @@ -12,6 +12,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +USE_UF2_BOOTLOADER = 1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py JLINK_PATH ?= /media/RT1020-EVK/ diff --git a/ports/mimxrt/boards/MIMXRT1021.ld b/ports/mimxrt/boards/MIMXRT1021.ld index bef0c13df55..78add04c0c2 100644 --- a/ports/mimxrt/boards/MIMXRT1021.ld +++ b/ports/mimxrt/boards/MIMXRT1021.ld @@ -14,9 +14,9 @@ flash_config_start = flash_start; flash_config_size = 0x00001000; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1052.ld b/ports/mimxrt/boards/MIMXRT1052.ld index ca656711a5a..ea034d713e2 100644 --- a/ports/mimxrt/boards/MIMXRT1052.ld +++ b/ports/mimxrt/boards/MIMXRT1052.ld @@ -16,9 +16,9 @@ flash_config_start = flash_start; flash_config_size = 0x00001000; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00200000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk index 3af7cd231a2..f57aaff3b8b 100644 --- a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk @@ -12,6 +12,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +USE_UF2_BOOTLOADER = 1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py JLINK_PATH ?= /media/RT1060-EVK/ diff --git a/ports/mimxrt/boards/MIMXRT1062.ld b/ports/mimxrt/boards/MIMXRT1062.ld index 5b91550d97d..3d7e6d06341 100644 --- a/ports/mimxrt/boards/MIMXRT1062.ld +++ b/ports/mimxrt/boards/MIMXRT1062.ld @@ -16,9 +16,9 @@ flash_config_start = flash_start; flash_config_size = 0x00001000; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1064.ld b/ports/mimxrt/boards/MIMXRT1064.ld index 708dac4d515..7c35cb60c75 100644 --- a/ports/mimxrt/boards/MIMXRT1064.ld +++ b/ports/mimxrt/boards/MIMXRT1064.ld @@ -10,9 +10,9 @@ flash_config_start = flash_start; flash_config_size = 0x00001000; ivt_start = flash_start + 0x00001000; ivt_size = 0x00001000; -interrupts_start = flash_start + 0x00002000; +interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; -text_start = flash_start + 0x00002400; +text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk index 95cfb4585ac..f175d26377a 100644 --- a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk @@ -12,6 +12,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +USE_UF2_BOOTLOADER = 1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py JLINK_PATH ?= /media/RT1064-EVK/ diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk index 58429d29811..1de497d74e3 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk +++ b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk @@ -6,6 +6,8 @@ MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x200000 # 2MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB +USE_UF2_BOOTLOADER = 1 + CFLAGS += -DMICROPY_HW_FLASH_DQS=kFlexSPIReadSampleClk_LoopbackInternally SRC_C += \ diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk index 63e68e1e579..c691e2dcb55 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk @@ -12,6 +12,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +USE_UF2_BOOTLOADER = 1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py CFLAGS += -DSPI_RETRY_TIMES=1000000 diff --git a/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk b/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk index 07f174944b7..87c122da9d4 100644 --- a/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk +++ b/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk @@ -6,5 +6,7 @@ MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x200000 # 2MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB +USE_UF2_BOOTLOADER = 1 + deploy: $(BUILD)/firmware.hex teensy_loader_cli --mcu=imxrt1062 -v -w $< diff --git a/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk b/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk index b297448a307..e28310d091a 100755 --- a/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk +++ b/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk @@ -10,6 +10,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +USE_UF2_BOOTLOADER = 1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py deploy: $(BUILD)/firmware.hex diff --git a/ports/mimxrt/boards/common.ld b/ports/mimxrt/boards/common.ld index 587fc82a195..477ba38bc89 100644 --- a/ports/mimxrt/boards/common.ld +++ b/ports/mimxrt/boards/common.ld @@ -42,7 +42,8 @@ MEMORY m_vfs (RX) : ORIGIN = vfs_start, LENGTH = vfs_size /* Teensy uses the last bit of flash for recovery. */ m_reserved (RX) : ORIGIN = (vfs_start + vfs_size), LENGTH = reserved_size - m_itcm (RX) : ORIGIN = itcm_start, LENGTH = itcm_size + m_isr (RX) : ORIGIN = itcm_start, LENGTH = 0x400 + m_itcm (RX) : ORIGIN = itcm_start + 0x400, LENGTH = itcm_size - 0x400 m_dtcm (RW) : ORIGIN = dtcm_start, LENGTH = dtcm_size m_ocrm (RW) : ORIGIN = ocrm_start, LENGTH = ocrm_size @@ -80,7 +81,8 @@ SECTIONS . = ALIGN(4); } > m_ivt - /* The startup code goes first into internal RAM */ + /* ISR Vector table in flash. Copied to ITCM by ResetHandler(). */ + .interrupts : { __Vectors = .; @@ -90,10 +92,9 @@ SECTIONS . = ALIGN(4); } > m_interrupts - __VECTOR_RAM = __Vectors; - __RAM_VECTOR_TABLE_SIZE_BYTES = 0x0; + __Vectors_RAM = ORIGIN(m_isr); - /* The program code and other data goes into internal RAM */ + /* Some program code and other data goes into internal RAM */ .text : { . = ALIGN(4); diff --git a/ports/mimxrt/hal/resethandler_MIMXRT10xx.S b/ports/mimxrt/hal/resethandler_MIMXRT10xx.S index fe933c6d600..2fb8772850b 100644 --- a/ports/mimxrt/hal/resethandler_MIMXRT10xx.S +++ b/ports/mimxrt/hal/resethandler_MIMXRT10xx.S @@ -64,6 +64,19 @@ Reset_Handler: * __ram_function_start__/__ram_function_end__ : ramfunction region * copied to. Both must be aligned to 4 bytes boundary. */ +/* Copy the ISR Vector table to the start of ITCM to be available when the + .uf2 bootloader is used */ + + ldr r1, = __Vectors + ldr r2, = __Vectors_RAM + movs r3, 1024 + +.LC_ISR: + subs r3, #4 + ldr r0, [r1, r3] + str r0, [r2, r3] + bgt .LC_ISR + ldr r1, =__etext ldr r2, =__data_start__ ldr r3, =__data_end__ diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 1212914d3b4..204699bcc42 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -76,6 +76,11 @@ typedef enum { MP_SOFT_RESET } reset_reason_t; +// Copied from inc/uf2.h in https://github.com/Microsoft/uf2-samd21 +#define DBL_TAP_REG SNVS->LPGPR[3] +#define DBL_TAP_MAGIC 0xf01669ef // Randomly selected, adjusted to have first and last bit set +#define DBL_TAP_MAGIC_QUICK_BOOT 0xf02669ef + static mp_obj_t mp_machine_unique_id(void) { unsigned char id[8]; mp_hal_get_unique_id(id); @@ -158,12 +163,17 @@ NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if defined(MICROPY_BOARD_ENTER_BOOTLOADER) // If a board has a custom bootloader, call it first. MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); - #elif FSL_ROM_HAS_RUNBOOTLOADER_API + #elif FSL_ROM_HAS_RUNBOOTLOADER_API && !MICROPY_MACHINE_UF2_BOOTLOADER // If not, enter ROM bootloader in serial downloader / USB mode. + // Skip that in case of the UF2 bootloader being available. uint32_t arg = 0xEB110000; ROM_RunBootloader(&arg); #else - // No custom bootloader, or run bootloader API, then just reset. + // No custom bootloader, or run bootloader API, the set + // the flag for the UF2 bootloader + // Pretend to be the first of the two reset presses needed to enter the + // bootloader. That way one reset will end in the bootloader. + DBL_TAP_REG = DBL_TAP_MAGIC; WDOG_TriggerSystemSoftwareReset(WDOG1); #endif while (1) { diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 923eff4dc70..b45f8408e64 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -115,6 +115,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) #define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_ONEWIRE (1) +#define MICROPY_PY_MACHINE_BOOTLOADER (1) // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (2) @@ -151,6 +152,7 @@ uint32_t trng_random_u32(void); #define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_USB_CDC_1200BPS_TOUCH (1) // Hooks to add builtins diff --git a/tools/autobuild/build-boards.sh b/tools/autobuild/build-boards.sh index 801e7062e57..bd6828cb40f 100755 --- a/tools/autobuild/build-boards.sh +++ b/tools/autobuild/build-boards.sh @@ -99,7 +99,7 @@ function build_esp8266_boards { } function build_mimxrt_boards { - build_boards modmimxrt.c $1 $2 bin hex + build_boards modmimxrt.c $1 $2 bin hex uf2 } function build_nrf_boards { From b251aec0fcfef75b43b69c329b9a34c3703a6b90 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 5 Oct 2024 12:57:47 +0200 Subject: [PATCH 0276/2098] mimxrt/hal: Update the LUT and re-enable PAGEPROGRAM_QUAD. Changes: - Change the LUT table ordering to be similar to the order of the UF2-Bootloader and fsl_romapi.h. - Rewrite the LUT entry for PAGEPROGRAM_QUAD and update the LUT. That enabled QUAD program again. Signed-off-by: robert-hh --- ports/mimxrt/flash.c | 6 +-- ports/mimxrt/hal/flexspi_flash_config.h | 24 ++++++---- ports/mimxrt/hal/flexspi_hyper_flash.c | 3 ++ ports/mimxrt/hal/flexspi_nor_flash.c | 40 ++++++++-------- ports/mimxrt/hal/qspi_nor_flash_config.c | 60 +++++++++++------------- 5 files changed, 67 insertions(+), 66 deletions(-) diff --git a/ports/mimxrt/flash.c b/ports/mimxrt/flash.c index 3a18f8f51b9..7df799884e9 100644 --- a/ports/mimxrt/flash.c +++ b/ports/mimxrt/flash.c @@ -28,10 +28,8 @@ void flash_init(void) { // Upload the custom flash configuration - // This should be performed by the boot ROM but for some reason it is not. - FLEXSPI_UpdateLUT(BOARD_FLEX_SPI, 0, - qspiflash_config.memConfig.lookupTable, - ARRAY_SIZE(qspiflash_config.memConfig.lookupTable)); + // And fix the entry for PAGEPROGRAM_QUAD + flexspi_nor_update_lut(); // Configure FLEXSPI IP FIFO access. BOARD_FLEX_SPI->MCR0 &= ~(FLEXSPI_MCR0_ARDFEN_MASK); diff --git a/ports/mimxrt/hal/flexspi_flash_config.h b/ports/mimxrt/hal/flexspi_flash_config.h index 5320231a149..7538537aeae 100644 --- a/ports/mimxrt/hal/flexspi_flash_config.h +++ b/ports/mimxrt/hal/flexspi_flash_config.h @@ -75,6 +75,12 @@ (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) +#define EMPTY_LUT \ + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ + // !@brief Definitions for FlexSPI Serial Clock Frequency typedef enum _FlexSpiSerialClockFreq { @@ -209,20 +215,20 @@ typedef struct _FlexSPIConfig } flexspi_mem_config_t; /* */ -#define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 0 +#define NOR_CMD_LUT_SEQ_IDX_READ 0 #define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG 1 -#define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD 2 +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI 2 #define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE 3 -#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI 4 +#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 4 #define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 -#define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 6 -#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD 7 -#define NOR_CMD_LUT_SEQ_IDX_READID 8 +#define NOR_CMD_LUT_SEQ_IDX_READQUAD 6 +#define NOR_CMD_LUT_SEQ_IDX_READID 7 +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM 9 -#define NOR_CMD_LUT_SEQ_IDX_ENTERQPI 10 +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD 10 #define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 -#define NOR_CMD_LUT_SEQ_IDX_EXITQPI 12 -#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 13 +// Index 12 is left empty +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 #define HYPERFLASH_CMD_LUT_SEQ_IDX_READDATA 0 #define HYPERFLASH_CMD_LUT_SEQ_IDX_WRITEDATA 1 diff --git a/ports/mimxrt/hal/flexspi_hyper_flash.c b/ports/mimxrt/hal/flexspi_hyper_flash.c index 5e5d87166d8..c0cd63ca0be 100644 --- a/ports/mimxrt/hal/flexspi_hyper_flash.c +++ b/ports/mimxrt/hal/flexspi_hyper_flash.c @@ -9,6 +9,9 @@ #include "fsl_clock.h" #include "flexspi_hyper_flash.h" +void flexspi_nor_update_lut(void) { +} + // Copy of a few (pseudo-)functions from fsl_clock.h, which were nor reliably // inlined when they should be. That caused DEBUG mode to fail. // It does not increase the code size, since they were supposed to be inline. diff --git a/ports/mimxrt/hal/flexspi_nor_flash.c b/ports/mimxrt/hal/flexspi_nor_flash.c index 956fb657db6..a890533a696 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.c +++ b/ports/mimxrt/hal/flexspi_nor_flash.c @@ -36,6 +36,23 @@ #include #include "fsl_common.h" #include "flexspi_nor_flash.h" +#include "flexspi_flash_config.h" + +uint32_t LUT_pageprogram_quad[4] = { + // 10 Page Program - quad mode + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x32, RADDR_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(WRITE_SDR, FLEXSPI_4PAD, 0x04, STOP, FLEXSPI_1PAD, 0), + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler +}; + +void flexspi_nor_update_lut(void) { + uint32_t lookuptable_copy[64]; + memcpy(lookuptable_copy, (const uint32_t *)&qspiflash_config.memConfig.lookupTable, 64 * sizeof(uint32_t)); + // write PAGEPROGRAM_QUAD code to entry 10 + memcpy(&lookuptable_copy[10 * 4], LUT_pageprogram_quad, 4 * sizeof(uint32_t)); + FLEXSPI_UpdateLUT(BOARD_FLEX_SPI, 0, lookuptable_copy, 64); +} void flexspi_nor_reset(FLEXSPI_Type *base) __attribute__((section(".ram_functions"))); void flexspi_nor_reset(FLEXSPI_Type *base) { @@ -106,9 +123,9 @@ status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base) __attribute__((section status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base) { flexspi_transfer_t flashXfer; status_t status; - uint32_t writeValue = 0x40; + uint32_t writeValue = qspiflash_config.memConfig.deviceModeArg; - /* Write neable */ + /* Write enable */ status = flexspi_nor_write_enable(base, 0); if (status != kStatus_Success) { @@ -228,22 +245,3 @@ status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, co return status; } - -status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId) __attribute__((section(".ram_functions"))); -status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId) { - uint32_t temp; - flexspi_transfer_t flashXfer; - flashXfer.deviceAddress = 0; - flashXfer.port = kFLEXSPI_PortA1; - flashXfer.cmdType = kFLEXSPI_Read; - flashXfer.SeqNumber = 1; - flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READID; - flashXfer.data = &temp; - flashXfer.dataSize = 2; - - status_t status = FLEXSPI_TransferBlocking(base, &flashXfer); - - *vendorId = temp; - - return status; -} diff --git a/ports/mimxrt/hal/qspi_nor_flash_config.c b/ports/mimxrt/hal/qspi_nor_flash_config.c index a8040737e51..9edf44fc64c 100644 --- a/ports/mimxrt/hal/qspi_nor_flash_config.c +++ b/ports/mimxrt/hal/qspi_nor_flash_config.c @@ -49,76 +49,75 @@ const flexspi_nor_config_t qspiflash_config = { .seqNum = 1u, }, .deviceModeArg = 0x40, - // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock .deviceType = kFlexSpiDeviceType_SerialNOR, .sflashPadType = kSerialFlash_4Pads, .serialClkFreq = MICROPY_HW_FLASH_CLK, .sflashA1Size = MICROPY_HW_FLASH_SIZE, .lookupTable = { - // 0 Read LUTs 0 -> 0 - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + // 0 Read LUTs 0 + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 24), FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 1 Read status register -> 1 + // 1 Read status register FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x05, READ_SDR, FLEXSPI_1PAD, 0x01), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 2 Fast read quad mode - SDR - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x6B, RADDR_SDR, FLEXSPI_1PAD, 0x18), - FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x08, READ_SDR, FLEXSPI_4PAD, 0x04), + // 2 Read extend parameters + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x81, READ_SDR, FLEXSPI_1PAD, 0x04), + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 3 Write Enable -> 3 + // 3 Write Enable FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x06, STOP, FLEXSPI_1PAD, 0), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 4 Read extend parameters - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x81, READ_SDR, FLEXSPI_1PAD, 0x04), + // 4 Write Status Reg + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x01, WRITE_SDR, FLEXSPI_1PAD, 0x01), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 5 Erase Sector -> 5 + // 5 Erase Sector FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x20, RADDR_SDR, FLEXSPI_1PAD, 24), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 6 Write Status Reg - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x01, WRITE_SDR, FLEXSPI_1PAD, 0x04), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + // 6 Fast read quad mode - SDR + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x6B, RADDR_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x08, READ_SDR, FLEXSPI_4PAD, 0x04), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 7 Page Program - quad mode (-> 9) - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x32, RADDR_SDR, FLEXSPI_1PAD, 0x18), - FLEXSPI_LUT_SEQ(WRITE_SDR, FLEXSPI_4PAD, 0x04, STOP, FLEXSPI_1PAD, 0), + // 7 Read ID + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x90, DUMMY_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x00, 0, 0, 0), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 8 Read ID - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x90, DUMMY_SDR, FLEXSPI_1PAD, 24), - FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x00, 0, 0, 0), + // 8 Erase Block (32k) + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x52, RADDR_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 9 Page Program - single mode -> 9 + // 9 Page Program - single mode FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x02, RADDR_SDR, FLEXSPI_1PAD, 24), FLEXSPI_LUT_SEQ(WRITE_SDR, FLEXSPI_1PAD, 0, 0, 0, 0), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 10 Enter QPI mode - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x35, STOP, FLEXSPI_1PAD, 0), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + // 10 Page Program - quad mode + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x32, RADDR_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(WRITE_SDR, FLEXSPI_4PAD, 0x04, STOP, FLEXSPI_1PAD, 0), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler @@ -128,15 +127,12 @@ const flexspi_nor_config_t qspiflash_config = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 12 Exit QPI mode - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_4PAD, 0xF5, STOP, FLEXSPI_1PAD, 0), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + // 12 Empty LUT + EMPTY_LUT - // 13 Erase Block (32k) -> 13 - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x52, RADDR_SDR, FLEXSPI_1PAD, 24), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + // 13 READ SDFP + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x5A, RADDR_SDR, FLEXSPI_1PAD, 24), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_1PAD, 8, READ_SDR, FLEXSPI_1PAD, 0x04), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler }, From 2a80d5c68b5e03570784d18985d5388995ba0e40 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 26 Jan 2025 21:39:58 +0100 Subject: [PATCH 0277/2098] mimxrt/flash: Swap the order of disabling IRQ and disabling the cache. This change stopped problems with USB IRQ happening during flash writes. Signed-off-by: robert-hh --- ports/mimxrt/flash.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/ports/mimxrt/flash.c b/ports/mimxrt/flash.c index 7df799884e9..cdcdc7483d8 100644 --- a/ports/mimxrt/flash.c +++ b/ports/mimxrt/flash.c @@ -43,14 +43,13 @@ void flash_init(void) { __attribute__((section(".ram_functions"))) status_t flash_erase_block(uint32_t erase_addr) { status_t status = kStatus_Fail; - SCB_CleanInvalidateDCache(); - SCB_DisableDCache(); __disable_irq(); + SCB_DisableDCache(); status = flexspi_nor_flash_erase_block(BOARD_FLEX_SPI, erase_addr); - __enable_irq(); SCB_EnableDCache(); + __enable_irq(); return status; } @@ -60,14 +59,13 @@ __attribute__((section(".ram_functions"))) status_t flash_erase_block(uint32_t e __attribute__((section(".ram_functions"))) status_t flash_erase_sector(uint32_t erase_addr) { status_t status = kStatus_Fail; - SCB_CleanInvalidateDCache(); - SCB_DisableDCache(); __disable_irq(); + SCB_DisableDCache(); status = flexspi_nor_flash_erase_sector(BOARD_FLEX_SPI, erase_addr); - __enable_irq(); SCB_EnableDCache(); + __enable_irq(); return status; } @@ -83,10 +81,6 @@ __attribute__((section(".ram_functions"))) status_t flash_write_block(uint32_t d if (length == 0) { status = kStatus_Success; // Nothing to do } else { - - SCB_CleanInvalidateDCache(); - SCB_DisableDCache(); - // write data in chunks not crossing a page boundary do { next_addr = dest_addr - (dest_addr % PAGE_SIZE_BYTES) + PAGE_SIZE_BYTES; // next page boundary @@ -96,7 +90,11 @@ __attribute__((section(".ram_functions"))) status_t flash_write_block(uint32_t d } __disable_irq(); + SCB_DisableDCache(); + status = flexspi_nor_flash_page_program(BOARD_FLEX_SPI, dest_addr, (uint32_t *)src, write_length); + + SCB_EnableDCache(); __enable_irq(); // Update remaining data length @@ -106,9 +104,6 @@ __attribute__((section(".ram_functions"))) status_t flash_write_block(uint32_t d src += write_length; dest_addr += write_length; } while ((length > 0) && (status == kStatus_Success)); - - SCB_EnableDCache(); - } return status; } From 9eb9451d8377c2adc5187cfb51ef68de843b680f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 7 Oct 2024 08:45:54 +0200 Subject: [PATCH 0278/2098] mimxrt/boards: Update the deploy instructions for the UF2 bootloader. Signed-off-by: robert-hh --- .../ADAFRUIT_METRO_M7/deploy_metro_m7.md | 148 ++---------------- .../boards/OLIMEX_RT1010/deploy_olimex.md | 42 ++--- ports/mimxrt/boards/SEEED_ARCH_MIX/deploy.md | 58 ++++++- ports/mimxrt/boards/deploy_mimxrt.md | 44 +++++- 4 files changed, 123 insertions(+), 169 deletions(-) diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md index bc92016b717..c5bfd1a28a7 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md @@ -1,141 +1,11 @@ ## 1. Deploy the MicroPython firmware to the Metro M7 board. -### 1.1 Deploy the firmware using the serial bootloader. - -For initial deployment of the firmware a few preparation steps are required, which -have to be done once. - -1. Get the files ufconv.py and uf2families.json from the micropython/tools directory, -e.g. at https://github.com/micropython/micropython/tree/master/tools. - -2. Get the NXP program sdphost for your operating system, e.g. from -https://github.com/adafruit/tinyuf2/tree/master/ports/mimxrt10xx/sdphost. -You can also get them from the NXP web sites. - -3. Get the UF2 boot-loader package https://github.com/adafruit/tinyuf2/releases/download/0.9.0/tinyuf2-imxrt1010_evk-0.9.0.zip -and extract the file tinyuf2-imxrt1010_evk-0.9.0.bin. - -Now you have all files at hand that you will need for updating. - -1. Get the firmware you want to upload from the MicroPython download page. - -2. Set the two BOOTSEL DIP switches to the 1/0 position, which is the opposite position of the normal use mode. - -3. Push the reset button. - -4. Run the commands: - -``` -sudo ./sdphost -u 0x1fc9,0x0145 -- write-file 0x20206400 tinyuf2-imxrt1010_evk-0.9.0.bin -sudo ./sdphost -u 0x1fc9,0x0145 -- jump-address 0x20207000 -``` -Wait until a drive icon appears on the computer (or mount it explicitly), and then run: -``` -python3 uf2conv.py --base 0x60000400 -f 0x4fb2d5bd -``` -You can put all of that in a script. Just add a short wait before the 3rd command to let the drive connect. - -5. Once the upload is finished, set the BOOTSEL DIP switches back to the 0/1 position and push reset. - -Using sudo is Linux specific. You may not need it at all, if the access rights are set properly, -and you will not need it for Windows. - -### 1.2 Deploy the firmware using a JTAG adapter. - -With a JTAG adapter the firmware can be easily installed. Appropriate tools are Segger JFlash Lite and -the Segger Edu Mini adapter. Just use the firmware.hex file for loading, which will be loaded at the -proper address. - - -## 2. Deploy the WiFi firmware. - -The NINA firmware in the NINA module has to be updated for use with MicroPython. That can be done -using MicroPython and two small Python scripts. - -The firmware binaries are available at -https://github.com/micropython/micropython-lib/tree/master/micropython/espflash -or https://github.com/robert-hh/Shared-Stuff. For the Metro M7 board, the -NINA_FW_v1.5.0_Airlift.bin file is needed. - -For firmware upload, the following connections to the WiFi module are required: - -- Pin Reset (as above) -- Pin GPIO0 -- UART RX -- UART TX - -The GPIO pins and UART device id varies between boards. At the Adafruit Metro M7 board, -the UART is UART(1), and the Pin names for reset and GPIO0 are ESP_RESET and ESP_GPIO0. -The firmware can be uploaded, using the espflash.py module, a short script -using espflash.py and mpremote. espflash.py is available at -https://github.com/micropython/micropython-lib/tree/master/micropython/espflash. -This place also holds the example script. - -``` -import espflash -from machine import Pin -from machine import UART -import sys -sys.path.append("/flash") - -reset = Pin("ESP_RESET", Pin.OUT) -gpio0 = Pin("ESP_GPIO0", Pin.OUT) -uart = UART(0, 115200, timeout=350) - -md5sum = b"b0b9ab23da820a469e597c41364acb3a" -path = "/remote/NINA_FW_v1.5.0_Airlift.bin" - -esp = espflash.ESPFlash(reset, gpio0, uart) -# Enter bootloader download mode, at 115200 -esp.bootloader() -# Can now change to higher/lower baud rate -esp.set_baudrate(921600) -# Must call this first before any flash functions. -esp.flash_attach() -# Read flash size -size = esp.flash_read_size() -# Configure flash parameters. -esp.flash_config(size) -# Write firmware image from internal storage. -esp.flash_write_file(path) -# Compares file and flash MD5 checksum. -esp.flash_verify_file(path, md5sum) -# Resets the ESP32 chip. -esp.reboot() -``` - -The script shows the set-up for the Metro M7 board. -The md5sum is the one of the WiFi firmware. It may change and -can be recalculated using e.g. the Linux `md5sum` command. It is used to -verify the firmware upload. To upload the firmware, place the firmware -and the above script (let's call it ninaflash.py) into the same directory -on your PC, and run the command: -``` -mpremote connect mount . run ninaflash.py -``` -After a while, the upload will start. A typical start sequence looks like: -``` -Local directory . is mounted at /remote -Failed to read response to command 8. -Failed to read response to command 8. -Changing baudrate => 921600 -Flash attached -Flash size 2.0 MBytes -Flash write size: 1310720 total_blocks: 320 block size: 4096 -Writing sequence number 0/320... -Writing sequence number 1/320... -Writing sequence number 2/320... -Writing sequence number 3/320... -Writing sequence number 4/320... -.... -.... -Writing sequence number 317/320... -Writing sequence number 318/320... -Writing sequence number 319/320... -Flash write finished -Flash verify: File MD5 b'b0b9ab23da820a469e597c41364acb3a' -Flash verify: Flash MD5 b'b0b9ab23da820a469e597c41364acb3a' -Firmware verified. -``` -The initial messages `Failed to read response to command 8.` -can be ignored. +The Metro M7 board comes pre-installed with a UF2 bootloader. It can +be started by pushing reset twice. Then the bootloader drive will +appear. If that does not happen or the bootloader was lost, you can +reinstall the bootloader using the instructions by Adafruit +here: https://learn.adafruit.com/adafruit-metro-m7-microsd/installing-the-bootloader + +Once the bootloader is installed and started, you can install MicroPython +by copying the .uf2 version of the firmware file to the bootloader +drive. When the firmware is installed, the drive will disappear. diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md b/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md index 92f3df57c9b..7abba784b94 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md +++ b/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md @@ -1,39 +1,43 @@ -For initial deployment of the firmware a few preparation steps are required, which -have to be done once. +The Olimex RT1011 board is delivered without firmware. The best option to +install MicroPython is installing a UF2 bootstrap loader first, which then can be +used to install and update MicroPython. The bootloader has to be installed +only once. -1. Get the files ufconv.py and uf2families.json from the micropython/tools directory, -e.g. at https://github.com/micropython/micropython/tree/master/tools. +For initial deployment of the bootloader a few preparation steps are required, which +have to be done once. -2. Get the NXP program sdphost for your operating system, e.g. from +1. Get the NXP program sdphost for your operating system, e.g. from https://github.com/adafruit/tinyuf2/tree/master/ports/mimxrt10xx/sdphost. You can also get them from the NXP web sites. -3. Get the UF2 boot-loader package https://github.com/adafruit/tinyuf2/releases/download/0.9.0/tinyuf2-imxrt1010_evk-0.9.0.zip -and extract the file tinyuf2-imxrt1010_evk-0.9.0.bin. +2. Get the UF2 boot-loader package https://github.com/adafruit/tinyuf2/releases/tag/0.21.0/tinyuf2-imxrt1010_evk-0.20.1.zip and extract the files `tinyuf2-imxrt1010_evk-0.21.0.bin` +. You may as well go for a newer release. Now you have all files at hand that you will need for updating. -1. Get the firmware you want to upload from the MicroPython download page. +1. Get the firmware file you want to upload with the .uf2 extension from the MicroPython download page. 2. Push and hold the "Boot" button, then press "Reset", and release both buttons. 3. Run the commands: ``` -sudo ./sdphost -u 0x1fc9,0x0145 -- write-file 0x20206400 tinyuf2-imxrt1010_evk-0.9.0.bin +sudo ./sdphost -u 0x1fc9,0x0145 -- write-file 0x20206400 tinyuf2-imxrt1010_evk-0.21.0.bin sudo ./sdphost -u 0x1fc9,0x0145 -- jump-address 0x20207000 ``` -Wait until a drive icon appears on the computer (or mount it explicitly), and then run: -``` -python3 uf2conv.py --base 0x60000400 -f 0x4fb2d5bd -``` -You can put all of that in a script. Just add a short wait before the 3rd command to let the drive connect. - -4. Once the upload is finished, push Reset again. +Wait until a drive icon appears on the computer (or mount it explicitly). Then the UF2 bootloader +is permanently installed. Using sudo is Linux specific. You may not need it at all, if the access rights are set properly, and you will not need it for Windows. -Once the generic boot-loader is available, this procedure is only required for the first -firmware load or in case the flash is corrupted and the existing firmware is not functioning -any more. +4. Once the upload of the bootloader is finished, push Reset twice. + +The bootloader should start and show a drive icon. Copy the .uf2 version of MicroPython +to this drive to install or update MicroPython. + +Once the UF2 bootloader is installed, only step 4 is required to deploy MicroPython. If +MicroPython is already installed, the bootloader can as well be invoked by calling +`machine.bootloader()`. + +If at any time the flash content is corrupted you can always start over from the beginning. diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/deploy.md b/ports/mimxrt/boards/SEEED_ARCH_MIX/deploy.md index e91c48d46ca..9178c717e36 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/deploy.md +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/deploy.md @@ -1,6 +1,52 @@ -Firmware upload to the Seed ARCH MIX board can be done using the J-Link interface -For that, follow the instructions given by Seed in their Wiki at -https://wiki.seeedstudio.com/Arch_Mix/#flashing-arduino-bootloader-to-arch-mix. -You will need a J-Link debug probe and software. What has been tested was the -Segger JLlink edu or Segger JLink edu mini. As matching loader tool you can use -Segger JFlashLite. The target address for loading is 0x60000000. +The Seeed Arch Mix board is delivered without firmware. The best option to +install MicroPython is installing a UF2 bootstrap loader first, which then can be +used to install and update MicroPython. The bootloader has to be installed +only once. + +For initial deployment of the bootloader a few preparation steps are required, which +have to be done once. + +1. Get the NXP program sdphost for your operating system, e.g. from +https://github.com/adafruit/tinyuf2/tree/master/ports/mimxrt10xx/sdphost. +You can also get them from the NXP web sites. + +2. Get the UF2 boot-loader package https://github.com/adafruit/tinyuf2/releases/tag/0.20.1/tinyuf2-imxrt1050_evkb-0.21.0.zip and extract the files `tinyuf2-imxrt1050_evkb-0.21.0.bin`. +You may as well go for a newer release. + +Now you have all files at hand that you will need for updating. + +1. Get the firmware file you want to upload with the .uf2 extension from the MicroPython download page. + +2. At the Seeed Arch Mix board, connect the RX pin (J3-19) with 3.3V, and change the DIP switches +3 an 4 at SW1 from 1-0 to 0-1. + +3. Push Reset. + +4. Run the commands: + +``` +sudo ./sdphost -u 0x1fc9,0x0130 -- write-file 0x1000 tinyuf2-imxrt1050_evkb-0.21.0.bin +sudo ./sdphost -u 0x1fc9,0x0130 -- jump-address 0x2000 +``` +Wait until a drive icon appears on the computer (or mount it explicitly). When using the above +mentioned bootloader, it has the label `RT1050BOOT`. Then the UF2 bootloader +is permanently installed. + +Using sudo is Linux specific. You may not need it at all, if the access rights are set properly, +and you will not need it for Windows. + +5. At the Seeed Arch Mix board, disconnect the RX pin (J3-19) with 3.3V, and change the DIP switches +3 an 4 at SW1 back to 1-0. + +6. Once the upload of the bootloader is finished or when it is already installed, push Reset twice. + +The bootloader should start and show a drive icon. Do not push too fast. The i.MX RT MCU +have no dedicated Reset Pin and are reset through power cycling, which may be slow. +Copy the .uf2 version of MicroPython to this drive to install or update MicroPython. +If after steps 1-4 the bootloader drive is already shown, you do not have to reset again. + +Once the UF2 bootloader is installed, only step 6 is required to deploy MicroPython. If +MicroPython is already installed, the bootloader can as well be invoked by calling +`machine.bootloader()` or switching the USB baud rate at the PC to 1200 baud and back. + +If at any time the flash content is corrupted you can always start over from the beginning. diff --git a/ports/mimxrt/boards/deploy_mimxrt.md b/ports/mimxrt/boards/deploy_mimxrt.md index 35752a8362c..7ad21fe64ca 100644 --- a/ports/mimxrt/boards/deploy_mimxrt.md +++ b/ports/mimxrt/boards/deploy_mimxrt.md @@ -1,11 +1,45 @@ -Firmware can be loaded to the MIMXRT development boards in various ways. The most convenient -one is using the built-in support MCU. When a PC is connected to the debug USB port, a drive -icon will appear. Firmware can be uploaded to the board by copying it to this drive. The copy -and flashing will take a few moments. At the end of the upload, the drive icon will disappear -and reappear again. Then the reset button has to be pushed, which starts the MicroPython firmware. +## Firmware installation options + +There are two ways to load the MicroPython firmware to the device: + +1. Load the MicroPython firmware directly to the device. The MicroPython +firmware files for that method have the extension .bin or .hex and are available +at the MicroPython download site. +2. Install a resident UF2 bootstrap loader to the device first and use that later for loading +MicroPython. The MicroPython firmware files for that method have the extension .uf2 +and are available at the MicroPython download site. The UF2 bootstrap loader can be obtained +from the site https://github.com/adafruit/tinyuf2. Open the recent release page and +get the version of the bootloader for your board. If there is no specific bootloader +for a specific board, get versions for the respective imxrt10xx-evk board. The file +with the .bin or .hex extension is the one to be installed first. + +## Direct loading of MicroPython or installation of the UF2 bootloader + +The MicroPython firmware or the UF2 bootstrap loader can be loaded to the MIMXRT development +boards in various ways. The most convenient one is using the built-in support MCU. When a PC +is connected to the debug USB port, a drive icon will appear. Firmware can be uploaded to +the board by copying it to this drive. The copy and flashing will take a few moments. +At the end of the upload, the drive icon will disappear and reappear again. Then the reset +button has to be pushed, which starts the MicroPython firmware. Depending on the power jumper settings, both the debug USB and OTG USB port have to be powered during firmware upload. You may as well load the firmware using the JLink port or openSDA interface with the appropriate tools. For more options, consult the user guide of the board. + +## Installing the MicroPython firmware using the UF2 bootloader + +When using the UF2 bootloader, the OTG USB port will be used. +Once the UF2 bootloader is installed, it has to be started to upload MicroPython.The +methods to start the bootloader are: + +- Push reset twice. +- Call machine.bootloader() e.g. from REPL. +- Switch the USB port shortly to 1200 baud and back. That requires MicroPython to be +installed. + +If there is no valid Firmware on the device, the bootloader will start automatically. +Once it's started, a drive ICON will appear. The MicroPython firmware file with .uf2 +extension must then be copied to that drive. When the file is copied and MicroPython +is installed, the drive disappears and MicroPython starts. From d40849d07d2523fa16070bfcae34b51b7b14942b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 3 Feb 2025 21:12:35 +0100 Subject: [PATCH 0279/2098] mimxrt/boards: Add flash configuration constants to mpconfigboard.mk. And use these to initialize the LUT table properly for the various flash types. The different flash types differ by 3 parameters. Thus it is easier to just keep one copy of the qspiflash_config structure with the LUT table and update it during flash initialisation as needed. Signed-off-by: robert-hh --- ports/mimxrt/Makefile | 6 ++++ .../boards/ADAFRUIT_METRO_M7/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1010_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1015_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1020_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1050_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1060_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1064_EVK/mpconfigboard.mk | 3 ++ .../boards/MIMXRT1170_EVK/mpconfigboard.mk | 3 ++ .../boards/OLIMEX_RT1010/mpconfigboard.mk | 3 ++ .../boards/SEEED_ARCH_MIX/mpconfigboard.mk | 3 ++ ports/mimxrt/boards/TEENSY40/mpconfigboard.mk | 3 ++ ports/mimxrt/boards/TEENSY41/mpconfigboard.mk | 3 ++ ports/mimxrt/hal/flexspi_flash_config.h | 2 +- ports/mimxrt/hal/flexspi_nor_flash.c | 16 +++++++-- ports/mimxrt/hal/flexspi_nor_flash.h | 1 + ports/mimxrt/hal/qspi_nor_flash_config.c | 34 ++++++++----------- 17 files changed, 72 insertions(+), 23 deletions(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index ad7d853b677..224b38384f7 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -425,6 +425,12 @@ endif ifdef MICROPY_HW_FLASH_CLK CFLAGS += -DMICROPY_HW_FLASH_CLK=$(MICROPY_HW_FLASH_CLK) endif +ifdef MICROPY_HW_FLASH_QE_CMD + CFLAGS += -DMICROPY_HW_FLASH_QE_CMD=$(MICROPY_HW_FLASH_QE_CMD) +endif +ifdef MICROPY_HW_FLASH_QE_ARG + CFLAGS += -DMICROPY_HW_FLASH_QE_ARG=$(MICROPY_HW_FLASH_QE_ARG) +endif CFLAGS += $(CFLAGS_EXTRA) diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk index 74d3a6f0f10..b9c1716a7f6 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1011DAE5A MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE ?= qspi_nor_flash MICROPY_HW_FLASH_SIZE ?= 0x800000 # 8MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 MICROPY_PY_NETWORK_NINAW10 ?= 1 MICROPY_PY_SSL ?= 1 diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk index 1aef451e2a6..592b89c68ec 100644 --- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1011DAE5A MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 USE_UF2_BOOTLOADER = 1 diff --git a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk index 0f719ac0a97..c5d02294bc3 100644 --- a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1015DAF5A MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 MICROPY_BOOT_BUFFER_SIZE = (32 * 1024) diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk index 7715e669f8d..5ef078210a2 100644 --- a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1021DAG5A MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x800000 # 8MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x01 +MICROPY_HW_FLASH_QE_ARG = 0x40 MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk index ef7bbc8f56c..8b048c85eaa 100644 --- a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk @@ -7,6 +7,9 @@ MICROPY_HW_FLASH_SIZE = 0x4000000 # 64MB MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x01 +MICROPY_HW_FLASH_QE_ARG = 0x40 MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk index f57aaff3b8b..54539952fb4 100644 --- a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1062DVJ6A MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x800000 # 8MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x01 +MICROPY_HW_FLASH_QE_ARG = 0x40 MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk index f175d26377a..b393d7ed9f2 100644 --- a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1064DVL6A MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = internal MICROPY_HW_FLASH_SIZE = 0x400000 # 4MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk index 6e5ee56aa10..ef4ab683a21 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk @@ -6,6 +6,9 @@ MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE ?= qspi_nor_flash MICROPY_HW_FLASH_SIZE ?= 0x1000000 # 16MB MICROPY_HW_FLASH_RESERVED ?= 0x100000 # 1MB CM4 Code address space +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x4000000 # 64MB diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk index 1de497d74e3..7e9987de0cf 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk +++ b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk @@ -5,6 +5,9 @@ MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x200000 # 2MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x01 +MICROPY_HW_FLASH_QE_ARG = 0x40 USE_UF2_BOOTLOADER = 1 diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk index c691e2dcb55..7ea107b00df 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk @@ -4,6 +4,9 @@ MCU_VARIANT = MIMXRT1052DVL6B MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x800000 # 8MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x01 +MICROPY_HW_FLASH_QE_ARG = 0x40 MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB diff --git a/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk b/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk index 87c122da9d4..9393252d1a8 100644 --- a/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk +++ b/ports/mimxrt/boards/TEENSY40/mpconfigboard.mk @@ -5,6 +5,9 @@ MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x200000 # 2MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 USE_UF2_BOOTLOADER = 1 diff --git a/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk b/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk index e28310d091a..6bb9e607f65 100755 --- a/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk +++ b/ports/mimxrt/boards/TEENSY41/mpconfigboard.mk @@ -5,6 +5,9 @@ MICROPY_FLOAT_IMPL = double MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x800000 # 8MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 diff --git a/ports/mimxrt/hal/flexspi_flash_config.h b/ports/mimxrt/hal/flexspi_flash_config.h index 7538537aeae..d9aa5ed5eab 100644 --- a/ports/mimxrt/hal/flexspi_flash_config.h +++ b/ports/mimxrt/hal/flexspi_flash_config.h @@ -75,7 +75,7 @@ (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) -#define EMPTY_LUT \ +#define EMPTY_LUT_SEQ \ FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), \ diff --git a/ports/mimxrt/hal/flexspi_nor_flash.c b/ports/mimxrt/hal/flexspi_nor_flash.c index a890533a696..6d5b830383f 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.c +++ b/ports/mimxrt/hal/flexspi_nor_flash.c @@ -46,11 +46,23 @@ uint32_t LUT_pageprogram_quad[4] = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler }; +uint32_t LUT_write_status[4] = { + // 4 Write status word for Quad mode + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, MICROPY_HW_FLASH_QE_CMD, WRITE_SDR, FLEXSPI_1PAD, 0x01), + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler +}; + void flexspi_nor_update_lut(void) { uint32_t lookuptable_copy[64]; memcpy(lookuptable_copy, (const uint32_t *)&qspiflash_config.memConfig.lookupTable, 64 * sizeof(uint32_t)); + // write WRITESTATUSREG code to entry 10 + memcpy(&lookuptable_copy[NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG * 4], + LUT_write_status, 4 * sizeof(uint32_t)); // write PAGEPROGRAM_QUAD code to entry 10 - memcpy(&lookuptable_copy[10 * 4], LUT_pageprogram_quad, 4 * sizeof(uint32_t)); + memcpy(&lookuptable_copy[NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD * 4], + LUT_pageprogram_quad, 4 * sizeof(uint32_t)); FLEXSPI_UpdateLUT(BOARD_FLEX_SPI, 0, lookuptable_copy, 64); } @@ -123,7 +135,7 @@ status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base) __attribute__((section status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base) { flexspi_transfer_t flashXfer; status_t status; - uint32_t writeValue = qspiflash_config.memConfig.deviceModeArg; + uint32_t writeValue = MICROPY_HW_FLASH_QE_ARG; /* Write enable */ status = flexspi_nor_write_enable(base, 0); diff --git a/ports/mimxrt/hal/flexspi_nor_flash.h b/ports/mimxrt/hal/flexspi_nor_flash.h index 6526142af22..2e61effc2b7 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.h +++ b/ports/mimxrt/hal/flexspi_nor_flash.h @@ -47,6 +47,7 @@ extern flexspi_nor_config_t qspiflash_config; status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId); status_t flexspi_nor_init(void); void flexspi_nor_update_lut(void); +status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base); status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size); diff --git a/ports/mimxrt/hal/qspi_nor_flash_config.c b/ports/mimxrt/hal/qspi_nor_flash_config.c index 9edf44fc64c..36ccc30fc19 100644 --- a/ports/mimxrt/hal/qspi_nor_flash_config.c +++ b/ports/mimxrt/hal/qspi_nor_flash_config.c @@ -48,7 +48,7 @@ const flexspi_nor_config_t qspiflash_config = { .seqId = 4u, .seqNum = 1u, }, - .deviceModeArg = 0x40, + .deviceModeArg = MICROPY_HW_FLASH_QE_ARG, .deviceType = kFlexSpiDeviceType_SerialNOR, .sflashPadType = kSerialFlash_4Pads, .serialClkFreq = MICROPY_HW_FLASH_CLK, @@ -68,10 +68,7 @@ const flexspi_nor_config_t qspiflash_config = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler // 2 Read extend parameters - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x81, READ_SDR, FLEXSPI_1PAD, 0x04), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + EMPTY_LUT_SEQ // 3 Write Enable FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x06, STOP, FLEXSPI_1PAD, 0), @@ -80,7 +77,7 @@ const flexspi_nor_config_t qspiflash_config = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler // 4 Write Status Reg - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x01, WRITE_SDR, FLEXSPI_1PAD, 0x01), + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, MICROPY_HW_FLASH_QE_CMD, WRITE_SDR, FLEXSPI_1PAD, 0x01), FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler @@ -92,16 +89,10 @@ const flexspi_nor_config_t qspiflash_config = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler // 6 Fast read quad mode - SDR - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x6B, RADDR_SDR, FLEXSPI_1PAD, 24), - FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x08, READ_SDR, FLEXSPI_4PAD, 0x04), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + EMPTY_LUT_SEQ // 7 Read ID - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x90, DUMMY_SDR, FLEXSPI_1PAD, 24), - FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x00, 0, 0, 0), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + EMPTY_LUT_SEQ // 8 Erase Block (32k) FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x52, RADDR_SDR, FLEXSPI_1PAD, 24), @@ -127,14 +118,17 @@ const flexspi_nor_config_t qspiflash_config = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - // 12 Empty LUT - EMPTY_LUT + // 12 Not used + EMPTY_LUT_SEQ // 13 READ SDFP - FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x5A, RADDR_SDR, FLEXSPI_1PAD, 24), - FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_1PAD, 8, READ_SDR, FLEXSPI_1PAD, 0x04), - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler - FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler + EMPTY_LUT_SEQ + + // 14 Not used + EMPTY_LUT_SEQ + + // 15 Not used + EMPTY_LUT_SEQ }, }, .pageSize = 256u, From 0a433a02e1eab1c95e57129a1e828780c871be42 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 4 Feb 2025 18:17:47 +0100 Subject: [PATCH 0280/2098] mimxrt/hal: Set the flexspi flash CLK frequency on boot. The flash clock frequency may have been set to a different value by a bootloader. Set the frequency according to the configured value. Use a table of pre-calculated dividers to get the closest value for the flash frequency, achieving for MIMXRT10xx: 30 -> 30.85 MHz 50 -> 49.65 MHz 60 -> 60 MHz 75 -> 75.13 MHz 80 -> 80 MHz 100 -> 99.31 Mhz 133 -> 132.92 MHz 166 -> 166.15 MHz for MIMXRT1176: 30 -> 31 MHz 50 -> 52.8 MJz 60 -> 58.7 MHz 75 -> 75.4 MHz 80 -> 75.4 MHz 100 -> 105.6 MHz 133 -> 132 MHz 166 -> 176 MHz Signed-off-by: robert-hh --- ports/mimxrt/flash.c | 3 +- ports/mimxrt/hal/flexspi_hyper_flash.c | 2 +- ports/mimxrt/hal/flexspi_hyper_flash.h | 2 +- ports/mimxrt/hal/flexspi_nor_flash.c | 93 +++++++++++++++++++++++++- ports/mimxrt/hal/flexspi_nor_flash.h | 3 +- 5 files changed, 95 insertions(+), 8 deletions(-) diff --git a/ports/mimxrt/flash.c b/ports/mimxrt/flash.c index cdcdc7483d8..57091b36ddc 100644 --- a/ports/mimxrt/flash.c +++ b/ports/mimxrt/flash.c @@ -29,7 +29,8 @@ void flash_init(void) { // Upload the custom flash configuration // And fix the entry for PAGEPROGRAM_QUAD - flexspi_nor_update_lut(); + // Update the flash CLK + flexspi_nor_update_lut_clk(MICROPY_HW_FLASH_CLK); // Configure FLEXSPI IP FIFO access. BOARD_FLEX_SPI->MCR0 &= ~(FLEXSPI_MCR0_ARDFEN_MASK); diff --git a/ports/mimxrt/hal/flexspi_hyper_flash.c b/ports/mimxrt/hal/flexspi_hyper_flash.c index c0cd63ca0be..9171640a9ed 100644 --- a/ports/mimxrt/hal/flexspi_hyper_flash.c +++ b/ports/mimxrt/hal/flexspi_hyper_flash.c @@ -9,7 +9,7 @@ #include "fsl_clock.h" #include "flexspi_hyper_flash.h" -void flexspi_nor_update_lut(void) { +void flexspi_nor_update_lut_clk(uint32_t freq_index) { } // Copy of a few (pseudo-)functions from fsl_clock.h, which were nor reliably diff --git a/ports/mimxrt/hal/flexspi_hyper_flash.h b/ports/mimxrt/hal/flexspi_hyper_flash.h index a6454a1c9a2..40416d6e21d 100644 --- a/ports/mimxrt/hal/flexspi_hyper_flash.h +++ b/ports/mimxrt/hal/flexspi_hyper_flash.h @@ -47,7 +47,7 @@ extern flexspi_nor_config_t qspiflash_config; status_t flexspi_nor_hyperflash_cfi(FLEXSPI_Type *base); void flexspi_hyper_flash_init(void); -void flexspi_nor_update_lut(void); +void flexspi_nor_update_lut_clk(uint32_t freq_index); status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size); diff --git a/ports/mimxrt/hal/flexspi_nor_flash.c b/ports/mimxrt/hal/flexspi_nor_flash.c index 6d5b830383f..7fdb6b7c13d 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.c +++ b/ports/mimxrt/hal/flexspi_nor_flash.c @@ -38,6 +38,9 @@ #include "flexspi_nor_flash.h" #include "flexspi_flash_config.h" +bool flash_busy_status_pol = 0; +bool flash_busy_status_offset = 0; + uint32_t LUT_pageprogram_quad[4] = { // 10 Page Program - quad mode FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x32, RADDR_SDR, FLEXSPI_1PAD, 24), @@ -54,16 +57,100 @@ uint32_t LUT_write_status[4] = { FLEXSPI_LUT_SEQ(0, 0, 0, 0, 0, 0), // Filler }; -void flexspi_nor_update_lut(void) { +#if defined(MIMXRT117x_SERIES) +static uint8_t div_table_mhz[] = { + 17, // Entry 0 is out of range + 17, // 30 -> 31 MHz + 10, // 50 -> 52.8 MHz + 9, // 60 -> 58.7 MHz + 7, // 75 -> 75.4 MHz + 7, // 80 -> 75.4 MHz + 5, // 100 -> 105.6 Mhz + 4, // 133 -> 132 MHz + 3 // 166 -> 176 MHz +}; + +#else +typedef struct _ps_div_t { + uint8_t pfd480_div; + uint8_t podf_div; +} ps_div_t; + +static ps_div_t div_table_mhz[] = { + { 35, 8 }, // Entry 0 is out of range + { 35, 8 }, // 30 -> 30.85 MHz + { 29, 6 }, // 50 -> 49.65 MHz + { 18, 8 }, // 60 -> 60 MHz + { 23, 5 }, // 75 -> 75.13 MHz + { 18, 6 }, // 80 -> 80 MHz + { 17, 5 }, // 100 -> 191 Mhz + { 13, 5 }, // 133 -> 132.92 MHz + { 13, 4 } // 166 -> 166.15 MHz +}; +#endif + +__attribute__((section(".ram_functions"))) void flexspi_nor_update_lut_clk(uint32_t freq_index) { + // Create a local copy of the LookupTable. Modify the entry for WRITESTATUSREG + // Add an entry for PAGEPROGRAM_QUAD. uint32_t lookuptable_copy[64]; memcpy(lookuptable_copy, (const uint32_t *)&qspiflash_config.memConfig.lookupTable, 64 * sizeof(uint32_t)); - // write WRITESTATUSREG code to entry 10 + // write local WRITESTATUSREG code to index 4 memcpy(&lookuptable_copy[NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG * 4], LUT_write_status, 4 * sizeof(uint32_t)); - // write PAGEPROGRAM_QUAD code to entry 10 + // write local PAGEPROGRAM_QUAD code to index 10 memcpy(&lookuptable_copy[NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD * 4], LUT_pageprogram_quad, 4 * sizeof(uint32_t)); + // Update the LookupTable. FLEXSPI_UpdateLUT(BOARD_FLEX_SPI, 0, lookuptable_copy, 64); + + __DSB(); + __ISB(); + __disable_irq(); + SCB_DisableDCache(); + + #if defined(MIMXRT117x_SERIES) + volatile uint8_t pll2_div = div_table_mhz[freq_index] - 1; + + while (!FLEXSPI_GetBusIdleStatus(BOARD_FLEX_SPI)) { + } + FLEXSPI_Enable(BOARD_FLEX_SPI, false); + + // Disable FlexSPI clock + // Flexspi is clocked by PLL2. Only the divider can be changed. + CCM->LPCG[kCLOCK_Flexspi1].DIRECT = ((uint32_t)kCLOCK_Off & CCM_LPCG_DIRECT_ON_MASK); + // Change the PLL divider + CCM->CLOCK_ROOT[kCLOCK_Root_Flexspi1].CONTROL = (CCM->CLOCK_ROOT[kCLOCK_Root_Flexspi1].CONTROL & ~CCM_CLOCK_ROOT_CONTROL_DIV_MASK) | + CCM_CLOCK_ROOT_CONTROL_DIV(pll2_div); + // Re-enable FlexSPI clock + CCM->LPCG[kCLOCK_Flexspi1].DIRECT = ((uint32_t)kCLOCK_On & CCM_LPCG_DIRECT_ON_MASK); + + #else + + volatile uint8_t pfd480_div = div_table_mhz[freq_index].pfd480_div; + volatile uint8_t podf_div = div_table_mhz[freq_index].podf_div - 1; + + while (!FLEXSPI_GetBusIdleStatus(BOARD_FLEX_SPI)) { + } + FLEXSPI_Enable(BOARD_FLEX_SPI, false); + + // Disable FlexSPI clock + CCM->CCGR6 &= ~CCM_CCGR6_CG5_MASK; + // Changing the clock is OK now. + // Change the PFD + CCM_ANALOG->PFD_480 = (CCM_ANALOG->PFD_480 & ~CCM_ANALOG_PFD_480_TOG_PFD0_FRAC_MASK) | CCM_ANALOG_PFD_480_TOG_PFD0_FRAC(pfd480_div); + // Change the flexspi divider + CCM->CSCMR1 = (CCM->CSCMR1 & ~CCM_CSCMR1_FLEXSPI_PODF_MASK) | CCM_CSCMR1_FLEXSPI_PODF(podf_div); + // Re-enable FlexSPI + CCM->CCGR6 |= CCM_CCGR6_CG5_MASK; + #endif + + FLEXSPI_Enable(BOARD_FLEX_SPI, true); + FLEXSPI_SoftwareReset(BOARD_FLEX_SPI); + while (!FLEXSPI_GetBusIdleStatus(BOARD_FLEX_SPI)) { + } + + SCB_EnableDCache(); + __enable_irq(); } void flexspi_nor_reset(FLEXSPI_Type *base) __attribute__((section(".ram_functions"))); diff --git a/ports/mimxrt/hal/flexspi_nor_flash.h b/ports/mimxrt/hal/flexspi_nor_flash.h index 2e61effc2b7..dc5798d97f4 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.h +++ b/ports/mimxrt/hal/flexspi_nor_flash.h @@ -45,8 +45,7 @@ extern flexspi_nor_config_t qspiflash_config; status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId); -status_t flexspi_nor_init(void); -void flexspi_nor_update_lut(void); +void flexspi_nor_update_lut_clk(uint32_t freq_index); status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base); status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address); From 2f646f93d375577d7bc5b500e6fe50c9381875c2 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 29 Nov 2024 16:22:51 +0100 Subject: [PATCH 0281/2098] mimxrt: Add optional MSC support. Add MSC support using internal flash storage or SD card. Note this is disabled by default, and can be enabled by boards if needed. Signed-off-by: iabdalkader --- ports/mimxrt/Makefile | 1 + ports/mimxrt/main.c | 15 ++++ ports/mimxrt/modmimxrt.c | 3 + ports/mimxrt/modmimxrt.h | 1 + ports/mimxrt/mpconfigport.h | 7 ++ ports/mimxrt/msc_disk.c | 153 ++++++++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+) create mode 100644 ports/mimxrt/msc_disk.c diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 224b38384f7..8106ab4f3ff 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -217,6 +217,7 @@ SRC_C += \ modmimxrt.c \ mphalport.c \ mpnetworkport.c \ + msc_disk.c \ network_lan.c \ pendsv.c \ pin.c \ diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index adb071a7fee..6b9d4fa17af 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -39,6 +39,7 @@ #include "led.h" #include "pendsv.h" #include "modmachine.h" +#include "modmimxrt.h" #if MICROPY_PY_LWIP #include "lwip/init.h" @@ -55,6 +56,7 @@ #include "systick.h" #include "extmod/modnetwork.h" +#include "extmod/vfs.h" extern uint8_t _sstack, _estack, _gc_heap_start, _gc_heap_end; @@ -113,6 +115,19 @@ int main(void) { // Execute _boot.py to set up the filesystem. pyexec_frozen_module("_boot.py", false); + #if MICROPY_HW_USB_MSC + // Set the USB medium to flash block device. + mimxrt_msc_medium = &mimxrt_flash_type; + + #if MICROPY_PY_MACHINE_SDCARD + const char *path = "/sdcard"; + // If SD is mounted, set the USB medium to SD. + if (mp_vfs_lookup_path(path, &path) != MP_VFS_NONE) { + mimxrt_msc_medium = &machine_sdcard_type; + } + #endif + #endif + // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); diff --git a/ports/mimxrt/modmimxrt.c b/ports/mimxrt/modmimxrt.c index 0f67538ce0d..d6990273885 100644 --- a/ports/mimxrt/modmimxrt.c +++ b/ports/mimxrt/modmimxrt.c @@ -31,6 +31,9 @@ static const mp_rom_map_elem_t mimxrt_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mimxrt) }, { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&mimxrt_flash_type) }, + #if MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_MSC), MP_ROM_TRUE }, + #endif }; static MP_DEFINE_CONST_DICT(mimxrt_module_globals, mimxrt_module_globals_table); diff --git a/ports/mimxrt/modmimxrt.h b/ports/mimxrt/modmimxrt.h index e0477166918..19cf799f887 100644 --- a/ports/mimxrt/modmimxrt.h +++ b/ports/mimxrt/modmimxrt.h @@ -30,5 +30,6 @@ extern const mp_obj_type_t mimxrt_flash_type; extern const mp_obj_module_t mp_module_mimxrt; +extern const mp_obj_type_t *mimxrt_msc_medium; #endif // MICROPY_INCLUDED_MIMXRT_MODMIMXRT_H diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index b45f8408e64..7da85f1aee5 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -151,8 +151,15 @@ uint32_t trng_random_u32(void); #endif #define MICROPY_HW_ENABLE_USBDEV (1) +// Enable USB-CDC serial port +#ifndef MICROPY_HW_USB_CDC #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_USB_CDC_1200BPS_TOUCH (1) +#endif +// Enable USB Mass Storage with FatFS filesystem. +#ifndef MICROPY_HW_USB_MSC +#define MICROPY_HW_USB_MSC (0) +#endif // Hooks to add builtins diff --git a/ports/mimxrt/msc_disk.c b/ports/mimxrt/msc_disk.c new file mode 100644 index 00000000000..c3801f481e6 --- /dev/null +++ b/ports/mimxrt/msc_disk.c @@ -0,0 +1,153 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * Copyright (c) 2024-2025 Ibrahim Abdelkader + * + * 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 "tusb.h" +#if CFG_TUD_MSC +#include "flash.h" +#include BOARD_FLASH_OPS_HEADER_H +#include "stdlib.h" +#include "modmimxrt.h" +#if MICROPY_PY_MACHINE_SDCARD +#include "sdcard.h" + +#ifndef MICROPY_HW_SDCARD_SDMMC +#define MICROPY_HW_SDCARD_SDMMC (1) +#endif + +#define MSC_SDCARD_INDEX (MICROPY_HW_SDCARD_SDMMC - 1) +#endif + +// This implementation does Not support Flash sector caching. +// MICROPY_FATFS_MAX_SS must be identical to SECTOR_SIZE_BYTES +#define BLOCK_SIZE (SECTOR_SIZE_BYTES) +#define BLOCK_COUNT (MICROPY_HW_FLASH_STORAGE_BYTES / BLOCK_SIZE) +#define FLASH_BASE_ADDR (MICROPY_HW_FLASH_STORAGE_BASE) + +static bool msc_ejected = false; + +const mp_obj_type_t *mimxrt_msc_medium = NULL; + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + memcpy(vendor_id, MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING), 8)); + memcpy(product_id, MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING), 16)); + memcpy(product_rev, MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING), 4)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (msc_ejected || mimxrt_msc_medium == NULL) { + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + return true; +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + if (mimxrt_msc_medium == &mimxrt_flash_type) { + *block_size = BLOCK_SIZE; + *block_count = BLOCK_COUNT; + #if MICROPY_PY_MACHINE_SDCARD + } else if (mimxrt_msc_medium == &machine_sdcard_type) { + mimxrt_sdcard_obj_t *card = &mimxrt_sdcard_objs[MSC_SDCARD_INDEX]; + *block_size = card->block_len; + *block_count = card->block_count; + #endif + } +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + if (load_eject) { + if (start) { + // load disk storage + msc_ejected = false; + } else { + // unload disk storage + msc_ejected = true; + } + } + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + if (mimxrt_msc_medium == &mimxrt_flash_type) { + flash_read_block(FLASH_BASE_ADDR + lba * BLOCK_SIZE, buffer, bufsize); + #if MICROPY_PY_MACHINE_SDCARD + } else if (mimxrt_msc_medium == &machine_sdcard_type) { + mimxrt_sdcard_obj_t *card = &mimxrt_sdcard_objs[MSC_SDCARD_INDEX]; + sdcard_read(card, buffer, lba, bufsize / card->block_len); + #endif + } + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + if (mimxrt_msc_medium == &mimxrt_flash_type) { + // Erase count sectors starting at lba + for (int n = 0; n < (bufsize / BLOCK_SIZE); n++) { + flash_erase_sector(FLASH_BASE_ADDR + (lba + n) * BLOCK_SIZE); + } + flash_write_block(FLASH_BASE_ADDR + lba * BLOCK_SIZE, buffer, bufsize); + #if MICROPY_PY_MACHINE_SDCARD + } else if (mimxrt_msc_medium == &machine_sdcard_type) { + mimxrt_sdcard_obj_t *card = &mimxrt_sdcard_objs[MSC_SDCARD_INDEX]; + sdcard_write(card, buffer, lba, bufsize / card->block_len); + + #endif + } + return bufsize; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { + int32_t resplen = 0; + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Sync the logical unit if needed. + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + return resplen; +} +#endif From cb417505f3c39a526162fe8db9365035ea415c98 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 7 Feb 2025 13:39:02 +0100 Subject: [PATCH 0282/2098] mimxrt/boards: Reduce stack size for 1011 and 1015 MCUs. Reduced to 16KBs to allow enabling MSC. Signed-off-by: iabdalkader --- ports/mimxrt/boards/MIMXRT1011.ld | 4 ++-- ports/mimxrt/boards/MIMXRT1015.ld | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index ab363bd56a0..0e961a49433 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -27,8 +27,8 @@ dtcm_size = 0x00008000; ocrm_start = 0x20200000; ocrm_size = 0x00010000; -/* 20kiB stack. */ -__stack_size__ = 0x5000; +/* 16kiB stack. */ +__stack_size__ = 0x4000; _estack = __StackTop; _sstack = __StackLimit; diff --git a/ports/mimxrt/boards/MIMXRT1015.ld b/ports/mimxrt/boards/MIMXRT1015.ld index 0237d348c24..58b88e0fe30 100644 --- a/ports/mimxrt/boards/MIMXRT1015.ld +++ b/ports/mimxrt/boards/MIMXRT1015.ld @@ -27,8 +27,8 @@ dtcm_size = 0x00008000; ocrm_start = 0x20200000; ocrm_size = 0x00010000; -/* 24kiB stack. */ -__stack_size__ = 0x5000; +/* 16kiB stack. */ +__stack_size__ = 0x4000; _estack = __StackTop; _sstack = __StackLimit; From 752c1672af7f516922416e8afe7f4cb42e151cae Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 7 Feb 2025 13:45:44 +0100 Subject: [PATCH 0283/2098] tools/ci.sh: Build MIMXRT1060_EVK with MSC enabled as part of mimxrt CI. Signed-off-by: iabdalkader --- tools/ci.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index 2c647012f14..ff362efd29e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -230,6 +230,8 @@ function ci_mimxrt_build { make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 submodules make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 + make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK submodules + make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK CFLAGS_EXTRA=-DMICROPY_HW_USB_MSC=1 } ######################################################################################## From 11c9656fad716d42a33e9f5bc65596300d141daa Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Tue, 19 Sep 2023 11:41:46 +1000 Subject: [PATCH 0284/2098] tools/mpremote: Support mip install from package.json on local fs. Add support for `mpremote mip install package.json` where `package.json` is a json file on the local filesystem. Without this, package json files can only be loaded from http, https, github or gitlab URLs. This is useful for testing `package.json` files for pacages in development and for constructing one's own `package.json` files for Python packages which are not yet available for installation using mip. Signed-off-by: Glenn Moloney --- tools/mpremote/mpremote/mip.py | 40 ++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index d23a0e2cbcb..4ce62b7f77b 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -13,6 +13,8 @@ _PACKAGE_INDEX = "https://micropython.org/pi/v2" +allowed_mip_url_prefixes = ("http://", "https://", "github:", "gitlab:") + # This implements os.makedirs(os.dirname(path)) def _ensure_path_exists(transport, path): @@ -78,16 +80,25 @@ def _download_file(transport, url, dest): def _install_json(transport, package_json_url, index, target, version, mpy): - try: - with urllib.request.urlopen(_rewrite_url(package_json_url, version)) as response: - package_json = json.load(response) - except urllib.error.HTTPError as e: - if e.status == 404: - raise CommandError(f"Package not found: {package_json_url}") - else: - raise CommandError(f"Error {e.status} requesting {package_json_url}") - except urllib.error.URLError as e: - raise CommandError(f"{e.reason} requesting {package_json_url}") + if package_json_url.startswith(allowed_mip_url_prefixes): + try: + with urllib.request.urlopen(_rewrite_url(package_json_url, version)) as response: + package_json = json.load(response) + except urllib.error.HTTPError as e: + if e.status == 404: + raise CommandError(f"Package not found: {package_json_url}") + else: + raise CommandError(f"Error {e.status} requesting {package_json_url}") + except urllib.error.URLError as e: + raise CommandError(f"{e.reason} requesting {package_json_url}") + elif package_json_url.endswith(".json"): + try: + with open(package_json_url, "r") as f: + package_json = json.load(f) + except OSError: + raise CommandError(f"Error opening {package_json_url}") + else: + raise CommandError(f"Invalid url for package: {package_json_url}") for target_path, short_hash in package_json.get("hashes", ()): fs_target_path = target + "/" + target_path file_url = f"{index}/file/{short_hash[:2]}/{short_hash}" @@ -100,12 +111,7 @@ def _install_json(transport, package_json_url, index, target, version, mpy): def _install_package(transport, package, index, target, version, mpy): - if ( - package.startswith("http://") - or package.startswith("https://") - or package.startswith("github:") - or package.startswith("gitlab:") - ): + if package.startswith(allowed_mip_url_prefixes): if package.endswith(".py") or package.endswith(".mpy"): print(f"Downloading {package} to {target}") _download_file( @@ -118,6 +124,8 @@ def _install_package(transport, package, index, target, version, mpy): package += "/" package += "package.json" print(f"Installing {package} to {target}") + elif package.endswith(".json"): + pass else: if not version: version = "latest" From b11ba39c57ef6dd4d5a865464229a2aede9c825e Mon Sep 17 00:00:00 2001 From: Neil Ludban Date: Sat, 25 Jan 2025 16:00:24 -0500 Subject: [PATCH 0285/2098] rp2/modules: Fix memory leak and logic bug in handling of _pio_funcs. The `rp2` package use a global dict `_pio_funcs` to populate a namespace for `@asm_pio` functions to be executed in. That dict is not cleaned up after use, keeping references to bound methods of a `PIOASMEmit`. By not setting/clearing all the functions, `asm_pio_encode` unintentionally allows the use of the old directives (harmless) as well as `jmp` (in general, produces the wrong output). Fix that by making sure `_pio_funcs` is returned to its original state after using it: - For `@asm_pio` update the target dict from `_pio_funcs` and then set additional functions as needed, leaving `_pio_funcs` unchanged. - For `asm_pio_encode`, borrow `_pio_funcs` to use as globals (avoiding a bunch of memory alloc/free) but delete the instruction entries after use. Signed-off-by: Neil Ludban --- ports/rp2/modules/rp2.py | 91 +++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/ports/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py index e9be7dac8d5..6068926036b 100644 --- a/ports/rp2/modules/rp2.py +++ b/ports/rp2/modules/rp2.py @@ -215,49 +215,46 @@ def set(self, dest, data): # "block": see above "clear": 0x40, "rel": lambda x: x | 0x10, - # functions - "wrap_target": None, - "wrap": None, - "label": None, - "word": None, - "nop": None, - "jmp": None, - "wait": None, - "in_": None, - "out": None, - "push": None, - "pull": None, - "mov": None, - "irq": None, - "set": None, } +_pio_directives = ( + "wrap_target", + "wrap", + "label", +) + + +_pio_instructions = ( + "word", + "nop", + "jmp", + "wait", + "in_", + "out", + "push", + "pull", + "mov", + "irq", + "set", +) + + def asm_pio(**kw): emit = PIOASMEmit(**kw) def dec(f): nonlocal emit - gl = _pio_funcs - gl["wrap_target"] = emit.wrap_target - gl["wrap"] = emit.wrap - gl["label"] = emit.label - gl["word"] = emit.word - gl["nop"] = emit.nop - gl["jmp"] = emit.jmp - gl["wait"] = emit.wait - gl["in_"] = emit.in_ - gl["out"] = emit.out - gl["push"] = emit.push - gl["pull"] = emit.pull - gl["mov"] = emit.mov - gl["irq"] = emit.irq - gl["set"] = emit.set - - old_gl = f.__globals__.copy() - f.__globals__.clear() - f.__globals__.update(gl) + gl = f.__globals__ + old_gl = gl.copy() + gl.clear() + + gl.update(_pio_funcs) + for name in _pio_directives: + gl[name] = getattr(emit, name) + for name in _pio_instructions: + gl[name] = getattr(emit, name) emit.start_pass(0) f() @@ -265,8 +262,8 @@ def dec(f): emit.start_pass(1) f() - f.__globals__.clear() - f.__globals__.update(old_gl) + gl.clear() + gl.update(old_gl) return emit.prog @@ -284,19 +281,15 @@ def asm_pio_encode(instr, sideset_count, sideset_opt=False): emit.num_sideset = 0 gl = _pio_funcs - gl["word"] = emit.word - gl["nop"] = emit.nop - # gl["jmp"] = emit.jmp currently not supported - gl["wait"] = emit.wait - gl["in_"] = emit.in_ - gl["out"] = emit.out - gl["push"] = emit.push - gl["pull"] = emit.pull - gl["mov"] = emit.mov - gl["irq"] = emit.irq - gl["set"] = emit.set - - exec(instr, gl) + for name in _pio_instructions: + gl[name] = getattr(emit, name) + gl["jmp"] = None # emit.jmp currently not supported + + try: + exec(instr, gl) + finally: + for name in _pio_instructions: + del gl[name] if len(emit.prog[_PROG_DATA]) != 1: raise PIOASMError("expecting exactly 1 instruction") From 12dd9cb74598bb0144cb7ac7dcbc5f5631372afa Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 29 Jan 2025 15:18:31 +1100 Subject: [PATCH 0286/2098] docs/esp32: Add documentation for SPI Ethernet devices on esp32 port. Also cross-link with the other WIZNET5K driver, to avoid confusion. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 110 ++++++++++++++++++++++++++---- docs/library/network.LAN.rst | 7 +- docs/library/network.WIZNET5K.rst | 3 + 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index c5c2dfb38e5..0780e013008 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -121,11 +121,20 @@ calling ``wlan.config(reconnects=n)``, where n are the number of desired reconne attempts (0 means it won't retry, -1 will restore the default behaviour of trying to reconnect forever). +.. _esp32_network_lan: + LAN ^^^ -To use the wired interfaces via :class:`network.LAN` one has to specify the pins -and mode :: +Built-in MAC (original ESP32) +""""""""""""""""""""""""""""" + +The original ESP32 SoC has a built-in Ethernet MAC. Using this MAC requires an +external Ethernet PHY to be wired to the chip's EMAC pins. Most of the EMAC pin +assignments are fixed, consult the ESP32 datasheet for details. + +If the PHY is connected, the internal Ethernet MAC can be configured via +the :class:`network.LAN` constructor:: import network @@ -134,20 +143,33 @@ and mode :: lan.ipconfig('addr4') # get the interface's IPv4 addresses -The keyword arguments for the constructor defining the PHY type and interface are: +Required keyword arguments for the constructor: + +- ``mdc`` and ``mdio`` - :class:`machine.Pin` objects (or integers) specifying + the MDC and MDIO pins. +- ``phy_type`` - Select the PHY device type. Supported devices are + ``PHY_LAN8710``, ``PHY_LAN8720``, ``PHY_IP101``, ``PHY_RTL8201``, + ``PHY_DP83848``, ``PHY_KSZ8041`` and ``PHY_KSZ8081``. These values are all + constants defined in the ``network`` module. +- ``phy_addr`` - The address number of the PHY device. Must be an integer in the + range 0x00 to 0x1f, inclusive. Common values are ``0`` and ``1``. + +All of the above keyword arguments must be present to configure the interface. + +Optional keyword arguments: -- mdc=pin-object # set the mdc and mdio pins. -- mdio=pin-object -- reset=pin-object # set the reset pin of the PHY device. -- power=pin-object # set the pin which switches the power of the PHY device. -- phy_type= # Select the PHY device type. Supported devices are PHY_LAN8710, - PHY_LAN8720, PH_IP101, PHY_RTL8201, PHY_DP83848 and PHY_KSZ8041 -- phy_addr=number # The address number of the PHY device. -- ref_clk_mode=mode # Defines, whether the ref_clk at the ESP32 is an input - or output. Suitable values are Pin.IN and Pin.OUT. -- ref_clk=pin-object # defines the Pin used for ref_clk. +- ``reset`` - :class:`machine.Pin` object (or integer) specifying the PHY reset pin. +- ``power`` - :class:`machine.Pin` object (or integer) specifying a pin which + switches the power of the PHY device. +- ``ref_clk`` - :class:`machine.Pin` object (or integer) specifying the pin used + for the EMAC ``ref_clk`` signal. If not specified, the board default is used + (typically GPIO 0, but may be different if a particular board has Ethernet.) +- ``ref_clk_mode`` - Defines whether the EMAC ``ref_clk`` pin of the ESP32 + should be an input or an output. Suitable values are ``machine.Pin.IN`` and + ``machine.Pin.OUT``. If not specified, the board default is used + (typically input, but may be different if a particular board has Ethernet.) -These are working configurations for LAN interfaces of popular boards:: +These are working configurations for LAN interfaces of some popular ESP32 boards:: # Olimex ESP32-GATEWAY: power controlled by Pin(5) # Olimex ESP32 PoE and ESP32-PoE ISO: power controlled by Pin(12) @@ -172,6 +194,66 @@ These are working configurations for LAN interfaces of popular boards:: lan = network.LAN(id=0, mdc=Pin(23), mdio=Pin(18), power=Pin(5), phy_type=network.PHY_IP101, phy_addr=1) + +.. _esp32_spi_ethernet: + +SPI Ethernet Interface +"""""""""""""""""""""" + +All ESP32 SoCs support external SPI Ethernet interface chips. These are Ethernet +interfaces that connect via a SPI bus, rather than an Ethernet RMII interface. + +.. note:: The only exception is the ESP32 ``d2wd`` variant, where this feature is disabled + to save code size. + +SPI Ethernet uses the same :class:`network.LAN` constructor, with a different +set of keyword arguments:: + + import machine, network + + spi = machine.SPI(1, sck=SCK_PIN, mosi=MOSI_PIN, miso=MISO_PIN) + lan = network.LAN(spi=spi, cs=CS_PIN, ...) # Set the pin and mode configuration + lan.active(True) # activate the interface + lan.ipconfig('addr4') # get the interface's IPv4 addresses + +Required keyword arguments for the constructor: + +- ``spi`` - Should be a :class:`machine.SPI` object configured for this + connection. Note that any clock speed configured on the SPI object is ignored, + the SPI Ethernet clock speed is configured at compile time. +- ``cs`` - :class:`machine.Pin` object (or integer) specifying the CS pin + connected to the interface. +- ``int`` - :class:`machine.Pin` object (or integer) specifying the INT pin + connected to the interface. +- ``phy_type`` - Select the SPI Ethernet interface type. Supported devices are + ``PHY_KSZ8851SNL``, ``PHY_DM9051``, ``PHY_W5500``. These values are all + constants defined in the ``network`` module. +- ``phy_addr`` - The address number of the PHY device. Must be an integer in the + range 0x00 to 0x1f, inclusive. This is usually ``0`` for SPI Ethernet devices. + +All of the above keyword arguments must be present to configure the interface. + +Optional keyword arguments for the constructor: + +- ``reset`` - :class:`machine.Pin` object (or integer) specifying the SPI Ethernet + interface reset pin. +- ``power`` - :class:`machine.Pin` object (or integer) specifying a pin which + switches the power of the SPI Ethernet interface. + +Here is a sample configuration for a WIZNet W5500 chip connected to pins on +an ESP32-S3 development board:: + + import machine, network + from machine import Pin, SPI + + spi = SPI(1, sck=Pin(12), mosi=Pin(13), miso=Pin(14)) + lan = network.LAN(spi=spi, phy_type=network.PHY_W5500, phy_addr=0, + cs=Pin(10), int=Pin(11)) + +.. note:: WIZnet W5500 Ethernet is also supported on some other MicroPython + ports, but using a :ref:`different software interface + `. + Delay and timing ---------------- diff --git a/docs/library/network.LAN.rst b/docs/library/network.LAN.rst index 31ce8e98411..1b3c19c560b 100644 --- a/docs/library/network.LAN.rst +++ b/docs/library/network.LAN.rst @@ -6,7 +6,7 @@ class LAN -- control an Ethernet module This class allows you to control the Ethernet interface. The PHY hardware type is board-specific. -Example usage:: +Example usage, for a board with built-in LAN support:: import network nic = network.LAN(0) @@ -32,7 +32,7 @@ Constructors - *phy_addr* specifies the address of the PHY interface. As with *phy_type*, the hardwired value has to be used for most boards and that value is the default. - *ref_clk_mode* specifies, whether the data clock is provided by the Ethernet controller or - the PYH interface. + the PHY interface. The default value is the one that matches the board. If set to ``LAN.OUT`` or ``Pin.OUT`` or ``True``, the clock is driven by the Ethernet controller, if set to ``LAN.IN`` or ``Pin.IN`` or ``False``, the clock is driven by the PHY interface. @@ -41,6 +41,9 @@ Constructors nic = LAN(0, phy_type=LAN.PHY_LAN8720, phy_addr=1, ref_clk_mode=Pin.IN) + .. note:: On esp32 port the constructor requires different arguments. See + :ref:`esp32 port reference `. + Methods ------- diff --git a/docs/library/network.WIZNET5K.rst b/docs/library/network.WIZNET5K.rst index a19bd452823..d0778a7a37c 100644 --- a/docs/library/network.WIZNET5K.rst +++ b/docs/library/network.WIZNET5K.rst @@ -9,6 +9,9 @@ the W5200 and W5500 chipsets. The particular chipset that is supported by the firmware is selected at compile-time via the MICROPY_PY_NETWORK_WIZNET5K option. +.. note:: The esp32 port also supports WIZnet W5500 chipsets, but this port + uses the :ref:`network.LAN interface `. + Example usage:: import network From bab099826e956bcc000b8a3c45b144c97d870fe2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 28 Jan 2025 11:54:57 +1100 Subject: [PATCH 0287/2098] docs: Note which ports have default or optional network.PPP support. Also add the default values of these macros to the respective `mpconfigport.h` files, to improve discoverability. Signed-off-by: Angus Gratton --- docs/library/network.PPP.rst | 7 ++++++- ports/rp2/mpconfigport.h | 4 ++++ ports/stm32/mpconfigport.h | 14 +++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/library/network.PPP.rst b/docs/library/network.PPP.rst index 17a8d1c1cd8..15ab1da1934 100644 --- a/docs/library/network.PPP.rst +++ b/docs/library/network.PPP.rst @@ -5,7 +5,12 @@ 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. +the PPP protocol. + +.. note:: Currently only the esp32 port has PPP support enabled in the default + firmware build. PPP support can be enabled in custom builds of the + stm32 and rp2 ports by enabling networking support and setting + ``MICROPY_PY_NETWORK_PPP_LWIP`` to 1. Example usage:: diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index bc289bcbf97..fe8287ba1d6 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -212,6 +212,10 @@ #ifndef MICROPY_PY_WEBREPL #define MICROPY_PY_WEBREPL (1) #endif + +#ifndef MICROPY_PY_NETWORK_PPP_LWIP +#define MICROPY_PY_NETWORK_PPP_LWIP (0) +#endif #endif #if MICROPY_PY_NETWORK_CYW43 diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index dc0a767fb07..2b57446ee82 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -146,9 +146,6 @@ #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (HAL_RCC_GetSysClockFreq() / 48) #define MICROPY_PY_WEBSOCKET (MICROPY_PY_LWIP) #define MICROPY_PY_WEBREPL (MICROPY_PY_LWIP) -#ifndef MICROPY_PY_SOCKET -#define MICROPY_PY_SOCKET (1) -#endif #ifndef MICROPY_PY_NETWORK #define MICROPY_PY_NETWORK (1) #endif @@ -156,6 +153,17 @@ #define MICROPY_PY_ONEWIRE (1) #endif +// optional network features +#if MICROPY_PY_NETWORK +#ifndef MICROPY_PY_SOCKET +#define MICROPY_PY_SOCKET (1) +#endif + +#ifndef MICROPY_PY_NETWORK_PPP_LWIP +#define MICROPY_PY_NETWORK_PPP_LWIP (0) +#endif +#endif + // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (2) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ From 0a55f1f40c8a0a69f34f550d45138d9ba7a1d467 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Feb 2025 12:20:38 +1100 Subject: [PATCH 0288/2098] docs/reference: Add strings vs bytes to speed optimisation tips. Also add some additional context links, suggestions for alternative classes, etc. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/array.rst | 4 ++++ docs/library/builtins.rst | 4 ++++ docs/reference/speed_python.rst | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/library/array.rst b/docs/library/array.rst index f417a7046e2..957260c2c7f 100644 --- a/docs/library/array.rst +++ b/docs/library/array.rst @@ -19,6 +19,10 @@ Classes array are given by *iterable*. If it is not provided, an empty array is created. + In addition to the methods below, array objects also implement the buffer + protocol. This means the contents of the entire array can be accessed as raw + bytes via a `memoryview` or other interfaces which use this protocol. + .. method:: append(val) Append new element *val* to the end of array, growing it. diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index 5956aea7ab2..b5d08ba7fed 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -19,6 +19,8 @@ Functions and types .. class:: bytearray() + |see_cpython| `python:bytearray`. + .. class:: bytes() |see_cpython| `python:bytes`. @@ -104,6 +106,8 @@ Functions and types .. class:: memoryview() + |see_cpython| `python:memoryview`. + .. function:: min() .. function:: next() diff --git a/docs/reference/speed_python.rst b/docs/reference/speed_python.rst index 0c68dd60bde..a660432f6da 100644 --- a/docs/reference/speed_python.rst +++ b/docs/reference/speed_python.rst @@ -57,6 +57,8 @@ and used in various methods. This is covered in further detail :ref:`Controlling garbage collection ` below. +.. _speed_buffers: + Buffers ~~~~~~~ @@ -69,6 +71,13 @@ example, objects which support stream interface (e.g., file or UART) provide ``r method which allocates new buffer for read data, but also a ``readinto()`` method to read data into an existing buffer. +Some useful classes for creating reusable buffer objects: + +- :class:`bytearray` +- :mod:`array` (:ref:`discussed below`) +- :class:`io.StringIO` and :class:`io.BytesIO` +- :class:`micropython.RingIO` + Floating point ~~~~~~~~~~~~~~ @@ -80,15 +89,20 @@ point to sections of the code where performance is not paramount. For example, capture ADC readings as integers values to an array in one quick go, and only then convert them to floating-point numbers for signal processing. +.. _speed_arrays: + Arrays ~~~~~~ Consider the use of the various types of array classes as an alternative to lists. -The `array` module supports various element types with 8-bit elements supported +The :mod:`array` module supports various element types with 8-bit elements supported by Python's built in `bytes` and `bytearray` classes. These data structures all store elements in contiguous memory locations. Once again to avoid memory allocation in critical code these should be pre-allocated and passed as arguments or as bound objects. +Memoryviews +~~~~~~~~~~~ + When passing slices of objects such as `bytearray` instances, Python creates a copy which involves allocation of the size proportional to the size of slice. This can be alleviated using a `memoryview` object. The `memoryview` itself @@ -118,6 +132,23 @@ of buffer and fills in entire buffer. What if you need to put data in the middle of existing buffer? Just create a memoryview into the needed section of buffer and pass it to ``readinto()``. +Strings vs Bytes +~~~~~~~~~~~~~~~~ + +MicroPython uses :ref:`string interning ` to save space when there are +multiple identical strings. Each time a new string is allocated at runtime (for +example, when two other strings are concatenated), MicroPython checks whether +the new string can be interned to save RAM. + +If you have code which performs performance-critical string operations then +consider using :class:`bytes` objects and literals (i.e. ``b"abc"``). This skips +the interning check, and can be several times faster than performing the same +operations with string objects. + +.. note:: The fastest performance will always be achieved by avoiding new object + creation entirely, for example with a reusable :ref:`buffer as described + above`. + Identifying the slowest section of code --------------------------------------- From 3b6252466139d5dfc7458eeea5d759b6c9850146 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 10 Feb 2025 17:20:29 +0100 Subject: [PATCH 0289/2098] docs/library/espnow: Clarify usage of the "rate" configuration key. This commit adds a clarification for the ESPNow module's documentation regarding its "config" method. The original documentation for that method could be interpreted as having all its configuration keys being able to be queried, but the "rate" configuration key is actually write-only due to ESP-IDF's lack of a way to retrieve that bit of information from the radio's configuration. The documentation changes highlight the fact that said configuration key is actually write-only. Signed-off-by: Alessandro Gatti --- docs/library/espnow.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index 379e60486f2..84e9e9465de 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -164,11 +164,13 @@ Configuration wait forever. The timeout can also be provided as arg to `recv()`/`irecv()`/`recvinto()`. - *rate*: (ESP32 only, IDF>=4.3.0 only) Set the transmission speed for + *rate*: (ESP32 only) Set the transmission speed for ESPNow packets. Must be set to a number from the allowed numeric values in `enum wifi_phy_rate_t - `_. + `_. This + parameter is actually *write-only* due to ESP-IDF not providing any + means for querying the radio interface's rate parameter. .. data:: Returns: From 372ecfef02eccc4e52a5d0abef068c7d25fc4315 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 21 Jan 2025 00:56:59 +1100 Subject: [PATCH 0290/2098] tests/run-tests.py: Give more information when CPython crashes. To make it easier to diagnose why CPython crashed. Signed-off-by: Damien George --- tests/run-tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 1df1b35d23b..b108b163950 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -955,8 +955,8 @@ def run_one_test(test_file): cwd=os.path.dirname(test_file), stderr=subprocess.STDOUT, ) - except subprocess.CalledProcessError: - output_expected = b"CPYTHON3 CRASH" + except subprocess.CalledProcessError as er: + output_expected = b"CPYTHON3 CRASH:\n" + er.output # Canonical form for all host platforms is to use \n for end-of-line. output_expected = output_expected.replace(b"\r\n", b"\n") From 62e821ccb82fd8362a8198ad59ccb51b8a5c441e Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 19 Jan 2025 23:30:59 +1100 Subject: [PATCH 0291/2098] py/objcode: Factor code object out into its own file. The `mp_obj_code_t` and `mp_type_code` code object was defined internally in both `py/builtinevex.c` and `py/profile.c`, with completely different implementations (the former very minimal, the latter quite complete). This commit factors these implementations into a new, separate source file, and allows the code object to have four different modes, selected at compile-time: - MICROPY_PY_BUILTINS_CODE_NONE: code object not included in the build. - MICROPY_PY_BUILTINS_CODE_MINIMUM: very simple code object that just holds a reference to the function that it represents. This level is used when MICROPY_PY_BUILTINS_COMPILE is enabled. - MICROPY_PY_BUILTINS_CODE_BASIC: simple code object that holds a reference to the proto-function and its constants. - MICROPY_PY_BUILTINS_CODE_FULL: almost complete implementation of the code object. This level is used when MICROPY_PY_SYS_SETTRACE is enabled. Signed-off-by: Damien George --- py/builtinevex.c | 51 +++++++++----- py/compile.c | 2 +- py/compile.h | 5 +- py/mpconfig.h | 9 +++ py/obj.h | 1 + py/objcode.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++ py/objcode.h | 99 +++++++++++++++++++++++++++ py/profile.c | 140 ++----------------------------------- py/profile.h | 12 +--- py/py.cmake | 1 + py/py.mk | 1 + py/runtime.c | 7 +- 12 files changed, 337 insertions(+), 166 deletions(-) create mode 100644 py/objcode.c create mode 100644 py/objcode.h diff --git a/py/builtinevex.c b/py/builtinevex.c index e25cbd4d085..74a46404926 100644 --- a/py/builtinevex.c +++ b/py/builtinevex.c @@ -26,6 +26,7 @@ #include +#include "py/objcode.h" #include "py/objfun.h" #include "py/compile.h" #include "py/runtime.h" @@ -33,17 +34,6 @@ #if MICROPY_PY_BUILTINS_COMPILE -typedef struct _mp_obj_code_t { - mp_obj_base_t base; - mp_obj_t module_fun; -} mp_obj_code_t; - -static MP_DEFINE_CONST_OBJ_TYPE( - mp_type_code, - MP_QSTR_code, - MP_TYPE_FLAG_NONE - ); - static mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj_dict_t *locals) { // save context nlr_jump_callback_node_globals_locals_t ctx; @@ -57,19 +47,28 @@ static mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj // set exception handler to restore context if an exception is raised nlr_push_jump_callback(&ctx.callback, mp_globals_locals_set_from_nlr_jump_callback); + #if MICROPY_PY_BUILTINS_CODE >= MICROPY_PY_BUILTINS_CODE_BASIC + mp_module_context_t *module_context = m_new_obj(mp_module_context_t); + module_context->module.base.type = &mp_type_module; + module_context->module.globals = globals; + module_context->constants = *mp_code_get_constants(self); + mp_obj_t module_fun = mp_make_function_from_proto_fun(mp_code_get_proto_fun(self), module_context, NULL); + #else // The call to mp_parse_compile_execute() in mp_builtin_compile() below passes // NULL for the globals, so repopulate that entry now with the correct globals. + mp_obj_t module_fun = self->module_fun; if (mp_obj_is_type(self->module_fun, &mp_type_fun_bc) #if MICROPY_EMIT_NATIVE || mp_obj_is_type(self->module_fun, &mp_type_fun_native) #endif ) { - mp_obj_fun_bc_t *fun_bc = MP_OBJ_TO_PTR(self->module_fun); + mp_obj_fun_bc_t *fun_bc = MP_OBJ_TO_PTR(module_fun); ((mp_module_context_t *)fun_bc->context)->module.globals = globals; } + #endif // execute code - mp_obj_t ret = mp_call_function_0(self->module_fun); + mp_obj_t ret = mp_call_function_0(module_fun); // deregister exception handler and restore context nlr_pop_jump_callback(true); @@ -108,9 +107,29 @@ static mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("bad compile mode")); } - mp_obj_code_t *code = mp_obj_malloc(mp_obj_code_t, &mp_type_code); - code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); - return MP_OBJ_FROM_PTR(code); + #if MICROPY_PY_BUILTINS_CODE >= MICROPY_PY_BUILTINS_CODE_BASIC + + mp_parse_tree_t parse_tree = mp_parse(lex, parse_input_kind); + mp_module_context_t ctx; + ctx.module.globals = NULL; + mp_compiled_module_t cm; + cm.context = &ctx; + mp_compile_to_raw_code(&parse_tree, lex->source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT, &cm); + + #if MICROPY_PY_BUILTINS_CODE >= MICROPY_PY_BUILTINS_CODE_FULL + mp_module_context_t *ctx_ptr = m_new_obj(mp_module_context_t); + *ctx_ptr = ctx; + return mp_obj_new_code(ctx_ptr, cm.rc, true); + #else + return mp_obj_new_code(ctx.constants, cm.rc); + #endif + + #else + + mp_obj_t module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); + return mp_obj_new_code(module_fun); + + #endif } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_compile_obj, 3, 6, mp_builtin_compile); diff --git a/py/compile.c b/py/compile.c index 60f06d7773e..7a1151bcd66 100644 --- a/py/compile.c +++ b/py/compile.c @@ -3467,7 +3467,7 @@ static void scope_compute_things(scope_t *scope) { } } -#if !MICROPY_PERSISTENT_CODE_SAVE +#if !MICROPY_EXPOSE_MP_COMPILE_TO_RAW_CODE static #endif void mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl, mp_compiled_module_t *cm) { diff --git a/py/compile.h b/py/compile.h index f9970a521d6..64afc487d7c 100644 --- a/py/compile.h +++ b/py/compile.h @@ -30,6 +30,9 @@ #include "py/parse.h" #include "py/emitglue.h" +// Whether mp_compile_to_raw_code is exposed as a public function. +#define MICROPY_EXPOSE_MP_COMPILE_TO_RAW_CODE (MICROPY_PY_BUILTINS_CODE >= MICROPY_PY_BUILTINS_CODE_BASIC || MICROPY_PERSISTENT_CODE_SAVE) + #if MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT // set to `true` to allow top-level await expressions extern bool mp_compile_allow_top_level_await; @@ -40,7 +43,7 @@ extern bool mp_compile_allow_top_level_await; // mp_globals_get() will be used for the context mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl); -#if MICROPY_PERSISTENT_CODE_SAVE +#if MICROPY_EXPOSE_MP_COMPILE_TO_RAW_CODE // this has the same semantics as mp_compile void mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl, mp_compiled_module_t *cm); #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index 64138a9ea7a..76aff4681d3 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1129,6 +1129,15 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_BYTEARRAY (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif +// Whether to support code objects, and how many features they have +#define MICROPY_PY_BUILTINS_CODE_NONE (0) +#define MICROPY_PY_BUILTINS_CODE_MINIMUM (1) +#define MICROPY_PY_BUILTINS_CODE_BASIC (2) +#define MICROPY_PY_BUILTINS_CODE_FULL (3) +#ifndef MICROPY_PY_BUILTINS_CODE +#define MICROPY_PY_BUILTINS_CODE (MICROPY_PY_SYS_SETTRACE ? MICROPY_PY_BUILTINS_CODE_FULL : (MICROPY_PY_BUILTINS_COMPILE ? MICROPY_PY_BUILTINS_CODE_MINIMUM : MICROPY_PY_BUILTINS_CODE_NONE)) +#endif + // Whether to support dict.fromkeys() class method #ifndef MICROPY_PY_BUILTINS_DICT_FROMKEYS #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) diff --git a/py/obj.h b/py/obj.h index bd31ef11d09..93d1e0ee343 100644 --- a/py/obj.h +++ b/py/obj.h @@ -840,6 +840,7 @@ extern const mp_obj_type_t mp_type_fun_bc; extern const mp_obj_type_t mp_type_fun_native; extern const mp_obj_type_t mp_type_fun_viper; extern const mp_obj_type_t mp_type_fun_asm; +extern const mp_obj_type_t mp_type_code; extern const mp_obj_type_t mp_type_module; extern const mp_obj_type_t mp_type_staticmethod; extern const mp_obj_type_t mp_type_classmethod; diff --git a/py/objcode.c b/py/objcode.c new file mode 100644 index 00000000000..9b98a696798 --- /dev/null +++ b/py/objcode.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/objcode.h" + +#if MICROPY_PY_BUILTINS_CODE == MICROPY_PY_BUILTINS_CODE_NONE + +// Code object not implemented at this configuration level. + +#elif MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_code, + MP_QSTR_code, + MP_TYPE_FLAG_NONE + ); + +#elif MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_FULL + +#include "py/profile.h" + +static void code_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_code_t *o = MP_OBJ_TO_PTR(o_in); + const mp_raw_code_t *rc = o->rc; + const mp_bytecode_prelude_t *prelude = &rc->prelude; + mp_printf(print, + "", + MP_CODE_QSTR_MAP(o->context, prelude->qstr_block_name_idx), + o, + MP_CODE_QSTR_MAP(o->context, 0), + rc->line_of_definition + ); +} + +static mp_obj_tuple_t *code_consts(const mp_module_context_t *context, const mp_raw_code_t *rc) { + mp_obj_tuple_t *consts = MP_OBJ_TO_PTR(mp_obj_new_tuple(rc->n_children + 1, NULL)); + + size_t const_no = 0; + for (size_t i = 0; i < rc->n_children; ++i) { + mp_obj_t code = mp_obj_new_code(context, rc->children[i], true); + consts->items[const_no++] = code; + } + consts->items[const_no++] = mp_const_none; + + return consts; +} + +static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { + // const mp_bytecode_prelude_t *prelude = &rc->prelude; + uint start = 0; + uint stop = rc->fun_data_len - start; + + uint last_lineno = mp_prof_bytecode_lineno(rc, start); + uint lasti = 0; + + const uint buffer_chunk_size = (stop - start) >> 2; // heuristic magic + uint buffer_size = buffer_chunk_size; + byte *buffer = m_new(byte, buffer_size); + uint buffer_index = 0; + + for (uint i = start; i < stop; ++i) { + uint lineno = mp_prof_bytecode_lineno(rc, i); + size_t line_diff = lineno - last_lineno; + if (line_diff > 0) { + uint instr_diff = (i - start) - lasti; + + assert(instr_diff < 256); + assert(line_diff < 256); + + if (buffer_index + 2 > buffer_size) { + buffer = m_renew(byte, buffer, buffer_size, buffer_size + buffer_chunk_size); + buffer_size = buffer_size + buffer_chunk_size; + } + last_lineno = lineno; + lasti = i - start; + buffer[buffer_index++] = instr_diff; + buffer[buffer_index++] = line_diff; + } + } + + mp_obj_t o = mp_obj_new_bytes(buffer, buffer_index); + m_del(byte, buffer, buffer_size); + return o; +} + +static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // not load attribute + return; + } + mp_obj_code_t *o = MP_OBJ_TO_PTR(self_in); + const mp_raw_code_t *rc = o->rc; + const mp_bytecode_prelude_t *prelude = &rc->prelude; + switch (attr) { + case MP_QSTR_co_code: + dest[0] = mp_obj_new_bytes( + (void *)prelude->opcodes, + rc->fun_data_len - (prelude->opcodes - (const byte *)rc->fun_data) + ); + break; + case MP_QSTR_co_consts: + dest[0] = MP_OBJ_FROM_PTR(code_consts(o->context, rc)); + break; + case MP_QSTR_co_filename: + dest[0] = MP_OBJ_NEW_QSTR(MP_CODE_QSTR_MAP(o->context, 0)); + break; + case MP_QSTR_co_firstlineno: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_prof_bytecode_lineno(rc, 0)); + break; + case MP_QSTR_co_name: + dest[0] = MP_OBJ_NEW_QSTR(MP_CODE_QSTR_MAP(o->context, prelude->qstr_block_name_idx)); + break; + case MP_QSTR_co_names: + dest[0] = MP_OBJ_FROM_PTR(o->dict_locals); + break; + case MP_QSTR_co_lnotab: + if (!o->lnotab) { + o->lnotab = raw_code_lnotab(rc); + } + dest[0] = o->lnotab; + break; + } +} + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_code, + MP_QSTR_code, + MP_TYPE_FLAG_NONE, + print, code_print, + attr, code_attr + ); + +mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc, bool result_required) { + mp_obj_code_t *o; + if (result_required) { + o = m_new_obj(mp_obj_code_t); + } else { + o = m_new_obj_maybe(mp_obj_code_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + } + o->base.type = &mp_type_code; + o->context = context; + o->rc = rc; + o->dict_locals = mp_locals_get(); // this is a wrong! how to do this properly? + o->lnotab = MP_OBJ_NULL; + return MP_OBJ_FROM_PTR(o); +} + +#endif diff --git a/py/objcode.h b/py/objcode.h new file mode 100644 index 00000000000..8db9a34b6e1 --- /dev/null +++ b/py/objcode.h @@ -0,0 +1,99 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ +#ifndef MICROPY_INCLUDED_PY_OBJCODE_H +#define MICROPY_INCLUDED_PY_OBJCODE_H + +#include "py/bc.h" + +#if MICROPY_PY_BUILTINS_CODE == MICROPY_PY_BUILTINS_CODE_NONE + +// Code object not implemented at this configuration level. + +#elif MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_MINIMUM + +typedef struct _mp_obj_code_t { + mp_obj_base_t base; + mp_obj_t module_fun; +} mp_obj_code_t; + +static inline mp_obj_t mp_obj_new_code(mp_obj_t module_fun) { + mp_obj_code_t *code = mp_obj_malloc(mp_obj_code_t, &mp_type_code); + code->module_fun = module_fun; + return MP_OBJ_FROM_PTR(code); +} + +#elif MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC + +typedef struct _mp_obj_code_t { + mp_obj_base_t base; + mp_module_constants_t constants; + const void *proto_fun; +} mp_obj_code_t; + +static inline mp_obj_t mp_obj_new_code(const mp_module_constants_t constants, const void *proto_fun) { + mp_obj_code_t *code = mp_obj_malloc(mp_obj_code_t, &mp_type_code); + code->constants = constants; + code->proto_fun = proto_fun; + return MP_OBJ_FROM_PTR(code); +} + +static inline const mp_module_constants_t *mp_code_get_constants(mp_obj_code_t *self) { + return &self->constants; +} + +static inline const void *mp_code_get_proto_fun(mp_obj_code_t *self) { + return self->proto_fun; +} + +#elif MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_FULL + +#include "py/emitglue.h" + +#define MP_CODE_QSTR_MAP(context, idx) (context->constants.qstr_table[idx]) + +typedef struct _mp_obj_code_t { + // TODO this was 4 words + mp_obj_base_t base; + const mp_module_context_t *context; + const mp_raw_code_t *rc; + mp_obj_dict_t *dict_locals; + mp_obj_t lnotab; +} mp_obj_code_t; + +mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc, bool result_required); + +static inline const mp_module_constants_t *mp_code_get_constants(mp_obj_code_t *self) { + return &self->context->constants; +} + +static inline const void *mp_code_get_proto_fun(mp_obj_code_t *self) { + // A mp_raw_code_t is always a proto_fun (but not the other way around). + return self->rc; +} + +#endif + +#endif // MICROPY_INCLUDED_PY_OBJCODE_H diff --git a/py/profile.c b/py/profile.c index 92f414ace7c..397d0291f9f 100644 --- a/py/profile.c +++ b/py/profile.c @@ -38,9 +38,8 @@ #endif #define prof_trace_cb MP_STATE_THREAD(prof_trace_callback) -#define QSTR_MAP(context, idx) (context->constants.qstr_table[idx]) -static uint mp_prof_bytecode_lineno(const mp_raw_code_t *rc, size_t bc) { +uint mp_prof_bytecode_lineno(const mp_raw_code_t *rc, size_t bc) { const mp_bytecode_prelude_t *prelude = &rc->prelude; return mp_bytecode_get_source_line(prelude->line_info, prelude->line_info_top, bc); } @@ -68,137 +67,6 @@ void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelud prelude->line_info = ip; } -/******************************************************************************/ -// code object - -static void code_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { - (void)kind; - mp_obj_code_t *o = MP_OBJ_TO_PTR(o_in); - const mp_raw_code_t *rc = o->rc; - const mp_bytecode_prelude_t *prelude = &rc->prelude; - mp_printf(print, - "", - QSTR_MAP(o->context, prelude->qstr_block_name_idx), - o, - QSTR_MAP(o->context, 0), - rc->line_of_definition - ); -} - -static mp_obj_tuple_t *code_consts(const mp_module_context_t *context, const mp_raw_code_t *rc) { - mp_obj_tuple_t *consts = MP_OBJ_TO_PTR(mp_obj_new_tuple(rc->n_children + 1, NULL)); - - size_t const_no = 0; - for (size_t i = 0; i < rc->n_children; ++i) { - mp_obj_t code = mp_obj_new_code(context, rc->children[i]); - if (code == MP_OBJ_NULL) { - m_malloc_fail(sizeof(mp_obj_code_t)); - } - consts->items[const_no++] = code; - } - consts->items[const_no++] = mp_const_none; - - return consts; -} - -static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { - // const mp_bytecode_prelude_t *prelude = &rc->prelude; - uint start = 0; - uint stop = rc->fun_data_len - start; - - uint last_lineno = mp_prof_bytecode_lineno(rc, start); - uint lasti = 0; - - const uint buffer_chunk_size = (stop - start) >> 2; // heuristic magic - uint buffer_size = buffer_chunk_size; - byte *buffer = m_new(byte, buffer_size); - uint buffer_index = 0; - - for (uint i = start; i < stop; ++i) { - uint lineno = mp_prof_bytecode_lineno(rc, i); - size_t line_diff = lineno - last_lineno; - if (line_diff > 0) { - uint instr_diff = (i - start) - lasti; - - assert(instr_diff < 256); - assert(line_diff < 256); - - if (buffer_index + 2 > buffer_size) { - buffer = m_renew(byte, buffer, buffer_size, buffer_size + buffer_chunk_size); - buffer_size = buffer_size + buffer_chunk_size; - } - last_lineno = lineno; - lasti = i - start; - buffer[buffer_index++] = instr_diff; - buffer[buffer_index++] = line_diff; - } - } - - mp_obj_t o = mp_obj_new_bytes(buffer, buffer_index); - m_del(byte, buffer, buffer_size); - return o; -} - -static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - if (dest[0] != MP_OBJ_NULL) { - // not load attribute - return; - } - mp_obj_code_t *o = MP_OBJ_TO_PTR(self_in); - const mp_raw_code_t *rc = o->rc; - const mp_bytecode_prelude_t *prelude = &rc->prelude; - switch (attr) { - case MP_QSTR_co_code: - dest[0] = mp_obj_new_bytes( - (void *)prelude->opcodes, - rc->fun_data_len - (prelude->opcodes - (const byte *)rc->fun_data) - ); - break; - case MP_QSTR_co_consts: - dest[0] = MP_OBJ_FROM_PTR(code_consts(o->context, rc)); - break; - case MP_QSTR_co_filename: - dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, 0)); - break; - case MP_QSTR_co_firstlineno: - dest[0] = MP_OBJ_NEW_SMALL_INT(mp_prof_bytecode_lineno(rc, 0)); - break; - case MP_QSTR_co_name: - dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, prelude->qstr_block_name_idx)); - break; - case MP_QSTR_co_names: - dest[0] = MP_OBJ_FROM_PTR(o->dict_locals); - break; - case MP_QSTR_co_lnotab: - if (!o->lnotab) { - o->lnotab = raw_code_lnotab(rc); - } - dest[0] = o->lnotab; - break; - } -} - -MP_DEFINE_CONST_OBJ_TYPE( - mp_type_settrace_codeobj, - MP_QSTR_code, - MP_TYPE_FLAG_NONE, - print, code_print, - attr, code_attr - ); - -mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc) { - mp_obj_code_t *o = m_new_obj_maybe(mp_obj_code_t); - if (o == NULL) { - return MP_OBJ_NULL; - } - o->base.type = &mp_type_settrace_codeobj; - o->context = context; - o->rc = rc; - o->dict_locals = mp_locals_get(); // this is a wrong! how to do this properly? - o->lnotab = MP_OBJ_NULL; - return MP_OBJ_FROM_PTR(o); -} - /******************************************************************************/ // frame object @@ -211,9 +79,9 @@ static void frame_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t mp_printf(print, "", frame, - QSTR_MAP(code->context, 0), + MP_CODE_QSTR_MAP(code->context, 0), frame->lineno, - QSTR_MAP(code->context, prelude->qstr_block_name_idx) + MP_CODE_QSTR_MAP(code->context, prelude->qstr_block_name_idx) ); } @@ -265,7 +133,7 @@ mp_obj_t mp_obj_new_frame(const mp_code_state_t *code_state) { return MP_OBJ_NULL; } - mp_obj_code_t *code = o->code = MP_OBJ_TO_PTR(mp_obj_new_code(code_state->fun_bc->context, code_state->fun_bc->rc)); + mp_obj_code_t *code = o->code = MP_OBJ_TO_PTR(mp_obj_new_code(code_state->fun_bc->context, code_state->fun_bc->rc, false)); if (code == NULL) { return MP_OBJ_NULL; } diff --git a/py/profile.h b/py/profile.h index 7f3f9140346..db72b9f0768 100644 --- a/py/profile.h +++ b/py/profile.h @@ -28,20 +28,12 @@ #define MICROPY_INCLUDED_PY_PROFILING_H #include "py/emitglue.h" +#include "py/objcode.h" #if MICROPY_PY_SYS_SETTRACE #define mp_prof_is_executing MP_STATE_THREAD(prof_callback_is_executing) -typedef struct _mp_obj_code_t { - // TODO this was 4 words - mp_obj_base_t base; - const mp_module_context_t *context; - const mp_raw_code_t *rc; - mp_obj_dict_t *dict_locals; - mp_obj_t lnotab; -} mp_obj_code_t; - typedef struct _mp_obj_frame_t { mp_obj_base_t base; const mp_code_state_t *code_state; @@ -53,9 +45,9 @@ typedef struct _mp_obj_frame_t { bool trace_opcodes; } mp_obj_frame_t; +uint mp_prof_bytecode_lineno(const mp_raw_code_t *rc, size_t bc); void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelude); -mp_obj_t mp_obj_new_code(const mp_module_context_t *mc, const mp_raw_code_t *rc); mp_obj_t mp_obj_new_frame(const mp_code_state_t *code_state); // This is the implementation for the sys.settrace diff --git a/py/py.cmake b/py/py.cmake index 0fee74ddcb4..1c81ed4c58f 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -72,6 +72,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/objattrtuple.c ${MICROPY_PY_DIR}/objbool.c ${MICROPY_PY_DIR}/objboundmeth.c + ${MICROPY_PY_DIR}/objcode.c ${MICROPY_PY_DIR}/objcell.c ${MICROPY_PY_DIR}/objclosure.c ${MICROPY_PY_DIR}/objcomplex.c diff --git a/py/py.mk b/py/py.mk index c0b7e1ac8b9..e352d89792b 100644 --- a/py/py.mk +++ b/py/py.mk @@ -149,6 +149,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ objboundmeth.o \ objcell.o \ objclosure.o \ + objcode.o \ objcomplex.o \ objdeque.o \ objdict.o \ diff --git a/py/runtime.c b/py/runtime.c index deb55bf283a..bf26921f9ab 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1620,10 +1620,13 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i mp_obj_t module_fun = mp_compile(&parse_tree, source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT); mp_obj_t ret; - if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { + #if MICROPY_PY_BUILTINS_COMPILE && MICROPY_PY_BUILTINS_CODE == MICROPY_PY_BUILTINS_CODE_MINIMUM + if (globals == NULL) { // for compile only, return value is the module function ret = module_fun; - } else { + } else + #endif + { // execute module function and get return value ret = mp_call_function_0(module_fun); } From ceb8ba60b4fea0c32e4977d0e45d5c0203b27b34 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 20 Jan 2025 22:23:48 +1100 Subject: [PATCH 0292/2098] py/objfun: Implement function.__code__ and function constructor. This allows retrieving the code object of a function using `function.__code__`, and then reconstructing a function from a code object using `FunctionType(code_object)`. This feature is controlled by `MICROPY_PY_FUNCTION_ATTRS_CODE` and is enabled at the full-features level. Signed-off-by: Damien George --- py/mpconfig.h | 7 ++- py/objfun.c | 47 +++++++++++++++++++ tests/basics/fun_code.py | 36 ++++++++++++++ tests/basics/fun_code_micropython.py | 19 ++++++++ tests/basics/fun_code_micropython.py.exp | 1 + tests/basics/subclass_native1.py | 6 +-- tests/micropython/native_fun_attrs_code.py | 21 +++++++++ .../micropython/native_fun_attrs_code.py.exp | 1 + 8 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 tests/basics/fun_code.py create mode 100644 tests/basics/fun_code_micropython.py create mode 100644 tests/basics/fun_code_micropython.py.exp create mode 100644 tests/micropython/native_fun_attrs_code.py create mode 100644 tests/micropython/native_fun_attrs_code.py.exp diff --git a/py/mpconfig.h b/py/mpconfig.h index 76aff4681d3..5c4d19bb56f 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1041,6 +1041,11 @@ typedef double mp_float_t; #define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to implement the __code__ attribute on functions, and function constructor +#ifndef MICROPY_PY_FUNCTION_ATTRS_CODE +#define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES) +#endif + // Whether to support the descriptors __get__, __set__, __delete__ // This costs some code size and makes load/store/delete of instance // attributes slower for the classes that use this feature @@ -1135,7 +1140,7 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_CODE_BASIC (2) #define MICROPY_PY_BUILTINS_CODE_FULL (3) #ifndef MICROPY_PY_BUILTINS_CODE -#define MICROPY_PY_BUILTINS_CODE (MICROPY_PY_SYS_SETTRACE ? MICROPY_PY_BUILTINS_CODE_FULL : (MICROPY_PY_BUILTINS_COMPILE ? MICROPY_PY_BUILTINS_CODE_MINIMUM : MICROPY_PY_BUILTINS_CODE_NONE)) +#define MICROPY_PY_BUILTINS_CODE (MICROPY_PY_SYS_SETTRACE ? MICROPY_PY_BUILTINS_CODE_FULL : (MICROPY_PY_FUNCTION_ATTRS_CODE ? MICROPY_PY_BUILTINS_CODE_BASIC : (MICROPY_PY_BUILTINS_COMPILE ? MICROPY_PY_BUILTINS_CODE_MINIMUM : MICROPY_PY_BUILTINS_CODE_NONE))) #endif // Whether to support dict.fromkeys() class method diff --git a/py/objfun.c b/py/objfun.c index 0b1b8c115f2..a742c526725 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -28,6 +28,8 @@ #include #include +#include "py/emitglue.h" +#include "py/objcode.h" #include "py/objtuple.h" #include "py/objfun.h" #include "py/runtime.h" @@ -151,6 +153,30 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { return name; } +#if MICROPY_PY_FUNCTION_ATTRS_CODE +static mp_obj_t fun_bc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type; + mp_arg_check_num(n_args, n_kw, 2, 2, false); + + if (!mp_obj_is_type(args[0], &mp_type_code)) { + mp_raise_TypeError(NULL); + } + if (!mp_obj_is_type(args[1], &mp_type_dict)) { + mp_raise_TypeError(NULL); + } + + mp_obj_code_t *code = MP_OBJ_TO_PTR(args[0]); + mp_obj_t globals = args[1]; + + mp_module_context_t *module_context = m_new_obj(mp_module_context_t); + module_context->module.base.type = &mp_type_module; + module_context->module.globals = MP_OBJ_TO_PTR(globals); + module_context->constants = *mp_code_get_constants(code); + + return mp_make_function_from_proto_fun(mp_code_get_proto_fun(code), module_context, NULL); +} +#endif + #if MICROPY_CPYTHON_COMPAT static void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; @@ -340,9 +366,29 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals); } + #if MICROPY_PY_FUNCTION_ATTRS_CODE + if (attr == MP_QSTR___code__) { + const mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + if ((self->base.type == &mp_type_fun_bc + || self->base.type == &mp_type_gen_wrap) + && self->child_table == NULL) { + #if MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC + dest[0] = mp_obj_new_code(self->context->constants, self->bytecode); + #else + dest[0] = mp_obj_new_code(self->context, self->rc, true); + #endif + } + } + #endif } #endif +#if MICROPY_PY_FUNCTION_ATTRS_CODE +#define FUN_BC_MAKE_NEW make_new, fun_bc_make_new, +#else +#define FUN_BC_MAKE_NEW +#endif + #if MICROPY_CPYTHON_COMPAT #define FUN_BC_TYPE_PRINT print, fun_bc_print, #else @@ -359,6 +405,7 @@ MP_DEFINE_CONST_OBJ_TYPE( mp_type_fun_bc, MP_QSTR_function, MP_TYPE_FLAG_BINDS_SELF, + FUN_BC_MAKE_NEW FUN_BC_TYPE_PRINT FUN_BC_TYPE_ATTR call, fun_bc_call diff --git a/tests/basics/fun_code.py b/tests/basics/fun_code.py new file mode 100644 index 00000000000..59e1f7ec048 --- /dev/null +++ b/tests/basics/fun_code.py @@ -0,0 +1,36 @@ +# Test function.__code__ attribute. + +try: + (lambda: 0).__code__ +except AttributeError: + print("SKIP") + raise SystemExit + + +def f(): + return a + + +ftype = type(f) + +# Test __code__ access and function constructor. +code = f.__code__ +print(type(ftype(code, {})) is ftype) + +# Test instantiating multiple code's with different globals dicts. +code = f.__code__ +f1 = ftype(code, {"a": 1}) +f2 = ftype(code, {"a": 2}) +print(f1(), f2()) + +# Test bad first argument type. +try: + ftype(None, {}) +except TypeError: + print("TypeError") + +# Test bad second argument type. +try: + ftype(f.__code__, None) +except TypeError: + print("TypeError") diff --git a/tests/basics/fun_code_micropython.py b/tests/basics/fun_code_micropython.py new file mode 100644 index 00000000000..2c319a2db8c --- /dev/null +++ b/tests/basics/fun_code_micropython.py @@ -0,0 +1,19 @@ +# Test MicroPython-specific restrictions of function.__code__ attribute. + +try: + (lambda: 0).__code__ +except AttributeError: + print("SKIP") + raise SystemExit + + +def f_with_children(): + def g(): + pass + + +# Can't access __code__ when function has children. +try: + f_with_children.__code__ +except AttributeError: + print("AttributeError") diff --git a/tests/basics/fun_code_micropython.py.exp b/tests/basics/fun_code_micropython.py.exp new file mode 100644 index 00000000000..d169edffb4c --- /dev/null +++ b/tests/basics/fun_code_micropython.py.exp @@ -0,0 +1 @@ +AttributeError diff --git a/tests/basics/subclass_native1.py b/tests/basics/subclass_native1.py index 288a686d1a7..74b377eac91 100644 --- a/tests/basics/subclass_native1.py +++ b/tests/basics/subclass_native1.py @@ -21,11 +21,9 @@ class mylist(list): # TODO: Faults #print(a + a) -def foo(): - print("hello from foo") - +# subclassing a type that doesn't have make_new at the C level (not allowed) try: - class myfunc(type(foo)): + class myfunc(type([].append)): pass except TypeError: print("TypeError") diff --git a/tests/micropython/native_fun_attrs_code.py b/tests/micropython/native_fun_attrs_code.py new file mode 100644 index 00000000000..d277a7b9b26 --- /dev/null +++ b/tests/micropython/native_fun_attrs_code.py @@ -0,0 +1,21 @@ +# Test MicroPython-specific restrictions of function.__code__ attribute. + +try: + (lambda: 0).__code__ +except AttributeError: + print("SKIP") + raise SystemExit + +import micropython + + +@micropython.native +def f_native(): + pass + + +# Can't access __code__ when function is native code. +try: + f_native.__code__ +except AttributeError: + print("AttributeError") diff --git a/tests/micropython/native_fun_attrs_code.py.exp b/tests/micropython/native_fun_attrs_code.py.exp new file mode 100644 index 00000000000..d169edffb4c --- /dev/null +++ b/tests/micropython/native_fun_attrs_code.py.exp @@ -0,0 +1 @@ +AttributeError From a11ba7775e600b45c0e93443ca05dffb09a49389 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 17 Jul 2024 16:06:12 +1000 Subject: [PATCH 0293/2098] py/persistentcode: Add mp_raw_code_save_fun_to_bytes. Serialises a bytecode function/generator to a valid .mpy as bytes. Signed-off-by: Damien George --- py/mpconfig.h | 5 ++ py/persistentcode.c | 190 +++++++++++++++++++++++++++++++++++++++++++- py/persistentcode.h | 1 + 3 files changed, 194 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 5c4d19bb56f..a25d8cd32a2 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -342,6 +342,11 @@ #define MICROPY_PERSISTENT_CODE_SAVE_FILE (0) #endif +// Whether to support converting functions to persistent code (bytes) +#ifndef MICROPY_PERSISTENT_CODE_SAVE_FUN +#define MICROPY_PERSISTENT_CODE_SAVE_FUN (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + // Whether generated code can persist independently of the VM/runtime instance // This is enabled automatically when needed by other features #ifndef MICROPY_PERSISTENT_CODE diff --git a/py/persistentcode.c b/py/persistentcode.c index 840ee49d3e4..2a42b904bc1 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -526,7 +526,7 @@ void mp_raw_code_load_file(qstr filename, mp_compiled_module_t *context) { #endif // MICROPY_PERSISTENT_CODE_LOAD -#if MICROPY_PERSISTENT_CODE_SAVE +#if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_PERSISTENT_CODE_SAVE_FUN #include "py/objstr.h" @@ -624,6 +624,10 @@ static void save_obj(mp_print_t *print, mp_obj_t o) { } } +#endif // MICROPY_PERSISTENT_CODE_SAVE || MICROPY_PERSISTENT_CODE_SAVE_FUN + +#if MICROPY_PERSISTENT_CODE_SAVE + static void save_raw_code(mp_print_t *print, const mp_raw_code_t *rc) { // Save function kind and data length mp_print_uint(print, (rc->fun_data_len << 3) | ((rc->n_children != 0) << 2) | (rc->kind - MP_CODE_BYTECODE)); @@ -693,6 +697,8 @@ void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print) { save_raw_code(print, cm->rc); } +#endif // MICROPY_PERSISTENT_CODE_SAVE + #if MICROPY_PERSISTENT_CODE_SAVE_FILE #include @@ -723,4 +729,184 @@ void mp_raw_code_save_file(mp_compiled_module_t *cm, qstr filename) { #endif // MICROPY_PERSISTENT_CODE_SAVE_FILE -#endif // MICROPY_PERSISTENT_CODE_SAVE +#if MICROPY_PERSISTENT_CODE_SAVE_FUN + +#include "py/bc0.h" +#include "py/objfun.h" +#include "py/smallint.h" +#include "py/gc.h" + +#define MP_BC_OPCODE_HAS_SIGNED_OFFSET(opcode) (MP_BC_UNWIND_JUMP <= (opcode) && (opcode) <= MP_BC_POP_JUMP_IF_FALSE) + +typedef struct _bit_vector_t { + size_t max_bit_set; + size_t alloc; + uintptr_t *bits; +} bit_vector_t; + +static void bit_vector_init(bit_vector_t *self) { + self->max_bit_set = 0; + self->alloc = 1; + self->bits = m_new(uintptr_t, self->alloc); +} + +static void bit_vector_clear(bit_vector_t *self) { + m_del(uintptr_t, self->bits, self->alloc); +} + +static bool bit_vector_is_set(bit_vector_t *self, size_t index) { + const size_t bits_size = sizeof(*self->bits) * MP_BITS_PER_BYTE; + return index / bits_size < self->alloc + && (self->bits[index / bits_size] & (1 << (index % bits_size))) != 0; +} + +static void bit_vector_set(bit_vector_t *self, size_t index) { + const size_t bits_size = sizeof(*self->bits) * MP_BITS_PER_BYTE; + self->max_bit_set = MAX(self->max_bit_set, index); + if (index / bits_size >= self->alloc) { + size_t new_alloc = self->alloc * 2; + self->bits = m_renew(uintptr_t, self->bits, self->alloc, new_alloc); + self->alloc = new_alloc; + } + self->bits[index / bits_size] |= 1 << (index % bits_size); +} + +typedef struct _mp_opcode_t { + uint8_t opcode; + uint8_t format; + uint8_t size; + mp_int_t arg; + uint8_t extra_arg; +} mp_opcode_t; + +static mp_opcode_t mp_opcode_decode(const uint8_t *ip) { + const uint8_t *ip_start = ip; + uint8_t opcode = *ip++; + uint8_t opcode_format = MP_BC_FORMAT(opcode); + mp_uint_t arg = 0; + uint8_t extra_arg = 0; + if (opcode_format == MP_BC_FORMAT_QSTR || opcode_format == MP_BC_FORMAT_VAR_UINT) { + arg = *ip & 0x7f; + if (opcode == MP_BC_LOAD_CONST_SMALL_INT && (arg & 0x40) != 0) { + arg |= (mp_uint_t)(-1) << 7; + } + while ((*ip & 0x80) != 0) { + arg = (arg << 7) | (*++ip & 0x7f); + } + ++ip; + } else if (opcode_format == MP_BC_FORMAT_OFFSET) { + if ((*ip & 0x80) == 0) { + arg = *ip++; + if (MP_BC_OPCODE_HAS_SIGNED_OFFSET(opcode)) { + arg -= 0x40; + } + } else { + arg = (ip[0] & 0x7f) | (ip[1] << 7); + ip += 2; + if (MP_BC_OPCODE_HAS_SIGNED_OFFSET(opcode)) { + arg -= 0x4000; + } + } + } + if ((opcode & MP_BC_MASK_EXTRA_BYTE) == 0) { + extra_arg = *ip++; + } + + mp_opcode_t op = { opcode, opcode_format, ip - ip_start, arg, extra_arg }; + return op; +} + +mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, const uint8_t *bytecode) { + const uint8_t *fun_data = bytecode; + const uint8_t *fun_data_top = fun_data + gc_nbytes(fun_data); + + // Extract function information. + const byte *ip = fun_data; + MP_BC_PRELUDE_SIG_DECODE(ip); + MP_BC_PRELUDE_SIZE_DECODE(ip); + + // Track the qstrs used by the function. + bit_vector_t qstr_table_used; + bit_vector_init(&qstr_table_used); + + // Track the objects used by the function. + bit_vector_t obj_table_used; + bit_vector_init(&obj_table_used); + + const byte *ip_names = ip; + mp_uint_t simple_name = mp_decode_uint(&ip_names); + bit_vector_set(&qstr_table_used, simple_name); + for (size_t i = 0; i < n_pos_args + n_kwonly_args; ++i) { + mp_uint_t arg_name = mp_decode_uint(&ip_names); + bit_vector_set(&qstr_table_used, arg_name); + } + + // Skip pass source code info and cell info. + // Then ip points to the start of the opcodes. + ip += n_info + n_cell; + + // Decode bytecode. + while (ip < fun_data_top) { + mp_opcode_t op = mp_opcode_decode(ip); + if (op.opcode == MP_BC_BASE_RESERVED) { + // End of opcodes. + fun_data_top = ip; + } else if (op.opcode == MP_BC_LOAD_CONST_OBJ) { + bit_vector_set(&obj_table_used, op.arg); + } else if (op.format == MP_BC_FORMAT_QSTR) { + bit_vector_set(&qstr_table_used, op.arg); + } + ip += op.size; + } + + mp_uint_t fun_data_len = fun_data_top - fun_data; + + mp_print_t print; + vstr_t vstr; + vstr_init_print(&vstr, 64, &print); + + // Start with .mpy header. + const uint8_t header[4] = { 'M', MPY_VERSION, 0, MP_SMALL_INT_BITS }; + mp_print_bytes(&print, header, sizeof(header)); + + // Number of entries in constant table. + mp_print_uint(&print, qstr_table_used.max_bit_set + 1); + mp_print_uint(&print, obj_table_used.max_bit_set + 1); + + // Save qstrs. + for (size_t i = 0; i <= qstr_table_used.max_bit_set; ++i) { + if (bit_vector_is_set(&qstr_table_used, i)) { + save_qstr(&print, consts->qstr_table[i]); + } else { + save_qstr(&print, MP_QSTR_); + } + } + + // Save constant objects. + for (size_t i = 0; i <= obj_table_used.max_bit_set; ++i) { + if (bit_vector_is_set(&obj_table_used, i)) { + save_obj(&print, consts->obj_table[i]); + } else { + save_obj(&print, mp_const_none); + } + } + + bit_vector_clear(&qstr_table_used); + bit_vector_clear(&obj_table_used); + + // Save function kind and data length. + mp_print_uint(&print, fun_data_len << 3); + + // Save function code. + mp_print_bytes(&print, fun_data, fun_data_len); + + // Create and return bytes representing the .mpy data. + return mp_obj_new_bytes_from_vstr(&vstr); +} + +#endif // MICROPY_PERSISTENT_CODE_SAVE_FUN + +#if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE +// An mp_obj_list_t that tracks relocated native code to prevent the GC from reclaiming them. +MP_REGISTER_ROOT_POINTER(mp_obj_t track_reloc_code_list); +#endif diff --git a/py/persistentcode.h b/py/persistentcode.h index f0b7f70f7d7..cf257a7ab1f 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -121,6 +121,7 @@ void mp_raw_code_load_file(qstr filename, mp_compiled_module_t *ctx); void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print); void mp_raw_code_save_file(mp_compiled_module_t *cm, qstr filename); +mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, const uint8_t *bytecode); void mp_native_relocate(void *reloc, uint8_t *text, uintptr_t reloc_text); From c3a18d74ebebe1c68955c3dce3c782af949aa4c7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 20 Jan 2025 22:24:10 +1100 Subject: [PATCH 0294/2098] extmod/modmarshal: Add new marshal module. This commit implements a small subset of the CPython `marshal` module. It implements `marshal.dumps()` and `marshal.loads()`, but only supports (un)marshalling code objects at this stage. The semantics match CPython, except that the actual marshalled bytes is not compatible with CPython's marshalled bytes. The module is enabled at the everything level (only on the unix coverage build at this stage). Signed-off-by: Damien George --- extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/modmarshal.c | 88 ++++++++++++++++++ ports/windows/msvc/sources.props | 1 + py/mpconfig.h | 7 +- tests/extmod/marshal_basic.py | 38 ++++++++ tests/extmod/marshal_micropython.py | 21 +++++ tests/extmod/marshal_stress.py | 122 +++++++++++++++++++++++++ tests/ports/unix/extra_coverage.py.exp | 10 +- 9 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 extmod/modmarshal.c create mode 100644 tests/extmod/marshal_basic.py create mode 100644 tests/extmod/marshal_micropython.py create mode 100644 tests/extmod/marshal_stress.py diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 532ce83f907..3643f1aee7b 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -24,6 +24,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/modframebuf.c ${MICROPY_EXTMOD_DIR}/modlwip.c ${MICROPY_EXTMOD_DIR}/modmachine.c + ${MICROPY_EXTMOD_DIR}/modmarshal.c ${MICROPY_EXTMOD_DIR}/modnetwork.c ${MICROPY_EXTMOD_DIR}/modonewire.c ${MICROPY_EXTMOD_DIR}/modasyncio.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 6d54ae2222e..a510f3c54c2 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -29,6 +29,7 @@ SRC_EXTMOD_C += \ extmod/modjson.c \ extmod/modlwip.c \ extmod/modmachine.c \ + extmod/modmarshal.c \ extmod/modnetwork.c \ extmod/modonewire.c \ extmod/modopenamp.c \ diff --git a/extmod/modmarshal.c b/extmod/modmarshal.c new file mode 100644 index 00000000000..93d2bcf1150 --- /dev/null +++ b/extmod/modmarshal.c @@ -0,0 +1,88 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/objcode.h" +#include "py/objfun.h" +#include "py/persistentcode.h" +#include "py/runtime.h" + +#if MICROPY_PY_MARSHAL + +static mp_obj_t marshal_dumps(mp_obj_t value_in) { + if (mp_obj_is_type(value_in, &mp_type_code)) { + mp_obj_code_t *code = MP_OBJ_TO_PTR(value_in); + const void *proto_fun = mp_code_get_proto_fun(code); + const uint8_t *bytecode; + if (mp_proto_fun_is_bytecode(proto_fun)) { + bytecode = proto_fun; + } else { + const mp_raw_code_t *rc = proto_fun; + if (!(rc->kind == MP_CODE_BYTECODE && rc->children == NULL)) { + mp_raise_ValueError(MP_ERROR_TEXT("function must be bytecode with no children")); + } + bytecode = rc->fun_data; + } + return mp_raw_code_save_fun_to_bytes(mp_code_get_constants(code), bytecode); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("unmarshallable object")); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(marshal_dumps_obj, marshal_dumps); + +static mp_obj_t marshal_loads(mp_obj_t data_in) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ); + mp_module_context_t ctx; + ctx.module.globals = mp_globals_get(); + mp_compiled_module_t cm = { .context = &ctx }; + mp_raw_code_load_mem(bufinfo.buf, bufinfo.len, &cm); + #if MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC + return mp_obj_new_code(ctx.constants, cm.rc); + #else + mp_module_context_t *ctx_ptr = m_new_obj(mp_module_context_t); + *ctx_ptr = ctx; + return mp_obj_new_code(ctx_ptr, cm.rc, true); + #endif +} +static MP_DEFINE_CONST_FUN_OBJ_1(marshal_loads_obj, marshal_loads); + +static const mp_rom_map_elem_t mod_marshal_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_marshal) }, + { MP_ROM_QSTR(MP_QSTR_dumps), MP_ROM_PTR(&marshal_dumps_obj) }, + { MP_ROM_QSTR(MP_QSTR_loads), MP_ROM_PTR(&marshal_loads_obj) }, +}; + +static MP_DEFINE_CONST_DICT(mod_marshal_globals, mod_marshal_globals_table); + +const mp_obj_module_t mp_module_marshal = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mod_marshal_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_marshal, mp_module_marshal); + +#endif // MICROPY_PY_MARSHAL diff --git a/ports/windows/msvc/sources.props b/ports/windows/msvc/sources.props index f7c4c6bcac0..dcd10ddee91 100644 --- a/ports/windows/msvc/sources.props +++ b/ports/windows/msvc/sources.props @@ -15,6 +15,7 @@ + diff --git a/py/mpconfig.h b/py/mpconfig.h index a25d8cd32a2..66b3d125e78 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -344,7 +344,7 @@ // Whether to support converting functions to persistent code (bytes) #ifndef MICROPY_PERSISTENT_CODE_SAVE_FUN -#define MICROPY_PERSISTENT_CODE_SAVE_FUN (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#define MICROPY_PERSISTENT_CODE_SAVE_FUN (MICROPY_PY_MARSHAL) #endif // Whether generated code can persist independently of the VM/runtime instance @@ -1382,6 +1382,11 @@ typedef double mp_float_t; #define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) #endif +// Whether to provide "marshal" module +#ifndef MICROPY_PY_MARSHAL +#define MICROPY_PY_MARSHAL (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + // Whether to provide "math" module #ifndef MICROPY_PY_MATH #define MICROPY_PY_MATH (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) diff --git a/tests/extmod/marshal_basic.py b/tests/extmod/marshal_basic.py new file mode 100644 index 00000000000..9e7b70be482 --- /dev/null +++ b/tests/extmod/marshal_basic.py @@ -0,0 +1,38 @@ +# Test the marshal module, basic functionality. + +try: + import marshal + + (lambda: 0).__code__ +except (AttributeError, ImportError): + print("SKIP") + raise SystemExit + +ftype = type(lambda: 0) + +# Test basic dumps and loads. +print(ftype(marshal.loads(marshal.dumps((lambda: a).__code__)), {"a": 4})()) + +# Test dumps of a result from compile(). +ftype(marshal.loads(marshal.dumps(compile("print(a)", "", "exec"))), {"print": print, "a": 5})() + +# Test marshalling a function with arguments. +print(ftype(marshal.loads(marshal.dumps((lambda x, y: x + y).__code__)), {})(1, 2)) + +# Test marshalling a function with default arguments. +print(ftype(marshal.loads(marshal.dumps((lambda x=0: x).__code__)), {})("arg")) + +# Test marshalling a function containing constant objects (a tuple). +print(ftype(marshal.loads(marshal.dumps((lambda: (None, ...)).__code__)), {})()) + +# Test instantiating multiple code's with different globals dicts. +code = marshal.loads(marshal.dumps((lambda: a).__code__)) +f1 = ftype(code, {"a": 1}) +f2 = ftype(code, {"a": 2}) +print(f1(), f2()) + +# Test unmarshallable object. +try: + marshal.dumps(type) +except ValueError: + print("ValueError") diff --git a/tests/extmod/marshal_micropython.py b/tests/extmod/marshal_micropython.py new file mode 100644 index 00000000000..213b3bf3189 --- /dev/null +++ b/tests/extmod/marshal_micropython.py @@ -0,0 +1,21 @@ +# Test the marshal module, MicroPython-specific functionality. + +try: + import marshal +except ImportError: + print("SKIP") + raise SystemExit + +import unittest + + +class Test(unittest.TestCase): + def test_function_with_children(self): + # Can't marshal a function with children (in this case the module has a child function f). + code = compile("def f(): pass", "", "exec") + with self.assertRaises(ValueError): + marshal.dumps(code) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod/marshal_stress.py b/tests/extmod/marshal_stress.py new file mode 100644 index 00000000000..b52475c0361 --- /dev/null +++ b/tests/extmod/marshal_stress.py @@ -0,0 +1,122 @@ +# Test the marshal module, stressing edge cases. + +try: + import marshal + + (lambda: 0).__code__ +except (AttributeError, ImportError): + print("SKIP") + raise SystemExit + +ftype = type(lambda: 0) + +# Test a large function. + + +def large_function(arg0, arg1, arg2, arg3): + # Arguments. + print(arg0, arg1, arg2, arg3) + + # Positive medium-sized integer (still a small-int though). + print(1234) + + # Negative small-ish integer. + print(-20) + + # More than 64 constant objects. + x = (0,) + x = (1,) + x = (2,) + x = (3,) + x = (4,) + x = (5,) + x = (6,) + x = (7,) + x = (8,) + x = (9,) + x = (10,) + x = (11,) + x = (12,) + x = (13,) + x = (14,) + x = (15,) + x = (16,) + x = (17,) + x = (18,) + x = (19,) + x = (20,) + x = (21,) + x = (22,) + x = (23,) + x = (24,) + x = (25,) + x = (26,) + x = (27,) + x = (28,) + x = (29,) + x = (30,) + x = (31,) + x = (32,) + x = (33,) + x = (34,) + x = (35,) + x = (36,) + x = (37,) + x = (38,) + x = (39,) + x = (40,) + x = (41,) + x = (42,) + x = (43,) + x = (44,) + x = (45,) + x = (46,) + x = (47,) + x = (48,) + x = (49,) + x = (50,) + x = (51,) + x = (52,) + x = (53,) + x = (54,) + x = (55,) + x = (56,) + x = (57,) + x = (58,) + x = (59,) + x = (60,) + x = (61,) + x = (62,) + x = (63,) + x = (64,) + + # Small jump. + x = 0 + while x < 2: + print("loop", x) + x += 1 + + # Large jump. + x = 0 + while x < 2: + try: + try: + try: + print + except Exception as e: + print + finally: + print + except Exception as e: + print + finally: + print + except Exception as e: + print + finally: + print("loop", x) + x += 1 + + +code = marshal.dumps(large_function.__code__) +ftype(marshal.loads(code), {"print": print})(0, 1, 2, 3) diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 176db8e9f84..5ff947e883d 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -56,13 +56,13 @@ cmath collections cppexample cryptolib deflate errno example_package ffi framebuf gc hashlib heapq io json machine -math os platform random -re select socket struct -sys termios time tls -uctypes vfs websocket +marshal math os platform +random re select socket +struct sys termios time +tls uctypes vfs websocket me -micropython machine math +micropython machine marshal math argv atexit byteorder exc_info executable exit getsizeof implementation From e40a3fdb81fc7184e945754c06d42e169a296ab8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 22 Jan 2025 15:13:13 +1100 Subject: [PATCH 0295/2098] docs/library/marshal: Document the marshal module. Signed-off-by: Damien George --- docs/library/index.rst | 1 + docs/library/marshal.rst | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/library/marshal.rst diff --git a/docs/library/index.rst b/docs/library/index.rst index 4209a0781a6..2919378ce13 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -69,6 +69,7 @@ library. heapq.rst io.rst json.rst + marshal.rst math.rst os.rst platform.rst diff --git a/docs/library/marshal.rst b/docs/library/marshal.rst new file mode 100644 index 00000000000..6d3213e0a6e --- /dev/null +++ b/docs/library/marshal.rst @@ -0,0 +1,28 @@ +:mod:`marshal` -- Python object serialization +============================================= + +.. module:: marshal + :synopsis: Convert Python objects to and from a binary format + +|see_cpython_module| :mod:`python:marshal`. + +This module implements conversion between Python objects and a binary format. +The format is specific to MicroPython but does not depend on the machine +architecture, so the data can be transferred and used on a different MicroPython +instance, as long as the version of the binary data matches (it's currently +versioned as the mpy file version, see :ref:`mpy_files`). + +Functions +--------- + +.. function:: dumps(value, /) + + Convert the given *value* to binary format and return a corresponding ``bytes`` + object. + + Currently, code objects are the only supported values that can be converted. + +.. function:: loads(data, /) + + Convert the given bytes-like *data* to its corresponding Python object, and + return it. From 30acb16ad34babf25d126a7b471616421167cb35 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 3 Jan 2025 12:02:12 +1100 Subject: [PATCH 0296/2098] extmod/vfs_rom: Remove ability to create VfsRom from an address. It's not necessary to support this, which allows an arbitrary memory address to be specified and potentially allows invalid memory accesses. Requiring an object with the buffer protocol is safer, and also means that the length of the region is always specified. Signed-off-by: Damien George --- extmod/vfs_rom.c | 12 +++++------- tests/extmod/vfs_rom.py | 3 ++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/extmod/vfs_rom.c b/extmod/vfs_rom.c index b8b6f8e40d1..99ddaba95ed 100644 --- a/extmod/vfs_rom.c +++ b/extmod/vfs_rom.c @@ -198,15 +198,13 @@ static mp_obj_t vfs_rom_make_new(const mp_obj_type_t *type, size_t n_args, size_ self->base.type = type; self->memory = args[0]; + // Get the ROMFS memory region. mp_buffer_info_t bufinfo; - if (mp_get_buffer(self->memory, &bufinfo, MP_BUFFER_READ)) { - if (bufinfo.len < ROMFS_SIZE_MIN) { - mp_raise_OSError(MP_ENODEV); - } - self->filesystem = bufinfo.buf; - } else { - self->filesystem = (const uint8_t *)(uintptr_t)mp_obj_get_int_truncated(self->memory); + mp_get_buffer_raise(self->memory, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len < ROMFS_SIZE_MIN) { + mp_raise_OSError(MP_ENODEV); } + self->filesystem = bufinfo.buf; // Verify it is a ROMFS. if (!(self->filesystem[0] == ROMFS_HEADER_BYTE0 diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index d416d838705..f7958a93962 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -226,7 +226,8 @@ def test_unknown_record(self): class TestStandalone(TestBase): def test_constructor(self): self.assertIsInstance(vfs.VfsRom(self.romfs), vfs.VfsRom) - self.assertIsInstance(vfs.VfsRom(self.romfs_addr), vfs.VfsRom) + with self.assertRaises(TypeError): + vfs.VfsRom(self.romfs_addr) def test_mount(self): vfs.VfsRom(self.romfs).mount(True, False) From 1a67d720c786fd85c848ffba4b792048d262095f Mon Sep 17 00:00:00 2001 From: rufusclark <50201718+rufusclark@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:32:35 +0100 Subject: [PATCH 0297/2098] tools/pyboard.py: Make get_time use machine.RTC instead of pyb.RTC. The current code evaluates `pyb.RTC().datetime()` resulting in a remote side exception, as `pyb` is not defined on most ports (only stm32). The code should evaluate `machine.RTC().datetime()` and hence return the current time. Signed-off-by: rufusclark <50201718+rufusclark@users.noreply.github.com> Signed-off-by: Damien George --- tools/pyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pyboard.py b/tools/pyboard.py index dac51e7d6b8..0cf5b3d4659 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -506,7 +506,7 @@ def execfile(self, filename): return self.exec_(pyfile) def get_time(self): - t = str(self.eval("pyb.RTC().datetime()"), encoding="utf8")[1:-1].split(", ") + t = str(self.eval("machine.RTC().datetime()"), encoding="utf8")[1:-1].split(", ") return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6]) def fs_exists(self, src): From b675c87992b8b62ca8b18a05862e9c7b9427b9de Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 15:08:25 +1100 Subject: [PATCH 0298/2098] esp32/machine_sdcard: Fix invalid result of SDCard.read/writeblocks. Functions would return NULL instead of `mp_const_false` if failed to init. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_sdcard.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 92c6e64698c..da6b0df282c 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -364,7 +364,7 @@ static mp_obj_t machine_sdcard_readblocks(mp_obj_t self_in, mp_obj_t block_num, err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); if (err != ESP_OK) { - return false; + return mp_const_false; } mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); @@ -381,7 +381,7 @@ static mp_obj_t machine_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t block_num, err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); if (err != ESP_OK) { - return false; + return mp_const_false; } mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); From 842e3617a0f152f837b2e8cc4e9d4efae6cb52a4 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 11 Dec 2024 13:17:47 +0100 Subject: [PATCH 0299/2098] renesas-ra/Makefile: Remove id_code section from binary file generation. The linker scripts for most of these microcontrollers contain a non-contiguous flash section for the ID code that results in big binary files, which exceed the flash size. This commit removes the ID code section from the main firmware binary, and outputs it to a separate binary, which can be deployed manually if ID code is enabled. Signed-off-by: iabdalkader --- ports/renesas-ra/Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index dd70f56d7ca..03eb9b52b41 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -442,7 +442,8 @@ endef define GENERATE_BIN $(ECHO) "GEN $(1)" - $(Q)$(OBJCOPY) -I ihex -O binary $(2) $(1) + $(Q)$(OBJCOPY) -O binary -j .id_code $(2) $(BUILD)/id_code.bin + $(Q)$(OBJCOPY) -O binary --remove-section=.id_code $(2) $(1) endef define GENERATE_HEX @@ -452,7 +453,7 @@ endef .PHONY: -$(BUILD)/firmware.bin: $(BUILD)/firmware.hex +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(call GENERATE_BIN,$@,$^) $(BUILD)/firmware.hex: $(BUILD)/firmware.elf From aef6705a321fbefb06288b5be1f5931bf8c42fe3 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 22 Jan 2025 17:17:03 -0600 Subject: [PATCH 0300/2098] extmod/lwip-include: Increase number of lwIP timers when mDNS enabled. Despite the code comments claiming one is sufficient, the mDNS application is capable of using up to twelve timers. Three per IP protocol are started at once in `mdns_start_multicast_timeouts_ipvX`, then another two per protocol can be started in `mdns_handle_question`. Further timers can be started for two additional callbacks. Having certain timers, such as `MDNS_MULTICAST_TIMEOUT`, fail to start due to none being free will break mDNS forever as the app will never realize it's safe to transmit a packet. Therefore, this commit goes somewhat overkill and allocates the maximal amount of timers; it's uncertain if all can run simultaneously, or how many callback timers are needed. Each timer struct is 16 bytes on standard 32 bit builds. Plus, say, 8 bytes of allocater overhead, that's 288 more bytes of RAM used which shouldn't be too horrible. Users who don't need mDNS can manually disable it to recover the RAM if necessary. This fixes mDNS on W5500_EVB_PICO (among other boards). Before, mDNS would work for a bit after connection until the host's cache expired a minute or two later. Then the board would never respond to further queries. With this patch, all works well. Signed-off-by: Thomas Watson --- extmod/lwip-include/lwipopts_common.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extmod/lwip-include/lwipopts_common.h b/extmod/lwip-include/lwipopts_common.h index 8688db064ef..3e423090949 100644 --- a/extmod/lwip-include/lwipopts_common.h +++ b/extmod/lwip-include/lwipopts_common.h @@ -65,7 +65,9 @@ #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) + +// The mDNS responder requires 5 timers per IP version plus 2 others. Not having enough silently breaks it. +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + (LWIP_MDNS_RESPONDER * (2 + (5 * (LWIP_IPV4 + LWIP_IPV6))))) #define SO_REUSE 1 #define TCP_LISTEN_BACKLOG 1 From 321b30ca564bb33c625292247d00f7dd29dc9559 Mon Sep 17 00:00:00 2001 From: Keenan Johnson Date: Thu, 13 Feb 2025 13:11:38 -0800 Subject: [PATCH 0301/2098] extmod/modtls_mbedtls: Wire in support for DTLS. This commit enables support for DTLS, i.e. TLS over datagram transport protocols like UDP. While support for DTLS is absent in CPython, it is worth supporting it in MicroPython because it is the basis of the ubiquitous CoAP protocol, used in many IoT projects. To select DTLS, a new set of "protocols" are added to SSLContext: - ssl.PROTOCOL_DTLS_CLIENT - ssl.PROTOCOL_DTLS_SERVER If one of these is set, the library assumes that the underlying socket is a datagram-like socket (i.e. UDP or similar). Our own timer callbacks are implemented because the out of the box implementation relies on `gettimeofday()`. This new DTLS feature is enabled on all ports that use mbedTLS. This commit is an update to a previous PR #10062. Addresses issue #5270 which requested DTLS support. Signed-off-by: Keenan Johnson --- docs/library/ssl.rst | 21 +++++++ extmod/mbedtls/mbedtls_config_common.h | 1 + extmod/modtls_mbedtls.c | 76 ++++++++++++++++++++++++-- ports/esp32/boards/sdkconfig.base | 3 + tests/extmod/tls_dtls.py | 51 +++++++++++++++++ tests/extmod/tls_dtls.py.exp | 3 + 6 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 tests/extmod/tls_dtls.py create mode 100644 tests/extmod/tls_dtls.py.exp diff --git a/docs/library/ssl.rst b/docs/library/ssl.rst index dff90b8da58..4327c74bad6 100644 --- a/docs/library/ssl.rst +++ b/docs/library/ssl.rst @@ -117,11 +117,32 @@ Exceptions This exception does NOT exist. Instead its base class, OSError, is used. +DTLS support +------------ + +.. admonition:: Difference to CPython + :class: attention + + This is a MicroPython extension. + +This module supports DTLS in client and server mode via the `PROTOCOL_DTLS_CLIENT` +and `PROTOCOL_DTLS_SERVER` constants that can be used as the ``protocol`` argument +of `SSLContext`. + +In this case the underlying socket is expected to behave as a datagram socket (i.e. +like the socket opened with ``socket.socket`` with ``socket.AF_INET`` as ``af`` and +``socket.SOCK_DGRAM`` as ``type``). + +DTLS is only supported on ports that use mbed TLS, and it is not enabled by default: +it requires enabling ``MBEDTLS_SSL_PROTO_DTLS`` in the specific port configuration. + Constants --------- .. data:: ssl.PROTOCOL_TLS_CLIENT ssl.PROTOCOL_TLS_SERVER + ssl.PROTOCOL_DTLS_CLIENT (when DTLS support is enabled) + ssl.PROTOCOL_DTLS_SERVER (when DTLS support is enabled) Supported values for the *protocol* parameter. diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 6ea8540af99..6cd14befc31 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -89,6 +89,7 @@ #define MBEDTLS_SHA384_C #define MBEDTLS_SHA512_C #define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_PROTO_DTLS #define MBEDTLS_SSL_SRV_C #define MBEDTLS_SSL_TLS_C #define MBEDTLS_X509_CRT_PARSE_C diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 3fd416d72f5..6c34805da42 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -37,6 +37,7 @@ #include "py/stream.h" #include "py/objstr.h" #include "py/reader.h" +#include "py/mphal.h" #include "py/gc.h" #include "extmod/vfs.h" @@ -47,6 +48,9 @@ #include "mbedtls/pk.h" #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" +#ifdef MBEDTLS_SSL_PROTO_DTLS +#include "mbedtls/timing.h" +#endif #include "mbedtls/debug.h" #include "mbedtls/error.h" #if MBEDTLS_VERSION_NUMBER >= 0x03000000 @@ -65,6 +69,14 @@ #define MP_STREAM_POLL_RDWR (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR) +#define MP_ENDPOINT_IS_SERVER (1 << 0) +#define MP_TRANSPORT_IS_DTLS (1 << 1) + +#define MP_PROTOCOL_TLS_CLIENT 0 +#define MP_PROTOCOL_TLS_SERVER MP_ENDPOINT_IS_SERVER +#define MP_PROTOCOL_DTLS_CLIENT MP_TRANSPORT_IS_DTLS +#define MP_PROTOCOL_DTLS_SERVER MP_ENDPOINT_IS_SERVER | MP_TRANSPORT_IS_DTLS + // This corresponds to an SSLContext object. typedef struct _mp_obj_ssl_context_t { mp_obj_base_t base; @@ -91,6 +103,12 @@ typedef struct _mp_obj_ssl_socket_t { uintptr_t poll_mask; // Indicates which read or write operations the protocol needs next int last_error; // The last error code, if any + + #ifdef MBEDTLS_SSL_PROTO_DTLS + mp_uint_t timer_start_ms; + mp_uint_t timer_fin_ms; + mp_uint_t timer_int_ms; + #endif } mp_obj_ssl_socket_t; static const mp_obj_type_t ssl_context_type; @@ -242,7 +260,10 @@ static mp_obj_t ssl_context_make_new(const mp_obj_type_t *type_in, size_t n_args mp_arg_check_num(n_args, n_kw, 1, 1, false); // This is the "protocol" argument. - mp_int_t endpoint = mp_obj_get_int(args[0]); + mp_int_t protocol = mp_obj_get_int(args[0]); + + int endpoint = (protocol & MP_ENDPOINT_IS_SERVER) ? MBEDTLS_SSL_IS_SERVER : MBEDTLS_SSL_IS_CLIENT; + int transport = (protocol & MP_TRANSPORT_IS_DTLS) ? MBEDTLS_SSL_TRANSPORT_DATAGRAM : MBEDTLS_SSL_TRANSPORT_STREAM; // Create SSLContext object. #if MICROPY_PY_SSL_FINALISER @@ -282,7 +303,7 @@ static mp_obj_t ssl_context_make_new(const mp_obj_type_t *type_in, size_t n_args } ret = mbedtls_ssl_config_defaults(&self->conf, endpoint, - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + transport, MBEDTLS_SSL_PRESET_DEFAULT); if (ret != 0) { mbedtls_raise_error(ret); } @@ -525,6 +546,39 @@ static int _mbedtls_ssl_recv(void *ctx, byte *buf, size_t len) { } } +#ifdef MBEDTLS_SSL_PROTO_DTLS +static void _mbedtls_timing_set_delay(void *ctx, uint32_t int_ms, uint32_t fin_ms) { + mp_obj_ssl_socket_t *o = (mp_obj_ssl_socket_t *)ctx; + + o->timer_int_ms = int_ms; + o->timer_fin_ms = fin_ms; + + if (fin_ms != 0) { + o->timer_start_ms = mp_hal_ticks_ms(); + } +} + +static int _mbedtls_timing_get_delay(void *ctx) { + mp_obj_ssl_socket_t *o = (mp_obj_ssl_socket_t *)ctx; + + if (o->timer_fin_ms == 0) { + return -1; + } + + mp_uint_t elapsed_ms = mp_hal_ticks_ms() - o->timer_start_ms; + + if (elapsed_ms >= o->timer_fin_ms) { + return 2; + } + + if (elapsed_ms >= o->timer_int_ms) { + return 1; + } + + return 0; +} +#endif + static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t sock, bool server_side, bool do_handshake_on_connect, mp_obj_t server_hostname) { @@ -577,6 +631,10 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t mp_raise_ValueError(MP_ERROR_TEXT("CERT_REQUIRED requires server_hostname")); } + #ifdef MBEDTLS_SSL_PROTO_DTLS + mbedtls_ssl_set_timer_cb(&o->ssl, o, _mbedtls_timing_set_delay, _mbedtls_timing_get_delay); + #endif + mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL); if (do_handshake_on_connect) { @@ -788,6 +846,12 @@ static const mp_rom_map_elem_t ssl_socket_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + #ifdef MBEDTLS_SSL_PROTO_DTLS + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&mp_stream_read1_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv_into), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&mp_stream_write1_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendall), MP_ROM_PTR(&mp_stream_write_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socket_setblocking_obj) }, { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, #if MICROPY_PY_SSL_FINALISER @@ -879,8 +943,12 @@ static const mp_rom_map_elem_t mp_module_tls_globals_table[] = { // Constants. { MP_ROM_QSTR(MP_QSTR_MBEDTLS_VERSION), MP_ROM_PTR(&mbedtls_version_obj)}, - { MP_ROM_QSTR(MP_QSTR_PROTOCOL_TLS_CLIENT), MP_ROM_INT(MBEDTLS_SSL_IS_CLIENT) }, - { MP_ROM_QSTR(MP_QSTR_PROTOCOL_TLS_SERVER), MP_ROM_INT(MBEDTLS_SSL_IS_SERVER) }, + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_TLS_CLIENT), MP_ROM_INT(MP_PROTOCOL_TLS_CLIENT) }, + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_TLS_SERVER), MP_ROM_INT(MP_PROTOCOL_TLS_SERVER) }, + #ifdef MBEDTLS_SSL_PROTO_DTLS + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_DTLS_CLIENT), MP_ROM_INT(MP_PROTOCOL_DTLS_CLIENT) }, + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_DTLS_SERVER), MP_ROM_INT(MP_PROTOCOL_DTLS_SERVER) }, + #endif { MP_ROM_QSTR(MP_QSTR_CERT_NONE), MP_ROM_INT(MBEDTLS_SSL_VERIFY_NONE) }, { MP_ROM_QSTR(MP_QSTR_CERT_OPTIONAL), MP_ROM_INT(MBEDTLS_SSL_VERIFY_OPTIONAL) }, { MP_ROM_QSTR(MP_QSTR_CERT_REQUIRED), MP_ROM_INT(MBEDTLS_SSL_VERIFY_REQUIRED) }, diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index e20835c70c4..530db427119 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -64,6 +64,9 @@ CONFIG_MBEDTLS_HAVE_TIME_DATE=y CONFIG_MBEDTLS_PLATFORM_TIME_ALT=y CONFIG_MBEDTLS_HAVE_TIME=y +# Enable DTLS +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y + # Disable ALPN support as it's not implemented in MicroPython CONFIG_MBEDTLS_SSL_ALPN=n diff --git a/tests/extmod/tls_dtls.py b/tests/extmod/tls_dtls.py new file mode 100644 index 00000000000..b2d716769d3 --- /dev/null +++ b/tests/extmod/tls_dtls.py @@ -0,0 +1,51 @@ +# Test DTLS functionality including timeout handling + +try: + from tls import PROTOCOL_DTLS_CLIENT, PROTOCOL_DTLS_SERVER, SSLContext, CERT_NONE + import io +except ImportError: + print("SKIP") + raise SystemExit + + +class DummySocket(io.IOBase): + def __init__(self): + self.write_buffer = bytearray() + self.read_buffer = bytearray() + + def write(self, data): + return len(data) + + def readinto(self, buf): + # This is a placeholder socket that doesn't actually read anything + # so the read buffer is always empty. + return None + + def ioctl(self, req, arg): + if req == 4: # MP_STREAM_CLOSE + return 0 + return -1 + + +# Create dummy sockets for testing +server_socket = DummySocket() +client_socket = DummySocket() + +# Wrap the DTLS Server +dtls_server_ctx = SSLContext(PROTOCOL_DTLS_SERVER) +dtls_server_ctx.verify_mode = CERT_NONE +dtls_server = dtls_server_ctx.wrap_socket(server_socket, do_handshake_on_connect=False) +print("Wrapped DTLS Server") + +# Wrap the DTLS Client +dtls_client_ctx = SSLContext(PROTOCOL_DTLS_CLIENT) +dtls_client_ctx.verify_mode = CERT_NONE +dtls_client = dtls_client_ctx.wrap_socket(client_socket, do_handshake_on_connect=False) +print("Wrapped DTLS Client") + +# Trigger the timing check multiple times with different elapsed times +for i in range(10): # Try multiple iterations to hit the timing window + dtls_client.write(b"test") + data = dtls_server.read(1024) # This should eventually hit the timing condition + +print("OK") diff --git a/tests/extmod/tls_dtls.py.exp b/tests/extmod/tls_dtls.py.exp new file mode 100644 index 00000000000..78d72bff188 --- /dev/null +++ b/tests/extmod/tls_dtls.py.exp @@ -0,0 +1,3 @@ +Wrapped DTLS Server +Wrapped DTLS Client +OK From 8987b39e0b772d22539022f0961c8ea0f7162751 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Feb 2025 09:54:56 +1100 Subject: [PATCH 0302/2098] tests/multi_net: Add test for DTLS server and client. This adds a multi-test for DTLS server and client behaviour. It works on all ports that enable this feature (eg unix, esp32, rp2, stm32), but bare-metal ports that use lwIP are not reliable as the DTLS server because the lwIP bindings only support queuing one UDP packet at a time (that needs to be fixed). Also, to properly implement a DTLS server sockets need to support `socket.recvfrom(n, MSG_PEEK)`. That can be implemented in the future. Signed-off-by: Damien George --- tests/multi_net/tls_dtls_server_client.py | 89 +++++++++++++++++++ tests/multi_net/tls_dtls_server_client.py.exp | 14 +++ 2 files changed, 103 insertions(+) create mode 100644 tests/multi_net/tls_dtls_server_client.py create mode 100644 tests/multi_net/tls_dtls_server_client.py.exp diff --git a/tests/multi_net/tls_dtls_server_client.py b/tests/multi_net/tls_dtls_server_client.py new file mode 100644 index 00000000000..d50deb354ed --- /dev/null +++ b/tests/multi_net/tls_dtls_server_client.py @@ -0,0 +1,89 @@ +# Test DTLS server and client, sending a small amount of data between them. + +try: + import socket + import tls +except ImportError: + print("SKIP") + raise SystemExit + +PORT = 8000 + +# These are test certificates. See tests/README.md for details. +certfile = "ec_cert.der" +keyfile = "ec_key.der" + +try: + with open(certfile, "rb") as cf: + cert = cadata = cf.read() + with open(keyfile, "rb") as kf: + key = kf.read() +except OSError: + print("SKIP") + raise SystemExit + + +# DTLS server. +def instance0(): + multitest.globals(IP=multitest.get_network_ip()) + + # Create a UDP socket and bind it to accept incoming connections. + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + + multitest.next() + + # Wait for the client to connect. + data, client_addr = s.recvfrom(1) + print("incoming connection", data) + + # Connect back to the client, so the UDP socket can be used like a stream. + s.connect(client_addr) + + # Create the DTLS context and load the certificate. + ctx = tls.SSLContext(tls.PROTOCOL_DTLS_SERVER) + ctx.load_cert_chain(cert, key) + + # Wrap the UDP socket in server mode. + print("wrap socket") + s = ctx.wrap_socket(s, server_side=1) + + # Transfer some data. + for _ in range(4): + print(s.recv(16)) + s.send(b"server to client") + + # Close the DTLS and UDP connection. + s.close() + + +# DTLS client. +def instance1(): + multitest.next() + + # Create a UDP socket and connect to the server. + addr = socket.getaddrinfo(IP, PORT)[0][-1] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + print("connect") + s.connect(addr) + + # Send one byte to indicate a connection, and so the server can obtain our address. + s.write("X") + + # Create a DTLS context and load the certificate. + ctx = tls.SSLContext(tls.PROTOCOL_DTLS_CLIENT) + ctx.verify_mode = tls.CERT_REQUIRED + ctx.load_verify_locations(cadata) + + # Wrap the UDP socket. + print("wrap socket") + s = ctx.wrap_socket(s, server_hostname="micropython.local") + + # Transfer some data. + for _ in range(4): + s.send(b"client to server") + print(s.recv(16)) + + # Close the DTLS and UDP connection. + s.close() diff --git a/tests/multi_net/tls_dtls_server_client.py.exp b/tests/multi_net/tls_dtls_server_client.py.exp new file mode 100644 index 00000000000..f2ff396e181 --- /dev/null +++ b/tests/multi_net/tls_dtls_server_client.py.exp @@ -0,0 +1,14 @@ +--- instance0 --- +incoming connection b'X' +wrap socket +b'client to server' +b'client to server' +b'client to server' +b'client to server' +--- instance1 --- +connect +wrap socket +b'server to client' +b'server to client' +b'server to client' +b'server to client' From 71df9d0636c8b0b95ab009f82bde7d5b970835ec Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 11 Feb 2025 16:42:03 +1100 Subject: [PATCH 0303/2098] rp2: Fix build failure if threads are disabled. Regression in 3af006ef meant that pendsv.c no longer compiled if threads were disabled in the build config. Add an implementation based on the earlier one (simple counter) for the non-threads case. It seems like with the current usage patterns there's no need for the counter to be incremented/decremented atomically on a single core config. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/pendsv.c | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 4ba1e81604b..2c086f89429 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -43,10 +43,14 @@ static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; +static inline void pendsv_resume_run_dispatch(void); + 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. +#if MICROPY_PY_THREAD + +// Important to use a 'nowait' mutex 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 mp_thread_recursive_mutex_t pendsv_mutex; void pendsv_init(void) { @@ -62,7 +66,40 @@ void pendsv_suspend(void) { void pendsv_resume(void) { mp_thread_recursive_mutex_unlock(&pendsv_mutex); + pendsv_resume_run_dispatch(); +} + +static inline int pendsv_suspend_count(void) { + return pendsv_mutex.mutex.enter_count; +} + +#else + +// Without threads we don't include any pico-sdk mutex in the build, +// but also we don't need to worry about cross-thread contention (or +// races with interrupts that update this counter). +static int pendsv_lock; + +void pendsv_init(void) { +} + +void pendsv_suspend(void) { + pendsv_lock++; +} + +void pendsv_resume(void) { + assert(pendsv_lock > 0); + pendsv_lock--; + pendsv_resume_run_dispatch(); +} +static inline int pendsv_suspend_count(void) { + return pendsv_lock; +} + +#endif + +static inline void pendsv_resume_run_dispatch(void) { // Run pendsv if needed. Find an entry with a dispatch and call pendsv dispatch // with it. If pendsv runs it will service all slots. int count = PENDSV_DISPATCH_NUM_SLOTS; @@ -76,7 +113,7 @@ 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 (pendsv_suspend_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 @@ -97,6 +134,7 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { // PendSV interrupt handler to perform background processing. void PendSV_Handler(void) { + #if MICROPY_PY_THREAD if (!mp_thread_recursive_mutex_lock(&pendsv_mutex, 0)) { // Failure here means core 1 holds pendsv_mutex. ISR will // run again after core 1 calls pendsv_resume(). @@ -104,6 +142,9 @@ void PendSV_Handler(void) { } // Core 0 should not already have locked pendsv_mutex assert(pendsv_mutex.mutex.enter_count == 1); + #else + assert(pendsv_suspend_count() == 0); + #endif #if MICROPY_PY_NETWORK_CYW43 CYW43_STAT_INC(PENDSV_RUN_COUNT); @@ -117,5 +158,7 @@ void PendSV_Handler(void) { } } + #if MICROPY_PY_THREAD mp_thread_recursive_mutex_unlock(&pendsv_mutex); + #endif } From 516709be88571da0195125c840e772a3ec70fa66 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 11 Feb 2025 17:44:19 +1100 Subject: [PATCH 0304/2098] py/mkrules.cmake: Support passing CFLAGS_EXTRA in environment variable. This works similarly to the existing support in "bare metal" make ports, with the caveat that CMake will only set this value on a clean build and will reuse the previous value otherwise. This is slightly different to the CMake built-in support for CFLAGS, as this variable is used when evaluating source files for qstr generation, etc. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/mkrules.cmake | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index 3ee4c4c31aa..907500dd675 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -53,6 +53,15 @@ foreach(_arg ${MICROPY_CPP_DEF}) endforeach() list(APPEND MICROPY_CPP_FLAGS ${MICROPY_CPP_FLAGS_EXTRA}) +# Include anything passed in via CFLAGS_EXTRA +# in both MICROPY_CPP_FLAGS and CMAKE_C_FLAGS +if(DEFINED ENV{CFLAGS_EXTRA}) + set(CFLAGS_EXTRA $ENV{CFLAGS_EXTRA}) + string(APPEND CMAKE_C_FLAGS " ${CFLAGS_EXTRA}") # ... not a list + separate_arguments(CFLAGS_EXTRA) + list(APPEND MICROPY_CPP_FLAGS ${CFLAGS_EXTRA}) # ... a list +endif() + find_package(Python3 REQUIRED COMPONENTS Interpreter) target_sources(${MICROPY_TARGET} PRIVATE From 1034b17558b108b65aca480d3b5b8f933e128369 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 11 Feb 2025 17:46:33 +1100 Subject: [PATCH 0305/2098] tools/ci.sh: Build the W5100S_EVB_PICO board with no threads. Serves as a build test for a config we don't otherwise support. Signed-off-by: Angus Gratton --- tools/ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index ff362efd29e..d50af4a0ae2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -342,7 +342,8 @@ function ci_rp2_build { 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 + # This build doubles as a build test for disabling threads in the config + make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO CFLAGS_EXTRA=-DMICROPY_PY_THREAD=0 # Test building ninaw10 driver and NIC interface. make ${MAKEOPTS} -C ports/rp2 BOARD=ARDUINO_NANO_RP2040_CONNECT submodules From 6425c9ecc746eec797f7ba9a37d803244f7d2192 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Feb 2025 00:40:22 +0100 Subject: [PATCH 0306/2098] esp32: Remove unneeded "memory.h" header file. This commit removes "memory.h" from the ESP32 port tree, as it is no longer needed with recent ESP-IDF versions. Signed-off-by: Alessandro Gatti --- ports/esp32/memory.h | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 ports/esp32/memory.h diff --git a/ports/esp32/memory.h b/ports/esp32/memory.h deleted file mode 100644 index 1f07fe409d7..00000000000 --- a/ports/esp32/memory.h +++ /dev/null @@ -1,2 +0,0 @@ -// this is needed for lib/crypto-algorithms/sha256.c -#include From 1e3cce1397c76f7714a5c4940b5021965944d268 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 20 Feb 2025 09:19:33 +0100 Subject: [PATCH 0307/2098] mimxrt/boards/ADAFRUIT_METRO_M7: Reduce flash freq to 100MHz. It was set to 133Mhz, but that is not stable. Reduce to 100MHz. The UF2 bootloader runs at 100MHz, so no need for a change of the bootloader. Signed-off-by: robert-hh --- ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk index b9c1716a7f6..520cea063fb 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk @@ -4,7 +4,7 @@ MCU_VARIANT = MIMXRT1011DAE5A MICROPY_FLOAT_IMPL = single MICROPY_HW_FLASH_TYPE ?= qspi_nor_flash MICROPY_HW_FLASH_SIZE ?= 0x800000 # 8MB -MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz MICROPY_HW_FLASH_QE_CMD = 0x31 MICROPY_HW_FLASH_QE_ARG = 0x02 From 4364d9411ad4c71bbf6de9bcc82683d6ee671cf5 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 20 Feb 2025 09:19:33 +0100 Subject: [PATCH 0308/2098] mimxrt/hal/flexspi_nor_flash: Fix typo in comment about frequency. Signed-off-by: robert-hh --- ports/mimxrt/hal/flexspi_nor_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/mimxrt/hal/flexspi_nor_flash.c b/ports/mimxrt/hal/flexspi_nor_flash.c index 7fdb6b7c13d..1476c1ec3a5 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.c +++ b/ports/mimxrt/hal/flexspi_nor_flash.c @@ -83,7 +83,7 @@ static ps_div_t div_table_mhz[] = { { 18, 8 }, // 60 -> 60 MHz { 23, 5 }, // 75 -> 75.13 MHz { 18, 6 }, // 80 -> 80 MHz - { 17, 5 }, // 100 -> 191 Mhz + { 17, 5 }, // 100 -> 101 Mhz { 13, 5 }, // 133 -> 132.92 MHz { 13, 4 } // 166 -> 166.15 MHz }; From 2992e3495630a8b83683db28241d638dbc53c911 Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Tue, 19 Sep 2023 13:04:46 +1000 Subject: [PATCH 0309/2098] tools/mpremote: Add support for relative urls in package.json files. URLs in `package.json` may now be specified relative to the base URL of the `package.json` file. Relative URLs wil work for `package.json` files installed from the web as well as local file paths. Docs: update `docs/reference/packages.rst` to add documentation for: - Installing packages from local filesystems (PR #12476); and - Using relative URLs in the `package.json` file (PR #12477); - Update the packaging example to encourage relative URLs as the default in `package.json`. Add `tools/mpremote/tests/test_mip_local_install.sh` to test the installation of a package from local files using relative URLs in the `package.json`. Signed-off-by: Glenn Moloney --- docs/reference/packages.rst | 48 +++++++++++-- tools/mpremote/mpremote/mip.py | 42 ++++++++---- .../mpremote/tests/test_mip_local_install.sh | 67 +++++++++++++++++++ .../tests/test_mip_local_install.sh.exp | 11 +++ 4 files changed, 149 insertions(+), 19 deletions(-) create mode 100755 tools/mpremote/tests/test_mip_local_install.sh create mode 100644 tools/mpremote/tests/test_mip_local_install.sh.exp diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 86b8efaa195..5b5f626d452 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -96,6 +96,18 @@ The ``--target=path``, ``--no-mpy``, and ``--index`` arguments can be set:: $ mpremote mip install --no-mpy pkgname $ mpremote mip install --index https://host/pi pkgname +:term:`mpremote` can also install packages from files stored on the host's local +filesystem:: + + $ mpremote mip install path/to/pkg.py + $ mpremote mip install path/to/app/package.json + $ mpremote mip install \\path\\to\\pkg.py + +This is especially useful for testing packages during development and for +installing packages from local clones of GitHub repositories. Note that URLs in +``package.json`` files must use forward slashes ("/") as directory separators, +even on Windows, so that they are compatible with installing from the web. + Installing packages manually ---------------------------- @@ -116,12 +128,25 @@ To write a "self-hosted" package that can be downloaded by ``mip`` or ``mpremote``, you need a static webserver (or GitHub) to host either a single .py file, or a ``package.json`` file alongside your .py files. -A typical ``package.json`` for an example ``mlx90640`` library looks like:: +An example ``mlx90640`` library hosted on GitHub could be installed with:: + + $ mpremote mip install github:org/micropython-mlx90640 + +The layout for the package on GitHub might look like:: + + https://github.com/org/micropython-mlx90640/ + package.json + mlx90640/ + __init__.py + utils.py + +The ``package.json`` specifies the location of files to be installed and other +dependencies:: { "urls": [ - ["mlx90640/__init__.py", "github:org/micropython-mlx90640/mlx90640/__init__.py"], - ["mlx90640/utils.py", "github:org/micropython-mlx90640/mlx90640/utils.py"] + ["mlx90640/__init__.py", "mlx90640/__init__.py"], + ["mlx90640/utils.py", "mlx90640/utils.py"] ], "deps": [ ["collections-defaultdict", "latest"], @@ -132,9 +157,20 @@ A typical ``package.json`` for an example ``mlx90640`` library looks like:: "version": "0.2" } -This includes two files, hosted at a GitHub repo named -``org/micropython-mlx90640``, which install into the ``mlx90640`` directory on -the device. It depends on ``collections-defaultdict`` and ``os-path`` which will +The ``urls`` list specifies the files to be installed according to:: + + "urls": [ + [destination_path, source_url] + ... + +where ``destination_path`` is the location and name of the file to be installed +on the device and ``source_url`` is the URL of the file to be installed. The +source URL would usually be specified relative to the directory containing the +``package.json`` file, but can also be an absolute URL, eg:: + + ["mlx90640/utils.py", "github:org/micropython-mlx90640/mlx90640/utils.py"] + +The package depends on ``collections-defaultdict`` and ``os-path`` which will be installed automatically from the :term:`micropython-lib`. The third dependency installs the content as defined by the ``package.json`` file of the ``main`` branch of the GitHub repo ``org/micropython-additions``. diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index 4ce62b7f77b..858b7933d11 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -7,6 +7,7 @@ import json import tempfile import os +import os.path from .commands import CommandError, show_progress_bar @@ -64,22 +65,33 @@ def _rewrite_url(url, branch=None): def _download_file(transport, url, dest): - try: - with urllib.request.urlopen(url) as src: - data = src.read() - print("Installing:", dest) - _ensure_path_exists(transport, dest) - transport.fs_writefile(dest, data, progress_callback=show_progress_bar) - except urllib.error.HTTPError as e: - if e.status == 404: - raise CommandError(f"File not found: {url}") - else: - raise CommandError(f"Error {e.status} requesting {url}") - except urllib.error.URLError as e: - raise CommandError(f"{e.reason} requesting {url}") + if url.startswith(allowed_mip_url_prefixes): + try: + with urllib.request.urlopen(url) as src: + data = src.read() + except urllib.error.HTTPError as e: + if e.status == 404: + raise CommandError(f"File not found: {url}") + else: + raise CommandError(f"Error {e.status} requesting {url}") + except urllib.error.URLError as e: + raise CommandError(f"{e.reason} requesting {url}") + else: + if "\\" in url: + raise CommandError(f'Use "/" instead of "\\" in file URLs: {url!r}\n') + try: + with open(url, "rb") as f: + data = f.read() + except OSError as e: + raise CommandError(f"{e.strerror} opening {url}") + + print("Installing:", dest) + _ensure_path_exists(transport, dest) + transport.fs_writefile(dest, data, progress_callback=show_progress_bar) def _install_json(transport, package_json_url, index, target, version, mpy): + base_url = "" if package_json_url.startswith(allowed_mip_url_prefixes): try: with urllib.request.urlopen(_rewrite_url(package_json_url, version)) as response: @@ -91,12 +103,14 @@ def _install_json(transport, package_json_url, index, target, version, mpy): raise CommandError(f"Error {e.status} requesting {package_json_url}") except urllib.error.URLError as e: raise CommandError(f"{e.reason} requesting {package_json_url}") + base_url = package_json_url.rpartition("/")[0] elif package_json_url.endswith(".json"): try: with open(package_json_url, "r") as f: package_json = json.load(f) except OSError: raise CommandError(f"Error opening {package_json_url}") + base_url = os.path.dirname(package_json_url) else: raise CommandError(f"Invalid url for package: {package_json_url}") for target_path, short_hash in package_json.get("hashes", ()): @@ -105,6 +119,8 @@ def _install_json(transport, package_json_url, index, target, version, mpy): _download_file(transport, file_url, fs_target_path) for target_path, url in package_json.get("urls", ()): fs_target_path = target + "/" + target_path + if base_url and not url.startswith(allowed_mip_url_prefixes): + url = f"{base_url}/{url}" # Relative URLs _download_file(transport, _rewrite_url(url, version), fs_target_path) for dep, dep_version in package_json.get("deps", ()): _install_package(transport, dep, index, target, dep_version, mpy) diff --git a/tools/mpremote/tests/test_mip_local_install.sh b/tools/mpremote/tests/test_mip_local_install.sh new file mode 100755 index 00000000000..fb8c597bd14 --- /dev/null +++ b/tools/mpremote/tests/test_mip_local_install.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# This test the "mpremote mip install" from local files. It creates a package +# and "mip installs" it into a ramdisk. The package is then imported and +# executed. The package is a simple "Hello, world!" example. + +set -e + +PACKAGE=mip_example +PACKAGE_DIR=${TMP}/example +MODULE_DIR=${PACKAGE_DIR}/${PACKAGE} + +target=/__ramdisk +block_size=512 +num_blocks=50 + +# Create the smallest permissible ramdisk. +cat << EOF > "${TMP}/ramdisk.py" +class RAMBlockDev: + def __init__(self, block_size, num_blocks): + self.block_size = block_size + self.data = bytearray(block_size * num_blocks) + + def readblocks(self, block_num, buf): + for i in range(len(buf)): + buf[i] = self.data[block_num * self.block_size + i] + + def writeblocks(self, block_num, buf): + for i in range(len(buf)): + self.data[block_num * self.block_size + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return len(self.data) // self.block_size + if op == 5: # get block size + return self.block_size + +import os + +bdev = RAMBlockDev(${block_size}, ${num_blocks}) +os.VfsFat.mkfs(bdev) +os.mount(bdev, '${target}') +EOF + +echo ----- Setup +mkdir -p ${MODULE_DIR} +echo "def hello(): print('Hello, world!')" > ${MODULE_DIR}/hello.py +echo "from .hello import hello" > ${MODULE_DIR}/__init__.py +cat > ${PACKAGE_DIR}/package.json < Date: Wed, 12 Feb 2025 14:03:31 +1100 Subject: [PATCH 0310/2098] github/workflows: Stop using ubuntu-20.04. For GitHub Actions, ubuntu-20.04 is deprecated and will be removed by 1st April 2025. See announcement at https://github.com/actions/runner-images/issues/11101 This commit changes actions that use ubuntu-20.04 to a newer image. Signed-off-by: Damien George --- .github/workflows/mpy_format.yml | 2 +- .github/workflows/ports_esp32.yml | 2 +- .github/workflows/ports_mimxrt.yml | 2 +- .github/workflows/ports_nrf.yml | 2 +- .github/workflows/ports_renesas-ra.yml | 2 +- .github/workflows/ports_stm32.yml | 2 +- .github/workflows/ports_unix.yml | 8 ++++---- tools/ci.sh | 17 +++++++++++------ 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index baa02ce08d5..b6768a46c3c 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -15,7 +15,7 @@ concurrency: jobs: test: - runs-on: ubuntu-20.04 # use 20.04 to get python2 + runs-on: ubuntu-22.04 # use 22.04 to get python2 steps: - uses: actions/checkout@v4 - name: Install packages diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 45808b659ad..0d76974f019 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -25,7 +25,7 @@ jobs: ci_func: # names are functions in ci.sh - esp32_build_cmod_spiram_s2 - esp32_build_s3_c3 - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ports_mimxrt.yml b/.github/workflows/ports_mimxrt.yml index 9e782bf63b3..7743e036ab3 100644 --- a/.github/workflows/ports_mimxrt.yml +++ b/.github/workflows/ports_mimxrt.yml @@ -19,7 +19,7 @@ concurrency: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest defaults: run: working-directory: 'micropython repo' # test build with space in path diff --git a/.github/workflows/ports_nrf.yml b/.github/workflows/ports_nrf.yml index d9cffb9778c..76727c9d1f6 100644 --- a/.github/workflows/ports_nrf.yml +++ b/.github/workflows/ports_nrf.yml @@ -19,7 +19,7 @@ concurrency: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install packages diff --git a/.github/workflows/ports_renesas-ra.yml b/.github/workflows/ports_renesas-ra.yml index b1a30c2f117..b9fa74331dc 100644 --- a/.github/workflows/ports_renesas-ra.yml +++ b/.github/workflows/ports_renesas-ra.yml @@ -19,7 +19,7 @@ concurrency: jobs: build_renesas_ra_board: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install packages diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index f5e01dc1f62..8800f145189 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -26,7 +26,7 @@ jobs: - stm32_pyb_build - stm32_nucleo_build - stm32_misc_build - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install packages diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index a095136b4a6..2547015038e 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -98,7 +98,7 @@ jobs: run: tests/run-tests.py --print-failures coverage_32bit: - runs-on: ubuntu-20.04 # use 20.04 to get libffi-dev:i386 + runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - uses: actions/checkout@v4 - name: Install packages @@ -116,7 +116,7 @@ jobs: run: tests/run-tests.py --print-failures nanbox: - runs-on: ubuntu-20.04 # use 20.04 to get python2, and libffi-dev:i386 + runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 steps: - uses: actions/checkout@v4 - name: Install packages @@ -142,7 +142,7 @@ jobs: run: tests/run-tests.py --print-failures stackless_clang: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install packages @@ -156,7 +156,7 @@ jobs: run: tests/run-tests.py --print-failures float_clang: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install packages diff --git a/tools/ci.sh b/tools/ci.sh index d50af4a0ae2..9125b5bc9f1 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -106,12 +106,16 @@ function ci_code_size_build { # .mpy file format function ci_mpy_format_setup { + sudo apt-get update + sudo apt-get install python2.7 sudo pip3 install pyelftools + python2.7 --version + python3 --version } function ci_mpy_format_test { # Test mpy-tool.py dump feature on bytecode - python2 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy + python2.7 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy # Test mpy-tool.py dump feature on native code @@ -268,18 +272,18 @@ function ci_powerpc_build { # ports/qemu function ci_qemu_setup_arm { - ci_mpy_format_setup ci_gcc_arm_setup sudo apt-get update sudo apt-get install qemu-system + sudo pip3 install pyelftools qemu-system-arm --version } function ci_qemu_setup_rv32 { - ci_mpy_format_setup ci_gcc_riscv_setup sudo apt-get update sudo apt-get install qemu-system + sudo pip3 install pyelftools qemu-system-riscv32 --version } @@ -580,10 +584,11 @@ function ci_unix_coverage_run_native_mpy_tests { function ci_unix_32bit_setup { sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 + sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 python2.7 sudo pip3 install setuptools sudo pip3 install pyelftools gcc --version + python2.7 --version python3 --version } @@ -602,12 +607,12 @@ function ci_unix_coverage_32bit_run_native_mpy_tests { function ci_unix_nanbox_build { # Use Python 2 to check that it can run the build scripts - ci_unix_build_helper PYTHON=python2 VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" + ci_unix_build_helper PYTHON=python2.7 VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" ci_unix_build_ffi_lib_helper gcc -m32 } function ci_unix_nanbox_run_tests { - ci_unix_run_tests_full_helper nanbox PYTHON=python2 + ci_unix_run_tests_full_helper nanbox PYTHON=python2.7 } function ci_unix_float_build { From 8b1ed4473dd215d564498f577cb6f3339c3c2e57 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Feb 2025 16:46:43 +1100 Subject: [PATCH 0311/2098] github/workflows: Include the Python version in the ESP-IDF cache key. Changing runner OS can change Python version, and ESP-IDF installs are keyed on ESP-IDF and Python version together. Signed-off-by: Angus Gratton --- .github/workflows/ports_esp32.yml | 4 ++-- tools/ci.sh | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 0d76974f019..4c07f894377 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -30,8 +30,8 @@ jobs: - uses: actions/checkout@v4 - id: idf_ver - name: Read the ESP-IDF version - run: source tools/ci.sh && echo "IDF_VER=$IDF_VER" | tee "$GITHUB_OUTPUT" + name: Read the ESP-IDF version (including Python version) + run: source tools/ci.sh && echo "IDF_VER=${IDF_VER}-py${PYTHON_VER}" | tee "$GITHUB_OUTPUT" - name: Cached ESP-IDF install id: cache_esp_idf diff --git a/tools/ci.sh b/tools/ci.sh index 9125b5bc9f1..5b108d62025 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -140,6 +140,7 @@ function ci_cc3200_build { # GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) IDF_VER=v5.2.2 +PYTHON_VER=$(python --version | cut -d' ' -f2) export IDF_CCACHE_ENABLE=1 From 5e206fdeb5225ce24efb9411cc879bafaa04b86f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 18 Feb 2025 13:24:11 +0100 Subject: [PATCH 0312/2098] all: Upgrade codespell to v2.4.1. This commit upgrades from codespell==2.2.6 to the current codespell==2.4.1, adding emac to the ignore-words-list. Signed-off-by: Christian Clauss --- .github/workflows/codespell.yml | 2 +- .pre-commit-config.yaml | 2 +- docs/reference/isr_rules.rst | 2 +- docs/reference/speed_python.rst | 2 +- examples/usercmodule/cexample/examplemodule.c | 2 +- extmod/modlwip.c | 2 +- ports/esp32/mpthreadport.c | 2 +- ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.h | 2 +- ports/mimxrt/eth.c | 2 +- ports/mimxrt/hal/flexspi_flash_config.h | 2 +- ports/mimxrt/sdcard.c | 2 +- ports/nrf/boards/MICROBIT/modules/microbitfont.h | 2 +- ports/rp2/main.c | 2 +- ports/samd/mcu/samd21/mpconfigmcu.h | 2 +- ports/samd/mcu/samd51/mpconfigmcu.h | 2 +- ports/windows/windows_mphal.c | 2 +- py/reader.h | 2 +- pyproject.toml | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 2d8b4627aac..1d6b1dc9d2b 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v4 # codespell version should be kept in sync with .pre-commit-config.yml - - run: pip install --user codespell==2.2.6 tomli + - run: pip install --user codespell==2.4.1 tomli - run: codespell diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1c811339ad..193b2c6e835 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: ruff-format - repo: https://github.com/codespell-project/codespell # Version should be kept in sync with .github/workflows/codespell.yml - rev: v2.2.6 + rev: v2.4.1 hooks: - id: codespell name: Spellcheck for changed files (codespell) diff --git a/docs/reference/isr_rules.rst b/docs/reference/isr_rules.rst index 14010fb207f..5e8d6ad6120 100644 --- a/docs/reference/isr_rules.rst +++ b/docs/reference/isr_rules.rst @@ -209,7 +209,7 @@ issue a further interrupt. It then schedules a callback to process the data. Scheduled callbacks should comply with the principles of interrupt handler design outlined below. This is to avoid problems resulting from I/O activity and the modification of shared data which can arise in any code -which pre-empts the main program loop. +which preempts the main program loop. Execution time needs to be considered in relation to the frequency with which interrupts can occur. If an interrupt occurs while the previous callback is executing, a further instance of the callback will be queued diff --git a/docs/reference/speed_python.rst b/docs/reference/speed_python.rst index a660432f6da..64fd9df6c05 100644 --- a/docs/reference/speed_python.rst +++ b/docs/reference/speed_python.rst @@ -219,7 +219,7 @@ process known as garbage collection reclaims the memory used by these redundant objects and the allocation is then tried again - a process which can take several milliseconds. -There may be benefits in pre-empting this by periodically issuing `gc.collect()`. +There may be benefits in preempting this by periodically issuing `gc.collect()`. Firstly doing a collection before it is actually required is quicker - typically on the order of 1ms if done frequently. Secondly you can determine the point in code where this time is used rather than have a longer delay occur at random points, diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c index d13515e72cd..83cc3b27c05 100644 --- a/examples/usercmodule/cexample/examplemodule.c +++ b/examples/usercmodule/cexample/examplemodule.c @@ -74,7 +74,7 @@ MP_DEFINE_CONST_OBJ_TYPE( // - A custom representation for __repr__ and __str__. // - Custom attribute handling to create a read/write "property". // -// It re-uses some of the elements of the basic Timer class. This is allowed +// It reuses some of the elements of the basic Timer class. This is allowed // because they both use example_Timer_obj_t as the instance structure. // Handles AdvancedTimer.__repr__, AdvancedTimer.__str__. diff --git a/extmod/modlwip.c b/extmod/modlwip.c index bcccd363e67..f109e0029bb 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -476,7 +476,7 @@ static void _lwip_tcp_err_unaccepted(void *arg, err_t err) { // because it's only ever used by lwIP if tcp_connect is called on the TCP PCB. lwip_socket_obj_t *socket = (lwip_socket_obj_t *)pcb->connected; - // Array is not volatile because thiss callback is executed within the lwIP context + // Array is not volatile because this callback is executed within the lwIP context uint8_t alloc = socket->incoming.connection.alloc; struct tcp_pcb **tcp_array = (struct tcp_pcb **)lwip_socket_incoming_array(socket); diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index f436ebb80a7..962b5780d08 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -213,7 +213,7 @@ int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { xSemaphoreGive(mutex->handle); - // Python threads run at equal priority, so pre-emptively yield here to + // Python threads run at equal priority, so preemptively yield here to // prevent pathological imbalances where a thread unlocks and then // immediately re-locks a mutex before a context switch can occur, leaving // another thread waiting for an unbounded period of time. diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.h b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.h index 784373c9394..1ce06e7f408 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.h +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.h @@ -52,7 +52,7 @@ { IOMUXC_GPIO_02_LPI2C1_SCL }, { IOMUXC_GPIO_01_LPI2C1_SDA }, \ { IOMUXC_GPIO_10_LPI2C2_SCL }, { IOMUXC_GPIO_09_LPI2C2_SDA }, -// Wifi Deinitions +// Wifi Definitions #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-metro-m7" #define MICROPY_HW_WIFI_SPI_ID (0) diff --git a/ports/mimxrt/eth.c b/ports/mimxrt/eth.c index 3eb1a0cc352..1ac19b83d6d 100644 --- a/ports/mimxrt/eth.c +++ b/ports/mimxrt/eth.c @@ -327,7 +327,7 @@ static void eth_gpio_init(const iomux_table_t iomux_table[], size_t iomux_table_ } } -// eth_phy_init: Initilaize the PHY interface +// eth_phy_init: Initialize the PHY interface static void eth_phy_init(phy_handle_t *phyHandle, phy_config_t *phy_config, phy_speed_t *speed, phy_duplex_t *duplex, uint32_t phy_settle_time) { diff --git a/ports/mimxrt/hal/flexspi_flash_config.h b/ports/mimxrt/hal/flexspi_flash_config.h index d9aa5ed5eab..239d7b3b4f8 100644 --- a/ports/mimxrt/hal/flexspi_flash_config.h +++ b/ports/mimxrt/hal/flexspi_flash_config.h @@ -119,7 +119,7 @@ enum kFlexSpiMiscOffset_WordAddressableEnable = 3, // !< Bit for Word Addressable enable kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, // !< Bit for Safe Configuration Frequency enable kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, // !< Bit for Pad setting override enable - kFlexSpiMiscOffset_DdrModeEnable = 6, // !< Bit for DDR clock confiuration indication. + kFlexSpiMiscOffset_DdrModeEnable = 6, // !< Bit for DDR clock configuration indication. }; // !@brief Flash Type Definition diff --git a/ports/mimxrt/sdcard.c b/ports/mimxrt/sdcard.c index ae52ed5d2cd..84e2c98949d 100644 --- a/ports/mimxrt/sdcard.c +++ b/ports/mimxrt/sdcard.c @@ -980,7 +980,7 @@ bool sdcard_power_on(mimxrt_sdcard_obj_t *card) { return false; } - // Finialize initialization + // Finalize initialization card->state->initialized = true; return true; } diff --git a/ports/nrf/boards/MICROBIT/modules/microbitfont.h b/ports/nrf/boards/MICROBIT/modules/microbitfont.h index 2ae0c8fab86..6e80acfe072 100644 --- a/ports/nrf/boards/MICROBIT/modules/microbitfont.h +++ b/ports/nrf/boards/MICROBIT/modules/microbitfont.h @@ -36,7 +36,7 @@ DEALINGS IN THE SOFTWARE. * * Example: { 0x08, 0x08, 0x08, 0x0, 0x08 } * - * The above will produce an exclaimation mark on the second column in form the left. + * The above will produce an exclamation mark on the second column in form the left. * * We could compress further, but the complexity of decode would likely outweigh the gains. */ diff --git a/ports/rp2/main.c b/ports/rp2/main.c index d6bf4482671..58da63c06f1 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -90,7 +90,7 @@ 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(SYS_CLK_KHZ, false); - // Hook for setting up anything that needs to be super early in the bootup process. + // Hook for setting up anything that needs to be super early in the boot-up process. MICROPY_BOARD_STARTUP(); #if MICROPY_HW_ENABLE_UART_REPL diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index 29965f50f63..f0a7a73e0c0 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -1,4 +1,4 @@ -// Deinitions common to all SAMD21 boards +// Definitions common to all SAMD21 boards #include "samd21.h" #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 9a7b8528f35..831949bab56 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -1,4 +1,4 @@ -// Deinitions common to all SAMD51 boards +// Definitions common to all SAMD51 boards #include "samd51.h" #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) diff --git a/ports/windows/windows_mphal.c b/ports/windows/windows_mphal.c index b636393f540..5a65b2fd2ae 100644 --- a/ports/windows/windows_mphal.c +++ b/ports/windows/windows_mphal.c @@ -70,7 +70,7 @@ void mp_hal_stdio_mode_orig(void) { } // Handler to be installed by SetConsoleCtrlHandler, currently used only to handle Ctrl-C. -// This handler has to be installed just once (this has to be done elswhere in init code). +// This handler has to be installed just once (this has to be done elsewhere in init code). // Previous versions of the mp_hal code would install a handler whenever Ctrl-C input is // allowed and remove the handler again when it is not. That is not necessary though (1), // and it might introduce problems (2) because console notifications are delivered to the diff --git a/py/reader.h b/py/reader.h index 301c70ab3f0..6378457007c 100644 --- a/py/reader.h +++ b/py/reader.h @@ -48,7 +48,7 @@ void mp_reader_new_file(mp_reader_t *reader, qstr filename); void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd); // Try to efficiently read the given number of bytes from a ROM-based reader. -// Returns a valid, non-NULL pointer to the requseted data if the reader points to ROM. +// Returns a valid, non-NULL pointer to the requested data if the reader points to ROM. // Returns NULL if the reader does not point to ROM. const uint8_t *mp_reader_try_read_rom(mp_reader_t *reader, size_t len); diff --git a/pyproject.toml b/pyproject.toml index 1650bd088ea..aeb25c8a03a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.codespell] count = "" ignore-regex = '\b[A-Z]{3}\b' -ignore-words-list = "ans,asend,deques,dout,extint,hsi,iput,mis,numer,shft,synopsys,technic,ure" +ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,numer,shft,synopsys,technic,ure" quiet-level = 3 skip = """ */build*,\ From 3f0dd13d931c88a759a779853dfeaee566f0e79a Mon Sep 17 00:00:00 2001 From: Ronald Weber Date: Mon, 24 Feb 2025 17:34:29 +0100 Subject: [PATCH 0313/2098] docs: Fix double 'the' in documentation. Signed-off-by: Ronald Weber --- docs/esp8266/tutorial/repl.rst | 2 +- docs/library/framebuf.rst | 2 +- docs/library/machine.UART.rst | 2 +- docs/reference/repl.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/esp8266/tutorial/repl.rst b/docs/esp8266/tutorial/repl.rst index bc0142aaef5..db55d45facd 100644 --- a/docs/esp8266/tutorial/repl.rst +++ b/docs/esp8266/tutorial/repl.rst @@ -38,7 +38,7 @@ browser. The latest versions of Firefox and Chrome are supported. For your convenience, WebREPL client is hosted at ``__. Alternatively, you can install it -locally from the the GitHub repository +locally from the GitHub repository ``__. Before connecting to WebREPL, you should set a password and enable it via diff --git a/docs/library/framebuf.rst b/docs/library/framebuf.rst index 149f4d6609b..f22a3613bdb 100644 --- a/docs/library/framebuf.rst +++ b/docs/library/framebuf.rst @@ -114,7 +114,7 @@ Drawing text .. method:: FrameBuffer.text(s, x, y[, c]) - Write text to the FrameBuffer using the the coordinates as the upper-left + Write text to the FrameBuffer using the coordinates as the upper-left corner of the text. The color of the text can be defined by the optional argument but is otherwise a default value of 1. All characters have dimensions of 8x8 pixels and there is currently no way to change the font. diff --git a/docs/library/machine.UART.rst b/docs/library/machine.UART.rst index 4dcb4a1e7c4..5be79cccce8 100644 --- a/docs/library/machine.UART.rst +++ b/docs/library/machine.UART.rst @@ -83,7 +83,7 @@ Methods - *pins* is a 4 or 2 item list indicating the TX, RX, RTS and CTS pins (in that order). Any of the pins can be None if one wants the UART to operate with limited functionality. - If the RTS pin is given the the RX pin must be given as well. The same applies to CTS. + If the RTS pin is given the RX pin must be given as well. The same applies to CTS. When no pins are given, then the default set of TX and RX pins is taken, and hardware flow control will be disabled. If *pins* is ``None``, no pin assignment will be made. diff --git a/docs/reference/repl.rst b/docs/reference/repl.rst index be02ddebde8..2c019350f7e 100644 --- a/docs/reference/repl.rst +++ b/docs/reference/repl.rst @@ -213,7 +213,7 @@ echo turned off, and with optional flow control. Raw mode is entered using Ctrl-A. You then send your python code, followed by a Ctrl-D. The Ctrl-D will be acknowledged by 'OK' and then the python code will be compiled and executed. Any output (or errors) will be sent back. Entering -Ctrl-B will leave raw mode and return the the regular (aka friendly) REPL. +Ctrl-B will leave raw mode and return the regular (aka friendly) REPL. Raw-paste mode is an additional mode within the raw REPL that includes flow control, and which compiles code as it receives it. This makes it more robust for high-speed From dc2fcfcc5511a371ff684f7d7772e7a7b479246d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 18 Feb 2025 10:01:09 +0100 Subject: [PATCH 0314/2098] all: Upgrade to ruff v0.9.6. Signed-off-by: Christian Clauss --- .github/workflows/ruff.yml | 2 +- .pre-commit-config.yaml | 2 +- ports/nrf/examples/seeed_tft.py | 1 + pyproject.toml | 22 ++++++++++++++----- tests/cpydiff/builtin_next_arg2.py | 1 + tests/cpydiff/core_class_delnotimpl.py | 1 + tests/cpydiff/core_fstring_concat.py | 2 ++ tests/cpydiff/core_function_argcount.py | 1 + tests/cpydiff/core_import_all.py | 1 + tests/cpydiff/core_import_path.py | 1 + tests/cpydiff/core_import_split_ns_pkgs.py | 1 + tests/cpydiff/core_locals_eval.py | 1 + tests/cpydiff/module_array_comparison.py | 1 + tests/cpydiff/module_array_constructor.py | 1 + tests/cpydiff/modules_array_containment.py | 1 + tests/cpydiff/modules_array_deletion.py | 1 + tests/cpydiff/modules_array_subscrstep.py | 1 + tests/cpydiff/modules_json_nonserializable.py | 1 + tests/cpydiff/modules_os_environ.py | 1 + tests/cpydiff/modules_os_getenv.py | 1 + tests/cpydiff/modules_struct_fewargs.py | 1 + tests/cpydiff/modules_struct_manyargs.py | 1 + .../modules_struct_whitespace_in_format.py | 1 + tests/cpydiff/modules_sys_stdassign.py | 1 + tests/cpydiff/syntax_assign_expr.py | 1 + tests/cpydiff/syntax_spaces.py | 1 + tests/cpydiff/syntax_unicode_nameesc.py | 1 + tests/cpydiff/types_bytearray_sliceassign.py | 1 + tests/cpydiff/types_bytes_format.py | 1 + tests/cpydiff/types_bytes_keywords.py | 1 + tests/cpydiff/types_bytes_subscrstep.py | 1 + tests/cpydiff/types_dict_keys_set.py | 1 + tests/cpydiff/types_exception_attrs.py | 1 + tests/cpydiff/types_exception_chaining.py | 1 + tests/cpydiff/types_exception_instancevar.py | 1 + tests/cpydiff/types_exception_loops.py | 1 + tests/cpydiff/types_float_rounding.py | 1 + tests/cpydiff/types_list_delete_subscrstep.py | 1 + tests/cpydiff/types_list_store_noniter.py | 1 + tests/cpydiff/types_list_store_subscrstep.py | 1 + tests/cpydiff/types_memoryview_invalid.py | 1 + tests/cpydiff/types_str_endswith.py | 1 + tests/cpydiff/types_str_formatsubscr.py | 1 + tests/cpydiff/types_str_keywords.py | 1 + tests/cpydiff/types_str_ljust_rjust.py | 1 + tests/cpydiff/types_str_rsplitnone.py | 1 + tests/cpydiff/types_str_subscrstep.py | 1 + tests/cpydiff/types_tuple_subscrstep.py | 1 + tests/ports/cc3200/pin.py | 1 + tests/unicode/unicode.py | 2 +- tools/gen-cpydiff.py | 6 ++--- tools/mpremote/mpremote/transport.py | 2 +- tools/pyboard.py | 2 +- tools/pydfu.py | 5 ++--- 54 files changed, 73 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 29e9ddbf8b6..9265c25bcba 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v4 # ruff version should be kept in sync with .pre-commit-config.yaml - - run: pip install --user ruff==0.1.3 + - run: pipx install ruff==0.9.6 - run: ruff check --output-format=github . - run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 193b2c6e835..d07f9b0fda2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: stages: [commit-msg] - repo: https://github.com/charliermarsh/ruff-pre-commit # Version should be kept in sync with .github/workflows/ruff.yml - rev: v0.1.3 + rev: v0.9.6 hooks: - id: ruff - id: ruff-format diff --git a/ports/nrf/examples/seeed_tft.py b/ports/nrf/examples/seeed_tft.py index f53b5602263..263da7ae0be 100644 --- a/ports/nrf/examples/seeed_tft.py +++ b/ports/nrf/examples/seeed_tft.py @@ -44,6 +44,7 @@ tf = mount_tf() os.listdir() """ + import vfs import time import framebuf diff --git a/pyproject.toml b/pyproject.toml index aeb25c8a03a..d5e3d0e8474 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,17 @@ line-length = 99 target-version = "py37" [tool.ruff.lint] +exclude = [ # Ruff finds Python SyntaxError in these files + "tests/cmdline/repl_autocomplete.py", + "tests/cmdline/repl_autoindent.py", + "tests/cmdline/repl_basic.py", + "tests/cmdline/repl_cont.py", + "tests/cmdline/repl_emacs_keys.py", + "tests/cmdline/repl_words_move.py", + "tests/feature_check/repl_emacs_check.py", + "tests/feature_check/repl_words_move_check.py", + "tests/micropython/viper_args.py", +] extend-select = ["C9", "PLC"] ignore = [ "E401", @@ -37,14 +48,12 @@ ignore = [ "F401", "F403", "F405", - "PLC1901", + "PLC0206", ] +mccabe.max-complexity = 40 -[tool.ruff.mccabe] -max-complexity = 40 - -[tool.ruff.per-file-ignores] -# Exclude all tests from linting (does not apply to formatting). +[tool.ruff.lint.per-file-ignores] +# Exclude all tests from linting. "tests/**/*.py" = ["ALL"] "ports/cc3200/tools/uniflash.py" = ["E711"] # manifest.py files are evaluated with some global names pre-defined @@ -57,3 +66,4 @@ max-complexity = 40 # repl_: not real python files # viper_args: uses f(*) exclude = ["tests/basics/*.py", "tests/*/repl_*.py", "tests/micropython/viper_args.py"] +quote-style = "preserve" diff --git a/tests/cpydiff/builtin_next_arg2.py b/tests/cpydiff/builtin_next_arg2.py index 5df2d6e70f8..ed9565fe0fe 100644 --- a/tests/cpydiff/builtin_next_arg2.py +++ b/tests/cpydiff/builtin_next_arg2.py @@ -9,4 +9,5 @@ except StopIteration: val = deflt """ + print(next(iter(range(0)), 42)) diff --git a/tests/cpydiff/core_class_delnotimpl.py b/tests/cpydiff/core_class_delnotimpl.py index 18c176e9bb1..6fac764ac98 100644 --- a/tests/cpydiff/core_class_delnotimpl.py +++ b/tests/cpydiff/core_class_delnotimpl.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import gc diff --git a/tests/cpydiff/core_fstring_concat.py b/tests/cpydiff/core_fstring_concat.py index 83bf18820ec..3daa13d7536 100644 --- a/tests/cpydiff/core_fstring_concat.py +++ b/tests/cpydiff/core_fstring_concat.py @@ -6,7 +6,9 @@ """ x, y = 1, 2 +# fmt: off print("aa" f"{x}") # works print(f"{x}" "ab") # works print("a{}a" f"{x}") # fails print(f"{x}" "a{}b") # fails +# fmt: on diff --git a/tests/cpydiff/core_function_argcount.py b/tests/cpydiff/core_function_argcount.py index 5f3dca4dcdb..c257def726a 100644 --- a/tests/cpydiff/core_function_argcount.py +++ b/tests/cpydiff/core_function_argcount.py @@ -4,6 +4,7 @@ cause: MicroPython counts "self" as an argument. workaround: Interpret error messages with the information above in mind. """ + try: [].append() except Exception as e: diff --git a/tests/cpydiff/core_import_all.py b/tests/cpydiff/core_import_all.py index 5adf9ae3eb1..0fbe9d4d4ec 100644 --- a/tests/cpydiff/core_import_all.py +++ b/tests/cpydiff/core_import_all.py @@ -4,6 +4,7 @@ cause: Not implemented. workaround: Manually import the sub-modules directly in __init__.py using ``from . import foo, bar``. """ + from modules3 import * foo.hello() diff --git a/tests/cpydiff/core_import_path.py b/tests/cpydiff/core_import_path.py index 959fd571f57..2028386be84 100644 --- a/tests/cpydiff/core_import_path.py +++ b/tests/cpydiff/core_import_path.py @@ -4,6 +4,7 @@ cause: MicroPython doesn't support namespace packages split across filesystem. Beyond that, MicroPython's import system is highly optimized for minimal memory usage. workaround: Details of import handling is inherently implementation dependent. Don't rely on such details in portable applications. """ + import modules print(modules.__path__) diff --git a/tests/cpydiff/core_import_split_ns_pkgs.py b/tests/cpydiff/core_import_split_ns_pkgs.py index 5c92b63124a..a1cfd84893d 100644 --- a/tests/cpydiff/core_import_split_ns_pkgs.py +++ b/tests/cpydiff/core_import_split_ns_pkgs.py @@ -4,6 +4,7 @@ cause: MicroPython's import system is highly optimized for simplicity, minimal memory usage, and minimal filesystem search overhead. workaround: Don't install modules belonging to the same namespace package in different directories. For MicroPython, it's recommended to have at most 3-component module search paths: for your current application, per-user (writable), system-wide (non-writable). """ + import sys sys.path.append(sys.path[1] + "/modules") diff --git a/tests/cpydiff/core_locals_eval.py b/tests/cpydiff/core_locals_eval.py index 025d2263729..3f6ad2a1dbb 100644 --- a/tests/cpydiff/core_locals_eval.py +++ b/tests/cpydiff/core_locals_eval.py @@ -4,6 +4,7 @@ cause: MicroPython doesn't maintain symbolic local environment, it is optimized to an array of slots. Thus, local variables can't be accessed by a name. Effectively, ``eval(expr)`` in MicroPython is equivalent to ``eval(expr, globals(), globals())``. workaround: Unknown """ + val = 1 diff --git a/tests/cpydiff/module_array_comparison.py b/tests/cpydiff/module_array_comparison.py index a442af3f5bc..4929b1e590d 100644 --- a/tests/cpydiff/module_array_comparison.py +++ b/tests/cpydiff/module_array_comparison.py @@ -4,6 +4,7 @@ cause: Code size workaround: Compare individual elements """ + import array array.array("b", [1, 2]) == array.array("i", [1, 2]) diff --git a/tests/cpydiff/module_array_constructor.py b/tests/cpydiff/module_array_constructor.py index 08cf2ef2d1c..a53589d22ae 100644 --- a/tests/cpydiff/module_array_constructor.py +++ b/tests/cpydiff/module_array_constructor.py @@ -4,6 +4,7 @@ cause: MicroPython implements implicit truncation in order to reduce code size and execution time workaround: If CPython compatibility is needed then mask the value explicitly """ + import array a = array.array("b", [257]) diff --git a/tests/cpydiff/modules_array_containment.py b/tests/cpydiff/modules_array_containment.py index 8f25b0d4914..b29895aa758 100644 --- a/tests/cpydiff/modules_array_containment.py +++ b/tests/cpydiff/modules_array_containment.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import array print(1 in array.array("B", b"12")) diff --git a/tests/cpydiff/modules_array_deletion.py b/tests/cpydiff/modules_array_deletion.py index 3376527373e..2e7c31124b2 100644 --- a/tests/cpydiff/modules_array_deletion.py +++ b/tests/cpydiff/modules_array_deletion.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import array a = array.array("b", (1, 2, 3)) diff --git a/tests/cpydiff/modules_array_subscrstep.py b/tests/cpydiff/modules_array_subscrstep.py index 24308bd9042..65b12332f54 100644 --- a/tests/cpydiff/modules_array_subscrstep.py +++ b/tests/cpydiff/modules_array_subscrstep.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import array a = array.array("b", (1, 2, 3)) diff --git a/tests/cpydiff/modules_json_nonserializable.py b/tests/cpydiff/modules_json_nonserializable.py index d6a5660cadf..d83e7beca2d 100644 --- a/tests/cpydiff/modules_json_nonserializable.py +++ b/tests/cpydiff/modules_json_nonserializable.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import json try: diff --git a/tests/cpydiff/modules_os_environ.py b/tests/cpydiff/modules_os_environ.py index 491d8a31016..4edde166567 100644 --- a/tests/cpydiff/modules_os_environ.py +++ b/tests/cpydiff/modules_os_environ.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Use ``getenv``, ``putenv`` and ``unsetenv`` """ + import os try: diff --git a/tests/cpydiff/modules_os_getenv.py b/tests/cpydiff/modules_os_getenv.py index d1e828438e4..d5acd414eb3 100644 --- a/tests/cpydiff/modules_os_getenv.py +++ b/tests/cpydiff/modules_os_getenv.py @@ -4,6 +4,7 @@ cause: The ``environ`` attribute is not implemented workaround: Unknown """ + import os print(os.getenv("NEW_VARIABLE")) diff --git a/tests/cpydiff/modules_struct_fewargs.py b/tests/cpydiff/modules_struct_fewargs.py index cb6b0fd874e..49b2a3213c8 100644 --- a/tests/cpydiff/modules_struct_fewargs.py +++ b/tests/cpydiff/modules_struct_fewargs.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import struct try: diff --git a/tests/cpydiff/modules_struct_manyargs.py b/tests/cpydiff/modules_struct_manyargs.py index 03395baad3b..e3b78930f21 100644 --- a/tests/cpydiff/modules_struct_manyargs.py +++ b/tests/cpydiff/modules_struct_manyargs.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + import struct try: diff --git a/tests/cpydiff/modules_struct_whitespace_in_format.py b/tests/cpydiff/modules_struct_whitespace_in_format.py index a882b38569a..a7a1d2facdf 100644 --- a/tests/cpydiff/modules_struct_whitespace_in_format.py +++ b/tests/cpydiff/modules_struct_whitespace_in_format.py @@ -4,6 +4,7 @@ cause: MicroPython is optimised for code size. workaround: Don't use spaces in format strings. """ + import struct try: diff --git a/tests/cpydiff/modules_sys_stdassign.py b/tests/cpydiff/modules_sys_stdassign.py index 7d086078a93..29afe1b1203 100644 --- a/tests/cpydiff/modules_sys_stdassign.py +++ b/tests/cpydiff/modules_sys_stdassign.py @@ -4,6 +4,7 @@ cause: They are stored in read-only memory. workaround: Unknown """ + import sys sys.stdin = None diff --git a/tests/cpydiff/syntax_assign_expr.py b/tests/cpydiff/syntax_assign_expr.py index d4ed063b39a..58f57ca1fbe 100644 --- a/tests/cpydiff/syntax_assign_expr.py +++ b/tests/cpydiff/syntax_assign_expr.py @@ -4,4 +4,5 @@ cause: MicroPython is optimised for code size and doesn't check this case. workaround: Do not rely on this behaviour if writing CPython compatible code. """ + print([i := -1 for i in range(4)]) diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index c308240a78d..03d25d56199 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + try: print(eval("1and 0")) except SyntaxError: diff --git a/tests/cpydiff/syntax_unicode_nameesc.py b/tests/cpydiff/syntax_unicode_nameesc.py index 21628c974d8..838394b6ebd 100644 --- a/tests/cpydiff/syntax_unicode_nameesc.py +++ b/tests/cpydiff/syntax_unicode_nameesc.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("\N{LATIN SMALL LETTER A}") diff --git a/tests/cpydiff/types_bytearray_sliceassign.py b/tests/cpydiff/types_bytearray_sliceassign.py index e4068b04b98..353f61988fa 100644 --- a/tests/cpydiff/types_bytearray_sliceassign.py +++ b/tests/cpydiff/types_bytearray_sliceassign.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + b = bytearray(4) b[0:1] = [1, 2] print(b) diff --git a/tests/cpydiff/types_bytes_format.py b/tests/cpydiff/types_bytes_format.py index ad049877116..1a15e572c8a 100644 --- a/tests/cpydiff/types_bytes_format.py +++ b/tests/cpydiff/types_bytes_format.py @@ -4,4 +4,5 @@ cause: MicroPython strives to be a more regular implementation, so if both `str` and `bytes` support ``__mod__()`` (the % operator), it makes sense to support ``format()`` for both too. Support for ``__mod__`` can also be compiled out, which leaves only ``format()`` for bytes formatting. workaround: If you are interested in CPython compatibility, don't use ``.format()`` on bytes objects. """ + print(b"{}".format(1)) diff --git a/tests/cpydiff/types_bytes_keywords.py b/tests/cpydiff/types_bytes_keywords.py index ade83d0a709..a459c94b41e 100644 --- a/tests/cpydiff/types_bytes_keywords.py +++ b/tests/cpydiff/types_bytes_keywords.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Pass the encoding as a positional parameter, e.g. ``print(bytes('abc', 'utf-8'))`` """ + print(bytes("abc", encoding="utf8")) diff --git a/tests/cpydiff/types_bytes_subscrstep.py b/tests/cpydiff/types_bytes_subscrstep.py index 51b94cb710f..c566cbdb630 100644 --- a/tests/cpydiff/types_bytes_subscrstep.py +++ b/tests/cpydiff/types_bytes_subscrstep.py @@ -4,4 +4,5 @@ cause: MicroPython is highly optimized for memory usage. workaround: Use explicit loop for this very rare operation. """ + print(b"123"[0:3:2]) diff --git a/tests/cpydiff/types_dict_keys_set.py b/tests/cpydiff/types_dict_keys_set.py index 3a0849a3556..a5f127962e6 100644 --- a/tests/cpydiff/types_dict_keys_set.py +++ b/tests/cpydiff/types_dict_keys_set.py @@ -4,4 +4,5 @@ cause: Not implemented. workaround: Explicitly convert keys to a set before using set operations. """ + print({1: 2, 3: 4}.keys() & {1}) diff --git a/tests/cpydiff/types_exception_attrs.py b/tests/cpydiff/types_exception_attrs.py index ad72b62a61a..5fed45451d2 100644 --- a/tests/cpydiff/types_exception_attrs.py +++ b/tests/cpydiff/types_exception_attrs.py @@ -4,6 +4,7 @@ cause: MicroPython is optimised to reduce code size. workaround: Only use ``value`` on ``StopIteration`` exceptions, and ``errno`` on ``OSError`` exceptions. Do not use or rely on these attributes on other exceptions. """ + e = Exception(1) print(e.value) print(e.errno) diff --git a/tests/cpydiff/types_exception_chaining.py b/tests/cpydiff/types_exception_chaining.py index 836c4eb3e73..cff68d4124a 100644 --- a/tests/cpydiff/types_exception_chaining.py +++ b/tests/cpydiff/types_exception_chaining.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Unknown """ + try: raise TypeError except TypeError: diff --git a/tests/cpydiff/types_exception_instancevar.py b/tests/cpydiff/types_exception_instancevar.py index adc353361f0..fb67771baf3 100644 --- a/tests/cpydiff/types_exception_instancevar.py +++ b/tests/cpydiff/types_exception_instancevar.py @@ -4,6 +4,7 @@ cause: MicroPython is highly optimized for memory usage. workaround: Use user-defined exception subclasses. """ + e = Exception() e.x = 0 print(e.x) diff --git a/tests/cpydiff/types_exception_loops.py b/tests/cpydiff/types_exception_loops.py index 8d326cbbbb0..549f1dd0a5b 100644 --- a/tests/cpydiff/types_exception_loops.py +++ b/tests/cpydiff/types_exception_loops.py @@ -4,6 +4,7 @@ cause: Condition checks are optimized to happen at the end of loop body, and that line number is reported. workaround: Unknown """ + l = ["-foo", "-bar"] i = 0 diff --git a/tests/cpydiff/types_float_rounding.py b/tests/cpydiff/types_float_rounding.py index a5b591966b0..206e359ed9b 100644 --- a/tests/cpydiff/types_float_rounding.py +++ b/tests/cpydiff/types_float_rounding.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("%.1g" % -9.9) diff --git a/tests/cpydiff/types_list_delete_subscrstep.py b/tests/cpydiff/types_list_delete_subscrstep.py index 36e6f526b3d..3c801d84949 100644 --- a/tests/cpydiff/types_list_delete_subscrstep.py +++ b/tests/cpydiff/types_list_delete_subscrstep.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Use explicit loop for this rare operation. """ + l = [1, 2, 3, 4] del l[0:4:2] print(l) diff --git a/tests/cpydiff/types_list_store_noniter.py b/tests/cpydiff/types_list_store_noniter.py index 1d69b4a8231..581b1369801 100644 --- a/tests/cpydiff/types_list_store_noniter.py +++ b/tests/cpydiff/types_list_store_noniter.py @@ -4,6 +4,7 @@ cause: RHS is restricted to be a tuple or list workaround: Use ``list()`` on RHS to convert the iterable to a list """ + l = [10, 20] l[0:1] = range(4) print(l) diff --git a/tests/cpydiff/types_list_store_subscrstep.py b/tests/cpydiff/types_list_store_subscrstep.py index 1460372bb17..97590267f4a 100644 --- a/tests/cpydiff/types_list_store_subscrstep.py +++ b/tests/cpydiff/types_list_store_subscrstep.py @@ -4,6 +4,7 @@ cause: Unknown workaround: Use explicit loop for this rare operation. """ + l = [1, 2, 3, 4] l[0:4:2] = [5, 6] print(l) diff --git a/tests/cpydiff/types_memoryview_invalid.py b/tests/cpydiff/types_memoryview_invalid.py index c995a2e8991..f288c50ab57 100644 --- a/tests/cpydiff/types_memoryview_invalid.py +++ b/tests/cpydiff/types_memoryview_invalid.py @@ -6,6 +6,7 @@ In the worst case scenario, resizing an object which is the target of a memoryview can cause the memoryview(s) to reference invalid freed memory (a use-after-free bug) and corrupt the MicroPython runtime. workaround: Do not change the size of any ``bytearray`` or ``io.bytesIO`` object that has a ``memoryview`` assigned to it. """ + b = bytearray(b"abcdefg") m = memoryview(b) b.extend(b"hijklmnop") diff --git a/tests/cpydiff/types_str_endswith.py b/tests/cpydiff/types_str_endswith.py index f222ac1cd3a..890c7ba5ef4 100644 --- a/tests/cpydiff/types_str_endswith.py +++ b/tests/cpydiff/types_str_endswith.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("abc".endswith("c", 1)) diff --git a/tests/cpydiff/types_str_formatsubscr.py b/tests/cpydiff/types_str_formatsubscr.py index 1b83cfff6cd..5c59a1d200a 100644 --- a/tests/cpydiff/types_str_formatsubscr.py +++ b/tests/cpydiff/types_str_formatsubscr.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("{a[0]}".format(a=[1, 2])) diff --git a/tests/cpydiff/types_str_keywords.py b/tests/cpydiff/types_str_keywords.py index 77a4eac1c1d..640dfa6c391 100644 --- a/tests/cpydiff/types_str_keywords.py +++ b/tests/cpydiff/types_str_keywords.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Input the encoding format directly. eg ``print(bytes('abc', 'utf-8'))`` """ + print(str(b"abc", encoding="utf8")) diff --git a/tests/cpydiff/types_str_ljust_rjust.py b/tests/cpydiff/types_str_ljust_rjust.py index 72e5105e025..7f91c137e90 100644 --- a/tests/cpydiff/types_str_ljust_rjust.py +++ b/tests/cpydiff/types_str_ljust_rjust.py @@ -4,4 +4,5 @@ cause: MicroPython is highly optimized for memory usage. Easy workarounds available. workaround: Instead of ``s.ljust(10)`` use ``"%-10s" % s``, instead of ``s.rjust(10)`` use ``"% 10s" % s``. Alternatively, ``"{:<10}".format(s)`` or ``"{:>10}".format(s)``. """ + print("abc".ljust(10)) diff --git a/tests/cpydiff/types_str_rsplitnone.py b/tests/cpydiff/types_str_rsplitnone.py index 5d334fea2f8..ae524620a08 100644 --- a/tests/cpydiff/types_str_rsplitnone.py +++ b/tests/cpydiff/types_str_rsplitnone.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("a a a".rsplit(None, 1)) diff --git a/tests/cpydiff/types_str_subscrstep.py b/tests/cpydiff/types_str_subscrstep.py index 2d3245e5582..cd57f18ccb3 100644 --- a/tests/cpydiff/types_str_subscrstep.py +++ b/tests/cpydiff/types_str_subscrstep.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print("abcdefghi"[0:9:2]) diff --git a/tests/cpydiff/types_tuple_subscrstep.py b/tests/cpydiff/types_tuple_subscrstep.py index f90f3c5bf4d..60ba31af4cf 100644 --- a/tests/cpydiff/types_tuple_subscrstep.py +++ b/tests/cpydiff/types_tuple_subscrstep.py @@ -4,4 +4,5 @@ cause: Unknown workaround: Unknown """ + print((1, 2, 3, 4)[0:4:2]) diff --git a/tests/ports/cc3200/pin.py b/tests/ports/cc3200/pin.py index 43537bba5ce..d7b0fa976cd 100644 --- a/tests/ports/cc3200/pin.py +++ b/tests/ports/cc3200/pin.py @@ -3,6 +3,7 @@ pull up or pull down connected. GP12 and GP17 must be connected together """ + from machine import Pin import os diff --git a/tests/unicode/unicode.py b/tests/unicode/unicode.py index 072e049fde4..58d406e63eb 100644 --- a/tests/unicode/unicode.py +++ b/tests/unicode/unicode.py @@ -5,7 +5,7 @@ # Test all three forms of Unicode escape, and # all blocks of UTF-8 byte patterns -s = "a\xA9\xFF\u0123\u0800\uFFEE\U0001F44C" +s = "a\xa9\xff\u0123\u0800\uffee\U0001f44c" for i in range(-len(s), len(s)): print("s[%d]: %s %X" % (i, s[i], ord(s[i]))) print("s[:%d]: %d chars, '%s'" % (i, len(s[:i]), s[:i])) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 578b6c136f5..c0b5734389c 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -22,9 +22,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -""" gen-cpydiff generates documentation which outlines operations that differ between MicroPython - and CPython. This script is called by the docs Makefile for html and Latex and may be run - manually using the command make gen-cpydiff. """ +"""gen-cpydiff generates documentation which outlines operations that differ between MicroPython +and CPython. This script is called by the docs Makefile for html and Latex and may be run +manually using the command make gen-cpydiff.""" import os import subprocess diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index d845796953a..c8fdc54a889 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -73,7 +73,7 @@ def fs_listdir(self, src=""): def repr_consumer(b): buf.extend(b.replace(b"\x04", b"")) - cmd = "import os\nfor f in os.ilistdir(%s):\n" " print(repr(f), end=',')" % ( + cmd = "import os\nfor f in os.ilistdir(%s):\n print(repr(f), end=',')" % ( ("'%s'" % src) if src else "" ) try: diff --git a/tools/pyboard.py b/tools/pyboard.py index 0cf5b3d4659..d49365f617e 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -530,7 +530,7 @@ def fs_listdir(self, src=""): def repr_consumer(b): buf.extend(b.replace(b"\x04", b"")) - cmd = "import os\nfor f in os.ilistdir(%s):\n" " print(repr(f), end=',')" % ( + cmd = "import os\nfor f in os.ilistdir(%s):\n print(repr(f), end=',')" % ( ("'%s'" % src) if src else "" ) try: diff --git a/tools/pydfu.py b/tools/pydfu.py index e1e4074a6ba..cd7354818cd 100755 --- a/tools/pydfu.py +++ b/tools/pydfu.py @@ -344,8 +344,7 @@ def read_dfu_file(filename): # B uint8_t targets Number of targets dfu_prefix, data = consume("<5sBIB", data, "signature version size targets") print( - " %(signature)s v%(version)d, image size: %(size)d, " - "targets: %(targets)d" % dfu_prefix + " %(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d" % dfu_prefix ) for target_idx in range(dfu_prefix["targets"]): # Decode the Image Prefix @@ -359,7 +358,7 @@ def read_dfu_file(filename): # I uint32_t size Size of image (without prefix) # I uint32_t elements Number of elements in the image img_prefix, data = consume( - "<6sBI255s2I", data, "signature altsetting named name " "size elements" + "<6sBI255s2I", data, "signature altsetting named name size elements" ) img_prefix["num"] = target_idx if img_prefix["named"]: From 8ce7a58be21eb77532b109594abcc4e47aa8d887 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 25 Feb 2025 10:43:01 +0100 Subject: [PATCH 0315/2098] tests: Four typos in tests directory. Found by codespell. Signed-off-by: Christian Clauss --- tests/README.md | 2 +- tests/extmod_hardware/machine_pwm.py | 2 +- tests/ports/esp32/esp32_nvs.py | 2 +- tests/run-tests.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/README.md b/tests/README.md index 891668c8b87..21e14eee5e1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,7 +13,7 @@ target platform and run the appropriate set of tests for that platform. For exa That will run tests on the `/dev/ttyACM0` serial port. You can also use shortcut device names like `a` for `/dev/ttyACM` and `c` for `COM`. Use -`./run-tests.py --help` to see all of the device possibilites, and other options. +`./run-tests.py --help` to see all of the device possibilities, and other options. There are three kinds of tests: diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 014030be5c6..e27da325486 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -1,4 +1,4 @@ -# Test machine.PWM, frequncy and duty cycle (using machine.time_pulse_us). +# Test machine.PWM, frequency and duty cycle (using machine.time_pulse_us). # # IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input # pins must be wired together (see the variable `pwm_pulse_pins`). diff --git a/tests/ports/esp32/esp32_nvs.py b/tests/ports/esp32/esp32_nvs.py index fd8b152ca71..e6c308cde2f 100644 --- a/tests/ports/esp32/esp32_nvs.py +++ b/tests/ports/esp32/esp32_nvs.py @@ -4,7 +4,7 @@ nvs = NVS("mp-test") -# test setting and gettin an integer kv +# test setting and getting an integer kv nvs.set_i32("key1", 1234) print(nvs.get_i32("key1")) nvs.set_i32("key2", -503) diff --git a/tests/run-tests.py b/tests/run-tests.py index b108b163950..1af3dfc2973 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -848,7 +848,7 @@ def run_one_test(test_file): test_file_abspath = os.path.abspath(test_file).replace("\\", "/") if args.filters: - # Default verdict is the opposit of the first action + # Default verdict is the opposite of the first action verdict = "include" if args.filters[0][0] == "exclude" else "exclude" for action, pat in args.filters: if pat.search(test_file): From 731f7adf4f4541a36b32d59087148f78b39d7a27 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Feb 2025 16:11:13 +1100 Subject: [PATCH 0316/2098] stm32/sdcard: Fix unchecked uint32_t overflow in SD card driver. Manifests as `readblocks(-1, buf)` failing. The ST HAL does a bounds check, but it checks `(block_num + num_blocks)` is within bounds, so if these values overflow then it allows the read which seems to hang some SD Cards (but not all). Fix by explicitly testing for overflow in our layer of the driver. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/sdcard.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index a7d06293996..eef6548ddf4 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -498,13 +498,27 @@ static HAL_StatusTypeDef sdcard_wait_finished(uint32_t timeout) { return HAL_OK; } -mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { +static HAL_StatusTypeDef sdcard_common_checks(uint32_t block_num, uint32_t num_blocks) { // check that SD card is initialised if (!(pyb_sdmmc_flags & PYB_SDMMC_FLAG_ACTIVE)) { return HAL_ERROR; } - HAL_StatusTypeDef err = HAL_OK; + // check that adding block_num & num_blocks don't overflow + // (the ST HAL does a bounds check, but only after adding them...) + uint32_t end_block; + if (__builtin_add_overflow(block_num, num_blocks, &end_block)) { + return HAL_ERROR; + } + + return HAL_OK; +} + +mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + HAL_StatusTypeDef err = sdcard_common_checks(block_num, num_blocks); + if (err != HAL_OK) { + return err; + } // check that dest pointer is aligned on a 4-byte boundary uint8_t *orig_dest = NULL; @@ -595,13 +609,11 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo } mp_uint_t sdcard_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { - // check that SD card is initialised - if (!(pyb_sdmmc_flags & PYB_SDMMC_FLAG_ACTIVE)) { - return HAL_ERROR; + HAL_StatusTypeDef err = sdcard_common_checks(block_num, num_blocks); + if (err != HAL_OK) { + return err; } - HAL_StatusTypeDef err = HAL_OK; - // check that src pointer is aligned on a 4-byte boundary if (((uint32_t)src & 3) != 0) { // pointer is not aligned, so allocate a temporary block to do the write From 78728dc94abf9482d07efb3056d03ff6d41d7b1a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Feb 2025 16:43:58 +1100 Subject: [PATCH 0317/2098] stm32/sdcard: Drop the pyb.SDCard timeout from 60 to 30 seconds. 60 seconds is long enough that the USB serial connection drops out before it times out (at least on my computer). Also refactor out the timeout argument from sdcard_wait_finished, to try and save a little code size. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/sdcard.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index eef6548ddf4..706d6315c44 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -158,6 +158,8 @@ static uint8_t pyb_sdmmc_flags; +#define TIMEOUT_MS 30000 + // TODO: I think that as an optimization, we can allocate these dynamically // if an sd card is detected. This will save approx 260 bytes of RAM // when no sdcard was being used. @@ -441,7 +443,7 @@ static void sdcard_reset_periph(void) { SDIO->ICR = SDMMC_STATIC_FLAGS; } -static HAL_StatusTypeDef sdcard_wait_finished(uint32_t timeout) { +static HAL_StatusTypeDef sdcard_wait_finished(void) { // Wait for HAL driver to be ready (eg for DMA to finish) uint32_t start = HAL_GetTick(); for (;;) { @@ -463,7 +465,7 @@ static HAL_StatusTypeDef sdcard_wait_finished(uint32_t timeout) { } __WFI(); enable_irq(irq_state); - if (HAL_GetTick() - start >= timeout) { + if (HAL_GetTick() - start >= TIMEOUT_MS) { return HAL_TIMEOUT; } } @@ -490,7 +492,7 @@ static HAL_StatusTypeDef sdcard_wait_finished(uint32_t timeout) { if (!(state == HAL_SD_CARD_SENDING || state == HAL_SD_CARD_RECEIVING || state == HAL_SD_CARD_PROGRAMMING)) { return HAL_ERROR; } - if (HAL_GetTick() - start >= timeout) { + if (HAL_GetTick() - start >= TIMEOUT_MS) { return HAL_TIMEOUT; } __WFI(); @@ -569,7 +571,7 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo err = HAL_SD_ReadBlocks_DMA(&sdmmc_handle.sd, dest, block_num, num_blocks); } if (err == HAL_OK) { - err = sdcard_wait_finished(60000); + err = sdcard_wait_finished(); } #if SDIO_USE_GPDMA @@ -588,14 +590,14 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo } else { #if MICROPY_HW_ENABLE_MMCARD if (pyb_sdmmc_flags & PYB_SDMMC_FLAG_MMC) { - err = HAL_MMC_ReadBlocks(&sdmmc_handle.mmc, dest, block_num, num_blocks, 60000); + err = HAL_MMC_ReadBlocks(&sdmmc_handle.mmc, dest, block_num, num_blocks, TIMEOUT_MS); } else #endif { - err = HAL_SD_ReadBlocks(&sdmmc_handle.sd, dest, block_num, num_blocks, 60000); + err = HAL_SD_ReadBlocks(&sdmmc_handle.sd, dest, block_num, num_blocks, TIMEOUT_MS); } if (err == HAL_OK) { - err = sdcard_wait_finished(60000); + err = sdcard_wait_finished(); } } @@ -662,7 +664,7 @@ mp_uint_t sdcard_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t n err = HAL_SD_WriteBlocks_DMA(&sdmmc_handle.sd, (uint8_t *)src, block_num, num_blocks); } if (err == HAL_OK) { - err = sdcard_wait_finished(60000); + err = sdcard_wait_finished(); } #if SDIO_USE_GPDMA @@ -681,14 +683,14 @@ mp_uint_t sdcard_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t n } else { #if MICROPY_HW_ENABLE_MMCARD if (pyb_sdmmc_flags & PYB_SDMMC_FLAG_MMC) { - err = HAL_MMC_WriteBlocks(&sdmmc_handle.mmc, (uint8_t *)src, block_num, num_blocks, 60000); + err = HAL_MMC_WriteBlocks(&sdmmc_handle.mmc, (uint8_t *)src, block_num, num_blocks, TIMEOUT_MS); } else #endif { - err = HAL_SD_WriteBlocks(&sdmmc_handle.sd, (uint8_t *)src, block_num, num_blocks, 60000); + err = HAL_SD_WriteBlocks(&sdmmc_handle.sd, (uint8_t *)src, block_num, num_blocks, TIMEOUT_MS); } if (err == HAL_OK) { - err = sdcard_wait_finished(60000); + err = sdcard_wait_finished(); } } From e3101ce1b3782955ea2d103922f0dc19bc0331f6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 25 Feb 2025 15:14:08 +1100 Subject: [PATCH 0318/2098] qemu/boards/SABRELITE: Increase MicroPython heap to 160k. Signed-off-by: Damien George --- ports/qemu/boards/SABRELITE/mpconfigboard.mk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/qemu/boards/SABRELITE/mpconfigboard.mk b/ports/qemu/boards/SABRELITE/mpconfigboard.mk index 80ea08f12cf..e56dba712ff 100644 --- a/ports/qemu/boards/SABRELITE/mpconfigboard.mk +++ b/ports/qemu/boards/SABRELITE/mpconfigboard.mk @@ -12,6 +12,10 @@ LDSCRIPT = mcu/arm/imx6.ld SRC_BOARD_O = shared/runtime/gchelper_generic.o +# Use a larger heap than the default so tests run with the native emitter have +# enough memory (because emitted ARM machine code is larger than Thumb2 code). +MICROPY_HEAP_SIZE ?= 163840 + # It's really armv7a but closest supported value is armv6. MPY_CROSS_FLAGS += -march=armv6 From 14ba32bb205fa1f7d6ac456879b08aadb4e1aaf7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 24 Feb 2025 23:14:48 +1100 Subject: [PATCH 0319/2098] extmod/vfs_rom: Add bounds checking for all filesystem accesses. Testing with ROMFS shows that it is relatively easy to end up with a corrupt filesystem on the device -- eg due to the ROMFS deploy process stopping half way through -- which could lead to hard crashes. Notably, there can be boot loops trying to mount a corrupt filesystem, crashes when importing modules like `os` that first scan the filesystem for `os.py`, and crashing when deploying a new ROMFS in certain cases because the old one is removed while still mounted. The main problem is that `mp_decode_uint()` has an loop that keeps going as long as it reads 0xff byte values, which can happen in the case of erased and unwritten flash. This commit adds full bounds checking in the new `mp_decode_uint_checked()` function, and that makes all ROMFS filesystem accesses robust. Signed-off-by: Damien George --- extmod/vfs_rom.c | 114 +++++++++++++++++++++++++++++++--------- tests/extmod/vfs_rom.py | 73 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 24 deletions(-) diff --git a/extmod/vfs_rom.c b/extmod/vfs_rom.c index 99ddaba95ed..ff3652d2ce3 100644 --- a/extmod/vfs_rom.c +++ b/extmod/vfs_rom.c @@ -91,6 +91,7 @@ #define ROMFS_RECORD_KIND_DATA_POINTER (3) #define ROMFS_RECORD_KIND_DIRECTORY (4) #define ROMFS_RECORD_KIND_FILE (5) +#define ROMFS_RECORD_KIND_FILESYSTEM (0x14a6b1) typedef mp_uint_t record_kind_t; @@ -101,34 +102,72 @@ struct _mp_obj_vfs_rom_t { const uint8_t *filesystem_end; }; -static record_kind_t extract_record(const uint8_t **fs, const uint8_t **fs_next) { - record_kind_t record_kind = mp_decode_uint(fs); - mp_uint_t record_len = mp_decode_uint(fs); +// Returns 0 for success, -1 for failure. +static int mp_decode_uint_checked(const uint8_t **ptr, const uint8_t *ptr_max, mp_uint_t *value_out) { + mp_uint_t unum = 0; + byte val; + const uint8_t *p = *ptr; + do { + if (p >= ptr_max) { + return -1; + } + val = *p++; + unum = (unum << 7) | (val & 0x7f); + } while ((val & 0x80) != 0); + *ptr = p; + *value_out = unum; + return 0; +} + +static record_kind_t extract_record(const uint8_t **fs, const uint8_t **fs_next, const uint8_t *fs_max) { + mp_uint_t record_kind; + if (mp_decode_uint_checked(fs, fs_max, &record_kind) != 0) { + return ROMFS_RECORD_KIND_UNUSED; + } + mp_uint_t record_len; + if (mp_decode_uint_checked(fs, fs_max, &record_len) != 0) { + return ROMFS_RECORD_KIND_UNUSED; + } *fs_next = *fs + record_len; return record_kind; } -static void extract_data(mp_obj_vfs_rom_t *self, const uint8_t *fs, const uint8_t *fs_top, size_t *size_out, const uint8_t **data_out) { - *size_out = 0; - *data_out = NULL; +// Returns 0 for success, a negative integer for failure. +static int extract_data(mp_obj_vfs_rom_t *self, const uint8_t *fs, const uint8_t *fs_top, size_t *size_out, const uint8_t **data_out) { while (fs < fs_top) { const uint8_t *fs_next; - record_kind_t record_kind = extract_record(&fs, &fs_next); - if (record_kind == ROMFS_RECORD_KIND_DATA_VERBATIM) { - // Verbatim data. - *size_out = fs_next - fs; - *data_out = fs; + record_kind_t record_kind = extract_record(&fs, &fs_next, fs_top); + if (record_kind == ROMFS_RECORD_KIND_UNUSED) { + // Corrupt filesystem. break; + } else if (record_kind == ROMFS_RECORD_KIND_DATA_VERBATIM) { + // Verbatim data. + if (size_out != NULL) { + *size_out = fs_next - fs; + *data_out = fs; + } + return 0; } else if (record_kind == ROMFS_RECORD_KIND_DATA_POINTER) { // Pointer to data. - *size_out = mp_decode_uint(&fs); - *data_out = self->filesystem + mp_decode_uint(&fs); - break; + mp_uint_t size; + if (mp_decode_uint_checked(&fs, fs_next, &size) != 0) { + break; + } + mp_uint_t offset; + if (mp_decode_uint_checked(&fs, fs_next, &offset) != 0) { + break; + } + if (size_out != NULL) { + *size_out = size; + *data_out = self->filesystem + offset; + } + return 0; } else { // Skip this record. fs = fs_next; } } + return -MP_EIO; } // Searches for `path` in the filesystem. @@ -144,10 +183,17 @@ mp_import_stat_t mp_vfs_rom_search_filesystem(mp_obj_vfs_rom_t *self, const char } while (path_len > 0 && fs < fs_top) { const uint8_t *fs_next; - record_kind_t record_kind = extract_record(&fs, &fs_next); - if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { + record_kind_t record_kind = extract_record(&fs, &fs_next, fs_top); + if (record_kind == ROMFS_RECORD_KIND_UNUSED) { + // Corrupt filesystem. + return MP_IMPORT_STAT_NO_EXIST; + } else if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { // A directory or file record. - mp_uint_t name_len = mp_decode_uint(&fs); + mp_uint_t name_len; + if (mp_decode_uint_checked(&fs, fs_next, &name_len) != 0) { + // Corrupt filesystem. + return MP_IMPORT_STAT_NO_EXIST; + } if ((name_len == path_len || (name_len < path_len && path[name_len] == '/')) && memcmp(path, fs, name_len) == 0) { @@ -167,8 +213,9 @@ mp_import_stat_t mp_vfs_rom_search_filesystem(mp_obj_vfs_rom_t *self, const char if (path_len != 0) { return MP_IMPORT_STAT_NO_EXIST; } - if (size_out != NULL) { - extract_data(self, fs, fs_top, size_out, data_out); + if (extract_data(self, fs, fs_top, size_out, data_out) != 0) { + // Corrupt filesystem. + return MP_IMPORT_STAT_NO_EXIST; } return MP_IMPORT_STAT_FILE; } @@ -214,7 +261,15 @@ static mp_obj_t vfs_rom_make_new(const mp_obj_type_t *type, size_t n_args, size_ } // The ROMFS is a record itself, so enter into it and compute its limit. - extract_record(&self->filesystem, &self->filesystem_end); + record_kind_t record_kind = extract_record(&self->filesystem, &self->filesystem_end, self->filesystem + bufinfo.len); + if (record_kind != ROMFS_RECORD_KIND_FILESYSTEM) { + mp_raise_OSError(MP_ENODEV); + } + + // Check the filesystem is within the limits of the input buffer. + if (self->filesystem_end > (const uint8_t *)bufinfo.buf + bufinfo.len) { + mp_raise_OSError(MP_ENODEV); + } return MP_OBJ_FROM_PTR(self); } @@ -259,13 +314,21 @@ static mp_obj_t vfs_rom_ilistdir_it_iternext(mp_obj_t self_in) { while (self->index < self->index_top) { const uint8_t *index_next; - record_kind_t record_kind = extract_record(&self->index, &index_next); + record_kind_t record_kind = extract_record(&self->index, &index_next, self->index_top); uint32_t type; mp_uint_t name_len; size_t data_len; - if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { + if (record_kind == ROMFS_RECORD_KIND_UNUSED) { + // Corrupt filesystem. + self->index = self->index_top; + break; + } else if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) { // A directory or file record. - name_len = mp_decode_uint(&self->index); + if (mp_decode_uint_checked(&self->index, index_next, &name_len) != 0) { + // Corrupt filesystem. + self->index = self->index_top; + break; + } if (record_kind == ROMFS_RECORD_KIND_DIRECTORY) { // A directory. type = MP_S_IFDIR; @@ -274,7 +337,10 @@ static mp_obj_t vfs_rom_ilistdir_it_iternext(mp_obj_t self_in) { // A file. type = MP_S_IFREG; const uint8_t *data_value; - extract_data(self->vfs_rom, self->index + name_len, index_next, &data_len, &data_value); + if (extract_data(self->vfs_rom, self->index + name_len, index_next, &data_len, &data_value) != 0) { + // Corrupt filesystem. + break; + } } } else { // Skip this record. diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index f7958a93962..dc88481c028 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -223,6 +223,79 @@ def test_unknown_record(self): self.assertEqual(f.read(), b"contents") +class TestCorrupt(unittest.TestCase): + def test_corrupt_filesystem(self): + # Make the filesystem length bigger than the buffer. + romfs = bytearray(make_romfs(())) + romfs[3] = 0x01 + with self.assertRaises(OSError): + vfs.VfsRom(romfs) + + # Corrupt the filesystem length. + romfs = bytearray(make_romfs(())) + romfs[3] = 0xFF + with self.assertRaises(OSError): + vfs.VfsRom(romfs) + + # Corrupt the contents of the filesystem. + romfs = bytearray(make_romfs(())) + romfs[3] = 0x01 + romfs.extend(b"\xff\xff") + fs = vfs.VfsRom(romfs) + with self.assertRaises(OSError): + fs.stat("a") + self.assertEqual(list(fs.ilistdir("")), []) + + def test_corrupt_file_entry(self): + romfs = make_romfs((("file", b"data"),)) + + # Corrupt the length of filename. + romfs_corrupt = bytearray(romfs) + romfs_corrupt[7:] = b"\xff" * (len(romfs) - 7) + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + self.assertEqual(list(fs.ilistdir("")), []) + + # Erase the data record (change it to a padding record). + romfs_corrupt = bytearray(romfs) + romfs_corrupt[12] = VfsRomWriter.ROMFS_RECORD_KIND_PADDING + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + self.assertEqual(list(fs.ilistdir("")), []) + + # Corrupt the header of the data record. + romfs_corrupt = bytearray(romfs) + romfs_corrupt[12:] = b"\xff" * (len(romfs) - 12) + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + + # Corrupt the interior of the data record. + romfs_corrupt = bytearray(romfs) + romfs_corrupt[13:] = b"\xff" * (len(romfs) - 13) + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + + # Change the data record to an indirect pointer and corrupt the length. + romfs_corrupt = bytearray(romfs) + romfs_corrupt[12] = VfsRomWriter.ROMFS_RECORD_KIND_DATA_POINTER + romfs_corrupt[14:18] = b"\xff\xff\xff\xff" + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + + # Change the data record to an indirect pointer and corrupt the offset. + romfs_corrupt = bytearray(romfs) + romfs_corrupt[12] = VfsRomWriter.ROMFS_RECORD_KIND_DATA_POINTER + romfs_corrupt[14:18] = b"\x00\xff\xff\xff" + fs = vfs.VfsRom(romfs_corrupt) + with self.assertRaises(OSError): + fs.stat("file") + + class TestStandalone(TestBase): def test_constructor(self): self.assertIsInstance(vfs.VfsRom(self.romfs), vfs.VfsRom) From dc2c33b07f83ac4fcbcbcbb294e5726a95507490 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 4 Jan 2025 12:51:18 +0100 Subject: [PATCH 0320/2098] py/emitinlinerv32: Fix compilation with ESP-IDF v5.2 and later. This commit fixes a compilation warning (turned error) about a potentially uninitialised variable being used. The warning can be ignored as the variable in question is always written to, but the code has been changed to silence that warning. Signed-off-by: Alessandro Gatti --- py/emitinlinerv32.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index 179e289c67b..ba171b4e6fa 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -709,9 +709,11 @@ static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr o return false; } - mp_uint_t rd; - mp_uint_t rs1; - parse_register_node(nodes[0], &rd, opcode_data->argument1_kind & C); + mp_uint_t rd = 0; + mp_uint_t rs1 = 0; + if (!parse_register_node(nodes[0], &rd, opcode_data->argument1_kind & C)) { + return false; + } if (!parse_register_node(nodes[1], &rs1, opcode_data->argument3_kind & C)) { return false; } From 8633abc08291d98035c65904dc4dab9983ddada3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 4 Jan 2025 12:54:44 +0100 Subject: [PATCH 0321/2098] py/emitinlinerv32: Reduce the footprint of compiled code. This commit introduces a few changes aimed at reducing the amount of space taken by the inline assembler once compiled: * The register string table uses 2 bytes for each qstr rather than the usual 4 * The opcode table uses 2 bytes for each qstr rather than the usual 4 * Opcode masks are not embedded in each opcode entry but looked up via an additional smaller table, reducing the number of bytes taken by an opcode's masks from 12 to 2 (with a fixed overhead of 24 bytes for the the masks themselves stored elsewhere) * Some error messages had a trailing period, now removed * Error messages have been parameterised when possible, and the overall text length is smaller. Signed-off-by: Alessandro Gatti --- py/emitinlinerv32.c | 332 ++++++++++++++++++++++++-------------------- 1 file changed, 181 insertions(+), 151 deletions(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index ba171b4e6fa..a9a81ddf16c 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -60,7 +60,7 @@ struct _emit_inline_asm_t { qstr *label_lookup; }; -static const qstr REGISTERS_QSTR_TABLE[] = { +static const qstr_short_t REGISTERS_QSTR_TABLE[] = { MP_QSTR_zero, MP_QSTR_ra, MP_QSTR_sp, MP_QSTR_gp, MP_QSTR_tp, MP_QSTR_t0, MP_QSTR_t1, MP_QSTR_t2, MP_QSTR_s0, MP_QSTR_s1, MP_QSTR_a0, MP_QSTR_a1, MP_QSTR_a2, MP_QSTR_a3, MP_QSTR_a4, MP_QSTR_a5, MP_QSTR_a6, MP_QSTR_a7, MP_QSTR_s2, MP_QSTR_s3, MP_QSTR_s4, MP_QSTR_s5, MP_QSTR_s6, MP_QSTR_s7, @@ -185,18 +185,21 @@ static bool emit_inline_rv32_label(emit_inline_asm_t *emit, mp_uint_t label_num, return true; } -#define CALL_RRR 0 // Register, Register, Register -#define CALL_RR 1 // Register, Register -#define CALL_RRI 2 // Register, Register, Immediate -#define CALL_RRL 3 // Register, Register, Label -#define CALL_RI 4 // Register, Immediate -#define CALL_L 5 // Label -#define CALL_R 6 // Register -#define CALL_RL 7 // Register, Label -#define CALL_N 8 // No arguments -#define CALL_I 9 // Immediate -#define CALL_RII 10 // Register, Immediate, Immediate -#define CALL_RIR 11 // Register, Immediate(Register) +typedef enum { + CALL_RRR, // Opcode Register, Register, Register + CALL_RR, // Opcode Register, Register + CALL_RRI, // Opcode Register, Register, Immediate + CALL_RRL, // Opcode Register, Register, Label + CALL_RI, // Opcode Register, Immediate + CALL_L, // Opcode Label + CALL_R, // Opcode Register + CALL_RL, // Opcode Register, Label + CALL_N, // Opcode + CALL_I, // Opcode Immediate + CALL_RII, // Opcode Register, Register, Immediate + CALL_RIR, // Opcode Register, Immediate(Register) + CALL_COUNT +} call_convention_t; #define N 0 // No argument #define R 1 // Register @@ -217,18 +220,21 @@ typedef void (*call_r_t)(asm_rv32_t *state, mp_uint_t rd); typedef void (*call_n_t)(asm_rv32_t *state); typedef struct _opcode_t { - qstr qstring; - void *emitter; + qstr_short_t qstring; + uint16_t argument1_mask : 4; + uint16_t argument2_mask : 4; + uint16_t argument3_mask : 4; + uint16_t arguments_count : 2; + // 2 bits available here uint32_t calling_convention : 4; uint32_t argument1_kind : 4; - uint32_t argument1_shift : 5; + uint32_t argument1_shift : 4; uint32_t argument2_kind : 4; - uint32_t argument2_shift : 5; + uint32_t argument2_shift : 4; uint32_t argument3_kind : 4; - uint32_t argument3_shift : 5; - uint32_t argument1_mask; - uint32_t argument2_mask; - uint32_t argument3_mask; + uint32_t argument3_shift : 4; + // 4 bits available here + void *emitter; } opcode_t; #define opcode_li asm_rv32_emit_optimised_load_immediate @@ -249,89 +255,129 @@ static void opcode_la(asm_rv32_t *state, mp_uint_t rd, mp_int_t displacement) { #define IZ (I | Z) #define IUZ (I | U | Z) +#define MASK_NOT_USED 0 + +enum { + MASK_FFFFFFFF, + MASK_00000FFF, + MASK_FFFFF000, + MASK_00001FFE, + MASK_0000001F, + MASK_FFFFFFFE, + MASK_0000003F, + MASK_0000FF00, + MASK_000003FC, + MASK_000001FE, + MASK_00000FFE, + MASK_FFFFFFFA, + MASK_0001F800, + MASK_0000007C, + MASK_000000FC, + MASK_001FFFFE, +}; + +static const uint32_t OPCODE_MASKS[] = { + [MASK_FFFFFFFF] = 0xFFFFFFFF, + [MASK_00000FFF] = 0x00000FFF, + [MASK_FFFFF000] = 0xFFFFF000, + [MASK_00001FFE] = 0x00001FFE, + [MASK_0000001F] = 0x0000001F, + [MASK_FFFFFFFE] = 0xFFFFFFFE, + [MASK_0000003F] = 0x0000003F, + [MASK_0000FF00] = 0x0000FF00, + [MASK_000003FC] = 0x000003FC, + [MASK_000001FE] = 0x000001FE, + [MASK_00000FFE] = 0x00000FFE, + [MASK_FFFFFFFA] = 0xFFFFFFFA, + [MASK_0001F800] = 0x0001F800, + [MASK_0000007C] = 0x0000007C, + [MASK_000000FC] = 0x000000FC, + [MASK_001FFFFE] = 0x001FFFFE, +}; + static const opcode_t OPCODES[] = { - { MP_QSTR_add, asm_rv32_opcode_add, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_addi, asm_rv32_opcode_addi, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_and_, asm_rv32_opcode_and, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_andi, asm_rv32_opcode_andi, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_auipc, asm_rv32_opcode_auipc, CALL_RI, R, 0, I, 12, N, 0, 0xFFFFFFFF, 0xFFFFF000, 0x00000000 }, - { MP_QSTR_beq, asm_rv32_opcode_beq, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_bge, asm_rv32_opcode_bge, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_bgeu, asm_rv32_opcode_bgeu, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_blt, asm_rv32_opcode_blt, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_bltu, asm_rv32_opcode_bltu, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_bne, asm_rv32_opcode_bne, CALL_RRL, R, 0, R, 0, L, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00001FFE }, - { MP_QSTR_csrrc, asm_rv32_opcode_csrrc, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_csrrs, asm_rv32_opcode_csrrs, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_csrrw, asm_rv32_opcode_csrrw, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_csrrci, asm_rv32_opcode_csrrci, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, - { MP_QSTR_csrrsi, asm_rv32_opcode_csrrsi, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, - { MP_QSTR_csrrwi, asm_rv32_opcode_csrrwi, CALL_RII, R, 0, IU, 0, IU, 0, 0xFFFFFFFF, 0x00000FFF, 0x0000001F }, - { MP_QSTR_c_add, asm_rv32_opcode_cadd, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFE, 0xFFFFFFFE, 0x00000000 }, - { MP_QSTR_c_addi, asm_rv32_opcode_caddi, CALL_RI, R, 0, IZ, 0, N, 0, 0xFFFFFFFE, 0x0000003F, 0x00000000 }, - { MP_QSTR_c_addi4spn, asm_rv32_opcode_caddi4spn, CALL_RI, R, 0, IUZ, 0, N, 0, 0x0000FF00, 0x000003FC, 0x00000000 }, - { MP_QSTR_c_and, asm_rv32_opcode_cand, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, - { MP_QSTR_c_andi, asm_rv32_opcode_candi, CALL_RI, RC, 0, I, 0, N, 0, 0x0000FF00, 0x0000003F, 0x00000000 }, - { MP_QSTR_c_beqz, asm_rv32_opcode_cbeqz, CALL_RL, RC, 0, L, 0, N, 0, 0x0000FF00, 0x000001FE, 0x00000000 }, - { MP_QSTR_c_bnez, asm_rv32_opcode_cbnez, CALL_RL, RC, 0, L, 0, N, 0, 0x0000FF00, 0x000001FE, 0x00000000 }, - { MP_QSTR_c_ebreak, asm_rv32_opcode_cebreak, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, - { MP_QSTR_c_j, asm_rv32_opcode_cj, CALL_L, L, 0, N, 0, N, 0, 0x00000FFE, 0x00000000, 0x00000000 }, - { MP_QSTR_c_jal, asm_rv32_opcode_cjal, CALL_L, L, 0, N, 0, N, 0, 0x00000FFE, 0x00000000, 0x00000000 }, - { MP_QSTR_c_jalr, asm_rv32_opcode_cjalr, CALL_R, R, 0, N, 0, N, 0, 0xFFFFFFFE, 0x00000000, 0x00000000 }, - { MP_QSTR_c_jr, asm_rv32_opcode_cjr, CALL_R, R, 0, N, 0, N, 0, 0xFFFFFFFE, 0x00000000, 0x00000000 }, - { MP_QSTR_c_li, asm_rv32_opcode_cli, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFE, 0x0000003F, 0x00000000 }, - { MP_QSTR_c_lui, asm_rv32_opcode_clui, CALL_RI, R, 0, IUZ, 12, N, 0, 0xFFFFFFFA, 0x0001F800, 0x00000000 }, - { MP_QSTR_c_lw, asm_rv32_opcode_clw, CALL_RIR, RC, 0, I, 0, RC, 0, 0x0000FF00, 0x0000007C, 0x0000FF00 }, - { MP_QSTR_c_lwsp, asm_rv32_opcode_clwsp, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFE, 0x000000FC, 0x00000000 }, - { MP_QSTR_c_mv, asm_rv32_opcode_cmv, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFE, 0xFFFFFFFE, 0x00000000 }, - { MP_QSTR_c_nop, asm_rv32_opcode_cnop, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, - { MP_QSTR_c_or, asm_rv32_opcode_cor, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, - { MP_QSTR_c_slli, asm_rv32_opcode_cslli, CALL_RI, R, 0, IU, 0, N, 0, 0xFFFFFFFE, 0x0000001F, 0x00000000 }, - { MP_QSTR_c_srai, asm_rv32_opcode_csrai, CALL_RI, RC, 0, IU, 0, N, 0, 0x0000FF00, 0x0000001F, 0x00000000 }, - { MP_QSTR_c_srli, asm_rv32_opcode_csrli, CALL_RI, RC, 0, IU, 0, N, 0, 0x0000FF00, 0x0000001F, 0x00000000 }, - { MP_QSTR_c_sub, asm_rv32_opcode_csub, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, - { MP_QSTR_c_sw, asm_rv32_opcode_csw, CALL_RIR, RC, 0, I, 0, RC, 0, 0x0000FF00, 0x0000007C, 0x0000FF00 }, - { MP_QSTR_c_swsp, asm_rv32_opcode_cswsp, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFF, 0x000000FC, 0x00000000 }, - { MP_QSTR_c_xor, asm_rv32_opcode_cxor, CALL_RR, RC, 0, RC, 0, N, 0, 0x0000FF00, 0x0000FF00, 0x00000000 }, - { MP_QSTR_div, asm_rv32_opcode_div, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_divu, asm_rv32_opcode_divu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_ebreak, asm_rv32_opcode_ebreak, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, - { MP_QSTR_ecall, asm_rv32_opcode_ecall, CALL_N, N, 0, N, 0, N, 0, 0x00000000, 0x00000000, 0x00000000 }, - { MP_QSTR_jal, asm_rv32_opcode_jal, CALL_RL, R, 0, L, 0, N, 0, 0xFFFFFFFF, 0x001FFFFE, 0x00000000 }, - { MP_QSTR_jalr, asm_rv32_opcode_jalr, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_la, opcode_la, CALL_RL, R, 0, L, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, - { MP_QSTR_lb, asm_rv32_opcode_lb, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_lbu, asm_rv32_opcode_lbu, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_lh, asm_rv32_opcode_lh, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_lhu, asm_rv32_opcode_lhu, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_li, opcode_li, CALL_RI, R, 0, I, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, - { MP_QSTR_lui, asm_rv32_opcode_lui, CALL_RI, R, 0, I, 12, N, 0, 0xFFFFFFFF, 0xFFFFF000, 0x00000000 }, - { MP_QSTR_lw, asm_rv32_opcode_lw, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_mv, asm_rv32_opcode_cmv, CALL_RR, R, 0, R, 0, N, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, - { MP_QSTR_mul, asm_rv32_opcode_mul, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_mulh, asm_rv32_opcode_mulh, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_mulhsu, asm_rv32_opcode_mulhsu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_mulhu, asm_rv32_opcode_mulhu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_or_, asm_rv32_opcode_or, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_ori, asm_rv32_opcode_ori, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_rem, asm_rv32_opcode_rem, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_remu, asm_rv32_opcode_remu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_sb, asm_rv32_opcode_sb, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_sh, asm_rv32_opcode_sh, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_sll, asm_rv32_opcode_sll, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_slli, asm_rv32_opcode_slli, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, - { MP_QSTR_slt, asm_rv32_opcode_slt, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_slti, asm_rv32_opcode_slti, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_sltiu, asm_rv32_opcode_sltiu, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, - { MP_QSTR_sltu, asm_rv32_opcode_sltu, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_sra, asm_rv32_opcode_sra, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_srai, asm_rv32_opcode_srai, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, - { MP_QSTR_srl, asm_rv32_opcode_srl, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_srli, asm_rv32_opcode_srli, CALL_RRI, R, 0, R, 0, IU, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000001F }, - { MP_QSTR_sub, asm_rv32_opcode_sub, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_sw, asm_rv32_opcode_sw, CALL_RIR, R, 0, I, 0, R, 0, 0xFFFFFFFF, 0x00000FFF, 0xFFFFFFFF }, - { MP_QSTR_xor, asm_rv32_opcode_xor, CALL_RRR, R, 0, R, 0, R, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, - { MP_QSTR_xori, asm_rv32_opcode_xori, CALL_RRI, R, 0, R, 0, I, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF }, + { MP_QSTR_add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_add }, + { MP_QSTR_addi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_addi }, + { MP_QSTR_and_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_and }, + { MP_QSTR_andi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_andi }, + { MP_QSTR_auipc, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, asm_rv32_opcode_auipc }, + { MP_QSTR_beq, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_beq }, + { MP_QSTR_bge, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bge }, + { MP_QSTR_bgeu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bgeu }, + { MP_QSTR_blt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_blt }, + { MP_QSTR_bltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bltu }, + { MP_QSTR_bne, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bne }, + { MP_QSTR_csrrc, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrc }, + { MP_QSTR_csrrs, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrs }, + { MP_QSTR_csrrw, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrw }, + { MP_QSTR_csrrci, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrci }, + { MP_QSTR_csrrsi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrsi }, + { MP_QSTR_csrrwi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrwi }, + { MP_QSTR_c_add, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cadd }, + { MP_QSTR_c_addi, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, IZ, 0, N, 0, asm_rv32_opcode_caddi }, + { MP_QSTR_c_addi4spn, MASK_0000FF00, MASK_000003FC, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 0, N, 0, asm_rv32_opcode_caddi4spn }, + { MP_QSTR_c_and, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cand }, + { MP_QSTR_c_andi, MASK_0000FF00, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, RC, 0, I, 0, N, 0, asm_rv32_opcode_candi }, + { MP_QSTR_c_beqz, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, asm_rv32_opcode_cbeqz }, + { MP_QSTR_c_bnez, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, asm_rv32_opcode_cbnez }, + { MP_QSTR_c_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_cebreak }, + { MP_QSTR_c_j, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, asm_rv32_opcode_cj }, + { MP_QSTR_c_jal, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, asm_rv32_opcode_cjal }, + { MP_QSTR_c_jalr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, asm_rv32_opcode_cjalr }, + { MP_QSTR_c_jr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, asm_rv32_opcode_cjr }, + { MP_QSTR_c_li, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_cli }, + { MP_QSTR_c_lui, MASK_FFFFFFFA, MASK_0001F800, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 12, N, 0, asm_rv32_opcode_clui }, + { MP_QSTR_c_lw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, asm_rv32_opcode_clw }, + { MP_QSTR_c_lwsp, MASK_FFFFFFFE, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_clwsp }, + { MP_QSTR_c_mv, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cmv }, + { MP_QSTR_c_nop, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_cnop }, + { MP_QSTR_c_or, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cor }, + { MP_QSTR_c_slli, MASK_FFFFFFFE, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, R, 0, IU, 0, N, 0, asm_rv32_opcode_cslli }, + { MP_QSTR_c_srai, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, asm_rv32_opcode_csrai }, + { MP_QSTR_c_srli, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, asm_rv32_opcode_csrli }, + { MP_QSTR_c_sub, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_csub }, + { MP_QSTR_c_sw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, asm_rv32_opcode_csw }, + { MP_QSTR_c_swsp, MASK_FFFFFFFF, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_cswsp }, + { MP_QSTR_c_xor, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cxor }, + { MP_QSTR_div, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_div }, + { MP_QSTR_divu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_divu }, + { MP_QSTR_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_ebreak }, + { MP_QSTR_ecall, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_ecall }, + { MP_QSTR_jal, MASK_FFFFFFFF, MASK_001FFFFE, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, asm_rv32_opcode_jal }, + { MP_QSTR_jalr, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_jalr }, + { MP_QSTR_la, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, opcode_la }, + { MP_QSTR_lb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lb }, + { MP_QSTR_lbu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lbu }, + { MP_QSTR_lh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lh }, + { MP_QSTR_lhu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lhu }, + { MP_QSTR_li, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, opcode_li }, + { MP_QSTR_lui, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, asm_rv32_opcode_lui }, + { MP_QSTR_lw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lw }, + { MP_QSTR_mv, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cmv }, + { MP_QSTR_mul, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mul }, + { MP_QSTR_mulh, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulh }, + { MP_QSTR_mulhsu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulhsu }, + { MP_QSTR_mulhu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulhu }, + { MP_QSTR_or_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_or }, + { MP_QSTR_ori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_ori }, + { MP_QSTR_rem, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_rem }, + { MP_QSTR_remu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_remu }, + { MP_QSTR_sb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sb }, + { MP_QSTR_sh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sh }, + { MP_QSTR_sll, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sll }, + { MP_QSTR_slli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_slli }, + { MP_QSTR_slt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_slt }, + { MP_QSTR_slti, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_slti }, + { MP_QSTR_sltiu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_sltiu }, + { MP_QSTR_sltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sltu }, + { MP_QSTR_sra, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sra }, + { MP_QSTR_srai, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_srai }, + { MP_QSTR_srl, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_srl }, + { MP_QSTR_srli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_srli }, + { MP_QSTR_sub, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sub }, + { MP_QSTR_sw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sw }, + { MP_QSTR_xor, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_xor }, + { MP_QSTR_xori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_xori }, }; #undef RC @@ -384,6 +430,10 @@ static bool validate_integer(mp_uint_t value, mp_uint_t mask, mp_uint_t flags) { return true; } +#define ET_WRONG_ARGUMENT_KIND MP_ERROR_TEXT("opcode '%q' argument %d: expecting %q") +#define ET_WRONG_ARGUMENTS_COUNT MP_ERROR_TEXT("opcode '%q': expecting %d arguments") +#define ET_OUT_OF_RANGE MP_ERROR_TEXT("opcode '%q' argument %d: out of range") + static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index) { assert((node_index < 3) && "Invalid argument node number."); @@ -396,19 +446,19 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, case 0: kind = opcode->argument1_kind; shift = opcode->argument1_shift; - mask = opcode->argument1_mask; + mask = OPCODE_MASKS[opcode->argument1_mask]; break; case 1: kind = opcode->argument2_kind; shift = opcode->argument2_shift; - mask = opcode->argument2_mask; + mask = OPCODE_MASKS[opcode->argument2_mask]; break; case 2: kind = opcode->argument3_kind; shift = opcode->argument3_shift; - mask = opcode->argument3_mask; + mask = OPCODE_MASKS[opcode->argument3_mask]; break; default: @@ -417,6 +467,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, switch (kind & 0x03) { case N: + assert(mask == OPCODE_MASKS[MASK_NOT_USED] && "Invalid mask index for missing operand."); return true; case R: { @@ -424,15 +475,14 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (!parse_register_node(node, ®ister_index, false)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must be a register"), - opcode_qstr, node_index + 1)); + ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_register)); return false; } if ((mask & (1U << register_index)) == 0) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d is an invalid register"), + MP_ERROR_TEXT("opcode '%q' argument %d: unknown register"), opcode_qstr, node_index + 1)); return false; } @@ -446,8 +496,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (!mp_parse_node_get_int_maybe(node, &object)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must be an integer"), - opcode_qstr, node_index + 1)); + ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_integer)); return false; } @@ -474,8 +523,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (!MP_PARSE_NODE_IS_ID(node)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must be a label"), - opcode_qstr, node_index + 1)); + ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_label)); return false; } @@ -484,7 +532,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (label_index >= emit->max_num_labels && emit->pass == MP_PASS_EMIT) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d label '%q' is undefined"), + MP_ERROR_TEXT("opcode '%q' argument %d: undefined label '%q'"), opcode_qstr, node_index + 1, qstring)); return false; } @@ -512,15 +560,13 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, out_of_range: emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), - opcode_qstr, node_index + 1)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_qstr, node_index + 1)); return false; zero_immediate: emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must not be zero"), + MP_ERROR_TEXT("opcode '%q' argument %d: must not be zero"), opcode_qstr, node_index + 1)); return false; } @@ -556,18 +602,14 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr mp_obj_t object; if (!mp_parse_node_get_int_maybe(node_struct->nodes[0], &object)) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must be an integer"), - opcode_qstr, 2)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_WRONG_ARGUMENT_KIND, opcode_qstr, 2, MP_QSTR_integer)); return false; } mp_uint_t value = mp_obj_get_int_truncated(object); value = (~value + 1) & (mp_uint_t)-1; - if (!validate_integer(value << opcode_data->argument2_shift, opcode_data->argument2_mask, opcode_data->argument2_kind)) { + if (!validate_integer(value << opcode_data->argument2_shift, OPCODE_MASKS[opcode_data->argument2_mask], opcode_data->argument2_kind)) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), - opcode_qstr, 2)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_qstr, 2)); return false; } } else { @@ -587,8 +629,7 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr invalid_structure: emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d must be an integer offset to a register"), - opcode_qstr, node_index + 1)); + ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_offset)); return false; } @@ -724,11 +765,9 @@ static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr o if (negative) { immediate = (~immediate + 1) & (mp_uint_t)-1; } - if (!is_in_signed_mask(opcode_data->argument2_mask, immediate)) { + if (!is_in_signed_mask(OPCODE_MASKS[opcode_data->argument2_mask], immediate)) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("opcode '%q' argument %d is out of range"), - opcode, 2)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode, 2)); return false; } @@ -748,49 +787,40 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin if (!opcode_data) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("unsupported or unknown RV32 instruction '%q'."), opcode)); + MP_ERROR_TEXT("unknown RV32 instruction '%q'"), opcode)); return; } - size_t required_arguments = 0; - if (opcode_data->argument1_kind != N) { - required_arguments++; - } - if (opcode_data->argument2_kind != N) { - required_arguments++; - } - if (opcode_data->argument3_kind != N) { - required_arguments++; - } - + assert((opcode_data->argument1_mask < MP_ARRAY_SIZE(OPCODE_MASKS)) && "Argument #1 opcode mask index out of bounds."); + assert((opcode_data->argument2_mask < MP_ARRAY_SIZE(OPCODE_MASKS)) && "Argument #2 opcode mask index out of bounds."); + assert((opcode_data->argument3_mask < MP_ARRAY_SIZE(OPCODE_MASKS)) && "Argument #3 opcode mask index out of bounds."); + assert((opcode_data->calling_convention < CALL_COUNT) && "Calling convention index out of bounds."); if (opcode_data->calling_convention != CALL_RIR) { - if (required_arguments != arguments_count) { + if (opcode_data->arguments_count != arguments_count) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("RV32 instruction '%q' requires %d arguments."), opcode, required_arguments)); + ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->arguments_count)); return; } - if (required_arguments >= 1 && !validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { + if (opcode_data->arguments_count >= 1 && !validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { return; } - if (required_arguments >= 2 && !validate_argument(emit, opcode, opcode_data, argument_nodes[1], 1)) { + if (opcode_data->arguments_count >= 2 && !validate_argument(emit, opcode, opcode_data, argument_nodes[1], 1)) { return; } - if (required_arguments >= 3 && !validate_argument(emit, opcode, opcode_data, argument_nodes[2], 2)) { + if (opcode_data->arguments_count >= 3 && !validate_argument(emit, opcode, opcode_data, argument_nodes[2], 2)) { return; } handle_opcode(emit, opcode, opcode_data, argument_nodes); return; } - assert(required_arguments == 3 && "Invalid arguments count for calling convention."); assert((opcode_data->argument2_kind & U) == 0 && "Offset must not be unsigned."); assert((opcode_data->argument2_kind & Z) == 0 && "Offset can be zero."); if (arguments_count != 2) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("RV32 instruction '%q' requires %d arguments."), opcode, 2)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_WRONG_ARGUMENTS_COUNT, opcode, 2)); return; } From 50fab08e6b861adf65905d6adacd74201c87ddb9 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 28 Jan 2025 14:58:29 +0100 Subject: [PATCH 0322/2098] py/emitinlinextensa: Simplify register name lookup. This commit changes the Xtensa inline assembly parser to use a slightly simpler (and probably a tiny bit more efficient) way to look up register names when decoding instruction parameters. Signed-off-by: Alessandro Gatti --- py/emitinlinextensa.c | 49 +++++++++---------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/py/emitinlinextensa.c b/py/emitinlinextensa.c index 57056d597aa..fed259cfc6b 100644 --- a/py/emitinlinextensa.c +++ b/py/emitinlinextensa.c @@ -115,50 +115,21 @@ static bool emit_inline_xtensa_label(emit_inline_asm_t *emit, mp_uint_t label_nu return true; } -typedef struct _reg_name_t { byte reg; - byte name[3]; -} reg_name_t; -static const reg_name_t reg_name_table[] = { - {0, "a0\0"}, - {1, "a1\0"}, - {2, "a2\0"}, - {3, "a3\0"}, - {4, "a4\0"}, - {5, "a5\0"}, - {6, "a6\0"}, - {7, "a7\0"}, - {8, "a8\0"}, - {9, "a9\0"}, - {10, "a10"}, - {11, "a11"}, - {12, "a12"}, - {13, "a13"}, - {14, "a14"}, - {15, "a15"}, +static const qstr_short_t REGISTERS[16] = { + MP_QSTR_a0, MP_QSTR_a1, MP_QSTR_a2, MP_QSTR_a3, MP_QSTR_a4, MP_QSTR_a5, MP_QSTR_a6, MP_QSTR_a7, + MP_QSTR_a8, MP_QSTR_a9, MP_QSTR_a10, MP_QSTR_a11, MP_QSTR_a12, MP_QSTR_a13, MP_QSTR_a14, MP_QSTR_a15 }; -// return empty string in case of error, so we can attempt to parse the string -// without a special check if it was in fact a string -static const char *get_arg_str(mp_parse_node_t pn) { - if (MP_PARSE_NODE_IS_ID(pn)) { - qstr qst = MP_PARSE_NODE_LEAF_ARG(pn); - return qstr_str(qst); - } else { - return ""; - } -} - static mp_uint_t get_arg_reg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { - const char *reg_str = get_arg_str(pn); - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(reg_name_table); i++) { - const reg_name_t *r = ®_name_table[i]; - if (reg_str[0] == r->name[0] - && reg_str[1] == r->name[1] - && reg_str[2] == r->name[2] - && (reg_str[2] == '\0' || reg_str[3] == '\0')) { - return r->reg; + if (MP_PARSE_NODE_IS_ID(pn)) { + qstr node_qstr = MP_PARSE_NODE_LEAF_ARG(pn); + for (size_t i = 0; i < MP_ARRAY_SIZE(REGISTERS); i++) { + if (node_qstr == REGISTERS[i]) { + return i; + } } } + emit_inline_xtensa_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("'%s' expects a register"), op)); From 5fdd249c55d44ed0513e931c9b7364dd8b3454d9 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 30 Jan 2025 10:23:06 +0100 Subject: [PATCH 0323/2098] py/parsenum: Reduce code footprint of mp_parse_num_float. The mantissa parsing code uses a floating point variable to accumulate digits. Using an `mp_float_uint_t` variable instead and casting to `mp_float_t` at the very end reduces code size. In some cases, it also improves the rounding behaviour as extra digits are taken into account by the int-to-float conversion code. An extra test case handles the special case where mantissa overflow occurs while processing deferred trailing zeros. Signed-off-by: Yoctopuce dev --- py/parsenum.c | 25 ++++++++++++++----------- tests/float/float_parse.py | 3 +++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index 27d66411987..3281eb4b851 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -179,39 +179,40 @@ typedef enum { } parse_dec_in_t; #if MICROPY_PY_BUILTINS_FLOAT -// DEC_VAL_MAX only needs to be rough and is used to retain precision while not overflowing +// MANTISSA_MAX is used to retain precision while not overflowing mantissa // SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float // EXACT_POWER_OF_10 is the largest value of x so that 10^x can be stored exactly in a float // Note: EXACT_POWER_OF_10 is at least floor(log_5(2^mantissa_length)). Indeed, 10^n = 2^n * 5^n // so we only have to store the 5^n part in the mantissa (the 2^n part will go into the float's // exponent). #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT -#define DEC_VAL_MAX 1e20F +#define MANTISSA_MAX 0x19999998U #define SMALL_NORMAL_VAL (1e-37F) #define SMALL_NORMAL_EXP (-37) #define EXACT_POWER_OF_10 (9) #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE -#define DEC_VAL_MAX 1e200 +#define MANTISSA_MAX 0x1999999999999998ULL #define SMALL_NORMAL_VAL (1e-307) #define SMALL_NORMAL_EXP (-307) #define EXACT_POWER_OF_10 (22) #endif // Break out inner digit accumulation routine to ease trailing zero deferral. -static void accept_digit(mp_float_t *p_dec_val, int dig, int *p_exp_extra, int in) { +static mp_float_uint_t accept_digit(mp_float_uint_t p_mantissa, unsigned int dig, int *p_exp_extra, int in) { // Core routine to ingest an additional digit. - if (*p_dec_val < DEC_VAL_MAX) { + if (p_mantissa < MANTISSA_MAX) { // dec_val won't overflow so keep accumulating - *p_dec_val = 10 * *p_dec_val + dig; if (in == PARSE_DEC_IN_FRAC) { --(*p_exp_extra); } + return 10u * p_mantissa + dig; } else { // dec_val might overflow and we anyway can't represent more digits // of precision, so ignore the digit and just adjust the exponent if (in == PARSE_DEC_IN_INTG) { ++(*p_exp_extra); } + return p_mantissa; } } #endif // MICROPY_PY_BUILTINS_FLOAT @@ -273,6 +274,7 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex // string should be a decimal number parse_dec_in_t in = PARSE_DEC_IN_INTG; bool exp_neg = false; + mp_float_uint_t mantissa = 0; int exp_val = 0; int exp_extra = 0; int trailing_zeros_intg = 0, trailing_zeros_frac = 0; @@ -288,9 +290,9 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex exp_val = 10 * exp_val + dig; } } else { - if (dig == 0 || dec_val >= DEC_VAL_MAX) { + if (dig == 0 || mantissa >= MANTISSA_MAX) { // Defer treatment of zeros in fractional part. If nothing comes afterwards, ignore them. - // Also, once we reach DEC_VAL_MAX, treat every additional digit as a trailing zero. + // Also, once we reach MANTISSA_MAX, treat every additional digit as a trailing zero. if (in == PARSE_DEC_IN_INTG) { ++trailing_zeros_intg; } else { @@ -299,14 +301,14 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex } else { // Time to un-defer any trailing zeros. Intg zeros first. while (trailing_zeros_intg) { - accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_INTG); + mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_INTG); --trailing_zeros_intg; } while (trailing_zeros_frac) { - accept_digit(&dec_val, 0, &exp_extra, PARSE_DEC_IN_FRAC); + mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_FRAC); --trailing_zeros_frac; } - accept_digit(&dec_val, dig, &exp_extra, in); + mantissa = accept_digit(mantissa, dig, &exp_extra, in); } } } else if (in == PARSE_DEC_IN_INTG && dig == '.') { @@ -340,6 +342,7 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex // apply the exponent, making sure it's not a subnormal value exp_val += exp_extra + trailing_zeros_intg; + dec_val = (mp_float_t)mantissa; if (exp_val < SMALL_NORMAL_EXP) { exp_val -= SMALL_NORMAL_EXP; dec_val *= SMALL_NORMAL_VAL; diff --git a/tests/float/float_parse.py b/tests/float/float_parse.py index de27c33e7be..6131da0a63a 100644 --- a/tests/float/float_parse.py +++ b/tests/float/float_parse.py @@ -31,6 +31,9 @@ print(float("1e18446744073709551621")) print(float("1e-18446744073709551621")) +# mantissa overflow while processing deferred trailing zeros +print(float("10000000000000000000001")) + # check small decimals are as close to their true value as possible for n in range(1, 10): print(float("0.%u" % n) == n / 10) From 86526e9c2b5fcde2ecdee3817cc002920078b07b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Feb 2025 14:37:55 +1100 Subject: [PATCH 0324/2098] tools/mpremote: Optimise readline support in mount. This significantly speeds up readline on files opened directly from an mpremote mount. Signed-off-by: Andrew Leech --- tools/mpremote/mpremote/transport_serial.py | 41 ++++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index d3148f9ace3..6aed0bb496b 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -404,12 +404,13 @@ def umount_local(self): "CMD_OPEN": 4, "CMD_CLOSE": 5, "CMD_READ": 6, - "CMD_WRITE": 7, - "CMD_SEEK": 8, - "CMD_REMOVE": 9, - "CMD_RENAME": 10, - "CMD_MKDIR": 11, - "CMD_RMDIR": 12, + "CMD_READLINE": 7, + "CMD_WRITE": 8, + "CMD_SEEK": 9, + "CMD_REMOVE": 10, + "CMD_RENAME": 11, + "CMD_MKDIR": 12, + "CMD_RMDIR": 13, } fs_hook_code = """\ @@ -592,12 +593,16 @@ def readinto(self, buf): return n def readline(self): - l = '' - while 1: - c = self.read(1) - l += c - if c == '\\n' or c == '': - return l + c = self.cmd + c.begin(CMD_READLINE) + c.wr_s8(self.fd) + data = c.rd_bytes(None) + c.end() + if self.is_text: + data = str(data, 'utf8') + else: + data = bytes(data) + return data def readlines(self): ls = [] @@ -746,8 +751,7 @@ def __mount(): """ # Apply basic compression on hook code. -for key, value in fs_hook_cmds.items(): - fs_hook_code = re.sub(key, str(value), fs_hook_code) +fs_hook_code = re.sub(r"CMD_[A-Z_]+", lambda m: str(fs_hook_cmds[m.group(0)]), fs_hook_code) fs_hook_code = re.sub(" *#.*$", "", fs_hook_code, flags=re.MULTILINE) fs_hook_code = re.sub("\n\n+", "\n", fs_hook_code) fs_hook_code = re.sub(" ", " ", fs_hook_code) @@ -887,6 +891,14 @@ def do_read(self): self.wr_bytes(buf) # self.log_cmd(f"read {fd} {n} -> {len(buf)}") + def do_readline(self): + fd = self.rd_s8() + buf = self.data_files[fd][0].readline() + if self.data_files[fd][1]: + buf = bytes(buf, "utf8") + self.wr_bytes(buf) + # self.log_cmd(f"readline {fd} -> {len(buf)}") + def do_seek(self): fd = self.rd_s8() n = self.rd_s32() @@ -960,6 +972,7 @@ def do_rmdir(self): fs_hook_cmds["CMD_OPEN"]: do_open, fs_hook_cmds["CMD_CLOSE"]: do_close, fs_hook_cmds["CMD_READ"]: do_read, + fs_hook_cmds["CMD_READLINE"]: do_readline, fs_hook_cmds["CMD_WRITE"]: do_write, fs_hook_cmds["CMD_SEEK"]: do_seek, fs_hook_cmds["CMD_REMOVE"]: do_remove, From 71c7c03e411121b0193590bb683961e83c787ab1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 26 Feb 2025 17:12:45 +1100 Subject: [PATCH 0325/2098] tools/mpremote/tests: Add test for RemoteFile.readline. Signed-off-by: Damien George --- tools/mpremote/tests/test_mount.sh | 4 ++++ tools/mpremote/tests/test_mount.sh.exp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tools/mpremote/tests/test_mount.sh b/tools/mpremote/tests/test_mount.sh index 9eab0b0d6e8..724ae1417fc 100755 --- a/tools/mpremote/tests/test_mount.sh +++ b/tools/mpremote/tests/test_mount.sh @@ -26,3 +26,7 @@ $MPREMOTE mount ${TMP} exec "import mount_package; mount_package.x(); mount_pack echo ----- $MPREMOTE mount ${TMP} exec "open('test.txt', 'w').write('hello world\n')" cat "${TMP}/test.txt" + +# Test RemoteFile.readline and RemoteFile.readlines methods. +echo ----- +$MPREMOTE mount ${TMP} exec "print(open('test.txt').readlines())" diff --git a/tools/mpremote/tests/test_mount.sh.exp b/tools/mpremote/tests/test_mount.sh.exp index 560f5e4f10a..616cb75bcf5 100644 --- a/tools/mpremote/tests/test_mount.sh.exp +++ b/tools/mpremote/tests/test_mount.sh.exp @@ -5,3 +5,6 @@ Local directory ${TMP} is mounted at /remote ----- Local directory ${TMP} is mounted at /remote hello world +----- +['hello world\n'] +Local directory ${TMP} is mounted at /remote From 13b02376af4e9d4b76bf053935b51ddc7c87df03 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Feb 2025 12:44:19 +1100 Subject: [PATCH 0326/2098] lib/pico-sdk: Update to version 2.1.1. Release notes: https://github.com/raspberrypi/pico-sdk/releases/tag/2.1.1 Signed-off-by: Damien George --- lib/pico-sdk | 2 +- ports/rp2/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index 95ea6acad13..bddd20f928c 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit 95ea6acad131124694cda1c162c52cd30e0aece0 +Subproject commit bddd20f928ce76142793bef434d4f75f4af6e433 diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 8a4092529b1..7002ad8769e 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -276,7 +276,7 @@ if(PICO_RP2040) 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 + ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_common_m33.S ) endif() From 9939b6c6b906cec702e825a7b58f5647c70c178e Mon Sep 17 00:00:00 2001 From: machdyne Date: Fri, 20 Dec 2024 23:39:02 +0100 Subject: [PATCH 0327/2098] rp2/boards/MACHDYNE_WERKZEUG: Add support for Machdyne Werkzeug. Signed-off-by: machdyne --- ports/rp2/boards/MACHDYNE_WERKZEUG/board.json | 19 ++++++++++++ .../MACHDYNE_WERKZEUG/mpconfigboard.cmake | 3 ++ .../boards/MACHDYNE_WERKZEUG/mpconfigboard.h | 3 ++ ports/rp2/boards/MACHDYNE_WERKZEUG/pins.csv | 30 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 ports/rp2/boards/MACHDYNE_WERKZEUG/board.json create mode 100644 ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.cmake create mode 100644 ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.h create mode 100644 ports/rp2/boards/MACHDYNE_WERKZEUG/pins.csv diff --git a/ports/rp2/boards/MACHDYNE_WERKZEUG/board.json b/ports/rp2/boards/MACHDYNE_WERKZEUG/board.json new file mode 100644 index 00000000000..c657f18b21d --- /dev/null +++ b/ports/rp2/boards/MACHDYNE_WERKZEUG/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "USB-C" + ], + "images": [ + "werkzeug.jpg" + ], + "mcu": "rp2040", + "product": "Werkzeug", + "thumbnail": "", + "url": "https://machdyne.com/product/werkzeug-multi-tool/", + "vendor": "Machdyne" +} diff --git a/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.cmake b/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.cmake new file mode 100644 index 00000000000..281c4e6f1b4 --- /dev/null +++ b/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.cmake @@ -0,0 +1,3 @@ +# cmake file for Machdyne Werkzeug +set(PICO_BOARD "machdyne_werkzeug") +set(PICO_PLATFORM "rp2040") diff --git a/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.h b/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.h new file mode 100644 index 00000000000..476ea23b9c8 --- /dev/null +++ b/ports/rp2/boards/MACHDYNE_WERKZEUG/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Machdyne Werkzeug" +#define MICROPY_HW_FLASH_STORAGE_BYTES (384 * 1024) diff --git a/ports/rp2/boards/MACHDYNE_WERKZEUG/pins.csv b/ports/rp2/boards/MACHDYNE_WERKZEUG/pins.csv new file mode 100644 index 00000000000..c3c57813206 --- /dev/null +++ b/ports/rp2/boards/MACHDYNE_WERKZEUG/pins.csv @@ -0,0 +1,30 @@ +GP0,GPIO0 +GP1,GPIO1 +GP2,GPIO2 +GP3,GPIO3 +GP4,GPIO4 +GP5,GPIO5 +GP6,GPIO6 +GP7,GPIO7 +GP8,GPIO8 +GP9,GPIO9 +GP10,GPIO10 +GP11,GPIO11 +PMOD10,GPIO12 +PMOD4,GPIO13 +PMOD9,GPIO14 +PMOD3,GPIO15 +PMOD8,GPIO16 +PMOD2,GPIO17 +PMOD7,GPIO18 +PMOD1,GPIO19 +LED_GREEN,GPIO20 +LED_RED,GPIO21 +USBA_POWER,GPIO22 +USBA_DN,GPIO23 +USBA_DP,GPIO24 +USBA_DP_PU,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +GP29,GPIO29 From cad62c20f2971b1248f55f27ab9f6d9bf8ccdafa Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Tue, 18 Feb 2025 14:57:53 -0700 Subject: [PATCH 0328/2098] rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA: Add SparkFun XRP Controller. Signed-off-by: Dryw Wade --- .../SPARKFUN_XRP_CONTROLLER_BETA/board.json | 23 +++++++ .../SPARKFUN_XRP_CONTROLLER_BETA/manifest.py | 6 ++ .../mpconfigboard.cmake | 14 ++++ .../mpconfigboard.h | 28 ++++++++ .../SPARKFUN_XRP_CONTROLLER_BETA/pins.csv | 65 +++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/manifest.py create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/pins.csv diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json new file mode 100644 index 00000000000..cece5743265 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Dual-core", + "External Flash", + "IMU", + "JST-SH", + "USB", + "WiFi" + ], + "images": [ + "22727-_01.jpg" + ], + "mcu": "rp2040", + "product": "XRP Controller (Beta)", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller.html", + "vendor": "Sparkfun" +} diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/manifest.py b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/manifest.py new file mode 100644 index 00000000000..4e38f09cdee --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +# Bluetooth +require("aioble") diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.cmake new file mode 100644 index 00000000000..6c06efefb64 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.cmake @@ -0,0 +1,14 @@ +# cmake file for SparkFun XRP Controller (Beta) + +set(PICO_BOARD "pico_w") + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h new file mode 100644 index 00000000000..56071e18733 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h @@ -0,0 +1,28 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun XRP Controller (Beta)" + +// todo: We need something to check our binary size +#define MICROPY_HW_FLASH_STORAGE_BYTES (848 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "XRP" + +// CYW43 driver configuration. +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +// For debugging mbedtls - also set +// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose +// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +// If this returns true for a pin then its irq will not be disabled on a soft reboot +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) + +#define MICROPY_HW_I2C1_SDA (18) +#define MICROPY_HW_I2C1_SCL (19) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/pins.csv b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/pins.csv new file mode 100644 index 00000000000..dd047063b5c --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/pins.csv @@ -0,0 +1,65 @@ +# XRP default pin names +MOTOR_L_IN_1,GPIO6 +MOTOR_L_IN_2,GPIO7 +MOTOR_R_IN_1,GPIO14 +MOTOR_R_IN_2,GPIO15 +MOTOR_3_IN_1,GPIO2 +MOTOR_3_IN_2,GPIO3 +MOTOR_4_IN_1,GPIO10 +MOTOR_4_IN_2,GPIO11 +MOTOR_L_ENCODER_A,GPIO4 +MOTOR_L_ENCODER_B,GPIO5 +MOTOR_R_ENCODER_A,GPIO12 +MOTOR_R_ENCODER_B,GPIO13 +MOTOR_3_ENCODER_A,GPIO0 +MOTOR_3_ENCODER_B,GPIO1 +MOTOR_4_ENCODER_A,GPIO8 +MOTOR_4_ENCODER_B,GPIO9 +SERVO_1,GPIO16 +SERVO_2,GPIO17 +I2C_SDA_1,GPIO18 +I2C_SCL_1,GPIO19 +DISTANCE_TRIGGER,GPIO20 +DISTANCE_ECHO,GPIO21 +LINE_L,GPIO26 +LINE_R,GPIO27 +BOARD_VIN_MEASURE,GPIO28 +BOARD_USER_BUTTON,GPIO22 +BOARD_LED,EXT_GPIO0 + +# XRP alternate pin names +ML_IN_1,GPIO6 +ML_IN_2,GPIO7 +MR_IN_1,GPIO14 +MR_IN_2,GPIO15 +M3_IN_1,GPIO2 +M3_IN_2,GPIO3 +M4_IN_1,GPIO10 +M4_IN_2,GPIO11 +ML_ENC_A,GPIO4 +ML_ENC_B,GPIO5 +MR_ENC_A,GPIO12 +MR_ENC_B,GPIO13 +M3_ENC_A,GPIO0 +M3_ENC_B,GPIO1 +M4_ENC_A,GPIO8 +M4_ENC_B,GPIO9 +S1,GPIO16 +S2,GPIO17 +SDA_1,GPIO18 +SCL_1,GPIO19 +RANGE_TRIGGER,GPIO20 +RANGE_ECHO,GPIO21 +REFLECTANCE_L,GPIO26 +REFLECTANCE_R,GPIO27 +BRD_VIN,GPIO28 +BRD_USR_BTN,GPIO22 +BRD_LED,EXT_GPIO0 + +# LED default names +LED,EXT_GPIO0 + +# Radio GPIO pins +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 From bb4ec886f8994ad7b2db6f80c7b46e6eedbc40e7 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Fri, 24 Jan 2025 09:36:02 -0700 Subject: [PATCH 0329/2098] rp2/machine_i2c: Make I2C bus ID arg optional with default. This commit gives the option to not pass an I2C Bus ID when creating a machine I2C object. If the ID is not provided, the default bus ID (which is `PICO_DEFAULT_I2C`) is used. This allows users to simply declare an I2C object with `machine.I2C()` without passing any arguments, thus creating an object with the default I2C ID, SCL, and SDA. Signed-off-by: Malcolm McKellips --- ports/rp2/machine_i2c.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/rp2/machine_i2c.c b/ports/rp2/machine_i2c.c index 28032e8085c..e97a852b243 100644 --- a/ports/rp2/machine_i2c.c +++ b/ports/rp2/machine_i2c.c @@ -97,7 +97,11 @@ static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_prin mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_freq, ARG_scl, ARG_sda, ARG_timeout }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + #ifdef PICO_DEFAULT_I2C + { MP_QSTR_id, MP_ARG_INT, {.u_int = PICO_DEFAULT_I2C} }, + #else + { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} }, + #endif { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, { MP_QSTR_scl, MICROPY_I2C_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_sda, MICROPY_I2C_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -108,8 +112,9 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - // Get I2C bus. - int i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + int i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_obj)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } From c143eb50248b3a3f521775f09f5073675095e48a Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Fri, 24 Jan 2025 13:05:07 -0700 Subject: [PATCH 0330/2098] esp32/machine_i2c: Make I2C bus ID arg optional with default. Similar to the previous commit, this allows constructing an I2C instance without specifying an ID. The default ID is I2C_NUM_0. Signed-off-by: Malcolm McKellips --- ports/esp32/machine_i2c.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 732a62f47f1..12b86e4aa02 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -146,12 +146,15 @@ static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // Create a SoftI2C instance if no id is specified (or is -1) but other arguments are given + if (n_args != 0) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + } // Parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = I2C_NUM_0} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, @@ -161,7 +164,9 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get I2C bus - mp_int_t i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + mp_int_t i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } From ec876a5e27163299ff60ee70e4ef21073dd67704 Mon Sep 17 00:00:00 2001 From: garywill Date: Sun, 22 Dec 2024 13:35:21 +0800 Subject: [PATCH 0331/2098] esp32/README: Make some minor improvements to the README. Changes: - To add user to Linux dialout group, usermod is the universal Linux way. adduser is Debian-based way. - When installing IDF, we don't have to install all toolchains for all chips. - List currently supported chip models. - Other minor typo and gramma corrections. Signed-off-by: garywill --- ports/esp32/README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 6ed7eddb8b8..d9115072a45 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -5,6 +5,9 @@ This is a port of MicroPython to the Espressif ESP32 series of microcontrollers. It uses the ESP-IDF framework and MicroPython runs as a task under FreeRTOS. +Currently supports ESP32, ESP32-C3, ESP32-C6, ESP32-S2 and ESP32-S3 +(ESP8266 is supported by a separate MicroPython port). + Supported features include: - REPL (Python prompt) over UART0. - 16k stack for the MicroPython task and approximately 100k Python heap. @@ -67,12 +70,16 @@ After you've cloned and checked out the IDF to the correct version, run the ```bash $ cd esp-idf -$ ./install.sh # (or install.bat on Windows) +$ ./install.sh esp32 # (or install.bat on Windows) $ source export.sh # (or export.bat on Windows) ``` -The `install.sh` step only needs to be done once. You will need to source -`export.sh` for every new session. +The `install.sh` step only needs to be done once. Change `esp32` if you are +targeting other chip. Use comma-separated list like `esp32,esp32s2` to +install for multiple chips. Or omit the chip to install for all Espressif +chips (which is slower). + +You will need to source `export.sh` for every new session. Building the firmware --------------------- @@ -85,7 +92,7 @@ this repository): $ make -C mpy-cross ``` -Then to build MicroPython for the ESP32 run: +Then to build MicroPython for ESP32 run: ```bash $ cd ports/esp32 @@ -106,7 +113,7 @@ rebooting or logging out and in again. (Note: on some distributions this may be the `uucp` group, run `ls -la /dev/ttyUSB0` to check.) ```bash -$ sudo adduser dialout +$ sudo usermod -aG dialout ``` If you are installing MicroPython to your module for the first time, or @@ -141,7 +148,7 @@ $ idf.py -D MICROPY_BOARD=ESP32_GENERIC build $ idf.py flash ``` -Some boards also support "variants", which are allow for small variations of +Some boards also support "variants", which allow for small variations of an otherwise similar board. For example different flash sizes or features. For example to build the `OTA` variant of `ESP32_GENERIC`. @@ -173,7 +180,7 @@ or $ miniterm.py /dev/ttyUSB0 115200 ``` -You can also use `idf.py monitor`. +You can also use `idf.py monitor` or `mpremote`. Configuring the WiFi and using the board ---------------------------------------- From cbd21b39778d3f7b808d0c631a193625035b2190 Mon Sep 17 00:00:00 2001 From: Karl Palsson Date: Mon, 27 Jan 2025 16:05:07 +0000 Subject: [PATCH 0332/2098] esp32/esp32_common.cmake: Allow overriding linker.lf. Particularly for out of tree builds, one may need to provide alternative or extra linker fragment files, or specify an absolute path to the default `linker.lf` file. In the default case, do nothing, provide a plain `linker.lf`, as before. Signed-off-by: Karl Palsson --- ports/esp32/esp32_common.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index e03d2ff11aa..059989c10a4 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -178,6 +178,13 @@ list(APPEND IDF_COMPONENTS vfs ) +# Provide the default LD fragment if not set +if (MICROPY_USER_LDFRAGMENTS) + set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) +else() + set(MICROPY_LDFRAGMENTS linker.lf) +endif() + # Register the main IDF component. idf_component_register( SRCS @@ -197,7 +204,7 @@ idf_component_register( ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} LDFRAGMENTS - linker.lf + ${MICROPY_LDFRAGMENTS} REQUIRES ${IDF_COMPONENTS} ) From 61cb293b761d22de9ece8d7cfdb38555fc0a410a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 17 Jan 2025 21:12:19 +0100 Subject: [PATCH 0333/2098] esp32/machine_pin: Implement Pin.toggle() method. The actual output pin value is taken from the OUT register, not from the pad. Tested with: - ESP32 low and high Pin numbers - ESP32C3 low Pin numbers - ESP32C6 low Pin numbers - ESP32S2 low and high Pin numbers - ESP32S3 low and high Pin numbers Signed-off-by: robert-hh --- ports/esp32/machine_pin.c | 10 ++++++++++ ports/esp32/mphalport.h | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 74202d6f758..b821abf1fc1 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -287,6 +287,15 @@ static mp_obj_t machine_pin_on(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); +// pin.toggle() +static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + gpio_num_t index = PIN_OBJ_PTR_INDEX(self); + gpio_set_level(index, 1 - mp_hal_pin_read_output(index)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); + // pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING) static mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_handler, ARG_trigger, ARG_wake }; @@ -366,6 +375,7 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, // class attributes diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index a908d784d7f..a919a859a42 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -36,6 +36,7 @@ #include "freertos/task.h" #include "driver/spi_master.h" +#include "soc/gpio_reg.h" #define MICROPY_PLATFORM_VERSION "IDF" IDF_VER @@ -133,6 +134,15 @@ static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { return gpio_get_level(pin); } +static inline int mp_hal_pin_read_output(mp_hal_pin_obj_t pin) { + #if defined(GPIO_OUT1_REG) + return pin < 32 + ? (*(uint32_t *)GPIO_OUT_REG >> pin) & 1 + : (*(uint32_t *)GPIO_OUT1_REG >> (pin - 32)) & 1; + #else + return (*(uint32_t *)GPIO_OUT_REG >> pin) & 1; + #endif +} static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { gpio_set_level(pin, v); } From e009ab06c581cf2e8b6dbf8d2002105c97131a1f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 19 Jan 2025 10:31:56 +0100 Subject: [PATCH 0334/2098] esp8266/machine_pin: Implement Pin.toggle() method. Tested with a generic ESP8266 device. The actual output value is taken from the output register, not by reading the pad level. Signed-off-by: robert-hh --- ports/esp8266/esp_mphal.h | 7 +++++++ ports/esp8266/machine_pin.c | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/ports/esp8266/esp_mphal.h b/ports/esp8266/esp_mphal.h index e677fa450d3..0a4f92ac0bf 100644 --- a/ports/esp8266/esp_mphal.h +++ b/ports/esp8266/esp_mphal.h @@ -100,6 +100,13 @@ void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin); else { gpio_output_set(1 << (p), 0, 1 << (p), 0); } \ } while (0) #define mp_hal_pin_read(p) pin_get(p) +static inline int mp_hal_pin_read_output(mp_hal_pin_obj_t pin) { + if (pin >= 16) { + return READ_PERI_REG(RTC_GPIO_OUT) & 1; + } else { + return (GPIO_REG_READ(GPIO_OUT_ADDRESS) >> pin) & 1; + } +} #define mp_hal_pin_write(p, v) pin_set((p), (v)) void *ets_get_esf_buf_ctlblk(void); diff --git a/ports/esp8266/machine_pin.c b/ports/esp8266/machine_pin.c index ec22c7c588c..658966679d1 100644 --- a/ports/esp8266/machine_pin.c +++ b/ports/esp8266/machine_pin.c @@ -373,6 +373,14 @@ static mp_obj_t pyb_pin_on(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(pyb_pin_on_obj, pyb_pin_on); +// pin.toggle() +static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { + pyb_pin_obj_t *self = self_in; + pin_set(self->phys_port, 1 - mp_hal_pin_read_output(self->phys_port)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); + // pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) static mp_obj_t pyb_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_handler, ARG_trigger, ARG_hard }; @@ -435,6 +443,7 @@ static const mp_rom_map_elem_t pyb_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&pyb_pin_value_obj) }, { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&pyb_pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&pyb_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&pyb_pin_irq_obj) }, // class constants From 22310ae27aea56377b31bdcc2d7c2ba712316c4f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 19 Jan 2025 11:06:26 +0100 Subject: [PATCH 0335/2098] cc3200/mods/pybpin: Implement Pin.toggle() method. Tested with a WiPy 1 board. Signed-off-by: robert-hh --- ports/cc3200/mods/pybpin.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ports/cc3200/mods/pybpin.c b/ports/cc3200/mods/pybpin.c index 037c78a32cb..ea40aa9df8d 100644 --- a/ports/cc3200/mods/pybpin.c +++ b/ports/cc3200/mods/pybpin.c @@ -680,6 +680,20 @@ static mp_obj_t pin_value(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_value_obj, 1, 2, pin_value); +// pin.toggle() +static mp_obj_t pin_toggle(mp_obj_t self_in) { + pin_obj_t *self = self_in; + if (self->value) { + self->value = 0; + MAP_GPIOPinWrite(self->port, self->bit, 0); + } else { + self->value = 1; + MAP_GPIOPinWrite(self->port, self->bit, self->bit); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(pin_toggle_obj, pin_toggle); + static mp_obj_t pin_id(mp_obj_t self_in) { pin_obj_t *self = self_in; return MP_OBJ_NEW_QSTR(self->name); @@ -902,6 +916,7 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pin_init_obj) }, { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&pin_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&pin_toggle_obj) }, { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&pin_id_obj) }, { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&pin_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_pull), MP_ROM_PTR(&pin_pull_obj) }, From 48925fd7932c9d27f7248bc3308a77c172702878 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 22 Jan 2025 18:03:52 +0100 Subject: [PATCH 0336/2098] docs/library/machine.Pin: Show availability of low, high and toggle. Signed-off-by: robert-hh --- docs/library/machine.Pin.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/library/machine.Pin.rst b/docs/library/machine.Pin.rst index 68070133b8f..4aac0f4b94b 100644 --- a/docs/library/machine.Pin.rst +++ b/docs/library/machine.Pin.rst @@ -209,13 +209,13 @@ The following methods are not part of the core Pin API and only implemented on c Set pin to "0" output level. - Availability: nrf, rp2, stm32 ports. + Availability: mimxrt, nrf, renesas-ra, rp2, samd, stm32 ports. .. method:: Pin.high() Set pin to "1" output level. - Availability: nrf, rp2, stm32 ports. + Availability: mimxrt, nrf, renesas-ra, rp2, samd, stm32 ports. .. method:: Pin.mode([mode]) @@ -242,7 +242,7 @@ The following methods are not part of the core Pin API and only implemented on c Toggle output pin from "0" to "1" or vice-versa. - Availability: mimxrt, samd, rp2 ports. + Availability: cc3200, esp32, esp8266, mimxrt, rp2, samd ports. Constants --------- From 69ffd2aaf09e35b68ea045872a63af6403fc1c8f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 8 Dec 2024 21:37:18 +0100 Subject: [PATCH 0337/2098] renesas-ra/modrenesas: Expose the Flash block device to Python code. A new module called renesas is added, like in other ports. The accessible block device allows to use Python methods for creating and modifying the file system. The Flash block device for the file system can be accessed with: from renesas import Flash bdev = Flash(start=0) Signed-off-by: robert-hh --- ports/renesas-ra/Makefile | 1 + ports/renesas-ra/modrenesas.c | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ports/renesas-ra/modrenesas.c diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index 03eb9b52b41..ec47510d981 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -250,6 +250,7 @@ SRC_C += \ machine_pin.c \ machine_rtc.c \ machine_sdcard.c \ + modrenesas.c \ extint.c \ usrsw.c \ flash.c \ diff --git a/ports/renesas-ra/modrenesas.c b/ports/renesas-ra/modrenesas.c new file mode 100644 index 00000000000..2524bbe6d7d --- /dev/null +++ b/ports/renesas-ra/modrenesas.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "storage.h" + +static const mp_rom_map_elem_t renesas_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_renesas) }, + { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&pyb_flash_type) }, +}; +static MP_DEFINE_CONST_DICT(renesas_module_globals, renesas_module_globals_table); + +const mp_obj_module_t mp_module_renesas = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&renesas_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_renesas, mp_module_renesas); From eb45d97898abd9aae93d0c953634cabb5ea327e3 Mon Sep 17 00:00:00 2001 From: Glenn Moloney Date: Tue, 25 Feb 2025 14:36:29 +1100 Subject: [PATCH 0338/2098] py/objstr: Support tuples and start/end args in startswith and endswith. This change allows tuples to be passed as the prefix/suffix argument to the `str.startswith()` and `str.endswith()` methods. The methods will return `True` if the string starts/ends with any of the prefixes/suffixes in the tuple. Also adds full support for the `start` and `end` arguments to both methods for compatibility with CPython. Tests have been updated for the new behaviour. Signed-off-by: Glenn Moloney --- py/objstr.c | 67 ++++++++++++++--------- tests/basics/string_endswith.py | 27 +++++++-- tests/basics/string_endswith_upy.py | 6 -- tests/basics/string_endswith_upy.py.exp | 1 - tests/basics/string_startswith.py | 19 +++++++ tests/basics/string_startswith_upy.py | 6 -- tests/basics/string_startswith_upy.py.exp | 1 - tests/misc/non_compliant.py | 6 -- tests/misc/non_compliant.py.exp | 1 - 9 files changed, 83 insertions(+), 51 deletions(-) delete mode 100644 tests/basics/string_endswith_upy.py delete mode 100644 tests/basics/string_endswith_upy.py.exp delete mode 100644 tests/basics/string_startswith_upy.py delete mode 100644 tests/basics/string_startswith_upy.py.exp diff --git a/py/objstr.c b/py/objstr.c index fc0623eb7af..307e956a152 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -67,6 +67,26 @@ static void check_is_str_or_bytes(mp_obj_t self_in) { mp_check_self(mp_obj_is_str_or_bytes(self_in)); } +static const byte *get_substring_data(const mp_obj_t obj, size_t n_args, const mp_obj_t *args, size_t *len) { + // Get substring data from obj, using args[0,1] to specify start and end indices. + GET_STR_DATA_LEN(obj, str, str_len); + if (n_args > 0) { + const mp_obj_type_t *self_type = mp_obj_get_type(obj); + const byte *end = str + str_len; + if (n_args > 1 && args[1] != mp_const_none) { + end = str_index_to_ptr(self_type, str, str_len, args[1], true); + } + if (args[0] != mp_const_none) { + str = str_index_to_ptr(self_type, str, str_len, args[0], true); + } + str_len = MAX(end - str, 0); + } + if (len) { + *len = str_len; + } + return str; +} + /******************************************************************************/ /* str */ @@ -802,37 +822,34 @@ static mp_obj_t str_rindex(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rindex_obj, 2, 4, str_rindex); -// TODO: (Much) more variety in args -static mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) { - const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); - GET_STR_DATA_LEN(args[0], str, str_len); - size_t prefix_len; - const char *prefix = mp_obj_str_get_data(args[1], &prefix_len); - const byte *start = str; - if (n_args > 2) { - start = str_index_to_ptr(self_type, str, str_len, args[2], true); +static mp_obj_t str_startendswith(size_t n_args, const mp_obj_t *args, bool ends_with) { + size_t str_len; + const byte *str = get_substring_data(args[0], n_args - 2, args + 2, &str_len); + mp_obj_t *prefixes = (mp_obj_t *)&args[1]; + size_t n_prefixes = 1; + if (mp_obj_is_type(args[1], &mp_type_tuple)) { + mp_obj_tuple_get(args[1], &n_prefixes, &prefixes); } - if (prefix_len + (start - str) > str_len) { - return mp_const_false; + size_t prefix_len; + for (size_t i = 0; i < n_prefixes; i++) { + const char *prefix = mp_obj_str_get_data(prefixes[i], &prefix_len); + const byte *s = str + (ends_with ? str_len - prefix_len : 0); + if (prefix_len <= str_len && memcmp(s, prefix, prefix_len) == 0) { + return mp_const_true; + } } - return mp_obj_new_bool(memcmp(start, prefix, prefix_len) == 0); + return mp_const_false; } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 3, str_startswith); -static mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { - GET_STR_DATA_LEN(args[0], str, str_len); - size_t suffix_len; - const char *suffix = mp_obj_str_get_data(args[1], &suffix_len); - if (n_args > 2) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("start/end indices")); - } +static mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) { + return str_startendswith(n_args, args, false); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 4, str_startswith); - if (suffix_len > str_len) { - return mp_const_false; - } - return mp_obj_new_bool(memcmp(str + (str_len - suffix_len), suffix, suffix_len) == 0); +static mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { + return str_startendswith(n_args, args, true); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 3, str_endswith); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 4, str_endswith); enum { LSTRIP, RSTRIP, STRIP }; diff --git a/tests/basics/string_endswith.py b/tests/basics/string_endswith.py index 683562d10c3..2b0a063988b 100644 --- a/tests/basics/string_endswith.py +++ b/tests/basics/string_endswith.py @@ -5,11 +5,28 @@ print("foobar".endswith("")) print("foobar".endswith("foobarbaz")) -#print("1foobar".startswith("foo", 1)) -#print("1foo".startswith("foo", 1)) -#print("1foo".startswith("1foo", 1)) -#print("1fo".startswith("foo", 1)) -#print("1fo".startswith("foo", 10)) +print("foobar".endswith("bar", 3)) +print("foobar".endswith("bar", 4)) +print("foobar".endswith("foo", 0, 3)) +print("foobar".endswith("foo", 0, 4)) +print("foobar".endswith("foo", 1, 3)) +print("foobar".endswith("foo", 1, 3)) +print("foobar".endswith("oo", 1, 3)) +print("foobar".endswith("o", 2, 3)) +print("foobar".endswith("o", 3, 3)) +print("foobar".endswith("o", 4, 3)) + +print("foobar".endswith("bar", None, None)) +print("foobar".endswith("bar", None, 3)) +print("foobar".endswith("bar", 3, None)) +print("foobar".endswith("bar", 2, None)) +print("foobar".endswith("foo", None, 3)) + +print("foobar".endswith(("bar", "foo"))) +print("foobar".endswith(("foo", "bar"))) +print("foobar".endswith(("foo", "bar1"))) +print("foobar".endswith(("bar", ))) +print("foobar".endswith(("foo", ))) try: "foobar".endswith(1) diff --git a/tests/basics/string_endswith_upy.py b/tests/basics/string_endswith_upy.py deleted file mode 100644 index 06a4e71d2c9..00000000000 --- a/tests/basics/string_endswith_upy.py +++ /dev/null @@ -1,6 +0,0 @@ -# MicroPython doesn't support tuple argument - -try: - "foobar".endswith(("bar", "sth")) -except TypeError: - print("TypeError") diff --git a/tests/basics/string_endswith_upy.py.exp b/tests/basics/string_endswith_upy.py.exp deleted file mode 100644 index 6002b71c56e..00000000000 --- a/tests/basics/string_endswith_upy.py.exp +++ /dev/null @@ -1 +0,0 @@ -TypeError diff --git a/tests/basics/string_startswith.py b/tests/basics/string_startswith.py index e63ae3c1866..eefdea82198 100644 --- a/tests/basics/string_startswith.py +++ b/tests/basics/string_startswith.py @@ -10,6 +10,25 @@ print("1fo".startswith("foo", 1)) print("1fo".startswith("foo", 10)) +print("1foobar".startswith("foo", 1, 5)) +print("1foobar".startswith("foo", 1, 4)) +print("1foobar".startswith("foo", 1, 3)) +print("1foobar".startswith("oo", 2, 4)) +print("1foobar".startswith("o", 3, 4)) +print("1foobar".startswith("o", 4, 4)) +print("1foobar".startswith("o", 5, 4)) + +print("foobar".startswith("foo", None, None)) +print("foobar".startswith("foo", None, 3)) +print("foobar".startswith("foo", None, 2)) +print("foobar".startswith("bar", 3, None)) + + +print("foobar".startswith(("foo", "sth"))) +print("foobar".startswith(("sth", "foo"))) +print("foobar".startswith(("sth", "foo2"))) +print("foobar".startswith(("foo", ))) + try: "foobar".startswith(1) except TypeError: diff --git a/tests/basics/string_startswith_upy.py b/tests/basics/string_startswith_upy.py deleted file mode 100644 index 9ea1796c218..00000000000 --- a/tests/basics/string_startswith_upy.py +++ /dev/null @@ -1,6 +0,0 @@ -# MicroPython doesn't support tuple argument - -try: - "foobar".startswith(("foo", "sth")) -except TypeError: - print("TypeError") diff --git a/tests/basics/string_startswith_upy.py.exp b/tests/basics/string_startswith_upy.py.exp deleted file mode 100644 index 6002b71c56e..00000000000 --- a/tests/basics/string_startswith_upy.py.exp +++ /dev/null @@ -1 +0,0 @@ -TypeError diff --git a/tests/misc/non_compliant.py b/tests/misc/non_compliant.py index da90f90ac3e..dd62f63255c 100644 --- a/tests/misc/non_compliant.py +++ b/tests/misc/non_compliant.py @@ -63,12 +63,6 @@ except NotImplementedError: print("NotImplementedError") -# str.endswith(s, start) not implemented -try: - "abc".endswith("c", 1) -except NotImplementedError: - print("NotImplementedError") - # str subscr with step!=1 not implemented try: print("abc"[1:2:3]) diff --git a/tests/misc/non_compliant.py.exp b/tests/misc/non_compliant.py.exp index 3f15a144060..deefe78a712 100644 --- a/tests/misc/non_compliant.py.exp +++ b/tests/misc/non_compliant.py.exp @@ -13,7 +13,6 @@ NotImplementedError NotImplementedError NotImplementedError NotImplementedError -NotImplementedError b'\x01\x02' b'\x01\x00' NotImplementedError From 213f1c1ee07016c1a4d21cc3b1ea4ea85ba33c98 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 7 Feb 2025 21:40:09 +0100 Subject: [PATCH 0339/2098] samd/machine_i2c: Support default instance and SCL/SDA pin values. If a board configures a default I2C instance and/or SCL/SDA pins, then these no longer need to be given in the constructor. This allows the user to easily construct the default I2C instance via `machine.I2C()` and that will work on the default pins as designated on the board silkscreen. Signed-off-by: robert-hh --- ports/samd/machine_i2c.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index a7b728ddaf3..03af3d29ed6 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -34,6 +34,7 @@ #include "extmod/modmachine.h" #include "samd_soc.h" #include "pin_af.h" +#include "genhdr/pins.h" #include "clock_config.h" #define DEFAULT_I2C_FREQ (400000) @@ -119,17 +120,26 @@ void common_i2c_irq_handler(int i2c_id) { static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "I2C(%u, freq=%u, scl=%u, sda=%u)", - self->id, self->freq, self->scl, self->sda); + mp_printf(print, "I2C(%u, freq=%u, scl=\"%q\", sda=\"%q\")", + self->id, self->freq, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name); } mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_freq, ARG_scl, ARG_sda }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + #if MICROPY_HW_DEFAULT_I2C_ID < 0 + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + #else + { MP_QSTR_id, MP_ARG_INT, {.u_int = MICROPY_HW_DEFAULT_I2C_ID} }, + #endif { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, + #if defined(pin_SCL) && defined(pin_SDA) + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = pin_SCL} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = pin_SDA} }, + #else { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #endif }; // Parse args. @@ -137,7 +147,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get I2C bus. - int id = mp_obj_get_int(args[ARG_id].u_obj); + int id = args[ARG_id].u_int; if (id < 0 || id >= SERCOM_INST_NUM) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), id); } @@ -148,15 +158,13 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n self->instance = sercom_instance[self->id]; // Set SCL/SDA pins. - sercom_pad_config_t scl_pad_config; self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); - scl_pad_config = get_sercom_config(self->scl, self->id); - - sercom_pad_config_t sda_pad_config; self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); - sda_pad_config = get_sercom_config(self->sda, self->id); + + sercom_pad_config_t scl_pad_config = get_sercom_config(self->scl, self->id); + sercom_pad_config_t sda_pad_config = get_sercom_config(self->sda, self->id); if (sda_pad_config.pad_nr != 0 || scl_pad_config.pad_nr != 1) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for sda or scl")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid sda/scl pin")); } MP_STATE_PORT(sercom_table[self->id]) = self; self->freq = args[ARG_freq].u_int; From 62ed69b016d98167db01c5283758bfde95c6aec9 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 7 Feb 2025 21:43:36 +0100 Subject: [PATCH 0340/2098] samd/machine_spi: Support default instance and SCK/MOSI/MISO pin values. If a board configures a default SPI instance and/or SCK/MOSI/MISO pins, then the user can create a default SPI object using `machine.SPI()`. Also, if MISO is not going to be used, then MISO can be set to `None` with `miso=None`. Signed-off-by: robert-hh --- ports/samd/machine_spi.c | 43 ++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c index 9c31cc2778d..744de2000b0 100644 --- a/ports/samd/machine_spi.c +++ b/ports/samd/machine_spi.c @@ -33,6 +33,7 @@ #include "extmod/modmachine.h" #include "samd_soc.h" #include "pin_af.h" +#include "genhdr/pins.h" #include "clock_config.h" #define DEFAULT_SPI_BAUDRATE (1000000) @@ -82,8 +83,15 @@ void common_spi_irq_handler(int spi_id) { static void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "SPI(%u, baudrate=%u, firstbit=%u, polarity=%u, phase=%u, bits=8)", - self->id, self->baudrate, self->firstbit, self->polarity, self->phase); + mp_printf(print, "SPI(%u, baudrate=%u, firstbit=%u, polarity=%u, phase=%u, bits=8," + " sck=\"%q\", mosi=\"%q\", miso=", + self->id, self->baudrate, self->firstbit, self->polarity, self->phase, + pin_find_by_id(self->sck)->name, pin_find_by_id(self->mosi)->name); + if (self->miso == 0xff) { + mp_printf(print, "None)"); + } else { + mp_printf(print, "\"%q\")", pin_find_by_id(self->miso)->name); + } } static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -96,7 +104,7 @@ static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj { MP_QSTR_firstbit, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} }, }; machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -132,14 +140,16 @@ static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj if (args[ARG_mosi].u_obj != mp_const_none) { self->mosi = mp_hal_get_pin_obj(args[ARG_mosi].u_obj); } - if (args[ARG_miso].u_obj != mp_const_none) { - self->miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj); + if (args[ARG_miso].u_obj != MP_ROM_INT(-1)) { + self->miso = args[ARG_miso].u_obj == mp_const_none ? 0xff : mp_hal_get_pin_obj(args[ARG_miso].u_obj); } - // Initialise the SPI peripheral if any arguments given, or it was not initialised previously. if (n_args > 0 || kw_args->used > 0 || self->new) { self->new = false; + if (self->sck == 0xff || self->mosi == 0xff) { + mp_raise_ValueError(MP_ERROR_TEXT("missing sck/mosi")); + } // Get the pad and alt-fct numbers. self->sck_pad_config = get_sercom_config(self->sck, self->id); self->mosi_pad_config = get_sercom_config(self->mosi, self->id); @@ -155,7 +165,7 @@ static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj } else if (self->mosi_pad_config.pad_nr == 0 && self->sck_pad_config.pad_nr == 3) { dopo = 3; } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for sck or mosi")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid sck/mosi pin")); } #elif defined(MCU_SAMD51) if (self->mosi_pad_config.pad_nr == 0 && self->sck_pad_config.pad_nr == 1) { @@ -232,10 +242,16 @@ static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj } static mp_obj_t machine_spi_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); + mp_arg_check_num(n_args, n_kw, MICROPY_HW_DEFAULT_SPI_ID < 0 ? 1 : 0, MP_OBJ_FUN_ARGS_MAX, true); // Get SPI bus. - int spi_id = mp_obj_get_int(args[0]); + int spi_id = MICROPY_HW_DEFAULT_SPI_ID; + + if (n_args > 0) { + spi_id = mp_obj_get_int(args[0]); + n_args--; + args++; + } if (spi_id < 0 || spi_id > SERCOM_INST_NUM) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); } @@ -247,16 +263,23 @@ static mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, s self->polarity = DEFAULT_SPI_POLARITY; self->phase = DEFAULT_SPI_PHASE; self->firstbit = DEFAULT_SPI_FIRSTBIT; + #if defined(pin_SCK) && defined(pin_MOSI) && defined(pin_MISO) + // Initialize with the default pins + self->sck = mp_hal_get_pin_obj((mp_obj_t)pin_SCK); + self->mosi = mp_hal_get_pin_obj((mp_obj_t)pin_MOSI); + self->miso = mp_hal_get_pin_obj((mp_obj_t)pin_MISO); + #else self->mosi = 0xff; // 0xff: pin not defined (yet) self->miso = 0xff; self->sck = 0xff; + #endif self->new = true; MP_STATE_PORT(sercom_table[spi_id]) = self; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_spi_init((mp_obj_base_t *)self, n_args - 1, args + 1, &kw_args); + machine_spi_init((mp_obj_base_t *)self, n_args, args, &kw_args); return self; } From daef1c1d146207e6a42219a7d6717df337a11377 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 7 Feb 2025 22:08:17 +0100 Subject: [PATCH 0341/2098] samd/machine_uart: Support default instance and TX/RX pin values. If a board configures a default UART instance and/or TX/RX pins then the user can create a default UART object using `machine.UART()`. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 6e9a5538647..45bfc3ddd50 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 "genhdr/pins.h" #include "shared/runtime/softtimer.h" #define DEFAULT_UART_BAUDRATE (115200) @@ -298,7 +299,7 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ } mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " - "timeout=%u, timeout_char=%u, rxbuf=%d" + "tx=\"%q\", rx=\"%q\", timeout=%u, timeout_char=%u, rxbuf=%d" #if MICROPY_HW_UART_TXBUF ", txbuf=%d" #endif @@ -310,7 +311,8 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #endif ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], - self->stop + 1, self->timeout, self->timeout_char, rxbuf_len + self->stop + 1, pin_find_by_id(self->tx)->name, pin_find_by_id(self->rx)->name, + self->timeout, self->timeout_char, rxbuf_len #if MICROPY_HW_UART_TXBUF , txbuf_len #endif @@ -448,7 +450,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // Check the rx/tx pin assignments if (self->tx == 0xff || self->rx == 0xff || (self->tx / 4) != (self->rx / 4)) { - mp_raise_ValueError(MP_ERROR_TEXT("Non-matching or missing rx/tx")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid or missing rx/tx")); } self->rx_pad_config = get_sercom_config(self->rx, self->id); self->tx_pad_config = get_sercom_config(self->tx, self->id); @@ -479,10 +481,16 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } static mp_obj_t mp_machine_uart_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); + mp_arg_check_num(n_args, n_kw, MICROPY_HW_DEFAULT_UART_ID < 0 ? 1 : 0, MP_OBJ_FUN_ARGS_MAX, true); // Get UART bus. - int uart_id = mp_obj_get_int(args[0]); + int uart_id = MICROPY_HW_DEFAULT_UART_ID; + + if (n_args > 0) { + uart_id = mp_obj_get_int(args[0]); + n_args--; + args++; + } if (uart_id < 0 || uart_id > SERCOM_INST_NUM) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), uart_id); } @@ -495,8 +503,15 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->stop = 0; self->timeout = 1; self->timeout_char = 1; + #if defined(pin_TX) && defined(pin_RX) + // Initialize with the default pins + self->tx = mp_hal_get_pin_obj((mp_obj_t)pin_TX); + self->rx = mp_hal_get_pin_obj((mp_obj_t)pin_RX); + #else + // Have to be defined self->tx = 0xff; self->rx = 0xff; + #endif #if MICROPY_HW_UART_RTSCTS self->rts = 0xff; self->cts = 0xff; @@ -510,7 +525,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - mp_machine_uart_init_helper(self, n_args - 1, args + 1, &kw_args); + mp_machine_uart_init_helper(self, n_args, args, &kw_args); return MP_OBJ_FROM_PTR(self); } From 4cbaab17669b54ddfe8c509eb0f7653706120211 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 8 Feb 2025 09:23:11 +0100 Subject: [PATCH 0342/2098] samd/boards: Add missing TX/RX, SCL/SDA and SCK/MOSI/MISO pin names. These were missing and are needed to support UART/I2C/SPI default pins. Signed-off-by: robert-hh --- ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv | 4 ++-- ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv | 2 ++ ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv | 2 ++ ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv | 3 +++ ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/pins.csv | 3 +++ ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv | 8 +++++++- ports/samd/boards/MINISAM_M4/pins.csv | 6 ++++-- ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv | 2 ++ ports/samd/boards/SEEED_XIAO_SAMD21/pins.csv | 9 ++++++++- ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv | 3 ++- 10 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv index 8993419a268..f2676693ec4 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv @@ -21,8 +21,8 @@ A2,PB09 A3,PA04 A4,PA05 A5,PB02 -TX,PB22 -RX,PB23 +RX,PA11 +TX,PA10 SCL,PA23 SDA,PA22 MOSI,PB10 diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv index ec7a8bc1638..056339de133 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv @@ -15,6 +15,8 @@ A2,PB08 A3,PB09 A4,PA04 A5,PB06 +RX,PB17 +TX,PB16 SCL,PA13 SDA,PA12 MOSI,PB23 diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv index 845cefb8a97..250e42d9d04 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv @@ -16,6 +16,8 @@ A2,PB09 A3,PA04 A4,PA05 A5,PB02 +RX,PA11 +TX,PA10 SDA,PA22 SCL,PA23 MOSI,PB10 diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv index c82beccfa2b..8597815a124 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv @@ -16,6 +16,9 @@ A2,PB08 A3,PB09 A4,PA04 A5,PA06 + +RX,PA16 +TX,PA17 SDA,PA12 SCL,PA13 MOSI,PA00 diff --git a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/pins.csv index cee4bf02b7a..aef7391d12d 100644 --- a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/pins.csv @@ -21,6 +21,9 @@ D11,PA19 D12,PA17 D13,PA16 +RX,PA23 +TX,PA22 + SDA,PB02 SCL,PB03 diff --git a/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv b/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv index 47306f8de1c..fb1e645cf4d 100644 --- a/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv +++ b/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv @@ -1,3 +1,10 @@ +RX,PA07 +TX,PA06 +SDA,PA08 +SCL,PA09 +MOSI,PA06 +MISO,PA09 +SCK,PA07 D0,PA08 D1,PA02 D2,PA09 @@ -5,7 +12,6 @@ D3,PA07 D4,PA06 DOTSTAR_DATA,PA00 DOTSTAR_CLK,PA01 - LED,PA10 USB_DM,PA24 diff --git a/ports/samd/boards/MINISAM_M4/pins.csv b/ports/samd/boards/MINISAM_M4/pins.csv index 3c1ab4c6038..d6a3ef80320 100644 --- a/ports/samd/boards/MINISAM_M4/pins.csv +++ b/ports/samd/boards/MINISAM_M4/pins.csv @@ -7,6 +7,8 @@ A5,PA06 A6,PA07 RX,PA16 TX,PA17 +D0,PA16 +D1,PA17 D3,PA19 D4,PA20 D5,PA21 @@ -15,8 +17,8 @@ BUTTON,PA00 AREF,PA03 SDA,PA12 SCL,PA13 -MOSI,PB22 -MISO,PB23 +MOSI,PB23 +MISO,PB22 SCK,PA01 DOTSTAR_DATA,PB03 DOTSTAR_CLK,PB02 diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv index f263253cbaa..c335ba2baff 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv @@ -19,6 +19,8 @@ ENABLE_5V,PC14 ENABLE_3V3,PC15 TX,PB26 RX,PB27 +SDA,PA13 +SCL,PA12 SDA0,PA13 SCL0,PA12 SDA1,PA17 diff --git a/ports/samd/boards/SEEED_XIAO_SAMD21/pins.csv b/ports/samd/boards/SEEED_XIAO_SAMD21/pins.csv index 969c60028b4..6f70f3308e7 100644 --- a/ports/samd/boards/SEEED_XIAO_SAMD21/pins.csv +++ b/ports/samd/boards/SEEED_XIAO_SAMD21/pins.csv @@ -1,3 +1,11 @@ +RX,PB09 +TX,PB08 +SCL,PA09 +SDA,PA08 +MOSI,PA06 +MISO,PA05 +SCK,PA07 + A0_D0,PA02 A1_D1,PA04 A2_D2,PA10 @@ -9,7 +17,6 @@ A7_D7,PB09 A8_D8,PA07 A9_D9,PA05 A10_D10,PA06 - USER_LED,PA17 RX_LED,PA18 TX_LED,PA19 diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv index 0ae269724da..fe47fef27f2 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv @@ -15,6 +15,8 @@ A2,PB09 A3,PA04 A4,PA05 A5,PB02 +RX,PA13 +TX,PA12 SDA,PA22 SCL,PA23 MOSI,PB12 @@ -30,7 +32,6 @@ TXLED,PA27 USB_DM,PA24 USB_DP,PA25 -USB_SOF,PA23 SWCLK,PA30 SWDIO,PA31 From b9b4f1b40b9f97a5b8893afe9cb139a09fb76fd9 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 11 Feb 2025 10:07:07 +0100 Subject: [PATCH 0343/2098] samd/boards: Provide default IDs for UART, I2C and SPI. In combination with the defautl Pins the default device can now be instantiated e.g. as: uart = UART(). Similar for I2C and SPI. Signed-off-by: robert-hh --- .../boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h | 4 ++++ .../boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h | 4 ++++ .../boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h | 4 ++++ .../boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h | 4 ++++ .../boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.h | 4 ++++ ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h | 4 ++++ ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h | 4 ++++ ports/samd/boards/MINISAM_M4/mpconfigboard.h | 4 ++++ ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h | 4 ++++ ports/samd/boards/SEEED_XIAO_SAMD21/mpconfigboard.h | 4 ++++ .../boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h | 4 ++++ ports/samd/mpconfigport.h | 9 +++++++++ 12 files changed, 53 insertions(+) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h index 880df8d2003..a2df6337654 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h @@ -3,5 +3,9 @@ #define MICROPY_HW_XOSC32K (1) +#define MICROPY_HW_DEFAULT_UART_ID (2) +#define MICROPY_HW_DEFAULT_I2C_ID (3) +#define MICROPY_HW_DEFAULT_SPI_ID (4) + #define MICROPY_HW_SPIFLASH (1) #define MICROPY_HW_SPIFLASH_ID (2) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h index a9f7d518e23..f68a2630319 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h @@ -4,4 +4,8 @@ #define MICROPY_HW_XOSC32K (1) #define MICROPY_HW_MCU_OSC32KULP (1) +#define MICROPY_HW_DEFAULT_UART_ID (5) +#define MICROPY_HW_DEFAULT_I2C_ID (2) +#define MICROPY_HW_DEFAULT_SPI_ID (1) + #define MICROPY_HW_QSPIFLASH GD25Q16C diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h index 16018fdc563..84b75414f5e 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h @@ -3,5 +3,9 @@ #define MICROPY_HW_DFLL_USB_SYNC (1) +#define MICROPY_HW_DEFAULT_SPI_ID (4) +#define MICROPY_HW_DEFAULT_I2C_ID (3) +#define MICROPY_HW_DEFAULT_UART_ID (0) + #define MICROPY_HW_SPIFLASH (1) #define MICROPY_HW_SPIFLASH_ID (5) diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h index 2f246c60b18..47eabbd8787 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h @@ -3,4 +3,8 @@ #define MICROPY_HW_DFLL_USB_SYNC (1) +#define MICROPY_HW_DEFAULT_SPI_ID (1) +#define MICROPY_HW_DEFAULT_I2C_ID (2) +#define MICROPY_HW_DEFAULT_UART_ID (3) + #define MICROPY_HW_QSPIFLASH GD25Q16C diff --git a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.h index 7893cd706ac..0a2a1c2debe 100644 --- a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.h @@ -4,6 +4,10 @@ #define MICROPY_HW_XOSC32K (1) #define MICROPY_HW_QSPIFLASH GD25Q16C +#define MICROPY_HW_DEFAULT_UART_ID (3) +#define MICROPY_HW_DEFAULT_I2C_ID (5) +#define MICROPY_HW_DEFAULT_SPI_ID (2) + // defines for WLAN #define MICROPY_HW_WIFI_SPI_ID (2) #define MICROPY_HW_WIFI_SPI_BAUDRATE (8000000) diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h index 0acf28afbfe..78b03ce7fbd 100644 --- a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigboard.h @@ -3,4 +3,8 @@ #define MICROPY_HW_DFLL_USB_SYNC (1) +#define MICROPY_HW_DEFAULT_UART_ID (0) +#define MICROPY_HW_DEFAULT_I2C_ID (1) +#define MICROPY_HW_DEFAULT_SPI_ID (0) + #define MICROPY_HW_SPIFLASH_ID (3) diff --git a/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h index 5732a20e3ad..1edff009859 100644 --- a/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h @@ -2,3 +2,7 @@ #define MICROPY_HW_MCU_NAME "SAMD21E18A" #define MICROPY_HW_DFLL_USB_SYNC (1) + +#define MICROPY_HW_DEFAULT_UART_ID (0) +#define MICROPY_HW_DEFAULT_I2C_ID (2) +#define MICROPY_HW_DEFAULT_SPI_ID (0) diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h index 87acf301e36..6d908bdcb81 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.h +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.h @@ -3,4 +3,8 @@ #define MICROPY_HW_DFLL_USB_SYNC (1) +#define MICROPY_HW_DEFAULT_UART_ID (3) +#define MICROPY_HW_DEFAULT_I2C_ID (2) +#define MICROPY_HW_DEFAULT_SPI_ID (1) + #define MICROPY_HW_QSPIFLASH GD25Q16C diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h index 062f69ae4e7..7f6d4234577 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h @@ -3,4 +3,8 @@ #define MICROPY_HW_XOSC32K (1) +#define MICROPY_HW_DEFAULT_UART_ID (2) +#define MICROPY_HW_DEFAULT_I2C_ID (4) +#define MICROPY_HW_DEFAULT_SPI_ID (5) + #define MICROPY_HW_QSPIFLASH W25Q32JV_IQ diff --git a/ports/samd/boards/SEEED_XIAO_SAMD21/mpconfigboard.h b/ports/samd/boards/SEEED_XIAO_SAMD21/mpconfigboard.h index 7447c5c3acf..f0213a88e29 100644 --- a/ports/samd/boards/SEEED_XIAO_SAMD21/mpconfigboard.h +++ b/ports/samd/boards/SEEED_XIAO_SAMD21/mpconfigboard.h @@ -3,3 +3,7 @@ #define MICROPY_HW_XOSC32K (1) #define MICROPY_HW_ADC_VREF (2) + +#define MICROPY_HW_DEFAULT_UART_ID (4) +#define MICROPY_HW_DEFAULT_I2C_ID (2) +#define MICROPY_HW_DEFAULT_SPI_ID (0) diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h index 706fc3c64c3..fe2226a59eb 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h @@ -3,6 +3,10 @@ #define MICROPY_HW_XOSC32K (1) +#define MICROPY_HW_DEFAULT_UART_ID (2) +#define MICROPY_HW_DEFAULT_I2C_ID (3) +#define MICROPY_HW_DEFAULT_SPI_ID (4) + // There seems to be an inconsistency in the SAMD51 Thing bootloader in that // the bootloader magic address is at the end of a 192k RAM area, instead of // 256k. Since the SAMD51x20A has 256k RAM, the loader symbol is at that address diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 0b47500bf7e..514f3839488 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -138,6 +138,15 @@ #ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID (0x9802) #endif +#ifndef MICROPY_HW_DEFAULT_UART_ID +#define MICROPY_HW_DEFAULT_UART_ID (-1) +#endif +#ifndef MICROPY_HW_DEFAULT_I2C_ID +#define MICROPY_HW_DEFAULT_I2C_ID (-1) +#endif +#ifndef MICROPY_HW_DEFAULT_SPI_ID +#define MICROPY_HW_DEFAULT_SPI_ID (-1) +#endif // Additional entries for use with pendsv_schedule_dispatch. #ifndef MICROPY_BOARD_PENDSV_ENTRIES From 016ae19cf0e148ba7b33c2c80312b6f3641217dc Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Feb 2025 10:24:49 +0100 Subject: [PATCH 0344/2098] docs/samd: Update the SAMD documentation describing default IDs/pins. Changes are: - Add the RX/TX pins to the table. In most cases these are the D0/D1 pins. - Document the ability for the instantiation of the default devices without submitting ID or pins. - Improve the example script creating the pin list to show multiple name assigments to the same pin. - Fix errors in the pinout document. Signed-off-by: robert-hh --- docs/samd/pinout.rst | 78 ++++++++++++++++++++++++++++-------------- docs/samd/quickref.rst | 29 ++++++++++++++-- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 776ab74bcdb..3945e2bf2ff 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -41,9 +41,11 @@ Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC 35 PB03 FLASH_MISO 3 11 - 5/1 6/1 - 54 PB22 FLASH_MOSI 6 - - 5/2 7/0 - 55 PB23 FLASH_SCK 7 - - 5/3 7/1 - + 11 PA11 RX 11 19 0/3 2/3 1/1 0/3 + 10 PA10 TX 10 18 0/2 2/2 1/0 0/2 12 PA12 MISO 12 - 2/0 4/0 2/0 0/6 - 42 PB10 MOSI 10 - - 4/2 5/0 0/4 - 43 PB11 SCK 11 - - 4/3 5/1 0/5 + 42 PA12 MOSI 10 - - 4/2 5/0 0/4 + 43 PA13 SCK 11 - - 4/3 5/1 0/5 23 PA23 SCL 7 - 3/1 5/1 4/1 0/5 22 PA22 SDA 6 - 3/0 5/0 4/0 0/4 30 PA30 SWCLK 10 - - 1/2 1/0 - @@ -127,7 +129,7 @@ Examples for Adafruit ItsyBitsy M0 Express: - SPI 1 at pins D11/D12/D13 - SPI 2 at pins D0/D4/D1 - SPI 3 at pins D11/D12/D13 -- SPI 4 at Pin MOSI/MISO/SCK This is the default SPI device at the MOSI/MISO/SCK labelled pins. +- SPI 2 at Pin MOSI/MISO/SCK This is the default SPI device at the MOSI/MISO/SCK labelled pins. or other combinations. @@ -169,6 +171,8 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 22 PA22 D13 6 - - 3/0 5/1 4/0 1/6 0/2 34 PB02 DOTSTAR_CLK 2 14 - - 5/0 6/0 2/2 - 35 PB03 DOTSTAR_DATA 9 15 - - 5/1 6/1 - - + 16 PA16 RX 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 TX 1 - - 1/1 3/0 2/1 1/1 0/5 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - 0 PA00 MOSI 0 - - - 1/0 2/0 - - 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 @@ -235,7 +239,7 @@ The I2C devices and signals must be chosen according to the following rules: - The SDA signal must be at a Pin with pad numbers 0. - The SCL signal must be at a Pin with pad numbers 1. -Examples for Adafruit ItsyBitsy M0 Express: +Examples for Adafruit ItsyBitsy M4 Express: - I2C 0 at pins A3/A4 - I2C 1 at pins D0/D1 @@ -253,7 +257,7 @@ The SPI devices and signals must be chosen according to the following rules: - The following pad number pairs are suitable for MOSI/SCK: 0/1 and 3/1. - The MISO signal must be at a Pin with a different pad number than MOSI or SCK. -Examples for Adafruit ItsyBitsy M0 Express: +Examples for Adafruit ItsyBitsy M4 Express: - SPI 1 at Pin MOSI/MISO/SCK This is the default SPI device at the MOSI/MISO/SCK labelled pins. - SPI 3 at pins D13/D11/D12 @@ -296,6 +300,8 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 21 PA21 D11 5 - - 5/3 3/3 7/1 1/5 0/1 22 PA22 D12 6 - - 3/0 5/1 4/0 1/6 0/2 23 PA23 D13 7 - - 3/1 5/0 4/1 1/7 0/3 + 49 PB17 RX 1 - - 5/1 - 6/1 3/1 0/5 + 48 PB16 TX 0 - - 5/0 - 6/0 3/0 0/4 54 PB22 MISO 22 - - 1/2 5/2 7/0 - - 55 PB23 MOSI 7 - - 1/3 5/3 7/1 - - 35 PB03 NEOPIXEL 9 15 - - 5/1 6/1 - - @@ -381,6 +387,8 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 42 PB10 FLASH_SCK 10 - - - 4/2 5/0 0/4 1/0 10 PA10 FLASH_WP 10 10 - 0/2 2/2 1/0 0/2 1/6 + 23 PA23 RX 7 - - 3/1 5/0 4/1 1/7 0/3 + 22 PA22 TX 6 - - 3/0 5/1 4/0 1/6 0/2 14 PA14 MISO 14 - - 2/2 4/2 3/0 2/0 1/2 12 PA12 MOSI 12 - - 2/0 4/1 2/0 0/6 1/2 54 PB22 NEOPIXEL 22 - - 1/2 5/2 7/0 - - @@ -429,6 +437,13 @@ Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC 5 PA05 A9_D9 5 5 - 0/1 0/1 - 6 PA06 A10_D10 6 6 - 0/2 1/0 - 18 PA18 RX_LED 2 - 1/2 3/2 3/0 0/2 + 41 PB09 RX 9 3 - 4/1 4/1 - + 40 PB08 TX 8 2 - 4/0 4/0 - + 8 PA08 SDA - 16 0/0 2/0 0/0 1/2 + 9 PA09 SCL 9 17 0/1 2/1 0/1 1/3 + 6 PA06 MOSI 6 6 - 0/2 1/0 - + 5 PA05 MISO 5 5 - 0/1 0/1 - + 7 PA07 SCK 7 7 - 0/3 1/1 - 30 PA30 SWCLK 10 - - 1/2 1/0 - 31 PA31 SWDIO 11 - - 1/3 1/1 - 19 PA19 TX_LED 3 - 1/3 3/3 3/1 0/3 @@ -503,6 +518,8 @@ Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC 43 PB11 SCK 11 - - 4/3 5/1 0/5 23 PA23 SCL 7 - 3/1 5/1 4/1 0/5 22 PA22 SDA 6 - 3/0 5/0 4/0 0/4 + 11 PA11 RX 11 19 0/3 2/3 1/1 0/3 + 10 PA10 TX 10 18 0/2 2/2 1/0 0/2 30 PA30 SWCLK 10 - - 1/2 1/0 - 31 PA31 SWDIO 11 - - 1/3 1/1 - 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 @@ -518,9 +535,9 @@ Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. The default devices at the board are: -- UART 5 at pins PB23/PB22, labelled RX/TX +- UART 2 at pins PA11/PA10, labelled RX/TX - I2C 3 at pins PA22/PA23, labelled SDA/SCL -- SPI 4 at pins PA10/PA12/PA11, labelled MOSI, MISO and SCK +- SPI 4 at pins PB10/PA12/PB11, labelled MOSI, MISO and SCK - DAC output on pin PA02, labelled A0 Adafruit Trinket M0 pin assignment table @@ -536,6 +553,13 @@ Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC 6 PA06 D4 6 6 - 0/2 1/0 - 1 PA01 DOTSTAR_CLK 1 - - 1/1 2/1 - 0 PA00 DOTSTAR_DATA 0 - - 1/0 2/0 - + 7 PA07 RX 7 7 - 0/3 1/1 - + 6 PA06 TX 6 6 - 0/2 1/0 - + 8 PA08 SDA - 16 0/0 2/0 0/0 1/2 + 9 PA09 SCL 9 17 0/1 2/1 0/1 1/3 + 6 PA06 MOSI 6 6 - 0/2 1/0 - + 9 PA09 MISO 9 17 0/1 2/1 0/1 1/3 + 7 PA07 SCK 7 7 - 0/3 1/1 - 10 PA10 LED 10 18 0/2 2/2 1/0 0/2 30 PA30 SWCLK 10 - - 1/2 1/0 - 31 PA31 SWDIO 11 - - 1/3 1/1 - @@ -716,8 +740,10 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 34 PB02 DOTSTAR_CLK 2 14 - - 5/0 6/0 2/2 - 35 PB03 DOTSTAR_DATA 9 15 - - 5/1 6/1 - - 15 PA15 LED 15 - - 2/3 4/3 3/1 2/1 1/3 - 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - - 54 PB22 MOSI 22 - - 1/2 5/2 7/0 - - + 16 PA16 RX 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 TX 1 - - 1/1 3/0 2/1 1/1 0/5 + 55 PB23 MOSI 7 - - 1/3 5/3 7/1 - - + 54 PB22 MISO 22 - - 1/2 5/2 7/0 - - 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 @@ -893,6 +919,8 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 11 PA11 FLASH_MISO 11 11 - 0/3 2/3 1/1 0/3 1/7 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 9 PA09 FLASH_SCK 9 9 3 0/1 2/0 0/1 0/1 1/5 + 13 PA13 RX 13 - - 2/1 4/0 2/1 0/7 1/3 + 12 PA12 TX 12 - - 2/0 4/1 2/0 0/6 1/2 43 PB11 MISO 12 - - - 4/3 5/1 0/5 1/1 44 PB12 MOSI 12 - - 4/0 - 4/0 3/0 0/0 55 PB23 RXD 7 - - 1/3 5/3 7/1 - - @@ -931,7 +959,7 @@ Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. The default devices at the board are: - UART 2 at pins PA13/PA12, labelled RXD/TXD -- I2C 5 at pins PA22/PA23, labelled SDA/SCL +- I2C 3 at pins PA22/PA23, labelled SDA/SCL - SPI 4 at pins PB12/PB11/PB13, labelled MOSI, MISO and SCK - DAC output on pins PA02 and PA05, labelled A0 and A4 @@ -1163,22 +1191,22 @@ The tables shown above were created with small a Python script running on the ta else: return "zzzzzzz%03d" % i[0] - def table(num=127, sort=True): - pintbl = [] - inv_bd = {v: k for k, v in Pin.board.__dict__.items()} - for i in range(num): - try: - p = Pin(i) - pi = pininfo(p) - if p in inv_bd.keys(): - name = inv_bd[p] - else: - name = "" - pintbl.append((i, name, pininfo(i))) - except: - pass - # print("not defined") + def pinnum(p): + return (ord(p[1]) - ord("A")) * 32 + int(p[2:]) + def table(num = 127, sort=True): + pintbl = [] + pinlist = [] + for name in Pin.board.__dict__.keys(): + p = Pin(name) + pi = pininfo(p) + pintbl.append((pinnum(pi[0]), name, pi)) + pinlist.append(p) + for pc in Pin.cpu.__dict__.keys(): + p = Pin(pc) + pi = pininfo(p) + if not p in pinlist: + pintbl.append((pinnum(pi[0]), "", pi)) if sort: pintbl.sort(key=tblkey) for item in pintbl: diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index d57dc679083..781686d2f60 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -163,10 +163,15 @@ See :ref:`machine.UART `. :: uart3.write('hello') # write 5 bytes uart3.read(5) # read up to 5 bytes + uart = UART() # Use the default values for id, rx and tx. + uart = UART(baudrate=9600) # Use the default UART and set the baudrate + The SAMD21/SAMD51 MCUs have up to eight hardware so called SERCOM devices, which can be used as UART, SPI or I2C device, but not every MCU variant and board exposes all TX and RX pins for users. For the assignment of Pins to devices and UART signals, -refer to the :ref:`SAMD pinout `. +refer to the :ref:`SAMD pinout `. If the id, rx or tx pins are not specified, +the default values are used. The first positional argument (if given) is assumed to be the UART id. +If the baudrate is changed and the UART id is omitted, it must be set using the baudrate keyword. PWM (pulse width modulation) ---------------------------- @@ -373,8 +378,18 @@ signal pins for users. Hardware SPI is accessed via the spi = SPI(1, sck=Pin("SCK"), mosi=Pin("MOSI"), miso=Pin("MISO"), baudrate=10000000) spi.write('Hello World') -If miso is not specified, it is not used. For the assignment of Pins to SPI devices and signals, refer to -:ref:`SAMD pinout `. +For the assignment of Pins to SPI devices and signals, refer to +:ref:`SAMD pinout `. If the id, miso, mosi or sck pins are not specified, +the default values are used. So it is possible to create the SPI object as:: + + from machine import SPI + spi = SPI() # Use the default device and default baudrate + spi = SPI(baudrate=12_000_000) # Use the default device and change the baudrate + +If the MISO signal shall be omitted, it must be defined as miso=None. +The first positional argument (if given) is assumed to be the SPI id. +If the baudrate is changed while the SPI id is omitted, it must be +set using the baudrate keyword. Note: Even if the highest reliable baud rate at the moment is about 24 Mhz, setting a baud rate will not always result in exactly that frequency, especially @@ -407,6 +422,7 @@ The SAMD21/SAMD51 MCUs have up to eight hardware so called SERCOM devices, which can be used as UART, SPI or I2C device, but not every MCU variant and board exposes all signal pins for users. For the assignment of Pins to devices and I2C signals, refer to :ref:`SAMD pinout `. +If the id, scl or sda pins are not specified, the default values are used. Hardware I2C is accessed via the :ref:`machine.I2C ` class and has the same methods as software SPI above:: @@ -416,6 +432,13 @@ has the same methods as software SPI above:: i2c = I2C(2, scl=Pin("SCL"), sda=Pin("SDA"), freq=400_000) i2c.writeto(0x76, b"Hello World") + i2c2 = I2C() # Use the default values for id, scl and sda. + i2c2 = I2C(freq=100_000) # Use the default device and set freq. + +The first positional argument (if given) is assumed to be the I2C id. +If the freq is changed and the I2C id is omitted, it must be set using +the freq keyword. + OneWire driver -------------- From 5f01232dd7c0a211e8beff24dc88efa48598ef36 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 26 Feb 2025 10:58:37 +1100 Subject: [PATCH 0345/2098] tests/run-tests: Remove any 'expected' file from a unittest run. This won't be generated normally, but a failed run (for example, from a unittest with an error or which doesn't call unittest.main()) will generate one. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/run-tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 1af3dfc2973..38f11613602 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -996,6 +996,8 @@ def run_one_test(test_file): if output_expected is not None: with open(filename_expected, "wb") as f: f.write(output_expected) + else: + rm_f(filename_expected) # in case left over from previous failed run with open(filename_mupy, "wb") as f: f.write(output_mupy) failed_tests.append((test_name, test_file)) From b4cf82b2d62c60c5e6e10d8d23c6b099b96c6f75 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 26 Feb 2025 11:00:53 +1100 Subject: [PATCH 0346/2098] test/run-tests: Print a note if it looks like unittest.main() missing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/run-tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 38f11613602..50adb6b4d3b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1004,6 +1004,16 @@ def run_one_test(test_file): test_count.increment() + # Print a note if this looks like it might have been a misfired unittest + if not uses_unittest and not test_passed: + with open(test_file, "r") as f: + if any(re.match("^import.+unittest", l) for l in f.readlines()): + print( + "NOTE: {} may be a unittest that doesn't run unittest.main()".format( + test_file + ) + ) + if pyb: num_threads = 1 From f5b4545761dbbce0c00dec6d90d180663a61eaa4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 2 Mar 2025 23:52:13 +1100 Subject: [PATCH 0347/2098] py/modsys: Add sys.implementation._build entry. For a given MicroPython firmware/executable it can be sometimes important to know how it was built, which variant/board configuration it came from. This commit adds a new field `sys.implementation._build` that can help identify the configuration that MicroPython was built with. For now it's either: * for unix, webassembly and windows ports * - for microcontroller ports (the variant is optional) In the future additional elements may be added to this string, separated by a hyphen. Resolves issue #16498. Signed-off-by: Damien George --- docs/library/sys.rst | 12 ++++++++++++ py/mkrules.cmake | 13 +++++++++++++ py/mkrules.mk | 11 +++++++++++ py/modsys.c | 23 +++++++++++++++++++---- tests/basics/sys1.py | 6 ++++++ 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/docs/library/sys.rst b/docs/library/sys.rst index c72214c1310..baefd927051 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -75,6 +75,8 @@ Constants * *version* - tuple (major, minor, micro, releaselevel), e.g. (1, 22, 0, '') * *_machine* - string describing the underlying machine * *_mpy* - supported mpy file-format version (optional attribute) + * *_build* - string that can help identify the configuration that + MicroPython was built with This object is the recommended way to distinguish MicroPython from other Python implementations (note that it still may not exist in the very @@ -83,6 +85,16 @@ Constants Starting with version 1.22.0-preview, the fourth node *releaselevel* in *implementation.version* is either an empty string or ``"preview"``. + The *_build* entry was added in version 1.25.0 and is a hyphen-separated + set of elements. New elements may be appended in the future so it's best to + access this field using ``sys.implementation._build.split("-")``. The + elements that are currently used are: + + * On the unix, webassembly and windows ports the first element is the variant + name, for example ``'standard'``. + * On microcontroller targets, the first element is the board name and the second + element (if present) is the board variant, for example ``'RPI_PICO2-RISCV'`` + .. admonition:: Difference to CPython :class: attention diff --git a/py/mkrules.cmake b/py/mkrules.cmake index 907500dd675..cafcbce5681 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -19,6 +19,19 @@ if(NOT MICROPY_PREVIEW_VERSION_2) set(MICROPY_PREVIEW_VERSION_2 0) endif() +# Set the board name. +if(MICROPY_BOARD) + if(MICROPY_BOARD_VARIANT) + set(MICROPY_BOARD_BUILD_NAME ${MICROPY_BOARD}-${MICROPY_BOARD_VARIANT}) + else() + set(MICROPY_BOARD_BUILD_NAME ${MICROPY_BOARD}) + endif() + + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_BOARD_BUILD_NAME="${MICROPY_BOARD_BUILD_NAME}" + ) +endif() + # Need to do this before extracting MICROPY_CPP_DEF below. Rest of frozen # manifest handling is at the end of this file. if(MICROPY_FROZEN_MANIFEST) diff --git a/py/mkrules.mk b/py/mkrules.mk index e9504ce39f8..ad855c0d7fc 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -27,6 +27,17 @@ OBJ_EXTRA_ORDER_DEPS += $(HEADER_BUILD)/compressed.data.h CFLAGS += -DMICROPY_ROM_TEXT_COMPRESSION=1 endif +# Set the variant or board name. +ifneq ($(VARIANT),) +CFLAGS += -DMICROPY_BOARD_BUILD_NAME=\"$(VARIANT)\" +else ifneq ($(BOARD),) +ifeq ($(BOARD_VARIANT),) +CFLAGS += -DMICROPY_BOARD_BUILD_NAME=\"$(BOARD)\" +else +CFLAGS += -DMICROPY_BOARD_BUILD_NAME=\"$(BOARD)-$(BOARD_VARIANT)\" +endif +endif + # QSTR generation uses the same CFLAGS, with these modifications. QSTR_GEN_FLAGS = -DNO_QSTR # Note: := to force evaluation immediately. diff --git a/py/modsys.c b/py/modsys.c index e90ea2233a7..9ab02293b90 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -92,6 +92,17 @@ static const MP_DEFINE_STR_OBJ(mp_sys_implementation_machine_obj, MICROPY_BANNER #endif #if MICROPY_PY_ATTRTUPLE + +#if defined(MICROPY_BOARD_BUILD_NAME) +static const MP_DEFINE_STR_OBJ(mp_sys_implementation__build_obj, MICROPY_BOARD_BUILD_NAME); +#define MICROPY_BOARD_BUILD (1) +#define SYS_IMPLEMENTATION_ELEMS__BUILD \ + , MP_ROM_PTR(&mp_sys_implementation__build_obj) +#else +#define MICROPY_BOARD_BUILD (0) +#define SYS_IMPLEMENTATION_ELEMS__BUILD +#endif + #if MICROPY_PREVIEW_VERSION_2 #define SYS_IMPLEMENTATION_ELEMS__V2 \ , MP_ROM_TRUE @@ -106,6 +117,9 @@ static const qstr impl_fields[] = { #if MICROPY_PERSISTENT_CODE_LOAD MP_QSTR__mpy, #endif + #if defined(MICROPY_BOARD_BUILD_NAME) + MP_QSTR__build, + #endif #if MICROPY_PREVIEW_VERSION_2 MP_QSTR__v2, #endif @@ -113,19 +127,20 @@ static const qstr impl_fields[] = { static MP_DEFINE_ATTRTUPLE( mp_sys_implementation_obj, impl_fields, - 3 + MICROPY_PERSISTENT_CODE_LOAD + MICROPY_PREVIEW_VERSION_2, + 3 + MICROPY_PERSISTENT_CODE_LOAD + MICROPY_BOARD_BUILD + MICROPY_PREVIEW_VERSION_2, SYS_IMPLEMENTATION_ELEMS_BASE SYS_IMPLEMENTATION_ELEMS__MPY + SYS_IMPLEMENTATION_ELEMS__BUILD SYS_IMPLEMENTATION_ELEMS__V2 ); #else static const mp_rom_obj_tuple_t mp_sys_implementation_obj = { {&mp_type_tuple}, 3 + MICROPY_PERSISTENT_CODE_LOAD, - // Do not include SYS_IMPLEMENTATION_ELEMS__V2 because - // SYS_IMPLEMENTATION_ELEMS__MPY may be empty if + // Do not include SYS_IMPLEMENTATION_ELEMS__BUILD or SYS_IMPLEMENTATION_ELEMS__V2 + // because SYS_IMPLEMENTATION_ELEMS__MPY may be empty if // MICROPY_PERSISTENT_CODE_LOAD is disabled, which means they'll share - // the same index. Cannot query _v2 if MICROPY_PY_ATTRTUPLE is + // the same index. Cannot query _build or _v2 if MICROPY_PY_ATTRTUPLE is // disabled. { SYS_IMPLEMENTATION_ELEMS_BASE diff --git a/tests/basics/sys1.py b/tests/basics/sys1.py index 95d8905d135..94220fe4a60 100644 --- a/tests/basics/sys1.py +++ b/tests/basics/sys1.py @@ -24,6 +24,12 @@ # Effectively skip subtests print(int) +if hasattr(sys.implementation, '_build'): + print(type(sys.implementation._build)) +else: + # Effectively skip subtests + print(str) + try: print(sys.intern('micropython') == 'micropython') has_intern = True From 859b6efce52b0ca605681e3b10e2304577942a9a Mon Sep 17 00:00:00 2001 From: danicampora Date: Fri, 21 Feb 2025 14:41:03 +0100 Subject: [PATCH 0348/2098] zephyr/machine_wdt: Add watchdog timer implementation. Simple implementation in-line with the rest of the MicroPython ports Tested on the nRF52832 and the nRF5340. Signed-off-by: danicampora --- ports/zephyr/machine_wdt.c | 92 +++++++++++++++++++++++++++++++++++++ ports/zephyr/mpconfigport.h | 4 ++ ports/zephyr/prj.conf | 3 ++ 3 files changed, 99 insertions(+) create mode 100644 ports/zephyr/machine_wdt.c diff --git a/ports/zephyr/machine_wdt.c b/ports/zephyr/machine_wdt.c new file mode 100644 index 00000000000..7c5e6337ca3 --- /dev/null +++ b/ports/zephyr/machine_wdt.c @@ -0,0 +1,92 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Daniel Campora on behalf of REMOTE TECH LTD + * + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_wdt.c via MICROPY_PY_MACHINE_WDT_INCLUDEFILE. + +#include +#include +#include +#include +#include + +#include "py/mperrno.h" + +typedef struct _machine_wdt_obj_t { + mp_obj_base_t base; + struct device *wdt; + int32_t channel_id; +} machine_wdt_obj_t; + +static machine_wdt_obj_t wdt_default = { + {&machine_wdt_type}, NULL, -1 +}; + +static machine_wdt_obj_t *mp_machine_wdt_make_new_instance(mp_int_t id, mp_int_t timeout_ms) { + if (id != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid WDT id")); + } + + if (timeout_ms <= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("watchdog timeout too short")); + } + + wdt_default.wdt = (struct device *)DEVICE_DT_GET(DT_ALIAS(watchdog0)); + + if (!device_is_ready(wdt_default.wdt)) { + mp_raise_OSError(MP_ENODEV); + } + + struct wdt_timeout_cfg wdt_config = { + /* Reset SoC when watchdog timer expires. */ + .flags = WDT_FLAG_RESET_SOC, + + /* Expire watchdog after max window */ + .window.min = 0, + .window.max = timeout_ms, + }; + + wdt_default.channel_id = wdt_install_timeout(wdt_default.wdt, &wdt_config); + + if (wdt_default.channel_id < 0) { + mp_raise_OSError(-wdt_default.channel_id); + } + + mp_int_t rs_code = wdt_setup(wdt_default.wdt, WDT_OPT_PAUSE_IN_SLEEP | WDT_OPT_PAUSE_HALTED_BY_DBG); + + if (rs_code < 0) { + mp_raise_OSError(-rs_code); + } + + return &wdt_default; +} + +static void mp_machine_wdt_feed(machine_wdt_obj_t *self) { + mp_int_t rs_code = wdt_feed(self->wdt, self->channel_id); + if (rs_code < 0) { + mp_raise_OSError(-rs_code); + } +} diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index a2e74e69289..c4fa6c5b954 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -68,6 +68,10 @@ #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/zephyr/machine_uart.c" +#ifdef CONFIG_WATCHDOG +#define MICROPY_PY_MACHINE_WDT (1) +#define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/zephyr/machine_wdt.c" +#endif #define MICROPY_PY_STRUCT (0) #ifdef CONFIG_NETWORKING // If we have networking, we likely want errno comfort diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index eac04216c7f..ae37c3ff076 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -79,3 +79,6 @@ CONFIG_NET_BUF_POOL_USAGE=y CONFIG_MICROPY_CONFIGFILE="mpconfigport.h" CONFIG_MICROPY_VFS_FAT=y CONFIG_MICROPY_VFS_LFS2=y + +CONFIG_WATCHDOG=y +CONFIG_WDT_DISABLE_AT_BOOT=y From fc71f7832f202acf8ab45ab36ebffd706cb0ab59 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Fri, 18 Oct 2024 12:08:11 +0300 Subject: [PATCH 0349/2098] py/makeqstrdata.py: Implement MicroPython compatibility. This allows running `py/makeqstrdata.py` with MicroPython itself. Signed-off-by: Volodymyr Shymanskyy Signed-off-by: Angus Gratton --- py/makeqstrdata.py | 64 ++++++++++++++++++++++++++++++++++++++-------- pyproject.toml | 2 +- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/py/makeqstrdata.py b/py/makeqstrdata.py index 480344180a8..3a9c7aff525 100644 --- a/py/makeqstrdata.py +++ b/py/makeqstrdata.py @@ -9,17 +9,54 @@ import re import sys -# Python 2/3 compatibility: +# Python 2/3/MicroPython compatibility: # - iterating through bytes is different -# - codepoint2name lives in a different module -import platform - -if platform.python_version_tuple()[0] == "2": +# - codepoint2name from html.entities is hard-coded +if sys.version_info[0] == 2: bytes_cons = lambda val, enc=None: bytearray(val) - from htmlentitydefs import codepoint2name -elif platform.python_version_tuple()[0] == "3": +elif sys.version_info[0] == 3: # Also handles MicroPython bytes_cons = bytes - from html.entities import codepoint2name + +# fmt: off +codepoint2name = { + 198: "AElig", 193: "Aacute", 194: "Acirc", 192: "Agrave", 913: "Alpha", 197: "Aring", 195: "Atilde", + 196: "Auml", 914: "Beta", 199: "Ccedil", 935: "Chi", 8225: "Dagger", 916: "Delta", 208: "ETH", + 201: "Eacute", 202: "Ecirc", 200: "Egrave", 917: "Epsilon", 919: "Eta", 203: "Euml", 915: "Gamma", + 205: "Iacute", 206: "Icirc", 204: "Igrave", 921: "Iota", 207: "Iuml", 922: "Kappa", 923: "Lambda", + 924: "Mu", 209: "Ntilde", 925: "Nu", 338: "OElig", 211: "Oacute", 212: "Ocirc", 210: "Ograve", + 937: "Omega", 927: "Omicron", 216: "Oslash", 213: "Otilde", 214: "Ouml", 934: "Phi", 928: "Pi", + 8243: "Prime", 936: "Psi", 929: "Rho", 352: "Scaron", 931: "Sigma", 222: "THORN", 932: "Tau", + 920: "Theta", 218: "Uacute", 219: "Ucirc", 217: "Ugrave", 933: "Upsilon", 220: "Uuml", 926: "Xi", + 221: "Yacute", 376: "Yuml", 918: "Zeta", 225: "aacute", 226: "acirc", 180: "acute", 230: "aelig", + 224: "agrave", 8501: "alefsym", 945: "alpha", 38: "amp", 8743: "and", 8736: "ang", 229: "aring", + 8776: "asymp", 227: "atilde", 228: "auml", 8222: "bdquo", 946: "beta", 166: "brvbar", 8226: "bull", + 8745: "cap", 231: "ccedil", 184: "cedil", 162: "cent", 967: "chi", 710: "circ", 9827: "clubs", + 8773: "cong", 169: "copy", 8629: "crarr", 8746: "cup", 164: "curren", 8659: "dArr", 8224: "dagger", + 8595: "darr", 176: "deg", 948: "delta", 9830: "diams", 247: "divide", 233: "eacute", 234: "ecirc", + 232: "egrave", 8709: "empty", 8195: "emsp", 8194: "ensp", 949: "epsilon", 8801: "equiv", 951: "eta", + 240: "eth", 235: "euml", 8364: "euro", 8707: "exist", 402: "fnof", 8704: "forall", 189: "frac12", + 188: "frac14", 190: "frac34", 8260: "frasl", 947: "gamma", 8805: "ge", 62: "gt", 8660: "hArr", + 8596: "harr", 9829: "hearts", 8230: "hellip", 237: "iacute", 238: "icirc", 161: "iexcl", 236: "igrave", + 8465: "image", 8734: "infin", 8747: "int", 953: "iota", 191: "iquest", 8712: "isin", 239: "iuml", + 954: "kappa", 8656: "lArr", 955: "lambda", 9001: "lang", 171: "laquo", 8592: "larr", 8968: "lceil", + 8220: "ldquo", 8804: "le", 8970: "lfloor", 8727: "lowast", 9674: "loz", 8206: "lrm", 8249: "lsaquo", + 8216: "lsquo", 60: "lt", 175: "macr", 8212: "mdash", 181: "micro", 183: "middot", 8722: "minus", + 956: "mu", 8711: "nabla", 160: "nbsp", 8211: "ndash", 8800: "ne", 8715: "ni", 172: "not", 8713: "notin", + 8836: "nsub", 241: "ntilde", 957: "nu", 243: "oacute", 244: "ocirc", 339: "oelig", 242: "ograve", + 8254: "oline", 969: "omega", 959: "omicron", 8853: "oplus", 8744: "or", 170: "ordf", 186: "ordm", + 248: "oslash", 245: "otilde", 8855: "otimes", 246: "ouml", 182: "para", 8706: "part", 8240: "permil", + 8869: "perp", 966: "phi", 960: "pi", 982: "piv", 177: "plusmn", 163: "pound", 8242: "prime", + 8719: "prod", 8733: "prop", 968: "psi", 34: "quot", 8658: "rArr", 8730: "radic", 9002: "rang", + 187: "raquo", 8594: "rarr", 8969: "rceil", 8221: "rdquo", 8476: "real", 174: "reg", 8971: "rfloor", + 961: "rho", 8207: "rlm", 8250: "rsaquo", 8217: "rsquo", 8218: "sbquo", 353: "scaron", 8901: "sdot", + 167: "sect", 173: "shy", 963: "sigma", 962: "sigmaf", 8764: "sim", 9824: "spades", 8834: "sub", + 8838: "sube", 8721: "sum", 8835: "sup", 185: "sup1", 178: "sup2", 179: "sup3", 8839: "supe", + 223: "szlig", 964: "tau", 8756: "there4", 952: "theta", 977: "thetasym", 8201: "thinsp", 254: "thorn", + 732: "tilde", 215: "times", 8482: "trade", 8657: "uArr", 250: "uacute", 8593: "uarr", 251: "ucirc", + 249: "ugrave", 168: "uml", 978: "upsih", 965: "upsilon", 252: "uuml", 8472: "weierp", 958: "xi", + 253: "yacute", 165: "yen", 255: "yuml", 950: "zeta", 8205: "zwj", 8204: "zwnj" +} +# fmt: on # end compatibility code codepoint2name[ord("-")] = "hyphen" @@ -295,6 +332,9 @@ "", } +# Matches any string that needs no escaping (alphanum + _ only) +RE_NO_ESCAPE = re.compile(r"^[a-zA-Z0-9_]$") + # this must match the equivalent function in qstr.c def compute_hash(qstr, bytes_hash): @@ -307,15 +347,17 @@ def compute_hash(qstr, bytes_hash): def qstr_escape(qst): - def esc_char(m): - c = ord(m.group(0)) + def esc_char(c): + if RE_NO_ESCAPE.match(c): + return c + c = ord(c) try: name = codepoint2name[c] except KeyError: name = "0x%02x" % c return "_" + name + "_" - return re.sub(r"[^A-Za-z0-9_]", esc_char, qst) + return "".join(map(esc_char, qst)) static_qstr_list_ident = list(map(qstr_escape, static_qstr_list)) diff --git a/pyproject.toml b/pyproject.toml index d5e3d0e8474..8053df24dd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.codespell] count = "" ignore-regex = '\b[A-Z]{3}\b' -ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,numer,shft,synopsys,technic,ure" +ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,notin,numer,shft,synopsys,technic,ure,curren" quiet-level = 3 skip = """ */build*,\ From e1b2f2e078727999148a71ab969c1685173d25c0 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Fri, 18 Oct 2024 12:08:11 +0300 Subject: [PATCH 0350/2098] tools/mpy-tool.py: Add support for self-hosting of mpy-tool. This allows running mpy-tool using MicroPython itself. An appropriate test is added to CI to make sure it continues to work. Signed-off-by: Volodymyr Shymanskyy Signed-off-by: Angus Gratton --- tools/ci.sh | 10 ++++++++++ tools/mpy-tool.py | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 5b108d62025..682c3ae507a 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -118,9 +118,19 @@ function ci_mpy_format_test { python2.7 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy + # Build MicroPython + ci_unix_standard_build + micropython=./ports/unix/build-standard/micropython + $micropython -m mip install --target . argparse __future__ + export MICROPYPATH=. + + # Test mpy-tool.py running under MicroPython + $micropython ./tools/mpy-tool.py -x -d tests/frozen/frozentest.mpy + # Test mpy-tool.py dump feature on native code make -C examples/natmod/features1 ./tools/mpy-tool.py -xd examples/natmod/features1/features1.mpy + $micropython ./tools/mpy-tool.py -x -d examples/natmod/features1/features1.mpy } ######################################################################################## diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index cdceae37466..0094c12177d 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -24,11 +24,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# Python 2/3 compatibility code +# Python 2/3/MicroPython compatibility code from __future__ import print_function -import platform +import sys -if platform.python_version_tuple()[0] == "2": +if sys.version_info[0] == 2: from binascii import hexlify as hexlify_py2 str_cons = lambda val, enc=None: str(val) @@ -41,7 +41,7 @@ def hexlify_to_str(b): x = hexlify_py2(b) return ":".join(x[i : i + 2] for i in range(0, len(x), 2)) -else: +elif sys.version_info[0] == 3: # Also handles MicroPython from binascii import hexlify str_cons = str From 6fba1e406bde7ddf00f7cf52bd8c44764c8a1a97 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Fri, 18 Oct 2024 12:08:11 +0300 Subject: [PATCH 0351/2098] tools/mpy-tool.py: Support calling main() from an external script. Signed-off-by: Volodymyr Shymanskyy Signed-off-by: Angus Gratton --- tools/mpy-tool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 0094c12177d..69d3a6c06f6 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1765,7 +1765,7 @@ def copy_section(file, offset, offset2): f.write(merged_mpy) -def main(): +def main(args=None): global global_qstrs import argparse @@ -1797,7 +1797,7 @@ def main(): ) cmd_parser.add_argument("-o", "--output", default=None, help="output file") cmd_parser.add_argument("files", nargs="+", help="input .mpy files") - args = cmd_parser.parse_args() + args = cmd_parser.parse_args(args) # set config values relevant to target machine config.MICROPY_LONGINT_IMPL = { From 4d034f817c861b90cbe7ab0cb59d85036031750f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 15 Feb 2025 00:58:01 +0100 Subject: [PATCH 0352/2098] esp8266/network_wlan: Allow enumerating connected stations in AP mode. This commit introduces the ability to obtain a list of stations connected to the device when in soft-AP mode. A new parameter ("stations") to pass to WLAN.status is supported, returning a tuple of (bssid, ipv4) entries, one per connected station. An empty tuple is returned if no stations are connected, and an exception is raised if an error occurred whilst building the python objects to return to the interpreter. Documentation is also updated to cover the new parameter. This fixes #5395. Signed-off-by: Alessandro Gatti --- docs/library/network.WLAN.rst | 13 ++++++++++++- ports/esp8266/network_wlan.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 09fefc5929f..ee0ef490fda 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -85,7 +85,18 @@ Methods * ``STAT_GOT_IP`` -- connection successful. When called with one argument *param* should be a string naming the status - parameter to retrieve. Supported parameters in WiFI STA mode are: ``'rssi'``. + parameter to retrieve, and different parameters are supported depending on the + mode the WiFi is in. + + In STA mode, passing ``'rssi'`` returns a signal strength indicator value, whose + format varies depending on the port (this is available on all ports that support + WiFi network interfaces, except for CC3200). + + In AP mode, passing ``'stations'`` returns a list of connected WiFi stations + (this is available on all ports that support WiFi network interfaces, except for + CC3200). The format of the station information entries varies across ports, + providing either the raw BSSID of the connected station, the IP address of the + connected station, or both. .. method:: WLAN.isconnected() diff --git a/ports/esp8266/network_wlan.c b/ports/esp8266/network_wlan.c index 7131b88bac9..9084f246fea 100644 --- a/ports/esp8266/network_wlan.c +++ b/ports/esp8266/network_wlan.c @@ -170,6 +170,29 @@ static mp_obj_t esp_disconnect(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(esp_disconnect_obj, esp_disconnect); +static void build_stations_list_cleanup_callback(void *station_info) { + if (station_info != NULL) { + wifi_softap_free_station_info(); + } +} + +static mp_obj_t build_stations_list(void) { + struct station_info *info = wifi_softap_get_station_info(); + MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, build_stations_list_cleanup_callback, (void *)info); + nlr_push_jump_callback(&ctx.callback, mp_call_function_1_from_nlr_jump_callback); + mp_obj_list_t *stations = MP_OBJ_TO_PTR(mp_obj_new_list(0, NULL)); + struct station_info *current = info; + mp_obj_t entry[2] = { 0 }; + while (current != NULL) { + entry[0] = mp_obj_new_bytes(current->bssid, sizeof(current->bssid)); + entry[1] = netutils_format_ipv4_addr((uint8_t *)¤t->ip.addr, NETUTILS_BIG); + mp_obj_list_append(stations, mp_obj_new_tuple(2, entry)); + current = STAILQ_NEXT(current, next); + } + nlr_pop_jump_callback(true); + return MP_OBJ_FROM_PTR(stations); +} + static mp_obj_t esp_status(size_t n_args, const mp_obj_t *args) { wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 1) { @@ -185,6 +208,12 @@ static mp_obj_t esp_status(size_t n_args, const mp_obj_t *args) { if (self->if_id == STATION_IF) { return MP_OBJ_NEW_SMALL_INT(wifi_station_get_rssi()); } + break; + case MP_QSTR_stations: + if (self->if_id == SOFTAP_IF) { + return build_stations_list(); + } + break; } mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); } From 6be7570219c05f5dd4a5e0bc10be3cff851da8c8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 4 Mar 2025 10:48:55 +1100 Subject: [PATCH 0353/2098] nrf/modules: Fix access of read-only buffer in Flash.writeblocks. When writing to flash, the source buffer only needs to be read-only, not writable. This fix allows passing in `bytes` and other read-only buffer objects. Signed-off-by: Damien George --- ports/nrf/modules/nrf/flashbdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/nrf/modules/nrf/flashbdev.c b/ports/nrf/modules/nrf/flashbdev.c index e490dc53dd6..41833b228e2 100644 --- a/ports/nrf/modules/nrf/flashbdev.c +++ b/ports/nrf/modules/nrf/flashbdev.c @@ -80,7 +80,7 @@ mp_obj_t nrf_flashbdev_writeblocks(size_t n_args, const mp_obj_t *args) { nrf_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); uint32_t block_num = mp_obj_get_int(args[1]); mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); mp_int_t address = self->start + (block_num * FLASH_PAGESIZE); From 9dd4cef81416711ad05d3ee874f2813b42926541 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 3 Jan 2025 11:53:00 +1100 Subject: [PATCH 0354/2098] py/objarray: Add MP_DEFINE_MEMORYVIEW_OBJ convenience macro. This allows defining a `memoryview` instance, either statically or on the C stack. Signed-off-by: Damien George --- py/objarray.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/py/objarray.h b/py/objarray.h index 4a0e8a983fe..bb7a514b979 100644 --- a/py/objarray.h +++ b/py/objarray.h @@ -53,6 +53,10 @@ typedef struct _mp_obj_array_t { } mp_obj_array_t; #if MICROPY_PY_BUILTINS_MEMORYVIEW + +#define MP_DEFINE_MEMORYVIEW_OBJ(obj_name, typecode, offset, len, ptr) \ + mp_obj_array_t obj_name = {{&mp_type_memoryview}, (typecode), (offset), (len), (ptr)} + static inline void mp_obj_memoryview_init(mp_obj_array_t *self, size_t typecode, size_t offset, size_t len, void *items) { self->base.type = &mp_type_memoryview; self->typecode = typecode; @@ -60,6 +64,7 @@ static inline void mp_obj_memoryview_init(mp_obj_array_t *self, size_t typecode, self->len = len; self->items = items; } + #endif #if MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY From 89e6c58c8007e2628741e73c32866c23e8478ae1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Nov 2024 12:57:34 +1100 Subject: [PATCH 0355/2098] extmod/modvfs: Add vfs.rom_ioctl function and its ioctl constants. This is a generic interface to allow querying and modifying the read-only memory area of a device, if it has such an area. Signed-off-by: Damien George --- extmod/modvfs.c | 7 +++++++ extmod/vfs.h | 15 +++++++++++++++ ports/qemu/mpconfigport.h | 1 + ports/unix/variants/mpconfigvariant_common.h | 1 + py/mpconfig.h | 5 +++++ 5 files changed, 29 insertions(+) diff --git a/extmod/modvfs.c b/extmod/modvfs.c index df422365b75..41841f05569 100644 --- a/extmod/modvfs.c +++ b/extmod/modvfs.c @@ -38,11 +38,18 @@ #error "MICROPY_PY_VFS requires MICROPY_VFS" #endif +#if MICROPY_VFS_ROM_IOCTL +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_rom_ioctl_obj, 1, 4, mp_vfs_rom_ioctl); +#endif + static const mp_rom_map_elem_t vfs_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_vfs) }, { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, + #if MICROPY_VFS_ROM_IOCTL + { MP_ROM_QSTR(MP_QSTR_rom_ioctl), MP_ROM_PTR(&mp_vfs_rom_ioctl_obj) }, + #endif #if MICROPY_VFS_FAT { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, #endif diff --git a/extmod/vfs.h b/extmod/vfs.h index 626e25a3511..cc9c50d2900 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -52,6 +52,13 @@ #define MP_BLOCKDEV_IOCTL_BLOCK_SIZE (5) #define MP_BLOCKDEV_IOCTL_BLOCK_ERASE (6) +// Constants for vfs.rom_ioctl() function. +#define MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS (1) // rom_ioctl(1) +#define MP_VFS_ROM_IOCTL_GET_SEGMENT (2) // rom_ioctl(2, ) +#define MP_VFS_ROM_IOCTL_WRITE_PREPARE (3) // rom_ioctl(3, , ) +#define MP_VFS_ROM_IOCTL_WRITE (4) // rom_ioctl(4, , , ) +#define MP_VFS_ROM_IOCTL_WRITE_COMPLETE (5) // rom_ioctl(5, ) + // At the moment the VFS protocol just has import_stat, but could be extended to other methods typedef struct _mp_vfs_proto_t { mp_import_stat_t (*import_stat)(void *self, const char *path); @@ -122,4 +129,12 @@ MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_rmdir_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_stat_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj); +#if MICROPY_VFS_ROM_IOCTL +// When MICROPY_VFS_ROM_IOCTL is enabled a port must define the following function. +// This is a generic interface to allow querying and modifying the user-accessible, +// read-only memory area of a device, if it is configured with such an area. +// Supported ioctl commands are given by MP_VFS_ROM_IOCTL_xxx. +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args); +#endif + #endif // MICROPY_INCLUDED_EXTMOD_VFS_H diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 0a25df4d0ac..b0250727732 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -64,6 +64,7 @@ #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS (1) #define MICROPY_VFS_ROM (1) +#define MICROPY_VFS_ROM_IOCTL (0) // type definitions for the specific machine diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 093477af0ad..9eeed879736 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -123,3 +123,4 @@ #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS_ROM (1) +#define MICROPY_VFS_ROM_IOCTL (0) diff --git a/py/mpconfig.h b/py/mpconfig.h index 66b3d125e78..05f39b3605b 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1006,6 +1006,11 @@ typedef double mp_float_t; #define MICROPY_VFS_WRITABLE (1) #endif +// Whether to enable the mp_vfs_rom_ioctl C function, and vfs.rom_ioctl Python function +#ifndef MICROPY_VFS_ROM_IOCTL +#define MICROPY_VFS_ROM_IOCTL (MICROPY_VFS_ROM) +#endif + // Support for VFS POSIX component, to mount a POSIX filesystem within VFS #ifndef MICROPY_VFS_POSIX #define MICROPY_VFS_POSIX (0) From d4b8ca2ffc3967007297d17e995d955ca39b1d6d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 21 Feb 2025 00:37:49 +1100 Subject: [PATCH 0356/2098] extmod/vfs: Add mp_vfs_mount_romfs_protected() helper. This function will attempt to create a `VfsRom` instance and mount it at location "/rom" in the filesystem. Signed-off-by: Damien George --- extmod/vfs.c | 30 ++++++++++++++++++++++++++++++ extmod/vfs.h | 3 +++ py/qstrdefs.h | 5 +++++ 3 files changed, 38 insertions(+) diff --git a/extmod/vfs.c b/extmod/vfs.c index b9c5ab0fc3a..c83b8ce7d56 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -46,6 +46,10 @@ #include "extmod/vfs_posix.h" #endif +#if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL +#include "extmod/vfs_rom.h" +#endif + // For mp_vfs_proxy_call, the maximum number of additional args that can be passed. // A fixed maximum size is used to avoid the need for a costly variable array. #define PROXY_MAX_ARGS (2) @@ -552,6 +556,32 @@ int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point) { return ret; } +#if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL + +int mp_vfs_mount_romfs_protected(void) { + int ret; + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(MP_VFS_ROM_IOCTL_GET_SEGMENT), MP_OBJ_NEW_SMALL_INT(0) }; + mp_obj_t rom = mp_vfs_rom_ioctl(2, args); + mp_obj_t romfs = mp_call_function_1(MP_OBJ_FROM_PTR(&mp_type_vfs_rom), rom); + mp_obj_t mount_point = MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom); + mp_call_function_2(MP_OBJ_FROM_PTR(&mp_vfs_mount_obj), romfs, mount_point); + #if MICROPY_PY_SYS_PATH_ARGV_DEFAULTS + // Add "/rom" and "/rom/lib" to `sys.path`. + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom)); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom_slash_lib)); + #endif + ret = 0; // success + nlr_pop(); + } else { + ret = -MP_EIO; + } + return ret; +} + +#endif + MP_REGISTER_ROOT_POINTER(struct _mp_vfs_mount_t *vfs_cur); MP_REGISTER_ROOT_POINTER(struct _mp_vfs_mount_t *vfs_mount_table); diff --git a/extmod/vfs.h b/extmod/vfs.h index cc9c50d2900..3acc09db122 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -112,6 +112,9 @@ mp_obj_t mp_vfs_stat(mp_obj_t path_in); mp_obj_t mp_vfs_statvfs(mp_obj_t path_in); int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point); +#if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL +int mp_vfs_mount_romfs_protected(void); +#endif MP_DECLARE_CONST_FUN_OBJ_KW(mp_vfs_mount_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_umount_obj); diff --git a/py/qstrdefs.h b/py/qstrdefs.h index 5003636df3d..0b50d279f9d 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -68,6 +68,11 @@ Q(utf-8) Q(.frozen) #endif +#if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL +Q(/rom) +Q(/rom/lib) +#endif + #if MICROPY_ENABLE_PYSTACK Q(pystack exhausted) #endif From 840b641024cffd0b4e3f941e53c699c699c21811 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 21 Feb 2025 11:34:09 +1100 Subject: [PATCH 0357/2098] py/runtime: Automatically mount ROMFS as part of mp_init. This is put in `mp_init()` to make it consistent across all ports. Signed-off-by: Damien George --- py/runtime.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/py/runtime.c b/py/runtime.c index bf26921f9ab..58819819ad0 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -46,6 +46,10 @@ #include "py/cstack.h" #include "py/gc.h" +#if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL +#include "extmod/vfs.h" +#endif + #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) #define DEBUG_printf DEBUG_printf @@ -185,6 +189,11 @@ void mp_init(void) { #endif MP_THREAD_GIL_ENTER(); + + #if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL + // Mount ROMFS if it exists. + mp_vfs_mount_romfs_protected(); + #endif } void mp_deinit(void) { From 0c98c60b68bf338c695d43e41b15def59949eb39 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:57:46 +1100 Subject: [PATCH 0358/2098] tools/mpremote: Add romfs query, build and deploy commands. These commands use the `vfs.rom_ioctl()` function to manage the ROM partitions on a device, and create and deploy ROMFS images. Signed-off-by: Damien George --- docs/reference/mpremote.rst | 24 ++++ tools/mpremote/mpremote/commands.py | 183 +++++++++++++++++++++++++++- tools/mpremote/mpremote/main.py | 31 +++++ tools/mpremote/mpremote/romfs.py | 148 ++++++++++++++++++++++ 4 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 tools/mpremote/mpremote/romfs.py diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index d4c6983595f..ef23cd85c21 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -78,6 +78,7 @@ The full list of supported commands are: - `mip ` - `mount ` - `unmount ` +- `romfs ` - `rtc ` - `sleep ` - `reset ` @@ -347,6 +348,29 @@ The full list of supported commands are: This happens automatically when ``mpremote`` terminates, but it can be used in a sequence to unmount an earlier mount before subsequent command are run. +.. _mpremote_command_romfs: + +- **romfs** -- manage ROMFS partitions on the device: + + .. code-block:: bash + + $ mpremote romfs + + ```` may be: + + - ``romfs query`` to list all the available ROMFS partitions and their size + - ``romfs [-o ] build `` to create a ROMFS image from the given + source directory; the default output file is the source appended by ``.romfs`` + - ``romfs [-p ] deploy `` to deploy a ROMFS image to the device; + will also create a temporary ROMFS image if the source is a directory + + The ``build`` and ``deploy`` sub-commands both support the ``-m``/``--mpy`` option + to automatically compile ``.py`` files to ``.mpy`` when creating the ROMFS image. + This option is enabled by default, but only works if the ``mpy_cross`` Python + package has been installed (eg via ``pip install mpy_cross``). If the package is + not installed then a warning is printed and ``.py`` files remain as is. Compiling + of ``.py`` files can be disabled with the ``--no-mpy`` option. + .. _mpremote_command_rtc: - **rtc** -- set/get the device clock (RTC): diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index f86befd0803..7dd448c8b77 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -1,12 +1,15 @@ +import binascii import hashlib import os import sys import tempfile +import zlib import serial.tools.list_ports -from .transport import TransportError, stdout_write_bytes +from .transport import TransportError, TransportExecError, stdout_write_bytes from .transport_serial import SerialTransport +from .romfs import make_romfs class CommandError(Exception): @@ -478,3 +481,181 @@ def do_rtc(state, args): state.transport.exec("machine.RTC().datetime({})".format(timetuple)) else: print(state.transport.eval("machine.RTC().datetime()")) + + +def _do_romfs_query(state, args): + state.ensure_raw_repl() + state.did_action() + + # Detect the romfs and get its associated device. + state.transport.exec("import vfs") + if not state.transport.eval("hasattr(vfs,'rom_ioctl')"): + print("ROMFS is not enabled on this device") + return + num_rom_partitions = state.transport.eval("vfs.rom_ioctl(1)") + if num_rom_partitions <= 0: + print("No ROMFS partitions available") + return + + for rom_id in range(num_rom_partitions): + state.transport.exec(f"dev=vfs.rom_ioctl(2,{rom_id})") + has_object = state.transport.eval("hasattr(dev,'ioctl')") + if has_object: + rom_block_count = state.transport.eval("dev.ioctl(4,0)") + rom_block_size = state.transport.eval("dev.ioctl(5,0)") + rom_size = rom_block_count * rom_block_size + print( + f"ROMFS{rom_id} partition has size {rom_size} bytes ({rom_block_count} blocks of {rom_block_size} bytes each)" + ) + else: + rom_size = state.transport.eval("len(dev)") + print(f"ROMFS{rom_id} partition has size {rom_size} bytes") + romfs = state.transport.eval("bytes(memoryview(dev)[:12])") + print(f" Raw contents: {romfs.hex(':')} ...") + if not romfs.startswith(b"\xd2\xcd\x31"): + print(" Not a valid ROMFS") + else: + size = 0 + for value in romfs[3:]: + size = (size << 7) | (value & 0x7F) + if not value & 0x80: + break + print(f" ROMFS image size: {size}") + + +def _do_romfs_build(state, args): + state.did_action() + + if args.path is None: + raise CommandError("romfs build: source path not given") + + input_directory = args.path + + if args.output is None: + output_file = input_directory + ".romfs" + else: + output_file = args.output + + romfs = make_romfs(input_directory, mpy_cross=args.mpy) + + print(f"Writing {len(romfs)} bytes to output file {output_file}") + with open(output_file, "wb") as f: + f.write(romfs) + + +def _do_romfs_deploy(state, args): + state.ensure_raw_repl() + state.did_action() + transport = state.transport + + if args.path is None: + raise CommandError("romfs deploy: source path not given") + + rom_id = args.partition + romfs_filename = args.path + + # Read in or create the ROMFS filesystem image. + if romfs_filename.endswith(".romfs"): + with open(romfs_filename, "rb") as f: + romfs = f.read() + else: + romfs = make_romfs(romfs_filename, mpy_cross=args.mpy) + print(f"Image size is {len(romfs)} bytes") + + # Detect the ROMFS partition and get its associated device. + state.transport.exec("import vfs") + if not state.transport.eval("hasattr(vfs,'rom_ioctl')"): + raise CommandError("ROMFS is not enabled on this device") + transport.exec(f"dev=vfs.rom_ioctl(2,{rom_id})") + if transport.eval("isinstance(dev,int) and dev<0"): + raise CommandError(f"ROMFS{rom_id} partition not found on device") + has_object = transport.eval("hasattr(dev,'ioctl')") + if has_object: + rom_block_count = transport.eval("dev.ioctl(4,0)") + rom_block_size = transport.eval("dev.ioctl(5,0)") + rom_size = rom_block_count * rom_block_size + print( + f"ROMFS{rom_id} partition has size {rom_size} bytes ({rom_block_count} blocks of {rom_block_size} bytes each)" + ) + else: + rom_size = transport.eval("len(dev)") + print(f"ROMFS{rom_id} partition has size {rom_size} bytes") + + # Check if ROMFS filesystem image will fit in the target partition. + if len(romfs) > rom_size: + print("ROMFS image is too big for the target partition") + sys.exit(1) + + # Prepare ROMFS partition for writing. + print(f"Preparing ROMFS{rom_id} partition for writing") + transport.exec("import vfs\ntry:\n vfs.umount('/rom')\nexcept:\n pass") + chunk_size = 4096 + if has_object: + for offset in range(0, len(romfs), rom_block_size): + transport.exec(f"dev.ioctl(6,{offset // rom_block_size})") + chunk_size = min(chunk_size, rom_block_size) + else: + rom_min_write = transport.eval(f"vfs.rom_ioctl(3,{rom_id},{len(romfs)})") + chunk_size = max(chunk_size, rom_min_write) + + # Detect capabilities of the device to use the fastest method of transfer. + has_bytes_fromhex = transport.eval("hasattr(bytes,'fromhex')") + try: + transport.exec("from binascii import a2b_base64") + has_a2b_base64 = True + except TransportExecError: + has_a2b_base64 = False + try: + transport.exec("from io import BytesIO") + transport.exec("from deflate import DeflateIO,RAW") + has_deflate_io = True + except TransportExecError: + has_deflate_io = False + + # Deploy the ROMFS filesystem image to the device. + for offset in range(0, len(romfs), chunk_size): + romfs_chunk = romfs[offset : offset + chunk_size] + romfs_chunk += bytes(chunk_size - len(romfs_chunk)) + if has_deflate_io: + # Needs: binascii.a2b_base64, io.BytesIO, deflate.DeflateIO. + romfs_chunk_compressed = zlib.compress(romfs_chunk, wbits=-9) + buf = binascii.b2a_base64(romfs_chunk_compressed).strip() + transport.exec(f"buf=DeflateIO(BytesIO(a2b_base64({buf})),RAW,9).read()") + elif has_a2b_base64: + # Needs: binascii.a2b_base64. + buf = binascii.b2a_base64(romfs_chunk) + transport.exec(f"buf=a2b_base64({buf})") + elif has_bytes_fromhex: + # Needs: bytes.fromhex. + buf = romfs_chunk.hex() + transport.exec(f"buf=bytes.fromhex('{buf}')") + else: + # Needs nothing special. + transport.exec("buf=" + repr(romfs_chunk)) + print(f"\rWriting at offset {offset}", end="") + if has_object: + transport.exec( + f"dev.writeblocks({offset // rom_block_size},buf,{offset % rom_block_size})" + ) + else: + transport.exec(f"vfs.rom_ioctl(4,{rom_id},{offset},buf)") + + # Complete writing. + if not has_object: + transport.eval(f"vfs.rom_ioctl(5,{rom_id})") + + print() + print("ROMFS image deployed") + + +def do_romfs(state, args): + if args.command[0] == "query": + _do_romfs_query(state, args) + elif args.command[0] == "build": + _do_romfs_build(state, args) + elif args.command[0] == "deploy": + _do_romfs_deploy(state, args) + else: + raise CommandError( + f"romfs: '{args.command[0]}' is not a command; pass romfs --help for a list" + ) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index e6e397020fe..bdae8f16365 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -36,6 +36,7 @@ do_resume, do_rtc, do_soft_reset, + do_romfs, ) from .mip import do_mip from .repl import do_repl @@ -228,6 +229,32 @@ def argparse_mip(): return cmd_parser +def argparse_romfs(): + cmd_parser = argparse.ArgumentParser(description="manage ROM partitions") + _bool_flag( + cmd_parser, + "mpy", + "m", + True, + "automatically compile .py files to .mpy when building the ROMFS image (default)", + ) + cmd_parser.add_argument( + "--partition", + "-p", + type=int, + default=0, + help="ROMFS partition to use", + ) + cmd_parser.add_argument( + "--output", + "-o", + help="output file", + ) + cmd_parser.add_argument("command", nargs=1, help="romfs command, one of: query, build, deploy") + cmd_parser.add_argument("path", nargs="?", help="path to directory to deploy") + return cmd_parser + + def argparse_none(description): return lambda: argparse.ArgumentParser(description=description) @@ -302,6 +329,10 @@ def argparse_none(description): do_version, argparse_none("print version and exit"), ), + "romfs": ( + do_romfs, + argparse_romfs, + ), } # Additional commands aliases. diff --git a/tools/mpremote/mpremote/romfs.py b/tools/mpremote/mpremote/romfs.py new file mode 100644 index 00000000000..ae781a36dfe --- /dev/null +++ b/tools/mpremote/mpremote/romfs.py @@ -0,0 +1,148 @@ +# MIT license; Copyright (c) 2022 Damien P. George + +import struct, sys, os + +try: + from mpy_cross import run as mpy_cross_run +except ImportError: + mpy_cross_run = None + + +class VfsRomWriter: + ROMFS_HEADER = b"\xd2\xcd\x31" + + ROMFS_RECORD_KIND_UNUSED = 0 + ROMFS_RECORD_KIND_PADDING = 1 + ROMFS_RECORD_KIND_DATA_VERBATIM = 2 + ROMFS_RECORD_KIND_DATA_POINTER = 3 + ROMFS_RECORD_KIND_DIRECTORY = 4 + ROMFS_RECORD_KIND_FILE = 5 + + def __init__(self): + self._dir_stack = [(None, bytearray())] + + def _encode_uint(self, value): + encoded = [value & 0x7F] + value >>= 7 + while value != 0: + encoded.insert(0, 0x80 | (value & 0x7F)) + value >>= 7 + return bytes(encoded) + + def _pack(self, kind, payload): + return self._encode_uint(kind) + self._encode_uint(len(payload)) + payload + + def _extend(self, data): + buf = self._dir_stack[-1][1] + buf.extend(data) + return len(buf) + + def finalise(self): + _, data = self._dir_stack.pop() + encoded_kind = VfsRomWriter.ROMFS_HEADER + encoded_len = self._encode_uint(len(data)) + if (len(encoded_kind) + len(encoded_len) + len(data)) % 2 == 1: + encoded_len = b"\x80" + encoded_len + data = encoded_kind + encoded_len + data + return data + + def opendir(self, dirname): + self._dir_stack.append((dirname, bytearray())) + + def closedir(self): + dirname, dirdata = self._dir_stack.pop() + dirdata = self._encode_uint(len(dirname)) + bytes(dirname, "ascii") + dirdata + self._extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DIRECTORY, dirdata)) + + def mkdata(self, data): + assert len(self._dir_stack) == 1 + return self._extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_VERBATIM, data)) - len( + data + ) + + def mkfile(self, filename, filedata): + filename = bytes(filename, "ascii") + payload = self._encode_uint(len(filename)) + payload += filename + if isinstance(filedata, tuple): + sub_payload = self._encode_uint(filedata[0]) + sub_payload += self._encode_uint(filedata[1]) + payload += self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_POINTER, sub_payload) + else: + payload += self._pack(VfsRomWriter.ROMFS_RECORD_KIND_DATA_VERBATIM, filedata) + self._dir_stack[-1][1].extend(self._pack(VfsRomWriter.ROMFS_RECORD_KIND_FILE, payload)) + + +def copy_recursively(vfs, src_dir, print_prefix, mpy_cross): + assert src_dir.endswith("/") + DIR = 1 << 14 + mpy_cross_missed = 0 + dir_contents = sorted(os.listdir(src_dir)) + for name in dir_contents: + src_name = src_dir + name + st = os.stat(src_name) + + if name == dir_contents[-1]: + # Last entry in the directory listing. + print_entry = "\\--" + print_recurse = " " + else: + # Not the last entry in the directory listing. + print_entry = "|--" + print_recurse = "| " + + if st[0] & DIR: + # A directory, enter it and copy its contents recursively. + print(print_prefix + print_entry, name + "/") + vfs.opendir(name) + mpy_cross_missed += copy_recursively( + vfs, src_name + "/", print_prefix + print_recurse, mpy_cross + ) + vfs.closedir() + else: + # A file. + did_mpy = False + name_extra = "" + if mpy_cross and name.endswith(".py"): + name_mpy = name[:-3] + ".mpy" + src_name_mpy = src_dir + name_mpy + if not os.path.isfile(src_name_mpy): + if mpy_cross_run is not None: + did_mpy = True + proc = mpy_cross_run(src_name) + proc.wait() + else: + mpy_cross_missed += 1 + if did_mpy: + name_extra = " -> .mpy" + print(print_prefix + print_entry, name + name_extra) + if did_mpy: + name = name_mpy + src_name = src_name_mpy + with open(src_name, "rb") as src: + vfs.mkfile(name, src.read()) + if did_mpy: + os.remove(src_name_mpy) + return mpy_cross_missed + + +def make_romfs(src_dir, *, mpy_cross): + if not src_dir.endswith("/"): + src_dir += "/" + + vfs = VfsRomWriter() + + # Build the filesystem recursively. + print("Building romfs filesystem, source directory: {}".format(src_dir)) + print("/") + try: + mpy_cross_missed = copy_recursively(vfs, src_dir, "", mpy_cross) + except OSError as er: + print("Error: OSError {}".format(er), file=sys.stderr) + sys.exit(1) + + if mpy_cross_missed: + print("Warning: `mpy_cross` module not found, .py files were not precompiled") + mpy_cross = False + + return vfs.finalise() From bea7645b2e55881c4f42e6cfbe2a6433c5986794 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:56:50 +1100 Subject: [PATCH 0359/2098] stm32: Implement vfs.rom_ioctl with support for internal/external flash. This commit implements `vfs.rom_ioctl()` to query, erase and write both internal and external flash, depending on how the board configures its flash memory. A board can configure ROM as follows. To use internal flash memory: #define MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH (1) To use external flash memory (QSPI memory mapped): #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) #define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_obj) Then the partition must be defined as symbols in the linker script: _micropy_hw_romfs_part1_start _micropy_hw_romfs_part1_size And finally the partition needs to be enabled: #define MICROPY_HW_ROMFS_ENABLE_PART1 (1) There's support for a second, optional partition via: _micropy_hw_romfs_part2_start _micropy_hw_romfs_part2_size #define MICROPY_HW_ROMFS_ENABLE_PART1 (1) Signed-off-by: Damien George --- ports/stm32/Makefile | 1 + ports/stm32/mpconfigboard_common.h | 20 ++++ ports/stm32/mpconfigport.h | 3 + ports/stm32/qspi.c | 2 - ports/stm32/qspi.h | 7 ++ ports/stm32/vfs_rom_ioctl.c | 161 +++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 ports/stm32/vfs_rom_ioctl.c diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index a9612f1a579..8ac9a8af03d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -291,6 +291,7 @@ SRC_C += \ storage.c \ sdcard.c \ sdram.c \ + vfs_rom_ioctl.c \ fatfs_port.c \ lcd.c \ accel.c \ diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 4ce0a75b87e..2a650077f3a 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -67,6 +67,26 @@ #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (1) #endif +// Whether to enable ROMFS on the internal flash. +#ifndef MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH +#define MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH (0) +#endif + +// Whether to enable ROMFS on external QSPI flash. +#ifndef MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (0) +#endif + +// Whether to enable ROMFS partition 1. +#ifndef MICROPY_HW_ROMFS_ENABLE_PART1 +#define MICROPY_HW_ROMFS_ENABLE_PART1 (0) +#endif + +// Whether to enable ROMFS partition 2. +#ifndef MICROPY_HW_ROMFS_ENABLE_PART2 +#define MICROPY_HW_ROMFS_ENABLE_PART2 (0) +#endif + // Whether to enable storage on the internal flash of the MCU #ifndef MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 2b57446ee82..da86fa64162 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -79,6 +79,9 @@ #define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_VFS (1) +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH || MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI) +#endif // control over Python builtins #ifndef MICROPY_PY_BUILTINS_HELP_TEXT diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 98364db41d4..34359a1ccf8 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -34,8 +34,6 @@ #if defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) -#define QSPI_MAP_ADDR (0x90000000) - #ifndef MICROPY_HW_QSPI_PRESCALER #define MICROPY_HW_QSPI_PRESCALER 3 // F_CLK = F_AHB/3 (72MHz when CPU is 216MHz) #endif diff --git a/ports/stm32/qspi.h b/ports/stm32/qspi.h index c774b12582b..6fe91168cfd 100644 --- a/ports/stm32/qspi.h +++ b/ports/stm32/qspi.h @@ -28,9 +28,16 @@ #include "drivers/bus/qspi.h" +#define QSPI_MAP_ADDR (0x90000000) +#define QSPI_MAP_ADDR_MAX (0xa0000000) + extern const mp_qspi_proto_t qspi_proto; void qspi_init(void); void qspi_memory_map(void); +static inline bool qspi_is_valid_addr(uint32_t addr) { + return QSPI_MAP_ADDR <= addr && addr < QSPI_MAP_ADDR_MAX; +} + #endif // MICROPY_INCLUDED_STM32_QSPI_H diff --git a/ports/stm32/vfs_rom_ioctl.c b/ports/stm32/vfs_rom_ioctl.c new file mode 100644 index 00000000000..5024d44e5ad --- /dev/null +++ b/ports/stm32/vfs_rom_ioctl.c @@ -0,0 +1,161 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/obj.h" +#include "py/objarray.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "drivers/memory/spiflash.h" + +#include "flash.h" +#include "qspi.h" +#include "storage.h" + +#if MICROPY_VFS_ROM_IOCTL + +#if MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) +#define MICROPY_HW_ROMFS_PART1_START (uintptr_t)(&_micropy_hw_romfs_part1_start) +#define MICROPY_HW_ROMFS_PART1_SIZE (uintptr_t)(&_micropy_hw_romfs_part1_size) +extern uint8_t _micropy_hw_romfs_part1_start; +extern uint8_t _micropy_hw_romfs_part1_size; +#endif + +#if MICROPY_HW_ROMFS_ENABLE_PART2 && !defined(MICROPY_HW_ROMFS_PART2_START) +#define MICROPY_HW_ROMFS_PART2_START (uintptr_t)(&_micropy_hw_romfs_part2_start) +#define MICROPY_HW_ROMFS_PART2_SIZE (uintptr_t)(&_micropy_hw_romfs_part2_size) +extern uint8_t _micropy_hw_romfs_part2_start; +extern uint8_t _micropy_hw_romfs_part2_size; +#endif + +#define ROMFS_MEMORYVIEW(base, size) {{&mp_type_memoryview}, 'B', 0, (size), (void *)(base)} + +static const mp_obj_array_t romfs_obj_table[] = { + #if MICROPY_HW_ROMFS_ENABLE_PART1 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART1_START, MICROPY_HW_ROMFS_PART1_SIZE), + #endif + #if MICROPY_HW_ROMFS_ENABLE_PART2 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART2_START, MICROPY_HW_ROMFS_PART2_SIZE), + #endif +}; + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + mp_int_t cmd = mp_obj_get_int(args[0]); + if (cmd == MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS) { + return MP_OBJ_NEW_SMALL_INT(MP_ARRAY_SIZE(romfs_obj_table)); + } + + if (n_args < 2) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + mp_int_t romfs_id = mp_obj_get_int(args[1]); + if (!(0 <= romfs_id && romfs_id < MP_ARRAY_SIZE(romfs_obj_table))) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + const mp_obj_array_t *romfs_obj = &romfs_obj_table[romfs_id]; + uintptr_t romfs_base = (uintptr_t)romfs_obj->items; + uintptr_t romfs_len = romfs_obj->len; + + if (cmd == MP_VFS_ROM_IOCTL_GET_SEGMENT) { + // Return the ROMFS memoryview object. + return MP_OBJ_FROM_PTR(romfs_obj); + } + + if (cmd == MP_VFS_ROM_IOCTL_WRITE_PREPARE) { + // Erase sectors in given range. + if (n_args < 3) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = romfs_base; + uint32_t dest_max = dest + mp_obj_get_int(args[2]); + if (dest_max > romfs_base + romfs_len) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + #if MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH + if (flash_is_valid_addr(dest)) { + while (dest < dest_max) { + int ret = flash_erase(dest); + if (ret < 0) { + return MP_OBJ_NEW_SMALL_INT(ret); + } + uint32_t sector_size = 0; + flash_get_sector_info(dest, NULL, §or_size); + dest += sector_size; + } + return MP_OBJ_NEW_SMALL_INT(16); + } + #endif + + #if MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI + if (qspi_is_valid_addr(dest)) { + dest -= QSPI_MAP_ADDR; + dest_max -= QSPI_MAP_ADDR; + while (dest < dest_max) { + int ret = mp_spiflash_erase_block(MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ, dest); + if (ret < 0) { + return MP_OBJ_NEW_SMALL_INT(ret); + } + dest += MP_SPIFLASH_ERASE_BLOCK_SIZE; + } + return MP_OBJ_NEW_SMALL_INT(4); + } + #endif + } + + if (cmd == MP_VFS_ROM_IOCTL_WRITE) { + // Write data to flash. + if (n_args < 4) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = romfs_base + mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + if (dest + bufinfo.len > romfs_base + romfs_len) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + #if MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH + if (flash_is_valid_addr(dest)) { + int ret = flash_write(dest, bufinfo.buf, bufinfo.len / 4); + return MP_OBJ_NEW_SMALL_INT(ret); + } + #endif + + #if MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI + if (qspi_is_valid_addr(dest)) { + dest -= QSPI_MAP_ADDR; + int ret = mp_spiflash_write(MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ, dest, bufinfo.len, bufinfo.buf); + return MP_OBJ_NEW_SMALL_INT(ret); + } + #endif + } + + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); +} + +#endif // MICROPY_VFS_ROM_IOCTL From 45c36f87ea025386c7d2f9b3c5d8916e54b70318 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 21 Feb 2025 00:45:13 +1100 Subject: [PATCH 0360/2098] stm32/boards: Enable ROMFS partitions on PYBD_SFx boards. Using unused and previously inaccessible external QSPI flash. Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 7 ++++++- ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 5 +++++ ports/stm32/boards/PYBD_SF6/f767.ld | 6 +++++- ports/stm32/boards/PYBD_SF6/mpconfigboard.h | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index 70d7f9cc4b8..05126233ec7 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -20,7 +20,8 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 480K /* sectors 2-7 */ - FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 2048K /* external QSPI */ + FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 1024K /* external QSPI */ + FLASH_ROMFS (rx): ORIGIN = 0x90100000, LENGTH = 1024K /* external QSPI */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K /* DTCM+SRAM1+SRAM2 */ } @@ -39,6 +40,10 @@ _ram_end = ORIGIN(RAM) + LENGTH(RAM); _heap_start = _ebss; /* heap starts just after statically allocated memory */ _heap_end = _sstack; +/* ROMFS location */ +_micropy_hw_romfs_part1_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part1_size = LENGTH(FLASH_ROMFS); + /* Define output sections */ SECTIONS { diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index f0ef67e77e6..e8923bf9b66 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -64,6 +64,11 @@ void board_sleep(int value); #define MICROPY_HW_RTC_USE_US (1) #define MICROPY_HW_RTC_USE_CALOUT (1) +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev2.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART1 (1) + // SPI flash #1, for R/W storage #define MICROPY_HW_SOFTQSPI_SCK_LOW(self) (GPIOE->BSRR = (0x10000 << 11)) #define MICROPY_HW_SOFTQSPI_SCK_HIGH(self) (GPIOE->BSRR = (1 << 11)) diff --git a/ports/stm32/boards/PYBD_SF6/f767.ld b/ports/stm32/boards/PYBD_SF6/f767.ld index 4262a48a996..68da1f19d40 100644 --- a/ports/stm32/boards/PYBD_SF6/f767.ld +++ b/ports/stm32/boards/PYBD_SF6/f767.ld @@ -18,7 +18,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 2016K /* sectors 1-11 3x32K 1*128K 7*256K */ - FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 2048K /* external QSPI */ + FLASH_ROMFS (rx): ORIGIN = 0x90000000, LENGTH = 2048K /* external QSPI */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512K /* DTCM=128k, SRAM1=368K, SRAM2=16K */ } @@ -37,4 +37,8 @@ _ram_end = ORIGIN(RAM) + LENGTH(RAM); _heap_start = _ebss; /* heap starts just after statically allocated memory */ _heap_end = _sstack; +/* ROMFS location */ +_micropy_hw_romfs_part1_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part1_size = LENGTH(FLASH_ROMFS); + INCLUDE common_bl.ld diff --git a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h index 8c11d7b2eec..526a1111779 100644 --- a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h @@ -45,6 +45,11 @@ #define MICROPY_HW_CLK_PLLQ (6) #define MICROPY_HW_FLASH_LATENCY (FLASH_LATENCY_4) +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev2.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART1 (1) + // Extra UART config #define MICROPY_HW_UART7_TX (pyb_pin_W16) #define MICROPY_HW_UART7_RX (pyb_pin_W22B) From 50a7362b3eff211a5051eeaecc88bdde045c90d1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Mar 2022 10:57:38 +1100 Subject: [PATCH 0361/2098] rp2: Implement vfs.rom_ioctl with support for external flash. Not enabled by default on any board. A board can enable a ROMFS partition by defining `MICROPY_HW_ROMFS_BYTES` in its `mpconfigboard.h` file. For example: #define MICROPY_HW_ROMFS_BYTES (128 * 1024) The ROMFS partition is placed at the end of the flash allocated for the firmware, giving less space for the firmware. It then lives between the firmware and the read/write filesystem. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 11 ++++++++++ ports/rp2/rp2_flash.c | 45 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index fe8287ba1d6..38dcad1dae6 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -67,6 +67,16 @@ #endif #endif +// Number of bytes of flash to allocate to the ROMFS partition. +#ifndef MICROPY_HW_ROMFS_BYTES +#define MICROPY_HW_ROMFS_BYTES (0) +#endif + +// Number of bytes of flash to allocate to read/write filesystem storage. +#ifndef MICROPY_HW_FLASH_STORAGE_BYTES +#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) +#endif + #ifndef MICROPY_CONFIG_ROM_LEVEL #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) #endif @@ -170,6 +180,7 @@ #define MICROPY_VFS (1) #define MICROPY_VFS_LFS2 (1) #define MICROPY_VFS_FAT (1) +#define MICROPY_VFS_ROM (MICROPY_HW_ROMFS_BYTES > 0) #define MICROPY_SSL_MBEDTLS (1) #define MICROPY_PY_LWIP_PPP (MICROPY_PY_NETWORK_PPP_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index a487fb1633b..f3f154a1402 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -28,6 +28,7 @@ #include "py/mphal.h" #include "py/runtime.h" +#include "py/mperrno.h" #include "extmod/vfs.h" #include "modrp2.h" #include "hardware/flash.h" @@ -35,15 +36,16 @@ #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) -#ifndef MICROPY_HW_FLASH_STORAGE_BYTES -#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) -#endif +static_assert(MICROPY_HW_ROMFS_BYTES % 4096 == 0, "ROMFS size must be a multiple of 4K"); static_assert(MICROPY_HW_FLASH_STORAGE_BYTES % 4096 == 0, "Flash storage size must be a multiple of 4K"); #ifndef MICROPY_HW_FLASH_STORAGE_BASE #define MICROPY_HW_FLASH_STORAGE_BASE (PICO_FLASH_SIZE_BYTES - MICROPY_HW_FLASH_STORAGE_BYTES) #endif +// Put ROMFS at the upper end of the code space. +#define MICROPY_HW_ROMFS_BASE (MICROPY_HW_FLASH_STORAGE_BASE - MICROPY_HW_ROMFS_BYTES) + static_assert(MICROPY_HW_FLASH_STORAGE_BYTES <= PICO_FLASH_SIZE_BYTES, "MICROPY_HW_FLASH_STORAGE_BYTES too big"); static_assert(MICROPY_HW_FLASH_STORAGE_BASE + MICROPY_HW_FLASH_STORAGE_BYTES <= PICO_FLASH_SIZE_BYTES, "MICROPY_HW_FLASH_STORAGE_BYTES too big"); @@ -53,6 +55,14 @@ typedef struct _rp2_flash_obj_t { uint32_t flash_size; } rp2_flash_obj_t; +#if MICROPY_HW_ROMFS_BYTES > 0 +static rp2_flash_obj_t rp2_flash_romfs_obj = { + .base = { &rp2_flash_type }, + .flash_base = MICROPY_HW_ROMFS_BASE, + .flash_size = MICROPY_HW_ROMFS_BYTES, +}; +#endif + static rp2_flash_obj_t rp2_flash_obj = { .base = { &rp2_flash_type }, .flash_base = MICROPY_HW_FLASH_STORAGE_BASE, @@ -138,6 +148,19 @@ static mp_obj_t rp2_flash_make_new(const mp_obj_type_t *type, size_t n_args, siz return MP_OBJ_FROM_PTR(self); } +static mp_int_t rp2_flash_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + rp2_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)(XIP_BASE + self->flash_base); + bufinfo->len = self->flash_size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Write unsupported. + return 1; + } +} + static mp_obj_t rp2_flash_readblocks(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; @@ -218,5 +241,21 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_QSTR_Flash, MP_TYPE_FLAG_NONE, make_new, rp2_flash_make_new, + buffer, rp2_flash_get_buffer, locals_dict, &rp2_flash_locals_dict ); + +#if MICROPY_VFS_ROM_IOCTL +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + switch (mp_obj_get_int(args[0])) { + #if MICROPY_HW_ROMFS_BYTES > 0 + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&rp2_flash_romfs_obj); + #endif + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} +#endif From 0255cb77ccbdf09894e59523aecff24f2caa6352 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Mar 2022 18:05:06 +1100 Subject: [PATCH 0362/2098] esp32: Implement vfs.rom_ioctl with support for external flash. Not enabled by default on any board. For a board to enable ROMFS it must: - Add `#define MICROPY_VFS_ROM (1)` to its `mpconfigboard.h` file. - Use `partitions-4MiB-romfs.csv` as its partitions file (or a similar partitions definition that has an entry labelled "romfs"). Signed-off-by: Damien George --- ports/esp32/esp32_partition.c | 76 +++++++++++++++++++++++++++ ports/esp32/partitions-4MiB-romfs.csv | 8 +++ 2 files changed, 84 insertions(+) create mode 100644 ports/esp32/partitions-4MiB-romfs.csv diff --git a/ports/esp32/esp32_partition.c b/ports/esp32/esp32_partition.c index 55830a285b0..f33e9da671b 100644 --- a/ports/esp32/esp32_partition.c +++ b/ports/esp32/esp32_partition.c @@ -53,6 +53,20 @@ typedef struct _esp32_partition_obj_t { uint16_t block_size; } esp32_partition_obj_t; +#if MICROPY_VFS_ROM_IOCTL + +static esp32_partition_obj_t esp32_partition_romfs_obj = { + .base = { .type = NULL }, + .part = NULL, + .cache = NULL, + .block_size = NATIVE_BLOCK_SIZE_BYTES, +}; + +static const void *esp32_partition_romfs_ptr = NULL; +static esp_partition_mmap_handle_t esp32_partition_romfs_handle; + +#endif + static esp32_partition_obj_t *esp32_partition_new(const esp_partition_t *part, uint16_t block_size) { if (part == NULL) { mp_raise_OSError(MP_ENOENT); @@ -114,6 +128,24 @@ static mp_obj_t esp32_partition_make_new(const mp_obj_type_t *type, size_t n_arg return MP_OBJ_FROM_PTR(esp32_partition_new(part, block_size)); } +#if MICROPY_VFS_ROM_IOCTL +static mp_int_t esp32_partition_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self == &esp32_partition_romfs_obj && flags == MP_BUFFER_READ) { + if (esp32_partition_romfs_ptr == NULL) { + check_esp_err(esp_partition_mmap(self->part, 0, self->part->size, ESP_PARTITION_MMAP_DATA, &esp32_partition_romfs_ptr, &esp32_partition_romfs_handle)); + } + bufinfo->buf = (void *)esp32_partition_romfs_ptr; + bufinfo->len = self->part->size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Unsupported. + return 1; + } +} +#endif + static mp_obj_t esp32_partition_find(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { // Parse args enum { ARG_type, ARG_subtype, ARG_label, ARG_block_size }; @@ -284,11 +316,55 @@ static const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(esp32_partition_locals_dict, esp32_partition_locals_dict_table); +#if MICROPY_VFS_ROM_IOCTL +#define ESP32_PARTITION_GET_BUFFER buffer, esp32_partition_get_buffer, +#else +#define ESP32_PARTITION_GET_BUFFER +#endif + MP_DEFINE_CONST_OBJ_TYPE( esp32_partition_type, MP_QSTR_Partition, MP_TYPE_FLAG_NONE, make_new, esp32_partition_make_new, print, esp32_partition_print, + ESP32_PARTITION_GET_BUFFER locals_dict, &esp32_partition_locals_dict ); + +/******************************************************************************/ +// romfs partition + +#if MICROPY_VFS_ROM_IOCTL + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + if (esp32_partition_romfs_obj.base.type == NULL) { + esp32_partition_romfs_obj.base.type = &esp32_partition_type; + // Get the romfs partition. + // TODO: number of segments ioctl can be used if there is more than one romfs. + esp_partition_iterator_t iter = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "romfs"); + if (iter != NULL) { + esp32_partition_romfs_obj.part = esp_partition_get(iter); + } + esp_partition_iterator_release(iter); + } + + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + if (esp32_partition_romfs_obj.part == NULL) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + return MP_OBJ_NEW_SMALL_INT(1); + } + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + if (esp32_partition_romfs_obj.part == NULL) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } else { + return MP_OBJ_FROM_PTR(&esp32_partition_romfs_obj); + } + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} + +#endif // MICROPY_VFS_ROM_IOCTL diff --git a/ports/esp32/partitions-4MiB-romfs.csv b/ports/esp32/partitions-4MiB-romfs.csv new file mode 100644 index 00000000000..dd02506e546 --- /dev/null +++ b/ports/esp32/partitions-4MiB-romfs.csv @@ -0,0 +1,8 @@ +# Notes: the offset of the partition table itself is set in +# $IDF_PATH/components/partition_table/Kconfig.projbuild. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x1D0000, +romfs, data, 0x8f, 0x1E0000, 0x20000, +vfs, data, fat, 0x200000, 0x200000, From 75ff8e5465c766bad0c79d1a5d98170f27f7070b Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Nov 2024 11:18:09 +1100 Subject: [PATCH 0363/2098] esp8266: Implement vfs.rom_ioctl with support for external flash. Not enabled by default on any board. For a board to enable ROMFS it must: - Add `#define MICROPY_VFS_ROM (1)` to its `mpconfigboard.h` file. - Add a FLASH_ROMFS partition to the linker script and expose the partition with: _micropy_hw_romfs_start = ORIGIN(FLASH_ROMFS); _micropy_hw_romfs_size = LENGTH(FLASH_ROMFS); Signed-off-by: Damien George --- ports/esp8266/Makefile | 1 + ports/esp8266/boards/esp8266_common.ld | 1 + ports/esp8266/modesp.c | 9 ++- ports/esp8266/vfs_rom_ioctl.c | 89 ++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 ports/esp8266/vfs_rom_ioctl.c diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 1721075eae1..1c9de01503a 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -134,6 +134,7 @@ SRC_C = \ fatfs_port.c \ posix_helpers.c \ hspi.c \ + vfs_rom_ioctl.c \ $(wildcard $(BOARD_DIR)/*.c) \ ifeq ($(MICROPY_PY_ESPNOW),1) diff --git a/ports/esp8266/boards/esp8266_common.ld b/ports/esp8266/boards/esp8266_common.ld index cf4883acc9d..206c3f14b43 100644 --- a/ports/esp8266/boards/esp8266_common.ld +++ b/ports/esp8266/boards/esp8266_common.ld @@ -170,6 +170,7 @@ SECTIONS *modonewire.o(.literal* .text*) *network_wlan.o(.literal* .text*) *esp_mphal.o(.literal* .text*) + *vfs_rom_ioctl.o(.literal* .text*) /* we put as much rodata as possible in this section */ /* note that only rodata accessed as a machine word is allowed here */ diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c index f303da1a332..c88022945cf 100644 --- a/ports/esp8266/modesp.c +++ b/ports/esp8266/modesp.c @@ -164,9 +164,16 @@ static MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); #define IS_OTA_FIRMWARE() ((*(uint32_t *)0x40200000 & 0xff00) == 0x100) extern byte _firmware_size[]; +#if MICROPY_VFS_ROM_IOCTL +extern uint8_t _micropy_hw_romfs_size; +#endif static mp_obj_t esp_flash_user_start(void) { - return MP_OBJ_NEW_SMALL_INT((uint32_t)_firmware_size); + uint32_t flash_user_start = (uint32_t)_firmware_size; + #if MICROPY_VFS_ROM_IOCTL + flash_user_start += (uint32_t)&_micropy_hw_romfs_size; + #endif + return MP_OBJ_NEW_SMALL_INT(flash_user_start); } static MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_user_start_obj, esp_flash_user_start); diff --git a/ports/esp8266/vfs_rom_ioctl.c b/ports/esp8266/vfs_rom_ioctl.c new file mode 100644 index 00000000000..c7528375bec --- /dev/null +++ b/ports/esp8266/vfs_rom_ioctl.c @@ -0,0 +1,89 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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/mperrno.h" +#include "py/objarray.h" +#include "extmod/vfs.h" +#include "ets_alt_task.h" +#include "user_interface.h" + +#if MICROPY_VFS_ROM_IOCTL + +#define FLASH_MEM_BASE (0x40200000) +#define FLASH_PAGE_SIZE (4096) + +#define MICROPY_HW_ROMFS_BASE (uintptr_t)(&_micropy_hw_romfs_start) +#define MICROPY_HW_ROMFS_BYTES (uintptr_t)(&_micropy_hw_romfs_size) + +#define ROMFS_SPI_FLASH_OFFSET (MICROPY_HW_ROMFS_BASE - FLASH_MEM_BASE) + +extern uint8_t _micropy_hw_romfs_start; +extern uint8_t _micropy_hw_romfs_size; + +static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, MICROPY_HW_ROMFS_BYTES, (void *)MICROPY_HW_ROMFS_BASE); + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&romfs_obj); + + case MP_VFS_ROM_IOCTL_WRITE_PREPARE: { + uint32_t dest = ROMFS_SPI_FLASH_OFFSET; + uint32_t dest_max = dest + mp_obj_get_int(args[2]); + while (dest < dest_max) { + ets_loop_iter(); // flash access takes time so run any pending tasks + SpiFlashOpResult res = spi_flash_erase_sector(dest / FLASH_PAGE_SIZE); + ets_loop_iter(); + if (res != SPI_FLASH_RESULT_OK) { + return MP_OBJ_NEW_SMALL_INT(res == SPI_FLASH_RESULT_TIMEOUT ? -MP_ETIMEDOUT : -MP_EIO); + } + dest += FLASH_PAGE_SIZE; + } + return MP_OBJ_NEW_SMALL_INT(4); // minimum write size + } + + case MP_VFS_ROM_IOCTL_WRITE: { + mp_int_t offset = ROMFS_SPI_FLASH_OFFSET + mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + ets_loop_iter(); // flash access takes time so run any pending tasks + SpiFlashOpResult res = spi_flash_write(offset, bufinfo.buf, (bufinfo.len + 3) & ~3); + ets_loop_iter(); + if (res == SPI_FLASH_RESULT_OK) { + return MP_OBJ_NEW_SMALL_INT(0); + } + return MP_OBJ_NEW_SMALL_INT(res == SPI_FLASH_RESULT_TIMEOUT ? -MP_ETIMEDOUT : -MP_EIO); + } + + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} + +#endif // MICROPY_VFS_ROM_IOCTL From 6bec36a4eec999eccc6aadf0de782ebf69454ced Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 1 Mar 2025 17:22:03 +1100 Subject: [PATCH 0364/2098] esp8266/boards: Add FLASH_2M_ROMFS variant with 320k ROM partition. The same as the 2M flash variant but with a 320KiB ROM partition. Signed-off-by: Damien George --- .../esp8266/boards/ESP8266_GENERIC/board.json | 3 ++- .../mpconfigvariant_FLASH_2M_ROMFS.mk | 12 ++++++++++ ports/esp8266/boards/esp8266_2MiB_ROMFS.ld | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_FLASH_2M_ROMFS.mk create mode 100644 ports/esp8266/boards/esp8266_2MiB_ROMFS.ld diff --git a/ports/esp8266/boards/ESP8266_GENERIC/board.json b/ports/esp8266/boards/ESP8266_GENERIC/board.json index 2b3a6b5507d..8f2aa1240f2 100644 --- a/ports/esp8266/boards/ESP8266_GENERIC/board.json +++ b/ports/esp8266/boards/ESP8266_GENERIC/board.json @@ -17,7 +17,8 @@ "variants": { "OTA": "OTA compatible", "FLASH_1M": "1MiB flash", - "FLASH_512K": "512kiB flash" + "FLASH_512K": "512kiB flash", + "FLASH_2M_ROMFS": "2MiB flash with ROMFS" }, "vendor": "Espressif" } diff --git a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_FLASH_2M_ROMFS.mk b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_FLASH_2M_ROMFS.mk new file mode 100644 index 00000000000..9ee3566d8bf --- /dev/null +++ b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_FLASH_2M_ROMFS.mk @@ -0,0 +1,12 @@ +LD_FILES = boards/esp8266_2MiB_ROMFS.ld + +MICROPY_PY_ESPNOW ?= 1 +MICROPY_PY_BTREE ?= 1 +MICROPY_VFS_FAT ?= 1 +MICROPY_VFS_LFS2 ?= 1 + +# Add asyncio and extra micropython-lib packages (in addition to the port manifest). +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest_2MiB.py + +# Configure mpconfigboard.h. +CFLAGS += -DMICROPY_ESP8266_2M -DMICROPY_VFS_ROM=1 diff --git a/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld b/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld new file mode 100644 index 00000000000..6020c3d485d --- /dev/null +++ b/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld @@ -0,0 +1,24 @@ +/* GNU linker script for ESP8266 with 2M or more flash, and includes a ROMFS partition + + Flash layout: + 0x40200000 36k header + iram/dram init + 0x40209000 668k firmware (irom0) + 0x402c0000 320k ROMFS + 0x40300000 1M+ filesystem (not memory mapped) +*/ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 16 + dram0_0_seg : org = 0x3ffe8000, len = 80K + iram1_0_seg : org = 0x40100000, len = 32K + irom0_0_seg : org = 0x40209000, len = 1M - 36K - 320K + FLASH_ROMFS : org = 0x402b0000, len = 320K +} + +/* define ROMFS extents */ +_micropy_hw_romfs_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_size = LENGTH(FLASH_ROMFS); + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld From be0fce942996af576acd2ac639a4be92f6c4eeb7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 4 Mar 2025 22:46:33 +1100 Subject: [PATCH 0365/2098] unix/main: Add coverage test for mounting ROMFS filesystem at startup. Signed-off-by: Damien George --- ports/unix/main.c | 27 +++++++++++++++++++ .../unix/variants/coverage/mpconfigvariant.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/ports/unix/main.c b/ports/unix/main.c index 58fa3ff589a..0d137fe1b58 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -45,6 +45,7 @@ #include "py/gc.h" #include "py/objstr.h" #include "py/cstack.h" +#include "py/mperrno.h" #include "py/mphal.h" #include "py/mpthread.h" #include "extmod/misc.h" @@ -548,7 +549,14 @@ MP_NOINLINE int main_(int argc, char **argv) { MP_OBJ_NEW_QSTR(MP_QSTR__slash_), }; mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map); + + // Make sure the root that was just mounted is the current VFS (it's always at + // the end of the linked list). Can't use chdir('/') because that will change + // the current path within the VfsPosix object. MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); + while (MP_STATE_VM(vfs_cur)->next != NULL) { + MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_cur)->next; + } } #endif @@ -803,3 +811,22 @@ void nlr_jump_fail(void *val) { fprintf(stderr, "FATAL: uncaught NLR %p\n", val); exit(1); } + +#if MICROPY_VFS_ROM_IOCTL + +static uint8_t romfs_buf[4] = { 0xd2, 0xcd, 0x31, 0x00 }; // empty ROMFS +static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, sizeof(romfs_buf), romfs_buf); + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&romfs_obj); + } + + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); +} + +#endif diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index e24e0f1b14a..a72e533e74c 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -41,4 +41,6 @@ #define MICROPY_DEBUG_PARSE_RULE_NAME (1) #define MICROPY_TRACKED_ALLOC (1) #define MICROPY_WARNINGS_CATEGORY (1) +#undef MICROPY_VFS_ROM_IOCTL +#define MICROPY_VFS_ROM_IOCTL (1) #define MICROPY_PY_CRYPTOLIB_CTR (1) From 3823aeb0f14c04084eba6566164d9b7dbd9e7ced Mon Sep 17 00:00:00 2001 From: danicampora Date: Fri, 21 Feb 2025 14:51:45 +0100 Subject: [PATCH 0366/2098] zephyr/machine_timer: Add machine.Timer class implementation. Simple `machine.Timer` implementation in-line with the rest of the MicroPython ports. Note: Only virtual timers are supported (not linked to any particular hardware peripheral). Tested with the nRF5340 and the nRF52840. Signed-off-by: danicampora --- ports/zephyr/CMakeLists.txt | 1 + ports/zephyr/machine_timer.c | 201 +++++++++++++++++++++++++++++++++++ ports/zephyr/modmachine.c | 1 + 3 files changed, 203 insertions(+) create mode 100644 ports/zephyr/machine_timer.c diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 68b6c0700b5..b9550890667 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -40,6 +40,7 @@ set(MICROPY_SOURCE_PORT machine_i2c.c machine_spi.c machine_pin.c + machine_timer.c modbluetooth_zephyr.c modsocket.c modzephyr.c diff --git a/ports/zephyr/machine_timer.c b/ports/zephyr/machine_timer.c new file mode 100644 index 00000000000..8ab2f629131 --- /dev/null +++ b/ports/zephyr/machine_timer.c @@ -0,0 +1,201 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Daniel Campora on behalf of REMOTE TECH LTD + * + * 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 + +#include "py/mperrno.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "modmachine.h" +#include +#include +#include + +#define TIMER_MS_PER_TICK (1000) + +typedef enum _machine_timer_mode_t { + TIMER_MODE_ONE_SHOT = 0, + TIMER_MODE_PERIODIC +} machine_timer_mode_t; + +typedef struct _machine_timer_obj_t { + mp_obj_base_t base; + + struct k_timer my_timer; + + machine_timer_mode_t mode; + uint32_t period_ms; + + mp_obj_t callback; + + struct _machine_timer_obj_t *next; +} machine_timer_obj_t; + +const mp_obj_type_t machine_timer_type; + +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); +static mp_obj_t machine_timer_deinit(mp_obj_t self_in); + +static void machine_timer_callback(struct k_timer *timer) { + machine_timer_obj_t *self = (machine_timer_obj_t *)k_timer_user_data_get(timer); + if (self->mode == TIMER_MODE_ONE_SHOT) { + machine_timer_deinit(self); + } + if (self->callback != mp_const_none) { + mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + } +} + +static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_timer_obj_t *self = self_in; + qstr mode_str = (self->mode == TIMER_MODE_PERIODIC) ? MP_QSTR_PERIODIC : MP_QSTR_ONE_SHOT; + mp_printf(print, "Timer(-1, mode=%q, period=%lu)", mode_str, self->period_ms); +} + +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); + + if (mp_obj_get_int(args[0]) != -1) { + mp_raise_ValueError(MP_ERROR_TEXT("only virtual timers are supported")); + } + + // Create the new timer. + machine_timer_obj_t *self = mp_obj_malloc_with_finaliser(machine_timer_obj_t, &machine_timer_type); + + // Add the timer to the linked-list of timers + self->next = MP_STATE_PORT(machine_timer_obj_head); + MP_STATE_PORT(machine_timer_obj_head) = self; + + if (n_args > 1 || n_kw > 0) { + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_timer_init_helper(self, n_args - 1, args + 1, &kw_args); + } + return 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) { + enum { + ARG_mode, + ARG_callback, + ARG_period, + ARG_freq, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIMER_MODE_PERIODIC} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + #if MICROPY_PY_BUILTINS_FLOAT + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #else + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + #endif + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + #if MICROPY_PY_BUILTINS_FLOAT + if (args[ARG_freq].u_obj != mp_const_none) { + self->period_ms = (uint32_t)(TIMER_MS_PER_TICK / mp_obj_get_float(args[ARG_freq].u_obj)); + } + #else + if (args[ARG_freq].u_int != 0xffffffff) { + self->period_ms = TIMER_MS_PER_TICK / ((uint64_t)args[ARG_freq].u_int); + } + #endif + else { + self->period_ms = args[ARG_period].u_int; + } + + self->mode = args[ARG_mode].u_int; + self->callback = args[ARG_callback].u_obj; + + k_timer_init(&self->my_timer, machine_timer_callback, NULL); + k_timer_user_data_set(&self->my_timer, self); + k_timer_start(&self->my_timer, K_MSEC(self->period_ms), K_MSEC(self->period_ms)); + + return mp_const_none; +} + +static mp_obj_t machine_timer_deinit(mp_obj_t self_in) { + machine_timer_obj_t *self = self_in; + machine_timer_obj_t *prev = NULL; + + k_timer_stop(&self->my_timer); + + // remove it from the linked list + for (machine_timer_obj_t *_timer = MP_STATE_PORT(machine_timer_obj_head); _timer != NULL; _timer = _timer->next) { + if (_timer == self) { + if (prev != NULL) { + prev->next = _timer->next; + } else { + // move the start pointer + MP_STATE_PORT(machine_timer_obj_head) = _timer->next; + } + break; + } else { + prev = _timer; + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_deinit_obj, machine_timer_deinit); + +static mp_obj_t machine_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return machine_timer_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init); + +static mp_obj_t machine_timer_value(mp_obj_t self_in) { + machine_timer_obj_t *self = self_in; + k_ticks_t ticks = k_timer_remaining_ticks(&self->my_timer); + return MP_OBJ_NEW_SMALL_INT(k_ticks_to_ms_near32(ticks)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_value_obj, machine_timer_value); + +static const mp_rom_map_elem_t machine_timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_timer_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_timer_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(TIMER_MODE_ONE_SHOT) }, + { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(TIMER_MODE_PERIODIC) }, +}; +static MP_DEFINE_CONST_DICT(machine_timer_locals_dict, machine_timer_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_timer_type, + MP_QSTR_Timer, + MP_TYPE_FLAG_NONE, + make_new, machine_timer_make_new, + print, machine_timer_print, + locals_dict, &machine_timer_locals_dict + ); + +MP_REGISTER_ROOT_POINTER(struct _machine_timer_obj_t *machine_timer_obj_head); diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index bbb9280a5dc..89ad12f185d 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -44,6 +44,7 @@ MICROPY_PY_MACHINE_RESET_ENTRY \ { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \ static mp_obj_t machine_reset(void) { sys_reboot(SYS_REBOOT_COLD); From 0ec496a98ca7ba0d81c877505fa7673bab5be75c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 12 Mar 2025 13:07:25 +1100 Subject: [PATCH 0367/2098] lib/cyw43-driver: Update driver to latest version v1.1.0. Includes various fixes and improvements to the WLAN driver, in particular: - Add WPA3 STA and AP support. - Attempt to reconnect to AP in response to validation error. - Update 43439 BT firmware for Data Length Extension fix. Signed-off-by: Damien George --- lib/cyw43-driver | 2 +- ports/mimxrt/cyw43_configport.h | 2 +- ports/rp2/cyw43_configport.h | 1 + ports/stm32/cyw43_configport.h | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/cyw43-driver b/lib/cyw43-driver index 9f6405f0b32..dd7568229f3 160000 --- a/lib/cyw43-driver +++ b/lib/cyw43-driver @@ -1 +1 @@ -Subproject commit 9f6405f0b3260968306d782e1c5ac275a46dc65d +Subproject commit dd7568229f3bf7a37737b9e1ef250c26efe75b23 diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h index 619db24b1b5..ab535a47776 100644 --- a/ports/mimxrt/cyw43_configport.h +++ b/ports/mimxrt/cyw43_configport.h @@ -107,7 +107,7 @@ #endif #if MICROPY_HW_ENABLE_RF_SWITCH -#define CYW43_PIN_WL_RFSW_VDD pin_WL_RFSW_VDD +#define CYW43_PIN_RFSW_VDD pin_WL_RFSW_VDD #endif #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 4294691c666..bb99bb2976f 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -35,6 +35,7 @@ #include "extmod/modnetwork.h" #include "pendsv.h" +#define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (1) #define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (10) diff --git a/ports/stm32/cyw43_configport.h b/ports/stm32/cyw43_configport.h index 1ea1ebde17e..59505118033 100644 --- a/ports/stm32/cyw43_configport.h +++ b/ports/stm32/cyw43_configport.h @@ -102,7 +102,7 @@ #define CYW43_PIN_BT_CTS pyb_pin_BT_CTS #if MICROPY_HW_ENABLE_RF_SWITCH -#define CYW43_PIN_WL_RFSW_VDD pyb_pin_WL_RFSW_VDD +#define CYW43_PIN_RFSW_VDD pyb_pin_WL_RFSW_VDD #endif #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) From dafff1fd0e37514b230081b1b34288d1d4b25867 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 12 Mar 2025 13:15:34 +1100 Subject: [PATCH 0368/2098] extmod/network_cyw43: Add WPA3 security constants. These are now supported by cyw43-driver. Signed-off-by: Damien George --- extmod/network_cyw43.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 02b022cb813..1b1b10b4074 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -583,6 +583,8 @@ static const mp_rom_map_elem_t network_cyw43_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(MOD_NETWORK_AP_IF) }, { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(CYW43_AUTH_OPEN) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(CYW43_AUTH_WPA2_MIXED_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3), MP_ROM_INT(CYW43_AUTH_WPA3_SAE_AES_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3), MP_ROM_INT(CYW43_AUTH_WPA3_WPA2_AES_PSK) }, { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(PM_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(PM_PERFORMANCE) }, From 4b1c666c28ef9adb5a9b5156624bccd6a3d129b6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Mar 2025 12:20:02 +1100 Subject: [PATCH 0369/2098] esp32: Merge the per-SoC "main" components back together. Removes redundant metadata from each, shouldn't otherwise change any build output. Reverts the split originally added in e4650125. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/CMakeLists.txt | 3 -- ports/esp32/esp32_common.cmake | 14 ++++++- .../esp32/{main_esp32 => main}/CMakeLists.txt | 0 .../{main_esp32 => main}/idf_component.yml | 4 ++ ports/esp32/main/linker_esp32.lf | 41 +++++++++++++++++++ ports/esp32/main_esp32/linker.lf | 39 ------------------ ports/esp32/main_esp32c3/CMakeLists.txt | 14 ------- ports/esp32/main_esp32c3/idf_component.yml | 5 --- ports/esp32/main_esp32c3/linker.lf | 1 - ports/esp32/main_esp32c6/CMakeLists.txt | 14 ------- ports/esp32/main_esp32c6/idf_component.yml | 5 --- ports/esp32/main_esp32c6/linker.lf | 1 - ports/esp32/main_esp32s2/CMakeLists.txt | 13 ------ ports/esp32/main_esp32s2/idf_component.yml | 6 --- ports/esp32/main_esp32s2/linker.lf | 1 - ports/esp32/main_esp32s3/CMakeLists.txt | 13 ------ ports/esp32/main_esp32s3/idf_component.yml | 6 --- ports/esp32/main_esp32s3/linker.lf | 1 - 18 files changed, 58 insertions(+), 123 deletions(-) rename ports/esp32/{main_esp32 => main}/CMakeLists.txt (100%) rename ports/esp32/{main_esp32 => main}/idf_component.yml (52%) create mode 100644 ports/esp32/main/linker_esp32.lf delete mode 100644 ports/esp32/main_esp32/linker.lf delete mode 100644 ports/esp32/main_esp32c3/CMakeLists.txt delete mode 100644 ports/esp32/main_esp32c3/idf_component.yml delete mode 100644 ports/esp32/main_esp32c3/linker.lf delete mode 100644 ports/esp32/main_esp32c6/CMakeLists.txt delete mode 100644 ports/esp32/main_esp32c6/idf_component.yml delete mode 100644 ports/esp32/main_esp32c6/linker.lf delete mode 100644 ports/esp32/main_esp32s2/CMakeLists.txt delete mode 100644 ports/esp32/main_esp32s2/idf_component.yml delete mode 100644 ports/esp32/main_esp32s2/linker.lf delete mode 100644 ports/esp32/main_esp32s3/CMakeLists.txt delete mode 100644 ports/esp32/main_esp32s3/idf_component.yml delete mode 100644 ports/esp32/main_esp32s3/linker.lf diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index c3a675eb2b7..1db374b40a4 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -61,8 +61,5 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) # Include main IDF cmake file. include($ENV{IDF_PATH}/tools/cmake/project.cmake) -# Set the location of the main component for the project (one per target). -list(APPEND EXTRA_COMPONENT_DIRS main_${IDF_TARGET}) - # Define the project. project(micropython) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 059989c10a4..09952363d6d 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -14,6 +14,18 @@ if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) endif() +# RISC-V specific inclusions +if(CONFIG_IDF_TARGET_ARCH_RISCV) + list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) + list(APPEND IDF_COMPONENTS riscv) +endif() + +if(NOT DEFINED MICROPY_PY_TINYUSB) + if(CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + set(MICROPY_PY_TINYUSB ON) + endif() +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) @@ -182,7 +194,7 @@ list(APPEND IDF_COMPONENTS if (MICROPY_USER_LDFRAGMENTS) set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) else() - set(MICROPY_LDFRAGMENTS linker.lf) + set(MICROPY_LDFRAGMENTS linker_esp32.lf) endif() # Register the main IDF component. diff --git a/ports/esp32/main_esp32/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt similarity index 100% rename from ports/esp32/main_esp32/CMakeLists.txt rename to ports/esp32/main/CMakeLists.txt diff --git a/ports/esp32/main_esp32/idf_component.yml b/ports/esp32/main/idf_component.yml similarity index 52% rename from ports/esp32/main_esp32/idf_component.yml rename to ports/esp32/main/idf_component.yml index 11f078f69f7..ccbde1f27e4 100644 --- a/ports/esp32/main_esp32/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -1,5 +1,9 @@ ## IDF Component Manager Manifest File dependencies: espressif/mdns: "~1.1.0" + espressif/esp_tinyusb: + rules: + - if: "target in [esp32s2, esp32s3]" + version: "~1.0.0" idf: version: ">=5.2.0" diff --git a/ports/esp32/main/linker_esp32.lf b/ports/esp32/main/linker_esp32.lf new file mode 100644 index 00000000000..27e4ac2194c --- /dev/null +++ b/ports/esp32/main/linker_esp32.lf @@ -0,0 +1,41 @@ +# This fixes components/esp_ringbuf/linker.lf for ESP32 only to allow us to put +# non-ISR ringbuf functions in flash. + +# Requires that both RINGBUF_PLACE_FUNCTIONS_INTO_FLASH and RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH +# are set to "n" (which is the default), otherwise this would result in duplicate section config +# when resolving the linker fragments. + +# The effect of this file is to leave the ISR functions in RAM (which we require), but apply a fixed +# version of RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y (leaving out prvGetFreeSize and prvGetCurMaxSizeByteBuf) +# See https://github.com/espressif/esp-idf/issues/13378 + +[mapping:esp_ringbuf_fix] +archive: libesp_ringbuf.a +entries: + if IDF_TARGET_ESP32 = y: + # This is exactly the list of functions from RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y, + # but with prvGetFreeSize and prvGetCurMaxSizeByteBuf removed. + ringbuf: prvGetCurMaxSizeNoSplit (default) + ringbuf: prvGetCurMaxSizeAllowSplit (default) + ringbuf: prvInitializeNewRingbuffer (default) + ringbuf: prvReceiveGeneric (default) + ringbuf: vRingbufferDelete (default) + ringbuf: vRingbufferGetInfo (default) + ringbuf: vRingbufferReturnItem (default) + ringbuf: xRingbufferAddToQueueSetRead (default) + ringbuf: xRingbufferCanRead (default) + ringbuf: xRingbufferCreate (default) + ringbuf: xRingbufferCreateStatic (default) + ringbuf: xRingbufferCreateNoSplit (default) + ringbuf: xRingbufferReceive (default) + ringbuf: xRingbufferReceiveSplit (default) + ringbuf: xRingbufferReceiveUpTo (default) + ringbuf: xRingbufferRemoveFromQueueSetRead (default) + ringbuf: xRingbufferSend (default) + ringbuf: xRingbufferSendAcquire (default) + ringbuf: xRingbufferSendComplete (default) + ringbuf: xRingbufferPrintInfo (default) + ringbuf: xRingbufferGetMaxItemSize (default) + ringbuf: xRingbufferGetCurFreeSize (default) + + # Everything else will have the default rule already applied (i.e. noflash_text). diff --git a/ports/esp32/main_esp32/linker.lf b/ports/esp32/main_esp32/linker.lf deleted file mode 100644 index e00cd63f554..00000000000 --- a/ports/esp32/main_esp32/linker.lf +++ /dev/null @@ -1,39 +0,0 @@ -# This fixes components/esp_ringbuf/linker.lf to allow us to put non-ISR ringbuf functions in flash. - -# Requires that both RINGBUF_PLACE_FUNCTIONS_INTO_FLASH and RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH -# are set to "n" (which is the default), otherwise this would result in duplicate section config -# when resolving the linker fragments. - -# The effect of this file is to leave the ISR functions in RAM (which we require), but apply a fixed -# version of RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y (leaving out prvGetFreeSize and prvGetCurMaxSizeByteBuf) -# See https://github.com/espressif/esp-idf/issues/13378 - -[mapping:esp_ringbuf_fix] -archive: libesp_ringbuf.a -entries: - # This is exactly the list of functions from RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y, - # but with prvGetFreeSize and prvGetCurMaxSizeByteBuf removed. - ringbuf: prvGetCurMaxSizeNoSplit (default) - ringbuf: prvGetCurMaxSizeAllowSplit (default) - ringbuf: prvInitializeNewRingbuffer (default) - ringbuf: prvReceiveGeneric (default) - ringbuf: vRingbufferDelete (default) - ringbuf: vRingbufferGetInfo (default) - ringbuf: vRingbufferReturnItem (default) - ringbuf: xRingbufferAddToQueueSetRead (default) - ringbuf: xRingbufferCanRead (default) - ringbuf: xRingbufferCreate (default) - ringbuf: xRingbufferCreateStatic (default) - ringbuf: xRingbufferCreateNoSplit (default) - ringbuf: xRingbufferReceive (default) - ringbuf: xRingbufferReceiveSplit (default) - ringbuf: xRingbufferReceiveUpTo (default) - ringbuf: xRingbufferRemoveFromQueueSetRead (default) - ringbuf: xRingbufferSend (default) - ringbuf: xRingbufferSendAcquire (default) - ringbuf: xRingbufferSendComplete (default) - ringbuf: xRingbufferPrintInfo (default) - ringbuf: xRingbufferGetMaxItemSize (default) - ringbuf: xRingbufferGetCurFreeSize (default) - - # Everything else will have the default rule already applied (i.e. noflash_text). diff --git a/ports/esp32/main_esp32c3/CMakeLists.txt b/ports/esp32/main_esp32c3/CMakeLists.txt deleted file mode 100644 index 307c0f32183..00000000000 --- a/ports/esp32/main_esp32c3/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../.. ABSOLUTE) -endif() - -# Set location of the ESP32 port directory. -if(NOT MICROPY_PORT_DIR) - get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) -endif() - -list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) -list(APPEND IDF_COMPONENTS riscv) - -include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/main_esp32c3/idf_component.yml b/ports/esp32/main_esp32c3/idf_component.yml deleted file mode 100644 index 11f078f69f7..00000000000 --- a/ports/esp32/main_esp32c3/idf_component.yml +++ /dev/null @@ -1,5 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - idf: - version: ">=5.2.0" diff --git a/ports/esp32/main_esp32c3/linker.lf b/ports/esp32/main_esp32c3/linker.lf deleted file mode 100644 index 31c5b4563ca..00000000000 --- a/ports/esp32/main_esp32c3/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for C3, see main_esp32/linker.lf). diff --git a/ports/esp32/main_esp32c6/CMakeLists.txt b/ports/esp32/main_esp32c6/CMakeLists.txt deleted file mode 100644 index 307c0f32183..00000000000 --- a/ports/esp32/main_esp32c6/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../.. ABSOLUTE) -endif() - -# Set location of the ESP32 port directory. -if(NOT MICROPY_PORT_DIR) - get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) -endif() - -list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) -list(APPEND IDF_COMPONENTS riscv) - -include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/main_esp32c6/idf_component.yml b/ports/esp32/main_esp32c6/idf_component.yml deleted file mode 100644 index 11f078f69f7..00000000000 --- a/ports/esp32/main_esp32c6/idf_component.yml +++ /dev/null @@ -1,5 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - idf: - version: ">=5.2.0" diff --git a/ports/esp32/main_esp32c6/linker.lf b/ports/esp32/main_esp32c6/linker.lf deleted file mode 100644 index cedabcf9793..00000000000 --- a/ports/esp32/main_esp32c6/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for C6, see main_esp32/linker.lf). diff --git a/ports/esp32/main_esp32s2/CMakeLists.txt b/ports/esp32/main_esp32s2/CMakeLists.txt deleted file mode 100644 index bc5ab939c3c..00000000000 --- a/ports/esp32/main_esp32s2/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../.. ABSOLUTE) -endif() - -# Set location of the ESP32 port directory. -if(NOT MICROPY_PORT_DIR) - get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) -endif() - -set(MICROPY_PY_TINYUSB ON) - -include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/main_esp32s2/idf_component.yml b/ports/esp32/main_esp32s2/idf_component.yml deleted file mode 100644 index 2ee00b28778..00000000000 --- a/ports/esp32/main_esp32s2/idf_component.yml +++ /dev/null @@ -1,6 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - espressif/esp_tinyusb: "~1.0.0" - idf: - version: ">=5.2.0" diff --git a/ports/esp32/main_esp32s2/linker.lf b/ports/esp32/main_esp32s2/linker.lf deleted file mode 100644 index 3c496fa878b..00000000000 --- a/ports/esp32/main_esp32s2/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for S2, see main_esp32/linker.lf). diff --git a/ports/esp32/main_esp32s3/CMakeLists.txt b/ports/esp32/main_esp32s3/CMakeLists.txt deleted file mode 100644 index bc5ab939c3c..00000000000 --- a/ports/esp32/main_esp32s3/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../.. ABSOLUTE) -endif() - -# Set location of the ESP32 port directory. -if(NOT MICROPY_PORT_DIR) - get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) -endif() - -set(MICROPY_PY_TINYUSB ON) - -include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/main_esp32s3/idf_component.yml b/ports/esp32/main_esp32s3/idf_component.yml deleted file mode 100644 index 2ee00b28778..00000000000 --- a/ports/esp32/main_esp32s3/idf_component.yml +++ /dev/null @@ -1,6 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - espressif/esp_tinyusb: "~1.0.0" - idf: - version: ">=5.2.0" diff --git a/ports/esp32/main_esp32s3/linker.lf b/ports/esp32/main_esp32s3/linker.lf deleted file mode 100644 index 81d27906be0..00000000000 --- a/ports/esp32/main_esp32s3/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for S3, see main_esp32/linker.lf). From 4d65b4e26119ab19de362174ec62cc3ea68a836a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Mar 2025 12:30:10 +1100 Subject: [PATCH 0370/2098] esp32: Remove the ESP32 ringbuffer linker workaround. Reverts workaround added in acbdbcd9. According to the linked ESP-IDF issue this was only a problem for ESP-IDF V5.0.x, and support for versions older than V5.2 was dropped in 6e5d8d009. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/sdkconfig.spiram | 2 +- ports/esp32/esp32_common.cmake | 2 -- ports/esp32/main/linker_esp32.lf | 41 ----------------------------- 3 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 ports/esp32/main/linker_esp32.lf diff --git a/ports/esp32/boards/sdkconfig.spiram b/ports/esp32/boards/sdkconfig.spiram index 35fe3c676dd..f5503d55414 100644 --- a/ports/esp32/boards/sdkconfig.spiram +++ b/ports/esp32/boards/sdkconfig.spiram @@ -13,6 +13,6 @@ CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=8192 # to PSRAM bug workarounds. Apply some options to reduce the firmware size. CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y -# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y # Workaround required: see main_esp32/linker.lf +CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 09952363d6d..6473f04a535 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -193,8 +193,6 @@ list(APPEND IDF_COMPONENTS # Provide the default LD fragment if not set if (MICROPY_USER_LDFRAGMENTS) set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) -else() - set(MICROPY_LDFRAGMENTS linker_esp32.lf) endif() # Register the main IDF component. diff --git a/ports/esp32/main/linker_esp32.lf b/ports/esp32/main/linker_esp32.lf deleted file mode 100644 index 27e4ac2194c..00000000000 --- a/ports/esp32/main/linker_esp32.lf +++ /dev/null @@ -1,41 +0,0 @@ -# This fixes components/esp_ringbuf/linker.lf for ESP32 only to allow us to put -# non-ISR ringbuf functions in flash. - -# Requires that both RINGBUF_PLACE_FUNCTIONS_INTO_FLASH and RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH -# are set to "n" (which is the default), otherwise this would result in duplicate section config -# when resolving the linker fragments. - -# The effect of this file is to leave the ISR functions in RAM (which we require), but apply a fixed -# version of RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y (leaving out prvGetFreeSize and prvGetCurMaxSizeByteBuf) -# See https://github.com/espressif/esp-idf/issues/13378 - -[mapping:esp_ringbuf_fix] -archive: libesp_ringbuf.a -entries: - if IDF_TARGET_ESP32 = y: - # This is exactly the list of functions from RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y, - # but with prvGetFreeSize and prvGetCurMaxSizeByteBuf removed. - ringbuf: prvGetCurMaxSizeNoSplit (default) - ringbuf: prvGetCurMaxSizeAllowSplit (default) - ringbuf: prvInitializeNewRingbuffer (default) - ringbuf: prvReceiveGeneric (default) - ringbuf: vRingbufferDelete (default) - ringbuf: vRingbufferGetInfo (default) - ringbuf: vRingbufferReturnItem (default) - ringbuf: xRingbufferAddToQueueSetRead (default) - ringbuf: xRingbufferCanRead (default) - ringbuf: xRingbufferCreate (default) - ringbuf: xRingbufferCreateStatic (default) - ringbuf: xRingbufferCreateNoSplit (default) - ringbuf: xRingbufferReceive (default) - ringbuf: xRingbufferReceiveSplit (default) - ringbuf: xRingbufferReceiveUpTo (default) - ringbuf: xRingbufferRemoveFromQueueSetRead (default) - ringbuf: xRingbufferSend (default) - ringbuf: xRingbufferSendAcquire (default) - ringbuf: xRingbufferSendComplete (default) - ringbuf: xRingbufferPrintInfo (default) - ringbuf: xRingbufferGetMaxItemSize (default) - ringbuf: xRingbufferGetCurFreeSize (default) - - # Everything else will have the default rule already applied (i.e. noflash_text). From 79fb5aa8789e71c9bcd8430b58267d96552be94f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 15:16:47 +1100 Subject: [PATCH 0371/2098] esp32/machine_sdcard: Add SDCard pin assignments for ESP32-S3 support. Previously ESP32-S3 SDMMC could only use fixed pin assignments, however the ESP-IDF defaults don't match common boards. The chip also supports using GPIO Matrix to assign any pin. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 2 +- docs/library/machine.SDCard.rst | 161 ++++++++++++++++++++++---------- ports/esp32/machine_sdcard.c | 47 ++++++++++ 3 files changed, 160 insertions(+), 50 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 0780e013008..d65782e501a 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -747,7 +747,7 @@ See :ref:`machine.SDCard `. :: import machine, os, vfs - # Slot 2 uses pins sck=18, cs=5, miso=19, mosi=23 + # On original ESP32, slot 2 uses pins sck=18, cs=5, miso=19, mosi=23 sd = machine.SDCard(slot=2) vfs.mount(sd, '/sd') # mount diff --git a/docs/library/machine.SDCard.rst b/docs/library/machine.SDCard.rst index e4bb25dfc03..62da8c3c2ea 100644 --- a/docs/library/machine.SDCard.rst +++ b/docs/library/machine.SDCard.rst @@ -23,7 +23,8 @@ arguments that might need to be set in order to use either a non-standard slot or a non-standard pin assignment. The exact subset of arguments supported will vary from platform to platform. -.. class:: SDCard(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, cs=None, freq=20000000) +.. class:: SDCard(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, + cs=None, cmd=None, data=None, freq=20000000) This class provides access to SD or MMC storage cards using either a dedicated SD/MMC interface hardware or through an SPI channel. @@ -37,7 +38,8 @@ vary from platform to platform. - *slot* selects which of the available interfaces to use. Leaving this unset will select the default interface. - - *width* selects the bus width for the SD/MMC interface. + - *width* selects the bus width for the SD/MMC interface. This many data + pins must be connected to the SD card. - *cd* can be used to specify a card-detect pin. @@ -51,7 +53,14 @@ vary from platform to platform. - *cs* can be used to specify an SPI chip select pin. - - *freq* selects the SD/MMC interface frequency in Hz (only supported on the ESP32). + The following additional parameters are only present on ESP32 port: + + - *cmd* can be used to specify the SD CMD pin (ESP32-S3 only). + + - *data* can be used to specify a list or tuple of SD data bus pins + (ESP32-S3 only). + + - *freq* selects the SD/MMC interface frequency in Hz. Implementation-specific details ------------------------------- @@ -67,52 +76,106 @@ The standard PyBoard has just one slot. No arguments are necessary or supported. ESP32 ````` -The ESP32 provides two channels of SD/MMC hardware and also supports -access to SD Cards through either of the two SPI ports that are -generally available to the user. As a result the *slot* argument can -take a value between 0 and 3, inclusive. Slots 0 and 1 use the -built-in SD/MMC hardware while slots 2 and 3 use the SPI ports. Slot 0 -supports 1, 4 or 8-bit wide access while slot 1 supports 1 or 4-bit -access; the SPI slots only support 1-bit access. - - .. note:: Slot 0 is used to communicate with on-board flash memory - on most ESP32 modules and so will be unavailable to the - user. - - .. note:: Most ESP32 modules that provide an SD card slot using the - dedicated hardware only wire up 1 data pin, so the default - value for *width* is 1. - -The pins used by the dedicated SD/MMC hardware are fixed. The pins -used by the SPI hardware can be reassigned. - - .. note:: If any of the SPI signals are remapped then all of the SPI - signals will pass through a GPIO multiplexer unit which - can limit the performance of high frequency signals. Since - the normal operating speed for SD cards is 40MHz this can - cause problems on some cards. - -The default (and preferred) pin assignment are as follows: - - ====== ====== ====== ====== ====== - Slot 0 1 2 3 - ------ ------ ------ ------ ------ - Signal Pin Pin Pin Pin - ====== ====== ====== ====== ====== - sck 6 14 18 14 - cmd 11 15 - cs 5 15 - miso 19 12 - mosi 23 13 - D0 7 2 - D1 8 4 - D2 9 12 - D3 10 13 - D4 16 - D5 17 - D6 5 - D7 18 - ====== ====== ====== ====== ====== +SD cards support access in both SD/MMC mode and the simpler (but slower) SPI +mode. ESP32 and ESP32-S3 chips can access SD cards using either mode. SPI mode +makes use of a `SPI` host peripheral, which cannot concurrently be used for +something else. + +The ``slot`` argument determines which mode is used. Different values are +available on different chips: + +====== ================= ============ ======================== +Slot Supported chips Mode Supported data width +====== ================= ============ ======================== +0 ESP32-S3 SD/MMC 1, 4, or 8 bits. +1 ESP32, ESP32-S3 SD/MMC 1 or 4 bits. +2 ESP32, ESP32-S3 `SPI` (id=1) 1 bit. +3 ESP32, ESP32-S3 `SPI` (id=0) 1 bit. +====== ================= ============ ======================== + +.. note:: On the original ESP32, SDMMC slot 0 is unavailable as its pins are + used to communicate with on-board flash memory. + +.. note:: Most ESP32 modules that provide an SD card slot using the + dedicated hardware only wire up 1 data pin, so the default + value for ``width`` is 1. + +Additional details depend on which ESP32 family chip is in use: + +Original ESP32 +~~~~~~~~~~~~~~ + +Pin assignments in SD/MMC mode are fixed on the original ESP32. When accessing a +card in SPI mode, pins can be set to different values in the constructor. + +The default pin assignments are as follows: + + ====== ====== ====== ====== ============ + Slot 1 2 3 Can be set + ------ ------ ------ ------ ------------ + Signal Pin Pin Pin + ====== ====== ====== ====== ============ + CLK 14 No + CMD 15 No + D0 2 No + D1 4 No + D2 12 No + D3 13 No + sck 18 14 Yes + cs 5 15 Yes + miso 19 12 Yes + mosi 23 13 Yes + ====== ====== ====== ====== ============ + +The ``cd`` and ``wp`` pins are not fixed in either mode and default to disabled, unless set. + +ESP32-S3 +~~~~~~~~ + +The ESP32-S3 chip allows pins to be set to different values for both SD/MMC and +SPI mode access. + +If not set, default pin assignments are as follows: + + ======== ====== ====== ====== ====== + Slot 0 1 2 3 + -------- ------ ------ ------ ------ + Signal Pin Pin Pin Pin + ======== ====== ====== ====== ====== + CLK 14 14 + CMD 15 15 + D0 2 2 + D1 4 4 + D2 12 12 + D3 13 13 + D4 33* + D5 34* + D6 35* + D7 36* + sck 37* 14 + cs 34* 13 + miso 37* 2 + mosi 35* 15 + ======== ====== ====== ====== ====== + +.. note:: Slots 0 and 1 cannot both be in use at the same time. + +.. note:: Pins marked with an asterisk * in the table must be changed from the + default if the ESP32-S3 board is configured for Octal SPI Flash or + PSRAM. + +To access a card in SD/MMC mode, set ``slot`` parameter value 0 or 1 and +parameters ``sck`` (for CLK), ``cmd`` and ``data`` as needed to assign pins. If +the ``data`` argument is passed then it should be a list or tuple of data pins +or pin numbers with length equal to the ``width`` argument. For example:: + + sd = SDCard(slot=0, width=4, sck=8, cmd=9, data=(10, 11, 12, 13)) + +To access a card in SPI mode, set ``slot`` parameter value 2 or 3 and pass +parameters ``sck``, ``cs``, ``miso``, ``mosi`` as needed to assign pins. + +In either mode the ``cd`` and ``wp`` pins default to disabled, unless set in the +constructor. cc3200 `````` diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index da6b0df282c..48fd96c6d29 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -177,6 +177,10 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args ARG_mosi, ARG_sck, ARG_cs, + #if SOC_SDMMC_USE_GPIO_MATRIX + ARG_cmd, + ARG_data, + #endif ARG_freq, }; static const mp_arg_t allowed_args[] = { @@ -189,6 +193,11 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // Optional assignment of SDMMC interface pins, if host supports this + #if SOC_SDMMC_USE_GPIO_MATRIX + { MP_QSTR_cmd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_data, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif // freq is valid for both SPI and SDMMC interfaces { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, }; @@ -211,6 +220,11 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args arg_vals[ARG_miso].u_obj, arg_vals[ARG_mosi].u_obj, arg_vals[ARG_sck].u_obj, arg_vals[ARG_cs].u_obj); + #if SOC_SDMMC_USE_GPIO_MATRIX + DEBUG_printf(" cmd=%p, data=%p", + arg_vals[ARG_cmd].u_obj, arg_vals[ARG_data].u_obj); + #endif + int slot_num = arg_vals[ARG_slot].u_int; if (slot_num < 0 || slot_num > 3) { mp_raise_ValueError(MP_ERROR_TEXT("slot number must be between 0 and 3 inclusive")); @@ -221,6 +235,13 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args if (is_spi) { slot_num -= 2; } + // Verify valid argument combinations + #if SOC_SDMMC_USE_GPIO_MATRIX + if (is_spi && (arg_vals[ARG_cmd].u_obj != mp_const_none + || arg_vals[ARG_data].u_obj != mp_const_none)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid config: SPI slot with SDMMC pin arguments")); + } + #endif DEBUG_printf(" Setting up host configuration"); @@ -307,6 +328,32 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args mp_raise_ValueError(MP_ERROR_TEXT("width must be 1 or 4 (or 8 on slot 0)")); } + #if SOC_SDMMC_USE_GPIO_MATRIX + // Optionally configure all the SDMMC pins, if chip supports this + SET_CONFIG_PIN(slot_config, clk, ARG_sck); // reuse SPI SCK for CLK + SET_CONFIG_PIN(slot_config, cmd, ARG_cmd); + if (arg_vals[ARG_data].u_obj != mp_const_none) { + mp_obj_t *data_vals; + size_t data_len; + mp_obj_get_array(arg_vals[ARG_data].u_obj, &data_len, &data_vals); + if (data_len != width) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("data argument length must match width %d"), width); + } + slot_config.d0 = machine_pin_get_id(data_vals[0]); + if (width > 1) { + slot_config.d1 = machine_pin_get_id(data_vals[1]); + slot_config.d2 = machine_pin_get_id(data_vals[2]); + slot_config.d3 = machine_pin_get_id(data_vals[3]); + } + if (width == 8) { + slot_config.d4 = machine_pin_get_id(data_vals[4]); + slot_config.d5 = machine_pin_get_id(data_vals[5]); + slot_config.d6 = machine_pin_get_id(data_vals[6]); + slot_config.d7 = machine_pin_get_id(data_vals[7]); + } + } + #endif + DEBUG_printf(" Calling init_slot()"); check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); } From c85eefc55b3cc8a460230129e11fb490a0a15f29 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 15:17:27 +1100 Subject: [PATCH 0372/2098] esp32/machine_sdcard: Add SDCard SPI mode support for ESP32-S2,C3,C6. These micros don't have full SDMMC host support, but they can initialise the SDCard in SPI mode. A bit limited on C3 and C6 as they only have one host SPI peripheral. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.SDCard.rst | 60 ++++++++++++------ ports/esp32/machine_sdcard.c | 104 ++++++++++++++++++++++++-------- 2 files changed, 121 insertions(+), 43 deletions(-) diff --git a/docs/library/machine.SDCard.rst b/docs/library/machine.SDCard.rst index 62da8c3c2ea..c4a0d5d172b 100644 --- a/docs/library/machine.SDCard.rst +++ b/docs/library/machine.SDCard.rst @@ -77,24 +77,34 @@ ESP32 ````` SD cards support access in both SD/MMC mode and the simpler (but slower) SPI -mode. ESP32 and ESP32-S3 chips can access SD cards using either mode. SPI mode -makes use of a `SPI` host peripheral, which cannot concurrently be used for -something else. +mode. -The ``slot`` argument determines which mode is used. Different values are -available on different chips: - -====== ================= ============ ======================== -Slot Supported chips Mode Supported data width -====== ================= ============ ======================== -0 ESP32-S3 SD/MMC 1, 4, or 8 bits. -1 ESP32, ESP32-S3 SD/MMC 1 or 4 bits. -2 ESP32, ESP32-S3 `SPI` (id=1) 1 bit. -3 ESP32, ESP32-S3 `SPI` (id=0) 1 bit. -====== ================= ============ ======================== +SPI mode makes use of a `SPI` host peripheral, which cannot concurrently be used +for other SPI interactions. -.. note:: On the original ESP32, SDMMC slot 0 is unavailable as its pins are - used to communicate with on-board flash memory. +The ``slot`` argument determines which mode is used. Different values are +supported on different chips: + +========== ======== ======== ============ ============ +Chip Slot 0 Slot 1 Slot 2 Slot 3 +========== ======== ======== ============ ============ +ESP32 SD/MMC SPI (id=1) SPI (id=0) +ESP32-C3 SPI (id=0) +ESP32-C6 SPI (id=0) +ESP32-S2 SPI (id=1) SPI (id=0) +ESP32-S3 SD/MMC SD/MMC SPI (id=1) SPI (id=0) +========== ======== ======== ============ ============ + +Different slots support different data bus widths (number of data pins): + +========== ========== ===================== +Slot Type Supported data widths +========== ========== ===================== +0 SD/MMC 1, 4, 8 +1 SD/MMC 1, 4 +2 SPI 1 +3 SPI 1 +========== ========== ===================== .. note:: Most ESP32 modules that provide an SD card slot using the dedicated hardware only wire up 1 data pin, so the default @@ -105,8 +115,9 @@ Additional details depend on which ESP32 family chip is in use: Original ESP32 ~~~~~~~~~~~~~~ -Pin assignments in SD/MMC mode are fixed on the original ESP32. When accessing a -card in SPI mode, pins can be set to different values in the constructor. +In SD/MMC mode (slot 1), pin assignments in SD/MMC mode are fixed on the +original ESP32. The SPI mode slots (2 & 3) allow pins to be set to different +values in the constructor. The default pin assignments are as follows: @@ -177,6 +188,19 @@ parameters ``sck``, ``cs``, ``miso``, ``mosi`` as needed to assign pins. In either mode the ``cd`` and ``wp`` pins default to disabled, unless set in the constructor. +Other ESP32 chips +~~~~~~~~~~~~~~~~~ + +Other ESP32 family chips do not have hardware SD/MMC host controllers and can +only access SD cards in SPI mode. + +To access a card in SPI mode, set ``slot`` parameter value 2 or 3 and pass +parameters ``sck``, ``cs``, ``miso``, ``mosi`` to assign pins. + +.. note:: ESP32-C3 and ESP32-C6 only have one available `SPI` bus, so the only + valid ``slot`` parameter value is 2. Using this bus for the SD card + will prevent also using it for :class:`machine.SPI`. + cc3200 `````` diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 48fd96c6d29..0f8bd844692 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -33,7 +33,9 @@ #if MICROPY_HW_ENABLE_SDCARD +#if SOC_SDMMC_HOST_SUPPORTED #include "driver/sdmmc_host.h" +#endif #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" #include "esp_log.h" @@ -69,18 +71,34 @@ typedef struct _sdcard_obj_t { #define _SECTOR_SIZE(self) (self->card.csd.sector_size) +// Number SPI buses available for firmware app (including for SD) +#define NUM_SD_SPI_BUS (SOC_SPI_PERIPH_NUM - 1) + +#if CONFIG_IDF_TARGET_ESP32 +#define SD_SLOT_MIN 1 +#elif SOC_SDMMC_HOST_SUPPORTED +#define SD_SLOT_MIN 0 +#else +#define SD_SLOT_MIN 2 +#endif +#define SD_SLOT_MAX (NUM_SD_SPI_BUS + 1) // Inclusive + // SPI bus default bus and device configuration. -static const spi_bus_config_t spi_bus_defaults[2] = { +static const spi_bus_config_t spi_bus_defaults[NUM_SD_SPI_BUS] = { { #if CONFIG_IDF_TARGET_ESP32 .miso_io_num = GPIO_NUM_19, .mosi_io_num = GPIO_NUM_23, .sclk_io_num = GPIO_NUM_18, - #else + #elif CONFIG_IDF_TARGET_ESP32S3 .miso_io_num = GPIO_NUM_36, .mosi_io_num = GPIO_NUM_35, .sclk_io_num = GPIO_NUM_37, + #else + .miso_io_num = GPIO_NUM_NC, + .mosi_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_NC, #endif .data2_io_num = GPIO_NUM_NC, .data3_io_num = GPIO_NUM_NC, @@ -92,6 +110,7 @@ static const spi_bus_config_t spi_bus_defaults[2] = { .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, .intr_flags = 0, }, + #if NUM_SD_SPI_BUS > 1 { .miso_io_num = GPIO_NUM_2, .mosi_io_num = GPIO_NUM_15, @@ -106,28 +125,34 @@ static const spi_bus_config_t spi_bus_defaults[2] = { .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, .intr_flags = 0, }, + #endif }; #if CONFIG_IDF_TARGET_ESP32 -static const uint8_t spi_dma_channel_defaults[2] = { +static const uint8_t spi_dma_channel_defaults[NUM_SD_SPI_BUS] = { 2, 1, }; #endif -static const sdspi_device_config_t spi_dev_defaults[2] = { +static const sdspi_device_config_t spi_dev_defaults[NUM_SD_SPI_BUS] = { + #if NUM_SD_SPI_BUS > 1 { #if CONFIG_IDF_TARGET_ESP32 .host_id = VSPI_HOST, .gpio_cs = GPIO_NUM_5, - #else + #elif CONFIG_IDF_TARGET_ESP32S3 .host_id = SPI3_HOST, .gpio_cs = GPIO_NUM_34, + #else + .host_id = SPI3_HOST, + .gpio_cs = GPIO_NUM_NC, #endif .gpio_cd = SDSPI_SLOT_NO_CD, .gpio_wp = SDSPI_SLOT_NO_WP, .gpio_int = SDSPI_SLOT_NO_INT, }, + #endif SDSPI_DEVICE_CONFIG_DEFAULT(), // HSPI (ESP32) / SPI2 (ESP32S3) }; @@ -159,12 +184,15 @@ static esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { // Expose the SD card or MMC as an object with the block protocol. // Create a new SDCard object -// The driver supports either the host SD/MMC controller (default) or SPI mode -// In both cases there are two "slots". Slot 0 on the SD/MMC controller is -// typically tied up with the flash interface in most ESP32 modules but in -// theory supports 1, 4 or 8-bit transfers. Slot 1 supports only 1 and 4-bit -// transfers. Only 1-bit is supported on the SPI interfaces. -// card = SDCard(slot=1, width=None, present_pin=None, wp_pin=None) +// +// SD/MMC or SPI mode is determined by the slot argument +// 0,1 is SD/MMC mode where supported. +// 2,3 is SPI mode where supported (1-bit only) +// +// Original ESP32 can't use 0 +// ESP32-C3/C6/etc can only use 2 (only one SPI bus, no SD/MMC controller) +// +// Consult machine.SDCard docs for more details. static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments @@ -183,8 +211,13 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args #endif ARG_freq, }; + #if SOC_SDMMC_HOST_SUPPORTED + static const int DEFAULT_SLOT = 1; + #else + static const int DEFAULT_SLOT = SD_SLOT_MAX; + #endif static const mp_arg_t allowed_args[] = { - { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SLOT} }, { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, { MP_QSTR_cd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_wp, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, @@ -226,15 +259,21 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args #endif int slot_num = arg_vals[ARG_slot].u_int; - if (slot_num < 0 || slot_num > 3) { - mp_raise_ValueError(MP_ERROR_TEXT("slot number must be between 0 and 3 inclusive")); + if (slot_num < SD_SLOT_MIN || slot_num > SD_SLOT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid slot number")); } + #if SOC_SDMMC_HOST_SUPPORTED // Slots 0 and 1 are native SD/MMC, slots 2 and 3 are SPI bool is_spi = (slot_num >= 2); + #else + bool is_spi = true; + #endif if (is_spi) { slot_num -= 2; + assert(slot_num < NUM_SD_SPI_BUS); } + // Verify valid argument combinations #if SOC_SDMMC_USE_GPIO_MATRIX if (is_spi && (arg_vals[ARG_cmd].u_obj != mp_const_none @@ -242,6 +281,13 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args mp_raise_ValueError(MP_ERROR_TEXT("invalid config: SPI slot with SDMMC pin arguments")); } #endif + #if SOC_SDMMC_HOST_SUPPORTED + if (!is_spi && (arg_vals[ARG_miso].u_obj != mp_const_none + || arg_vals[ARG_mosi].u_obj != mp_const_none + || arg_vals[ARG_cs].u_obj != mp_const_none)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid config: SDMMC slot with SPI pin arguments")); + } + #endif DEBUG_printf(" Setting up host configuration"); @@ -253,21 +299,17 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args if (is_spi) { sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); _temp_host.max_freq_khz = freq / 1000; + // SPI SDMMC sets the slot to the SPI host ID + _temp_host.slot = spi_dev_defaults[slot_num].host_id; self->host = _temp_host; - } else { + } + #if SOC_SDMMC_HOST_SUPPORTED + else { sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); _temp_host.max_freq_khz = freq / 1000; self->host = _temp_host; } - - if (is_spi) { - // Needs to match spi_dev_defaults above. - #if CONFIG_IDF_TARGET_ESP32 - self->host.slot = slot_num ? HSPI_HOST : VSPI_HOST; - #else - self->host.slot = slot_num ? SPI2_HOST : SPI3_HOST; - #endif - } + #endif DEBUG_printf(" Calling host.init()"); @@ -294,6 +336,15 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args SET_CONFIG_PIN(dev_config, gpio_cd, ARG_cd); SET_CONFIG_PIN(dev_config, gpio_wp, ARG_wp); + // On chips other than original ESP32 and S3, there are not + // always default SPI pins assigned + if (dev_config.gpio_cs == GPIO_NUM_NC + || bus_config.miso_io_num == GPIO_NUM_NC + || bus_config.mosi_io_num == GPIO_NUM_NC + || bus_config.sclk_io_num == GPIO_NUM_NC) { + mp_raise_ValueError(MP_ERROR_TEXT("SPI pin values required")); + } + DEBUG_printf(" Calling spi_bus_initialize()"); check_esp_err(spi_bus_initialize(spi_host_id, &bus_config, dma_channel)); @@ -309,7 +360,9 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args spi_bus_free(spi_host_id); mp_raise_ValueError(MP_ERROR_TEXT("SPI bus already in use")); } - } else { + } + #if SOC_SDMMC_HOST_SUPPORTED + else { // SD/MMC interface DEBUG_printf(" Setting up SDMMC slot configuration"); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); @@ -357,6 +410,7 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args DEBUG_printf(" Calling init_slot()"); check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); } + #endif // SOC_SDMMC_HOST_SUPPORTED DEBUG_printf(" Returning new card object: %p", self); return MP_OBJ_FROM_PTR(self); From 464121f30158e769b4bcd4e0187f9fc58b6e09d9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 18:17:02 +1100 Subject: [PATCH 0373/2098] esp32/boards: Enable machine.SDCard on all boards. This increases binary size by about 4KB on C3, probably a bit less on S2. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h | 2 -- ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h | 1 - ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.h | 1 - ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h | 2 -- ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h | 1 - ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h | 1 - ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h | 1 - ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h | 1 - ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h | 1 - ports/esp32/boards/UM_TINYC6/mpconfigboard.h | 1 - ports/esp32/boards/UM_TINYS2/mpconfigboard.h | 1 - 11 files changed, 13 deletions(-) diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h index a6cebdf6ee6..988c7db8a14 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.h @@ -3,7 +3,5 @@ #define MICROPY_HW_BOARD_NAME "ESP32C3 module" #define MICROPY_HW_MCU_NAME "ESP32C3" -#define MICROPY_HW_ENABLE_SDCARD (0) - // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h index be530ad3a87..d6cc9a6f678 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_HW_BOARD_NAME "ESP32C6 module" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_I2S (0) // Enable UART REPL for modules that have an external USB-UART and don't use native USB. diff --git a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.h index aaea41bcdec..9e03e8269b1 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.h @@ -2,7 +2,6 @@ #define MICROPY_HW_MCU_NAME "ESP32S2" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h index 46d720edc75..9eb0bada1ee 100644 --- a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h +++ b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.h @@ -2,8 +2,6 @@ #define MICROPY_HW_MCU_NAME "ESP32-C3FH4" #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-c3-mini" -#define MICROPY_HW_ENABLE_SDCARD (0) - #define MICROPY_HW_I2C0_SCL (10) #define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h index 9776b7b478a..08badc38457 100644 --- a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h +++ b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-s2-mini" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_I2C0_SCL (35) #define MICROPY_HW_I2C0_SDA (33) diff --git a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h index 9241280dec6..07db116c284 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h +++ b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-s2-pico" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_I2C0_SCL (9) #define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h index 9acab709685..656ec0bc6c3 100644 --- a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h +++ b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h @@ -1,7 +1,6 @@ #define MICROPY_HW_BOARD_NAME "M5Stack NanoC6" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_I2S (0) #define MICROPY_HW_I2C0_SCL (1) diff --git a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h index d8529b6342f..a2a00c0e46c 100644 --- a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h +++ b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "FeatherS2" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_I2C0_SCL (9) #define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h index e7e4d37ece9..bcc809895d1 100644 --- a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h +++ b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "FeatherS2-Neo" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_I2C0_SCL (9) #define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/UM_TINYC6/mpconfigboard.h b/ports/esp32/boards/UM_TINYC6/mpconfigboard.h index 1a3f1c904a7..09392d55594 100644 --- a/ports/esp32/boards/UM_TINYC6/mpconfigboard.h +++ b/ports/esp32/boards/UM_TINYC6/mpconfigboard.h @@ -1,7 +1,6 @@ #define MICROPY_HW_BOARD_NAME "Unexpected Maker TinyC6" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_I2S (0) #define MICROPY_HW_I2C0_SCL (7) diff --git a/ports/esp32/boards/UM_TINYS2/mpconfigboard.h b/ports/esp32/boards/UM_TINYS2/mpconfigboard.h index e0bde417c8a..ecfb0056b43 100644 --- a/ports/esp32/boards/UM_TINYS2/mpconfigboard.h +++ b/ports/esp32/boards/UM_TINYS2/mpconfigboard.h @@ -3,7 +3,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "TinyS2" #define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_HW_I2C0_SCL (9) #define MICROPY_HW_I2C0_SDA (8) From bfd974d65823c5ab4be1a2d0934d7392a5fa3069 Mon Sep 17 00:00:00 2001 From: garywill Date: Fri, 6 Dec 2024 13:20:34 +0800 Subject: [PATCH 0374/2098] esp32/machine_pwm: Correctly stop LEDC timer. The ESP32 PWM (LEDC) timer wasn't correctly stopped. `ledc_timer_rst()` is for resetting the timer counter to zero, not for stopping the timer. The correct way to stop a pwm timer is to pause it, then configure it with `deconfigure = true`. Signed-off-by: garywill --- ports/esp32/machine_pwm.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 6e3610b1566..7826769556c 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -156,7 +156,13 @@ static void pwm_deinit(int channel_idx) { int timer_idx = chans[channel_idx].timer_idx; if (timer_idx != -1) { if (!is_timer_in_use(channel_idx, timer_idx)) { - check_esp_err(ledc_timer_rst(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); + check_esp_err(ledc_timer_pause(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); + ledc_timer_config_t timer_config = { + .deconfigure = true, + .speed_mode = TIMER_IDX_TO_MODE(timer_idx), + .timer_num = TIMER_IDX_TO_TIMER(timer_idx), + }; + check_esp_err(ledc_timer_config(&timer_config)); // Flag it unused timers[chans[channel_idx].timer_idx].freq_hz = -1; } @@ -635,7 +641,13 @@ static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { if (!current_in_use) { // Free the old timer - check_esp_err(ledc_timer_rst(self->mode, self->timer)); + check_esp_err(ledc_timer_pause(self->mode, self->timer)); + ledc_timer_config_t timer_config = { + .deconfigure = true, + .speed_mode = self->mode, + .timer_num = self->timer, + }; + check_esp_err(ledc_timer_config(&timer_config)); // Flag it unused timers[current_timer_idx].freq_hz = -1; } From 4d2d60d6e1422dc5a66f15f7e48e3ebad2dfc41c Mon Sep 17 00:00:00 2001 From: Garry W <32130780+garywill@users.noreply.github.com> Date: Thu, 14 Nov 2024 04:19:31 +0000 Subject: [PATCH 0375/2098] esp32/machine_pin: Fix logic clearing USB_SERIAL_JTAG_USB_PAD_ENABLE. When we don't use USB JTAG, we want to use the two USB pins (D+/D-) as GPIO. So, do clear USB_SERIAL_JTAG_USB_PAD_ENABLE when USB SERIAL JTAG is not enabled Signed-off-by: Garry W <32130780+garywill@users.noreply.github.com> Signed-off-by: Angus Gratton --- ports/esp32/machine_pin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index b821abf1fc1..bb0d40c4d82 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -158,12 +158,12 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ } } - #if CONFIG_IDF_TARGET_ESP32C3 && MICROPY_HW_ESP_USB_SERIAL_JTAG + #if CONFIG_IDF_TARGET_ESP32C3 && !MICROPY_HW_ESP_USB_SERIAL_JTAG if (index == 18 || index == 19) { CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); } #endif - #if CONFIG_IDF_TARGET_ESP32C6 && CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED + #if CONFIG_IDF_TARGET_ESP32C6 && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED if (index == 12 || index == 13) { CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); } From 1d5dc723b40d978b84da2f9b1a48e880777385ea Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 15 Nov 2024 15:48:52 +1100 Subject: [PATCH 0376/2098] esp32/machine_pin: Fix availability of USB Serial/JTAG pins on ESP32-C6. Similar to parent commit, allow using USB Serial/JTAG pins for other purposes but only if this feature is disabled in the build config. Signed-off-by: Angus Gratton --- ports/esp32/machine_pin.c | 2 +- ports/esp32/machine_pin.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index bb0d40c4d82..d0c4ee1a7ec 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -163,7 +163,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); } #endif - #if CONFIG_IDF_TARGET_ESP32C6 && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED + #if CONFIG_IDF_TARGET_ESP32C6 && !MICROPY_HW_ESP_USB_SERIAL_JTAG if (index == 12 || index == 13) { CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); } diff --git a/ports/esp32/machine_pin.h b/ports/esp32/machine_pin.h index 3f94508e015..47f1ddebebf 100644 --- a/ports/esp32/machine_pin.h +++ b/ports/esp32/machine_pin.h @@ -108,7 +108,7 @@ #define MICROPY_HW_ENABLE_GPIO9 (1) #define MICROPY_HW_ENABLE_GPIO10 (1) #define MICROPY_HW_ENABLE_GPIO11 (1) -#if !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG +#if !MICROPY_HW_ESP_USB_SERIAL_JTAG #define MICROPY_HW_ENABLE_GPIO12 (1) #define MICROPY_HW_ENABLE_GPIO13 (1) #endif From e75ffc3bccb8b213704f0ddd2ce82731ab2069ba Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 11 Mar 2025 10:50:22 +0000 Subject: [PATCH 0377/2098] rp2/modrp2: Fix rp2.bootsel_button() function for RP2350. Co-authored-by: graham sanderson Signed-off-by: Phil Howard --- ports/rp2/modrp2.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ports/rp2/modrp2.c b/ports/rp2/modrp2.c index c90b6d58402..5a43c11e719 100644 --- a/ports/rp2/modrp2.c +++ b/ports/rp2/modrp2.c @@ -42,11 +42,17 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_network_country_obj); #endif +#define CS_PIN_INDEX 1 + +#if PICO_RP2040 +#define CS_BIT (1u << CS_PIN_INDEX) +#else +#define CS_BIT SIO_GPIO_HI_IN_QSPI_CSN_BITS +#endif + // Improved version of // https://github.com/raspberrypi/pico-examples/blob/master/picoboard/button/button.c static bool __no_inline_not_in_flash_func(bootsel_button)(void) { - const uint CS_PIN_INDEX = 1; - // Disable interrupts and the other core since they might be // executing code from flash and we are about to temporarily // disable flash access. @@ -65,7 +71,7 @@ static bool __no_inline_not_in_flash_func(bootsel_button)(void) { // The HI GPIO registers in SIO can observe and control the 6 QSPI pins. // The button pulls the QSPI_SS pin *low* when pressed. - bool button_state = !(sio_hw->gpio_hi_in & (1 << CS_PIN_INDEX)); + bool button_state = !(sio_hw->gpio_hi_in & CS_BIT); // Restore the QSPI_SS pin so we can use flash again. hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, From 9a070fee343bb1f96b908ce3bb3032a8ec86c904 Mon Sep 17 00:00:00 2001 From: Alex Brudner Date: Mon, 3 Mar 2025 17:09:56 -0700 Subject: [PATCH 0378/2098] rp2/boards/SPARKFUN_IOTREDBOARD_RP2350: Add support for IoT RedBoard. Signed-off-by: Alex Brudner Signed-off-by: Malcolm McKellips --- .../SPARKFUN_IOTREDBOARD_RP2350/board.json | 26 +++ .../SPARKFUN_IOTREDBOARD_RP2350/manifest.py | 7 + .../mpconfigboard.cmake | 20 ++ .../mpconfigboard.h | 61 ++++++ .../mpconfigvariant.cmake | 1 + .../mpconfigvariant_RISCV.cmake | 1 + .../SPARKFUN_IOTREDBOARD_RP2350/pins.csv | 70 +++++++ .../sparkfun_iotredboard_rp2350.h | 183 ++++++++++++++++++ 8 files changed, 369 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/board.json create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/manifest.py create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/pins.csv create mode 100644 ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/sparkfun_iotredboard_rp2350.h diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/board.json b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/board.json new file mode 100644 index 00000000000..a0dd4d835a1 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/board.json @@ -0,0 +1,26 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Dual-core", + "External Flash", + "USB-C", + "WiFi", + "External RAM", + "microSD", + "RGB LED", + "JST-SH", + "Battery Charging" + ], + "images": [ + "27708-IoT-RedBoard-RP2350-Feature.jpg" + ], + "mcu": "rp2350", + "product": "SparkFun IoT RedBoard RP2350", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-iot-redboard-rp2350.html", + "vendor": "SparkFun" +} diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/manifest.py b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/manifest.py new file mode 100644 index 00000000000..b1670d68044 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/manifest.py @@ -0,0 +1,7 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +require("aioble") + +require("sdcard") diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.cmake new file mode 100644 index 00000000000..beefc90db65 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.cmake @@ -0,0 +1,20 @@ +# cmake file for SparkFun IoT RedBoard RP2350 + +# TODO: DELETE THIS LINE WHEN SUBMODULED PICO-SDK INCLUDES THIS BOARD +set(PICO_BOARD_HEADER_DIRS ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) + +set(PICO_BOARD "sparkfun_iotredboard_rp2350") +set(PICO_PLATFORM "rp2350") + +set(PICO_NUM_GPIOS 48) + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.h new file mode 100644 index 00000000000..34d08b0bec4 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigboard.h @@ -0,0 +1,61 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun IoT RedBoard RP2350" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1536 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "IoTRedBoardRP2350" + +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +// USB VID/PID +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0047) + +// UART0 +#define MICROPY_HW_UART0_TX (0) +#define MICROPY_HW_UART0_RX (1) +#define MICROPY_HW_UART0_CTS (30) +#define MICROPY_HW_UART0_RTS (31) + +// UART1 +#define MICROPY_HW_UART1_TX (40) +#define MICROPY_HW_UART1_RX (41) +#define MICROPY_HW_UART1_CTS (42) +#define MICROPY_HW_UART1_RTS (43) + +// I2C0 +#define MICROPY_HW_I2C0_SCL (5) +#define MICROPY_HW_I2C0_SDA (4) + +// I2C1 +#define MICROPY_HW_I2C1_SCL (31) +#define MICROPY_HW_I2C1_SDA (30) + +// SPI0 +#define MICROPY_HW_SPI0_SCK (22) +#define MICROPY_HW_SPI0_MOSI (23) +#define MICROPY_HW_SPI0_MISO (20) + +// SD Card/SPI1 +#define MICROPY_HW_SPI1_SCK (10) +#define MICROPY_HW_SPI1_MOSI (11) +#define MICROPY_HW_SPI1_MISO (8) + +// PSRAM +#define MICROPY_HW_PSRAM_CS_PIN (47) +#define MICROPY_HW_ENABLE_PSRAM (1) + +// #include "enable_cyw43.h" + +// For debugging mbedtls - also set +// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose +// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant.cmake b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant.cmake new file mode 100644 index 00000000000..6fe039ba51b --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..65a97fc3350 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/pins.csv b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/pins.csv new file mode 100644 index 00000000000..d8d98e1ada9 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/pins.csv @@ -0,0 +1,70 @@ +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 +GP23,GPIO23 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +GP29,GPIO29 +GP30,GPIO30 +GP31,GPIO31 +GP32,GPIO32 +GP33,GPIO33 +GP34,GPIO34 +GP35,GPIO35 +GP39,GPIO39 +GP40,GPIO40 +GP41,GPIO41 +GP42,GPIO42 +GP43,GPIO43 +GP44,GPIO44 +GP45,GPIO45 +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 +WL_LED,EXT_GPIO0 +SD_DET,GPIO2 +RGB_LED,GPIO3 +NEOPIXEL,GPIO3 +B_ALRT,GPIO6 +PERIPH_POWER,GPIO7 +HSTX0,GPIO12 +HSTX1,GPIO13 +HSTX2,GPIO14 +HSTX3,GPIO15 +HSTX4,GPIO16 +HSTX5,GPIO17 +HSTX6,GPIO18 +HSTX7,GPIO19 +LED,GPIO25 +STAT,GPIO25 +SRC_5V,GPIO26 +BATT_PWR,GPIO27 +USER_SW,GPIO39 +A0,GPIO40 +A1,GPIO41 +A2,GPIO42 +A3,GPIO43 +A4,GPIO44 +A5,GPIO45 diff --git a/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/sparkfun_iotredboard_rp2350.h b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/sparkfun_iotredboard_rp2350.h new file mode 100644 index 00000000000..ac0972eb226 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTREDBOARD_RP2350/sparkfun_iotredboard_rp2350.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// ----------------------------------------------------- +// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO +// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES +// ----------------------------------------------------- +// Board definition for the SparkFun IoT RedBoard - RP2350 +// +// This header may be included by other board headers as "boards/sparkfun_iotredboard_rp2350.h" + +// pico_cmake_set PICO_PLATFORM=rp2350 +// pico_cmake_set PICO_CYW43_SUPPORTED = 1 + +#ifndef _BOARDS_SPARKFUN_IOTREDBOARD_RP2350_H +#define _BOARDS_SPARKFUN_IOTREDBOARD_RP2350_H + +// For board detection +#define SPARKFUN_IOTREDBOARD_RP2350 + +// --- RP2350 VARIANT --- +#define PICO_RP2350A 0 // 1 for RP2350A, 0 for RP2350B + +// --- BOARD SPECIFIC --- +#define SPARKFUN_IOTREDBOARD_RP2350_USER_SW_PIN 39 +#define SPARKFUN_IOTREDBOARD_RP2350_PSRAM_CS_PIN 47 + + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 0 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 1 +#endif + +// --- LED --- +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 25 +#endif + +#ifndef PICO_DEFAULT_WS2812_PIN +#define PICO_DEFAULT_WS2812_PIN 3 +#endif + +// --- I2C --- Qwiic connector is on these pins +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 4 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 5 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 22 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 23 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 20 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 21 +#endif + +// --- FLASH --- + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +// pico_cmake_set_default PICO_FLASH_SIZE_BYTES = (16 * 1024 * 1024) +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) +#endif + +// The IoT RedBoard has an SD Card. +#ifndef PICO_SD_CLK_PIN +#define PICO_SD_CLK_PIN 10 +#endif +#ifndef PICO_SD_CMD_PIN +#define PICO_SD_CMD_PIN 11 +#endif +#ifndef PICO_SD_DAT0_PIN +#define PICO_SD_DAT0_PIN 8 +#endif +#ifndef PICO_SD_DAT3_PIN +#define PICO_SD_DAT3_PIN 9 // DAT3 of the SD card is the chip select pin +#endif +#ifndef PICO_SD_DAT_PIN_COUNT +#define PICO_SD_DAT_PIN_COUNT 1 +#endif + +// The GPIO Pin used to monitor VSYS. Typically you would use this with ADC. +// There is an example in adc/read_vsys in pico-examples. +#ifndef PICO_VSYS_PIN +#define PICO_VSYS_PIN 46 +#endif + +// pico_cmake_set_default PICO_RP2350_A2_SUPPORTED = 1 +#ifndef PICO_RP2350_A2_SUPPORTED +#define PICO_RP2350_A2_SUPPORTED 1 +#endif + +// Bootloader activity LED in double reset mode. +#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED +#define PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED PICO_DEFAULT_LED_PIN +#endif + +// Bootloader activity LED in USB reset mode. +#ifndef PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED +#define PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED PICO_DEFAULT_LED_PIN +#endif + +// --- CYW43 --- + +#ifndef CYW43_WL_GPIO_COUNT +#define CYW43_WL_GPIO_COUNT 3 +#endif + +#ifndef CYW43_WL_GPIO_LED_PIN +#define CYW43_WL_GPIO_LED_PIN 0 +#endif + +// If CYW43_WL_GPIO_VBUS_PIN is defined then a CYW43 GPIO has to be used to read VBUS. +// This can be passed to cyw43_arch_gpio_get to determine if the device is battery powered. +// PICO_VBUS_PIN and CYW43_WL_GPIO_VBUS_PIN should not both be defined. +#ifndef CYW43_WL_GPIO_VBUS_PIN +#define CYW43_WL_GPIO_VBUS_PIN 2 +#endif + +// cyw43 SPI pins can't be changed at runtime +#ifndef CYW43_PIN_WL_DYNAMIC +#define CYW43_PIN_WL_DYNAMIC 0 +#endif + +// gpio pin to power up the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_REG_ON +#define CYW43_DEFAULT_PIN_WL_REG_ON 24u +#endif + +// gpio pin for spi data out to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_DATA_OUT +#define CYW43_DEFAULT_PIN_WL_DATA_OUT 38u +#endif + +// gpio pin for spi data in from the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_DATA_IN +#define CYW43_DEFAULT_PIN_WL_DATA_IN 38u +#endif + +// gpio (irq) pin for the irq line from the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_HOST_WAKE +#define CYW43_DEFAULT_PIN_WL_HOST_WAKE 38u +#endif + +// gpio pin for the spi clock line to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_CLOCK +#define CYW43_DEFAULT_PIN_WL_CLOCK 37u +#endif + +// gpio pin for the spi chip select to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_CS +#define CYW43_DEFAULT_PIN_WL_CS 36u +#endif + +#endif // _BOARDS_SPARKFUN_IOTREDBOARD_RP2350_H From 8dcf9290f85c98695866e6656bf9a2653c490828 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sat, 8 Mar 2025 19:24:09 +1100 Subject: [PATCH 0379/2098] rp2/boards/WEACTSTUDIO_RP2350B_CORE: Add WeAct Studio RP2350B Core. Signed-off-by: Matt Trentini --- .../WEACTSTUDIO_RP2350B_CORE/board.json | 23 ++++++++ .../WEACTSTUDIO_RP2350B_CORE/manifest.py | 1 + .../mpconfigboard.cmake | 13 +++++ .../WEACTSTUDIO_RP2350B_CORE/mpconfigboard.h | 16 +++++ .../mpconfigvariant.cmake | 2 + .../mpconfigvariant_RISCV.cmake | 2 + .../boards/WEACTSTUDIO_RP2350B_CORE/pins.csv | 48 +++++++++++++++ .../weactstudio_rp2350b_core.h | 58 +++++++++++++++++++ 8 files changed, 163 insertions(+) create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/board.json create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.cmake create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.h create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/pins.csv create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/weactstudio_rp2350b_core.h diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/board.json b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/board.json new file mode 100644 index 00000000000..bd280820fd3 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "External RAM", + "USB-C" + ], + "images": [ + "weactstudio_rp2350_core.png" + ], + "mcu": "rp2350", + "product": "RP2350B Core", + "thumbnail": "", + "url": "https://github.com/WeActStudio/WeActStudio.RP2350BCoreBoard", + "variants": { + "RISCV": "RISC V" + }, + "vendor": "WeAct Studio" +} diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py new file mode 100644 index 00000000000..832942f0526 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py @@ -0,0 +1 @@ +include("$(PORT_DIR)/boards/manifest.py") diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.cmake b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.cmake new file mode 100644 index 00000000000..f03452c6273 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.cmake @@ -0,0 +1,13 @@ +# CMake file for WeAct Studio RP2350B Core + +# The Core is powered by an RP2350B with 48 GPIOs +set(PICO_NUM_GPIOS 48) + +# The WeAct Studio boards don't have official pico-sdk support. +# So, add this board directory to the header search path and define PICO_BOARD +# which will instruct pico-sdk to look for weactstudio_rp2350b_core.h +list(APPEND PICO_BOARD_HEADER_DIRS ${MICROPY_BOARD_DIR}) +set(PICO_BOARD "weactstudio_rp2350b_core") + +# Freeze manifest and modules +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.h b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.h new file mode 100644 index 00000000000..1f0963db85c --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigboard.h @@ -0,0 +1,16 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "WeAct Studio RP2350B Core" +#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - (2 * 1024 * 1024)) + +// TODO: Split PSRAM option off as a variant +#define MICROPY_HW_PSRAM_CS_PIN (0) +#define MICROPY_HW_ENABLE_PSRAM (0) + +// Override machine_uart.c definitions. +// See weactstudio_rp2350b.h and note that the PICO_DEFAULT_UART configuration +// is not currently referenced in machine_uart.c. +#define MICROPY_HW_UART0_TX (16) +#define MICROPY_HW_UART0_RX (17) +#define MICROPY_HW_UART0_CTS (-1) +#define MICROPY_HW_UART0_RTS (-1) diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant.cmake b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant.cmake new file mode 100644 index 00000000000..531c9152440 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant.cmake @@ -0,0 +1,2 @@ +# Set the ARM-based RP2350 platform +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..9f62e459aa0 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/mpconfigvariant_RISCV.cmake @@ -0,0 +1,2 @@ +# Set the RISC-V-based RP2350 platform +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/pins.csv b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/pins.csv new file mode 100644 index 00000000000..50d0de8560a --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/pins.csv @@ -0,0 +1,48 @@ +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 +GP29,GPIO29 +GP30,GPIO30 +GP31,GPIO31 +GP32,GPIO32 +GP33,GPIO33 +GP34,GPIO34 +GP35,GPIO35 +GP36,GPIO36 +GP37,GPIO37 +GP38,GPIO38 +GP39,GPIO39 +GP40,GPIO40 +GP41,GPIO41 +GP42,GPIO42 +GP43,GPIO43 +GP44,GPIO44 +GP45,GPIO45 +GP46,GPIO46 +GP47,GPIO47 +KEY,GPIO23 +LED,GPIO25 diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/weactstudio_rp2350b_core.h b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/weactstudio_rp2350b_core.h new file mode 100644 index 00000000000..bb00ff28275 --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/weactstudio_rp2350b_core.h @@ -0,0 +1,58 @@ +#ifndef _BOARDS_WEACTSTUDIO_RP2350B_CORE_COMMON_H +#define _BOARDS_WEACTSTUDIO_RP2350B_CORE_COMMON_H + +// --- LED --- +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 25 +#endif + +// Note: Avoid using Pin 0 for any default peripheral since it can be used for +// extra PSRAM/flash + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 12 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 13 +#endif + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 8 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 9 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 18 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 19 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 16 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 17 +#endif + +// Flash configuration +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +#endif From ac30dcb20cbe3cab73133623adc3a0dfff77dd20 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 10 Mar 2025 17:34:47 -0600 Subject: [PATCH 0380/2098] rp2/boards/SPARKFUN_XRP_CONTROLLER: Add SparkFun XRP Controller. Signed-off-by: Dryw Wade --- .../boards/SPARKFUN_XRP_CONTROLLER/board.json | 25 ++++ .../SPARKFUN_XRP_CONTROLLER/manifest.py | 6 + .../mpconfigboard.cmake | 20 +++ .../SPARKFUN_XRP_CONTROLLER/mpconfigboard.h | 52 +++++++ .../boards/SPARKFUN_XRP_CONTROLLER/pins.csv | 84 +++++++++++ .../sparkfun_xrp_controller.h | 133 ++++++++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/manifest.py create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.h create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/pins.csv create mode 100644 ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/sparkfun_xrp_controller.h diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json new file mode 100644 index 00000000000..92486564b10 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json @@ -0,0 +1,25 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Dual-core", + "External Flash", + "External RAM", + "IMU", + "JST-SH", + "RGB LED", + "USB-C", + "WiFi" + ], + "images": [ + "26619-XRP-Controller-Board-Feature.jpg" + ], + "mcu": "rp2350", + "product": "XRP Controller", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller.html", + "vendor": "Sparkfun" +} diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/manifest.py b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/manifest.py new file mode 100644 index 00000000000..4e38f09cdee --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +# Bluetooth +require("aioble") diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.cmake new file mode 100644 index 00000000000..2af12f81382 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.cmake @@ -0,0 +1,20 @@ +# cmake file for SparkFun XRP Controller + +# TODO: DELETE THIS LINE WHEN SUBMODULED PICO-SDK INCLUDES THIS BOARD +set(PICO_BOARD_HEADER_DIRS ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) + +set(PICO_BOARD "sparkfun_xrp_controller") +set(PICO_PLATFORM "rp2350") + +set(PICO_NUM_GPIOS 48) + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.h new file mode 100644 index 00000000000..13f04b92d79 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/mpconfigboard.h @@ -0,0 +1,52 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun XRP Controller" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1536 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "XRP" + +// CYW43 driver configuration. +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +// For debugging mbedtls - also set +// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose +// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) + +#define MICROPY_HW_USB_VID (0x1B4F) +#define MICROPY_HW_USB_PID (0x0046) + +#define MICROPY_HW_I2C0_SDA (4) +#define MICROPY_HW_I2C0_SCL (5) + +#define MICROPY_HW_I2C1_SDA (38) +#define MICROPY_HW_I2C1_SCL (39) + +#define MICROPY_HW_SPI0_SCK (18) +#define MICROPY_HW_SPI0_MOSI (19) +#define MICROPY_HW_SPI0_MISO (16) + +#define MICROPY_HW_SPI1_SCK (14) +#define MICROPY_HW_SPI1_MOSI (15) +#define MICROPY_HW_SPI1_MISO (12) + +#define MICROPY_HW_UART0_TX (12) +#define MICROPY_HW_UART0_RX (13) +#define MICROPY_HW_UART0_CTS (14) +#define MICROPY_HW_UART0_RTS (15) + +#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 (47) +#define MICROPY_HW_ENABLE_PSRAM (1) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/pins.csv b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/pins.csv new file mode 100644 index 00000000000..abf77af56c0 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/pins.csv @@ -0,0 +1,84 @@ +# XRP default pin names +MOTOR_L_IN_1,GPIO34 +MOTOR_L_IN_2,GPIO35 +MOTOR_R_IN_1,GPIO32 +MOTOR_R_IN_2,GPIO33 +MOTOR_3_IN_1,GPIO20 +MOTOR_3_IN_2,GPIO21 +MOTOR_4_IN_1,GPIO10 +MOTOR_4_IN_2,GPIO11 +MOTOR_L_ENCODER_A,GPIO30 +MOTOR_L_ENCODER_B,GPIO31 +MOTOR_R_ENCODER_A,GPIO24 +MOTOR_R_ENCODER_B,GPIO25 +MOTOR_3_ENCODER_A,GPIO22 +MOTOR_3_ENCODER_B,GPIO23 +MOTOR_4_ENCODER_A,GPIO2 +MOTOR_4_ENCODER_B,GPIO3 +MOTOR_L_CURRENT,GPIO40 +MOTOR_R_CURRENT,GPIO43 +MOTOR_3_CURRENT,GPIO41 +MOTOR_4_CURRENT,GPIO42 +SERVO_1,GPIO6 +SERVO_2,GPIO9 +SERVO_3,GPIO7 +SERVO_4,GPIO8 +I2C_SDA_0,GPIO4 +I2C_SCL_0,GPIO5 +I2C_SDA_1,GPIO38 +I2C_SCL_1,GPIO39 +DISTANCE_TRIGGER,GPIO0 +DISTANCE_ECHO,GPIO1 +LINE_L,GPIO44 +LINE_R,GPIO45 +BOARD_VIN_MEASURE,GPIO46 +BOARD_USER_BUTTON,GPIO36 +BOARD_NEOPIXEL,GPIO37 +BOARD_LED,EXT_GPIO0 + +# XRP alternate pin names +ML_IN_1,GPIO34 +ML_IN_2,GPIO35 +MR_IN_1,GPIO32 +MR_IN_2,GPIO33 +M3_IN_1,GPIO20 +M3_IN_2,GPIO21 +M4_IN_1,GPIO10 +M4_IN_2,GPIO11 +ML_ENC_A,GPIO30 +ML_ENC_B,GPIO31 +MR_ENC_A,GPIO24 +MR_ENC_B,GPIO25 +M3_ENC_A,GPIO22 +M3_ENC_B,GPIO23 +M4_ENC_A,GPIO2 +M4_ENC_B,GPIO3 +ML_CUR,GPIO40 +MR_CUR,GPIO43 +M3_CUR,GPIO41 +M4_CUR,GPIO42 +S1,GPIO6 +S2,GPIO9 +S3,GPIO7 +S4,GPIO8 +SDA_0,GPIO4 +SCL_0,GPIO5 +SDA_1,GPIO38 +SCL_1,GPIO39 +RANGE_TRIGGER,GPIO0 +RANGE_ECHO,GPIO1 +REFLECTANCE_L,GPIO44 +REFLECTANCE_R,GPIO45 +BRD_VIN,GPIO46 +BRD_USR_BTN,GPIO36 +BRD_RGB_LED,GPIO37 +BRD_LED,EXT_GPIO0 + +# LED default names +LED,EXT_GPIO0 +NEOPIXEL,GPIO37 + +# Radio GPIO pins +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/sparkfun_xrp_controller.h b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/sparkfun_xrp_controller.h new file mode 100644 index 00000000000..70c2f13c753 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/sparkfun_xrp_controller.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// ----------------------------------------------------- +// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO +// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES +// ----------------------------------------------------- +// Board definition for the SparkFun XRP Controller +// +// This header may be included by other board headers as "boards/sparkfun_xrp_controller.h" + +// pico_cmake_set PICO_PLATFORM=rp2350 +// pico_cmake_set PICO_CYW43_SUPPORTED = 1 + +#ifndef _BOARDS_SPARKFUN_XRP_CONTROLLER_H +#define _BOARDS_SPARKFUN_XRP_CONTROLLER_H + +// For board detection +#define SPARKFUN_XRP_CONTROLLER + +// --- RP2350 VARIANT --- +#define PICO_RP2350A 0 + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 12 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 13 +#endif + +// --- LED --- +// no PICO_DEFAULT_LED_PIN - LED is on Wireless chip +#ifndef PICO_DEFAULT_WS2812_PIN +#define PICO_DEFAULT_WS2812_PIN 37 +#endif + +// --- I2C --- Qwiic connector is on these pins +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 4 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 5 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 18 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 19 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 16 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 17 +#endif + +// --- FLASH --- + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +// pico_cmake_set_default PICO_FLASH_SIZE_BYTES = (16 * 1024 * 1024) +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) +#endif + +#ifndef CYW43_WL_GPIO_COUNT +#define CYW43_WL_GPIO_COUNT 3 +#endif + +#ifndef CYW43_WL_GPIO_LED_PIN +#define CYW43_WL_GPIO_LED_PIN 0 +#endif + +// pico_cmake_set_default PICO_RP2350_A2_SUPPORTED = 1 +#ifndef PICO_RP2350_A2_SUPPORTED +#define PICO_RP2350_A2_SUPPORTED 1 +#endif + +// cyw43 SPI pins can't be changed at runtime +#ifndef CYW43_PIN_WL_DYNAMIC +#define CYW43_PIN_WL_DYNAMIC 0 +#endif + +// gpio pin to power up the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_REG_ON +#define CYW43_DEFAULT_PIN_WL_REG_ON 26u +#endif + +// gpio pin for spi data out to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_DATA_OUT +#define CYW43_DEFAULT_PIN_WL_DATA_OUT 29u +#endif + +// gpio pin for spi data in from the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_DATA_IN +#define CYW43_DEFAULT_PIN_WL_DATA_IN 29u +#endif + +// gpio (irq) pin for the irq line from the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_HOST_WAKE +#define CYW43_DEFAULT_PIN_WL_HOST_WAKE 29u +#endif + +// gpio pin for the spi clock line to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_CLOCK +#define CYW43_DEFAULT_PIN_WL_CLOCK 28u +#endif + +// gpio pin for the spi chip select to the cyw43 chip +#ifndef CYW43_DEFAULT_PIN_WL_CS +#define CYW43_DEFAULT_PIN_WL_CS 27u +#endif + +#endif From 2264340559ed9139658e8d78a6a714ec27c92142 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 10 Mar 2025 17:35:58 -0600 Subject: [PATCH 0381/2098] rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA: Fix XRP Controller Beta URL. Signed-off-by: Dryw Wade --- ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json index cece5743265..b7dbd016007 100644 --- a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json @@ -18,6 +18,6 @@ "mcu": "rp2040", "product": "XRP Controller (Beta)", "thumbnail": "", - "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller.html", + "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller-beta.html", "vendor": "Sparkfun" } From 416c6cf0c861da94d679f5aa739acf167165c2a4 Mon Sep 17 00:00:00 2001 From: Lesords <2385342343@qq.com> Date: Tue, 26 Nov 2024 02:25:37 +0000 Subject: [PATCH 0382/2098] rp2/boards/SEEED_XIAO_RP2350: Add new Seeed XIAO board definition. Signed-off-by: Lesords <2385342343@qq.com> --- ports/rp2/boards/SEEED_XIAO_RP2350/board.json | 24 +++++++++++++++++++ .../SEEED_XIAO_RP2350/mpconfigboard.cmake | 5 ++++ .../boards/SEEED_XIAO_RP2350/mpconfigboard.h | 3 +++ .../SEEED_XIAO_RP2350/mpconfigvariant.cmake | 1 + .../mpconfigvariant_RISCV.cmake | 1 + ports/rp2/boards/SEEED_XIAO_RP2350/pins.csv | 24 +++++++++++++++++++ 6 files changed, 58 insertions(+) create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/board.json create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.h create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2350/pins.csv diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/board.json b/ports/rp2/boards/SEEED_XIAO_RP2350/board.json new file mode 100644 index 00000000000..9198b17dbc9 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/board.json @@ -0,0 +1,24 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "Dual-core", + "External Flash", + "RGB LED", + "USB" + ], + "images": [ + "xiao_rp2350-font.jpg" + ], + "mcu": "rp2350", + "product": "XIAO RP2350", + "thumbnail": "", + "url": "https://www.seeedstudio.com/Seeed-XIAO-RP2350-p-5944.html", + "variants": { + "RISCV": "RISC-V CPU mode" + }, + "vendor": "Seeed Studio" +} diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.cmake new file mode 100644 index 00000000000..a22ea4a5c9d --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.cmake @@ -0,0 +1,5 @@ +# cmake file for Seeed XIAO RP2350 +set(PICO_BOARD "seeed_xiao_rp2350") + +# To change the gpio count for QFN-80 +# set(PICO_NUM_GPIOS 48) diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.h b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.h new file mode 100644 index 00000000000..ea7e7437567 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Seeed XIAO RP2350" +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - 1024 * 1024) diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant.cmake b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant.cmake new file mode 100644 index 00000000000..6fe039ba51b --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..65a97fc3350 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/SEEED_XIAO_RP2350/pins.csv b/ports/rp2/boards/SEEED_XIAO_RP2350/pins.csv new file mode 100644 index 00000000000..34b71a57c62 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2350/pins.csv @@ -0,0 +1,24 @@ +D0,GPIO26 +D1,GPIO27 +D2,GPIO28 +D3,GPIO5 +D4,GPIO6 +D5,GPIO7 +D6,GPIO0 +D7,GPIO1 +D8,GPIO2 +D9,GPIO4 +D10,GPIO3 +D11,GPIO21 +D12,GPIO20 +D13,GPIO17 +D14,GPIO16 +D15,GPIO11 +D16,GPIO12 +D17,GPIO10 +D18,GPIO9 +LED,GPIO25 +NEOPIXEL_POWER,GPIO23 +NEOPIXEL,GPIO22 +BAT_ADC_EN,GPIO19 +BAT_ADC,GPIO29 From bf9cdd21891d30807ad6e0e229774d589cd5709f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 13:23:13 +1100 Subject: [PATCH 0383/2098] stm32: Rename ROMFS partition config variables to start at index 0. Change ROMFS partition configuration variables to use index 0 as the starting partition number (instead of index 1). Reasons to do this: - `vfs.rom_ioctl()` numbers the partitions starting from 0 - `mpremote romfs -p ` numbers the partitions starting from 0 Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 4 ++-- ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 2 +- ports/stm32/boards/PYBD_SF6/f767.ld | 4 ++-- ports/stm32/boards/PYBD_SF6/mpconfigboard.h | 2 +- ports/stm32/mpconfigboard_common.h | 10 +++++----- ports/stm32/vfs_rom_ioctl.c | 20 ++++++++++---------- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index 05126233ec7..f5e6769834a 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -41,8 +41,8 @@ _heap_start = _ebss; /* heap starts just after statically allocated memory */ _heap_end = _sstack; /* ROMFS location */ -_micropy_hw_romfs_part1_start = ORIGIN(FLASH_ROMFS); -_micropy_hw_romfs_part1_size = LENGTH(FLASH_ROMFS); +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); /* Define output sections */ SECTIONS diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index e8923bf9b66..3ae11125d8b 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -67,7 +67,7 @@ void board_sleep(int value); // ROMFS config #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) #define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev2.spiflash) -#define MICROPY_HW_ROMFS_ENABLE_PART1 (1) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) // SPI flash #1, for R/W storage #define MICROPY_HW_SOFTQSPI_SCK_LOW(self) (GPIOE->BSRR = (0x10000 << 11)) diff --git a/ports/stm32/boards/PYBD_SF6/f767.ld b/ports/stm32/boards/PYBD_SF6/f767.ld index 68da1f19d40..0ee6cf76355 100644 --- a/ports/stm32/boards/PYBD_SF6/f767.ld +++ b/ports/stm32/boards/PYBD_SF6/f767.ld @@ -38,7 +38,7 @@ _heap_start = _ebss; /* heap starts just after statically allocated memory */ _heap_end = _sstack; /* ROMFS location */ -_micropy_hw_romfs_part1_start = ORIGIN(FLASH_ROMFS); -_micropy_hw_romfs_part1_size = LENGTH(FLASH_ROMFS); +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); INCLUDE common_bl.ld diff --git a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h index 526a1111779..45261523bda 100644 --- a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h @@ -48,7 +48,7 @@ // ROMFS config #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) #define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev2.spiflash) -#define MICROPY_HW_ROMFS_ENABLE_PART1 (1) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) // Extra UART config #define MICROPY_HW_UART7_TX (pyb_pin_W16) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 2a650077f3a..e01a4d4b87e 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -77,16 +77,16 @@ #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (0) #endif +// Whether to enable ROMFS partition 0. +#ifndef MICROPY_HW_ROMFS_ENABLE_PART0 +#define MICROPY_HW_ROMFS_ENABLE_PART0 (0) +#endif + // Whether to enable ROMFS partition 1. #ifndef MICROPY_HW_ROMFS_ENABLE_PART1 #define MICROPY_HW_ROMFS_ENABLE_PART1 (0) #endif -// Whether to enable ROMFS partition 2. -#ifndef MICROPY_HW_ROMFS_ENABLE_PART2 -#define MICROPY_HW_ROMFS_ENABLE_PART2 (0) -#endif - // Whether to enable storage on the internal flash of the MCU #ifndef MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1) diff --git a/ports/stm32/vfs_rom_ioctl.c b/ports/stm32/vfs_rom_ioctl.c index 5024d44e5ad..1fa4408c537 100644 --- a/ports/stm32/vfs_rom_ioctl.c +++ b/ports/stm32/vfs_rom_ioctl.c @@ -36,6 +36,13 @@ #if MICROPY_VFS_ROM_IOCTL +#if MICROPY_HW_ROMFS_ENABLE_PART0 && !defined(MICROPY_HW_ROMFS_PART0_START) +#define MICROPY_HW_ROMFS_PART0_START (uintptr_t)(&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_PART0_SIZE (uintptr_t)(&_micropy_hw_romfs_part0_size) +extern uint8_t _micropy_hw_romfs_part0_start; +extern uint8_t _micropy_hw_romfs_part0_size; +#endif + #if MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) #define MICROPY_HW_ROMFS_PART1_START (uintptr_t)(&_micropy_hw_romfs_part1_start) #define MICROPY_HW_ROMFS_PART1_SIZE (uintptr_t)(&_micropy_hw_romfs_part1_size) @@ -43,22 +50,15 @@ extern uint8_t _micropy_hw_romfs_part1_start; extern uint8_t _micropy_hw_romfs_part1_size; #endif -#if MICROPY_HW_ROMFS_ENABLE_PART2 && !defined(MICROPY_HW_ROMFS_PART2_START) -#define MICROPY_HW_ROMFS_PART2_START (uintptr_t)(&_micropy_hw_romfs_part2_start) -#define MICROPY_HW_ROMFS_PART2_SIZE (uintptr_t)(&_micropy_hw_romfs_part2_size) -extern uint8_t _micropy_hw_romfs_part2_start; -extern uint8_t _micropy_hw_romfs_part2_size; -#endif - #define ROMFS_MEMORYVIEW(base, size) {{&mp_type_memoryview}, 'B', 0, (size), (void *)(base)} static const mp_obj_array_t romfs_obj_table[] = { + #if MICROPY_HW_ROMFS_ENABLE_PART0 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART0_START, MICROPY_HW_ROMFS_PART0_SIZE), + #endif #if MICROPY_HW_ROMFS_ENABLE_PART1 ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART1_START, MICROPY_HW_ROMFS_PART1_SIZE), #endif - #if MICROPY_HW_ROMFS_ENABLE_PART2 - ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART2_START, MICROPY_HW_ROMFS_PART2_SIZE), - #endif }; mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { From d5aeca2e83a03657ee2b5b21225d5a10aba10f56 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 13:23:50 +1100 Subject: [PATCH 0384/2098] esp8266: Rename ROMFS partition config variables to include "part0". For consistency with the stm32 port. Signed-off-by: Damien George --- ports/esp8266/boards/esp8266_2MiB_ROMFS.ld | 4 ++-- ports/esp8266/modesp.c | 4 ++-- ports/esp8266/vfs_rom_ioctl.c | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld b/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld index 6020c3d485d..ce057981aba 100644 --- a/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld +++ b/ports/esp8266/boards/esp8266_2MiB_ROMFS.ld @@ -17,8 +17,8 @@ MEMORY } /* define ROMFS extents */ -_micropy_hw_romfs_start = ORIGIN(FLASH_ROMFS); -_micropy_hw_romfs_size = LENGTH(FLASH_ROMFS); +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); /* define common sections and symbols */ INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c index c88022945cf..e1f9d396875 100644 --- a/ports/esp8266/modesp.c +++ b/ports/esp8266/modesp.c @@ -165,13 +165,13 @@ static MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); extern byte _firmware_size[]; #if MICROPY_VFS_ROM_IOCTL -extern uint8_t _micropy_hw_romfs_size; +extern uint8_t _micropy_hw_romfs_part0_size; #endif static mp_obj_t esp_flash_user_start(void) { uint32_t flash_user_start = (uint32_t)_firmware_size; #if MICROPY_VFS_ROM_IOCTL - flash_user_start += (uint32_t)&_micropy_hw_romfs_size; + flash_user_start += (uint32_t)&_micropy_hw_romfs_part0_size; #endif return MP_OBJ_NEW_SMALL_INT(flash_user_start); } diff --git a/ports/esp8266/vfs_rom_ioctl.c b/ports/esp8266/vfs_rom_ioctl.c index c7528375bec..eb260cb50f4 100644 --- a/ports/esp8266/vfs_rom_ioctl.c +++ b/ports/esp8266/vfs_rom_ioctl.c @@ -35,13 +35,13 @@ #define FLASH_MEM_BASE (0x40200000) #define FLASH_PAGE_SIZE (4096) -#define MICROPY_HW_ROMFS_BASE (uintptr_t)(&_micropy_hw_romfs_start) -#define MICROPY_HW_ROMFS_BYTES (uintptr_t)(&_micropy_hw_romfs_size) +#define MICROPY_HW_ROMFS_BASE (uintptr_t)(&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_BYTES (uintptr_t)(&_micropy_hw_romfs_part0_size) #define ROMFS_SPI_FLASH_OFFSET (MICROPY_HW_ROMFS_BASE - FLASH_MEM_BASE) -extern uint8_t _micropy_hw_romfs_start; -extern uint8_t _micropy_hw_romfs_size; +extern uint8_t _micropy_hw_romfs_part0_start; +extern uint8_t _micropy_hw_romfs_part0_size; static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, MICROPY_HW_ROMFS_BYTES, (void *)MICROPY_HW_ROMFS_BASE); From 043dc7959206e13d044fda5d590e1452f4a226df Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 14 Jan 2025 11:34:21 +0100 Subject: [PATCH 0385/2098] stm32/boards/ARDUINO_GIGA: Enable 4MiB ROMFS partition in ext flash. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 9 +++++++-- ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index fdf3128a360..cef45d730cc 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -32,6 +32,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) #define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) + // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_SPIFLASH_SOFT_RESET (1) @@ -125,8 +130,8 @@ void GIGA_board_low_power(int mode); // QSPI flash #1 for storage #define MICROPY_HW_QSPI_PRESCALER (2) // 100MHz #define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (27) -// Reserve 1MiB at the end for compatibility with alternate firmware that places WiFi blob here. -#define MICROPY_HW_SPIFLASH_SIZE_BITS (120 * 1024 * 1024) +// Reserve 4MiB for romfs and 1MiB for WiFi/BT firmware. +#define MICROPY_HW_SPIFLASH_SIZE_BITS (88 * 1024 * 1024) #define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI2_CS) #define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI2_CLK) #define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI2_D0) diff --git a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld index 793a76b9702..e7bb950dbe6 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld @@ -13,11 +13,11 @@ MEMORY SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3 D2 */ SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ - FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ + FLASH_ROMFS (rx) : ORIGIN = 0x90B00000, LENGTH = 4096K /* romfs partition in QSPI flash */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,6 +44,10 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* Location of romfs filesystem */ +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); + /* OpenAMP shared memory region */ _openamp_shm_region_start = ORIGIN(SRAM4); _openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); From edc927a1852dba9d444c842f7bcf11bbaeeaf092 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 14 Jan 2025 11:35:45 +0100 Subject: [PATCH 0386/2098] stm32/boards/ARDUINO_NICLA_VISION: Enable 4MiB ROMFS part in ext flash. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 9 +++++++-- ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index 505f8b68901..47bf1be23d4 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -32,6 +32,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) #define MICROPY_HW_TIM_IS_RESERVED(id) (id == 3) +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) + // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_SPIFLASH_SOFT_RESET (1) @@ -130,8 +135,8 @@ void NICLAV_board_osc_enable(int enable); // QSPI flash for storage #define MICROPY_HW_QSPI_PRESCALER (2) // 100MHz #define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (27) -// Reserve 1MiB at the end for compatibility with alternate firmware that places WiFi blob here. -#define MICROPY_HW_SPIFLASH_SIZE_BITS (120 * 1024 * 1024) +// Reserve 4MiB for romfs and 1MiB for WiFi/BT firmware. +#define MICROPY_HW_SPIFLASH_SIZE_BITS (88 * 1024 * 1024) #define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI2_CS) #define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI2_CLK) #define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI2_D0) diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld index 6d6ce279f29..9bd7f49a8ec 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld @@ -13,11 +13,11 @@ MEMORY SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3 D2 */ SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ - FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ + FLASH_ROMFS (rx) : ORIGIN = 0x90B00000, LENGTH = 4096K /* romfs partition in QSPI flash */ } _cm4_ram_start = ORIGIN(SRAM4); @@ -46,6 +46,10 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* Location of romfs filesystem */ +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); + /* OpenAMP shared memory region */ _openamp_shm_region_start = ORIGIN(SRAM4); _openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); From 96ce08e498a2c62b38aad78f84f2a8f438dc7e3d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 14 Jan 2025 11:36:22 +0100 Subject: [PATCH 0387/2098] stm32/boards/ARDUINO_PORTENTA_H7: Enable 4MiB ROMFS part in ext flash. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 9 +++++++-- ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 900bf186737..90a14690566 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -32,6 +32,11 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) #define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) + // Flash storage config #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_SPIFLASH_SOFT_RESET (1) @@ -129,8 +134,8 @@ void PORTENTA_board_osc_enable(int enable); // QSPI flash #1 for storage #define MICROPY_HW_QSPI_PRESCALER (2) // 100MHz #define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (27) -// Reserve 1MiB at the end for compatibility with alternate firmware that places WiFi blob here. -#define MICROPY_HW_SPIFLASH_SIZE_BITS (120 * 1024 * 1024) +// Reserve 4MiB for romfs and 1MiB for WiFi/BT firmware. +#define MICROPY_HW_SPIFLASH_SIZE_BITS (88 * 1024 * 1024) #define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI2_CS) #define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI2_CLK) #define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI2_D0) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld index 793a76b9702..e7bb950dbe6 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld @@ -13,11 +13,11 @@ MEMORY SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3 D2 */ SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ - FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ + FLASH_ROMFS (rx) : ORIGIN = 0x90B00000, LENGTH = 4096K /* romfs partition in QSPI flash */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,6 +44,10 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* Location of romfs filesystem */ +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); + /* OpenAMP shared memory region */ _openamp_shm_region_start = ORIGIN(SRAM4); _openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); From 9db2398009ebb156fc6790f87be337c29f83b5d0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 9 Oct 2024 15:55:46 +1100 Subject: [PATCH 0388/2098] stm32/can: Refactor can.h API to not depend on pyboard can types. This is necessary for the machine.CAN implementation to use the same low-level functions. Includes some refactoring around FIFO selection as there was a footgun where CAN_FIFO0/1 are 0/1 but FDCAN_RX_FIFO0/1 are not. Added an explicit type for non-hardware-specific FIFO numbering. Also moved responsibility for re-enabling CAN receive interrupts into the higher layer (pyb_can.c layer) after calling can_receive(). Also includes this behaviour change for FDCAN boards: - Fix for boards with FDCAN not updating error status counters (num_error_warning, num_error_passive, num_bus_off). These are now updated the same as on boards with CAN Classic controllers, as documented. - Previously FDCAN boards would trigger the RX callback function on error events instead (passing undocumented irq numbers 3, 4, 5). This behaviour has been removed in favour of the documented behaviour of updating the status counters. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/can.c | 198 +++++++++++++++++----------------- ports/stm32/can.h | 84 ++++++++++----- ports/stm32/fdcan.c | 243 +++++++++++++++++++----------------------- ports/stm32/main.c | 6 +- ports/stm32/modpyb.c | 2 +- ports/stm32/pyb_can.c | 202 ++++++++++++++++++----------------- ports/stm32/pyb_can.h | 58 ++++++++++ 7 files changed, 431 insertions(+), 362 deletions(-) create mode 100644 ports/stm32/pyb_can.h diff --git a/ports/stm32/can.c b/ports/stm32/can.c index 490faeba709..47385e0fa7b 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -28,29 +28,15 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "can.h" +#include "pyb_can.h" #include "irq.h" #if MICROPY_HW_ENABLE_CAN -void can_init0(void) { - for (uint i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { - MP_STATE_PORT(pyb_can_obj_all)[i] = NULL; - } -} - -void can_deinit_all(void) { - for (int i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { - pyb_can_obj_t *can_obj = MP_STATE_PORT(pyb_can_obj_all)[i]; - if (can_obj != NULL) { - can_deinit(can_obj); - } - } -} - #if !MICROPY_HW_ENABLE_FDCAN -bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { - CAN_InitTypeDef *init = &can_obj->can.Init; +bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { + CAN_InitTypeDef *init = &can->Init; init->Mode = mode << 4; // shift-left so modes fit in a small-int init->Prescaler = prescaler; init->SJW = ((sjw - 1) & 3) << 24; @@ -67,7 +53,7 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ uint32_t sce_irq = 0; const machine_pin_obj_t *pins[2]; - switch (can_obj->can_id) { + switch (can_id) { #if defined(MICROPY_HW_CAN1_TX) case PYB_CAN_1: CANx = CAN1; @@ -107,21 +93,16 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ uint32_t pin_mode = MP_HAL_PIN_MODE_ALT; uint32_t pin_pull = MP_HAL_PIN_PULL_UP; for (int i = 0; i < 2; i++) { - if (!mp_hal_pin_config_alt(pins[i], pin_mode, pin_pull, AF_FN_CAN, can_obj->can_id)) { + if (!mp_hal_pin_config_alt(pins[i], pin_mode, pin_pull, AF_FN_CAN, can_id)) { return false; } } // init CANx - can_obj->can.Instance = CANx; - HAL_CAN_Init(&can_obj->can); + can->Instance = CANx; + HAL_CAN_Init(can); - can_obj->is_enabled = true; - can_obj->num_error_warning = 0; - can_obj->num_error_passive = 0; - can_obj->num_bus_off = 0; - - __HAL_CAN_ENABLE_IT(&can_obj->can, CAN_IT_ERR | CAN_IT_BOF | CAN_IT_EPV | CAN_IT_EWG); + __HAL_CAN_ENABLE_IT(can, CAN_IT_ERR | CAN_IT_BOF | CAN_IT_EPV | CAN_IT_EWG); NVIC_SetPriority(sce_irq, IRQ_PRI_CAN); HAL_NVIC_EnableIRQ(sce_irq); @@ -129,10 +110,9 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ return true; } -void can_deinit(pyb_can_obj_t *self) { - self->is_enabled = false; - HAL_CAN_DeInit(&self->can); - if (self->can.Instance == CAN1) { +void can_deinit(CAN_HandleTypeDef *can) { + HAL_CAN_DeInit(can); + if (can->Instance == CAN1) { HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn); HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn); HAL_NVIC_DisableIRQ(CAN1_SCE_IRQn); @@ -140,7 +120,7 @@ void can_deinit(pyb_can_obj_t *self) { __HAL_RCC_CAN1_RELEASE_RESET(); __HAL_RCC_CAN1_CLK_DISABLE(); #if defined(CAN2) - } else if (self->can.Instance == CAN2) { + } else if (can->Instance == CAN2) { HAL_NVIC_DisableIRQ(CAN2_RX0_IRQn); HAL_NVIC_DisableIRQ(CAN2_RX1_IRQn); HAL_NVIC_DisableIRQ(CAN2_SCE_IRQn); @@ -149,7 +129,7 @@ void can_deinit(pyb_can_obj_t *self) { __HAL_RCC_CAN2_CLK_DISABLE(); #endif #if defined(CAN3) - } else if (self->can.Instance == CAN3) { + } else if (can->Instance == CAN3) { HAL_NVIC_DisableIRQ(CAN3_RX0_IRQn); HAL_NVIC_DisableIRQ(CAN3_RX1_IRQn); HAL_NVIC_DisableIRQ(CAN3_SCE_IRQn); @@ -160,7 +140,19 @@ void can_deinit(pyb_can_obj_t *self) { } } -void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { +void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { + __HAL_CAN_DISABLE_IT(can, ((fifo == CAN_RX_FIFO0) ? + (CAN_IT_FMP0 | CAN_IT_FF0 | CAN_IT_FOV0) : + (CAN_IT_FMP1 | CAN_IT_FF1 | CAN_IT_FOV1))); +} + +void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received) { + __HAL_CAN_ENABLE_IT(can, ((fifo == CAN_RX_FIFO0) ? + ((enable_msg_received ? CAN_IT_FMP0 : 0) | CAN_IT_FF0 | CAN_IT_FOV0) : + ((enable_msg_received ? CAN_IT_FMP1 : 0) | CAN_IT_FF1 | CAN_IT_FOV1))); +} + +void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t bank) { CAN_FilterConfTypeDef filter; filter.FilterIdHigh = 0; @@ -168,18 +160,18 @@ void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { filter.FilterMaskIdHigh = 0; filter.FilterMaskIdLow = 0; filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; - filter.FilterNumber = f; + filter.FilterNumber = filter_num; filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_16BIT; filter.FilterActivation = DISABLE; filter.BankNumber = bank; - HAL_CAN_ConfigFilter(&self->can, &filter); + HAL_CAN_ConfigFilter(can, &filter); } -int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms) { +int can_receive(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms) { volatile uint32_t *rfr; - if (fifo == CAN_FIFO0) { + if (fifo == CAN_RX_FIFO0) { rfr = &can->Instance->RF0R; } else { rfr = &can->Instance->RF1R; @@ -222,13 +214,16 @@ int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t return 0; // success } -// We have our own version of CAN transmit so we can handle Timeout=0 correctly. -HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout) { +// Lightly modified version of HAL CAN_Transmit to handle Timeout=0 correctly +HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t Timeout) { uint32_t transmitmailbox; uint32_t tickstart; uint32_t rqcpflag = 0; uint32_t txokflag = 0; + hcan->pTxMsg = txmsg; + (void)data; // Not needed here, caller has set it up as &tx_msg->Data + // Check the parameters assert_param(IS_CAN_IDTYPE(hcan->pTxMsg->IDE)); assert_param(IS_CAN_RTR(hcan->pTxMsg->RTR)); @@ -312,79 +307,90 @@ HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout) { } } -static void can_rx_irq_handler(uint can_id, uint fifo_id) { - mp_obj_t callback; - pyb_can_obj_t *self; - mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); - byte *state; - - self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; - - if (fifo_id == CAN_FIFO0) { - callback = self->rxcallback0; - state = &self->rx_state0; +// Workaround for the __HAL_CAN macros expecting a CAN_HandleTypeDef which we +// don't have in the ISR. Using this "fake" struct instead of CAN_HandleTypeDef +// so it's not possible to accidentally call an API that uses one of the other +// fields in the structure. +typedef struct { + CAN_TypeDef *Instance; +} fake_handle_t; + +static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { + uint32_t full_flag, full_int, overrun_flag, overrun_int, pending_int; + + const fake_handle_t handle = { + .Instance = instance, + }; + + if (fifo == CAN_RX_FIFO0) { + full_flag = CAN_FLAG_FF0; + full_int = CAN_IT_FF0; + overrun_flag = CAN_FLAG_FOV0; + overrun_int = CAN_IT_FOV0; + pending_int = CAN_IT_FMP0; } else { - callback = self->rxcallback1; - state = &self->rx_state1; + full_flag = CAN_FLAG_FF1; + full_int = CAN_IT_FF1; + overrun_flag = CAN_FLAG_FOV1; + overrun_int = CAN_IT_FOV1; + pending_int = CAN_IT_FMP1; } - switch (*state) { - case RX_STATE_FIFO_EMPTY: - __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); - irq_reason = MP_OBJ_NEW_SMALL_INT(0); - *state = RX_STATE_MESSAGE_PENDING; - break; - case RX_STATE_MESSAGE_PENDING: - __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); - __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FF0 : CAN_FLAG_FF1); - irq_reason = MP_OBJ_NEW_SMALL_INT(1); - *state = RX_STATE_FIFO_FULL; - break; - case RX_STATE_FIFO_FULL: - __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1); - __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FOV0 : CAN_FLAG_FOV1); - irq_reason = MP_OBJ_NEW_SMALL_INT(2); - *state = RX_STATE_FIFO_OVERFLOW; - break; - case RX_STATE_FIFO_OVERFLOW: - // This should never happen - break; + bool full = __HAL_CAN_GET_FLAG(&handle, full_flag); + bool overrun = __HAL_CAN_GET_FLAG(&handle, overrun_flag); + + // Note: receive interrupt bits are disabled below, and re-enabled by the + // higher layer after calling can_receive() + + if (full) { + __HAL_CAN_DISABLE_IT(&handle, full_int); + __HAL_CAN_CLEAR_FLAG(&handle, full_flag); + if (!overrun) { + can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); + } + } + if (overrun) { + __HAL_CAN_DISABLE_IT(&handle, overrun_int); + __HAL_CAN_CLEAR_FLAG(&handle, overrun_flag); + can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); } - pyb_can_handle_callback(self, fifo_id, callback, irq_reason); + if (!(full || overrun)) { + // Process of elimination, if neither of the above + // FIFO status flags are set then message pending interrupt is what fired. + __HAL_CAN_DISABLE_IT(&handle, pending_int); + can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); + } } -static void can_sce_irq_handler(uint can_id) { - pyb_can_obj_t *self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; - if (self) { - self->can.Instance->MSR = CAN_MSR_ERRI; - uint32_t esr = self->can.Instance->ESR; - if (esr & CAN_ESR_BOFF) { - ++self->num_bus_off; - } else if (esr & CAN_ESR_EPVF) { - ++self->num_error_passive; - } else if (esr & CAN_ESR_EWGF) { - ++self->num_error_warning; - } +static void can_sce_irq_handler(uint can_id, CAN_TypeDef *instance) { + instance->MSR = CAN_MSR_ERRI; // Write to clear ERRIE interrupt + uint32_t esr = instance->ESR; + if (esr & CAN_ESR_BOFF) { + can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); + } else if (esr & CAN_ESR_EPVF) { + can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); + } else if (esr & CAN_ESR_EWGF) { + can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); } } #if defined(MICROPY_HW_CAN1_TX) void CAN1_RX0_IRQHandler(void) { IRQ_ENTER(CAN1_RX0_IRQn); - can_rx_irq_handler(PYB_CAN_1, CAN_FIFO0); + can_rx_irq_handler(PYB_CAN_1, CAN1, CAN_RX_FIFO0); IRQ_EXIT(CAN1_RX0_IRQn); } void CAN1_RX1_IRQHandler(void) { IRQ_ENTER(CAN1_RX1_IRQn); - can_rx_irq_handler(PYB_CAN_1, CAN_FIFO1); + can_rx_irq_handler(PYB_CAN_1, CAN1, CAN_RX_FIFO1); IRQ_EXIT(CAN1_RX1_IRQn); } void CAN1_SCE_IRQHandler(void) { IRQ_ENTER(CAN1_SCE_IRQn); - can_sce_irq_handler(PYB_CAN_1); + can_sce_irq_handler(PYB_CAN_1, CAN1); IRQ_EXIT(CAN1_SCE_IRQn); } #endif @@ -392,19 +398,19 @@ void CAN1_SCE_IRQHandler(void) { #if defined(MICROPY_HW_CAN2_TX) void CAN2_RX0_IRQHandler(void) { IRQ_ENTER(CAN2_RX0_IRQn); - can_rx_irq_handler(PYB_CAN_2, CAN_FIFO0); + can_rx_irq_handler(PYB_CAN_2, CAN2, CAN_RX_FIFO0); IRQ_EXIT(CAN2_RX0_IRQn); } void CAN2_RX1_IRQHandler(void) { IRQ_ENTER(CAN2_RX1_IRQn); - can_rx_irq_handler(PYB_CAN_2, CAN_FIFO1); + can_rx_irq_handler(PYB_CAN_2, CAN2, CAN_RX_FIFO1); IRQ_EXIT(CAN2_RX1_IRQn); } void CAN2_SCE_IRQHandler(void) { IRQ_ENTER(CAN2_SCE_IRQn); - can_sce_irq_handler(PYB_CAN_2); + can_sce_irq_handler(PYB_CAN_2, CAN2); IRQ_EXIT(CAN2_SCE_IRQn); } #endif @@ -412,19 +418,19 @@ void CAN2_SCE_IRQHandler(void) { #if defined(MICROPY_HW_CAN3_TX) void CAN3_RX0_IRQHandler(void) { IRQ_ENTER(CAN3_RX0_IRQn); - can_rx_irq_handler(PYB_CAN_3, CAN_FIFO0); + can_rx_irq_handler(PYB_CAN_3, CAN3, CAN_RX_FIFO0); IRQ_EXIT(CAN3_RX0_IRQn); } void CAN3_RX1_IRQHandler(void) { IRQ_ENTER(CAN3_RX1_IRQn); - can_rx_irq_handler(PYB_CAN_3, CAN_FIFO1); + can_rx_irq_handler(PYB_CAN_3, CAN3, CAN_RX_FIFO1); IRQ_EXIT(CAN3_RX1_IRQn); } void CAN3_SCE_IRQHandler(void) { IRQ_ENTER(CAN3_SCE_IRQn); - can_sce_irq_handler(PYB_CAN_3); + can_sce_irq_handler(PYB_CAN_3, CAN3); IRQ_EXIT(CAN3_SCE_IRQn); } #endif diff --git a/ports/stm32/can.h b/ports/stm32/can.h index 45ac7187e00..3422f4180d6 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -26,10 +26,17 @@ #ifndef MICROPY_INCLUDED_STM32_CAN_H #define MICROPY_INCLUDED_STM32_CAN_H -#include "py/obj.h" +#include "py/mphal.h" #if MICROPY_HW_ENABLE_CAN +// This API provides a higher abstraction layer over the two STM32 +// CAN controller APIs provided by the ST HAL (one for their classic +// CAN controller, one for their FD CAN controller.) + +// There are two implementations, can.c and fdcan.c - only one is included in +// the build for a given board. + #define PYB_CAN_1 (1) #define PYB_CAN_2 (2) #define PYB_CAN_3 (3) @@ -61,31 +68,56 @@ typedef enum _rx_state_t { RX_STATE_FIFO_OVERFLOW, } rx_state_t; -typedef struct _pyb_can_obj_t { - mp_obj_base_t base; - mp_obj_t rxcallback0; - mp_obj_t rxcallback1; - mp_uint_t can_id : 8; - bool is_enabled : 1; - byte rx_state0; - byte rx_state1; - uint16_t num_error_warning; - uint16_t num_error_passive; - uint16_t num_bus_off; - CAN_HandleTypeDef can; -} pyb_can_obj_t; - -extern const mp_obj_type_t pyb_can_type; - -void can_init0(void); -void can_deinit_all(void); -bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); -void can_deinit(pyb_can_obj_t *self); - -void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank); -int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); -HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout); -void pyb_can_handle_callback(pyb_can_obj_t *self, uint fifo_id, mp_obj_t callback, mp_obj_t irq_reason); +typedef enum { + CAN_INT_MESSAGE_RECEIVED, + CAN_INT_FIFO_FULL, + CAN_INT_FIFO_OVERFLOW, // These first 3 correspond to pyb.CAN.rxcallback args + + CAN_INT_ERR_BUS_OFF, + CAN_INT_ERR_PASSIVE, + CAN_INT_ERR_WARNING, +} can_int_t; + +// RX FIFO numbering +// +// Note: For traditional CAN peripheral, the values of CAN_FIFO0 and CAN_FIFO1 are the same +// as this enum. FDCAN macros FDCAN_RX_FIFO0 and FDCAN_RX_FIFO1 are hardware offsets and not the same. +typedef enum { + CAN_RX_FIFO0, + CAN_RX_FIFO1, +} can_rx_fifo_t; + +bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); +void can_deinit(CAN_HandleTypeDef *can); + +void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t bank); +int can_receive(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); +HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t Timeout); + +// Disable all CAN receive interrupts for a FIFO +void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo); + +// Enable CAN receive interrupts for a FIFO. +// +// Interrupt for CAN_INT_MESSAGE_RECEIVED is only enabled if enable_msg_received is set. +void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received); + +// Implemented in pyb_can.c, called from lower layer +extern void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo); + +#if MICROPY_HW_ENABLE_FDCAN + +static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { + return HAL_FDCAN_GetRxFifoFillLevel(can, fifo == CAN_RX_FIFO0 ? FDCAN_RX_FIFO0 : FDCAN_RX_FIFO1); +} + +#else + +static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { + return __HAL_CAN_MSG_PENDING(can, fifo == CAN_RX_FIFO0 ? CAN_FIFO0 : CAN_FIFO1); +} + +#endif // MICROPY_HW_ENABLE_FDCAN #endif // MICROPY_HW_ENABLE_CAN diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index 04d8135c081..b87ab48f1cc 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -28,6 +28,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "can.h" +#include "pyb_can.h" #include "irq.h" #if MICROPY_HW_ENABLE_CAN && MICROPY_HW_ENABLE_FDCAN @@ -44,9 +45,13 @@ #define FDCAN_ELEMENT_MASK_FIDX (0x7f000000) // Filter Index #define FDCAN_ELEMENT_MASK_ANMF (0x80000000) // Accepted Non-matching Frame -#define FDCAN_RX_FIFO0_MASK (FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST | FDCAN_FLAG_RX_FIFO0_FULL | FDCAN_FLAG_RX_FIFO0_NEW_MESSAGE) -#define FDCAN_RX_FIFO1_MASK (FDCAN_FLAG_RX_FIFO1_MESSAGE_LOST | FDCAN_FLAG_RX_FIFO1_FULL | FDCAN_FLAG_RX_FIFO1_NEW_MESSAGE) -#define FDCAN_ERROR_STATUS_MASK (FDCAN_FLAG_ERROR_PASSIVE | FDCAN_FLAG_ERROR_WARNING | FDCAN_FLAG_BUS_OFF) +#define FDCAN_IT_RX_FIFO0_MASK (FDCAN_IT_RX_FIFO0_MESSAGE_LOST | FDCAN_IT_RX_FIFO0_FULL | FDCAN_IT_RX_FIFO0_NEW_MESSAGE) +#define FDCAN_IT_RX_FIFO1_MASK (FDCAN_IT_RX_FIFO1_MESSAGE_LOST | FDCAN_IT_RX_FIFO1_FULL | FDCAN_IT_RX_FIFO1_NEW_MESSAGE) +#define FDCAN_IT_ERROR_STATUS_MASK (FDCAN_IT_ERROR_PASSIVE | FDCAN_IT_ERROR_WARNING | FDCAN_IT_BUS_OFF) + +#define FDCAN_IT_RX_NEW_MESSAGE_MASK (FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE) +#define FDCAN_IT_RX_FULL_MASK (FDCAN_IT_RX_FIFO0_FULL | FDCAN_IT_RX_FIFO1_FULL) +#define FDCAN_IT_RX_MESSAGE_LOST_MASK (FDCAN_IT_RX_FIFO0_MESSAGE_LOST | FDCAN_IT_RX_FIFO1_MESSAGE_LOST) #if defined(STM32H7) // adaptations for H7 to G4 naming convention in HAL @@ -63,10 +68,10 @@ // also defined in _hal_fdcan.c, but not able to declare extern and reach the variable const uint8_t DLCtoBytes[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; -bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { +bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { (void)auto_restart; - FDCAN_InitTypeDef *init = &can_obj->can.Init; + FDCAN_InitTypeDef *init = &can->Init; // Configure FDCAN with FD frame and BRS support. init->FrameFormat = FDCAN_FRAME_FD_BRS; init->Mode = mode; @@ -94,7 +99,7 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ // To support 2 FDCAN instances simultaneously, the Message RAM is divided in half by // setting the second FDCAN memory offset to half the RAM size. With this configuration, // the maximum words per FDCAN instance is 1280 32-bit words. - if (can_obj->can_id == PYB_CAN_1) { + if (can_id == PYB_CAN_1) { init->MessageRAMOffset = 0; } else { init->MessageRAMOffset = FDCAN_MESSAGE_RAM_SIZE / 2; @@ -139,7 +144,7 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ FDCAN_GlobalTypeDef *CANx = NULL; const machine_pin_obj_t *pins[2]; - switch (can_obj->can_id) { + switch (can_id) { #if defined(MICROPY_HW_CAN1_TX) case PYB_CAN_1: CANx = FDCAN1; @@ -167,39 +172,34 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ uint32_t pin_mode = MP_HAL_PIN_MODE_ALT; uint32_t pin_pull = MP_HAL_PIN_PULL_UP; for (int i = 0; i < 2; ++i) { - if (!mp_hal_pin_config_alt(pins[i], pin_mode, pin_pull, AF_FN_CAN, can_obj->can_id)) { + if (!mp_hal_pin_config_alt(pins[i], pin_mode, pin_pull, AF_FN_CAN, can_id)) { return false; } } // init CANx - can_obj->can.Instance = CANx; + can->Instance = CANx; // catch bad configuration errors. - if (HAL_FDCAN_Init(&can_obj->can) != HAL_OK) { + if (HAL_FDCAN_Init(can) != HAL_OK) { return false; } // Disable acceptance of non-matching frames (enabled by default) - HAL_FDCAN_ConfigGlobalFilter(&can_obj->can, FDCAN_REJECT, FDCAN_REJECT, DISABLE, DISABLE); + HAL_FDCAN_ConfigGlobalFilter(can, FDCAN_REJECT, FDCAN_REJECT, DISABLE, DISABLE); // The configuration registers are locked after CAN is started. - HAL_FDCAN_Start(&can_obj->can); + HAL_FDCAN_Start(can); // Reset all filters for (int f = 0; f < init->StdFiltersNbr; ++f) { - can_clearfilter(can_obj, f, false); + can_clearfilter(can, f, false); } for (int f = 0; f < init->ExtFiltersNbr; ++f) { - can_clearfilter(can_obj, f, true); + can_clearfilter(can, f, true); } - can_obj->is_enabled = true; - can_obj->num_error_warning = 0; - can_obj->num_error_passive = 0; - can_obj->num_bus_off = 0; - - switch (can_obj->can_id) { + switch (can_id) { case PYB_CAN_1: NVIC_SetPriority(FDCAN1_IT0_IRQn, IRQ_PRI_CAN); HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); @@ -218,22 +218,18 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ return false; } // FDCAN IT 0 - HAL_FDCAN_ConfigInterruptLines(&can_obj->can, FDCAN_IT_GROUP_RX_FIFO0 | FDCAN_IT_GROUP_BIT_LINE_ERROR | FDCAN_IT_GROUP_PROTOCOL_ERROR, FDCAN_INTERRUPT_LINE0); + HAL_FDCAN_ConfigInterruptLines(can, FDCAN_IT_GROUP_RX_FIFO0 | FDCAN_IT_GROUP_BIT_LINE_ERROR | FDCAN_IT_GROUP_PROTOCOL_ERROR, FDCAN_INTERRUPT_LINE0); // FDCAN IT 1 - HAL_FDCAN_ConfigInterruptLines(&can_obj->can, FDCAN_IT_GROUP_RX_FIFO1, FDCAN_INTERRUPT_LINE1); + HAL_FDCAN_ConfigInterruptLines(can, FDCAN_IT_GROUP_RX_FIFO1, FDCAN_INTERRUPT_LINE1); - uint32_t ActiveITs = FDCAN_IT_BUS_OFF | FDCAN_IT_ERROR_WARNING | FDCAN_IT_ERROR_PASSIVE; - ActiveITs |= FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE; - ActiveITs |= FDCAN_IT_RX_FIFO0_MESSAGE_LOST | FDCAN_IT_RX_FIFO1_MESSAGE_LOST; - ActiveITs |= FDCAN_IT_RX_FIFO0_FULL | FDCAN_IT_RX_FIFO1_FULL; - HAL_FDCAN_ActivateNotification(&can_obj->can, ActiveITs, 0); + // Enable error interrupts. RX-related interrupts are enabled via can_enable_rx_interrupts() + HAL_FDCAN_ActivateNotification(can, FDCAN_IT_BUS_OFF | FDCAN_IT_ERROR_WARNING | FDCAN_IT_ERROR_PASSIVE, 0); return true; } -void can_deinit(pyb_can_obj_t *self) { - self->is_enabled = false; - HAL_FDCAN_DeInit(&self->can); - if (self->can.Instance == FDCAN1) { +void can_deinit(FDCAN_HandleTypeDef *can) { + HAL_FDCAN_DeInit(can); + if (can->Instance == FDCAN1) { HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn); // TODO check if FDCAN2 is used. @@ -241,7 +237,7 @@ void can_deinit(pyb_can_obj_t *self) { __HAL_RCC_FDCAN_RELEASE_RESET(); __HAL_RCC_FDCAN_CLK_DISABLE(); #if defined(MICROPY_HW_CAN2_TX) - } else if (self->can.Instance == FDCAN2) { + } else if (can->Instance == FDCAN2) { HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN2_IT1_IRQn); // TODO check if FDCAN2 is used. @@ -252,25 +248,52 @@ void can_deinit(pyb_can_obj_t *self) { } } -void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t extid) { - if (self && self->can.Instance) { - FDCAN_FilterTypeDef filter = {0}; - if (extid == 1) { - filter.IdType = FDCAN_EXTENDED_ID; - } else { - filter.IdType = FDCAN_STANDARD_ID; +void can_clearfilter(FDCAN_HandleTypeDef *can, uint32_t f, uint8_t extid) { + FDCAN_FilterTypeDef filter = {0}; + if (extid == 1) { + filter.IdType = FDCAN_EXTENDED_ID; + } else { + filter.IdType = FDCAN_STANDARD_ID; + } + filter.FilterIndex = f; + filter.FilterConfig = FDCAN_FILTER_DISABLE; + HAL_FDCAN_ConfigFilter(can, &filter); +} + +void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { + HAL_FDCAN_DeactivateNotification(can, (fifo == CAN_RX_FIFO0) ? FDCAN_IT_RX_FIFO0_MASK : FDCAN_IT_RX_FIFO1_MASK); +} + +void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received) { + uint32_t ints = (fifo == CAN_RX_FIFO0) ? FDCAN_IT_RX_FIFO0_MASK : FDCAN_IT_RX_FIFO1_MASK; + if (!enable_msg_received) { + ints &= FDCAN_IT_RX_NEW_MESSAGE_MASK; + } + HAL_FDCAN_ActivateNotification(can, ints, 0); +} + +HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *can, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t timeout_ms) { + uint32_t start = HAL_GetTick(); + while (HAL_FDCAN_GetTxFifoFreeLevel(can) == 0) { + if (timeout_ms == 0) { + mp_raise_OSError(MP_ETIMEDOUT); + } + // Check for the Timeout + if (timeout_ms != HAL_MAX_DELAY) { + if (HAL_GetTick() - start >= timeout_ms) { + mp_raise_OSError(MP_ETIMEDOUT); + } } - filter.FilterIndex = f; - filter.FilterConfig = FDCAN_FILTER_DISABLE; - HAL_FDCAN_ConfigFilter(&self->can, &filter); + MICROPY_EVENT_POLL_HOOK } + return HAL_FDCAN_AddMessageToTxFifoQ(can, txmsg, data); } -int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, uint8_t *data, uint32_t timeout_ms) { +int can_receive(FDCAN_HandleTypeDef *can, can_rx_fifo_t fifo, FDCAN_RxHeaderTypeDef *hdr, uint8_t *data, uint32_t timeout_ms) { volatile uint32_t *rxf, *rxa; uint32_t fl; - if (fifo == FDCAN_RX_FIFO0) { + if (fifo == CAN_RX_FIFO0) { rxf = &can->Instance->RXF0S; rxa = &can->Instance->RXF0A; fl = FDCAN_RXF0S_F0FL; @@ -293,7 +316,7 @@ int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, // Get pointer to incoming message uint32_t index, *address; - if (fifo == FDCAN_RX_FIFO0) { + if (fifo == CAN_RX_FIFO0) { index = (*rxf & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; #if defined(STM32G4) address = (uint32_t *)(can->msgRam.RxFIFO0SA + (index * (18U * 4U))); // SRAMCAN_RF0_SIZE bytes, size not configurable @@ -303,7 +326,6 @@ int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, } else { index = (*rxf & FDCAN_RXF1S_F1GI) >> FDCAN_RXF1S_F1GI_Pos; #if defined(STM32G4) - // ToDo: test FIFO1, FIFO 0 is ok address = (uint32_t *)(can->msgRam.RxFIFO1SA + (index * (18U * 4U))); // SRAMCAN_RF1_SIZE bytes, size not configurable #else address = (uint32_t *)(can->msgRam.RxFIFO1SA + (index * can->Init.RxFifo1ElmtSize * 4)); @@ -340,117 +362,66 @@ int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, return 0; // success } -static void can_rx_irq_handler(uint can_id, uint fifo_id) { - mp_obj_t callback; - pyb_can_obj_t *self; - mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); - byte *state; - - self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; - - CAN_TypeDef *can = self->can.Instance; - - uint32_t RxFifo0ITs; - uint32_t RxFifo1ITs; - // uint32_t Errors; - uint32_t ErrorStatusITs; - uint32_t Psr; - - RxFifo0ITs = can->IR & FDCAN_RX_FIFO0_MASK; - RxFifo0ITs &= can->IE; - RxFifo1ITs = can->IR & FDCAN_RX_FIFO1_MASK; - RxFifo1ITs &= can->IE; - // Errors = (&self->can)->Instance->IR & FDCAN_ERROR_MASK; - // Errors &= (&self->can)->Instance->IE; - ErrorStatusITs = can->IR & FDCAN_ERROR_STATUS_MASK; - ErrorStatusITs &= can->IE; - Psr = can->PSR; - - if (fifo_id == FDCAN_RX_FIFO0) { - callback = self->rxcallback0; - state = &self->rx_state0; - if (RxFifo0ITs & FDCAN_FLAG_RX_FIFO0_NEW_MESSAGE) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO0_NEW_MESSAGE); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO0_NEW_MESSAGE); - irq_reason = MP_OBJ_NEW_SMALL_INT(0); - *state = RX_STATE_MESSAGE_PENDING; - - } - if (RxFifo0ITs & FDCAN_FLAG_RX_FIFO0_FULL) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO0_FULL); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO0_FULL); - irq_reason = MP_OBJ_NEW_SMALL_INT(1); - *state = RX_STATE_FIFO_FULL; +static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { + uint32_t ints, rx_fifo_ints, error_ints; - } - if (RxFifo0ITs & FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO0_MESSAGE_LOST); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST); - irq_reason = MP_OBJ_NEW_SMALL_INT(2); - *state = RX_STATE_FIFO_OVERFLOW; - } + ints = instance->IR & instance->IE; + if (fifo == CAN_RX_FIFO0) { + rx_fifo_ints = ints & FDCAN_IT_RX_FIFO0_MASK; } else { - callback = self->rxcallback1; - state = &self->rx_state1; - if (RxFifo1ITs & FDCAN_FLAG_RX_FIFO1_NEW_MESSAGE) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO1_NEW_MESSAGE); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO1_NEW_MESSAGE); - irq_reason = MP_OBJ_NEW_SMALL_INT(0); - *state = RX_STATE_MESSAGE_PENDING; + rx_fifo_ints = ints & FDCAN_IT_RX_FIFO1_MASK; + } + error_ints = ints & FDCAN_IT_ERROR_STATUS_MASK; - } - if (RxFifo1ITs & FDCAN_FLAG_RX_FIFO1_FULL) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO1_FULL); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO1_FULL); - irq_reason = MP_OBJ_NEW_SMALL_INT(1); - *state = RX_STATE_FIFO_FULL; + // Disable receive interrupts, re-enabled by higher layer after calling can_receive() + // (Note: can't use __HAL_CAN API as only have a CAN_TypeDef, not CAN_HandleTypeDef) + instance->IE &= ~rx_fifo_ints; + instance->IR = rx_fifo_ints | error_ints; + if (rx_fifo_ints) { + if (rx_fifo_ints & FDCAN_IT_RX_NEW_MESSAGE_MASK) { + can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); + } + if (rx_fifo_ints & FDCAN_IT_RX_FULL_MASK) { + can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); } - if (RxFifo1ITs & FDCAN_FLAG_RX_FIFO1_MESSAGE_LOST) { - __HAL_FDCAN_DISABLE_IT(&self->can, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_RX_FIFO1_MESSAGE_LOST); - irq_reason = MP_OBJ_NEW_SMALL_INT(2); - *state = RX_STATE_FIFO_OVERFLOW; + if (rx_fifo_ints & FDCAN_IT_RX_MESSAGE_LOST_MASK) { + can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); } } - if (ErrorStatusITs & FDCAN_FLAG_ERROR_WARNING) { - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_ERROR_WARNING); - if (Psr & FDCAN_PSR_EW) { - irq_reason = MP_OBJ_NEW_SMALL_INT(3); - // mp_printf(MICROPY_ERROR_PRINTER, "clear warning %08x\n", (can->IR & FDCAN_ERROR_STATUS_MASK)); + if (error_ints) { + uint32_t Psr = instance->PSR; + + if (error_ints & FDCAN_IT_ERROR_WARNING) { + if (Psr & FDCAN_PSR_EW) { + can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); + } } - } - if (ErrorStatusITs & FDCAN_FLAG_ERROR_PASSIVE) { - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_ERROR_PASSIVE); - if (Psr & FDCAN_PSR_EP) { - irq_reason = MP_OBJ_NEW_SMALL_INT(4); - // mp_printf(MICROPY_ERROR_PRINTER, "clear passive %08x\n", (can->IR & FDCAN_ERROR_STATUS_MASK)); + if (error_ints & FDCAN_IT_ERROR_PASSIVE) { + if (Psr & FDCAN_PSR_EP) { + can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); + } } - } - if (ErrorStatusITs & FDCAN_FLAG_BUS_OFF) { - __HAL_FDCAN_CLEAR_FLAG(&self->can, FDCAN_FLAG_BUS_OFF); - if (Psr & FDCAN_PSR_BO) { - irq_reason = MP_OBJ_NEW_SMALL_INT(5); - // mp_printf(MICROPY_ERROR_PRINTER, "bus off %08x\n", (can->IR & FDCAN_ERROR_STATUS_MASK)); + if (error_ints & FDCAN_IT_BUS_OFF) { + if (Psr & FDCAN_PSR_BO) { + can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); + } } } - - pyb_can_handle_callback(self, fifo_id, callback, irq_reason); - // mp_printf(MICROPY_ERROR_PRINTER, "Ints: %08x, %08x, %08x\n", RxFifo0ITs, RxFifo1ITs, ErrorStatusITs); } #if defined(MICROPY_HW_CAN1_TX) void FDCAN1_IT0_IRQHandler(void) { IRQ_ENTER(FDCAN1_IT0_IRQn); - can_rx_irq_handler(PYB_CAN_1, FDCAN_RX_FIFO0); + can_rx_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO0); IRQ_EXIT(FDCAN1_IT0_IRQn); } void FDCAN1_IT1_IRQHandler(void) { IRQ_ENTER(FDCAN1_IT1_IRQn); - can_rx_irq_handler(PYB_CAN_1, FDCAN_RX_FIFO1); + can_rx_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO1); IRQ_EXIT(FDCAN1_IT1_IRQn); } #endif @@ -458,13 +429,13 @@ void FDCAN1_IT1_IRQHandler(void) { #if defined(MICROPY_HW_CAN2_TX) void FDCAN2_IT0_IRQHandler(void) { IRQ_ENTER(FDCAN2_IT0_IRQn); - can_rx_irq_handler(PYB_CAN_2, FDCAN_RX_FIFO0); + can_rx_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO0); IRQ_EXIT(FDCAN2_IT0_IRQn); } void FDCAN2_IT1_IRQHandler(void) { IRQ_ENTER(FDCAN2_IT1_IRQn); - can_rx_irq_handler(PYB_CAN_2, FDCAN_RX_FIFO1); + can_rx_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO1); IRQ_EXIT(FDCAN2_IT1_IRQn); } #endif diff --git a/ports/stm32/main.c b/ports/stm32/main.c index ea870ef0da7..55dbeed9824 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -86,7 +86,7 @@ #include "accel.h" #include "servo.h" #include "dac.h" -#include "can.h" +#include "pyb_can.h" #include "subghz.h" #if MICROPY_PY_THREAD @@ -536,7 +536,7 @@ void stm32_main(uint32_t reset_mode) { timer_init0(); #if MICROPY_HW_ENABLE_CAN - can_init0(); + pyb_can_init0(); #endif #if MICROPY_HW_ENABLE_USB @@ -683,7 +683,7 @@ void stm32_main(uint32_t reset_mode) { pyb_i2c_deinit_all(); #endif #if MICROPY_HW_ENABLE_CAN - can_deinit_all(); + pyb_can_deinit_all(); #endif #if MICROPY_HW_ENABLE_DAC dac_deinit_all(); diff --git a/ports/stm32/modpyb.c b/ports/stm32/modpyb.c index 176010a7e51..3d8696e3c42 100644 --- a/ports/stm32/modpyb.c +++ b/ports/stm32/modpyb.c @@ -41,7 +41,7 @@ #include "i2c.h" #include "spi.h" #include "uart.h" -#include "can.h" +#include "pyb_can.h" #include "adc.h" #include "storage.h" #include "sdcard.h" diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 181b3173866..0d004ecfcb5 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -27,6 +27,7 @@ #include #include +#include "py/obj.h" #include "py/objarray.h" #include "py/runtime.h" #include "py/gc.h" @@ -35,6 +36,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "bufhelper.h" +#include "pyb_can.h" #include "can.h" #include "irq.h" @@ -44,8 +46,6 @@ #define CAN_MAX_DATA_FRAME (64) -#define CAN_FIFO0 FDCAN_RX_FIFO0 -#define CAN_FIFO1 FDCAN_RX_FIFO1 #define CAN_FILTER_FIFO0 (0) // Default timings; 125Kbps @@ -82,21 +82,6 @@ #define CAN2_RX1_IRQn FDCAN2_IT1_IRQn #endif -#define CAN_IT_FIFO0_FULL FDCAN_IT_RX_FIFO0_FULL -#define CAN_IT_FIFO1_FULL FDCAN_IT_RX_FIFO1_FULL -#define CAN_IT_FIFO0_OVRF FDCAN_IT_RX_FIFO0_MESSAGE_LOST -#define CAN_IT_FIFO1_OVRF FDCAN_IT_RX_FIFO1_MESSAGE_LOST -#define CAN_IT_FIFO0_PENDING FDCAN_IT_RX_FIFO0_NEW_MESSAGE -#define CAN_IT_FIFO1_PENDING FDCAN_IT_RX_FIFO1_NEW_MESSAGE -#define CAN_FLAG_FIFO0_FULL FDCAN_FLAG_RX_FIFO0_FULL -#define CAN_FLAG_FIFO1_FULL FDCAN_FLAG_RX_FIFO1_FULL -#define CAN_FLAG_FIFO0_OVRF FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST -#define CAN_FLAG_FIFO1_OVRF FDCAN_FLAG_RX_FIFO1_MESSAGE_LOST - -#define __HAL_CAN_ENABLE_IT __HAL_FDCAN_ENABLE_IT -#define __HAL_CAN_DISABLE_IT __HAL_FDCAN_DISABLE_IT -#define __HAL_CAN_CLEAR_FLAG __HAL_FDCAN_CLEAR_FLAG -#define __HAL_CAN_MSG_PENDING HAL_FDCAN_GetRxFifoFillLevel extern const uint8_t DLCtoBytes[16]; #else @@ -113,21 +98,27 @@ extern const uint8_t DLCtoBytes[16]; #define CAN_MAXIMUM_NBS2 (8) #define CAN_MINIMUM_TSEG (1) -#define CAN_IT_FIFO0_FULL CAN_IT_FF0 -#define CAN_IT_FIFO1_FULL CAN_IT_FF1 -#define CAN_IT_FIFO0_OVRF CAN_IT_FOV0 -#define CAN_IT_FIFO1_OVRF CAN_IT_FOV1 -#define CAN_IT_FIFO0_PENDING CAN_IT_FMP0 -#define CAN_IT_FIFO1_PENDING CAN_IT_FMP1 -#define CAN_FLAG_FIFO0_FULL CAN_FLAG_FF0 -#define CAN_FLAG_FIFO1_FULL CAN_FLAG_FF1 -#define CAN_FLAG_FIFO0_OVRF CAN_FLAG_FOV0 -#define CAN_FLAG_FIFO1_OVRF CAN_FLAG_FOV1 - static uint8_t can2_start_bank = 14; #endif +static mp_obj_t pyb_can_deinit(mp_obj_t self_in); + +void pyb_can_init0(void) { + for (uint i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { + MP_STATE_PORT(pyb_can_obj_all)[i] = NULL; + } +} + +void pyb_can_deinit_all(void) { + for (int i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { + pyb_can_obj_t *can_obj = MP_STATE_PORT(pyb_can_obj_all)[i]; + if (can_obj != NULL) { + pyb_can_deinit(MP_OBJ_FROM_PTR(can_obj)); + } + } +} + static void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); if (!self->is_enabled) { @@ -283,15 +274,20 @@ static mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp // Init filter banks for classic CAN. can2_start_bank = args[ARG_num_filter_banks].u_int; for (int f = 0; f < CAN_MAX_FILTER; f++) { - can_clearfilter(self, f, can2_start_bank); + can_clearfilter(&self->can, f, can2_start_bank); } #endif - if (!can_init(self, args[ARG_mode].u_int, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, + if (!can_init(&self->can, self->can_id, args[ARG_mode].u_int, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, args[ARG_bs1].u_int, args[ARG_bs2].u_int, args[ARG_auto_restart].u_bool)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) init failure"), self->can_id); } + self->is_enabled = true; + self->num_error_warning = 0; + self->num_error_passive = 0; + self->num_bus_off = 0; + return mp_const_none; } @@ -346,7 +342,7 @@ static mp_obj_t pyb_can_make_new(const mp_obj_type_t *type, size_t n_args, size_ if (self->is_enabled) { // The caller is requesting a reconfiguration of the hardware // this can only be done if the hardware is in init mode - can_deinit(self); + pyb_can_deinit(MP_OBJ_FROM_PTR(self)); } self->rxcallback0 = mp_const_none; @@ -373,7 +369,8 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_init_obj, 1, pyb_can_init); // deinit() static mp_obj_t pyb_can_deinit(mp_obj_t self_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - can_deinit(self); + can_deinit(&self->can); + self->is_enabled = false; return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_deinit_obj, pyb_can_deinit); @@ -489,17 +486,8 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_can_info_obj, 1, 2, pyb_can_info) // any(fifo) - return `True` if any message waiting on the FIFO, else `False` static mp_obj_t pyb_can_any(mp_obj_t self_in, mp_obj_t fifo_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t fifo = mp_obj_get_int(fifo_in); - if (fifo == 0) { - if (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO0) != 0) { - return mp_const_true; - } - } else { - if (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO1) != 0) { - return mp_const_true; - } - } - return mp_const_false; + can_rx_fifo_t fifo = mp_obj_get_int(fifo_in); + return mp_obj_new_bool(can_rx_pending(&self->can, fifo) != 0); } static MP_DEFINE_CONST_FUN_OBJ_2(pyb_can_any_obj, pyb_can_any); @@ -533,6 +521,7 @@ static mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * } // send the data + HAL_StatusTypeDef status; CanTxMsgTypeDef tx_msg; #if MICROPY_HW_ENABLE_FDCAN @@ -594,27 +583,7 @@ static mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * tx_data[i] = ((byte *)bufinfo.buf)[i]; } - HAL_StatusTypeDef status; - #if MICROPY_HW_ENABLE_FDCAN - uint32_t timeout_ms = args[ARG_timeout].u_int; - uint32_t start = HAL_GetTick(); - while (HAL_FDCAN_GetTxFifoFreeLevel(&self->can) == 0) { - if (timeout_ms == 0) { - mp_raise_OSError(MP_ETIMEDOUT); - } - // Check for the Timeout - if (timeout_ms != HAL_MAX_DELAY) { - if (HAL_GetTick() - start >= timeout_ms) { - mp_raise_OSError(MP_ETIMEDOUT); - } - } - MICROPY_EVENT_POLL_HOOK - } - status = HAL_FDCAN_AddMessageToTxFifoQ(&self->can, &tx_msg, tx_data); - #else - self->can.pTxMsg = &tx_msg; - status = CAN_Transmit(&self->can, args[ARG_timeout].u_int); - #endif + status = can_transmit(&self->can, &tx_msg, tx_data, args[ARG_timeout].u_int); if (status != HAL_OK) { mp_hal_raise(status); @@ -646,12 +615,8 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * uint8_t *rx_data = rx_msg.Data; #endif - mp_uint_t fifo = args[ARG_fifo].u_int; - if (fifo == 0) { - fifo = CAN_FIFO0; - } else if (fifo == 1) { - fifo = CAN_FIFO1; - } else { + can_rx_fifo_t fifo = args[ARG_fifo].u_int; + if (fifo != CAN_RX_FIFO0 && fifo != CAN_RX_FIFO1) { mp_raise_TypeError(NULL); } @@ -667,30 +632,28 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * #endif // Manage the rx state machine - if ((fifo == CAN_FIFO0 && self->rxcallback0 != mp_const_none) || - (fifo == CAN_FIFO1 && self->rxcallback1 != mp_const_none)) { - byte *state = (fifo == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; - + if ((fifo == CAN_RX_FIFO0 && self->rxcallback0 != mp_const_none) || + (fifo == CAN_RX_FIFO1 && self->rxcallback1 != mp_const_none)) { + bool fifo_empty = can_rx_pending(&self->can, fifo) == 0; + byte *state = (fifo == CAN_RX_FIFO0) ? &self->rx_state0 : &self->rx_state1; switch (*state) { case RX_STATE_FIFO_EMPTY: break; case RX_STATE_MESSAGE_PENDING: - if (__HAL_CAN_MSG_PENDING(&self->can, fifo) == 0) { - // Fifo is empty - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); + if (fifo_empty) { *state = RX_STATE_FIFO_EMPTY; } break; case RX_STATE_FIFO_FULL: - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); *state = RX_STATE_MESSAGE_PENDING; break; case RX_STATE_FIFO_OVERFLOW: - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); *state = RX_STATE_MESSAGE_PENDING; break; } + + // Re-enable any interrupts that were disabled in RX IRQ handlers + can_enable_rx_interrupts(&self->can, fifo, fifo_empty); } // Create the tuple, or get the list, that will hold the return values @@ -756,12 +719,12 @@ static mp_obj_t pyb_can_clearfilter(size_t n_args, const mp_obj_t *pos_args, mp_ mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); #if MICROPY_HW_ENABLE_FDCAN - can_clearfilter(self, f, args[ARG_extframe].u_bool); + can_clearfilter(&self->can, f, args[ARG_extframe].u_bool); #else if (self->can_id == 2) { f += can2_start_bank; } - can_clearfilter(self, f, can2_start_bank); + can_clearfilter(&self->can, f, can2_start_bank); #endif return mp_const_none; } @@ -945,16 +908,12 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_setfilter_obj, 1, pyb_can_setfilter); static mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t callback_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t fifo = mp_obj_get_int(fifo_in); + can_rx_fifo_t fifo = mp_obj_get_int(fifo_in); mp_obj_t *callback; - callback = (fifo == 0) ? &self->rxcallback0 : &self->rxcallback1; + callback = (fifo == CAN_RX_FIFO0) ? &self->rxcallback0 : &self->rxcallback1; if (callback_in == mp_const_none) { - __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); - __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); - __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); - __HAL_CAN_CLEAR_FLAG(&self->can, (fifo == CAN_FIFO0) ? CAN_FLAG_FIFO0_FULL : CAN_FLAG_FIFO1_FULL); - __HAL_CAN_CLEAR_FLAG(&self->can, (fifo == CAN_FIFO0) ? CAN_FLAG_FIFO0_OVRF : CAN_FLAG_FIFO1_OVRF); + can_disable_rx_interrupts(&self->can, fifo); *callback = mp_const_none; } else if (*callback != mp_const_none) { // Rx call backs has already been initialized @@ -964,21 +923,19 @@ static mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t *callback = callback_in; uint32_t irq = 0; if (self->can_id == PYB_CAN_1) { - irq = (fifo == 0) ? CAN1_RX0_IRQn : CAN1_RX1_IRQn; + irq = (fifo == CAN_RX_FIFO0) ? CAN1_RX0_IRQn : CAN1_RX1_IRQn; #if defined(CAN2) } else if (self->can_id == PYB_CAN_2) { - irq = (fifo == 0) ? CAN2_RX0_IRQn : CAN2_RX1_IRQn; + irq = (fifo == CAN_RX_FIFO0) ? CAN2_RX0_IRQn : CAN2_RX1_IRQn; #endif #if defined(CAN3) } else { - irq = (fifo == 0) ? CAN3_RX0_IRQn : CAN3_RX1_IRQn; + irq = (fifo == CAN_RX_FIFO0) ? CAN3_RX0_IRQn : CAN3_RX1_IRQn; #endif } NVIC_SetPriority(irq, IRQ_PRI_CAN); HAL_NVIC_EnableIRQ(irq); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); + can_enable_rx_interrupts(&self->can, fifo, true); } return mp_const_none; } @@ -1038,8 +995,8 @@ static mp_uint_t can_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i uintptr_t flags = arg; ret = 0; if ((flags & MP_STREAM_POLL_RD) - && ((__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO0) != 0) - || (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO1) != 0))) { + && ((can_rx_pending(&self->can, 0) != 0) + || (can_rx_pending(&self->can, 1) != 0))) { ret |= MP_STREAM_POLL_RD; } #if MICROPY_HW_ENABLE_FDCAN @@ -1057,17 +1014,62 @@ static mp_uint_t can_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i return ret; } -void pyb_can_handle_callback(pyb_can_obj_t *self, uint fifo_id, mp_obj_t callback, mp_obj_t irq_reason) { +// IRQ handler, called from lower layer can.c or fdcan.c in ISR context + +void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { + mp_obj_t callback; + pyb_can_obj_t *self; + byte *state; + + self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; + + if (fifo == CAN_RX_FIFO0) { + callback = self->rxcallback0; + state = &self->rx_state0; + } else { + callback = self->rxcallback1; + state = &self->rx_state1; + } + + switch (interrupt) { + // These interrupts go on to run a Python callback, interrupt arg is the + // 'interrupt' enum value as an int. + case CAN_INT_MESSAGE_RECEIVED: + *state = RX_STATE_MESSAGE_PENDING; + break; + case CAN_INT_FIFO_FULL: + *state = RX_STATE_FIFO_FULL; + break; + case CAN_INT_FIFO_OVERFLOW: + *state = RX_STATE_FIFO_OVERFLOW; + break; + + // These interrupts do not run a Python callback + case CAN_INT_ERR_BUS_OFF: + self->num_bus_off++; + return; + case CAN_INT_ERR_PASSIVE: + self->num_error_passive++; + return; + case CAN_INT_ERR_WARNING: + self->num_error_warning++; + return; + + default: + return; // Should be unreachable + } + + // Run the callback if (callback != mp_const_none) { mp_sched_lock(); gc_lock(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - mp_call_function_2(callback, MP_OBJ_FROM_PTR(self), irq_reason); + mp_call_function_2(callback, MP_OBJ_FROM_PTR(self), MP_OBJ_NEW_SMALL_INT(interrupt)); nlr_pop(); } else { // Uncaught exception; disable the callback so it doesn't run again. - pyb_can_rxcallback(MP_OBJ_FROM_PTR(self), MP_OBJ_NEW_SMALL_INT(fifo_id), mp_const_none); + pyb_can_rxcallback(MP_OBJ_FROM_PTR(self), MP_OBJ_NEW_SMALL_INT(fifo), mp_const_none); mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in CAN(%u) rx interrupt handler\n", self->can_id); mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } diff --git a/ports/stm32/pyb_can.h b/ports/stm32/pyb_can.h new file mode 100644 index 00000000000..a82043d7863 --- /dev/null +++ b/ports/stm32/pyb_can.h @@ -0,0 +1,58 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 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. + */ +#ifndef MICROPY_INCLUDED_STM32_PYB_CAN_H +#define MICROPY_INCLUDED_STM32_PYB_CAN_H + +#include "py/mphal.h" + +#if MICROPY_HW_ENABLE_CAN + +#include "py/obj.h" +#include "can.h" + +typedef struct _pyb_can_obj_t { + mp_obj_base_t base; + mp_obj_t rxcallback0; + mp_obj_t rxcallback1; + mp_uint_t can_id : 8; + bool is_enabled : 1; + byte rx_state0; + byte rx_state1; + uint16_t num_error_warning; + uint16_t num_error_passive; + uint16_t num_bus_off; + CAN_HandleTypeDef can; +} pyb_can_obj_t; + +extern const mp_obj_type_t pyb_can_type; + +void pyb_can_deinit_all(void); +void pyb_can_init0(void); + +void pyb_can_irq_handler(uint can_id, can_rx_fifo_t fifo, can_int_t interrupt); + +#endif +#endif From dbda43b9e14ac1aa8a293840d3242d3baa489b18 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 May 2024 17:48:31 +1000 Subject: [PATCH 0389/2098] tests/multi_pyb_can: Add multitests for pyboard CAN controller. Currently only classic CAN, but tests run on both the stm32 classic CAN controller and the FD-CAN controller with the same results. Signed-off-by: Angus Gratton --- tests/multi_pyb_can/rx_callback.py | 98 ++++++++++++++++++++++++++ tests/multi_pyb_can/rx_callback.py.exp | 9 +++ tests/multi_pyb_can/rx_filters.py | 50 +++++++++++++ tests/multi_pyb_can/rx_filters.py.exp | 15 ++++ 4 files changed, 172 insertions(+) create mode 100644 tests/multi_pyb_can/rx_callback.py create mode 100644 tests/multi_pyb_can/rx_callback.py.exp create mode 100644 tests/multi_pyb_can/rx_filters.py create mode 100644 tests/multi_pyb_can/rx_filters.py.exp diff --git a/tests/multi_pyb_can/rx_callback.py b/tests/multi_pyb_can/rx_callback.py new file mode 100644 index 00000000000..960a225c93f --- /dev/null +++ b/tests/multi_pyb_can/rx_callback.py @@ -0,0 +1,98 @@ +from pyb import CAN +import time +import errno + +# Test the various receive IRQs, including overflow + +rx_overflow = False + +REASONS = ["received", "full", "overflow"] + +# CAN IDs +ID_SPAM = 0x345 # messages spammed into the receive FIFO +ID_ACK_OFLOW = 0x055 # message the receiver sends after it's seen an overflow +ID_AFTER = 0x100 # message the sender sends after the ACK + + +def cb0(bus, reason): + global rx_overflow + if reason != 0 and not rx_overflow: + # exact timing of 'received' callbacks depends on controller type, + # so only log the other two + print("rx0 reason", REASONS[reason]) + if reason == 2: + rx_overflow = True + + +# Accept all standard IDs on FIFO 0 +def _enable_accept_all(): + if hasattr(CAN, "MASK"): # FD-CAN controller + can.setfilter(0, CAN.RANGE, 0, (0x0, 0x7FF), extframe=False) + else: + can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0), extframe=False) + + +# Receiver +def instance0(): + _enable_accept_all() + can.rxcallback(0, cb0) + + multitest.next() + multitest.wait("sender ready") + multitest.broadcast("receiver ready") + + while not rx_overflow: + pass # Resume ASAP after FIFO0 overflows + + can.send(b"overflow", ID_ACK_OFLOW) + + # drain the receive FIFO, making sure we read at least on ID_SPAM message + rxed_spam = False + while can.any(0): + msg = can.recv(0, timeout=0) + assert msg[0] == ID_SPAM + rxed_spam = True + print("rxed_spam", rxed_spam) + + # This should be the one message with ID_AFTER, there may be one or two spam messages as well + for _ in range(10): + msg = can.recv(0, timeout=500) + if msg[0] == ID_AFTER: + print(msg) + break + + # RX FIFO should be empty now + print("any", can.any(0)) + + +# Sender +def instance1(): + _enable_accept_all() + multitest.next() + multitest.broadcast("sender ready") + multitest.wait("receiver ready") + + # Spam out messages until the receiver tells us its RX FIFO is full. + # + # The RX FIFO on the receiver can vary from 3 deep (BXCAN) to 25 deep (STM32H7), + # so we keep sending to it until we see a CAN message on ID_ACK_OFLOW indicating + # the receiver's FIFO has overflowed + for i in range(255): + can.send(bytes([i] * 8), ID_SPAM, timeout=25) + if can.any(0): + print(can.recv(0)) # should be ID_ACK_OFLOW + break + # on boards like STM32H7 the TX FIFO is really deep, so don't fill it too quickly... + time.sleep_ms(1) + + # give the receiver some time to make space in the FIFO + time.sleep_ms(200) + + # send the final message, the receiver should get this one + can.send(b"aaaaa", ID_AFTER) + + # Sender's RX FIFO should also be empty at this point + print("any", can.any(0)) + + +can = CAN(1, CAN.NORMAL, baudrate=500_000, sample_point=75) diff --git a/tests/multi_pyb_can/rx_callback.py.exp b/tests/multi_pyb_can/rx_callback.py.exp new file mode 100644 index 00000000000..3ab197cc103 --- /dev/null +++ b/tests/multi_pyb_can/rx_callback.py.exp @@ -0,0 +1,9 @@ +--- instance0 --- +rx0 reason full +rx0 reason overflow +rxed_spam True +(256, False, False, 0, b'aaaaa') +any False +--- instance1 --- +(85, False, False, 0, b'overflow') +any False diff --git a/tests/multi_pyb_can/rx_filters.py b/tests/multi_pyb_can/rx_filters.py new file mode 100644 index 00000000000..22e5eb03647 --- /dev/null +++ b/tests/multi_pyb_can/rx_filters.py @@ -0,0 +1,50 @@ +from pyb import CAN +import time +import errno + +# Test for the filtering capabilities for RX FIFO 0 and 1. + + +# Receiver +def instance0(): + # Configure to receive standard frames (in a range) on FIFO 0 + # and extended frames (in a range) on FIFO 1. + if hasattr(CAN, "MASK"): + # FD-CAN has independent filter banks for standard and extended IDs + can.setfilter(0, CAN.MASK, 0, (0x300, 0x700), extframe=False) + can.setfilter(0, CAN.MASK, 1, (0x3000, 0x7000), extframe=True) + else: + # pyb.CAN only supports MASK32 for extended ids + can.setfilter(0, CAN.MASK16, 0, (0x300, 0x700, 0x300, 0x700), extframe=False) + can.setfilter(1, CAN.MASK32, 1, (0x3000, 0x7000), extframe=True) + + multitest.next() + multitest.wait("sender ready") + multitest.broadcast("receiver ready") + for i in range(3): + print(i) + print("fifo0", can.recv(0, timeout=200)) + print("fifo1", can.recv(1, timeout=200)) + + try: + can.recv(0, timeout=100) # should time out + except OSError as e: + assert e.errno == errno.ETIMEDOUT + print("Timed out as expected") + + +# Sender +def instance1(): + multitest.next() + multitest.broadcast("sender ready") + multitest.wait("receiver ready") + + for i in range(3): + print(i) + can.send(bytes([i, 3] * i), 0x345) + can.send(bytes([0xEE] * i), 0x3700 + i, extframe=True) + can.send(b"abcdef", 0x123) # matches no filter, expect ACKed but not received + time.sleep_ms(5) # avoid flooding either our or the receiver's FIFO + + +can = CAN(1, CAN.NORMAL, baudrate=500_000, sample_point=75) diff --git a/tests/multi_pyb_can/rx_filters.py.exp b/tests/multi_pyb_can/rx_filters.py.exp new file mode 100644 index 00000000000..65fbdf4b1b2 --- /dev/null +++ b/tests/multi_pyb_can/rx_filters.py.exp @@ -0,0 +1,15 @@ +--- instance0 --- +0 +fifo0 (837, False, False, 0, b'') +fifo1 (14080, True, False, 0, b'') +1 +fifo0 (837, False, False, 0, b'\x01\x03') +fifo1 (14081, True, False, 0, b'\xee') +2 +fifo0 (837, False, False, 0, b'\x02\x03\x02\x03') +fifo1 (14082, True, False, 0, b'\xee\xee') +Timed out as expected +--- instance1 --- +0 +1 +2 From f187c77da8b0ff51927b62cd1f4efd78c03bdb7f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 27 Feb 2025 09:03:17 +0100 Subject: [PATCH 0390/2098] shared/runtime/pyexec: Add helper function to execute a vstr. Add `pyexec_vstr()` to execute Python code from a vstr source. Signed-off-by: iabdalkader --- shared/runtime/pyexec.c | 5 +++++ shared/runtime/pyexec.h | 1 + 2 files changed, 6 insertions(+) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 9dc4446ed4d..c828c758179 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -720,6 +720,11 @@ int pyexec_frozen_module(const char *name, bool allow_keyboard_interrupt) { } #endif +int pyexec_vstr(vstr_t *str, bool allow_keyboard_interrupt) { + mp_uint_t exec_flags = allow_keyboard_interrupt ? 0 : EXEC_FLAG_NO_INTERRUPT; + return parse_compile_execute(str, MP_PARSE_FILE_INPUT, exec_flags | EXEC_FLAG_SOURCE_IS_VSTR); +} + #if MICROPY_REPL_INFO mp_obj_t pyb_set_repl_info(mp_obj_t o_value) { repl_display_debugging_info = mp_obj_get_int(o_value); diff --git a/shared/runtime/pyexec.h b/shared/runtime/pyexec.h index 5779d3e09a8..95f44816266 100644 --- a/shared/runtime/pyexec.h +++ b/shared/runtime/pyexec.h @@ -42,6 +42,7 @@ int pyexec_friendly_repl(void); int pyexec_file(const char *filename); int pyexec_file_if_exists(const char *filename); int pyexec_frozen_module(const char *name, bool allow_keyboard_interrupt); +int pyexec_vstr(vstr_t *str, bool allow_keyboard_interrupt); void pyexec_event_repl_init(void); int pyexec_event_repl_process_char(int c); extern uint8_t pyexec_repl_active; From 51976110e2da32b7a5b7035b7d5f17ab49bbd40e Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Thu, 12 Sep 2024 14:39:59 +0300 Subject: [PATCH 0391/2098] tools/mpy_ld.py: Allow linking static libraries. This commit introduces an additional symbol resolution mechanism to the natmod linking process. This allows the build scripts to look for required symbols into selected libraries that are provided by the compiler installation (libgcc and libm at the moment). For example, using soft-float code in natmods, whilst technically possible, was not an easy process and required some additional work to pull it off. With this addition all the manual (and error-prone) operations have been automated and folded into `tools/mpy_ld.py`. Both newlib and picolibc toolchains are supported, albeit the latter may require a bit of extra configuration depending on the environment the build process runs on. Picolibc's soft-float functions aren't in libm - in fact the shipped libm is nothing but a stub - but they are inside libc. This is usually not a problem as these changes cater for that configuration quirk, but on certain compilers the include paths used to find libraries in may not be updated to take Picolibc's library directory into account. The bare metal RISC-V compiler shipped with the CI OS image (GCC 10.2.0 on Ubuntu 22.04LTS) happens to exhibit this very problem. To work around that for CI builds, the Picolibc libraries' path is hardcoded in the Makefile directives used by the linker, but this can be changed by setting the PICOLIBC_ROOT environment library when building natmods. Signed-off-by: Volodymyr Shymanskyy Co-authored-by: Alessandro Gatti --- docs/develop/natmod.rst | 7 + examples/natmod/features2/Makefile | 3 + examples/natmod/features2/main.c | 11 ++ py/dynruntime.mk | 81 +++++++--- tools/ar_util.py | 236 +++++++++++++++++++++++++++++ tools/ci.sh | 44 +++++- tools/mpy_ld.py | 140 ++++++++++------- 7 files changed, 440 insertions(+), 82 deletions(-) create mode 100644 tools/ar_util.py diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index ba45e4305c6..18678eaefbf 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -70,6 +70,13 @@ The known limitations are: So, if your C code has writable data, make sure the data is defined globally, without an initialiser, and only written to within functions. +The native module is not automatically linked against the standard static libraries +like ``libm.a`` and ``libgcc.a``, which can lead to ``undefined symbol`` errors. +You can link the runtime libraries by setting ``LINK_RUNTIME = 1`` +in your Makefile. Custom static libraries can also be linked by adding +``MPY_LD_FLAGS += -l path/to/library.a``. Note that these are linked into +the native module and will not be shared with other modules or the system. + Linker limitation: the native module is not linked against the symbol table of the full MicroPython firmware. Rather, it is linked against an explicit table of exported symbols found in ``mp_fun_table`` (in ``py/nativeglue.h``), that is fixed at firmware diff --git a/examples/natmod/features2/Makefile b/examples/natmod/features2/Makefile index 4fd23c6879d..5ddb74087b7 100644 --- a/examples/natmod/features2/Makefile +++ b/examples/natmod/features2/Makefile @@ -10,5 +10,8 @@ SRC = main.c prod.c test.py # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) ARCH = x64 +# Link with libm.a and libgcc.a from the toolchain +LINK_RUNTIME = 1 + # Include to get the rules for compiling and linking the module include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features2/main.c b/examples/natmod/features2/main.c index 22961aa494f..5ce8e7b0130 100644 --- a/examples/natmod/features2/main.c +++ b/examples/natmod/features2/main.c @@ -1,5 +1,6 @@ /* This example demonstrates the following features in a native module: - using floats + - calling math functions from libm.a - defining additional code in Python (see test.py) - have extra C code in a separate file (see prod.c) */ @@ -10,6 +11,9 @@ // Include the header for auxiliary C code for this module #include "prod.h" +// Include standard library header +#include + // Automatically detect if this module should include double-precision code. // If double precision is supported by the target architecture then it can // be used in native module regardless of what float setting the target @@ -41,6 +45,12 @@ static mp_obj_t add_d(mp_obj_t x, mp_obj_t y) { static MP_DEFINE_CONST_FUN_OBJ_2(add_d_obj, add_d); #endif +// A function that uses libm +static mp_obj_t call_round(mp_obj_t x) { + return mp_obj_new_float_from_f(roundf(mp_obj_get_float_to_f(x))); +} +static MP_DEFINE_CONST_FUN_OBJ_1(round_obj, call_round); + // A function that computes the product of floats in an array. // This function uses the most general C argument interface, which is more difficult // to use but has access to the globals dict of the module via self->globals. @@ -74,6 +84,7 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a #if USE_DOUBLE mp_store_global(MP_QSTR_add_d, MP_OBJ_FROM_PTR(&add_d_obj)); #endif + mp_store_global(MP_QSTR_round, MP_OBJ_FROM_PTR(&round_obj)); // The productf function uses the most general C argument interface mp_store_global(MP_QSTR_productf, MP_DYNRUNTIME_MAKE_FUNCTION(productf)); diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 68d5e2e268f..807befb464a 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -29,16 +29,17 @@ CFLAGS += -Wall -Werror -DNDEBUG CFLAGS += -DNO_QSTR CFLAGS += -DMICROPY_ENABLE_DYNRUNTIME CFLAGS += -DMP_CONFIGFILE='<$(CONFIG_H)>' -CFLAGS += -fpic -fno-common -CFLAGS += -U _FORTIFY_SOURCE # prevent use of __*_chk libc functions -#CFLAGS += -fdata-sections -ffunction-sections + +CFLAGS_ARCH += -fpic -fno-common +CFLAGS_ARCH += -U_FORTIFY_SOURCE # prevent use of __*_chk libc functions +#CFLAGS_ARCH += -fdata-sections -ffunction-sections MPY_CROSS_FLAGS += -march=$(ARCH) SRC_O += $(addprefix $(BUILD)/, $(patsubst %.c,%.o,$(filter %.c,$(SRC))) $(patsubst %.S,%.o,$(filter %.S,$(SRC)))) SRC_MPY += $(addprefix $(BUILD)/, $(patsubst %.py,%.mpy,$(filter %.py,$(SRC)))) -CLEAN_EXTRA += $(MOD).mpy +CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache ################################################################################ # Architecture configuration @@ -47,72 +48,74 @@ ifeq ($(ARCH),x86) # x86 CROSS = -CFLAGS += -m32 -fno-stack-protector +CFLAGS_ARCH += -m32 -fno-stack-protector MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),x64) # x64 CROSS = -CFLAGS += -fno-stack-protector +CFLAGS_ARCH += -fno-stack-protector MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),armv6m) # thumb CROSS = arm-none-eabi- -CFLAGS += -mthumb -mcpu=cortex-m0 +CFLAGS_ARCH += -mthumb -mcpu=cortex-m0 MICROPY_FLOAT_IMPL ?= none else ifeq ($(ARCH),armv7m) # thumb CROSS = arm-none-eabi- -CFLAGS += -mthumb -mcpu=cortex-m3 +CFLAGS_ARCH += -mthumb -mcpu=cortex-m3 MICROPY_FLOAT_IMPL ?= none else ifeq ($(ARCH),armv7emsp) # thumb CROSS = arm-none-eabi- -CFLAGS += -mthumb -mcpu=cortex-m4 -CFLAGS += -mfpu=fpv4-sp-d16 -mfloat-abi=hard +CFLAGS_ARCH += -mthumb -mcpu=cortex-m4 +CFLAGS_ARCH += -mfpu=fpv4-sp-d16 -mfloat-abi=hard MICROPY_FLOAT_IMPL ?= float else ifeq ($(ARCH),armv7emdp) # thumb CROSS = arm-none-eabi- -CFLAGS += -mthumb -mcpu=cortex-m7 -CFLAGS += -mfpu=fpv5-d16 -mfloat-abi=hard +CFLAGS_ARCH += -mthumb -mcpu=cortex-m7 +CFLAGS_ARCH += -mfpu=fpv5-d16 -mfloat-abi=hard MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),xtensa) # xtensa CROSS = xtensa-lx106-elf- -CFLAGS += -mforce-l32 +CFLAGS_ARCH += -mforce-l32 MICROPY_FLOAT_IMPL ?= none else ifeq ($(ARCH),xtensawin) # xtensawin CROSS = xtensa-esp32-elf- -CFLAGS += MICROPY_FLOAT_IMPL ?= float else ifeq ($(ARCH),rv32imc) # rv32imc CROSS = riscv64-unknown-elf- -CFLAGS += -march=rv32imac -mabi=ilp32 -mno-relax +CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax # If Picolibc is available then select it explicitly. Ubuntu 22.04 ships its # bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default # is "nosys" so a value must be provided. To avoid having per-distro # workarounds, always select Picolibc if available. -PICOLIBC_SPECS = $(shell $(CROSS)gcc --print-file-name=picolibc.specs) +PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs) ifneq ($(PICOLIBC_SPECS),picolibc.specs) -CFLAGS += --specs=$(PICOLIBC_SPECS) +CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS) +USE_PICOLIBC := 1 +PICOLIBC_ARCH := rv32imac +PICOLIBC_ABI := ilp32 endif MICROPY_FLOAT_IMPL ?= none @@ -122,7 +125,47 @@ $(error architecture '$(ARCH)' not supported) endif MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]') -CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) +CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) + +ifeq ($(LINK_RUNTIME),1) +# All of these picolibc-specific directives are here to work around a +# limitation of Ubuntu 22.04's RISC-V bare metal toolchain. In short, the +# specific version of GCC in use (10.2.0) does not seem to take into account +# extra paths provided by an explicitly passed specs file when performing name +# resolution via `--print-file-name`. +# +# If Picolibc is used and libc.a fails to resolve, then said file's path will +# be computed by searching the Picolibc libraries root for a libc.a file in a +# subdirectory whose path is built using the current `-march` and `-mabi` +# flags that are passed to GCC. The `PICOLIBC_ROOT` environment variable is +# checked to override the starting point for the library file search, and if +# it is not set then the default value is used, assuming that this is running +# on an Ubuntu 22.04 machine. +# +# This should be revised when the CI base image is updated to a newer Ubuntu +# version (that hopefully contains a newer RISC-V compiler) or to another Linux +# distribution. +ifeq ($(USE_PICOLIBC),1) +LIBM_NAME := libc.a +else +LIBM_NAME := libm.a +endif +LIBGCC_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-libgcc-file-name)) +LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=$(LIBM_NAME))) +ifeq ($(USE_PICOLIBC),1) +ifeq ($(LIBM_PATH),) +# The CROSS toolchain prefix usually ends with a dash, but that may not be +# always the case. If the prefix ends with a dash it has to be taken out as +# Picolibc's architecture directory won't have it in its name. GNU Make does +# not have any facility to perform character-level text manipulation so we +# shell out to sed. +CROSS_PREFIX := $(shell echo $(CROSS) | sed -e 's/-$$//') +PICOLIBC_ROOT ?= /usr/lib/picolibc/$(CROSS_PREFIX)/lib +LIBM_PATH := $(PICOLIBC_ROOT)/$(PICOLIBC_ARCH)/$(PICOLIBC_ABI)/$(LIBM_NAME) +endif +endif +MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH)) +endif CFLAGS += $(CFLAGS_EXTRA) @@ -165,7 +208,7 @@ $(BUILD)/%.mpy: %.py # Build native .mpy from object files $(BUILD)/$(MOD).native.mpy: $(SRC_O) $(ECHO) "LINK $<" - $(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) -o $@ $^ + $(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) $(MPY_LD_FLAGS) -o $@ $^ # Build final .mpy from all intermediate .mpy files $(MOD).mpy: $(BUILD)/$(MOD).native.mpy $(SRC_MPY) diff --git a/tools/ar_util.py b/tools/ar_util.py new file mode 100644 index 00000000000..b90d3790314 --- /dev/null +++ b/tools/ar_util.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +# +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2024 Volodymyr Shymanskyy +# +# 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. + +import os +import re +import hashlib +import functools +import pickle + +from elftools.elf import elffile +from collections import defaultdict + +try: + from ar import Archive +except: + Archive = None + + +class PickleCache: + def __init__(self, path, prefix=""): + self.path = path + self._get_fn = lambda key: os.path.join(path, prefix + key[:24]) + + def store(self, key, data): + os.makedirs(self.path, exist_ok=True) + # See also https://bford.info/cachedir/ + cachedir_tag_path = os.path.join(self.path, "CACHEDIR.TAG") + if not os.path.exists(cachedir_tag_path): + with open(cachedir_tag_path, "w") as f: + f.write( + "Signature: 8a477f597d28d172789f06886806bc55\n" + "# This file is a cache directory tag created by MicroPython.\n" + "# For information about cache directory tags see https://bford.info/cachedir/\n" + ) + with open(self._get_fn(key), "wb") as f: + pickle.dump(data, f) + + def load(self, key): + with open(self._get_fn(key), "rb") as f: + return pickle.load(f) + + +def cached(key, cache): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + cache_key = key(*args, **kwargs) + try: + d = cache.load(cache_key) + if d["key"] != cache_key: + raise Exception("Cache key mismatch") + return d["data"] + except Exception: + res = func(*args, **kwargs) + try: + cache.store( + cache_key, + { + "key": cache_key, + "data": res, + }, + ) + except Exception: + pass + return res + + return wrapper + + return decorator + + +class CachedArFile: + def __init__(self, fn): + if not Archive: + raise RuntimeError("Please run 'pip install ar' to link .a files") + self.fn = fn + self._archive = Archive(open(fn, "rb")) + info = self.load_symbols() + self.objs = info["objs"] + self.symbols = info["symbols"] + + def open(self, obj): + return self._archive.open(obj, "rb") + + def _cache_key(self): + sha = hashlib.sha256() + with open(self.fn, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha.update(chunk) + # Change this salt if the cache data format changes + sha.update(bytes.fromhex("00000000000000000000000000000001")) + return sha.hexdigest() + + @cached(key=_cache_key, cache=PickleCache(path=".mpy_ld_cache", prefix="ar_")) + def load_symbols(self): + print("Loading", self.fn) + objs = defaultdict(lambda: {"def": set(), "undef": set(), "weak": set()}) + symbols = {} + for entry in self._archive: + obj_name = entry.name + elf = elffile.ELFFile(self.open(obj_name)) + symtab = elf.get_section_by_name(".symtab") + if not symtab: + continue + + obj = objs[obj_name] + + for symbol in symtab.iter_symbols(): + sym_name = symbol.name + sym_bind = symbol["st_info"]["bind"] + + if sym_bind in ("STB_GLOBAL", "STB_WEAK"): + if symbol.entry["st_shndx"] != "SHN_UNDEF": + obj["def"].add(sym_name) + symbols[sym_name] = obj_name + else: + obj["undef"].add(sym_name) + + if sym_bind == "STB_WEAK": + obj["weak"].add(sym_name) + + return {"objs": dict(objs), "symbols": symbols} + + +def resolve(archives, symbols): + resolved_objs = [] # Object files needed to resolve symbols + unresolved_symbols = set() + provided_symbols = {} # Which symbol is provided by which object + symbol_stack = list(symbols) + + # A helper function to handle symbol resolution from a particular object + def add_obj(archive, symbol): + obj_name = archive.symbols[symbol] + obj_info = archive.objs[obj_name] + + obj_tuple = (archive, obj_name) + if obj_tuple in resolved_objs: + return # Already processed this object + + resolved_objs.append(obj_tuple) + + # Add the symbols this object defines + for defined_symbol in obj_info["def"]: + if defined_symbol in provided_symbols and not defined_symbol.startswith( + "__x86.get_pc_thunk." + ): + if defined_symbol in obj_info["weak"]: + continue + else: + raise RuntimeError(f"Multiple definitions for {defined_symbol}") + provided_symbols[defined_symbol] = obj_name # TODO: mark weak if needed + + # Recursively add undefined symbols from this object + for undef_symbol in obj_info["undef"]: + if undef_symbol in obj_info["weak"]: + print(f"Skippping weak dependency: {undef_symbol}") + continue + if undef_symbol not in provided_symbols: + symbol_stack.append(undef_symbol) # Add undefined symbol to resolve + + while symbol_stack: + symbol = symbol_stack.pop(0) + + if symbol in provided_symbols: + continue # Symbol is already resolved + + found = False + for archive in archives: + if symbol in archive.symbols: + add_obj(archive, symbol) + found = True + break + + if not found: + unresolved_symbols.add(symbol) + + return resolved_objs, list(unresolved_symbols) + + +def expand_ld_script(fn): + # This function parses a subset of ld scripts + # Typically these are just groups of static lib references + group_pattern = re.compile(r"GROUP\s*\(\s*([^\)]+)\s*\)", re.MULTILINE) + output_format_pattern = re.compile(r"OUTPUT_FORMAT\s*\(\s*([^\)]+)\s*\)", re.MULTILINE) + comment_pattern = re.compile(r"/\*.*?\*/", re.MULTILINE | re.DOTALL) + + with open(fn, "r") as f: + content = f.read() + content = comment_pattern.sub("", content).strip() + + # Ensure no unrecognized instructions + leftovers = content + for pattern in (group_pattern, output_format_pattern): + leftovers = pattern.sub("", leftovers) + if leftovers.strip(): + raise ValueError("Invalid instruction found in the ld script:" + leftovers) + + # Extract files from GROUP instructions + files = [] + for match in group_pattern.findall(content): + files.extend([file.strip() for file in re.split(r"[,\s]+", match) if file.strip()]) + + return files + + +def load_archive(fn): + ar_header = b"!\012" + with open(fn, "rb") as f: + is_ar_file = f.read(len(ar_header)) == ar_header + if is_ar_file: + return [CachedArFile(fn)] + else: + return [CachedArFile(item) for item in expand_ld_script(fn)] diff --git a/tools/ci.sh b/tools/ci.sh index 682c3ae507a..5d57175c80e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -155,12 +155,15 @@ PYTHON_VER=$(python --version | cut -d' ' -f2) export IDF_CCACHE_ENABLE=1 function ci_esp32_idf_setup { - pip3 install pyelftools git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git # doing a treeless clone isn't quite as good as --shallow-submodules, but it # is smaller than full clones and works when the submodule commit isn't a head. git -C esp-idf submodule update --init --recursive --filter=tree:0 ./esp-idf/install.sh + # Install additional packages for mpy_ld into the IDF env + source esp-idf/export.sh + pip3 install pyelftools + pip3 install ar } function ci_esp32_build_common { @@ -287,6 +290,7 @@ function ci_qemu_setup_arm { sudo apt-get update sudo apt-get install qemu-system sudo pip3 install pyelftools + sudo pip3 install ar qemu-system-arm --version } @@ -295,6 +299,7 @@ function ci_qemu_setup_rv32 { sudo apt-get update sudo apt-get install qemu-system sudo pip3 install pyelftools + sudo pip3 install ar qemu-system-riscv32 --version } @@ -385,6 +390,7 @@ function ci_samd_build { function ci_stm32_setup { ci_gcc_arm_setup pip3 install pyelftools + pip3 install ar pip3 install pyhy } @@ -503,18 +509,40 @@ function ci_native_mpy_modules_build { else arch=$1 fi - for natmod in features1 features3 features4 deflate framebuf heapq random re + for natmod in features1 features3 features4 heapq re do + make -C examples/natmod/$natmod clean make -C examples/natmod/$natmod ARCH=$arch done - # btree requires thread local storage support on rv32imc. - if [ $arch != rv32imc ]; then - make -C examples/natmod/btree ARCH=$arch + + # deflate, framebuf, and random currently cannot build on xtensa due to + # some symbols that have been removed from the compiler's runtime, in + # favour of being provided from ROM. + if [ $arch != "xtensa" ]; then + for natmod in deflate framebuf random + do + make -C examples/natmod/$natmod clean + make -C examples/natmod/$natmod ARCH=$arch + done fi - # features2 requires soft-float on armv7m and rv32imc. - if [ $arch != rv32imc ] && [ $arch != armv7m ]; then + + # features2 requires soft-float on armv7m, rv32imc, and xtensa. On armv6m + # the compiler generates absolute relocations in the object file + # referencing soft-float functions, which is not supported at the moment. + make -C examples/natmod/features2 clean + if [ $arch = "rv32imc" ] || [ $arch = "armv7m" ] || [ $arch = "xtensa" ]; then + make -C examples/natmod/features2 ARCH=$arch MICROPY_FLOAT_IMPL=float + elif [ $arch != "armv6m" ]; then make -C examples/natmod/features2 ARCH=$arch fi + + # btree requires thread local storage support on rv32imc, whilst on xtensa + # it relies on symbols that are provided from ROM but not exposed to + # natmods at the moment. + if [ $arch != "rv32imc" ] && [ $arch != "xtensa" ]; then + make -C examples/natmod/btree clean + make -C examples/natmod/btree ARCH=$arch + fi } function ci_native_mpy_modules_32bit_build { @@ -550,6 +578,7 @@ function ci_unix_standard_v2_run_tests { function ci_unix_coverage_setup { sudo pip3 install setuptools sudo pip3 install pyelftools + sudo pip3 install ar gcc --version python3 --version } @@ -598,6 +627,7 @@ function ci_unix_32bit_setup { sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 python2.7 sudo pip3 install setuptools sudo pip3 install pyelftools + sudo pip3 install ar gcc --version python2.7 --version python3 --version diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 54295208f1a..44a76bdee6b 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -30,6 +30,7 @@ import sys, os, struct, re from elftools.elf import elffile +import ar_util sys.path.append(os.path.dirname(__file__) + "/../py") import makeqstrdata as qstrutil @@ -664,7 +665,7 @@ def do_relocation_text(env, text_addr, r): R_XTENSA_PDIFF32, R_XTENSA_ASM_EXPAND, ): - if s.section.name.startswith(".text"): + if not hasattr(s, "section") or s.section.name.startswith(".text"): # it looks like R_XTENSA_[P]DIFF32 into .text is already correctly relocated, # and expand relaxations cannot occur in non-executable sections. return @@ -1075,59 +1076,59 @@ def process_riscv32_relocation(env, text_addr, r): return addr, value -def load_object_file(env, felf): - with open(felf, "rb") as f: - elf = elffile.ELFFile(f) - env.check_arch(elf["e_machine"]) - - # Get symbol table - symtab = list(elf.get_section_by_name(".symtab").iter_symbols()) - - # Load needed sections from ELF file - sections_shndx = {} # maps elf shndx to Section object - for idx, s in enumerate(elf.iter_sections()): - if s.header.sh_type in ("SHT_PROGBITS", "SHT_NOBITS"): - if s.data_size == 0: - # Ignore empty sections - pass - elif s.name.startswith((".literal", ".text", ".rodata", ".data.rel.ro", ".bss")): - sec = Section.from_elfsec(s, felf) - sections_shndx[idx] = sec - if s.name.startswith(".literal"): - env.literal_sections.append(sec) - else: - env.sections.append(sec) - elif s.name.startswith(".data"): - raise LinkError("{}: {} non-empty".format(felf, s.name)) +def load_object_file(env, f, felf): + elf = elffile.ELFFile(f) + env.check_arch(elf["e_machine"]) + + # Get symbol table + symtab = list(elf.get_section_by_name(".symtab").iter_symbols()) + + # Load needed sections from ELF file + sections_shndx = {} # maps elf shndx to Section object + for idx, s in enumerate(elf.iter_sections()): + if s.header.sh_type in ("SHT_PROGBITS", "SHT_NOBITS"): + if s.data_size == 0: + # Ignore empty sections + pass + elif s.name.startswith((".literal", ".text", ".rodata", ".data.rel.ro", ".bss")): + sec = Section.from_elfsec(s, felf) + sections_shndx[idx] = sec + if s.name.startswith(".literal"): + env.literal_sections.append(sec) else: - # Ignore section - pass - elif s.header.sh_type in ("SHT_REL", "SHT_RELA"): - shndx = s.header.sh_info - if shndx in sections_shndx: - sec = sections_shndx[shndx] - sec.reloc_name = s.name - sec.reloc = list(s.iter_relocations()) - for r in sec.reloc: - r.sym = symtab[r["r_info_sym"]] - - # Link symbols to their sections, and update known and unresolved symbols - for sym in symtab: - sym.filename = felf - shndx = sym.entry["st_shndx"] + env.sections.append(sec) + elif s.name.startswith(".data"): + raise LinkError("{}: {} non-empty".format(felf, s.name)) + else: + # Ignore section + pass + elif s.header.sh_type in ("SHT_REL", "SHT_RELA"): + shndx = s.header.sh_info if shndx in sections_shndx: - # Symbol with associated section - sym.section = sections_shndx[shndx] - if sym["st_info"]["bind"] in ("STB_GLOBAL", "STB_WEAK"): - # Defined global symbol - if sym.name in env.known_syms and not sym.name.startswith( - "__x86.get_pc_thunk." - ): - raise LinkError("duplicate symbol: {}".format(sym.name)) - env.known_syms[sym.name] = sym - elif sym.entry["st_shndx"] == "SHN_UNDEF" and sym["st_info"]["bind"] == "STB_GLOBAL": - # Undefined global symbol, needs resolving - env.unresolved_syms.append(sym) + sec = sections_shndx[shndx] + sec.reloc_name = s.name + sec.reloc = list(s.iter_relocations()) + for r in sec.reloc: + r.sym = symtab[r["r_info_sym"]] + + # Link symbols to their sections, and update known and unresolved symbols + dup_errors = [] + for sym in symtab: + sym.filename = felf + shndx = sym.entry["st_shndx"] + if shndx in sections_shndx: + # Symbol with associated section + sym.section = sections_shndx[shndx] + if sym["st_info"]["bind"] in ("STB_GLOBAL", "STB_WEAK"): + # Defined global symbol + if sym.name in env.known_syms and not sym.name.startswith("__x86.get_pc_thunk."): + dup_errors.append("duplicate symbol: {}".format(sym.name)) + env.known_syms[sym.name] = sym + elif sym.entry["st_shndx"] == "SHN_UNDEF" and sym["st_info"]["bind"] == "STB_GLOBAL": + # Undefined global symbol, needs resolving + env.unresolved_syms.append(sym) + if len(dup_errors): + raise LinkError("\n".join(dup_errors)) def link_objects(env, native_qstr_vals_len): @@ -1188,6 +1189,8 @@ def link_objects(env, native_qstr_vals_len): ] ) } + + undef_errors = [] for sym in env.unresolved_syms: assert sym["st_value"] == 0 if sym.name == "_GLOBAL_OFFSET_TABLE_": @@ -1205,7 +1208,10 @@ def link_objects(env, native_qstr_vals_len): sym.section = mp_fun_table_sec sym.mp_fun_table_offset = fun_table[sym.name] else: - raise LinkError("{}: undefined symbol: {}".format(sym.filename, sym.name)) + undef_errors.append("{}: undefined symbol: {}".format(sym.filename, sym.name)) + + if len(undef_errors): + raise LinkError("\n".join(undef_errors)) # Align sections, assign their addresses, and create full_text env.full_text = bytearray(env.arch.asm_jump(8)) # dummy, to be filled in later @@ -1446,8 +1452,27 @@ def do_link(args): log(LOG_LEVEL_2, "qstr vals: " + ", ".join(native_qstr_vals)) env = LinkEnv(args.arch) try: - for file in args.files: - load_object_file(env, file) + # Load object files + for fn in args.files: + with open(fn, "rb") as f: + load_object_file(env, f, fn) + + if args.libs: + # Load archive info + archives = [] + for item in args.libs: + archives.extend(ar_util.load_archive(item)) + # List symbols to look for + syms = set(sym.name for sym in env.unresolved_syms) + # Resolve symbols from libs + lib_objs, _ = ar_util.resolve(archives, syms) + # Load extra object files from libs + for ar, obj in lib_objs: + obj_name = ar.fn + ":" + obj + log(LOG_LEVEL_2, "using " + obj_name) + with ar.open(obj) as f: + load_object_file(env, f, obj_name) + link_objects(env, len(native_qstr_vals)) build_mpy(env, env.find_addr("mpy_init"), args.output, native_qstr_vals) except LinkError as er: @@ -1458,13 +1483,16 @@ def do_link(args): def main(): import argparse - cmd_parser = argparse.ArgumentParser(description="Run scripts on the pyboard.") + cmd_parser = argparse.ArgumentParser(description="Link native object files into a MPY bundle.") cmd_parser.add_argument( "--verbose", "-v", action="count", default=1, help="increase verbosity" ) cmd_parser.add_argument("--arch", default="x64", help="architecture") cmd_parser.add_argument("--preprocess", action="store_true", help="preprocess source files") cmd_parser.add_argument("--qstrs", default=None, help="file defining additional qstrs") + cmd_parser.add_argument( + "--libs", "-l", dest="libs", action="append", help="static .a libraries to link" + ) cmd_parser.add_argument( "--output", "-o", default=None, help="output .mpy file (default to input with .o->.mpy)" ) From 1a7cbac879fa578cd992a3baf10950180e89340d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 7 Mar 2025 00:04:14 +0100 Subject: [PATCH 0392/2098] tools/ci.sh: Build Xtensa natmods as part of the CI process. This commit expands the CI tests by checking whether the example native modules are able to be built for the Xtensa architecture. This was made possible by the changes to mpy_ld that allow symbol resolution across standard compiler-provided libraries. Signed-off-by: Alessandro Gatti --- tools/ci.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 5d57175c80e..7105b2460b7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -197,7 +197,7 @@ function ci_esp32_build_s3_c3 { # ports/esp8266 function ci_esp8266_setup { - sudo pip install pyserial esptool==3.3.1 + sudo pip3 install pyserial esptool==3.3.1 pyelftools ar wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz zcat xtensa-lx106-elf-standalone.tar.gz | tar x # Remove this esptool.py so pip version is used instead @@ -214,6 +214,9 @@ function ci_esp8266_build { make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_512K make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_1M + + # Test building native .mpy with xtensa architecture. + ci_native_mpy_modules_build xtensa } ######################################################################################## From 31a008c6e26eccc3798a9ab4169019a02eadb830 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 7 Mar 2025 00:07:11 +0100 Subject: [PATCH 0393/2098] tools/ci.sh: Do not assume the Python interpreter is called "python". This commit removes the assumption made by the CI scripts that the system-provided python executable is simply named "python". The scripts will now look for a binary called "python3" first, and then fall back to "python" if that is not found. Whilst this is currently the case for the CI environment, there are no guarantees for this going forward. For example minimal CI environments set up by some developers, using the same base OS, have their python executable called "python3". Signed-off-by: Alessandro Gatti --- tools/ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 7105b2460b7..c7a6db79d6c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -150,7 +150,8 @@ function ci_cc3200_build { # GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) IDF_VER=v5.2.2 -PYTHON_VER=$(python --version | cut -d' ' -f2) +PYTHON=$(command -v python3 2> /dev/null) +PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) export IDF_CCACHE_ENABLE=1 From f1018ee5c2dd0eabcc0cfe7e02c607594e489788 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 3 Mar 2025 13:48:41 +0100 Subject: [PATCH 0394/2098] mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT: Add new Makerdiary board. This is a board based on the i.MX RT1011 in breadboard-friendly shape. A good basic board with 16M flash and regular pinout, providing access to many GPIO_nn and GPIO_AD_nn Pins. Signed-off-by: robert-hh --- docs/mimxrt/pinout.rst | 67 +++++++++----- .../MAKERDIARY_RT1011_NANO_KIT/board.json | 19 ++++ .../deploy_makerdiary.md | 34 ++++++++ .../mpconfigboard.h | 87 +++++++++++++++++++ .../mpconfigboard.mk | 16 ++++ .../MAKERDIARY_RT1011_NANO_KIT/pins.csv | 42 +++++++++ 6 files changed, 245 insertions(+), 20 deletions(-) create mode 100644 ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/board.json create mode 100644 ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md create mode 100644 ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.h create mode 100644 ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.mk create mode 100644 ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/pins.csv diff --git a/docs/mimxrt/pinout.rst b/docs/mimxrt/pinout.rst index 3ff892c64d6..9aeb85401be 100644 --- a/docs/mimxrt/pinout.rst +++ b/docs/mimxrt/pinout.rst @@ -30,26 +30,28 @@ MIMXRT1170-EVK Debug USB D0/D1 D12/D11 D10/D13 Adafruit Metro M7 - D0/D1 D7/D3 A1/A0 Olimex RT1010Py - RxD/TxD D7/D8 D5/D6 Seeed ARCH MIX - J3_19/J3_20 J4_16/J4_17 J4_06/J4_07 +Makerdiary RT1011 - D9/D10 D13/A0 D11/D12 ================= =========== =========== =========== =========== | -================ =========== =========== ======= ======= ===== -Board / Pin UART4 UART5 UART6 UART7 UART8 -================ =========== =========== ======= ======= ===== -Teensy 4.0 16/17 21/20 25/24 28/29 - -Teensy 4.1 16/17 21/20 25/24 28/29 34/35 -MIMXRT1010-EVK - - - - - -MIMXRT1015-EVK - - - - - -MIMXRT1020-EVK D15/D14 A1/A0 - - - -MIMXRT1050-EVK A1/A0 - - - - -MIMXRT1050-EVKB A1/A0 - - - - -MIMXRT1060-EVK A1/A0 - - - - -MIMXRT1064-EVK A1/A0 - - - - -MIMXRT1170-EVK D15/D14 D25/D26 D33/D34 D35/D36 - -Olimex RT1010Py - - - - - -Seeed ARCH MIX J4_10/J4_11 J5_08/J5_12 - - - -================ =========== =========== ======= ======= ===== +================= =========== =========== ======= ======= ===== +Board / Pin UART4 UART5 UART6 UART7 UART8 +================= =========== =========== ======= ======= ===== +Teensy 4.0 16/17 21/20 25/24 28/29 - +Teensy 4.1 16/17 21/20 25/24 28/29 34/35 +MIMXRT1010-EVK - - - - - +MIMXRT1015-EVK - - - - - +MIMXRT1020-EVK D15/D14 A1/A0 - - - +MIMXRT1050-EVK A1/A0 - - - - +MIMXRT1050-EVKB A1/A0 - - - - +MIMXRT1060-EVK A1/A0 - - - - +MIMXRT1064-EVK A1/A0 - - - - +MIMXRT1170-EVK D15/D14 D25/D26 D33/D34 D35/D36 - +Olimex RT1010Py - - - - - +Seeed ARCH MIX J4_10/J4_11 J5_08/J5_12 - - - +Makerdiary RT1011 A1/A2 - - - - +================= =========== =========== ======= ======= ===== .. _mimxrt_pwm_pinout: @@ -188,7 +190,6 @@ LED_BLUE F1/3/B ========= =============== Pin Olimex RT1010PY ========= =============== -D0 - D1 F1/0/B D2 F1/0/A D3 F1/1/B @@ -197,13 +198,10 @@ D5 F1/2/B D6 F1/2/A D7 F1/3/B D8 F1/3/A -D9 - D10 F1/0/B D11 F1/0/A D12 F1/1/B D13 F1/1/A -D14 - -A0 - A1 F1/2/B A2 F1/2/A A3 F1/3/B @@ -214,6 +212,32 @@ CS0 F1/1/X SCK F1/0/X ========= =============== +| + +========= ================= +Pin Makerdiary RT1011 +========= ================= +D1 F1/0/B +D2 F1/0/A +D3 F1/1/B +D4 F1/1/A +D5 F1/2/B +D6 F1/2/A +D7 F1/3/B +D8 F1/3/A +A3 F1/2/B +A4 F1/2/A +A5 F1/3/B +A6 F1/3/A +A9 F1/3/X +A10 F1/2/X +A11 F1/1/X +SD1 F1/0/B +SD2 F1/0/A +LED F1/1/B +DIO F1/0/X +========= ================= + Legend: * Qm/n: QTMR module m, channel n @@ -322,6 +346,7 @@ MIXMXRT1170-EVK D10/-/D11/D12/D13 D28/-/D25/D24/D26 -/-/D14/D Adafruit Metro M7 -/-/MOSI/MISO/SCK - - Olimex RT1010Py - CS0/-/SDO/SDI/SCK SDCARD with CS1 Seeed ARCH MIX J4_12/-/J4_14/J4_13/J4_15 J3_09/J3_05/J3_08_J3_11 +Makerdiary RT1011 A5/A2/A4/A3/A6 A11/A1/A10/A9/CLK ================= ========================= ======================= =============== Pins denoted with (*) are by default not wired at the board. The CS0 and CS1 signals @@ -355,6 +380,7 @@ MIXMXRT1170-EVK D14/D15 D1/D0 A4/A5 D26/D25 D19/D18 Adafruit Metro M7 D14/D15 D0/D1 Olimex RT1010Py - SDA1/SCL1 SDA2/SCL2 - - Seeed ARCH MIX J3_17/J3_16 J4_06/J4_07 J5_05/J5_04 - - +Makerdiary RT1011 D1/D2 A7/A8 ================= =========== =========== =========== ======= ======= .. _mimxrt_i2s_pinout: @@ -379,6 +405,7 @@ Adafruit Metro M7 1 D8 D10 D9 D12 D14 D15 D13 Olimex RT1010Py 1 D8 D6 D7 D4 D1 D2 D3 Olimex RT1010Py 3 - D10 D9 D11 - - - MIMXRT_DEV 1 "MCK" "SCK_TX" "WS_TX" "SD_TX" "SCK_RX" "WS_RX" "SD_RX" +Makerdiary RT1011 1 D8 SD1 D7 D4 D1 D2 D3 ================= == ===== ======== ======= ======= ======== ======= ======= Symbolic pin names are provided for the MIMXRT_10xx_DEV boards. diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/board.json b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/board.json new file mode 100644 index 00000000000..ee04a5965fe --- /dev/null +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "deploy_makerdiary.md" + ], + "docs": "", + "features": [ + "External Flash", + "USB", + "microSD" + ], + "images": [ + "MAKERDIARY_RT1011_NANO_KIT.jpg" + ], + "mcu": "mimxrt", + "product": "iMX RT1011 Nano Kit", + "thumbnail": "", + "url": "https://makerdiary.com/products/imxrt1011-nanokit", + "vendor": "Makerdiary" +} diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md new file mode 100644 index 00000000000..fde33815922 --- /dev/null +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md @@ -0,0 +1,34 @@ +## 1. Deploy the MicroPython firmware to the iMX RT1011 Nano Kit. + +The iMX RT1011 Nano Kit comes pre-installed with a UF2 bootloader. +It can be started by pushing reset twice. Then the bootloader drive will +appear. If that does not happen or the bootloader was lost, you can +reinstall the bootloader using the procedure below. + +Once the bootloader is installed and started, you can install MicroPython +by copying the .uf2 version of the firmware file to the bootloader +drive. When the firmware is installed, the drive will disappear. + +## 2. Reinstall the bootloader iMX RT1011 Nano Kit. + +1. Get the NXP program sdphost for your operating system, e.g. from +https://github.com/adafruit/tinyuf2/tree/master/ports/mimxrt10xx/sdphost. +You can also get them from the NXP web sites. + +2. Get the UF2 boot-loader package https://github.com/adafruit/tinyuf2/releases/tag/0.21.0 +and extract the file tinyuf2-makerdiary_rt1011-0.21.0.bin. You may as +well go for a newer release. + +Now you have all files at hand that you will need for updating. + +1. Push and hold the "USR/BT" button, then press "RST", and release both buttons. + +2. Run the commands: + +``` +sudo ./sdphost -u 0x1fc9,0x0145 -- write-file 0x20206400 tinyuf2-makerdiary_rt1011-0.21.0.bin +sudo ./sdphost -u 0x1fc9,0x0145 -- jump-address 0x20207000 +``` +Wait until a drive icon appears on the computer (or mount it explicitly). +At this point the bootloader is installed. You can now copy the +MicroPython .uf2 file to the board. diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.h b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.h new file mode 100644 index 00000000000..28085294f14 --- /dev/null +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.h @@ -0,0 +1,87 @@ +#define MICROPY_HW_BOARD_NAME "iMX_RT1011_Nano_Kit" +#define MICROPY_HW_MCU_NAME "MIMXRT1011DAE5A" +#define MICROPY_HW_USB_MANUFACTURER_STRING "Makerdiary" +#define MICROPY_HW_USB_VID 0xf055 +#define MICROPY_HW_USB_PID 0x9802 +#define MICROPY_PY_OS_DUPTERM_BUILTIN_STREAM (0) + +// RT1011-Nanokit has 1 board LED +#define MICROPY_HW_LED1_PIN (pin_GPIO_SD_04) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) + +#define MICROPY_HW_NUM_PIN_IRQS (2 * 32) +#define MICROPY_PY_MACHINE_SDCARD (0) + + +#define MICROPY_HW_UART_NUM (sizeof(uart_index_table) / sizeof(uart_index_table)[0]) +#define MICROPY_HW_UART_INDEX { 0, 1, 2, 3, 4 } + +#define IOMUX_TABLE_UART \ + { IOMUXC_GPIO_10_LPUART1_TXD }, { IOMUXC_GPIO_09_LPUART1_RXD }, \ + { IOMUXC_GPIO_AD_00_LPUART2_TXD }, { IOMUXC_GPIO_13_LPUART2_RXD }, \ + { IOMUXC_GPIO_12_LPUART3_TXD }, { IOMUXC_GPIO_11_LPUART3_RXD }, \ + { IOMUXC_GPIO_AD_02_LPUART4_TXD }, { IOMUXC_GPIO_AD_01_LPUART4_RXD }, + +#define IOMUX_TABLE_UART_CTS_RTS \ + { IOMUXC_GPIO_08_LPUART1_CTS_B }, { IOMUXC_GPIO_07_LPUART1_RTS_B }, \ + { IOMUXC_GPIO_AD_08_LPUART2_CTS_B }, { IOMUXC_GPIO_AD_07_LPUART2_RTS_B }, \ + { IOMUXC_GPIO_AD_14_LPUART3_CTS_B }, { IOMUXC_GPIO_AD_13_LPUART3_RTS_B }, \ + { IOMUXC_GPIO_AD_14_LPUART4_CTS_B }, { IOMUXC_GPIO_AD_13_LPUART4_RTS_B }, + +#define MICROPY_HW_SPI_INDEX { 1, 2 } + +#define IOMUX_TABLE_SPI \ + { IOMUXC_GPIO_AD_06_LPSPI1_SCK }, { IOMUXC_GPIO_AD_05_LPSPI1_PCS0 }, \ + { IOMUXC_GPIO_AD_04_LPSPI1_SDO }, { IOMUXC_GPIO_AD_03_LPSPI1_SDI }, \ + { IOMUXC_GPIO_AD_02_LPSPI1_PCS1 }, \ + { IOMUXC_GPIO_AD_12_LPSPI2_SCK }, { IOMUXC_GPIO_AD_11_LPSPI2_PCS0 }, \ + { IOMUXC_GPIO_AD_10_LPSPI2_SDO }, { IOMUXC_GPIO_AD_09_LPSPI2_SDI }, \ + { IOMUXC_GPIO_AD_01_LPSPI2_PCS1 } + +#define DMA_REQ_SRC_RX { 0, kDmaRequestMuxLPSPI1Rx, kDmaRequestMuxLPSPI2Rx } +#define DMA_REQ_SRC_TX { 0, kDmaRequestMuxLPSPI1Tx, kDmaRequestMuxLPSPI2Tx } + +// Define mapping hardware I2C # to logical I2C # +// SDA/SCL HW-I2C Logical I2C +// SDA1/SCL1 LPI2C1 -> 0 +// SDA2/SCL2 LPI2C2 -> 1 + +#define MICROPY_HW_I2C_INDEX { 1, 2 } + +#define IOMUX_TABLE_I2C \ + { IOMUXC_GPIO_02_LPI2C1_SCL }, { IOMUXC_GPIO_01_LPI2C1_SDA }, \ + { IOMUXC_GPIO_AD_08_LPI2C2_SCL }, { IOMUXC_GPIO_AD_07_LPI2C2_SDA }, + +#define MICROPY_PY_MACHINE_I2S (1) +#define MICROPY_HW_I2S_NUM (3) +#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, 0, kCLOCK_Sai3Mux } +#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, 0, kCLOCK_Sai3PreDiv } +#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, 0, kCLOCK_Sai3Div } +#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, 0, kIOMUXC_GPR_SAI3MClkOutputDir } +#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, 0, kDmaRequestMuxSai3Rx } +#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, 0, kDmaRequestMuxSai3Tx } +#define I2S_AUDIO_PLL_CLOCK (2U) + +#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \ + { \ + .hw_id = _hwid, \ + .fn = _fn, \ + .mode = _mode, \ + .name = MP_QSTR_##_pin, \ + .iomux = {_iomux}, \ + } + +#define I2S_GPIO_MAP \ + { \ + I2S_GPIO(1, MCK, TX, GPIO_08, IOMUXC_GPIO_08_SAI1_MCLK), /* pin D8 */ \ + I2S_GPIO(1, SCK, RX, GPIO_01, IOMUXC_GPIO_01_SAI1_RX_BCLK), /* pin D1 */ \ + I2S_GPIO(1, WS, RX, GPIO_02, IOMUXC_GPIO_02_SAI1_RX_SYNC), /* pin D2 */ \ + I2S_GPIO(1, SD, RX, GPIO_03, IOMUXC_GPIO_03_SAI1_RX_DATA00), /* pin D3 */ \ + I2S_GPIO(1, SCK, TX, GPIO_06, IOMUXC_GPIO_06_SAI1_TX_BCLK), /* pin D6 */ \ + I2S_GPIO(1, WS, TX, GPIO_07, IOMUXC_GPIO_07_SAI1_TX_SYNC), /* pin D7 */ \ + I2S_GPIO(1, SD, TX, GPIO_04, IOMUXC_GPIO_04_SAI1_TX_DATA00), /* pin D4 */ \ + I2S_GPIO(3, SCK, TX, GPIO_SD_01, IOMUXC_GPIO_SD_01_SAI3_TX_BCLK), /* pin SD1 */ \ + I2S_GPIO(3, WS, TX, GPIO_SD_00, IOMUXC_GPIO_SD_00_SAI3_TX_SYNC), /* pin SD0 */ \ + I2S_GPIO(3, SD, TX, GPIO_SD_02, IOMUXC_GPIO_SD_02_SAI3_TX_DATA) /* pin SD2 */ \ + } diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.mk b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.mk new file mode 100644 index 00000000000..942012157dd --- /dev/null +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/mpconfigboard.mk @@ -0,0 +1,16 @@ +MCU_SERIES = MIMXRT1011 +MCU_VARIANT = MIMXRT1011DAE5A + +MICROPY_FLOAT_IMPL = single +MICROPY_HW_FLASH_TYPE = qspi_nor_flash +MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 + +USE_UF2_BOOTLOADER = 1 + +CFLAGS += -DMICROPY_HW_FLASH_SAMPLE_CLK=kFLEXSPIReadSampleClk_LoopbackInternally + +SRC_C += \ + hal/flexspi_nor_flash.c \ diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/pins.csv b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/pins.csv new file mode 100644 index 00000000000..c3f432e59f7 --- /dev/null +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/pins.csv @@ -0,0 +1,42 @@ +A0,GPIO_AD_00 +A1,GPIO_AD_01 +A2,GPIO_AD_02 +A3,GPIO_AD_03 +A4,GPIO_AD_04 +A5,GPIO_AD_05 +A6,GPIO_AD_06 +A7,GPIO_AD_07 +A8,GPIO_AD_08 +A9,GPIO_AD_09 +A10,GPIO_AD_10 +A11,GPIO_AD_11 +A14,GPIO_AD_14 + +D0,GPIO_00 +D1,GPIO_01 +D2,GPIO_02 +D3,GPIO_03 +D4,GPIO_04 +D5,GPIO_05 +D6,GPIO_06 +D7,GPIO_07 +D8,GPIO_08 +D9,GPIO_09 +D10,GPIO_10 +D11,GPIO_11 +D12,GPIO_12 +D13,GPIO_13 + +SD0,GPIO_SD_00 +SD1,GPIO_SD_01 +SD2,GPIO_SD_02 +SD5,GPIO_SD_05 +# Test pads at the bottom side +SD3,GPIO_SD_03 +SD4,GPIO_SD_04 +SD12,GPIO_SD_12 +SD13,GPIO_SD_13 + +LED,GPIO_SD_04 +CLK,GPIO_AD_12 +DIO,GPIO_AD_13 From 58f1ade318ecd72def23e9169a7a25d65d7967fb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 6 Dec 2024 20:38:19 +0100 Subject: [PATCH 0395/2098] mimxrt/machine_adc: Add ADC.read_uv() method. Matching the generic documentation. Signed-off-by: robert-hh --- ports/mimxrt/machine_adc.c | 19 ++++++++++--------- ports/mimxrt/mpconfigport.h | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/machine_adc.c b/ports/mimxrt/machine_adc.c index 29b73a20652..c332bd70312 100644 --- a/ports/mimxrt/machine_adc.c +++ b/ports/mimxrt/machine_adc.c @@ -41,6 +41,12 @@ // The ADC class doesn't have any constants for this port. #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS +#if defined(MIMXRT117x_SERIES) +#define ADC_UV_FULL_RANGE (3840000) +#else +#define ADC_UV_FULL_RANGE (3300000) +#endif + typedef struct _machine_adc_obj_t { mp_obj_base_t base; ADC_Type *adc; @@ -78,15 +84,6 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args ADC_Type *adc_instance = pin->adc_list[0].instance; // NOTE: we only use the first ADC assignment - multiple assignments are not supported for now uint8_t channel = pin->adc_list[0].channel; - #if 0 // done in adc_read_u16 - // Configure ADC peripheral channel - adc_channel_config_t channel_config = { - .channelNumber = (uint32_t)channel, - .enableInterruptOnConversionCompleted = false, - }; - ADC_SetChannelConfig(adc_instance, 0UL, &channel_config); // NOTE: we always choose channel group '0' since we only perform software triggered conversion - #endif - // Create ADC Instance machine_adc_obj_t *o = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); o->adc = adc_instance; @@ -169,3 +166,7 @@ void machine_adc_init(void) { } } #endif + +static mp_int_t mp_machine_adc_read_uv(machine_adc_obj_t *self) { + return (uint64_t)ADC_UV_FULL_RANGE * mp_machine_adc_read_u16(self) / 65536; +} diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 7da85f1aee5..b9fb87e050b 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -84,6 +84,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) #define MICROPY_PY_MACHINE_ADC_INCLUDEFILE "ports/mimxrt/machine_adc.c" +#define MICROPY_PY_MACHINE_ADC_READ_UV (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_BITSTREAM (1) #define MICROPY_PY_MACHINE_DHT_READINTO (1) From d8edae040fb194c95e9569a44968d4c3858e0ac0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 17 Jan 2025 09:40:59 +0100 Subject: [PATCH 0396/2098] mimxrt/mpconfigport: Enable support for exFAT. There is plenty of room in the MIMXRT board flash, so it can be enabled. Tested with: - MIMXRT1176_EVK - MIMXRT1061 (Teensy 4.1) - MIMXRT1010 (Olimex RT1010) Signed-off-by: robert-hh --- ports/mimxrt/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index b9fb87e050b..728354239c4 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -125,6 +125,7 @@ uint32_t trng_random_u32(void); #define MICROPY_FATFS_RPATH (2) #define MICROPY_FATFS_MULTI_PARTITION (1) #define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_EXFAT (1) #ifndef MICROPY_PY_NETWORK #define MICROPY_PY_NETWORK (1) From 2a5b97beaea2db3c7b8c6a77cf0ca854fdb16a4b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 15 Feb 2025 12:42:46 +0100 Subject: [PATCH 0397/2098] mimxrt/mpconfigport: Enable PPP for boards with lwIP. PPP is now enabled on all boards with Ethernet support. PPP could be enabled for other boards without Ethernet like the Teensy 4.0 as well in a second step. Enabling for MIMXRT101x boards is hardly possible due to the large RAM demand of lwIP. Tested with a Teensy 4.1 board and a SimCom A7608 GPRS/LTE modem. Signed-off-by: robert-hh --- ports/mimxrt/mpconfigport.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 728354239c4..96ffc153793 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -139,6 +139,10 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) #define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) #define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) +#ifndef MICROPY_PY_NETWORK_PPP_LWIP +#define MICROPY_PY_NETWORK_PPP_LWIP (MICROPY_PY_LWIP) +#endif +#define MICROPY_PY_LWIP_PPP (MICROPY_PY_NETWORK_PPP_LWIP) #ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) From 2d20dbce2c4e45123349051dec16e6565fec54ef Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 22 Feb 2025 16:20:50 +0100 Subject: [PATCH 0398/2098] mimxrt/machine_uart: Remove duplicate init and make IRQ optional. Changes: - The duplicate LPUART_Init call was not needed, just an edit fail. - Allow a port to disable UART.irq(). Some code for configuration stays, but the respective UART IRQ is not enabled. Calling uart.irq() will cause an exception by extmod/machine_uart.c. Signed-off-by: robert-hh --- ports/mimxrt/machine_uart.c | 10 +++++++--- ports/mimxrt/mpconfigport.h | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 9f9d6c8fd25..1c601b559d0 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -326,22 +326,23 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, self->timeout_char = min_timeout_char; } + self->config.rxIdleType = kLPUART_IdleTypeStartBit; + self->config.rxIdleConfig = kLPUART_IdleCharacter4; #if defined(MIMXRT117x_SERIES) // Use the Lpuart1 clock value, which is set for All UART devices. LPUART_Init(self->lpuart, &self->config, CLOCK_GetRootClockFreq(kCLOCK_Root_Lpuart1)); #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; + #if MICROPY_PY_MACHINE_UART_IRQ LPUART_EnableInterrupts(self->lpuart, kLPUART_IdleLineInterruptEnable); + #endif // The Uart supports inverting, but not the fsl API, so it has to coded directly // And it has to be done after LPUART_Init. @@ -381,6 +382,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout_char = 1; self->new = true; self->mp_irq_obj = NULL; + self->mp_irq_trigger = 0; LPUART_GetDefaultConfig(&self->config); @@ -427,6 +429,7 @@ void machine_uart_deinit_all(void) { } } +#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; @@ -475,6 +478,7 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args 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); diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 96ffc153793..9a7dfc630f3 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -114,7 +114,9 @@ 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) +#ifndef MICROPY_PY_MACHINE_UART_IRQ #define MICROPY_PY_MACHINE_UART_IRQ (1) +#endif #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) From 1398e7fd20ef399a003e4e476eb7726126f42d66 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 23 Feb 2025 16:11:56 +0100 Subject: [PATCH 0399/2098] mimxrt/hal/qspi_nor_flash_config: Use a safe common CS timing. The flash devices used by the MIMXRT board are specified either with 3ns or 5 ns CS setup and hold time. Since a single configuration file is used for all boards, use 5ns instead of 3ns to be safe, even if there were no problems so far. Signed-off-by: robert-hh --- ports/mimxrt/hal/qspi_nor_flash_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/mimxrt/hal/qspi_nor_flash_config.c b/ports/mimxrt/hal/qspi_nor_flash_config.c index 36ccc30fc19..0ed1b5032bb 100644 --- a/ports/mimxrt/hal/qspi_nor_flash_config.c +++ b/ports/mimxrt/hal/qspi_nor_flash_config.c @@ -38,8 +38,8 @@ const flexspi_nor_config_t qspiflash_config = { .tag = FLEXSPI_CFG_BLK_TAG, .version = FLEXSPI_CFG_BLK_VERSION, .readSampleClkSrc = MICROPY_HW_FLASH_DQS, - .csHoldTime = 3u, - .csSetupTime = 3u, + .csHoldTime = 5u, // safe time for all flash devices + .csSetupTime = 5u, .busyOffset = FLASH_BUSY_STATUS_OFFSET, // Status bit 0 indicates busy. .busyBitPolarity = FLASH_BUSY_STATUS_POL, // Busy when the bit is 1. .deviceModeCfgEnable = 1u, From b85ad4bd4128876908ed0493a1b1d3df3d972cf1 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Mar 2025 09:08:01 +0100 Subject: [PATCH 0400/2098] mimxrt/machine_uart: Fix rx/tx buffer allocation bug. The buffer would be reset on every call to `uart.init()`. If no sizes were given, the buffer would be set to the default size 256. That made problems e.g. with PPP. Also, the RX buffer was not stored at the UART object and not visible to GC as being in use. Then a `gc.collect()` would eventually free the buffer. This commit fixes those issues, keeping the buffer size if not deliberately changed and allocating new buffers only if the size was changed. Signed-off-by: robert-hh --- ports/mimxrt/machine_uart.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 1c601b559d0..0d682a8a4d1 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -66,6 +66,8 @@ typedef struct _machine_uart_obj_t { uint16_t tx_status; uint8_t *txbuf; uint16_t txbuf_len; + uint8_t *rxbuf; + uint16_t rxbuf_len; bool new; uint16_t mp_irq_trigger; // user IRQ trigger mask uint16_t mp_irq_flags; // user IRQ active IRQ flags @@ -197,7 +199,7 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ 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, + self->rxbuf_len, self->txbuf_len, self->timeout, self->timeout_char, _invert_name[self->invert], self->mp_irq_trigger); } @@ -291,25 +293,33 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, self->config.enableRx = true; // Set the RX buffer size if configured. - size_t rxbuf_len = DEFAULT_BUFFER_SIZE; if (args[ARG_rxbuf].u_int > 0) { - rxbuf_len = args[ARG_rxbuf].u_int; + size_t rxbuf_len = args[ARG_rxbuf].u_int; if (rxbuf_len < MIN_BUFFER_SIZE) { rxbuf_len = MIN_BUFFER_SIZE; } else if (rxbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("rxbuf too large")); } + // Force re-allocting of the buffer if the size changed + if (rxbuf_len != self->rxbuf_len) { + self->rxbuf = NULL; + self->rxbuf_len = rxbuf_len; + } } // Set the TX buffer size if configured. - size_t txbuf_len = DEFAULT_BUFFER_SIZE; if (args[ARG_txbuf].u_int > 0) { - txbuf_len = args[ARG_txbuf].u_int; + size_t txbuf_len = args[ARG_txbuf].u_int; if (txbuf_len < MIN_BUFFER_SIZE) { txbuf_len = MIN_BUFFER_SIZE; } else if (txbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("txbuf too large")); } + // Force re-allocting of the buffer if the size is changed + if (txbuf_len != self->txbuf_len) { + self->txbuf = NULL; + self->txbuf_len = txbuf_len; + } } // Initialise the UART peripheral if any arguments given, or it was not initialised previously. @@ -335,10 +345,13 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, LPUART_Init(self->lpuart, &self->config, CLOCK_GetClockRootFreq(kCLOCK_UartClkRoot)); #endif 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; + if (self->rxbuf == NULL) { + self->rxbuf = m_new(uint8_t, self->rxbuf_len + 1); + } + LPUART_TransferStartRingBuffer(self->lpuart, &self->handle, self->rxbuf, self->rxbuf_len); + if (self->txbuf == NULL) { + self->txbuf = m_new(uint8_t, self->txbuf_len); // Allocate the TX buffer. + } #if MICROPY_PY_MACHINE_UART_IRQ LPUART_EnableInterrupts(self->lpuart, kLPUART_IdleLineInterruptEnable); @@ -380,6 +393,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->invert = false; self->timeout = 1; self->timeout_char = 1; + self->rxbuf = NULL; + self->rxbuf_len = DEFAULT_BUFFER_SIZE; + self->txbuf = NULL; + self->txbuf_len = DEFAULT_BUFFER_SIZE; self->new = true; self->mp_irq_obj = NULL; self->mp_irq_trigger = 0; From 1e7328ca28de201d6cd1ef31fb64e37613c0f9fb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 13 Mar 2025 15:47:48 +0100 Subject: [PATCH 0401/2098] mimxrt/machine_i2c: Support the timeout keyword argument. Set the default timeout to 50000 us. The default used to be 0, causing the NXP I2C driver to silently stop working in case of a non-responding device. Signed-off-by: robert-hh --- ports/mimxrt/machine_i2c.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ports/mimxrt/machine_i2c.c b/ports/mimxrt/machine_i2c.c index 89fe47dbf60..b9d23a23c9b 100644 --- a/ports/mimxrt/machine_i2c.c +++ b/ports/mimxrt/machine_i2c.c @@ -36,6 +36,7 @@ #define DEFAULT_I2C_FREQ (400000) #define DEFAULT_I2C_DRIVE (6) +#define DEFAULT_I2C_TIMEOUT (50000) typedef struct _machine_i2c_obj_t { mp_obj_base_t base; @@ -82,16 +83,18 @@ bool lpi2c_set_iomux(int8_t hw_i2c, uint8_t drive) { static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "I2C(%u, freq=%u)", - self->i2c_id, self->master_config->baudRate_Hz); + mp_printf(print, "I2C(%u, freq=%u, timeout=%u)", + self->i2c_id, self->master_config->baudRate_Hz, + self->master_config->pinLowTimeout_ns / 1000); } mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_freq, ARG_drive}; + enum { ARG_id, ARG_freq, ARG_drive, ARG_timeout}; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, { MP_QSTR_drive, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_DRIVE} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_TIMEOUT} }, }; // Parse args. @@ -121,6 +124,9 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n LPI2C_MasterGetDefaultConfig(self->master_config); // Initialise the I2C peripheral. self->master_config->baudRate_Hz = args[ARG_freq].u_int; + if (args[ARG_timeout].u_int >= 0) { + self->master_config->pinLowTimeout_ns = args[ARG_timeout].u_int * 1000; // to be set as ns + } LPI2C_MasterInit(self->i2c_inst, self->master_config, BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT); return MP_OBJ_FROM_PTR(self); From cdcc70d4f86b2948db4003c7ed55092b628394a8 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 13 Mar 2025 16:50:27 +0100 Subject: [PATCH 0402/2098] mimxrt: Enable default devices for I2C, SPI and UART. Since all boards are configured to have a I2C(0), SPI(0) and UART(1), these can be set as default devices, allowing the instantiation of I2C(), SPI(), UART() without an id argument. Signed-off-by: robert-hh --- docs/mimxrt/quickref.rst | 12 +++++++++++- ports/mimxrt/machine_i2c.c | 5 +++-- ports/mimxrt/machine_spi.c | 5 +++-- ports/mimxrt/machine_uart.c | 14 +++++++++++--- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index 49d7befc745..9f1efd4ffca 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -122,10 +122,13 @@ See :ref:`machine.UART `. :: uart1 = UART(1, baudrate=115200) uart1.write('hello') # write 5 bytes uart1.read(5) # read up to 5 bytes + uart1 = UART(baudrate=19200) # open UART 1 at 19200 baud The i.MXRT has up to eight hardware UARTs, but not every board exposes all TX and RX pins for users. For the assignment of Pins to UART signals, -refer to the :ref:`UART pinout `. +refer to the :ref:`UART pinout `. If the UART ID is +omitted, UART(1) is selected. Then, the keyword +option for baudrate must be used to change it from the default value. PWM (pulse width modulation) ---------------------------- @@ -305,12 +308,15 @@ rates (up to 30Mhz). Hardware SPI is accessed via the cs_pin(0) spi.write('Hello World') cs_pin(1) + spi = SPI(baudrate=4_000_000) # Use SPI(0) at a baudrate of 4 MHz For the assignment of Pins to SPI signals, refer to :ref:`Hardware SPI pinout `. The keyword option cs=n can be used to enable the cs pin 0 or 1 for an automatic cs signal. The default is cs=-1. Using cs=-1 the automatic cs signal is not created. In that case, cs has to be set by the script. Clearing that assignment requires a power cycle. +If the SPI ID is omitted, SPI(0) is selected. Then, the keyword +option for baudrate must be used to change it from the default value. Notes: @@ -355,6 +361,10 @@ has the same methods as software SPI above:: i2c = I2C(0, 400_000) i2c.writeto(0x76, b"Hello World") + i2c = I2C(freq=100_000) # use I2C(0) at 100kHz + +If the I2C ID is omitted, I2C(0) is selected. Then, the keyword +option for freq must be used to change the freq from the default value. I2S bus ------- diff --git a/ports/mimxrt/machine_i2c.c b/ports/mimxrt/machine_i2c.c index b9d23a23c9b..d170804f4f0 100644 --- a/ports/mimxrt/machine_i2c.c +++ b/ports/mimxrt/machine_i2c.c @@ -34,6 +34,7 @@ #include "fsl_iomuxc.h" #include "fsl_lpi2c.h" +#define DEFAULT_I2C_ID (0) #define DEFAULT_I2C_FREQ (400000) #define DEFAULT_I2C_DRIVE (6) #define DEFAULT_I2C_TIMEOUT (50000) @@ -91,7 +92,7 @@ static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_prin mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_freq, ARG_drive, ARG_timeout}; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = DEFAULT_I2C_ID} }, { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, { MP_QSTR_drive, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_DRIVE} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_TIMEOUT} }, @@ -102,7 +103,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get I2C bus. - int i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + int i2c_id = args[ARG_id].u_int; if (i2c_id < 0 || i2c_id >= MICROPY_HW_I2C_NUM || i2c_index_table[i2c_id] == 0) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } diff --git a/ports/mimxrt/machine_spi.c b/ports/mimxrt/machine_spi.c index 56e155ff22f..4f6480607aa 100644 --- a/ports/mimxrt/machine_spi.c +++ b/ports/mimxrt/machine_spi.c @@ -37,6 +37,7 @@ #include "fsl_lpspi.h" #include "fsl_lpspi_edma.h" +#define DEFAULT_SPI_ID (0) #define DEFAULT_SPI_BAUDRATE (1000000) #define DEFAULT_SPI_POLARITY (0) #define DEFAULT_SPI_PHASE (0) @@ -130,7 +131,7 @@ static void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_prin mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_gap_ns, ARG_drive, ARG_cs }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = DEFAULT_SPI_ID} }, { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = DEFAULT_SPI_BAUDRATE} }, { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_POLARITY} }, { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_PHASE} }, @@ -146,7 +147,7 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get the SPI bus id. - int spi_id = mp_obj_get_int(args[ARG_id].u_obj); + int spi_id = args[ARG_id].u_int; if (spi_id < 0 || spi_id >= MP_ARRAY_SIZE(spi_index_table) || spi_index_table[spi_id] == 0) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); } diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 0d682a8a4d1..4dcaf72f986 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -37,6 +37,7 @@ #include "modmachine.h" #include "pin.h" +#define DEFAULT_UART_ID (1) #define DEFAULT_UART_BAUDRATE (115200) #define DEFAULT_BUFFER_SIZE (256) #define MIN_BUFFER_SIZE (32) @@ -377,10 +378,17 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } static mp_obj_t mp_machine_uart_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); + mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, true); // Get UART bus. - int uart_id = mp_obj_get_int(args[0]); + int uart_id; + if (n_args > 0) { + uart_id = mp_obj_get_int(args[0]); + n_args--; + args++; + } else { + uart_id = DEFAULT_UART_ID; + } if (uart_id < 0 || uart_id > MICROPY_HW_UART_NUM || uart_index_table[uart_id] == 0) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), uart_id); } @@ -409,7 +417,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg if (uart_present) { mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - mp_machine_uart_init_helper(self, n_args - 1, args + 1, &kw_args); + mp_machine_uart_init_helper(self, n_args, args, &kw_args); return MP_OBJ_FROM_PTR(self); } else { return mp_const_none; From 274c8c419c97bdb54fa14de37a38b08dcefd1493 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 23 Mar 2025 09:28:06 +0100 Subject: [PATCH 0403/2098] mimxrt/boards: Update deploy instructions. Make the final step of the deploy more detailed. Signed-off-by: robert-hh --- .../boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md | 6 +++++- .../MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md | 7 ++++++- ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md | 12 +++++++++--- ports/mimxrt/boards/deploy_mimxrt.md | 7 +++++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md index c5bfd1a28a7..a2d6f5c824f 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/deploy_metro_m7.md @@ -8,4 +8,8 @@ here: https://learn.adafruit.com/adafruit-metro-m7-microsd/installing-the-bootlo Once the bootloader is installed and started, you can install MicroPython by copying the .uf2 version of the firmware file to the bootloader -drive. When the firmware is installed, the drive will disappear. +drive. A LED on the board will start flickering, indicating that the +upload is ongoing. Once the upload is complete, the drive icon will +disappear. Wait until the LED stops flickering. In rare cases there +may be an error message coming up, especially when there are only few +or no changes in the firmware file. Then just repeat the upload. \ No newline at end of file diff --git a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md index fde33815922..49922183b12 100644 --- a/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md +++ b/ports/mimxrt/boards/MAKERDIARY_RT1011_NANO_KIT/deploy_makerdiary.md @@ -31,4 +31,9 @@ sudo ./sdphost -u 0x1fc9,0x0145 -- jump-address 0x20207000 ``` Wait until a drive icon appears on the computer (or mount it explicitly). At this point the bootloader is installed. You can now copy the -MicroPython .uf2 file to the board. +MicroPython .uf2 file to the board to start the firmware upload. +A LED on the board will start flickering, indicating that the +upload is ongoing. Once the upload is complete, the drive icon will +disappear. Wait until the LED stops flickering. In rare cases there +may be an error message coming up, especially when there are only few +or no changes in the firmware file. Then just repeat the upload. diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md b/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md index 7abba784b94..4db0b6f6119 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md +++ b/ports/mimxrt/boards/OLIMEX_RT1010/deploy_olimex.md @@ -33,10 +33,16 @@ and you will not need it for Windows. 4. Once the upload of the bootloader is finished, push Reset twice. -The bootloader should start and show a drive icon. Copy the .uf2 version of MicroPython -to this drive to install or update MicroPython. +The bootloader should start and show a drive icon. -Once the UF2 bootloader is installed, only step 4 is required to deploy MicroPython. If +5. Copy the .uf2 version of MicroPython to this drive to install or update MicroPython. + +A LED on the board will start flickering, indicating that the upload is ongoing. Once the upload +is complete, the drive icon will disappear. Wait until the LED stops flickering. In rare cases there +may be an error message coming up, especially when there are only few or no changes in +the firmware file. Then just repeat the copy. + +Once the UF2 bootloader is installed, only steps 4 and 5 are required to deploy MicroPython. If MicroPython is already installed, the bootloader can as well be invoked by calling `machine.bootloader()`. diff --git a/ports/mimxrt/boards/deploy_mimxrt.md b/ports/mimxrt/boards/deploy_mimxrt.md index 7ad21fe64ca..2c7f3500979 100644 --- a/ports/mimxrt/boards/deploy_mimxrt.md +++ b/ports/mimxrt/boards/deploy_mimxrt.md @@ -41,5 +41,8 @@ installed. If there is no valid Firmware on the device, the bootloader will start automatically. Once it's started, a drive ICON will appear. The MicroPython firmware file with .uf2 -extension must then be copied to that drive. When the file is copied and MicroPython -is installed, the drive disappears and MicroPython starts. +extension must then be copied to that drive. A LED on the board may start flickering, +indicating that the upload is ongoing. Once the upload is complete, the drive icon +will disappear. Wait until the LED stops flickering. In rare cases there +may be an error message coming up, especially when there are only few or +no changes in the firmware file. Then just repeat the copy. From c61e85910879d5db120d00f7790ed36f4dcda44d Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Mar 2025 15:47:35 +1100 Subject: [PATCH 0404/2098] drivers: Add MP_QSPI_IOCTL_MEMORY_MODIFIED to indicate flash changed. Signed-off-by: Damien George --- drivers/bus/qspi.h | 3 ++- drivers/bus/softqspi.c | 3 ++- drivers/memory/spiflash.c | 21 ++++++++++++++++++--- drivers/memory/spiflash.h | 2 ++ ports/stm32/octospi.c | 3 ++- ports/stm32/qspi.c | 2 +- 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/bus/qspi.h b/drivers/bus/qspi.h index 009f55b159d..7ba2e750943 100644 --- a/drivers/bus/qspi.h +++ b/drivers/bus/qspi.h @@ -37,10 +37,11 @@ enum { MP_QSPI_IOCTL_DEINIT, MP_QSPI_IOCTL_BUS_ACQUIRE, MP_QSPI_IOCTL_BUS_RELEASE, + MP_QSPI_IOCTL_MEMORY_MODIFIED, }; typedef struct _mp_qspi_proto_t { - int (*ioctl)(void *self, uint32_t cmd); + int (*ioctl)(void *self, uint32_t cmd, uintptr_t arg); int (*write_cmd_data)(void *self, uint8_t cmd, size_t len, uint32_t data); int (*write_cmd_addr_data)(void *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src); int (*read_cmd)(void *self, uint8_t cmd, size_t len, uint32_t *dest); diff --git a/drivers/bus/softqspi.c b/drivers/bus/softqspi.c index 65a504a739d..5cfc4db5e33 100644 --- a/drivers/bus/softqspi.c +++ b/drivers/bus/softqspi.c @@ -56,8 +56,9 @@ static void nibble_write(mp_soft_qspi_obj_t *self, uint8_t v) { mp_hal_pin_write(self->io3, (v >> 3) & 1); } -static int mp_soft_qspi_ioctl(void *self_in, uint32_t cmd) { +static int mp_soft_qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { mp_soft_qspi_obj_t *self = (mp_soft_qspi_obj_t*)self_in; + (void)arg; switch (cmd) { case MP_QSPI_IOCTL_INIT: diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index 773334e167a..09ab7157b88 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -61,14 +61,22 @@ static void mp_spiflash_acquire_bus(mp_spiflash_t *self) { const mp_spiflash_config_t *c = self->config; if (c->bus_kind == MP_SPIFLASH_BUS_QSPI) { - c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_ACQUIRE); + c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_ACQUIRE, 0); } } static void mp_spiflash_release_bus(mp_spiflash_t *self) { const mp_spiflash_config_t *c = self->config; if (c->bus_kind == MP_SPIFLASH_BUS_QSPI) { - c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_RELEASE); + c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_RELEASE, 0); + } +} + +static void mp_spiflash_notify_modified(mp_spiflash_t *self, uint32_t addr, uint32_t len) { + const mp_spiflash_config_t *c = self->config; + if (c->bus_kind == MP_SPIFLASH_BUS_QSPI) { + uintptr_t arg[2] = { addr, len }; + c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_MEMORY_MODIFIED, (uintptr_t)&arg[0]); } } @@ -174,7 +182,7 @@ void mp_spiflash_init(mp_spiflash_t *self) { mp_hal_pin_output(self->config->bus.u_spi.cs); self->config->bus.u_spi.proto->ioctl(self->config->bus.u_spi.data, MP_SPI_IOCTL_INIT); } else { - self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT); + self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, 0); } mp_spiflash_acquire_bus(self); @@ -285,6 +293,7 @@ static int mp_spiflash_write_page(mp_spiflash_t *self, uint32_t addr, size_t len int mp_spiflash_erase_block(mp_spiflash_t *self, uint32_t addr) { mp_spiflash_acquire_bus(self); int ret = mp_spiflash_erase_block_internal(self, addr); + mp_spiflash_notify_modified(self, addr, SECTOR_SIZE); mp_spiflash_release_bus(self); return ret; } @@ -300,6 +309,8 @@ int mp_spiflash_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *de } int mp_spiflash_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint8_t *src) { + uint32_t orig_addr = addr; + uint32_t orig_len = len; mp_spiflash_acquire_bus(self); int ret = 0; uint32_t offset = addr & (PAGE_SIZE - 1); @@ -317,12 +328,16 @@ int mp_spiflash_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint src += rest; offset = 0; } + mp_spiflash_notify_modified(self, orig_addr, orig_len); mp_spiflash_release_bus(self); return ret; } /******************************************************************************/ // Interface functions that use the cache +// +// These functions do not call mp_spiflash_notify_modified(), so shouldn't be +// used for memory-mapped flash (for example). #if MICROPY_HW_SPIFLASH_ENABLE_CACHE diff --git a/drivers/memory/spiflash.h b/drivers/memory/spiflash.h index 5ccf7d44c97..edd7d49330d 100644 --- a/drivers/memory/spiflash.h +++ b/drivers/memory/spiflash.h @@ -81,6 +81,8 @@ int mp_spiflash_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint #if MICROPY_HW_SPIFLASH_ENABLE_CACHE // These functions use the cache (which must already be configured) +// Note: don't use these functions in combination with memory-mapped +// flash, because MP_QSPI_IOCTL_MEMORY_MODIFIED is not called. int mp_spiflash_cache_flush(mp_spiflash_t *self); int mp_spiflash_cached_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest); int mp_spiflash_cached_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint8_t *src); diff --git a/ports/stm32/octospi.c b/ports/stm32/octospi.c index 407f485664c..345c4a23741 100644 --- a/ports/stm32/octospi.c +++ b/ports/stm32/octospi.c @@ -105,8 +105,9 @@ void octospi_init(void) { OCTOSPI1->CR |= OCTOSPI_CR_EN; } -static int octospi_ioctl(void *self_in, uint32_t cmd) { +static int octospi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { (void)self_in; + (void)arg; switch (cmd) { case MP_QSPI_IOCTL_INIT: octospi_init(); diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 34359a1ccf8..1d3239b0d26 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -170,7 +170,7 @@ void qspi_memory_map(void) { qspi_mpu_enable_mapped(); } -static int qspi_ioctl(void *self_in, uint32_t cmd) { +static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { (void)self_in; switch (cmd) { case MP_QSPI_IOCTL_INIT: From 396ab268df8a0b52e25108e41370ec6ab64fd337 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Mar 2025 15:59:22 +1100 Subject: [PATCH 0405/2098] stm32/qspi: Implement MP_QSPI_IOCTL_MEMORY_MODIFIED ioctl. stm32's QSPI driver supports memory-mapped mode. The memory-mapped flash can also be erased/written to. To support both these modes, it switches in and out of memory-mapped mode during an erase/write. If the flash is erased/written and then switched back to memory mapped mode, the cache related to the memory-mapped region that changed must be invalidated. Otherwise subsequent code may end up reading old data. That cache invalidation is currently not being done, and this commit fixes that. This bug has been around ever since QSPI memory-mapped mode existed, but it's never really been observed because it's not common to use flash in memory-mapped mode and also erase/write it. Eg PYBD_SF2 uses the memory-mapped flash in read-only mode to store additional firmware. But since the introduction of ROMFS, things changed. The `vfs.rom_ioctl()` command can erase/write memory-mapped flash. Signed-off-by: Damien George --- ports/stm32/qspi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 1d3239b0d26..1311c27d1d4 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -190,6 +190,14 @@ static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { // Switch to memory-map mode when bus is idle qspi_memory_map(); break; + case MP_QSPI_IOCTL_MEMORY_MODIFIED: { + uintptr_t *addr_len = (uintptr_t *)arg; + volatile void *addr = (volatile void *)(QSPI_MAP_ADDR + addr_len[0]); + size_t len = addr_len[1]; + SCB_InvalidateICache_by_Addr(addr, len); + SCB_InvalidateDCache_by_Addr(addr, len); + break; + } } return 0; // success } From 1e92bdd206f6f87ba65ea05c5b2623fca0b926cd Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Mon, 17 Mar 2025 08:41:31 -0600 Subject: [PATCH 0406/2098] rp2/boards: Fix SparkFun vendor name. The preferred/correct spelling is "SparkFun" so this commit updates all of the existing SparkFun board definitions with that spelling. --- ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json | 2 +- ports/rp2/boards/SPARKFUN_PROMICRO/board.json | 2 +- ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json | 2 +- ports/rp2/boards/SPARKFUN_THINGPLUS/board.json | 2 +- ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json | 2 +- ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json | 2 +- ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json index 4ddcd999fe5..e65a9462c73 100644 --- a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/board.json @@ -21,5 +21,5 @@ "product": "IoT Node LoRaWAN RP2350", "thumbnail": "", "url": "https://www.sparkfun.com/products/26060", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO/board.json b/ports/rp2/boards/SPARKFUN_PROMICRO/board.json index b8c8afc914e..66cea79ddee 100644 --- a/ports/rp2/boards/SPARKFUN_PROMICRO/board.json +++ b/ports/rp2/boards/SPARKFUN_PROMICRO/board.json @@ -17,5 +17,5 @@ "product": "Pro Micro RP2040", "thumbnail": "", "url": "https://www.sparkfun.com/products/18288", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json index d2bfa80a348..8e8b6319007 100644 --- a/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json +++ b/ports/rp2/boards/SPARKFUN_PROMICRO_RP2350/board.json @@ -18,5 +18,5 @@ "product": "Pro Micro RP2350", "thumbnail": "", "url": "https://www.sparkfun.com/products/24870", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS/board.json b/ports/rp2/boards/SPARKFUN_THINGPLUS/board.json index 3eeb3972655..e756c9bff44 100644 --- a/ports/rp2/boards/SPARKFUN_THINGPLUS/board.json +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS/board.json @@ -20,5 +20,5 @@ "product": "Thing Plus RP2040", "thumbnail": "", "url": "https://www.sparkfun.com/products/17745", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json index 09bbefe04dc..a6222d7a0de 100644 --- a/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS_RP2350/board.json @@ -23,5 +23,5 @@ "product": "Thing Plus RP2350", "thumbnail": "", "url": "https://www.sparkfun.com/products/25134", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json index 92486564b10..6a06c0875bd 100644 --- a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER/board.json @@ -21,5 +21,5 @@ "product": "XRP Controller", "thumbnail": "", "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller.html", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json index b7dbd016007..b0566459a05 100644 --- a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/board.json @@ -19,5 +19,5 @@ "product": "XRP Controller (Beta)", "thumbnail": "", "url": "https://www.sparkfun.com/sparkfun-experiential-robotics-platform-xrp-controller-beta.html", - "vendor": "Sparkfun" + "vendor": "SparkFun" } From 93a8c53d64e63da4b965265a24ccdd33806055db Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Mon, 17 Mar 2025 14:33:59 -0600 Subject: [PATCH 0407/2098] rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350: Add SD card support. The IOTNODE_LORAWAN_RP2350 has an SD card and we want users to be able to `import sdcard` without copying `sdcard.py` over to their board. Signed-off-by: Malcolm McKellips --- ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/manifest.py | 3 +++ .../boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/manifest.py diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/manifest.py b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/manifest.py new file mode 100644 index 00000000000..3f5fa79bf74 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("sdcard") diff --git a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake index 11fe1ee28fc..8a36724599d 100644 --- a/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake +++ b/ports/rp2/boards/SPARKFUN_IOTNODE_LORAWAN_RP2350/mpconfigboard.cmake @@ -5,3 +5,6 @@ set(PICO_BOARD_HEADER_DIRS ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) set(PICO_BOARD "sparkfun_iotnode_lorawan_rp2350") set(PICO_PLATFORM "rp2350") + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) From 39452dbeed27423fd207d3cc48c52b4e591deea5 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 22 Dec 2024 20:08:54 +1100 Subject: [PATCH 0408/2098] docs/rp2: Add network information to the rp2 quickref. Some rp2 boards include WiFi, at least with the very popular Pico W and Pico 2 W. New users frequently ask how to set up WiFi and are confused because it's not covered in the quickref. This commit adds the wlan section, copied and modified with notes from the ESP32 quickref. Signed-off-by: Matt Trentini --- docs/esp32/quickref.rst | 24 +++++++++---------- docs/rp2/quickref.rst | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index d65782e501a..ccc01099d17 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -83,30 +83,30 @@ The :class:`network.WLAN` class in the :mod:`network` module:: import network - wlan = network.WLAN(network.WLAN.IF_STA) # create station interface - wlan.active(True) # activate the interface - wlan.scan() # scan for access points - wlan.isconnected() # check if the station is connected to an AP + wlan = network.WLAN() # create station interface (the default, see below for an access point interface) + wlan.active(True) # activate the interface + wlan.scan() # scan for access points + wlan.isconnected() # check if the station is connected to an AP wlan.connect('ssid', 'key') # connect to an AP - wlan.config('mac') # get the interface's MAC address - wlan.ipconfig('addr4') # get the interface's IPv4 addresses + wlan.config('mac') # get the interface's MAC address + wlan.ipconfig('addr4') # get the interface's IPv4 addresses ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface - ap.config(ssid='ESP-AP') # set the SSID of the access point - ap.config(max_clients=10) # set how many clients can connect to the network - ap.active(True) # activate the interface + ap.config(ssid='ESP-AP') # set the SSID of the access point + ap.config(max_clients=10) # set how many clients can connect to the network + ap.active(True) # activate the interface A useful function for connecting to your local WiFi network is:: def do_connect(): - import network - wlan = network.WLAN(network.WLAN.IF_STA) + import machine, network + wlan = network.WLAN() wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect('ssid', 'key') while not wlan.isconnected(): - pass + machine.idle() print('network config:', wlan.ipconfig('addr4')) Once the network is established the :mod:`socket ` module can be used diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst index 6be31805007..23071d77215 100644 --- a/docs/rp2/quickref.rst +++ b/docs/rp2/quickref.rst @@ -55,6 +55,57 @@ The :mod:`rp2` module:: import rp2 +Networking +---------- + +WLAN +^^^^ + +.. note:: + This section applies only to devices that include WiFi support, such as the `Pico W`_ and `Pico 2 W`_. + +The :class:`network.WLAN` class in the :mod:`network` module:: + + import network + + wlan = network.WLAN() # create station interface (the default, see below for an access point interface) + wlan.active(True) # activate the interface + wlan.scan() # scan for access points + wlan.isconnected() # check if the station is connected to an AP + wlan.connect('ssid', 'key') # connect to an AP + wlan.config('mac') # get the interface's MAC address + wlan.ipconfig('addr4') # get the interface's IPv4 addresses + + ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface + ap.config(ssid='RP2-AP') # set the SSID of the access point + ap.config(max_clients=10) # set how many clients can connect to the network + ap.active(True) # activate the interface + +A useful function for connecting to your local WiFi network is:: + + def do_connect(): + import machine, network + wlan = network.WLAN() + wlan.active(True) + if not wlan.isconnected(): + print('connecting to network...') + wlan.connect('ssid', 'key') + while not wlan.isconnected(): + machine.idle() + print('network config:', wlan.ipconfig('addr4')) + +Once the network is established the :mod:`socket ` module can be used +to create and use TCP/UDP sockets as usual, and the ``requests`` module for +convenient HTTP requests. + +After a call to ``wlan.connect()``, the device will by default retry to connect +**forever**, even when the authentication failed or no AP is in range. +``wlan.status()`` will return ``network.STAT_CONNECTING`` in this state until a +connection succeeds or the interface gets disabled. + +.. _Pico W: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#picow-technical-specification +.. _Pico 2 W: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#pico2w-technical-specification + Delay and timing ---------------- From f315a376b616fb195a18700a724715f0e7d250e3 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Mar 2025 13:02:11 +0000 Subject: [PATCH 0409/2098] rp2/machine_i2c: Require an I2C bus ID when no default is available. When PICO_DEFAULT_I2C is not set require an I2C bus ID instead of using -1 as a default, which would fail with a cryptic: "I2C(-1) doesn't exist" Signed-off-by: Phil Howard --- ports/rp2/machine_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/machine_i2c.c b/ports/rp2/machine_i2c.c index e97a852b243..94212fb4870 100644 --- a/ports/rp2/machine_i2c.c +++ b/ports/rp2/machine_i2c.c @@ -100,7 +100,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n #ifdef PICO_DEFAULT_I2C { MP_QSTR_id, MP_ARG_INT, {.u_int = PICO_DEFAULT_I2C} }, #else - { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_id, MP_ARG_INT | MP_ARG_REQUIRED }, #endif { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, { MP_QSTR_scl, MICROPY_I2C_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, From a86122396d0cdb98ed0e72a4c208d7491581e3fa Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 12 Mar 2025 10:42:48 +0000 Subject: [PATCH 0410/2098] rp2/machine_spi: Make SPI ID optional. If the "spi_id" arg is not supplied and then the board default specified by PICO_DEFAULT_SPI will be used. Signed-off-by: Phil Howard --- ports/rp2/machine_spi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_spi.c b/ports/rp2/machine_spi.c index abf0a70bd0e..940e31b3b6e 100644 --- a/ports/rp2/machine_spi.c +++ b/ports/rp2/machine_spi.c @@ -128,7 +128,11 @@ static void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_prin mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + #ifdef PICO_DEFAULT_SPI + { MP_QSTR_id, MP_ARG_INT, {.u_int = PICO_DEFAULT_SPI} }, + #else + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, }, + #endif { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = DEFAULT_SPI_BAUDRATE} }, { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_POLARITY} }, { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_PHASE} }, @@ -144,7 +148,7 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get the SPI bus id. - int spi_id = mp_obj_get_int(args[ARG_id].u_obj); + int spi_id = args[ARG_id].u_int; if (spi_id < 0 || spi_id >= MP_ARRAY_SIZE(machine_spi_obj)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); } From dd7a950bbc27713df8cc461958757ec793189d72 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 12 Mar 2025 11:32:16 +0000 Subject: [PATCH 0411/2098] rp2/machine_spi: Allow MISO to be unspecified. It's common with write-only SPI displays for MISO to be repurposed as a register select or data/command pin. While that was possible by setting up the pin after a call to `machine.SPI()` this change makes `machine.SPI(miso=None)` explicit. Signed-off-by: Phil Howard --- ports/rp2/machine_spi.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/ports/rp2/machine_spi.c b/ports/rp2/machine_spi.c index 940e31b3b6e..680a3df2878 100644 --- a/ports/rp2/machine_spi.c +++ b/ports/rp2/machine_spi.c @@ -80,6 +80,9 @@ #endif +// Assume we won't get an RP2-compatible with 255 GPIO pins +#define MICROPY_HW_SPI_PIN_UNUSED UINT8_MAX + // SPI0 can be GP{0..7,16..23}, SPI1 can be GP{8..15,24..29}. #define IS_VALID_PERIPH(spi, pin) ((((pin) & 8) >> 3) == (spi)) // GP{2,6,10,14,...} @@ -120,9 +123,14 @@ static machine_spi_obj_t machine_spi_obj[] = { static void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "SPI(%u, baudrate=%u, polarity=%u, phase=%u, bits=%u, sck=%u, mosi=%u, miso=%u)", + mp_printf(print, "SPI(%u, baudrate=%u, polarity=%u, phase=%u, bits=%u, sck=%u, mosi=%u, miso=", self->spi_id, self->baudrate, self->polarity, self->phase, self->bits, - self->sck, self->mosi, self->miso); + self->sck, self->mosi); + if (self->miso == MICROPY_HW_SPI_PIN_UNUSED) { + mp_printf(print, "None)"); + } else { + mp_printf(print, "%u)", self->miso); + } } mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -140,7 +148,7 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_FIRSTBIT} }, { MP_QSTR_sck, MICROPY_SPI_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_mosi, MICROPY_SPI_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_miso, MICROPY_SPI_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_miso, MICROPY_SPI_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} }, }; // Parse the arguments. @@ -171,7 +179,10 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n } self->mosi = mosi; } - if (args[ARG_miso].u_obj != mp_const_none) { + + if (args[ARG_miso].u_obj == mp_const_none) { + self->miso = MICROPY_HW_SPI_PIN_UNUSED; + } else if (args[ARG_miso].u_obj != MP_OBJ_NEW_SMALL_INT(-1)) { int miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj); if (!IS_VALID_MISO(self->spi_id, miso)) { mp_raise_ValueError(MP_ERROR_TEXT("bad MISO pin")); @@ -194,7 +205,9 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n self->baudrate = spi_set_baudrate(self->spi_inst, self->baudrate); spi_set_format(self->spi_inst, self->bits, self->polarity, self->phase, self->firstbit); gpio_set_function(self->sck, GPIO_FUNC_SPI); - gpio_set_function(self->miso, GPIO_FUNC_SPI); + if (self->miso != MICROPY_HW_SPI_PIN_UNUSED) { + gpio_set_function(self->miso, GPIO_FUNC_SPI); + } gpio_set_function(self->mosi, GPIO_FUNC_SPI); } From 6fa498cba1a622723d3dc13e15331085aea60d3c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Mar 2025 16:24:55 +1100 Subject: [PATCH 0412/2098] rp2/mpnetworkport: Fix lost CYW43 WiFi events when using both cores. There's a very odd but predictable sequence of events that breaks Wi-Fi when using both cores: 1) CPU1 calls pendsv_suspend() - for example sleep() causes a softtimer node to be inserted, which calls pendsv_suspend(). 2) CYW43 sends wakeup IRQ. CPU0 GPIO IRQ handler schedules PendSV and disables the GPIO IRQ on CPU0, to re-enable after cyw43_poll() runs and completes. 3) CPU0 PendSV_Handler runs, sees pendsv is suspended, exits. 4) CPU1 calls pendsv_resume() and pendsv_resume() sees PendSV is pending and triggers it on CPU1. 5) CPU1 runs PendSV_Handler, runs cyw43_poll(), and at the end it re-enables the IRQ *but now on CPU1*. However CPU1 has GPIO IRQs disabled, so the CYW43 interrupt never runs again... The fix in this commit is to always enable/disable the interrupt on CPU0. This isn't supported by the pico-sdk, but it is supported by the hardware. Fixes issue #16779. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/mpnetworkport.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index af2cabb3bff..2687a568749 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -59,13 +59,36 @@ static soft_timer_entry_t mp_network_soft_timer; volatile int cyw43_has_pending = 0; +// The Pico SDK only lets us set GPIO wake on the current running CPU, but the +// hardware doesn't have this limit. We need to always enable/disable the pin +// interrupt on CPU0, regardless of which CPU runs PendSV and +// cyw43_post_poll_hook(). See feature request at https://github.com/raspberrypi/pico-sdk/issues/2354 +static void gpio_set_cpu0_host_wake_irq_enabled(bool enable) { + // This is a re-implementation of gpio_set_irq_enabled() and _gpio_set_irq_enabled() + // from the pico-sdk, but with the core, gpio, and event type hardcoded to shrink + // code size. + io_bank0_irq_ctrl_hw_t *irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; + uint32_t gpio = CYW43_PIN_WL_HOST_WAKE; + uint32_t events = CYW43_IRQ_LEVEL; + io_rw_32 *en_reg = &irq_ctrl_base->inte[gpio / 8]; + events <<= 4 * (gpio % 8); + if (enable) { + hw_set_bits(en_reg, events); + } else { + hw_clear_bits(en_reg, events); + } +} + +// GPIO IRQ always runs on CPU0 static void gpio_irq_handler(void) { uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); if (events & CYW43_IRQ_LEVEL) { - // As we use a high level interrupt, it will go off forever until it's serviced. - // So disable the interrupt until this is done. It's re-enabled again by - // CYW43_POST_POLL_HOOK which is called at the end of cyw43_poll_func. - gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, false); + // As we use a level interrupt (and can't use an edge interrupt + // as CYW43_PIN_WL_HOST_WAKE is also a SPI data pin), we need to disable + // the interrupt to stop it re-triggering until after PendSV run + // cyw43_poll(). It is re-enabled in cyw43_post_poll_hook(), implemented + // below. + gpio_set_cpu0_host_wake_irq_enabled(false); cyw43_has_pending = 1; __sev(); pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); @@ -81,9 +104,10 @@ void cyw43_irq_init(void) { #endif } +// This hook will run on whichever CPU serviced the PendSV interrupt void cyw43_post_poll_hook(void) { cyw43_has_pending = 0; - gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, true); + gpio_set_cpu0_host_wake_irq_enabled(true); } #endif From 23fb171b808a54eb51304ced9504ce66261bcc54 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Mar 2025 16:05:41 +1100 Subject: [PATCH 0413/2098] rp2/mpnetworkport: Refactor out cyw43_has_pending global variable. A better indication of whether a cyw43 event is pending is the actual flag in the PendSV handler table. (If this fails, could also use the GPIO interrupt enabled register bit). This commit was needed of a previous version of the fix in the parent commit, but it turned out not strictly necessary for the current version. However, it's still a good clean up. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/cyw43_configport.h | 6 ++++-- ports/rp2/modmachine.c | 2 +- ports/rp2/mpnetworkport.c | 5 +---- ports/rp2/pendsv.c | 4 ++++ ports/rp2/pendsv.h | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index bb99bb2976f..ead1a3953d9 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -140,10 +140,12 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #endif void cyw43_post_poll_hook(void); -extern volatile int cyw43_has_pending; +static inline bool cyw43_poll_is_pending(void) { + return pendsv_is_pending(PENDSV_DISPATCH_CYW43); +} static inline void cyw43_yield(void) { - if (!cyw43_has_pending) { + if (!cyw43_poll_is_pending()) { best_effort_wfe_or_timeout(make_timeout_time_ms(1)); } } diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 954ea216497..31665a7640d 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -145,7 +145,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { uint32_t my_interrupts = MICROPY_BEGIN_ATOMIC_SECTION(); #if MICROPY_PY_NETWORK_CYW43 - if (cyw43_has_pending && cyw43_poll != NULL) { + if (cyw43_poll_is_pending()) { MICROPY_END_ATOMIC_SECTION(my_interrupts); return; } diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index 2687a568749..7a6df7f6e5b 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -57,7 +57,6 @@ static soft_timer_entry_t mp_network_soft_timer; #define CYW43_IRQ_LEVEL GPIO_IRQ_LEVEL_HIGH #define CYW43_SHARED_IRQ_HANDLER_PRIORITY PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY -volatile int cyw43_has_pending = 0; // The Pico SDK only lets us set GPIO wake on the current running CPU, but the // hardware doesn't have this limit. We need to always enable/disable the pin @@ -89,9 +88,8 @@ static void gpio_irq_handler(void) { // cyw43_poll(). It is re-enabled in cyw43_post_poll_hook(), implemented // below. gpio_set_cpu0_host_wake_irq_enabled(false); - cyw43_has_pending = 1; - __sev(); pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); + __sev(); CYW43_STAT_INC(IRQ_COUNT); } } @@ -106,7 +104,6 @@ void cyw43_irq_init(void) { // This hook will run on whichever CPU serviced the PendSV interrupt void cyw43_post_poll_hook(void) { - cyw43_has_pending = 0; gpio_set_cpu0_host_wake_irq_enabled(true); } diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 2c086f89429..0bf64d0ae44 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -99,6 +99,10 @@ static inline int pendsv_suspend_count(void) { #endif +bool pendsv_is_pending(size_t slot) { + return pendsv_dispatch_table[slot] != NULL; +} + static inline void pendsv_resume_run_dispatch(void) { // Run pendsv if needed. Find an entry with a dispatch and call pendsv dispatch // with it. If pendsv runs it will service all slots. diff --git a/ports/rp2/pendsv.h b/ports/rp2/pendsv.h index a5d9440211a..6abe950df63 100644 --- a/ports/rp2/pendsv.h +++ b/ports/rp2/pendsv.h @@ -51,5 +51,6 @@ void pendsv_init(void); void pendsv_suspend(void); void pendsv_resume(void); void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f); +bool pendsv_is_pending(size_t slot); #endif // MICROPY_INCLUDED_RP2_PENDSV_H From 35d4d2d06bf2776070c0635e4e3093ef7a6c8f39 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Mar 2025 16:57:19 +1100 Subject: [PATCH 0414/2098] rp2/pendsv: Account for PendSV running on both cores, and without CYW43. Changes: - Move setting of PendSV priority to pendsv_init(). - Call pendsv_init() from CPU1 as well, to ensure priority is the same. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/mpnetworkport.c | 4 ---- ports/rp2/mpthreadport.c | 3 +++ ports/rp2/pendsv.c | 30 +++++++++++++++++++++++------- ports/rp2/pendsv.h | 3 --- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index 7a6df7f6e5b..e1e1567828b 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -42,7 +42,6 @@ static soft_timer_entry_t mp_network_soft_timer; #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #include "lib/cyw43-driver/src/cyw43_stats.h" -#include "hardware/irq.h" #if !defined(__riscv) #if PICO_RP2040 @@ -97,9 +96,6 @@ static void gpio_irq_handler(void) { void cyw43_irq_init(void) { gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); irq_set_enabled(IO_IRQ_BANK0, true); - #if !defined(__riscv) - NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); - #endif } // This hook will run on whichever CPU serviced the PendSV interrupt diff --git a/ports/rp2/mpthreadport.c b/ports/rp2/mpthreadport.c index 8fd5e5d7902..043a878c89b 100644 --- a/ports/rp2/mpthreadport.c +++ b/ports/rp2/mpthreadport.c @@ -106,6 +106,9 @@ static void core1_entry_wrapper(void) { // Allow MICROPY_BEGIN_ATOMIC_SECTION to be invoked from core0. multicore_lockout_victim_init(); + // Set PendSV interrupt priority correctly for CPU1 + pendsv_init(); + if (core1_entry) { core1_entry(core1_arg); } diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 0bf64d0ae44..05b521fde26 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -28,6 +28,7 @@ #include "py/mpconfig.h" #include "py/mpthread.h" #include "pendsv.h" +#include "hardware/irq.h" #if PICO_RP2040 #include "RP2040.h" @@ -45,6 +46,9 @@ static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; static inline void pendsv_resume_run_dispatch(void); +// PendSV IRQ priority, to run system-level tasks that preempt the main thread. +#define IRQ_PRI_PENDSV PICO_LOWEST_IRQ_PRIORITY + void PendSV_Handler(void); #if MICROPY_PY_THREAD @@ -53,8 +57,14 @@ void PendSV_Handler(void); // loop of mp_wfe_or_timeout(), where we don't want the CPU event bit to be set. static mp_thread_recursive_mutex_t pendsv_mutex; +// Called from CPU0 during boot, but may be called later when CPU1 wakes up void pendsv_init(void) { - mp_thread_recursive_mutex_init(&pendsv_mutex); + if (get_core_num() == 0) { + mp_thread_recursive_mutex_init(&pendsv_mutex); + } + #if !defined(__riscv) + NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); + #endif } void pendsv_suspend(void) { @@ -117,11 +127,13 @@ static inline void pendsv_resume_run_dispatch(void) { void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { pendsv_dispatch_table[slot] = f; + // There is a race here where other core calls pendsv_suspend() before ISR + // can execute so this check fails, but dispatch will happen later when + // other core calls pendsv_resume(). if (pendsv_suspend_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(). + // Note this register is part of each CPU core, so setting it on CPUx + // will set the IRQ and run PendSV_Handler on CPUx only. SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; #elif PICO_RISCV struct timespec ts; @@ -136,15 +148,19 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { } // PendSV interrupt handler to perform background processing. +// +// Handler can execute on either CPU if MICROPY_PY_THREAD is set (no code on +// CPU1 calls pendsv_schedule_dispatch(), but CPU1 can call pendsv_resume() +// which will trigger it). void PendSV_Handler(void) { #if MICROPY_PY_THREAD if (!mp_thread_recursive_mutex_lock(&pendsv_mutex, 0)) { - // Failure here means core 1 holds pendsv_mutex. ISR will - // run again after core 1 calls pendsv_resume(). + // Failure here means other core holds pendsv_mutex. ISR will + // run again after that core calls pendsv_resume(). return; } - // Core 0 should not already have locked pendsv_mutex + // This core should not already have locked pendsv_mutex assert(pendsv_mutex.mutex.enter_count == 1); #else assert(pendsv_suspend_count() == 0); diff --git a/ports/rp2/pendsv.h b/ports/rp2/pendsv.h index 6abe950df63..de89f0473e7 100644 --- a/ports/rp2/pendsv.h +++ b/ports/rp2/pendsv.h @@ -42,9 +42,6 @@ enum { #define PENDSV_DISPATCH_NUM_SLOTS PENDSV_DISPATCH_MAX -// PendSV IRQ priority, to run system-level tasks that preempt the main thread. -#define IRQ_PRI_PENDSV PICO_LOWEST_IRQ_PRIORITY - typedef void (*pendsv_dispatch_t)(void); void pendsv_init(void); From 2b2a43187890c47a90ef28b0874691d4df176d14 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Mar 2025 11:26:53 +0100 Subject: [PATCH 0415/2098] rp2/machine_uart: Fix unintended UART buffer allocation on init(). The buffer was be reset on every call to uart.init(). If no sizes were given, the buffer was set to the default size 256. That made problems e.g. with PPP. This commit fixes it, keeping the buffer size if not deliberately changed and allocating new buffers only if the size was changed. Signed-off-by: robert-hh --- ports/rp2/machine_uart.c | 41 ++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c index 334c6fda328..9a4507ada69 100644 --- a/ports/rp2/machine_uart.c +++ b/ports/rp2/machine_uart.c @@ -116,8 +116,10 @@ typedef struct _machine_uart_obj_t { uint16_t timeout_char; // timeout waiting between chars (in ms) uint8_t invert; uint8_t flow; + uint16_t rxbuf_len; ringbuf_t read_buffer; mutex_t *read_mutex; + uint16_t txbuf_len; ringbuf_t write_buffer; mutex_t *write_mutex; uint16_t mp_irq_trigger; // user IRQ trigger mask @@ -128,10 +130,10 @@ typedef struct _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, NULL}, + 0, 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_0, 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}, + 0, 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_1, 0, {NULL, 1, 0, 0}, &write_mutex_1, 0, 0, NULL}, }; static const char *_parity_name[] = {"None", "0", "1"}; @@ -252,7 +254,7 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ 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, 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->stop, self->tx, self->rx, self->txbuf_len, self->rxbuf_len, self->timeout, self->timeout_char, _invert_name[self->invert], self->mp_irq_trigger); } @@ -365,25 +367,33 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } // Set the RX buffer size if configured. - size_t rxbuf_len = DEFAULT_BUFFER_SIZE; if (args[ARG_rxbuf].u_int > 0) { - rxbuf_len = args[ARG_rxbuf].u_int; + size_t rxbuf_len = args[ARG_rxbuf].u_int; if (rxbuf_len < MIN_BUFFER_SIZE) { rxbuf_len = MIN_BUFFER_SIZE; } else if (rxbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("rxbuf too large")); } + // Force re-allocting of the buffer if the size changed + if (rxbuf_len != self->rxbuf_len) { + self->read_buffer.buf = NULL; + self->rxbuf_len = rxbuf_len; + } } // Set the TX buffer size if configured. - size_t txbuf_len = DEFAULT_BUFFER_SIZE; if (args[ARG_txbuf].u_int > 0) { - txbuf_len = args[ARG_txbuf].u_int; + size_t txbuf_len = args[ARG_txbuf].u_int; if (txbuf_len < MIN_BUFFER_SIZE) { txbuf_len = MIN_BUFFER_SIZE; } else if (txbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("txbuf too large")); } + // Force re-allocting of the buffer if the size changed + if (txbuf_len != self->txbuf_len) { + self->write_buffer.buf = NULL; + self->txbuf_len = txbuf_len; + } } // Initialise the UART peripheral if any arguments given, or it was not initialised previously. @@ -423,11 +433,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, uart_set_hw_flow(self->uart, self->flow & UART_HWCONTROL_CTS, self->flow & UART_HWCONTROL_RTS); // Allocate the RX/TX buffers. - ringbuf_alloc(&(self->read_buffer), rxbuf_len + 1); - MP_STATE_PORT(rp2_uart_rx_buffer[self->uart_id]) = self->read_buffer.buf; - - ringbuf_alloc(&(self->write_buffer), txbuf_len + 1); - MP_STATE_PORT(rp2_uart_tx_buffer[self->uart_id]) = self->write_buffer.buf; + if (self->read_buffer.buf == NULL) { + ringbuf_alloc(&(self->read_buffer), self->rxbuf_len + 1); + MP_STATE_PORT(rp2_uart_rx_buffer[self->uart_id]) = self->read_buffer.buf; + } + if (self->write_buffer.buf == NULL) { + ringbuf_alloc(&(self->write_buffer), self->txbuf_len + 1); + MP_STATE_PORT(rp2_uart_tx_buffer[self->uart_id]) = self->write_buffer.buf; + } // Set the irq handler. if (self->uart_id == 0) { @@ -454,6 +467,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // Get static peripheral object. machine_uart_obj_t *self = (machine_uart_obj_t *)&machine_uart_obj[uart_id]; + self->rxbuf_len = DEFAULT_BUFFER_SIZE; + self->read_buffer.buf = NULL; + self->txbuf_len = DEFAULT_BUFFER_SIZE; + self->write_buffer.buf = NULL; // Initialise the UART peripheral. mp_map_t kw_args; From 6db7b47ab931db3e95d2d97ad3c383a6ba035bb0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Mar 2025 11:39:12 +0100 Subject: [PATCH 0416/2098] samd/machine_uart: Fix unintended UART buffer allocation on init(). The buffer was be reset on every call to uart.init(). If no sizes were given, the buffer was set to the default size 256. That made problems e.g. with PPP. This commit fixes it, keeping the buffer size if not deliberately changed and allocating new buffers only if the size was changed. Cater for changes of the bits value, which requires a change to the buffer size. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 49 ++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 45bfc3ddd50..85a81660ca0 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -88,8 +88,10 @@ 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) bool new; + uint16_t rxbuf_len; ringbuf_t read_buffer; #if MICROPY_HW_UART_TXBUF + uint16_t txbuf_len; ringbuf_t write_buffer; #endif #if MICROPY_PY_MACHINE_UART_IRQ @@ -287,16 +289,6 @@ void machine_uart_set_baudrate(mp_obj_t self_in, uint32_t baudrate) { 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); - size_t rxbuf_len = self->read_buffer.size - 1; - #if MICROPY_HW_UART_TXBUF - size_t txbuf_len = self->write_buffer.size - 1; - #endif - if (self->bits > 8) { - rxbuf_len /= 2; - #if MICROPY_HW_UART_TXBUF - txbuf_len /= 2; - #endif - } mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " "tx=\"%q\", rx=\"%q\", timeout=%u, timeout_char=%u, rxbuf=%d" @@ -312,9 +304,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], self->stop + 1, pin_find_by_id(self->tx)->name, pin_find_by_id(self->rx)->name, - self->timeout, self->timeout_char, rxbuf_len + self->timeout, self->timeout_char, self->rxbuf_len #if MICROPY_HW_UART_TXBUF - , txbuf_len + , self->txbuf_len #endif #if MICROPY_HW_UART_RTSCTS , self->rts != 0xff ? pin_find_by_id(self->rts)->name : MP_QSTR_None @@ -358,6 +350,11 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // Set bits if configured. if (args[ARG_bits].u_int > 0) { self->bits = args[ARG_bits].u_int; + // Invalidate the buffers since the size may have to be changed + self->read_buffer.buf = NULL; + #if MICROPY_HW_UART_TXBUF + self->write_buffer.buf = NULL; + #endif } // Set parity if configured. @@ -414,7 +411,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } // Set the RX buffer size if configured. - size_t rxbuf_len = DEFAULT_BUFFER_SIZE; + size_t rxbuf_len = self->rxbuf_len; if (args[ARG_rxbuf].u_int > 0) { rxbuf_len = args[ARG_rxbuf].u_int; if (rxbuf_len < MIN_BUFFER_SIZE) { @@ -422,11 +419,16 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } else if (rxbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("rxbuf too large")); } + // Force re-allocting of the buffer if the size changed + if (rxbuf_len != self->rxbuf_len) { + self->read_buffer.buf = NULL; + self->rxbuf_len = rxbuf_len; + } } #if MICROPY_HW_UART_TXBUF // Set the TX buffer size if configured. - size_t txbuf_len = DEFAULT_BUFFER_SIZE; + size_t txbuf_len = self->txbuf_len; if (args[ARG_txbuf].u_int > 0) { txbuf_len = args[ARG_txbuf].u_int; if (txbuf_len < MIN_BUFFER_SIZE) { @@ -434,6 +436,11 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } else if (txbuf_len > MAX_BUFFER_SIZE) { mp_raise_ValueError(MP_ERROR_TEXT("txbuf too large")); } + // Force re-allocting of the buffer if the size changed + if (txbuf_len != self->txbuf_len) { + self->write_buffer.buf = NULL; + self->txbuf_len = txbuf_len; + } } #endif @@ -462,10 +469,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } // Allocate the RX/TX buffers. - ringbuf_alloc(&(self->read_buffer), rxbuf_len + 1); + if (self->read_buffer.buf == NULL) { + ringbuf_alloc(&(self->read_buffer), rxbuf_len + 1); + } #if MICROPY_HW_UART_TXBUF - ringbuf_alloc(&(self->write_buffer), txbuf_len + 1); + if (self->write_buffer.buf == NULL) { + ringbuf_alloc(&(self->write_buffer), txbuf_len + 1); + } #endif // Step 1: Configure the Pin mux. @@ -503,6 +514,12 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->stop = 0; self->timeout = 1; self->timeout_char = 1; + self->rxbuf_len = DEFAULT_BUFFER_SIZE; + self->read_buffer.buf = NULL; + #if MICROPY_HW_UART_TXBUF + self->txbuf_len = DEFAULT_BUFFER_SIZE; + self->write_buffer.buf = NULL; + #endif #if defined(pin_TX) && defined(pin_RX) // Initialize with the default pins self->tx = mp_hal_get_pin_obj((mp_obj_t)pin_TX); From 4dfee50a0be8d61c79928f7bb11a4b77c485aea1 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Mar 2025 11:39:12 +0100 Subject: [PATCH 0417/2098] samd/machine_uart: Fix lock-up in loopback mode if read buffer is full. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 85a81660ca0..5be38e961c1 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -156,7 +156,8 @@ void common_uart_irq_handler(int uart_id) { } } #endif - } else if (uart->USART.INTFLAG.bit.DRE != 0) { + } + if (uart->USART.INTFLAG.bit.DRE != 0) { #if MICROPY_HW_UART_TXBUF // handle the outgoing data if (ringbuf_avail(&self->write_buffer) > 0) { From 56e90cb60b028f0a306e6253ad183e6cd4a2b45e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 13:14:46 +1100 Subject: [PATCH 0418/2098] py/mpconfig: Enable 2-argument built-in next() at basic feature level. This is a pretty fundamental built-in and having CPython-compatible behaviour is beneficial. The code size increase is not much, and ports/boards can still disable it if needed to save space. Addresses issue #5384. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 05f39b3605b..dfa32ebf761 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1215,7 +1215,7 @@ typedef double mp_float_t; // Support for calling next() with second argument #ifndef MICROPY_PY_BUILTINS_NEXT2 -#define MICROPY_PY_BUILTINS_NEXT2 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#define MICROPY_PY_BUILTINS_NEXT2 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) #endif // Whether to support rounding of integers (incl bignum); eg round(123,-1)=120 From 994751c25139496eed9c0eb90949ae8fd681e734 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Mar 2025 10:58:43 +1100 Subject: [PATCH 0419/2098] tests/cpydiff: Remove builtin_next_arg2.py difference. Because 2-arg `next()` is implemented, and now enabled at the basic feature level. Signed-off-by: Damien George --- tests/cpydiff/builtin_next_arg2.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 tests/cpydiff/builtin_next_arg2.py diff --git a/tests/cpydiff/builtin_next_arg2.py b/tests/cpydiff/builtin_next_arg2.py deleted file mode 100644 index ed9565fe0fe..00000000000 --- a/tests/cpydiff/builtin_next_arg2.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -categories: Modules,builtins -description: Second argument to next() is not implemented -cause: MicroPython is optimised for code space. -workaround: Instead of ``val = next(it, deflt)`` use:: - - try: - val = next(it) - except StopIteration: - val = deflt -""" - -print(next(iter(range(0)), 42)) From fdc0c6f8f673cf5e6d2b02ff6820bcc14afe9c94 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Mar 2025 10:24:58 +1100 Subject: [PATCH 0420/2098] py/dynruntime: Make malloc functions raise MemoryError on failure. Addresses some TODOs in this file. Signed-off-by: Damien George --- py/dynruntime.h | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/py/dynruntime.h b/py/dynruntime.h index d360d824ac3..c93111bbd4d 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -28,6 +28,14 @@ // This header file contains definitions to dynamically implement the static // MicroPython runtime API defined in py/obj.h and py/runtime.h. +// +// All of the symbols made available in this header are overriding those defined +// in py/obj.h and py/runtime.h. This is done with macros. For macros that +// would be too complicated (usually more than a single expression), they call a +// static-inline function for the implementation. This function has the same +// name as the macro (hence the same name as a public API function) but with +// "_dyn" appended. For example, the m_malloc() macro calls the m_malloc_dyn() +// static-inline function. #include "py/binary.h" #include "py/nativeglue.h" @@ -39,6 +47,10 @@ #error "dynruntime.h included in non-dynamic-module build." #endif +#if MICROPY_MALLOC_USES_ALLOCATED_SIZE +#error "MICROPY_MALLOC_USES_ALLOCATED_SIZE must be disable in a dynamic-module build." +#endif + #undef MP_ROM_QSTR #undef MP_OBJ_QSTR_VALUE #undef MP_OBJ_NEW_QSTR @@ -52,13 +64,32 @@ /******************************************************************************/ // Memory allocation +#define m_malloc_fail(num_bytes) (m_malloc_fail_dyn((num_bytes))) #define m_malloc(n) (m_malloc_dyn((n))) #define m_free(ptr) (m_free_dyn((ptr))) #define m_realloc(ptr, new_num_bytes) (m_realloc_dyn((ptr), (new_num_bytes))) +#define m_realloc_maybe(ptr, new_num_bytes, allow_move) (m_realloc_maybe_dyn((ptr), (new_num_bytes), (allow_move))) + +static NORETURN inline void m_malloc_fail_dyn(size_t num_bytes) { + mp_fun_table.raise_msg( + mp_fun_table.load_global(MP_QSTR_MemoryError), + "memory allocation failed"); +} + +static inline void *m_realloc_maybe_dyn(void *ptr, size_t new_num_bytes, bool allow_move) { + return mp_fun_table.realloc_(ptr, new_num_bytes, allow_move); +} + +static inline void *m_realloc_checked_dyn(void *ptr, size_t new_num_bytes, bool allow_move) { + ptr = m_realloc_maybe(ptr, new_num_bytes, allow_move); + if (ptr == NULL && new_num_bytes != 0) { + m_malloc_fail(new_num_bytes); + } + return ptr; +} static inline void *m_malloc_dyn(size_t n) { - // TODO won't raise on OOM - return mp_fun_table.realloc_(NULL, n, false); + return m_realloc_checked_dyn(NULL, n, false); } static inline void m_free_dyn(void *ptr) { @@ -66,8 +97,7 @@ static inline void m_free_dyn(void *ptr) { } static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { - // TODO won't raise on OOM - return mp_fun_table.realloc_(ptr, new_num_bytes, true); + return m_realloc_checked_dyn(ptr, new_num_bytes, true); } /******************************************************************************/ From fa42487e45620534440256c9b29e4526f3137de9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Mar 2025 00:34:20 +1100 Subject: [PATCH 0421/2098] extmod/moddeflate: Keep DeflateIO state consistent on window alloc fail. Allocation of a large compression window may fail, and in that case keep the `DeflateIO` state consistent so its other methods (such as `close()`) still work. Consistency is kept by only updating the `self->write` member if the window allocation succeeds. Thanks to @jimmo for finding the bug. Signed-off-by: Damien George --- extmod/moddeflate.c | 12 ++++-- tests/extmod/deflate_compress_memory_error.py | 39 +++++++++++++++++++ .../deflate_compress_memory_error.py.exp | 2 + tests/run-tests.py | 1 + 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 tests/extmod/deflate_compress_memory_error.py create mode 100644 tests/extmod/deflate_compress_memory_error.py.exp diff --git a/extmod/moddeflate.c b/extmod/moddeflate.c index 4afad01618c..920b898b2cc 100644 --- a/extmod/moddeflate.c +++ b/extmod/moddeflate.c @@ -168,16 +168,20 @@ static bool deflateio_init_write(mp_obj_deflateio_t *self) { const mp_stream_p_t *stream = mp_get_stream_raise(self->stream, MP_STREAM_OP_WRITE); - self->write = m_new_obj(mp_obj_deflateio_write_t); - self->write->input_len = 0; - int wbits = self->window_bits; if (wbits == 0) { // Same default wbits for all formats. wbits = DEFLATEIO_DEFAULT_WBITS; } + + // Allocate the large window before allocating the mp_obj_deflateio_write_t, in case the + // window allocation fails the mp_obj_deflateio_t object will remain in a consistent state. size_t window_len = 1 << wbits; - self->write->window = m_new(uint8_t, window_len); + uint8_t *window = m_new(uint8_t, window_len); + + self->write = m_new_obj(mp_obj_deflateio_write_t); + self->write->window = window; + self->write->input_len = 0; uzlib_lz77_init(&self->write->lz77, self->write->window, window_len); self->write->lz77.dest_write_data = self; diff --git a/tests/extmod/deflate_compress_memory_error.py b/tests/extmod/deflate_compress_memory_error.py new file mode 100644 index 00000000000..56ce0603081 --- /dev/null +++ b/tests/extmod/deflate_compress_memory_error.py @@ -0,0 +1,39 @@ +# Test deflate.DeflateIO compression, with out-of-memory errors. + +try: + # Check if deflate is available. + import deflate + import io +except ImportError: + print("SKIP") + raise SystemExit + +# Check if compression is enabled. +if not hasattr(deflate.DeflateIO, "write"): + print("SKIP") + raise SystemExit + +# Create a compressor object. +b = io.BytesIO() +g = deflate.DeflateIO(b, deflate.RAW, 15) + +# Then, use up most of the heap. +l = [] +while True: + try: + l.append(bytearray(1000)) + except: + break +l.pop() + +# Try to compress. This will try to allocate a large window and fail. +try: + g.write('test') +except MemoryError: + print("MemoryError") + +# Should still be able to close the stream. +g.close() + +# The underlying output stream should be unchanged. +print(b.getvalue()) diff --git a/tests/extmod/deflate_compress_memory_error.py.exp b/tests/extmod/deflate_compress_memory_error.py.exp new file mode 100644 index 00000000000..606315c1460 --- /dev/null +++ b/tests/extmod/deflate_compress_memory_error.py.exp @@ -0,0 +1,2 @@ +MemoryError +b'' diff --git a/tests/run-tests.py b/tests/run-tests.py index 50adb6b4d3b..9e7cab4689a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -163,6 +163,7 @@ def open(self, path, mode): "extmod/asyncio_threadsafeflag.py", "extmod/asyncio_wait_for_fwd.py", "extmod/binascii_a2b_base64.py", + "extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory "extmod/re_stack_overflow.py", "extmod/time_res.py", "extmod/vfs_posix.py", From 458a8f2e15c8932f83331ce316db3a71551c7a9a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 14 Mar 2025 13:16:49 -0400 Subject: [PATCH 0422/2098] extmod/vfs: Refactor mp_vfs_mount to enable no-args mount overload. Signed-off-by: Anson Mansfield --- extmod/vfs.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/extmod/vfs.c b/extmod/vfs.c index c83b8ce7d56..88a32c3bc80 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -206,22 +206,24 @@ static mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { } mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_readonly, ARG_mkfs }; + enum { ARG_fsobj, ARG_mount_point, ARG_readonly, ARG_mkfs }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_readonly, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_FALSE} }, { MP_QSTR_mkfs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_FALSE} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // get the mount point size_t mnt_len; - const char *mnt_str = mp_obj_str_get_data(pos_args[1], &mnt_len); + const char *mnt_str = mp_obj_str_get_data(args[ARG_mount_point].u_obj, &mnt_len); // see if we need to auto-detect and create the filesystem - mp_obj_t vfs_obj = pos_args[0]; + mp_obj_t vfs_obj = args[ARG_fsobj].u_obj; mp_obj_t dest[2]; mp_load_method_maybe(vfs_obj, MP_QSTR_mount, dest); if (dest[0] == MP_OBJ_NULL) { @@ -238,11 +240,13 @@ mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args vfs->next = NULL; // call the underlying object to do any mounting operation - mp_vfs_proxy_call(vfs, MP_QSTR_mount, 2, (mp_obj_t *)&args); + mp_arg_val_t *proxy_args = &args[ARG_readonly]; + size_t proxy_args_len = MP_ARRAY_SIZE(args) - ARG_readonly; + mp_vfs_proxy_call(vfs, MP_QSTR_mount, proxy_args_len, (mp_obj_t *)proxy_args); // check that the destination mount point is unused const char *path_out; - mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(mp_obj_str_get_str(pos_args[1]), &path_out); + mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(mp_obj_str_get_str(args[ARG_mount_point].u_obj), &path_out); if (existing_mount != MP_VFS_NONE && existing_mount != MP_VFS_ROOT) { if (vfs->len != 1 && existing_mount->len == 1) { // if root dir is mounted, still allow to mount something within a subdir of root @@ -266,7 +270,7 @@ mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(mp_vfs_mount_obj, 2, mp_vfs_mount); +MP_DEFINE_CONST_FUN_OBJ_KW(mp_vfs_mount_obj, 0, mp_vfs_mount); mp_obj_t mp_vfs_umount(mp_obj_t mnt_in) { // remove vfs from the mount table From 1487a13079ec5e33cac8d273e6d2fa805bca4b1e Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 14 Mar 2025 13:19:06 -0400 Subject: [PATCH 0423/2098] extmod/vfs: Return mount table from no-args vfs.mount call. This extends the existing `vfs.mount()` function to accept zero arguments, in which case it returns a list of tuples of mounted filesystem objects and their mount location. Signed-off-by: Anson Mansfield --- extmod/vfs.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extmod/vfs.c b/extmod/vfs.c index 88a32c3bc80..aebf5ed295b 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -206,6 +206,18 @@ static mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { } mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + if (n_args == 0) { + // zero-args, output a table of all current mountpoints + mp_obj_t mount_list = mp_obj_new_list(0, NULL); + mp_vfs_mount_t *vfsp = MP_STATE_VM(vfs_mount_table); + while (vfsp != NULL) { + mp_obj_t items[] = { vfsp->obj, mp_obj_new_str(vfsp->str, vfsp->len) }; + mp_obj_list_append(mount_list, mp_obj_new_tuple(MP_ARRAY_SIZE(items), items)); + vfsp = vfsp->next; + } + return mount_list; + } + enum { ARG_fsobj, ARG_mount_point, ARG_readonly, ARG_mkfs }; static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, From 9fcc25b9d7e6246f9c1dda3c07f3e718f751e74c Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 3 Mar 2025 14:05:31 -0500 Subject: [PATCH 0424/2098] tests/extmod/vfs_mountinfo.py: Add test for no-args mount output. Signed-off-by: Anson Mansfield --- tests/extmod/vfs_mountinfo.py | 66 +++++++++++++++++++++++++++++++ tests/extmod/vfs_mountinfo.py.exp | 11 ++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/extmod/vfs_mountinfo.py create mode 100644 tests/extmod/vfs_mountinfo.py.exp diff --git a/tests/extmod/vfs_mountinfo.py b/tests/extmod/vfs_mountinfo.py new file mode 100644 index 00000000000..f674e807634 --- /dev/null +++ b/tests/extmod/vfs_mountinfo.py @@ -0,0 +1,66 @@ +# test VFS functionality without any particular filesystem type + +try: + import os, vfs +except ImportError: + print("SKIP") + raise SystemExit +import errno + + +class Filesystem: + def __init__(self, id, paths=[]): + self.id = id + self.paths = paths + + def mount(self, readonly, mksfs): + print("mount", self) + + def umount(self): + print("umount", self) + + def stat(self, path): + if path in self.paths: + return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + else: + raise OSError + + statvfs = stat + + def open(self, path, mode): + pass + + def __repr__(self): + return "Filesystem(%d)" % self.id + + +# first we umount any existing mount points the target may have +try: + vfs.umount("/") +except OSError: + pass +for path in os.listdir("/"): + vfs.umount("/" + path) + + +print(vfs.mount()) + +vfs.mount(Filesystem(1), "/foo") + +print(vfs.mount()) + +vfs.mount(Filesystem(2), "/bar/baz") + +print(vfs.mount()) + +vfs.mount(Filesystem(3), "/bar") + +print(vfs.mount()) + +vfs.umount("/bar/baz") + +print(vfs.mount()) + +vfs.mount(Filesystem(4), "/") + +print(vfs.mount()) diff --git a/tests/extmod/vfs_mountinfo.py.exp b/tests/extmod/vfs_mountinfo.py.exp new file mode 100644 index 00000000000..4ddf06c8c97 --- /dev/null +++ b/tests/extmod/vfs_mountinfo.py.exp @@ -0,0 +1,11 @@ +[] +mount Filesystem(1) +[(Filesystem(1), '/foo')] +mount Filesystem(2) +[(Filesystem(1), '/foo'), (Filesystem(2), '/bar/baz')] +mount Filesystem(3) +[(Filesystem(1), '/foo'), (Filesystem(2), '/bar/baz'), (Filesystem(3), '/bar')] +umount Filesystem(2) +[(Filesystem(1), '/foo'), (Filesystem(3), '/bar')] +mount Filesystem(4) +[(Filesystem(1), '/foo'), (Filesystem(3), '/bar'), (Filesystem(4), '/')] From c68a40ac94ea8a8dd6031dff6f21706977893bef Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 3 Mar 2025 14:25:14 -0500 Subject: [PATCH 0425/2098] docs/library/vfs: Document no-args mount output. Signed-off-by: Anson Mansfield --- docs/library/vfs.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/library/vfs.rst b/docs/library/vfs.rst index fcd06eb4353..1fa1e3060cb 100644 --- a/docs/library/vfs.rst +++ b/docs/library/vfs.rst @@ -34,6 +34,14 @@ represented by VFS classes. Will raise ``OSError(EPERM)`` if *mount_point* is already mounted. +.. function:: mount() + :noindex: + + With no arguments to :func:`mount`, return a list of tuples representing + all active mountpoints. + + The returned list has the form *[(fsobj, mount_point), ...]*. + .. function:: umount(mount_point) Unmount a filesystem. *mount_point* can be a string naming the mount location, From e4051a1ca66703090383678a1747c3f99e993309 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 14:39:41 +1100 Subject: [PATCH 0426/2098] extmod/vfs_rom: Implement minimal VfsRom.getcwd() method. This is needed if you chdir to a ROMFS and want to query your current directory. Prior to this change, using `os.getcwd()` when in a ROMFS would raise: AttributeError: 'VfsRom' object has no attribute 'getcwd' Signed-off-by: Damien George --- extmod/vfs_rom.c | 8 ++++++++ tests/extmod/vfs_rom.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/extmod/vfs_rom.c b/extmod/vfs_rom.c index ff3652d2ce3..7d814cb9805 100644 --- a/extmod/vfs_rom.c +++ b/extmod/vfs_rom.c @@ -300,6 +300,13 @@ static mp_obj_t vfs_rom_chdir(mp_obj_t self_in, mp_obj_t path_in) { } static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_chdir_obj, vfs_rom_chdir); +static mp_obj_t vfs_rom_getcwd(mp_obj_t self_in) { + (void)self_in; + // The current directory is always the root of the ROMFS. + return MP_OBJ_NEW_QSTR(MP_QSTR_); +} +static MP_DEFINE_CONST_FUN_OBJ_1(vfs_rom_getcwd_obj, vfs_rom_getcwd); + typedef struct _vfs_rom_ilistdir_it_t { mp_obj_base_t base; mp_fun_1_t iternext; @@ -436,6 +443,7 @@ static const mp_rom_map_elem_t vfs_rom_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&vfs_rom_open_obj) }, { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&vfs_rom_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&vfs_rom_getcwd_obj) }, { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&vfs_rom_ilistdir_obj) }, { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_rom_stat_obj) }, { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_rom_statvfs_obj) }, diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index dc88481c028..770b6863b9c 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -408,9 +408,11 @@ def test_listdir(self): def test_chdir(self): os.chdir("/test_rom") + self.assertEqual(os.getcwd(), "/test_rom") self.assertEqual(os.listdir(), self.romfs_listdir) os.chdir("/test_rom/") + self.assertEqual(os.getcwd(), "/test_rom") self.assertEqual(os.listdir(), self.romfs_listdir) # chdir within the romfs is not implemented. From 2db0c0225f41292f85b2cf49257d663856d5c8db Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 14:52:15 +1100 Subject: [PATCH 0427/2098] tools/mpremote: Make mip install skip /rom*/lib directories. If a ROMFS is mounted then "/rom/lib" is usually in `sys.path` before the writable filesystem's "lib" entry. The ROMFS directory cannot be installed to, so skip it if found. Signed-off-by: Damien George --- tools/mpremote/mpremote/mip.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index 858b7933d11..26ae8bec5ec 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -175,7 +175,11 @@ def do_mip(state, args): if args.target is None: state.transport.exec("import sys") - lib_paths = [p for p in state.transport.eval("sys.path") if p.endswith("/lib")] + lib_paths = [ + p + for p in state.transport.eval("sys.path") + if not p.startswith("/rom") and p.endswith("/lib") + ] if lib_paths and lib_paths[0]: args.target = lib_paths[0] else: From cccac2cc0127eb43de4524c74424ecaaa594c80e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 11 Mar 2025 17:17:39 +1100 Subject: [PATCH 0428/2098] rp2,esp32,extmod: Implement UPDATE_SUBMODULES in CMake. Rather than having Make calling CMake to generate a list of submodules and then run a Make target (which is complex and prone to masking other errors), implement the submodule update logic in CMake itself. Internal CMake-side changes are that GIT_SUBMODULES is now a CMake list, and the trigger variable name is changed from ECHO_SUBMODULES to UPDATE_SUBMODULES. The run is otherwise 100% a normal CMake run now, so most of the other special casing can be removed. Signed-off-by: Angus Gratton --- extmod/extmod.cmake | 89 ++++++++++++++++------------------ ports/esp32/Makefile | 14 +++--- ports/esp32/esp32_common.cmake | 15 +++++- ports/rp2/CMakeLists.txt | 18 +++---- ports/rp2/Makefile | 16 +++--- py/mkrules.cmake | 42 ++++++++++------ py/mkrules.mk | 9 +--- 7 files changed, 105 insertions(+), 98 deletions(-) diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 3643f1aee7b..7cacd0a4332 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -107,62 +107,57 @@ set(MICROPY_SOURCE_LIB_LIBM_SQRT_HW ${MICROPY_DIR}/lib/libm/thumb_vfp_sqrtf.c) if(MICROPY_PY_BTREE) set(MICROPY_LIB_BERKELEY_DIR "${MICROPY_DIR}/lib/berkeley-db-1.xx") - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/berkeley-db-1.xx) + list(APPEND GIT_SUBMODULES lib/berkeley-db-1.xx) - if(ECHO_SUBMODULES) - # No-op, we're just doing submodule/variant discovery. - # Cannot run the add_library/target_include_directories rules (even though - # the build won't run) because IDF will attempt verify the files exist. - elseif(NOT EXISTS ${MICROPY_LIB_BERKELEY_DIR}/README) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_LIB_BERKELEY_DIR}/README) # Regular build, submodule not initialised -- fail with a clear error. message(FATAL_ERROR " MICROPY_PY_BTREE is enabled but the berkeley-db submodule is not initialised.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") - else() - # Regular build, we have the submodule. - add_library(micropy_extmod_btree OBJECT - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_close.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_conv.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_debug.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_delete.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_get.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_open.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_overflow.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_page.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_put.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_search.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_seq.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_split.c - ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_utils.c - ${MICROPY_LIB_BERKELEY_DIR}/mpool/mpool.c - ) + endif() - target_include_directories(micropy_extmod_btree PRIVATE - ${MICROPY_LIB_BERKELEY_DIR}/include - ) + add_library(micropy_extmod_btree OBJECT + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_close.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_conv.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_debug.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_delete.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_get.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_open.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_overflow.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_page.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_put.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_search.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_seq.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_split.c + ${MICROPY_LIB_BERKELEY_DIR}/btree/bt_utils.c + ${MICROPY_LIB_BERKELEY_DIR}/mpool/mpool.c + ) - if(NOT BERKELEY_DB_CONFIG_FILE) - set(BERKELEY_DB_CONFIG_FILE "${MICROPY_DIR}/extmod/berkeley-db/berkeley_db_config_port.h") - endif() + target_include_directories(micropy_extmod_btree PRIVATE + ${MICROPY_LIB_BERKELEY_DIR}/include + ) - target_compile_definitions(micropy_extmod_btree PRIVATE - BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" - ) + if(NOT BERKELEY_DB_CONFIG_FILE) + set(BERKELEY_DB_CONFIG_FILE "${MICROPY_DIR}/extmod/berkeley-db/berkeley_db_config_port.h") + endif() - # The include directories and compile definitions below are needed to build - # modbtree.c and should be added to the main MicroPython target. + target_compile_definitions(micropy_extmod_btree PRIVATE + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" + ) - list(APPEND MICROPY_INC_CORE - "${MICROPY_LIB_BERKELEY_DIR}/include" - ) + # The include directories and compile definitions below are needed to build + # modbtree.c and should be added to the main MicroPython target. - list(APPEND MICROPY_DEF_CORE - MICROPY_PY_BTREE=1 - BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" - ) + list(APPEND MICROPY_INC_CORE + "${MICROPY_LIB_BERKELEY_DIR}/include" + ) - list(APPEND MICROPY_SOURCE_EXTMOD - ${MICROPY_EXTMOD_DIR}/modbtree.c - ) - endif() + list(APPEND MICROPY_DEF_CORE + MICROPY_PY_BTREE=1 + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" + ) + + list(APPEND MICROPY_SOURCE_EXTMOD + ${MICROPY_EXTMOD_DIR}/modbtree.c + ) endif() # Library for mbedtls @@ -350,5 +345,5 @@ if(MICROPY_PY_LWIP) ${MICROPY_LIB_LWIP_DIR}/include ) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/lwip) + list(APPEND GIT_SUBMODULES lib/lwip) endif() diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 1ce4d97208d..a4e5531850c 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -106,12 +106,10 @@ size-components: size-files: $(call RUN_IDF_PY,size-files) -# Running the build with ECHO_SUBMODULES set will trigger py/mkrules.cmake to -# print out the value of the GIT_SUBMODULES variable, prefixed with -# "GIT_SUBMODULES", and then abort. This extracts out that line from the idf.py -# output and passes the list of submodules to py/mkrules.mk which does the -# `git submodule init` on each. +# Run idf.py with the UPDATE_SUBMODULES flag to update +# necessary submodules for this board. +# +# This is done in a dedicated build directory as some CMake cache values are not +# set correctly if not all submodules are loaded yet. submodules: - @GIT_SUBMODULES=$$(IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D ECHO_SUBMODULES=1 build 2>&1 | \ - grep '^GIT_SUBMODULES=' | cut -d= -f2); \ - $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" GIT_SUBMODULES_FAIL_IF_EMPTY=1 submodules + IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D UPDATE_SUBMODULES=1 reconfigure diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 6473f04a535..f7b00900a03 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -71,8 +71,8 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) -string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) -if(MICROPY_PY_TINYUSB AND NOT ECHO_SUBMODULES) +list(APPEND GIT_SUBMODULES lib/tinyusb) +if(MICROPY_PY_TINYUSB) set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) @@ -195,6 +195,17 @@ if (MICROPY_USER_LDFRAGMENTS) set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) endif() +if (UPDATE_SUBMODULES) + # ESP-IDF checks if some paths exist before CMake does. Some paths don't + # yet exist if this is an UPDATE_SUBMODULES pass on a brand new checkout, so remove + # any path which might not exist yet. A "real" build will not set UPDATE_SUBMODULES. + unset(MICROPY_SOURCE_TINYUSB) + unset(MICROPY_SOURCE_EXTMOD) + unset(MICROPY_SOURCE_LIB) + unset(MICROPY_INC_TINYUSB) + unset(MICROPY_INC_CORE) +endif() + # Register the main IDF component. idf_component_register( SRCS diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 7002ad8769e..f89e2792c64 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -76,8 +76,8 @@ if (MICROPY_PY_NETWORK_CYW43) endif() # Necessary submodules for all boards. -string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/mbedtls) -string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) +list(APPEND GIT_SUBMODULES lib/mbedtls) +list(APPEND GIT_SUBMODULES lib/tinyusb) # Include component cmake fragments include(${MICROPY_DIR}/py/py.cmake) @@ -340,7 +340,7 @@ if (MICROPY_PY_BLUETOOTH_CYW43) endif() if (MICROPY_BLUETOOTH_BTSTACK) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/btstack) + list(APPEND GIT_SUBMODULES lib/btstack) list(APPEND MICROPY_SOURCE_PORT mpbtstackport.c) @@ -358,8 +358,8 @@ if (MICROPY_BLUETOOTH_BTSTACK) endif() if(MICROPY_BLUETOOTH_NIMBLE) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/mynewt-nimble) - if(NOT (${ECHO_SUBMODULES}) AND NOT EXISTS ${MICROPY_DIR}/lib/mynewt-nimble/nimble/host/include/host/ble_hs.h) + list(APPEND GIT_SUBMODULES lib/mynewt-nimble) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_DIR}/lib/mynewt-nimble/nimble/host/include/host/ble_hs.h) message(FATAL_ERROR " mynewt-nimble not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") endif() @@ -386,8 +386,8 @@ target_include_directories(${MICROPY_TARGET} PRIVATE ) if (MICROPY_PY_NETWORK_CYW43) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/cyw43-driver) - if((NOT (${ECHO_SUBMODULES})) AND NOT EXISTS ${MICROPY_DIR}/lib/cyw43-driver/src/cyw43.h) + list(APPEND GIT_SUBMODULES lib/cyw43-driver) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_DIR}/lib/cyw43-driver/src/cyw43.h) message(FATAL_ERROR " cyw43-driver not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") endif() @@ -433,8 +433,8 @@ if (MICROPY_PY_NETWORK_NINAW10) endif() if (MICROPY_PY_NETWORK_WIZNET5K) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/wiznet5k) - if((NOT (${ECHO_SUBMODULES})) AND NOT EXISTS ${MICROPY_DIR}/lib/wiznet5k/README.md) + list(APPEND GIT_SUBMODULES lib/wiznet5k) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_DIR}/lib/wiznet5k/README.md) message(FATAL_ERROR " wiznet5k not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") endif() diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 200899d338e..bfc85f3715e 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -65,15 +65,11 @@ all: clean: $(RM) -rf $(BUILD) -# First ensure that pico-sdk is initialised, then use cmake to pick everything -# else (including board-specific dependencies). -# Running the build with ECHO_SUBMODULES set will trigger py/mkrules.cmake to -# print out the value of the GIT_SUBMODULES variable, prefixed with -# "GIT_SUBMODULES", and then abort. This extracts out that line from the cmake -# output and passes the list of submodules to py/mkrules.mk which does the -# `git submodule init` on each. +# First ensure that pico-sdk is initialised, then run CMake with the +# UPDATE_SUBMODULES flag to update necessary submodules for this board. +# +# This is done in a dedicated build directory as some CMake cache values are not +# set correctly if not all submodules are loaded yet. submodules: $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="lib/pico-sdk" submodules - @GIT_SUBMODULES=$$(cmake -B $(BUILD)/submodules -DECHO_SUBMODULES=1 ${CMAKE_ARGS} -S . 2>&1 | \ - grep '^GIT_SUBMODULES=' | cut -d= -f2); \ - $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$${GIT_SUBMODULES}" GIT_SUBMODULES_FAIL_IF_EMPTY=1 submodules + cmake -S . -B $(BUILD)/submodules -DUPDATE_SUBMODULES=1 ${CMAKE_ARGS} diff --git a/py/mkrules.cmake b/py/mkrules.cmake index cafcbce5681..4374b8b4da3 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -209,16 +209,11 @@ if(MICROPY_FROZEN_MANIFEST) # Note: target_compile_definitions already added earlier. if(NOT MICROPY_LIB_DIR) - string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/micropython-lib) + list(APPEND GIT_SUBMODULES lib/micropython-lib) set(MICROPY_LIB_DIR ${MICROPY_DIR}/lib/micropython-lib) endif() - if(ECHO_SUBMODULES) - # No-op, we're just doing submodule/variant discovery. - # Note: All the following rules are safe to run in discovery mode even - # though the submodule might not be available as they do not directly depend - # on anything from the submodule. - elseif(NOT EXISTS ${MICROPY_LIB_DIR}/README.md) + if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_LIB_DIR}/README.md) message(FATAL_ERROR " micropython-lib not initialized.\n Run 'make BOARD=${MICROPY_BOARD} submodules'") endif() @@ -272,12 +267,29 @@ if(MICROPY_FROZEN_MANIFEST) ) endif() -# Update submodules -if(ECHO_SUBMODULES) - # If cmake is run with GIT_SUBMODULES defined on command line, process the port / board - # settings then print the final GIT_SUBMODULES variable and exit. - # Note: the GIT_SUBMODULES is done via echo rather than message, as message splits - # the output onto multiple lines - execute_process(COMMAND ${CMAKE_COMMAND} -E echo "GIT_SUBMODULES=${GIT_SUBMODULES}") - message(FATAL_ERROR "Done") +# Update submodules, this is invoked on some ports via 'make submodules'. +# +# Note: This logic has a Makefile equivalent in py/mkrules.mk +if(UPDATE_SUBMODULES AND GIT_SUBMODULES) + macro(run_git) + execute_process(COMMAND git ${ARGV} WORKING_DIRECTORY ${MICROPY_DIR} + RESULT_VARIABLE RES) + endmacro() + + list(JOIN GIT_SUBMODULES " " GIT_SUBMODULES_MSG) + message("Updating submodules: ${GIT_SUBMODULES_MSG}") + run_git(submodule sync ${GIT_SUBMODULES}) + if(RES EQUAL 0) + # If available, do blobless partial clones of submodules to save time and space. + # A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.). + run_git(submodule update --init --filter=blob:none ${GIT_SUBMODULES}) + # Fallback to standard submodule update if blobless isn't available (earlier than git 2.36.0) + if (NOT RES EQUAL 0) + run_git(submodule update --init ${GIT_SUBMODULES}) + endif() + endif() + + if (NOT RES EQUAL 0) + message(FATAL_ERROR "Submodule update failed") + endif() endif() diff --git a/py/mkrules.mk b/py/mkrules.mk index ad855c0d7fc..495d8d48bd2 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -262,19 +262,14 @@ endif # If available, do blobless partial clones of submodules to save time and space. # A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.). # Fallback to standard submodule update if blobless isn't available (earlier than 2.36.0) +# +# Note: This target has a CMake equivalent in py/mkrules.cmake submodules: $(ECHO) "Updating submodules: $(GIT_SUBMODULES)" ifneq ($(GIT_SUBMODULES),) $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) || \ git submodule update --init $(GIT_SUBMODULES) -else -ifeq ($(GIT_SUBMODULES_FAIL_IF_EMPTY),1) - # If you see this error, it may mean the internal step run by the port's build - # system to find git submodules has failed. Double-check dependencies are set correctly. - $(ECHO) "Internal build error: The submodule list should not be empty." - exit 1 -endif endif .PHONY: submodules From 50da085d93de55719198fb4fb1272f467e7a16a7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 11 Mar 2025 17:34:24 +1100 Subject: [PATCH 0429/2098] rp2: Print an error message if pico-sdk submodule is missing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index bfc85f3715e..8cba521b2b2 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -56,9 +56,11 @@ ifdef MICROPY_PREVIEW_VERSION_2 CMAKE_ARGS += -DMICROPY_PREVIEW_VERSION_2=1 endif +HELP_PICO_SDK_SUBMODULE ?= "\033[1;31mError: pico-sdk submodule is not initialized.\033[0m Run 'make submodules'" HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" all: + [ -f ../../lib/pico-sdk/README.md ] || (echo -e $(HELP_PICO_SDK_SUBMODULE); false) [ -e $(BUILD)/Makefile ] || cmake -S . -B $(BUILD) -DPICO_BUILD_DOCS=0 ${CMAKE_ARGS} $(MAKE) $(MAKE_ARGS) -C $(BUILD) || (echo -e $(HELP_BUILD_ERROR); false) From a828b99cffa97cc04c393a6fdec96bf9e39c07f5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 28 Mar 2025 11:46:39 +1100 Subject: [PATCH 0430/2098] esp32/Makefile: Use $(Q) prefix on all commands. This prevents printing the lengthy command and makes the build output a little cleaner. Signed-off-by: Damien George --- ports/esp32/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index a4e5531850c..7f31ea69192 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -70,12 +70,12 @@ endif HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" define RUN_IDF_PY - idf.py $(IDFPY_FLAGS) -B $(BUILD) $(1) + $(Q)idf.py $(IDFPY_FLAGS) -B $(BUILD) $(1) endef all: - idf.py $(IDFPY_FLAGS) -B $(BUILD) build || (echo -e $(HELP_BUILD_ERROR); false) - @$(PYTHON) makeimg.py \ + $(Q)idf.py $(IDFPY_FLAGS) -B $(BUILD) build || (echo -e $(HELP_BUILD_ERROR); false) + $(Q)$(PYTHON) makeimg.py \ $(BUILD)/sdkconfig \ $(BUILD)/bootloader/bootloader.bin \ $(BUILD)/partition_table/partition-table.bin \ @@ -112,4 +112,4 @@ size-files: # This is done in a dedicated build directory as some CMake cache values are not # set correctly if not all submodules are loaded yet. submodules: - IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D UPDATE_SUBMODULES=1 reconfigure + $(Q)IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D UPDATE_SUBMODULES=1 reconfigure From 5eee5a67dc69f6b701e9a79b6fd1e047dfbd55a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 28 Mar 2025 11:47:55 +1100 Subject: [PATCH 0431/2098] rp2/Makefile: Use $(Q) prefix on all commands. This prevents printing the lengthy command and makes the build output a little cleaner. Signed-off-by: Damien George --- ports/rp2/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 8cba521b2b2..76b5698d2c8 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -60,9 +60,9 @@ HELP_PICO_SDK_SUBMODULE ?= "\033[1;31mError: pico-sdk submodule is not initializ HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" all: - [ -f ../../lib/pico-sdk/README.md ] || (echo -e $(HELP_PICO_SDK_SUBMODULE); false) - [ -e $(BUILD)/Makefile ] || cmake -S . -B $(BUILD) -DPICO_BUILD_DOCS=0 ${CMAKE_ARGS} - $(MAKE) $(MAKE_ARGS) -C $(BUILD) || (echo -e $(HELP_BUILD_ERROR); false) + $(Q)[ -f ../../lib/pico-sdk/README.md ] || (echo -e $(HELP_PICO_SDK_SUBMODULE); false) + $(Q)[ -e $(BUILD)/Makefile ] || cmake -S . -B $(BUILD) -DPICO_BUILD_DOCS=0 ${CMAKE_ARGS} + $(Q)$(MAKE) $(MAKE_ARGS) -C $(BUILD) || (echo -e $(HELP_BUILD_ERROR); false) clean: $(RM) -rf $(BUILD) @@ -73,5 +73,5 @@ clean: # This is done in a dedicated build directory as some CMake cache values are not # set correctly if not all submodules are loaded yet. submodules: - $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="lib/pico-sdk" submodules - cmake -S . -B $(BUILD)/submodules -DUPDATE_SUBMODULES=1 ${CMAKE_ARGS} + $(Q)$(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="lib/pico-sdk" submodules + $(Q)cmake -S . -B $(BUILD)/submodules -DUPDATE_SUBMODULES=1 ${CMAKE_ARGS} From f96417dbf28617c533e4f2e65c65d1ed11f089fa Mon Sep 17 00:00:00 2001 From: Mark Seminatore Date: Mon, 31 Mar 2025 11:38:36 -0700 Subject: [PATCH 0432/2098] rp2/cyw43_configport: Fix cyw43 mDNS by properly starting mDNS on netif. The rp2 port has an incomplete mDNS implementation. The code in `main.c` calls `mdns_resp_init()` which opens the UDP socket for mDNS. However, no code in the cyw43 driver makes the proper calls to `mdns_resp_add_netif()` and `mdns_resp_remove_netif()` to send the announce packets. The wiznet5k driver does make these calls and was used as a model for these changes. This commit attempts to address this by very small changes to the `ports/rp2/cyw43_configport.h` file. The change uses new cyw43 driver hooks to map the driver macros `CYW43_CB_TCPIP_INIT_EXTRA` and `CYW43_CB_TCPIP_DEINIT_EXTRA` to the appropriate lwIP mDNS calls. Fixes issue #15297. Signed-off-by: Mark Seminatore --- ports/rp2/cyw43_configport.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index ead1a3953d9..55233057423 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -33,6 +33,7 @@ #include "py/mphal.h" #include "py/runtime.h" #include "extmod/modnetwork.h" +#include "lwip/apps/mdns.h" #include "pendsv.h" #define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (1) @@ -167,4 +168,19 @@ static inline void cyw43_delay_ms(uint32_t ms) { #define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() +#if LWIP_MDNS_RESPONDER == 1 + +// Hook for any additional TCP/IP initialization than needs to be done. +// Called after the netif specified by `itf` has been set up. +#ifndef CYW43_CB_TCPIP_INIT_EXTRA +#define CYW43_CB_TCPIP_INIT_EXTRA(self, itf) mdns_resp_add_netif(&self->netif[itf], mod_network_hostname_data) +#endif + +// Hook for any additional TCP/IP deinitialization than needs to be done. +// Called before the netif specified by `itf` is removed. +#ifndef CYW43_CB_TCPIP_DEINIT_EXTRA +#define CYW43_CB_TCPIP_DEINIT_EXTRA(self, itf) mdns_resp_remove_netif(&self->netif[itf]) +#endif + +#endif #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H From b33b9f81217bb1521138a8064d1d1bee3eb49e0f Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 24 Jan 2024 09:08:29 +1100 Subject: [PATCH 0433/2098] stm32/main: Catch and report corrupted lfs filesystem at startup. On stm32, the startup code attempts to mount the configured filesystem. If there is an existing littlefs filesystem that's suitable corrupted it's possible for the reported blocksize to be incorrect here: uint32_t block_size = lfs2_fromle32(superblock->block_size); This `block_size` (which is read from the filesystem iteself) is used to create the len argument passed to `pyb_flash_make_new()`. In that function the len arg is validated to be a mutliple of the underlying hardware block size, as well as not bigger than the physical flash. Any failure is raised as a ValueError. This exception is not caught currently in main, it flows up to the high level assert / startup failure. As this occurs before `boot.py` is run, the users (potentially frozen) application code doesn't have any opportunity to detect and handle the issue. This commit adds a helper function which attempts to create a block device, and on error returns `None` instead of raising an exception. Using this in main means that a potentially corrupt filesystem will simply remain unmounted, and the application can handle the issue safely. The fix here also handles the case where the littlefs filesystem is valid but the autodetection code (which detects the filesystem size) does not work correctly. In that case it will retry mounting the filesystem using the whole size of the block device. Signed-off-by: Andrew Leech --- ports/stm32/main.c | 11 ++++++++-- ports/stm32/storage.c | 48 +++++++++++++++++++++++++------------------ ports/stm32/storage.h | 4 ++++ 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 55dbeed9824..0f904a8ba95 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -185,8 +185,15 @@ MP_NOINLINE static bool init_flash_fs(uint reset_mode) { if (len != -1) { // Detected a littlefs filesystem so create correct block device for it - mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR_len), MP_OBJ_NEW_SMALL_INT(len) }; - bdev = MP_OBJ_TYPE_GET_SLOT(&pyb_flash_type, make_new)(&pyb_flash_type, 0, 1, args); + mp_obj_t lfs_bdev = pyb_flash_new_obj(0, len); + if (lfs_bdev == mp_const_none) { + // Invalid len detected, filesystem header block likely corrupted. + // Create bdev with default size to attempt to mount the whole flash or + // let user attempt recovery if desired. + bdev = pyb_flash_new_obj(0, -1); + } else { + bdev = lfs_bdev; + } } #endif diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index a6594fd4d98..d810261fbce 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -273,6 +273,29 @@ const pyb_flash_obj_t pyb_flash_obj = { 0, // actual size handled in ioctl, MP_BLOCKDEV_IOCTL_BLOCK_COUNT case }; +mp_obj_t pyb_flash_new_obj(mp_int_t start, mp_int_t len) { + uint32_t bl_len = (storage_get_block_count() - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE; + + if (start == -1) { + start = 0; + } else if (!(0 <= start && start < bl_len && start % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + return mp_const_none; + } + + if (len == -1) { + len = bl_len - start; + } else if (!(0 < len && start + len <= bl_len && len % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + return mp_const_none; + } + + pyb_flash_obj_t *self = mp_obj_malloc(pyb_flash_obj_t, &pyb_flash_type); + self->use_native_block_size = false; + self->start = start; + self->len = len; + + return MP_OBJ_FROM_PTR(self); +} + static void pyb_flash_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self == &pyb_flash_obj) { @@ -296,30 +319,15 @@ static mp_obj_t pyb_flash_make_new(const mp_obj_type_t *type, size_t n_args, siz // Default singleton object that accesses entire flash, including virtual partition table return MP_OBJ_FROM_PTR(&pyb_flash_obj); } - - pyb_flash_obj_t *self = mp_obj_malloc(pyb_flash_obj_t, &pyb_flash_type); - self->use_native_block_size = false; - - uint32_t bl_len = (storage_get_block_count() - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE; - mp_int_t start = args[ARG_start].u_int; - if (start == -1) { - start = 0; - } else if (!(0 <= start && start < bl_len && start % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { - mp_raise_ValueError(NULL); - } - mp_int_t len = args[ARG_len].u_int; - if (len == -1) { - len = bl_len - start; - } else if (!(0 < len && start + len <= bl_len && len % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + + mp_obj_t self = pyb_flash_new_obj(start, len); + if (self == mp_const_none) { + // Invalid start or end arg mp_raise_ValueError(NULL); } - - self->start = start; - self->len = len; - - return MP_OBJ_FROM_PTR(self); + return self; } static mp_obj_t pyb_flash_readblocks(size_t n_args, const mp_obj_t *args) { diff --git a/ports/stm32/storage.h b/ports/stm32/storage.h index 05654855aa0..accf6c39043 100644 --- a/ports/stm32/storage.h +++ b/ports/stm32/storage.h @@ -77,4 +77,8 @@ extern const struct _pyb_flash_obj_t pyb_flash_obj; struct _fs_user_mount_t; void pyb_flash_init_vfs(struct _fs_user_mount_t *vfs); +#if !BUILDING_MBOOT +mp_obj_t pyb_flash_new_obj(mp_int_t start, mp_int_t len); +#endif + #endif // MICROPY_INCLUDED_STM32_STORAGE_H From 1a47379dd64ab0dab557092293cc9a9e27b5a418 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 15 Jan 2025 08:39:16 +0100 Subject: [PATCH 0434/2098] stm32/boards: Add F427 AF CSV file. Signed-off-by: iabdalkader --- ports/stm32/boards/stm32f427_af.csv | 170 ++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 ports/stm32/boards/stm32f427_af.csv diff --git a/ports/stm32/boards/stm32f427_af.csv b/ports/stm32/boards/stm32f427_af.csv new file mode 100644 index 00000000000..34d5a94cb8f --- /dev/null +++ b/ports/stm32/boards/stm32f427_af.csv @@ -0,0 +1,170 @@ +Port ,Pin ,AF0 ,AF1 ,AF2 ,AF3 ,AF4 ,AF5 ,AF6 ,AF7 ,AF8 ,AF9 ,AF10 ,AF11 ,AF12 ,AF13 ,AF14,AF15 ,ADC + , ,SYS ,TIM1/2 ,TIM3/4/5,TIM8/9/10/11,I2C1/2/3 ,SPI1/2/3/4/5/6 ,SPI2/3/SAI1 ,SPI3/USART1/2/3,USART6/UART4/5/7/8,CAN1/2/TIM12/13/14/LCD,OTG2_HS/OTG1_FS,ETH ,FMC/SDIO/OTG2_FS ,DCMI ,LCD ,SYS , +PortA,PA0 , ,TIM2_CH1/TIM2_ETR,TIM5_CH1,TIM8_ETR , , , ,USART2_CTS ,UART4_TX , , ,ETH_MII_CRS , , , ,EVENTOUT, +PortA,PA1 , ,TIM2_CH2 ,TIM5_CH2, , , , ,USART2_RTS ,UART4_RX , , ,ETH_MII_RX_CLK/ETH_RMII_REF_CLK, , , ,EVENTOUT,ADC123_IN1 +PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3,TIM9_CH1 , , , ,USART2_TX , , , ,ETH_MDIO , , , ,EVENTOUT,ADC123_IN2 +PortA,PA3 , ,TIM2_CH4 ,TIM5_CH4,TIM9_CH2 , , , ,USART2_RX , , ,OTG_HS_ULPI_D0 ,ETH_MII_COL , , , ,EVENTOUT,ADC123_IN3 +PortA,PA4 , , , , , ,SPI1_NSS ,SPI3_NSS/I2S3_WS ,USART2_CK , , , , ,OTG_HS_SOF ,DCMI_HSYNC , ,EVENTOUT,ADC12_IN4 +PortA,PA5 , ,TIM2_CH1/TIM2_ETR, ,TIM8_CH1N , ,SPI1_SCK , , , , ,OTG_HS_ULPI_CK , , , , ,EVENTOUT,ADC12_IN5 +PortA,PA6 , ,TIM1_BKIN ,TIM3_CH1,TIM8_BKIN , ,SPI1_MISO , , , ,TIM13_CH1 , , , ,DCMI_PIXCLK, ,EVENTOUT,ADC12_IN6 +PortA,PA7 , ,TIM1_CH1N ,TIM3_CH2,TIM8_CH1N , ,SPI1_MOSI , , , ,TIM14_CH1 , ,ETH_MII_RX_DV/ETH_RMII_CRS_DV , , , ,EVENTOUT,ADC12_IN7 +PortA,PA8 ,MCO1 ,TIM1_CH1 , , ,I2C3_SCL , , ,USART1_CK , , ,OTG_FS_SOF , , , , ,EVENTOUT, +PortA,PA9 , ,TIM1_CH2 , , ,I2C3_SMBA, , ,USART1_TX , , , , , ,DCMI_D0 , ,EVENTOUT, +PortA,PA10, ,TIM1_CH3 , , , , , ,USART1_RX , , ,OTG_FS_ID , , ,DCMI_D1 , ,EVENTOUT, +PortA,PA11, ,TIM1_CH4 , , , , , ,USART1_CTS , ,CAN1_RX ,OTG_FS_DM , , , , ,EVENTOUT, +PortA,PA12, ,TIM1_ETR , , , , , ,USART1_RTS , ,CAN1_TX ,OTG_FS_DP , , , , ,EVENTOUT, +PortA,PA13,JTMS/SWDIO , , , , , , , , , , , , , , ,EVENTOUT, +PortA,PA14,JTCK/SWCLK , , , , , , , , , , , , , , ,EVENTOUT, +PortA,PA15,JTDI ,TIM2_CH1/TIM2_ETR, , , ,SPI1_NSS ,SPI3_NSS/I2S3_WS , , , , , , , , ,EVENTOUT, +PortB,PB0 , ,TIM1_CH2N ,TIM3_CH3,TIM8_CH2N , , , , , , ,OTG_HS_ULPI_D1 ,ETH_MII_RXD2 , , , ,EVENTOUT,ADC12_IN8 +PortB,PB1 , ,TIM1_CH3N ,TIM3_CH4,TIM8_CH3N , , , , , , ,OTG_HS_ULPI_D2 ,ETH_MII_RXD3 , , , ,EVENTOUT,ADC12_IN9 +PortB,PB2 , , , , , , , , , , , , , , , ,EVENTOUT, +PortB,PB3 ,JTDO/TRACESWO,TIM2_CH2 , , , ,SPI1_SCK ,SPI3_SCK/I2S3_CK , , , , , , , , ,EVENTOUT, +PortB,PB4 ,NJTRST , ,TIM3_CH1, , ,SPI1_MISO ,SPI3_MISO ,I2S3ext_SD , , , , , , , ,EVENTOUT, +PortB,PB5 , , ,TIM3_CH2, ,I2C1_SMBA,SPI1_MOSI ,SPI3_MOSI/I2S3_SD, , ,CAN2_RX ,OTG_HS_ULPI_D7 ,ETH_PPS_OUT ,FMC_SDCKE1 ,DCMI_D10 , ,EVENTOUT, +PortB,PB6 , , ,TIM4_CH1, ,I2C1_SCL , , ,USART1_TX , ,CAN2_TX , , ,FMC_SDNE1 ,DCMI_D5 , ,EVENTOUT, +PortB,PB7 , , ,TIM4_CH2, ,I2C1_SDA , , ,USART1_RX , , , , ,FMC_NL ,DCMI_VSYNC , ,EVENTOUT, +PortB,PB8 , , ,TIM4_CH3,TIM10_CH1 ,I2C1_SCL , , , , ,CAN1_RX , ,ETH_MII_TXD3 ,SDIO_D4 ,DCMI_D6 , ,EVENTOUT, +PortB,PB9 , , ,TIM4_CH4,TIM11_CH1 ,I2C1_SDA ,SPI2_NSS/I2S2_WS , , , ,CAN1_TX , , ,SDIO_D5 ,DCMI_D7 , ,EVENTOUT, +PortB,PB10, ,TIM2_CH3 , , ,I2C2_SCL ,SPI2_SCK/I2S2_CK , ,USART3_TX , , ,OTG_HS_ULPI_D3 ,ETH_MII_RX_ER , , , ,EVENTOUT, +PortB,PB11, ,TIM2_CH4 , , ,I2C2_SDA , , ,USART3_RX , , ,OTG_HS_ULPI_D4 ,ETH_MII_TX_EN/ETH_RMII_TX_EN , , , ,EVENTOUT, +PortB,PB12, ,TIM1_BKIN , , ,I2C2_SMBA,SPI2_NSS/I2S2_WS , ,USART3_CK , ,CAN2_RX ,OTG_HS_ULPI_D5 ,ETH_MII_TXD0/ETH_RMII_TXD0 ,OTG_HS_ID , , ,EVENTOUT, +PortB,PB13, ,TIM1_CH1N , , , ,SPI2_SCK/I2S2_CK , ,USART3_CTS , ,CAN2_TX ,OTG_HS_ULPI_D6 ,ETH_MII_TXD1/ETH_RMII_TXD1 , , , ,EVENTOUT, +PortB,PB14, ,TIM1_CH2N , ,TIM8_CH2N , ,SPI2_MISO ,I2S2ext_SD ,USART3_RTS , ,TIM12_CH1 , , ,OTG_HS_DM , , ,EVENTOUT, +PortB,PB15,RTC_REFIN ,TIM1_CH3N , ,TIM8_CH3N , ,SPI2_MOSI/I2S2_SD, , , ,TIM12_CH2 , , ,OTG_HS_DP , , ,EVENTOUT, +PortC,PC0 , , , , , , , , , , ,OTG_HS_ULPI_STP, ,FMC_SDNWE , , ,EVENTOUT,ADC123_IN10 +PortC,PC1 , , , , , , , , , , , ,ETH_MDC , , , ,EVENTOUT,ADC123_IN11 +PortC,PC2 , , , , , ,SPI2_MISO ,I2S2ext_SD , , , ,OTG_HS_ULPI_DIR,ETH_MII_TXD2 ,FMC_SDNE0 , , ,EVENTOUT,ADC123_IN12 +PortC,PC3 , , , , , ,SPI2_MOSI/I2S2_SD, , , , ,OTG_HS_ULPI_NXT,ETH_MII_TX_CLK ,FMC_SDCKE0 , , ,EVENTOUT,ADC123_IN13 +PortC,PC4 , , , , , , , , , , , ,ETH_MII_RXD0/ETH_RMII_RXD0 , , , ,EVENTOUT,ADC12_IN14 +PortC,PC5 , , , , , , , , , , , ,ETH_MII_RXD1/ETH_RMII_RXD1 , , , ,EVENTOUT,ADC12_IN15 +PortC,PC6 , , ,TIM3_CH1,TIM8_CH1 , ,I2S2_MCK , , ,USART6_TX , , , ,SDIO_D6 ,DCMI_D0 , ,EVENTOUT, +PortC,PC7 , , ,TIM3_CH2,TIM8_CH2 , , ,I2S3_MCK , ,USART6_RX , , , ,SDIO_D7 ,DCMI_D1 , ,EVENTOUT, +PortC,PC8 , , ,TIM3_CH3,TIM8_CH3 , , , , ,USART6_CK , , , ,SDIO_D0 ,DCMI_D2 , ,EVENTOUT, +PortC,PC9 ,MCO2 , ,TIM3_CH4,TIM8_CH4 ,I2C3_SDA ,I2S_CKIN , , , , , , ,SDIO_D1 ,DCMI_D3 , ,EVENTOUT, +PortC,PC10, , , , , , ,SPI3_SCK/I2S3_CK ,USART3_TX ,UART4_TX , , , ,SDIO_D2 ,DCMI_D8 , ,EVENTOUT, +PortC,PC11, , , , , ,I2S3ext_SD ,SPI3_MISO ,USART3_RX ,UART4_RX , , , ,SDIO_D3 ,DCMI_D4 , ,EVENTOUT, +PortC,PC12, , , , , , ,SPI3_MOSI/I2S3_SD,USART3_CK ,UART5_TX , , , ,SDIO_CK ,DCMI_D9 , ,EVENTOUT, +PortC,PC13, , , , , , , , , , , , , , , ,EVENTOUT, +PortC,PC14, , , , , , , , , , , , , , , ,EVENTOUT, +PortC,PC15, , , , , , , , , , , , , , , ,EVENTOUT, +PortD,PD0 , , , , , , , , , ,CAN1_RX , , ,FMC_D2 , , ,EVENTOUT, +PortD,PD1 , , , , , , , , , ,CAN1_TX , , ,FMC_D3 , , ,EVENTOUT, +PortD,PD2 , , ,TIM3_ETR, , , , , ,UART5_RX , , , ,SDIO_CMD ,DCMI_D11 , ,EVENTOUT, +PortD,PD3 , , , , , ,SPI2_SCK/I2S2_CK , ,USART2_CTS , , , , ,FMC_CLK ,DCMI_D5 , ,EVENTOUT, +PortD,PD4 , , , , , , , ,USART2_RTS , , , , ,FMC_NOE , , ,EVENTOUT, +PortD,PD5 , , , , , , , ,USART2_TX , , , , ,FMC_NWE , , ,EVENTOUT, +PortD,PD6 , , , , , ,SPI3_MOSI/I2S3_SD,SAI1_SD_A ,USART2_RX , , , , ,FMC_NWAIT ,DCMI_D10 , ,EVENTOUT, +PortD,PD7 , , , , , , , ,USART2_CK , , , , ,FMC_NE1/FMC_NCE2 , , ,EVENTOUT, +PortD,PD8 , , , , , , , ,USART3_TX , , , , ,FMC_D13 , , ,EVENTOUT, +PortD,PD9 , , , , , , , ,USART3_RX , , , , ,FMC_D14 , , ,EVENTOUT, +PortD,PD10, , , , , , , ,USART3_CK , , , , ,FMC_D15 , , ,EVENTOUT, +PortD,PD11, , , , , , , ,USART3_CTS , , , , ,FMC_A16 , , ,EVENTOUT, +PortD,PD12, , ,TIM4_CH1, , , , ,USART3_RTS , , , , ,FMC_A17 , , ,EVENTOUT, +PortD,PD13, , ,TIM4_CH2, , , , , , , , , ,FMC_A18 , , ,EVENTOUT, +PortD,PD14, , ,TIM4_CH3, , , , , , , , , ,FMC_D0 , , ,EVENTOUT, +PortD,PD15, , ,TIM4_CH4, , , , , , , , , ,FMC_D1 , , ,EVENTOUT, +PortE,PE0 , , ,TIM4_ETR, , , , , ,UART8_RX , , , ,FMC_NBL0 ,DCMI_D2 , ,EVENTOUT, +PortE,PE1 , , , , , , , , ,UART8_TX , , , ,FMC_NBL1 ,DCMI_D3 , ,EVENTOUT, +PortE,PE2 ,TRACECLK , , , , ,SPI4_SCK ,SAI1_MCLK_A , , , , ,ETH_MII_TXD3 ,FMC_A23 , , ,EVENTOUT, +PortE,PE3 ,TRACED0 , , , , , ,SAI1_SD_B , , , , , ,FMC_A19 , , ,EVENTOUT, +PortE,PE4 ,TRACED1 , , , , ,SPI4_NSS ,SAI1_FS_A , , , , , ,FMC_A20 ,DCMI_D4 , ,EVENTOUT, +PortE,PE5 ,TRACED2 , , ,TIM9_CH1 , ,SPI4_MISO ,SAI1_SCK_A , , , , , ,FMC_A21 ,DCMI_D6 , ,EVENTOUT, +PortE,PE6 ,TRACED3 , , ,TIM9_CH2 , ,SPI4_MOSI ,SAI1_SD_A , , , , , ,FMC_A22 ,DCMI_D7 , ,EVENTOUT, +PortE,PE7 , ,TIM1_ETR , , , , , , ,UART7_RX , , , ,FMC_D4 , , ,EVENTOUT, +PortE,PE8 , ,TIM1_CH1N , , , , , , ,UART7_TX , , , ,FMC_D5 , , ,EVENTOUT, +PortE,PE9 , ,TIM1_CH1 , , , , , , , , , , ,FMC_D6 , , ,EVENTOUT, +PortE,PE10, ,TIM1_CH2N , , , , , , , , , , ,FMC_D7 , , ,EVENTOUT, +PortE,PE11, ,TIM1_CH2 , , , ,SPI4_NSS , , , , , , ,FMC_D8 , , ,EVENTOUT, +PortE,PE12, ,TIM1_CH3N , , , ,SPI4_SCK , , , , , , ,FMC_D9 , , ,EVENTOUT, +PortE,PE13, ,TIM1_CH3 , , , ,SPI4_MISO , , , , , , ,FMC_D10 , , ,EVENTOUT, +PortE,PE14, ,TIM1_CH4 , , , ,SPI4_MOSI , , , , , , ,FMC_D11 , , ,EVENTOUT, +PortE,PE15, ,TIM1_BKIN , , , , , , , , , , ,FMC_D12 , , ,EVENTOUT, +PortF,PF0 , , , , ,I2C2_SDA , , , , , , , ,FMC_A0 , , ,EVENTOUT, +PortF,PF1 , , , , ,I2C2_SCL , , , , , , , ,FMC_A1 , , ,EVENTOUT, +PortF,PF2 , , , , ,I2C2_SMBA, , , , , , , ,FMC_A2 , , ,EVENTOUT, +PortF,PF3 , , , , , , , , , , , , ,FMC_A3 , , ,EVENTOUT,ADC3_IN9 +PortF,PF4 , , , , , , , , , , , , ,FMC_A4 , , ,EVENTOUT,ADC3_IN14 +PortF,PF5 , , , , , , , , , , , , ,FMC_A5 , , ,EVENTOUT,ADC3_IN15 +PortF,PF6 , , , ,TIM10_CH1 , ,SPI5_NSS ,SAI1_SD_B , ,UART7_RX , , , ,FMC_NIORD , , ,EVENTOUT,ADC3_IN4 +PortF,PF7 , , , ,TIM11_CH1 , ,SPI5_SCK ,SAI1_MCLK_B , ,UART7_TX , , , ,FMC_NREG , , ,EVENTOUT,ADC3_IN5 +PortF,PF8 , , , , , ,SPI5_MISO ,SAI1_SCK_B , , ,TIM13_CH1 , , ,FMC_NIOWR , , ,EVENTOUT,ADC3_IN6 +PortF,PF9 , , , , , ,SPI5_MOSI ,SAI1_FS_B , , ,TIM14_CH1 , , ,FMC_CD , , ,EVENTOUT,ADC3_IN7 +PortF,PF10, , , , , , , , , , , , ,FMC_INTR ,DCMI_D11 , ,EVENTOUT,ADC3_IN8 +PortF,PF11, , , , , ,SPI5_MOSI , , , , , , ,FMC_SDNRAS ,DCMI_D12 , ,EVENTOUT, +PortF,PF12, , , , , , , , , , , , ,FMC_A6 , , ,EVENTOUT, +PortF,PF13, , , , , , , , , , , , ,FMC_A7 , , ,EVENTOUT, +PortF,PF14, , , , , , , , , , , , ,FMC_A8 , , ,EVENTOUT, +PortF,PF15, , , , , , , , , , , , ,FMC_A9 , , ,EVENTOUT, +PortG,PG0 , , , , , , , , , , , , ,FMC_A10 , , ,EVENTOUT, +PortG,PG1 , , , , , , , , , , , , ,FMC_A11 , , ,EVENTOUT, +PortG,PG2 , , , , , , , , , , , , ,FMC_A12 , , ,EVENTOUT, +PortG,PG3 , , , , , , , , , , , , ,FMC_A13 , , ,EVENTOUT, +PortG,PG4 , , , , , , , , , , , , ,FMC_A14/FMC_BA0 , , ,EVENTOUT, +PortG,PG5 , , , , , , , , , , , , ,FMC_A15/FMC_BA1 , , ,EVENTOUT, +PortG,PG6 , , , , , , , , , , , , ,FMC_INT2 ,DCMI_D12 , ,EVENTOUT, +PortG,PG7 , , , , , , , , ,USART6_CK , , , ,FMC_INT3 ,DCMI_D13 , ,EVENTOUT, +PortG,PG8 , , , , , ,SPI6_NSS , , ,USART6_RTS , , ,ETH_PPS_OUT ,FMC_SDCLK , , ,EVENTOUT, +PortG,PG9 , , , , , , , , ,USART6_RX , , , ,FMC_NE2/FMC_NCE3 ,DCMI_VSYNC , ,EVENTOUT, +PortG,PG10, , , , , , , , , , , , ,FMC_NCE4_1/FMC_NE3,DCMI_D2 , ,EVENTOUT, +PortG,PG11, , , , , , , , , , , ,ETH_MII_TX_EN/ETH_RMII_TX_EN ,FMC_NCE4_2 ,DCMI_D3 , ,EVENTOUT, +PortG,PG12, , , , , ,SPI6_MISO , , ,USART6_RTS , , , ,FMC_NE4 , , ,EVENTOUT, +PortG,PG13, , , , , ,SPI6_SCK , , ,USART6_CTS , , ,ETH_MII_TXD0/ETH_RMII_TXD0 ,FMC_A24 , , ,EVENTOUT, +PortG,PG14, , , , , ,SPI6_MOSI , , ,USART6_TX , , ,ETH_MII_TXD1/ETH_RMII_TXD1 ,FMC_A25 , , ,EVENTOUT, +PortG,PG15, , , , , , , , ,USART6_CTS , , , ,FMC_SDNCAS ,DCMI_D13 , ,EVENTOUT, +PortH,PH0 , , , , , , , , , , , , , , , ,EVENTOUT, +PortH,PH1 , , , , , , , , , , , , , , , ,EVENTOUT, +PortH,PH2 , , , , , , , , , , , ,ETH_MII_CRS ,FMC_SDCKE0 , , ,EVENTOUT, +PortH,PH3 , , , , , , , , , , , ,ETH_MII_COL ,FMC_SDNE0 , , ,EVENTOUT, +PortH,PH4 , , , , ,I2C2_SCL , , , , , ,OTG_HS_ULPI_NXT, , , , ,EVENTOUT, +PortH,PH5 , , , , ,I2C2_SDA ,SPI5_NSS , , , , , , ,FMC_SDNWE , , ,EVENTOUT, +PortH,PH6 , , , , ,I2C2_SMBA,SPI5_SCK , , , ,TIM12_CH1 , , ,FMC_SDNE1 ,DCMI_D8 , , , +PortH,PH7 , , , , ,I2C3_SCL ,SPI5_MISO , , , , , ,ETH_MII_RXD3 ,FMC_SDCKE1 ,DCMI_D9 , , , +PortH,PH8 , , , , ,I2C3_SDA , , , , , , , ,FMC_D16 ,DCMI_HSYNC , ,EVENTOUT, +PortH,PH9 , , , , ,I2C3_SMBA, , , , ,TIM12_CH2 , , ,FMC_D17 ,DCMI_D0 , ,EVENTOUT, +PortH,PH10, , ,TIM5_CH1, , , , , , , , , ,FMC_D18 ,DCMI_D1 , ,EVENTOUT, +PortH,PH11, , ,TIM5_CH2, , , , , , , , , ,FMC_D19 ,DCMI_D2 , ,EVENTOUT, +PortH,PH12, , ,TIM5_CH3, , , , , , , , , ,FMC_D20 ,DCMI_D3 , ,EVENTOUT, +PortH,PH13, , , ,TIM8_CH1N , , , , , ,CAN1_TX , , ,FMC_D21 , , ,EVENTOUT, +PortH,PH14, , , ,TIM8_CH2N , , , , , , , , ,FMC_D22 ,DCMI_D4 , ,EVENTOUT, +PortH,PH15, , , ,TIM8_CH3N , , , , , , , , ,FMC_D23 ,DCMI_D11 , ,EVENTOUT, +PortI,PI0 , , ,TIM5_CH4, , ,SPI2_NSS/I2S2_WS , , , , , , ,FMC_D24 ,DCMI_D13 , ,EVENTOUT, +PortI,PI1 , , , , , ,SPI2_SCK/I2S2_CK , , , , , , ,FMC_D25 ,DCMI_D8 , ,EVENTOUT, +PortI,PI2 , , , ,TIM8_CH4 , ,SPI2_MISO ,I2S2ext_SD , , , , , ,FMC_D26 ,DCMI_D9 , ,EVENTOUT, +PortI,PI3 , , , ,TIM8_ETR , ,SPI2_MOSI/I2S2_SD, , , , , , ,FMC_D27 ,DCMI_D10 , ,EVENTOUT, +PortI,PI4 , , , ,TIM8_BKIN , , , , , , , , ,FMC_NBL2 ,DCMI_D5 , ,EVENTOUT, +PortI,PI5 , , , ,TIM8_CH1 , , , , , , , , ,FMC_NBL3 ,DCMI_VSYNC , ,EVENTOUT, +PortI,PI6 , , , ,TIM8_CH2 , , , , , , , , ,FMC_D28 ,DCMI_D6 , ,EVENTOUT, +PortI,PI7 , , , ,TIM8_CH3 , , , , , , , , ,FMC_D29 ,DCMI_D7 , ,EVENTOUT, +PortI,PI8 , , , , , , , , , , , , , , , ,EVENTOUT, +PortI,PI9 , , , , , , , , , ,CAN1_RX , , ,FMC_D30 , , ,EVENTOUT, +PortI,PI10, , , , , , , , , , , ,ETH_MII_RX_ER ,FMC_D31 , , ,EVENTOUT, +PortI,PI11, , , , , , , , , , ,OTG_HS_ULPI_DIR, , , , ,EVENTOUT, +PortI,PI12, , , , , , , , , , , , , , , ,EVENTOUT, +PortI,PI13, , , , , , , , , , , , , , , ,EVENTOUT, +PortI,PI14, , , , , , , , , , , , , , , ,EVENTOUT, +PortI,PI15, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ0 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ1 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ2 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ3 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ4 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ5 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ6 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ7 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ8 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ9 , , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ10, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ11, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ12, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ13, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ14, , , , , , , , , , , , , , , ,EVENTOUT, +PortJ,PJ15, , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK0 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK1 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK2 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK3 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK4 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK5 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK6 , , , , , , , , , , , , , , , ,EVENTOUT, +PortK,PK7 , , , , , , , , , , , , , , , ,EVENTOUT, From 3b948893d827c39c967b0ca17da3e8159eaef176 Mon Sep 17 00:00:00 2001 From: Herwin Grobben Date: Wed, 5 Feb 2025 11:08:09 +0100 Subject: [PATCH 0435/2098] stm32/stm32_it: Add handler for timer 20 interrupt. Signed-off-by: Herwin Grobben --- ports/stm32/stm32_it.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 2e625a5894d..4bf509bb946 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -775,6 +775,14 @@ void TIM17_FDCAN_IT1_IRQHandler(void) { } #endif +#if defined(STM32G4) +void TIM20_UP_IRQHandler(void) { + IRQ_ENTER(TIM20_UP_IRQn); + timer_irq_handler(20); + IRQ_EXIT(TIM20_UP_IRQn); +} +#endif + #if defined(STM32H7) void TIM15_IRQHandler(void) { IRQ_ENTER(TIM15_IRQn); From 91386b3d56b4ef3bb27af89846c1cc2712f6065f Mon Sep 17 00:00:00 2001 From: Herwin Grobben Date: Wed, 5 Feb 2025 13:58:57 +0100 Subject: [PATCH 0436/2098] stm32/timer: Use APB2 to calculate timer 20 source frequency. Signed-off-by: Herwin Grobben --- ports/stm32/timer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index acfdbd84eb2..5c333eb942a 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -264,8 +264,8 @@ uint32_t timer_get_source_freq(uint32_t tim_id) { #else uint32_t source, clk_div; - if (tim_id == 1 || (8 <= tim_id && tim_id <= 11)) { - // TIM{1,8,9,10,11} are on APB2 + if (tim_id == 1 || (8 <= tim_id && tim_id <= 11) || tim_id == 20) { + // TIM{1,8,9,10,11,20} are on APB2 #if defined(STM32F0) || defined(STM32G0) source = HAL_RCC_GetPCLK1Freq(); clk_div = RCC->CFGR & RCC_CFGR_PPRE; From c18e925431c57384a8f28c8699fe46c8b22aaa3a Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 24 Mar 2025 00:28:43 +1100 Subject: [PATCH 0437/2098] stm32/timer: Add support for STM32H5 Timer 1. Signed-off-by: Matt Trentini --- ports/stm32/timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 5c333eb942a..9d65b484cd1 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -840,7 +840,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(1, TIM1_BRK_UP_TRG_COM_IRQn), #elif defined(STM32F4) || defined(STM32F7) TIM_ENTRY(1, TIM1_UP_TIM10_IRQn), - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32H5) TIM_ENTRY(1, TIM1_UP_IRQn), #elif defined(STM32G4) || defined(STM32L4) || defined(STM32WB) TIM_ENTRY(1, TIM1_UP_TIM16_IRQn), From ac1cbef36661625dabc47bc4aa0aa7a0d076dec1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Mar 2025 14:18:04 +1100 Subject: [PATCH 0438/2098] stm32/qspi: Add qspi_memory_map_exit and restart. Signed-off-by: Damien George --- ports/stm32/qspi.c | 27 ++++++++++++++++++++------- ports/stm32/qspi.h | 2 ++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 1311c27d1d4..7334ece4f21 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -170,6 +170,25 @@ void qspi_memory_map(void) { qspi_mpu_enable_mapped(); } +void qspi_memory_map_exit(void) { + // Prevent access to QSPI memory-mapped region. + qspi_mpu_disable_all(); + + // Abort any ongoing transfer if peripheral is busy + if (QUADSPI->SR & QUADSPI_SR_BUSY) { + QUADSPI->CR |= QUADSPI_CR_ABORT; + while (QUADSPI->CR & QUADSPI_CR_ABORT) { + } + } +} + +// Needed on F7 due to errata 2.4.3: "Memory-mapped read operations may fail when timeout counter is enabled". +// Call this function to disable then re-enable memory-mapped mode, which resets the CS pin to inactive. +void qspi_memory_map_restart(void) { + qspi_memory_map_exit(); + qspi_memory_map(); +} + static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { (void)self_in; switch (cmd) { @@ -178,13 +197,7 @@ static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { break; case MP_QSPI_IOCTL_BUS_ACQUIRE: // Disable memory-mapped region during bus access - qspi_mpu_disable_all(); - // Abort any ongoing transfer if peripheral is busy - if (QUADSPI->SR & QUADSPI_SR_BUSY) { - QUADSPI->CR |= QUADSPI_CR_ABORT; - while (QUADSPI->CR & QUADSPI_CR_ABORT) { - } - } + qspi_memory_map_exit(); break; case MP_QSPI_IOCTL_BUS_RELEASE: // Switch to memory-map mode when bus is idle diff --git a/ports/stm32/qspi.h b/ports/stm32/qspi.h index 6fe91168cfd..a2d6b9f328d 100644 --- a/ports/stm32/qspi.h +++ b/ports/stm32/qspi.h @@ -35,6 +35,8 @@ extern const mp_qspi_proto_t qspi_proto; void qspi_init(void); void qspi_memory_map(void); +void qspi_memory_map_exit(void); +void qspi_memory_map_restart(void); static inline bool qspi_is_valid_addr(uint32_t addr) { return QSPI_MAP_ADDR <= addr && addr < QSPI_MAP_ADDR_MAX; From 1660faacf6c8a5837a76dfa2b12f90e87347863c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Mar 2025 22:01:56 +1100 Subject: [PATCH 0439/2098] stm32/boards/PYBD_SF2: Restart qspi memory-mapped mode during startup. The PYBD boards use an F7xx which has an errata 2.4.3: Memory-mapped read operations may fail when timeout counter is enabled This is unfortunate because it means that once QSPI memory-mapped flash is accessed the QSPI peripheral will leave the CS pin active (low) forever, which increases power consumption of the SPI flash chip (because it's active and waiting for commands). The exact amount of power increase depends on the flash, but the PYBD_SFx increase by about 2.5mA. Previously this increase in power only happened when QSPI flash was needed, eg on PYBD_SF2 when mbedtls or nimble libraries were used. On PYBD_SF6 it's actually never used. But with the introduction of ROMFS which lives in the QSPI flash, the memory is always access on start up to see if the ROMFS contains a valid image (it must read the memory to find out). That means these boards always consume about 2.5mA more after starting up (compared to when ROMFS is disabled). The fix in this commit is to explicitly restart the QSPI memory mapped mode during the start up process. More precisely, the restart is done after querying the ROMFS and just before trying to execute `boot.py`. That's the right location to keep power consumption permanently down if the QSPI is never used (eg ROMFS image doesn't exist). Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/board_init.c | 10 ++++++++++ ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/ports/stm32/boards/PYBD_SF2/board_init.c b/ports/stm32/boards/PYBD_SF2/board_init.c index b39da97c458..2f277d1cce7 100644 --- a/ports/stm32/boards/PYBD_SF2/board_init.c +++ b/ports/stm32/boards/PYBD_SF2/board_init.c @@ -61,6 +61,16 @@ void board_early_init(void) { #if !BUILDING_MBOOT +#include "boardctrl.h" +#include "qspi.h" + +int board_run_boot_py(boardctrl_state_t *state) { + // Due to errata 2.4.3, restart memory-mapped mode to deactivate the CS line and save power. + qspi_memory_map_restart(); + + return boardctrl_run_boot_py(state); +} + void board_sleep(int value) { mp_spiflash_deepsleep(&spi_bdev.spiflash, value); mp_spiflash_deepsleep(&spi_bdev2.spiflash, value); diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index 3ae11125d8b..da5ab9e7fa1 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -43,11 +43,14 @@ #define MICROPY_HW_ENABLE_RF_SWITCH (1) #define MICROPY_BOARD_EARLY_INIT board_early_init +#define MICROPY_BOARD_RUN_BOOT_PY board_run_boot_py #define MICROPY_BOARD_ENTER_STOP board_sleep(1); #define MICROPY_BOARD_LEAVE_STOP board_sleep(0); #define MICROPY_BOARD_ENTER_STANDBY board_sleep(1); #define MICROPY_BOARD_SDCARD_POWER mp_hal_pin_high(pyb_pin_EN_3V3); +struct _boardctrl_state_t; void board_early_init(void); +int board_run_boot_py(struct _boardctrl_state_t *state); void board_sleep(int value); // HSE is 25MHz, run SYS at 120MHz From 9ab6906f50fe41b6168ce64ae792e3750fd311f9 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 22 Mar 2025 02:35:32 +0100 Subject: [PATCH 0440/2098] esp32/esp32_common.cmake: Use native gchelper for RISC-V. This commit changes the gchelper implementation in use for RV32-based targets (ESP32C3, ESP32C6) from the generic one written in C to the one written in assembler that is specific to the CPU in question. The native implementation is already exercised on most CI builds as it is used by the QEMU port to compile and test the RV32 target. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index f7b00900a03..d026a64b0ce 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -16,7 +16,10 @@ endif() # RISC-V specific inclusions if(CONFIG_IDF_TARGET_ARCH_RISCV) - list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_native.c + ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s + ) list(APPEND IDF_COMPONENTS riscv) endif() From fda9bf491767603b762926fde35325480b5b14ee Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 22 Mar 2025 02:40:59 +0100 Subject: [PATCH 0441/2098] esp32/esp32_common.cmake: Clean up RISC-V directives. This commit cleans up a couple of RISC-V specific directives in the build script. Namely, removes the forced inclusion of the "riscv" component and introduces proper mpy-cross flags. The "riscv" component is already included by the ESP-IDF build framework, as certain low-level components would not build otherwise, so there is no need to add it to the required components list. The architecture flag for mpy-cross is now set for RISC-V targets, as it was previously set only for Xtensa targets (and it relied on a string comparison rather than using the appropriate configuration variable). Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index d026a64b0ce..99fcebac326 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -20,7 +20,6 @@ if(CONFIG_IDF_TARGET_ARCH_RISCV) ${MICROPY_DIR}/shared/runtime/gchelper_native.c ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s ) - list(APPEND IDF_COMPONENTS riscv) endif() if(NOT DEFINED MICROPY_PY_TINYUSB) @@ -237,8 +236,10 @@ idf_component_register( set(MICROPY_TARGET ${COMPONENT_TARGET}) # Define mpy-cross flags, for use with frozen code. -if(CONFIG_IDF_TARGET_ARCH STREQUAL "xtensa") -set(MICROPY_CROSS_FLAGS -march=xtensawin) +if(CONFIG_IDF_TARGET_ARCH_XTENSA) + set(MICROPY_CROSS_FLAGS -march=xtensawin) +elseif(CONFIG_IDF_TARGET_ARCH_RISCV) + set(MICROPY_CROSS_FLAGS -march=rv32imc) endif() # Set compile options for this port. From 6bb586619dc1b9022ca191016a3c32a6f89f3caa Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 22 Mar 2025 02:45:10 +0100 Subject: [PATCH 0442/2098] esp32/esp32_common.cmake: Remove obsolete definition. This commit removes a definition used back when ESP-IDF v4 was supported by MicroPython. Those times are now long gone, and so is the need for that particular definition to be set in the first place. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 99fcebac326..dedef6d782c 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -247,7 +247,6 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC ${MICROPY_DEF_CORE} ${MICROPY_DEF_BOARD} ${MICROPY_DEF_TINYUSB} - MICROPY_ESP_IDF_4=1 MICROPY_VFS_FAT=1 MICROPY_VFS_LFS2=1 FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" From 3805e65ed3b7306329bf0305d5b46f08d7619a11 Mon Sep 17 00:00:00 2001 From: Jon Nordby Date: Sat, 29 Mar 2025 00:38:14 +0100 Subject: [PATCH 0443/2098] tools/mpy_ld.py: Give better error for unsupported ARM absolute relocs. This is a known limitation, so better to give a clear warning than a catch-all AssertionError. Happens for example when trying to use soft-float on ARCH=armv6m Also give more details on the assertion for unknown relocations, such that one can see which symbol it affects etc, to aid in debugging. References issue #14430. Signed-off-by: Jon Nordby --- tools/mpy_ld.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 44a76bdee6b..70cab2b894b 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -702,9 +702,13 @@ def do_relocation_text(env, text_addr, r): elif env.arch.name == "EM_RISCV": (addr, value) = process_riscv32_relocation(env, text_addr, r) + elif env.arch.name == "EM_ARM" and r_info_type == R_ARM_ABS32: + # happens for soft-float on armv6m + raise ValueError("Absolute relocations not supported on ARM") + else: # Unknown/unsupported relocation - assert 0, r_info_type + assert 0, (r_info_type, s.name, s.entry, env.arch.name) # Write relocation if env.arch.name == "EM_RISCV": From e34412f0f49032fcd3a08317c8f2e01c7bdae24f Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 5 Apr 2025 22:53:25 +1100 Subject: [PATCH 0444/2098] tools/ci.sh: Manually install picotool for rp2 builds. If picotool is not installed, it's fetched and built when compiling each rp2 board. And the "develop" branch of picotool is used instead of a release. Installing it manually using the "master" branch means the latest released version is used (instead of a possibly unstable development version), and also makes building each rp2 board a little faster. Signed-off-by: Damien George --- tools/ci.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index c7a6db79d6c..948eeeaef2d 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -22,6 +22,15 @@ function ci_gcc_riscv_setup { riscv64-unknown-elf-gcc --version } +function ci_picotool_setup { + # Manually installing picotool ensures we use a release version, and speeds up the build. + git clone https://github.com/raspberrypi/pico-sdk.git + (cd pico-sdk && git submodule update --init lib/mbedtls) + git clone https://github.com/raspberrypi/picotool.git + (cd picotool && mkdir build && cd build && cmake -DPICO_SDK_PATH=../../pico-sdk .. && make && sudo make install) + picotool version +} + ######################################################################################## # c code formatting @@ -62,6 +71,7 @@ function ci_code_size_setup { gcc --version ci_gcc_arm_setup ci_gcc_riscv_setup + ci_picotool_setup } function ci_code_size_build { @@ -355,6 +365,7 @@ function ci_renesas_ra_board_build { function ci_rp2_setup { ci_gcc_arm_setup + ci_picotool_setup } function ci_rp2_build { From 57f1e60dd05a58b6d78c72af31ee4d706b1c47a1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 1 Apr 2025 12:09:41 +1100 Subject: [PATCH 0445/2098] tests/cpydiff: Update CPy diff for assign expr in nested comprehensions. Since 7c1584aef1 MicroPython matches CPython in most cases, aside from nested comprehensions. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/cpydiff/syntax_assign_expr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cpydiff/syntax_assign_expr.py b/tests/cpydiff/syntax_assign_expr.py index 58f57ca1fbe..704c5c3ecab 100644 --- a/tests/cpydiff/syntax_assign_expr.py +++ b/tests/cpydiff/syntax_assign_expr.py @@ -1,8 +1,8 @@ """ categories: Syntax,Operators -description: MicroPython allows using := to assign to the variable of a comprehension, CPython raises a SyntaxError. -cause: MicroPython is optimised for code size and doesn't check this case. -workaround: Do not rely on this behaviour if writing CPython compatible code. +description: MicroPython allows := to assign to the iteration variable in nested comprehensions, CPython does not. +cause: MicroPython is optimised for code size. Although it is a syntax error to assign to the iteration variable in a standard comprehension (same as CPython), it doesn't check if an inner nested comprehension assigns to the iteration variable of the outer comprehension. +workaround: Do not use := to assign to the iteration variable of a comprehension. """ -print([i := -1 for i in range(4)]) +print([[(j := i) for i in range(2)] for j in range(2)]) From e9a80fc9a0a13a6afe43973c526ea25b7bcdb9e7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 1 Apr 2025 11:53:31 +1100 Subject: [PATCH 0446/2098] tests/cpydiff: Remove types_str_endswith. MicroPython support for this behaviour was added in eb45d97898a. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/cpydiff/types_str_endswith.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 tests/cpydiff/types_str_endswith.py diff --git a/tests/cpydiff/types_str_endswith.py b/tests/cpydiff/types_str_endswith.py deleted file mode 100644 index 890c7ba5ef4..00000000000 --- a/tests/cpydiff/types_str_endswith.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -categories: Types,str -description: Start/end indices such as str.endswith(s, start) not implemented -cause: Unknown -workaround: Unknown -""" - -print("abc".endswith("c", 1)) From 74a5bf94c187d41d0458814312d57d2105233726 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 1 Apr 2025 11:52:04 +1100 Subject: [PATCH 0447/2098] tools/gen-cpydiff.py: Fail CPython diff generation if output matches. Previously this information was recorded in a "status" field of the result, but nothing ever parsed this result which led to non-differences not being removed. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/gen-cpydiff.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index c0b5734389c..2f9394deeae 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -69,7 +69,6 @@ "code", "output_cpy", "output_upy", - "status", ], ) @@ -98,7 +97,7 @@ def readfiles(): if not re.match(r"\s*# fmt: (on|off)\s*", x) ) - output = Output(test, class_, desc, cause, workaround, code, "", "", "") + output = Output(test, class_, desc, cause, workaround, code, "", "") files.append(output) except IndexError: print("Incorrect format in file " + test_fullpath) @@ -108,6 +107,7 @@ def readfiles(): def run_tests(tests): """executes all tests""" + same_results = False results = [] for test in tests: test_fullpath = os.path.join(TESTPATH, test.name) @@ -133,23 +133,26 @@ def run_tests(tests): output_upy = [com.decode("utf8") for com in process.communicate(input_py)] if output_cpy[0] == output_upy[0] and output_cpy[1] == output_upy[1]: - status = "Supported" - print("Supported operation!\nFile: " + test_fullpath) + print("Error: Test has same output in CPython vs MicroPython: " + test_fullpath) + same_results = True else: - status = "Unsupported" - - output = Output( - test.name, - test.class_, - test.desc, - test.cause, - test.workaround, - test.code, - output_cpy, - output_upy, - status, + output = Output( + test.name, + test.class_, + test.desc, + test.cause, + test.workaround, + test.code, + output_cpy, + output_upy, + ) + results.append(output) + + if same_results: + raise SystemExit( + "Failing due to non-differences in results. If MicroPython behaviour has changed " + "to match CPython, please remove the file(s) mentioned above." ) - results.append(output) results.sort(key=lambda x: x.class_) return results From 9e9be83fd69a36bf82019d1ecfcafa3b317ae899 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 29 Mar 2025 10:11:38 +0100 Subject: [PATCH 0448/2098] tools/mpremote: Allow .img for ROMFS file and validate ROMFS image. Currently the tool allows writing an invalid ROMFS image, with a bad header or images smaller than minimum size, and only checks the image extension. This commit allows deploying a ROMFS with either a ".img" or ".romfs" extension (in the future support may be added for other extensions that have different semantics, eg a manifest), and validates the image header before writing. Signed-off-by: iabdalkader --- tools/mpremote/mpremote/commands.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 7dd448c8b77..e385d050927 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -9,7 +9,7 @@ from .transport import TransportError, TransportExecError, stdout_write_bytes from .transport_serial import SerialTransport -from .romfs import make_romfs +from .romfs import make_romfs, VfsRomWriter class CommandError(Exception): @@ -555,7 +555,7 @@ def _do_romfs_deploy(state, args): romfs_filename = args.path # Read in or create the ROMFS filesystem image. - if romfs_filename.endswith(".romfs"): + if os.path.isfile(romfs_filename) and romfs_filename.endswith((".img", ".romfs")): with open(romfs_filename, "rb") as f: romfs = f.read() else: @@ -581,6 +581,11 @@ def _do_romfs_deploy(state, args): rom_size = transport.eval("len(dev)") print(f"ROMFS{rom_id} partition has size {rom_size} bytes") + # Check if ROMFS image is valid + if not romfs.startswith(VfsRomWriter.ROMFS_HEADER): + print("Invalid ROMFS image") + sys.exit(1) + # Check if ROMFS filesystem image will fit in the target partition. if len(romfs) > rom_size: print("ROMFS image is too big for the target partition") From 11f057dd9a5801e23b39822b3a57cbfa8cd10f00 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 6 Aug 2024 19:49:40 +0100 Subject: [PATCH 0449/2098] rp2: Add support for PSRAM 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 (required). Changes are: - ports/rp2/rp2_psram.[ch]: Add new PSRAM module. - ports/rp2/main.c: Add optional PSRAM support. - ports/rp2/CMakeLists.txt: Include rp2_psram.c. - ports/rp2/mpconfigport.h: Add MICROPY_HW_ENABLE_PSRAM. - ports/rp2/modmachine.c: Reconfigure PSRAM on freq change. Co-authored-by: Kirk Benell Co-authored-by: Mike Bell Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/main.c | 19 ++++ ports/rp2/modmachine.c | 4 + ports/rp2/mpconfigport.h | 4 + ports/rp2/rp2_psram.c | 198 +++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_psram.h | 44 +++++++++ 6 files changed, 270 insertions(+) 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 f89e2792c64..1a5029c1504 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 58da63c06f1..764af6b7466 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -46,6 +46,7 @@ #include "mpnetworkport.h" #include "genhdr/mpversion.h" #include "mp_usbd.h" +#include "rp2_psram.h" #include "pico/stdlib.h" #include "pico/binary_info.h" @@ -93,6 +94,10 @@ int main(int argc, char **argv) { // Hook for setting up anything that needs to be super early in the boot-up process. MICROPY_BOARD_STARTUP(); + #if MICROPY_HW_ENABLE_PSRAM + size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif + #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) setup_default_uart(); @@ -120,7 +125,21 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); + + #if MICROPY_HW_ENABLE_PSRAM + if (psram_size) { + #if MICROPY_GC_SPLIT_HEAP + gc_init(&__GcHeapStart, &__GcHeapEnd); + gc_add((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #else + gc_init((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #endif + } else { + gc_init(&__GcHeapStart, &__GcHeapEnd); + } + #else gc_init(&__GcHeapStart, &__GcHeapEnd); + #endif #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 31665a7640d..bf4e43a613c 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 MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif } static void mp_machine_idle(void) { diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 38dcad1dae6..28022ba4209 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -81,6 +81,10 @@ #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 #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #define MICROPY_ALLOC_PATH_MAX (128) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c new file mode 100644 index 00000000000..bb063f4af7a --- /dev/null +++ b/ports/rp2/rp2_psram.c @@ -0,0 +1,198 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Phil Howard + * Mike Bell + * Kirk D. Benell + * + * 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" + +#if MICROPY_HW_ENABLE_PSRAM + +#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" + +size_t __no_inline_not_in_flash_func(psram_detect)(void) { + int psram_size = 0; + + // 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 + } + } + + 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); + + uint32_t intr_stash = save_and_disable_interrupts(); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + restore_interrupts(intr_stash); + 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) { + } + + // 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 | \ + 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); + + restore_interrupts(intr_stash); + + return psram_size; +} + +#endif diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h new file mode 100644 index 00000000000..0eddf634159 --- /dev/null +++ b/ports/rp2/rp2_psram.h @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Phil Howard + * Mike Bell + * Kirk D. Benell + * + * 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_RP2_PSRAM_H +#define MICROPY_INCLUDED_RP2_RP2_PSRAM_H + +#if MICROPY_HW_ENABLE_PSRAM +#ifndef MICROPY_HW_PSRAM_CS_PIN +#error "MICROPY_HW_ENABLE_PSRAM requires MICROPY_HW_PSRAM_CS_PIN" +#endif + +#define PSRAM_BASE _u(0x11000000) + +extern size_t psram_init(uint cs_pin); +#endif + +#endif From b7d5caf2a3e08fa90bbb09b43b29455e2c20eaf6 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:33 +0100 Subject: [PATCH 0450/2098] rp2/mpconfigport: Configure heap for PSRAM. 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. Due to the size of PSRAM, GC stack was overflowing and causing the GC to scan through the entire memory pool. This caused noticable slowdowns during GC. Increase the stack from 256 to 4096 bytes to avoid overflow and increase the stack entry type size to accomodate 8MB+ PSRAM. Changes are: - ports/rp2/mpconfigport.h: Make split-heap optional and enable by default. - ports/rp2/mpconfigport.h: Increase GC stack entry type to uint32_t. - ports/rp2/mpconfigport.h: Raise GC stack size. Co-authored-by: Kirk Benell Signed-off-by: Phil Howard --- ports/rp2/mpconfigport.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 28022ba4209..3d65737266b 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -86,7 +86,15 @@ #endif // Memory allocation policies +#if MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#define MICROPY_ALLOC_GC_STACK_SIZE (1024) // Avoid slowdown when GC stack overflow causes a full sweep of PSRAM-backed heap +#else #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#endif +#ifndef MICROPY_GC_SPLIT_HEAP +#define MICROPY_GC_SPLIT_HEAP MICROPY_HW_ENABLE_PSRAM // whether PSRAM is added to or replaces the heap +#endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) From 89eea0f5e8d7fe2a1c0ca7c3bc1578b417d8fb8c Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 21:39:05 +0100 Subject: [PATCH 0451/2098] rp2/rp2_flash: Support flash writes from PSRAM. Add a 256 byte (FLASH_PAGE_SIZE) SRAM copy buffer to allow copies from PSRAM to flash. This would otherwise hardfault since PSRAM is disabled when doing a write to flash. Changes are: - ports/rp2/rp2_flash.c: Add 256 byte (flash page size) SRAM copy buffer for PSRAM to flash copies. - ports/rp2/rp2_flash.c: Invalidate the XIP cache to purge any PSRAM data before critical flash operations. Co-authored-by: Phil Howard Co-authored-by: Angus Gratton Signed-off-by: Phil Howard --- ports/rp2/rp2_flash.c | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index f3f154a1402..8739f17a4a5 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -33,9 +33,13 @@ #include "modrp2.h" #include "hardware/flash.h" #include "pico/binary_info.h" +#include "rp2_psram.h" #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) +// Size of buffer for flash writes from PSRAM, since they are mutually exclusive +#define COPY_BUFFER_SIZE_BYTES (FLASH_PAGE_SIZE) + static_assert(MICROPY_HW_ROMFS_BYTES % 4096 == 0, "ROMFS size must be a multiple of 4K"); static_assert(MICROPY_HW_FLASH_STORAGE_BYTES % 4096 == 0, "Flash storage size must be a multiple of 4K"); @@ -96,10 +100,27 @@ static uint32_t begin_critical_flash_section(void) { if (use_multicore_lockout()) { multicore_lockout_start_blocking(); } - return save_and_disable_interrupts(); + uint32_t state = save_and_disable_interrupts(); + + #if MICROPY_HW_ENABLE_PSRAM + // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM + // Use the upper 16k of the maintenance space (0x1bffc000 through 0x1bffffff) to workaround + // incorrect behaviour of the XIP clean operation, where it also alters the tag of the associated + // cache line: https://forums.raspberrypi.com/viewtopic.php?t=378249#p2263677 + volatile uint8_t *maintenance_ptr = (volatile uint8_t *)(XIP_SRAM_BASE + (XIP_MAINTENANCE_BASE - XIP_BASE)); + for (int i = 1; i < 16 * 1024; i += 8) { + maintenance_ptr[i] = 0; + } + #endif + + return state; } static void end_critical_flash_section(uint32_t state) { + #if MICROPY_HW_ENABLE_PSRAM + // The ROM function to program flash will reset PSRAM timings to defaults + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif restore_interrupts(state); if (use_multicore_lockout()) { multicore_lockout_end_blocking(); @@ -192,10 +213,43 @@ 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 copying from SRAM, can write direct to flash. + // If copying from PSRAM/flash, use an SRAM buffer and write in chunks. + #if MICROPY_HW_ENABLE_PSRAM + bool write_direct = (uintptr_t)bufinfo.buf >= SRAM_BASE; + #else + bool write_direct = true; + #endif + + if (write_direct) { + // If copying from SRAM, write direct + 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 MICROPY_HW_ENABLE_PSRAM + else { + size_t bytes_left = bufinfo.len; + size_t bytes_offset = 0; + static uint8_t copy_buffer[COPY_BUFFER_SIZE_BYTES] = {0}; + + while (bytes_left) { + memcpy(copy_buffer, bufinfo.buf + bytes_offset, MIN(bytes_left, COPY_BUFFER_SIZE_BYTES)); + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset + bytes_offset, copy_buffer, MIN(bytes_left, COPY_BUFFER_SIZE_BYTES)); + end_critical_flash_section(atomic_state); + bytes_offset += COPY_BUFFER_SIZE_BYTES; + if (bytes_left <= COPY_BUFFER_SIZE_BYTES) { + break; + } + bytes_left -= COPY_BUFFER_SIZE_BYTES; + mp_event_handle_nowait(); + } + } + #endif + // TODO check return value return mp_const_none; } From 91cff8e4f10ea665c5f3f4b16d62c98d6ca22037 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Tue, 25 Mar 2025 11:32:06 +0000 Subject: [PATCH 0452/2098] rp2/rp2_flash: Configure optimal flash timings. Configure flash timings dynamically to match the system clock. Reconfigure timings after flash writes. Changes are: - ports/rp2/main.c: Set default flash timings. - ports/rp2/modmachine.c: Configure optimal flash timings on freq change. - ports/rp2/rp2_flash.c: Reconfigure flash when leaving critical section. Signed-off-by: Phil Howard --- ports/rp2/main.c | 4 +++ ports/rp2/modmachine.c | 12 +++++++ ports/rp2/rp2_flash.c | 71 +++++++++++++++++++++++++++++++++++++++++- ports/rp2/rp2_flash.h | 34 ++++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 ports/rp2/rp2_flash.h diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 764af6b7466..0f10f63c6d2 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -26,6 +26,7 @@ #include +#include "rp2_flash.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -94,6 +95,9 @@ int main(int argc, char **argv) { // Hook for setting up anything that needs to be super early in the boot-up process. MICROPY_BOARD_STARTUP(); + // Set the flash divisor to an appropriate value + rp2_flash_set_timing(); + #if MICROPY_HW_ENABLE_PSRAM size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index bf4e43a613c..58a3a8ae4d8 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 8739f17a4a5..a9beabf051c 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -34,6 +34,12 @@ #include "hardware/flash.h" #include "pico/binary_info.h" #include "rp2_psram.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) @@ -94,6 +100,48 @@ static bool use_multicore_lockout(void) { ; } +// 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) { @@ -117,8 +165,9 @@ static uint32_t begin_critical_flash_section(void) { } static void end_critical_flash_section(uint32_t state) { + // The ROM function to program flash will have reset flash and PSRAM timings to defaults + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); #if MICROPY_HW_ENABLE_PSRAM - // The ROM function to program flash will reset PSRAM timings to defaults psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif restore_interrupts(state); @@ -313,3 +362,23 @@ mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { } } #endif + +// 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(void) { + 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 00000000000..3c1fbbff2f9 --- /dev/null +++ b/ports/rp2/rp2_flash.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Mike Bell + * Phil Howard + * + * 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_RP2_RP2_FLASH_H +#define MICROPY_INCLUDED_RP2_RP2_FLASH_H + +extern void rp2_flash_set_timing_for_freq(int clock_hz); +extern void rp2_flash_set_timing(void); + +#endif From 0ee160e7c06ddc6641257d5ebb88d006e731a3aa Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 18 Dec 2024 14:49:56 +1100 Subject: [PATCH 0453/2098] extmod/extmod.mk: Add cyw43_spi.c to list of sources. This file is part of the updated cyw43-driver. It will only be used if `CYW43_USE_SPI` is enabled. Signed-off-by: Damien George --- extmod/extmod.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/extmod/extmod.mk b/extmod/extmod.mk index a510f3c54c2..d2fe2acbad9 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -458,6 +458,7 @@ SRC_THIRDPARTY_C += $(addprefix $(CYW43_DIR)/src/,\ cyw43_lwip.c \ cyw43_ll.c \ cyw43_sdio.c \ + cyw43_spi.c \ cyw43_stats.c \ ) ifeq ($(MICROPY_PY_BLUETOOTH),1) From a9384c71c50a7bc4aaae1f88ddee5ebacc56ba81 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 28 Feb 2025 23:19:31 +1100 Subject: [PATCH 0454/2098] extmod/extmod.mk: Switch from drivers/cyw43/cywbt to lib/cyw43-drivers. The cyw43-driver now provides the Bluetooth initialisation code, making `drivers/cyw43/cywbt.c` obsolete. To use the new code a port must enable the `CYW43_ENABLE_BLUETOOTH_OVER_UART` option. Some ports have yet to migrate to the new code, so in the meantime they can explicitly add the old source to their source list and continue to use it without change. Signed-off-by: Damien George --- extmod/extmod.mk | 4 +--- ports/mimxrt/Makefile | 1 + ports/stm32/Makefile | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/extmod.mk b/extmod/extmod.mk index d2fe2acbad9..859f610c90a 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -454,6 +454,7 @@ CYW43_DIR = lib/cyw43-driver GIT_SUBMODULES += $(CYW43_DIR) CFLAGS_EXTMOD += -DMICROPY_PY_NETWORK_CYW43=1 SRC_THIRDPARTY_C += $(addprefix $(CYW43_DIR)/src/,\ + cyw43_bthci_uart.c \ cyw43_ctrl.c \ cyw43_lwip.c \ cyw43_ll.c \ @@ -461,9 +462,6 @@ SRC_THIRDPARTY_C += $(addprefix $(CYW43_DIR)/src/,\ cyw43_spi.c \ cyw43_stats.c \ ) -ifeq ($(MICROPY_PY_BLUETOOTH),1) -DRIVERS_SRC_C += drivers/cyw43/cywbt.c -endif $(BUILD)/$(CYW43_DIR)/src/cyw43_%.o: CFLAGS += -std=c11 endif # MICROPY_PY_NETWORK_CYW43 diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 8106ab4f3ff..5c542950280 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -280,6 +280,7 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c +DRIVERS_SRC_C += drivers/cyw43/cywbt.c endif # MICROPY_PY_BLUETOOTH ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 8ac9a8af03d..c938dcbda7d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -432,6 +432,7 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c +DRIVERS_SRC_C += drivers/cyw43/cywbt.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) SRC_C += mpnimbleport.c From 7268034d56d353e90992f0d688eb4a35ae86e09a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 20 Feb 2025 23:35:07 +1100 Subject: [PATCH 0455/2098] top: Add "ser" to codespell exclusion list. This word appears in the upcoming alif port. Signed-off-by: Damien George --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8053df24dd7..263b120d3fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.codespell] count = "" ignore-regex = '\b[A-Z]{3}\b' -ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,notin,numer,shft,synopsys,technic,ure,curren" +ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,notin,numer,ser,shft,synopsys,technic,ure,curren" quiet-level = 3 skip = """ */build*,\ From c5102a7858e9e23aaaa99e70bb0d196bd7e25ba4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Dec 2023 00:11:32 +1100 Subject: [PATCH 0456/2098] lib/alif_ensemble-cmsis-dfp: Add new submodule for Alif SDK v1.3.2. Signed-off-by: Damien George --- .gitmodules | 3 +++ lib/alif_ensemble-cmsis-dfp | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/alif_ensemble-cmsis-dfp diff --git a/.gitmodules b/.gitmodules index 6338f0e66d6..7c5a5172592 100644 --- a/.gitmodules +++ b/.gitmodules @@ -68,3 +68,6 @@ [submodule "lib/arduino-lib"] path = lib/arduino-lib url = https://github.com/arduino/arduino-lib-mpy.git +[submodule "lib/alif_ensemble-cmsis-dfp"] + path = lib/alif_ensemble-cmsis-dfp + url = https://github.com/alifsemi/alif_ensemble-cmsis-dfp.git diff --git a/lib/alif_ensemble-cmsis-dfp b/lib/alif_ensemble-cmsis-dfp new file mode 160000 index 00000000000..04b3176a003 --- /dev/null +++ b/lib/alif_ensemble-cmsis-dfp @@ -0,0 +1 @@ +Subproject commit 04b3176a0031c945d4306db00663379e9b431e08 From 1356860e2258cf2ecef43a65f40d83ddcbf98d0b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Mar 2025 16:47:39 +1100 Subject: [PATCH 0457/2098] lib/alif-security-toolkit: Add new submodule for Alif Security Toolkit. Signed-off-by: Damien George --- .gitmodules | 3 +++ lib/alif-security-toolkit | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/alif-security-toolkit diff --git a/.gitmodules b/.gitmodules index 7c5a5172592..02849ec9bdd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -71,3 +71,6 @@ [submodule "lib/alif_ensemble-cmsis-dfp"] path = lib/alif_ensemble-cmsis-dfp url = https://github.com/alifsemi/alif_ensemble-cmsis-dfp.git +[submodule "lib/alif-security-toolkit"] + path = lib/alif-security-toolkit + url = https://github.com/micropython/alif-security-toolkit.git diff --git a/lib/alif-security-toolkit b/lib/alif-security-toolkit new file mode 160000 index 00000000000..63698efe856 --- /dev/null +++ b/lib/alif-security-toolkit @@ -0,0 +1 @@ +Subproject commit 63698efe8567eed115fe620d5e5de75f460310d7 From 092d85557cc901a2840ac4abce3d12eb520c9765 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Dec 2023 00:11:41 +1100 Subject: [PATCH 0458/2098] alif/tinyusb_port: Add Alif TinyUSB DCD driver. From https://github.com/alifsemi/alif_vscode-tinyusb.git, commit c79f39361d334ee44f44fed30c56e70dbb368649 Signed-off-by: Damien George Signed-off-by: iabdalkader --- ports/alif/tinyusb_port/alif_dcd_reg.h | 691 ++++++++++++++++++++++++ ports/alif/tinyusb_port/tusb_alif_dcd.c | 688 +++++++++++++++++++++++ 2 files changed, 1379 insertions(+) create mode 100644 ports/alif/tinyusb_port/alif_dcd_reg.h create mode 100644 ports/alif/tinyusb_port/tusb_alif_dcd.c diff --git a/ports/alif/tinyusb_port/alif_dcd_reg.h b/ports/alif/tinyusb_port/alif_dcd_reg.h new file mode 100644 index 00000000000..84220f6be4c --- /dev/null +++ b/ports/alif/tinyusb_port/alif_dcd_reg.h @@ -0,0 +1,691 @@ +// *FORMAT-OFF* +///------------------------------------------------------------------------------------------------- +/// @file alif_dcd_reg.h +/// @author karol.saja@alifsemi.com +/// @version 0.0.1 +/// @date 2023-09-08 +/// @brief Low Level SPI driver +///------------------------------------------------------------------------------------------------- + +#ifndef __ALIF_DCD_REG_H__ +#define __ALIF_DCD_REG_H__ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +//-------------------------------------------------------------------------------------------------- + +#define _rw volatile uint32_t +#define __w volatile uint32_t +#define __r volatile const uint32_t + +volatile struct { + union { + _rw gsbuscfg0; + struct { + _rw incrbrstena : 1; + _rw incr4brstena : 1; + _rw incr8brstena : 1; + _rw incr16brstena : 1; + _rw incr32brstena : 1; + _rw incr64brstena : 1; + _rw incr128brstena : 1; + _rw incr256brstena : 1; + __r : 2; + _rw desbigend : 1; + _rw datbigend : 1; + __r : 4; + _rw deswrreqinfo : 4; + _rw datwrreqinfo : 4; + _rw desrdreqinfo : 4; + _rw datrdreqinfo : 4; + } gsbuscfg0_b; + }; + + union { + _rw gsbuscfg1; + struct { + __r : 8; + _rw pipetranslimit : 4; + _rw en1kpage : 1; + __r : 19; + } gsbuscfg1_b; + }; + + __r : 32; __r : 32; + + union { + _rw gctl; + struct { + _rw dsblclkgtng : 1; + _rw gblhibernationen : 1; + __r : 1; + _rw disscramble : 1; + _rw scaledown : 2; + _rw ramclksel : 2; + __r : 2; + _rw sofitpsync : 1; + _rw coresoftreset : 1; + _rw prtcapdir : 2; + _rw frmscldwn : 2; + __r : 1; + _rw bypssetaddr : 1; + __r : 14; + } gctl_b; + }; + + __r : 32; + + union { + _rw gsts; + struct { + __r curmod : 2; + __r : 2; + _rw buserraddrvld : 1; + _rw csrtimeout : 1; + __r device_ip : 1; + __r host_ip : 1; + __r adp_ip : 1; + __r bc_ip : 1; + __r otg_ip : 1; + __r ssic_ip : 1; + __r : 8; + __r cbelt : 12; + } gsts_b; + }; + + union { + _rw guctl1; + struct { + _rw loa_filter_en : 1; + _rw ovrld_l1_susp_com : 1; + _rw hc_parchk_disable : 1; + _rw hc_errata_enable : 1; + _rw l1_susp_thrld_for_host : 4; + _rw l1_susp_thrld_en_for_host : 1; + _rw dev_hs_nyet_bulk_spr : 1; + _rw resume_opmode_hs_host : 1; + __r : 1; + _rw disusb2refclkgtng : 1; + __r : 2; + _rw parkmode_disable_fsls : 1; + _rw parkmode_disable_hs : 1; + __r : 1; + _rw nak_per_enh_hs : 1; + _rw nak_per_enh_fs : 1; + _rw dev_lsp_tail_lock_dis : 1; + _rw ip_gap_add_on : 2; + _rw dev_l1_exit_by_hw : 1; + __r : 2; + _rw dev_trb_out_spr_ind : 1; + _rw tx_ipgap_linecheck_dis : 1; + _rw filter_se0_fsls_eop : 1; + __r : 1; + _rw dev_decouple_l1l2_evt : 1; + } guctl1_b; + }; + + union { + _rw gsnpsid; + }; + + __r : 32; + + union { + _rw guid; + struct { + _rw userid : 32; + } guid_b; + }; + + union { // <- host only, leaving unimplemented for now [FIXME] + _rw guctl; + struct { + } guctl_b; + }; + + union { + _rw gbuserraddrlo; + struct { + _rw buserraddr : 32; + } gbuserraddrlo_b; + }; + + union { + _rw gbuserraddrhi; + struct { + _rw buserraddr : 32; + } gbuserraddrhi_b; + }; + + __r : 32; __r : 32; + + __r ghwparams[8]; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; + + // base + 0x9C here + + union { + _rw guctl2; + struct { + __r : 11; + _rw disablecfc : 1; + _rw enableepcacheevict : 1; + __r : 1; + _rw rst_actbitlater : 1; + __r : 4; + _rw en_hp_pm_timer : 7; + __r : 6; + } guctl2_b; + }; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x100 here + + union { + _rw gusb2phycfg0; + struct { + _rw toutcal : 3; + _rw phyif : 1; + __r ulpi_utmi_sel : 1; + __r fsintf : 1; + _rw suspendusb20 : 1; + _rw physel : 1; + _rw enblslpm : 1; + _rw xcvrdly : 1; + _rw usbtrdtim : 4; + __r : 5; + _rw lsipd : 3; + _rw lstrd : 3; + _rw ovrd_fsls_disc_time : 1; + __r inv_sel_hsic : 1; + __r hsic_con_width_adj : 2; + _rw ulpi_lpm_with_opmode_chk : 1; + _rw u2_freeclk_exists : 1; + _rw physoftrst : 1; + } gusb2phycfg0_b; + }; + + __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x200 here + + union { + _rw gtxfifosiz[4]; + struct { + _rw txfdep : 16; + _rw txfstaddr : 16; + } gtxfifosiz_b[4]; + }; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x280 here + + union { + _rw grxfifosiz[4]; + struct { + _rw rxfdep : 16; + _rw rxfstaddr : 16; + } grxfifosiz_b[4]; + }; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x300 here + + union { + _rw gevntadrlo0; + struct { + _rw evntadrlo : 32; + } gevntadrlo0_b; + }; + + union { + _rw gevntadrhi0; + struct { + _rw evntadrhi : 32; + } gevntadrhi0_b; + }; + + union { + _rw gevntsiz0; + struct { + _rw eventsiz : 16; + __r : 15; + _rw evntintrptmask : 1; + } gevntsiz0_b; + }; + + union { + _rw gevntcount0; + struct { + _rw evntcount : 16; + __r : 15; + _rw evnt_handler_busy : 1; + } gevntcount0_b; + }; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x500 here + + __r ghwparams8; + + __r : 32; __r : 32; __r : 32; + + // base + 0x510 here + + union { + _rw gtxfifopridev; + struct { + _rw gtxfifopridev : 4; + __r : 28; + } gtxfifopridev_b; + }; + + __r : 32; + + union { + _rw gtxfifoprihst; + struct { + _rw gtxfifoprihst : 2; + __r : 30; + } gtxfifoprihst_b; + }; + + union { + _rw grxfifoprihst; + struct { + _rw grxfifoprihst : 2; + __r : 30; + } grxfifoprihst_b; + }; + + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x530 here + + union { + _rw gfladj; + struct { + _rw gfladj_30mhz : 6; + __r : 1; + _rw gfladj_30mhz_sdbnd_sel : 1; + _rw gfladj_refclk_fladj : 14; + __r : 1; + _rw gfladj_refclk_lpm_sel : 1; + _rw gfladj_refclk_240mhz_decr : 7; + _rw gfladj_refclk_240mhzdecr_pls1 : 1; + } gfladj_b; + }; + + __r : 32; __r : 32; __r : 32; + + // base + 0x540 here + + union { + _rw gusb2rhbctl0; + struct { + _rw ovrd_l1timeout : 4; + __r : 28; + } gusb2rhbctl0_b; + }; + +} *ugbl = (void *) (USB_BASE + 0xC100); + +volatile struct { + union { + _rw dcfg; + struct { + _rw devspd : 3; + _rw devaddr : 7; + __r : 2; + _rw intrnum : 5; + _rw nump : 5; + _rw lpmcap : 1; + _rw ignstrmpp : 1; + __r : 8; + } dcfg_b; + }; + + union { + _rw dctl; + struct { + __r : 1; + _rw tstctl : 4; + __w ulstchngreq : 4; + __r : 7; + _rw css : 1; + _rw crs : 1; + _rw l1hibernationen : 1; + _rw keepconnect : 1; + _rw lpm_nyet_thres : 4; + _rw hirdthres : 5; + __r : 1; + _rw csftrst : 1; + _rw run_stop : 1; + } dctl_b; + }; + + union { + _rw devten; + struct { + _rw dissconnevten : 1; + _rw usbrstevten : 1; + _rw connectdoneevten : 1; + _rw ulstcngen : 1; + _rw wkupevten : 1; + _rw hibernationreqevten : 1; + _rw u3l2l1suspen : 1; + _rw softevten : 1; + _rw l1suspen : 1; + _rw errticerrevten : 1; + __r : 2; + _rw vendevtstrcvden : 1; + __r : 1; + _rw l1wkupevten : 1; + __r : 1; + _rw eccerren : 1; + __r : 15; + } devten_b; + }; + + union { + _rw dsts; + struct { + __r connectspd : 3; + __r soffn : 14; + __r rxfifoempty : 1; + __r usblnkst : 4; + __r devctrlhlt : 1; + __r coreidle : 1; + __r sss : 1; + __r rss : 1; + __r : 2; + _rw sre : 1; + __r dcnrd : 1; + __r : 2; + } dsts_b; + }; + + union { + _rw dgcmdpar; + struct { + _rw parameter : 32; + } dgcmdpar_b; + }; + + union { + _rw dgcmd; + struct { + _rw cmdtyp : 8; + _rw cmdioc : 1; + __r : 1; + _rw cmdact : 1; + __r : 1; + __r cmdstatus : 4; + __r : 16; + } dgcmd_b; + }; + + __r : 32; __r : 32; + + union { + _rw dalepena; + struct { + _rw usbactep : 32; + } dalepena_b; + }; + + __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x100 here + + struct { + union { + _rw par2; + struct { + _rw parameter : 32; + } par2_b; + }; + + union { + _rw par1; + struct { + _rw parameter : 32; + } par1_b; + }; + + union { + _rw par0; + struct { + _rw parameter : 32; + } par0_b; + }; + + union { + _rw depcmd; + struct { + _rw cmdtyp : 4; + __r : 4; + _rw cmdioc : 1; + __r : 1; + _rw cmdact : 1; + _rw hipri_forcerm : 1; + _rw cmdstatus : 4; + _rw commandparam : 16; + } depcmd_b; + }; + } depcmd[8]; + + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + __r : 32; __r : 32; __r : 32; __r : 32; + + // base + 0x300 here + + union { + _rw dev_imod0; + struct { + _rw device_imodi : 16; + _rw device_imocd : 16; + } dev_imod0_b; + }; +} *udev = (void *) (USB_BASE + 0xC700); + +//-------------------------------------------------------------------------------------------------- + +typedef union { + _rw val : 32; + struct { + _rw sig : 8; + _rw evt : 5; + __r : 3; + _rw info : 9; + __r : 7; + } devt; + struct { + _rw sig : 1; + _rw ep : 5; + _rw evt : 4; + __r : 2; + _rw sts : 4; + _rw par : 16; + } depevt; +} evt_t; + +//-------------------------------------------------------------------------------------------------- + +enum { + CMDTYP_RESERVED = 0, + CMDTYP_DEPCFG, + CMDTYP_DEPXFERCFG, + CMDTYP_DEPGETSTATE, + CMDTYP_DEPSSTALL, + CMDTYP_DEPCSTALL, + CMDTYP_DEPSTRTXFER, + CMDTYP_DEPUPDXFER, + CMDTYP_DEPENDXFER, + CMDTYP_DEPSTARTCFG +} DEPCMD_CMDTYP; + +enum { + DEVT_DISCONNEVT = 0, + DEVT_USBRST, + DEVT_CONNECTDONE, + DEVT_ULSTCHNG, + DEVT_WKUPEVT, + // DEVT_HIBRQ not implemented + DEVT_USBSUSP = 6, + DEVT_SOF, + DEVT_L1SUSP, + DEVT_ERRTICERR, + DEVT_CMDCMPLT, + DEVT_EVNTOVERFLOW, + DEVT_VNDDEVTSTRCVED, + // reserved + DEVT_L1RESM = 14, + // reserved + DEVT_ECCERR = 16 +} DEVT; + +enum { + // reserved + DEPEVT_XFERCOMPLETE = 1, + DEPEVT_XFERINPROGRESS, + DEPEVT_XFERNOTREADY, + // not implemented + DEPEVT_STREAMEVT = 6, + DEPEVT_EPCMDCMPLT +} DEPEVT; + +enum { + // reserved + TRBCTL_NORMAL = 1, + TRBCTL_CTL_SETUP, + TRBCTL_CTL_STAT2, + TRBCTL_CTL_STAT3, + TRBCTL_CTL_DATA, + TRBCTL_ISO_FIRST, + TRBCTL_ISO, + TRBCTL_LINK, + TRBCTL_NORMAL_ZLP +} TRBCTL; + +//-------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------- + +#ifdef __cplusplus +} +#endif // __cplusplus + + +#endif // __ALIF_DCD_REG_H__ diff --git a/ports/alif/tinyusb_port/tusb_alif_dcd.c b/ports/alif/tinyusb_port/tusb_alif_dcd.c new file mode 100644 index 00000000000..510f0e9c13a --- /dev/null +++ b/ports/alif/tinyusb_port/tusb_alif_dcd.c @@ -0,0 +1,688 @@ +// *FORMAT-OFF* +/* Copyright (C) 2024 Alif Semiconductor - All Rights Reserved. + * Use, distribution and modification of this code is permitted under the + * terms stated in the Alif Semiconductor Software License Agreement + * + * You should have received a copy of the Alif Semiconductor Software + * License Agreement with this file. If not, please write to: + * contact@alifsemi.com, or visit: https://alifsemi.com/license + * + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED + +#if defined(CORE_M55_HE) +#include "M55_HE.h" +#elif defined(CORE_M55_HP) +#include "M55_HP.h" +#else +#error "Unsupported core!" +#endif + +#include "device/dcd.h" + +#include "alif_dcd_reg.h" + +#include "clk.h" +#include "power.h" + +// #define TUSB_ALIF_DEBUG +// #define TUSB_ALIF_DEBUG_DEPTH (2048) + +#if defined(TUSB_ALIF_DEBUG) +#if (1 < TUSB_ALIF_DEBUG_DEPTH) +#define LOG(...) memset(logbuf[bi % TUSB_ALIF_DEBUG_DEPTH], ' ', 48);\ + snprintf(logbuf[(bi++) % TUSB_ALIF_DEBUG_DEPTH], 48, __VA_ARGS__); +char logbuf[TUSB_ALIF_DEBUG_DEPTH][48]; +int bi = 0; +#else +#define LOG(...) memset(logbuf, ' ', 48);\ + snprintf(logbuf, 48, __VA_ARGS__) +char logbuf[48]; +#endif +#else +#define LOG(...) +#endif + +/// Structs and Buffers -------------------------------------------------------- + +static uint32_t _evnt_buf[1024] CFG_TUSB_MEM_SECTION __attribute__((aligned(4096))); // [TODO] runtime alloc +static volatile uint32_t* _evnt_tail; + +static uint8_t _ctrl_buf[64] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc +static uint32_t _xfer_trb[8][4] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc +static uint16_t _xfer_bytes[8]; +static bool _ctrl_long_data = false; +static bool _xfer_cfgd = false; +// static bool _addr_req = false; +static uint32_t _sts_stage = 0; + +/// Private Functions ---------------------------------------------------------- + +static uint8_t _dcd_cmd_wait(uint8_t ep, uint8_t typ, uint16_t param); +static uint8_t _dcd_start_xfer(uint8_t ep, void* buf, uint32_t size, uint8_t type); + +static void _dcd_handle_depevt(uint8_t ep, uint8_t evt, uint8_t sts, uint16_t par); +static void _dcd_handle_devt(uint8_t evt, uint16_t info); + +/// API Extension -------------------------------------------------------------- + +void dcd_uninit(void); + +/// Device Setup --------------------------------------------------------------- + +// Initializes the USB peripheral for device mode and enables it. +// This function should enable internal D+/D- pull-up for enumeration. +void dcd_init(uint8_t rhport) +{ + // enable 20mhz clock + enable_cgu_clk20m(); + // enable usb peripheral clock + enable_usb_periph_clk(); + // power up usb phy + enable_usb_phy_power(); + // disable usb phy isolation + disable_usb_phy_isolation(); + // clear usb phy power-on-reset signal + CLKCTL_PER_MST->USB_CTRL2 &= ~(1 << 8); + + // force stop/disconnect + udev->dctl_b.run_stop = 0; + + // dctl set csftrst to 1 and wait for 0 + udev->dctl_b.csftrst = 1; + while(0 != udev->dctl_b.csftrst); + + sys_busy_loop_us(50000); + ugbl->gctl_b.coresoftreset = 1; + ugbl->gusb2phycfg0_b.physoftrst = 1; + sys_busy_loop_us(50000); + ugbl->gusb2phycfg0_b.physoftrst = 0; + sys_busy_loop_us(50000); + ugbl->gctl_b.coresoftreset = 0; + sys_busy_loop_us(50000); + + ugbl->gsbuscfg0 = 0x00000009; + // ugbl->gusb2phycfg0 = 0x4000154F; // [TODO] document as bits + uint32_t reg = ugbl->gusb2phycfg0; + reg &= ~((1 << 3) | (1 << 4) | (0xF << 10)); // clear phyif, ulpi_utmi_sel and usbtrdtim + reg |= ((1 << 3) | (5 << 10)); + ugbl->gusb2phycfg0 = reg; + + // set device speed (USBHS only) + udev->dcfg_b.devspd = 0x0; // HS, this will need #if condition [TODO] + + // allocate ring buffer for events + memset(_evnt_buf, 0, sizeof(_evnt_buf)); + RTSS_CleanDCache_by_Addr(_evnt_buf, sizeof(_evnt_buf)); + _evnt_tail = _evnt_buf; + ugbl->gevntadrlo0 = (uint32_t) LocalToGlobal(_evnt_buf); + ugbl->gevntsiz0_b.eventsiz = (uint32_t) sizeof(_evnt_buf); + ugbl->gevntcount0_b.evntcount = 0; + + // write devten to enable usb reset, conn done, link state change... + udev->devten_b.dissconnevten = 1; + udev->devten_b.usbrstevten = 1; + udev->devten_b.connectdoneevten = 1; + udev->devten_b.ulstcngen = 1; + + // begin endpoint setup + _dcd_cmd_wait(0, CMDTYP_DEPSTARTCFG, 0); + + // configure CONTROL IN and OUT eps + udev->depcmd[0].par1 = (0 << 25) | (1 << 10) | (1 << 8); + udev->depcmd[0].par0 = (0 << 22) | (0 << 17) | (512 << 3) | (0 << 1); + _dcd_cmd_wait(0, CMDTYP_DEPCFG, 0); + + udev->depcmd[1].par1 = (1 << 25) | (1 << 10) | (1 << 8); + udev->depcmd[1].par0 = (0 << 22) | (0 << 17) | (512 << 3) | (0 << 1); + _dcd_cmd_wait(1, CMDTYP_DEPCFG, 0); + + // set initial xfer configuration for CONTROL eps + udev->depcmd[0].par0 = 1; + _dcd_cmd_wait(0, CMDTYP_DEPXFERCFG, 0); + + udev->depcmd[1].par0 = 1; + _dcd_cmd_wait(1, CMDTYP_DEPXFERCFG, 0); + + // enable pull-ups + dcd_connect(rhport); + + // prepare trb for the first setup packet + memset(_ctrl_buf, 0, sizeof(_ctrl_buf)); + _xfer_trb[0][0] = (uint32_t) LocalToGlobal(_ctrl_buf); + _xfer_trb[0][1] = 0; + _xfer_trb[0][2] = 8; + _xfer_trb[0][3] = (1 << 11) | (1 << 10) | (TRBCTL_CTL_SETUP << 4) | (1 << 1) | (1 << 0); + RTSS_CleanDCache_by_Addr(_xfer_trb[0], sizeof(_xfer_trb[0])); + + // send trb to the usb dma + udev->depcmd[0].par1 = (uint32_t) LocalToGlobal(_xfer_trb[0]); + udev->depcmd[0].par0 = 0; + _dcd_cmd_wait(0, CMDTYP_DEPSTRTXFER, 0); + + // enable ep event interrupts for CONTROL OUT and IN + udev->dalepena_b.usbactep = (1 << 1) | (1 << 0); + + // enable interrupts in the NVIC +#if !defined(TUSB_ALIF_NO_IRQ_CFG) + NVIC_ClearPendingIRQ(USB_IRQ_IRQn); + NVIC_SetPriority(USB_IRQ_IRQn, 5); +#endif + dcd_int_enable(rhport); +} + + +// Processes all the hardware generated events e.g bus reset, new data packet +// from host... It will be called by application in the MCU USB interrupt handler. +void dcd_int_handler(uint8_t rhport) +{ + LOG("%010u IRQ enter, evntcount %u", DWT->CYCCNT, ugbl->gevntcount0_b.evntcount); + + // process failures first + if (ugbl->gsts_b.device_ip) { + if (ugbl->gsts_b.csrtimeout || ugbl->gsts_b.buserraddrvld) { + // buserraddrvld is usually set when USB tries to write to protected + // memory region. Check linker script to ensure USB event buffer and + // TRBs reside in bulk memory or other NS-allowed region + __BKPT(0); + } + } + + // cycle through event queue + // evaluate on every iteration to prevent unnecessary isr exit/reentry + while (0 < ugbl->gevntcount0_b.evntcount) { + RTSS_InvalidateDCache_by_Addr(_evnt_buf, sizeof(_evnt_buf)); + volatile evt_t e = {.val = *_evnt_tail++}; + + LOG("%010u IRQ loop, evntcount %u evnt %08x", DWT->CYCCNT, + ugbl->gevntcount0_b.evntcount, e.val); + + // wrap around + if (_evnt_tail >= (_evnt_buf + 1024)) _evnt_tail = _evnt_buf; + + // dispatch the right handler for the event type + if (0 == e.depevt.sig) { // DEPEVT + _dcd_handle_depevt(e.depevt.ep, e.depevt.evt, e.depevt.sts, e.depevt.par); + } else if (1 == e.devt.sig) { // DEVT + _dcd_handle_devt(e.devt.evt, e.devt.info); + } else { + // bad event?? + LOG("Unknown event %u", e.val); + __BKPT(0); + } + + // consume one event + ugbl->gevntcount0 = 4; + } + + LOG("%010u IRQ exit, evntcount %u", DWT->CYCCNT, ugbl->gevntcount0_b.evntcount); +} + + +// Enables the USB device interrupt. +// May be used to prevent concurrency issues when mutating data structures +// shared between main code and the interrupt handler. +void dcd_int_enable (uint8_t rhport) +{ + NVIC_EnableIRQ(USB_IRQ_IRQn); + + (void) rhport; +} + +// Disables the USB device interrupt. +// May be used to prevent concurrency issues when mutating data structures +// shared between main code and the interrupt handler. +void dcd_int_disable(uint8_t rhport) +{ + NVIC_DisableIRQ(USB_IRQ_IRQn); + + (void) rhport; +} + +// Receive Set Address request, mcu port must also include status IN response. +// If your peripheral automatically changes address during enumeration you may +// leave this empty and also no queue an event for the corresponding SETUP packet. +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) +{ + LOG("%010u >%s", DWT->CYCCNT, __func__); + + udev->dcfg_b.devaddr = dev_addr; + dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); +} + +// Called to remote wake up host when suspended (e.g hid keyboard) +void dcd_remote_wakeup(uint8_t rhport) +{ + LOG("%010u >%s", DWT->CYCCNT, __func__); +} + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + udev->dctl_b.run_stop = 1; + + (void) rhport; +} + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + // [TODO] clear all xfers and eps first + + udev->dctl_b.run_stop = 0; + + (void) rhport; +} + +// Enable/Disable Start-of-frame interrupt. Default is disabled +void dcd_sof_enable(uint8_t rhport, bool en) +{ + LOG("%010u >%s", DWT->CYCCNT, __func__); +} + + +/// Endpoint Management -------------------------------------------------------- + +// Invoked when a control transfer's status stage is complete. +// May help DCD to prepare for next control transfer, this API is optional. +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) +{ + LOG("%010u >%s", DWT->CYCCNT, __func__); + + _ctrl_long_data = false; + _dcd_start_xfer(TUSB_DIR_OUT, _ctrl_buf, 8, TRBCTL_CTL_SETUP); +} + +// Opening an endpoint is done for all non-control endpoints once the host picks +// a configuration that the device should use. +// At this point, the endpoint should be enabled in the peripheral and +// configured to match the endpoint descriptor. +// Pay special attention to the direction of the endpoint you can get from the +// helper methods above. It will likely change what registers you are setting. +// Also make sure to enable endpoint specific interrupts. +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) +{ + LOG("%010u >%s %u %s %u %u", DWT->CYCCNT, __func__, desc_ep->bEndpointAddress, + desc_ep->bmAttributes.xfer == TUSB_XFER_BULK ? "bulk" : "int", + desc_ep->wMaxPacketSize, desc_ep->bInterval); + + if (TUSB_XFER_ISOCHRONOUS == desc_ep->bmAttributes.xfer) + return false; + + uint8_t ep = (tu_edpt_number(desc_ep->bEndpointAddress) << 1) | + tu_edpt_dir(desc_ep->bEndpointAddress); + + // [TODO] verify that the num doesn't exceed hw max + + if (false == _xfer_cfgd) { + _dcd_cmd_wait(0, CMDTYP_DEPSTARTCFG, 2); + _xfer_cfgd = true; + } + + uint8_t fifo_num = TUSB_DIR_IN == tu_edpt_dir(desc_ep->bEndpointAddress) ? + tu_edpt_number(desc_ep->bEndpointAddress) : 0; + uint8_t interval = 0 < desc_ep->bInterval ? (desc_ep->bInterval - 1) : 0; + + udev->depcmd[ep].par1 = (ep << 25) | (interval << 16) | + (1 << 10) | (1 << 8); + udev->depcmd[ep].par0 = (0 << 30) | (0 << 22) | (fifo_num << 17) | + ((desc_ep->wMaxPacketSize & 0x7FF) << 3) | + (desc_ep->bmAttributes.xfer << 1); + + _dcd_cmd_wait(ep, CMDTYP_DEPCFG, 0); + udev->depcmd[ep].par0 = 1; + _dcd_cmd_wait(ep, CMDTYP_DEPXFERCFG, 0); + + udev->dalepena_b.usbactep |= (1 << ep); + + return true; +} + +// Close all non-control endpoints, cancel all pending transfers if any. +// Invoked when switching from a non-zero Configuration by SET_CONFIGURE therefore +// required for multiple configuration support. +void dcd_edpt_close_all(uint8_t rhport) +{ + LOG("%010u >%s", DWT->CYCCNT, __func__); +} + +// Close an endpoint. his function is used for implementing alternate settings. +// After calling this, the device should not respond to any packets directed +// towards this endpoint. When called, this function must abort any transfers in +// progress through this endpoint, before returning. +// Implementation is optional. Must be called from the USB task. +// Interrupts could be disabled or enabled during the call. +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) TU_ATTR_WEAK; + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to +// notify the stack +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) +{ + // DEPSTRTXFER command + LOG("%010u >%s %u %x %u", DWT->CYCCNT, __func__, ep_addr, (uint32_t) buffer, total_bytes); + + uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr); + + switch (ep) { + case 0: { // CONTROL OUT + if (0 < total_bytes) { + // DATA OUT request + _xfer_bytes[0] = total_bytes; + _dcd_start_xfer(0, buffer, total_bytes, TRBCTL_CTL_STAT3); + } else { + // TinyUSB explicitly requests STATUS OUT fetch after DATA IN + if (2 == ++_sts_stage) { + _sts_stage = 0; + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT), + 0, XFER_RESULT_SUCCESS, true); + } + } + } break; + case 1: { // CONTROL IN + _xfer_bytes[1] = total_bytes; + + if (0 < total_bytes) { + RTSS_CleanDCache_by_Addr(buffer, total_bytes); + uint8_t type = _ctrl_long_data ? TRBCTL_NORMAL : TRBCTL_CTL_DATA; + if (64 == total_bytes) { + _ctrl_long_data = true; + } + _dcd_start_xfer(1, buffer, total_bytes, type); + } else { + // status events are handled directly from the ISR when USB + // controller triggers XferNotReady event for status stage + + if (2 == ++_sts_stage) { + _sts_stage = 0; + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN), + 0, XFER_RESULT_SUCCESS, true); + } + // dcd_event_xfer_complete(rhport, tu_edpt_addr(0, TUSB_DIR_IN), + // 0, XFER_RESULT_SUCCESS, false); + } + } break; + default: { // DATA EPs (BULK & INTERRUPT only) + _xfer_bytes[ep] = total_bytes; + if (TUSB_DIR_IN == tu_edpt_dir(ep_addr)) { + RTSS_CleanDCache_by_Addr(buffer, total_bytes); + } else { + total_bytes = 512; // temporary hack, controller requires max + // size requests on OUT endpoints [FIXME] + } + uint8_t ret = _dcd_start_xfer(ep, buffer, total_bytes, + total_bytes ? TRBCTL_NORMAL : TRBCTL_NORMAL_ZLP); + LOG("start xfer sts %u", ret); + } + } + + return true; +} + +// Submit a transfer using fifo, When complete dcd_event_xfer_complete() is invoked to notify the stack +// This API is optional, may be useful for register-based for transferring data. +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) TU_ATTR_WEAK; + +// Stall endpoint, any queuing transfer should be removed from endpoint +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) +{ + // DEPSSTALL command + LOG(">%s", __func__); + + uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr); + + _dcd_cmd_wait(ep, CMDTYP_DEPSSTALL, 0); + + if (0 == tu_edpt_number(ep_addr)) { + _ctrl_long_data = false; + _dcd_start_xfer(TUSB_DIR_OUT, _ctrl_buf, 8, TRBCTL_CTL_SETUP); + } +} + +// clear stall, data toggle is also reset to DATA0 +// This API never calls with control endpoints, since it is auto cleared when +// receiving setup packet +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) +{ + // DEPCSTALL command + LOG(">%s", __func__); + + uint8_t ep = (tu_edpt_number(ep_addr) << 1) | tu_edpt_dir(ep_addr); + + _dcd_cmd_wait(ep, CMDTYP_DEPCSTALL, 0); +} + +void dcd_uninit(void) +{ + CLKCTL_PER_MST->USB_CTRL2 |= 1 << 8; // set usb phy power-on-reset signal + enable_usb_phy_isolation(); // enable usb phy isolation + disable_usb_phy_power(); // power down usb phy + disable_usb_periph_clk(); // disable usb peripheral clock +} + +void USB_IRQHandler(void) +{ + dcd_int_handler(TUD_OPT_RHPORT); +} + + +static uint8_t _dcd_cmd_wait(uint8_t ep, uint8_t typ, uint16_t param) +{ + // capture phy state and disable lpm and suspend + uint32_t phycfg = ugbl->gusb2phycfg0; + ugbl->gusb2phycfg0_b.enblslpm = 0; + ugbl->gusb2phycfg0_b.suspendusb20 = 0; + + // set up command in depcmd register + udev->depcmd[ep].depcmd_b.cmdtyp = typ; + udev->depcmd[ep].depcmd_b.cmdioc = 0; + udev->depcmd[ep].depcmd_b.commandparam = param; + + // dispatch command and wait for completion + udev->depcmd[ep].depcmd_b.cmdact = 1; + while(0 != udev->depcmd[ep].depcmd_b.cmdact); + + // restore phy state + ugbl->gusb2phycfg0 = phycfg; + + return udev->depcmd[ep].depcmd_b.cmdstatus; +} + + +static void _dcd_handle_depevt(uint8_t ep, uint8_t evt, uint8_t sts, uint16_t par) +{ + LOG("%010u DEPEVT ep%u evt%u sts%u par%u", DWT->CYCCNT, ep, evt, sts, par); + + switch (evt) { + case DEPEVT_XFERCOMPLETE: { + LOG("Transfer complete"); + RTSS_InvalidateDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[0])); + if (0 == ep) { + uint8_t trbctl = (_xfer_trb[0][3] >> 4) & 0x3F; + LOG("ep0 xfer trb3 = %08x", _xfer_trb[0][3]); + if (TRBCTL_CTL_SETUP == trbctl) { + RTSS_InvalidateDCache_by_Addr(_ctrl_buf, sizeof(_ctrl_buf)); + LOG("%02x %02x %02x %02x %02x %02x %02x %02x", + _ctrl_buf[0], _ctrl_buf[1], _ctrl_buf[2], _ctrl_buf[3], + _ctrl_buf[4], _ctrl_buf[5], _ctrl_buf[6], _ctrl_buf[7]); + dcd_event_setup_received(TUD_OPT_RHPORT, _ctrl_buf, true); + } else if (TRBCTL_CTL_STAT3 == trbctl) { + if (0 < _xfer_bytes[0]) { + RTSS_InvalidateDCache_by_Addr((void*) _xfer_trb[0][0], _xfer_bytes[0]); + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT), + _xfer_bytes[0] - (_xfer_trb[0][2] & 0xFFFFFF), + XFER_RESULT_SUCCESS, true); + } else { + if (2 == ++_sts_stage) { + _sts_stage = 0; + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_OUT), + 0, XFER_RESULT_SUCCESS, true); + + // *(volatile uint32_t*) 0x4900C000 ^= 8; // [TEMP] + } + } + } else { + // invalid TRBCTL value + __BKPT(0); + } + } else if (1 == ep) { + uint8_t trbctl = (_xfer_trb[1][3] >> 4) & 0x3F; + LOG("ep1 xfer trb3 = %08x trb2 = %08x", _xfer_trb[1][3], _xfer_trb[1][2]); + if (TRBCTL_CTL_STAT2 != trbctl) { // STATUS IN notification is done at xfer request + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN), + _xfer_bytes[1] - (_xfer_trb[1][2] & 0xFFFFFF), + XFER_RESULT_SUCCESS, true); + } else { + if (2 == ++_sts_stage) { + _sts_stage = 0; + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(0, TUSB_DIR_IN), + 0, XFER_RESULT_SUCCESS, true); + + // *(volatile uint32_t*) 0x4900C000 ^= 8; // [TEMP] + } + } + } else { + // [TODO] check if ep is open + LOG("ep%u xfer trb3 = %08x trb2 = %08x", ep, _xfer_trb[ep][3], _xfer_trb[ep][2]); + if (TUSB_DIR_OUT == tu_edpt_dir(tu_edpt_addr(ep >> 1, ep & 1))) { + RTSS_InvalidateDCache_by_Addr((void*) _xfer_trb[ep][0], + 512 - _xfer_trb[ep][2]); + // _xfer_bytes[ep] - _xfer_trb[ep][2]); + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(ep >> 1, ep & 1), + 512 - _xfer_trb[ep][2], + XFER_RESULT_SUCCESS, true); + } else + dcd_event_xfer_complete(TUD_OPT_RHPORT, tu_edpt_addr(ep >> 1, ep & 1), + _xfer_bytes[ep] - _xfer_trb[ep][2], + XFER_RESULT_SUCCESS, true); + } + } break; + case DEPEVT_XFERINPROGRESS: { + LOG("Transfer in progress"); + } break; + case DEPEVT_XFERNOTREADY: { + LOG("Transfer not ready: %s", sts & 8 ? "no TRB" : "no XFER"); + + // XferNotReady NotActive for status stage + if ((1 == ep) && (0b0010 == (sts & 0b1011))) { + _dcd_start_xfer(1, NULL, 0, TRBCTL_CTL_STAT2); + break; + } + + if ((0 == ep) && (0b0010 == (sts & 0b1011))) { + _xfer_bytes[0] = 0; + _dcd_start_xfer(0, _ctrl_buf, 64, TRBCTL_CTL_STAT3); + break; + } + + if ((1 > ep) && (sts & (1 << 3))) { + if (_xfer_trb[ep][3] & (1 << 0)) { // transfer was configured + // dependxfer can only block when actbitlater is set + ugbl->guctl2_b.rst_actbitlater = 1; + _dcd_cmd_wait(ep, CMDTYP_DEPENDXFER, 0); + ugbl->guctl2_b.rst_actbitlater = 0; + + // reset the trb byte count and clean the cache + RTSS_InvalidateDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[0])); + _xfer_trb[ep][2] = _xfer_bytes[ep]; + RTSS_CleanDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[ep])); + + // prepare ep command + udev->depcmd[ep].par1 = (uint32_t) LocalToGlobal(_xfer_trb[ep]); + udev->depcmd[ep].par0 = 0; + + // issue the block command and pass the status + _dcd_cmd_wait(ep, CMDTYP_DEPSTRTXFER, 0); + + *(volatile uint32_t*) 0x49007000 ^= 16; // [TEMP] + } + } + } break; + case DEPEVT_EPCMDCMPLT: { + // redundant, currently no commands are issued with IOC bit set + } break; + } +} + + +static void _dcd_handle_devt(uint8_t evt, uint16_t info) +{ + LOG("%010u DEVT evt%u info%u", DWT->CYCCNT, evt, info); + switch (evt) { + case DEVT_USBRST: { + _xfer_cfgd = false; + + // [TODO] issue depcstall for any ep in stall mode + udev->dcfg_b.devaddr = 0; + LOG("USB reset"); + dcd_event_bus_reset(TUD_OPT_RHPORT, TUSB_SPEED_HIGH, true); // [TODO] actual speed + } break; + case DEVT_CONNECTDONE: { + // read conn speed from dsts + // program ramclksel in gctl if needed + LOG("Connect done"); + + udev->depcmd[0].par1 = (0 << 25) | (1 << 10) | (1 << 8); + udev->depcmd[0].par0 = (2 << 30) | (0 << 22) | (0 << 17) | (64 << 3) | (0 << 1); + _dcd_cmd_wait(0, CMDTYP_DEPCFG, 0); + + udev->depcmd[1].par1 = (1 << 25) | (1 << 10) | (1 << 8); + udev->depcmd[1].par0 = (2 << 30) | (0 << 22) | (0 << 17) | (64 << 3) | (0 << 1); + _dcd_cmd_wait(1, CMDTYP_DEPCFG, 0); + } break; + case DEVT_ULSTCHNG: { + LOG("Link status change"); + switch (info) { + case 0x3: { // suspend (L2) + dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_SUSPEND, true); + } break; + case 0x4: { // disconnected + dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_UNPLUGGED, true); + } break; + case 0xF: { // resume + dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_RESUME, true); + } break; + default: {} + } + // 0x0: ON state + // 0x2: L1 state (sleep) + // 0x3: L2 state (suspend) + // 0x4: disconnected state + // 0x5: early suspend + // 0xE: reset + // 0xF: resume + } break; + case DEVT_ERRTICERR: { + __BKPT(0); + } break; + default: { + LOG("Unknown DEVT event"); + } + } +} + + +static uint8_t _dcd_start_xfer(uint8_t ep, void* buf, uint32_t size, uint8_t type) +{ + dcd_int_disable(TUD_OPT_RHPORT); // prevent race conditions + + // program the trb and clean the cache + _xfer_trb[ep][0] = buf ? (uint32_t) LocalToGlobal(buf) : 0; + _xfer_trb[ep][1] = 0; + _xfer_trb[ep][2] = size; + _xfer_trb[ep][3] = (1 << 11) | (1 << 10) | (type << 4) | (1 << 1) | (1 << 0); + RTSS_CleanDCache_by_Addr(_xfer_trb[ep], sizeof(_xfer_trb[ep])); + + // prepare ep command + udev->depcmd[ep].par1 = (uint32_t) LocalToGlobal(_xfer_trb[ep]); + udev->depcmd[ep].par0 = 0; + + dcd_int_enable(TUD_OPT_RHPORT); + + // issue the block command and pass the status + return _dcd_cmd_wait(ep, CMDTYP_DEPSTRTXFER, 0); +} + +#endif // CFG_TUD_ENABLED From ebecd6d101f0ca73baa8a7452ae946aac8b42817 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 22 Oct 2024 08:48:25 +0200 Subject: [PATCH 0459/2098] alif/tinyusb_port: Disable USB IRQ on deinit. Signed-off-by: iabdalkader --- ports/alif/tinyusb_port/tusb_alif_dcd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/alif/tinyusb_port/tusb_alif_dcd.c b/ports/alif/tinyusb_port/tusb_alif_dcd.c index 510f0e9c13a..5ede5914786 100644 --- a/ports/alif/tinyusb_port/tusb_alif_dcd.c +++ b/ports/alif/tinyusb_port/tusb_alif_dcd.c @@ -460,6 +460,8 @@ void dcd_uninit(void) enable_usb_phy_isolation(); // enable usb phy isolation disable_usb_phy_power(); // power down usb phy disable_usb_periph_clk(); // disable usb peripheral clock + dcd_int_disable(TUD_OPT_RHPORT); + NVIC_ClearPendingIRQ(USB_IRQ_IRQn); } void USB_IRQHandler(void) From b8a9cdf0673916457ca2abf8a69ad8e22afaea11 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 28 Mar 2025 00:29:06 +1100 Subject: [PATCH 0460/2098] alif/tinyusb_port: Implement SOF event. Signed-off-by: Damien George --- ports/alif/tinyusb_port/tusb_alif_dcd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/alif/tinyusb_port/tusb_alif_dcd.c b/ports/alif/tinyusb_port/tusb_alif_dcd.c index 5ede5914786..9a990cedbe3 100644 --- a/ports/alif/tinyusb_port/tusb_alif_dcd.c +++ b/ports/alif/tinyusb_port/tusb_alif_dcd.c @@ -281,6 +281,8 @@ void dcd_disconnect(uint8_t rhport) void dcd_sof_enable(uint8_t rhport, bool en) { LOG("%010u >%s", DWT->CYCCNT, __func__); + + udev->devten_b.softevten = en; } @@ -656,6 +658,9 @@ static void _dcd_handle_devt(uint8_t evt, uint16_t info) // 0xE: reset // 0xF: resume } break; + case DEVT_SOF: { + dcd_event_bus_signal(TUD_OPT_RHPORT, DCD_EVENT_SOF, true); + } break; case DEVT_ERRTICERR: { __BKPT(0); } break; From ccc5935234bbef026db776ebeb83a94f353e7d6b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Dec 2023 00:11:46 +1100 Subject: [PATCH 0461/2098] alif: Add initial port to Alif Ensemble MCUs. This commit adds the beginning of a new alif port with support for Alif Ensemble MCUs. See https://alifsemi.com/ Supported features of this port added by this commit: - UART REPL. - TinyUSB support, for REPL and MSC. - Octal SPI flash support, for filesystem. - machine.Pin support. General notes about the port: - It uses make, similar to other bare-metal ports here. - The toolchain is the standard arm-none-eabi- toolchain. - Flashing a board can be done using either the built-in serial bootloader, or JLink (both supported here). - There are two required submodules (one for drivers/SDK, one for security tools), both of which are open source and on GitHub. - No special hardware or software is needed for development, just a board connected over USB. OpenMV have generously sponsored the development of this port. Signed-off-by: Damien George Signed-off-by: iabdalkader --- ports/alif/Makefile | 261 ++++++++++++++++++++++ ports/alif/README.md | 21 ++ ports/alif/alif_flash.c | 160 ++++++++++++++ ports/alif/boards/manifest.py | 5 + ports/alif/fatfs_port.c | 38 ++++ ports/alif/irq.h | 84 ++++++++ ports/alif/machine_pin.c | 298 ++++++++++++++++++++++++++ ports/alif/main.c | 145 +++++++++++++ ports/alif/mcu/M55_HP_cfg.json | 14 ++ ports/alif/mcu/ensemble.ld.S | 165 ++++++++++++++ ports/alif/mcu/make-pins.py | 64 ++++++ ports/alif/mcu/pins_prefix.c | 2 + ports/alif/modalif.c | 46 ++++ ports/alif/modalif.h | 33 +++ ports/alif/modmachine.c | 79 +++++++ ports/alif/modules/_boot.py | 23 ++ ports/alif/mpconfigport.h | 155 ++++++++++++++ ports/alif/mpconfigport.mk | 12 ++ ports/alif/mphalport.c | 152 +++++++++++++ ports/alif/mphalport.h | 152 +++++++++++++ ports/alif/mpuart.c | 113 ++++++++++ ports/alif/mpuart.h | 32 +++ ports/alif/msc_disk.c | 151 +++++++++++++ ports/alif/ospi_flash.c | 265 +++++++++++++++++++++++ ports/alif/ospi_flash.h | 52 +++++ ports/alif/ospi_xip_user.h | 5 + ports/alif/pendsv.c | 49 +++++ ports/alif/pendsv.h | 48 +++++ ports/alif/qstrdefsport.h | 2 + ports/alif/tinyusb_port/tusb_config.h | 73 +++++++ ports/alif/usbd.c | 41 ++++ 31 files changed, 2740 insertions(+) create mode 100644 ports/alif/Makefile create mode 100644 ports/alif/README.md create mode 100644 ports/alif/alif_flash.c create mode 100644 ports/alif/boards/manifest.py create mode 100644 ports/alif/fatfs_port.c create mode 100644 ports/alif/irq.h create mode 100644 ports/alif/machine_pin.c create mode 100644 ports/alif/main.c create mode 100644 ports/alif/mcu/M55_HP_cfg.json create mode 100644 ports/alif/mcu/ensemble.ld.S create mode 100755 ports/alif/mcu/make-pins.py create mode 100644 ports/alif/mcu/pins_prefix.c create mode 100644 ports/alif/modalif.c create mode 100644 ports/alif/modalif.h create mode 100644 ports/alif/modmachine.c create mode 100644 ports/alif/modules/_boot.py create mode 100644 ports/alif/mpconfigport.h create mode 100644 ports/alif/mpconfigport.mk create mode 100644 ports/alif/mphalport.c create mode 100644 ports/alif/mphalport.h create mode 100644 ports/alif/mpuart.c create mode 100644 ports/alif/mpuart.h create mode 100644 ports/alif/msc_disk.c create mode 100644 ports/alif/ospi_flash.c create mode 100644 ports/alif/ospi_flash.h create mode 100644 ports/alif/ospi_xip_user.h create mode 100644 ports/alif/pendsv.c create mode 100644 ports/alif/pendsv.h create mode 100644 ports/alif/qstrdefsport.h create mode 100644 ports/alif/tinyusb_port/tusb_config.h create mode 100644 ports/alif/usbd.c diff --git a/ports/alif/Makefile b/ports/alif/Makefile new file mode 100644 index 00000000000..2863ebe2c4d --- /dev/null +++ b/ports/alif/Makefile @@ -0,0 +1,261 @@ +################################################################################ +# Initial setup of Makefile environment + +BOARD ?= ALIF_ENSEMBLE +BOARD_DIR ?= boards/$(BOARD) +BUILD ?= build-$(BOARD) + +ifeq ($(wildcard $(BOARD_DIR)/.),) +$(error Invalid BOARD specified: $(BOARD_DIR)) +endif + +include ../../py/mkenv.mk +include mpconfigport.mk +include $(BOARD_DIR)/mpconfigboard.mk + +# qstr definitions (must come before including py.mk) +QSTR_DEFS += qstrdefsport.h + +# include py core make definitions +include $(TOP)/py/py.mk +include $(TOP)/extmod/extmod.mk + +################################################################################ +# Project specific settings and compiler/linker flags + +CROSS_COMPILE ?= arm-none-eabi- +GIT_SUBMODULES += lib/tinyusb lib/alif_ensemble-cmsis-dfp lib/alif-security-toolkit +PORT ?= /dev/ttyACM0 + +ALIF_TOOLS ?= ../../lib/alif-security-toolkit/toolkit +ALIF_DFP_REL_TOP ?= lib/alif_ensemble-cmsis-dfp +ALIF_DFP_REL_HERE ?= $(TOP)/lib/alif_ensemble-cmsis-dfp +CMSIS_DIR ?= $(TOP)/lib/cmsis/inc + +MCU_CORE ?= M55_HP +ALIF_CONFIG ?= mcu/$(MCU_CORE)_cfg.json +LD_FILE ?= mcu/ensemble.ld.S + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) +INC += -I$(BOARD_DIR) +INC += -I$(CMSIS_DIR) +INC += -I$(ALIF_DFP_REL_HERE)/drivers/include/ +INC += -I$(ALIF_DFP_REL_HERE)/ospi_xip/source/ospi +INC += -I$(ALIF_DFP_REL_HERE)/Device/common/config/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/common/include/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/config/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/include/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/$(MCU_SERIES)/$(MCU_VARIANT)/ +INC += -I$(TOP)/lib/tinyusb/src +INC += -Itinyusb_port + +GEN_PIN_MKPINS = mcu/make-pins.py +GEN_PIN_PREFIX = mcu/pins_prefix.c +GEN_PINS_BOARD_CSV = $(BOARD_DIR)/pins.csv +GEN_PINS_SRC = $(BUILD)/pins_board.c +GEN_PINS_HDR = $(HEADER_BUILD)/pins_board.h + +CFLAGS_FPU += -mfloat-abi=hard -mfpu=fpv5-d16 +CFLAGS_CORTEX_M55 += -mthumb -mcpu=cortex-m55 -mtune=cortex-m55 $(CFLAGS_FPU) + +CFLAGS += $(INC) -Wall -Werror -std=c99 $(CFLAGS_CORTEX_M55) -nostdlib +CFLAGS += -Wdouble-promotion -Wfloat-conversion +CFLAGS += -fdata-sections -ffunction-sections +CFLAGS += -D$(MCU_CORE) -DCORE_$(MCU_CORE) -DALIF_CMSIS_H="\"$(MCU_CORE).h\"" + +ifeq ($(MICROPY_FLOAT_IMPL),float) +CFLAGS += -fsingle-precision-constant +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT +else +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE +endif + +AFLAGS = -mthumb -march=armv8.1-m.main $(CFLAGS_FPU) + +LDFLAGS += -nostdlib +LDFLAGS += -T$(BUILD)/ensemble.ld -Map=$@.map --cref --gc-sections +LDFLAGS += --wrap=dcd_event_handler + +# Tune for Debugging or Optimization +ifeq ($(DEBUG), 1) +CFLAGS += -Og -ggdb3 +# Disable text compression in debug builds +MICROPY_ROM_TEXT_COMPRESSION = 0 +else +CFLAGS += -O2 -DNDEBUG +endif + +LIBS += "$(shell $(CC) $(CFLAGS) -print-libgcc-file-name)" + +JLINK_CMD = '\ +ExitOnError 1\n\ +Device $(JLINK_DEV)\n\ +SelectInterface SWD\n\ +Speed auto\n\ +Connect\n\ +Reset\n\ +ShowHWStatus\n\ +LoadFile "$(BUILD)/firmware_toc.bin",0x8057f1c0\n\ +LoadFile "$(BUILD)/firmware.bin",0x80000000\n\ +Reset\n\ +Exit' + +################################################################################ +# Source files and libraries + +SRC_O += \ + shared/runtime/gchelper_thumb2.o + +SRC_C = \ + alif_flash.c \ + fatfs_port.c \ + machine_pin.c \ + main.c \ + modalif.c \ + mphalport.c \ + mpuart.c \ + msc_disk.c \ + ospi_flash.c \ + pendsv.c \ + usbd.c \ + $(wildcard $(BOARD_DIR)/*.c) + +ifeq ($(MICROPY_FLOAT_IMPL),float) +LIBM_SRC_C += $(SRC_LIB_LIBM_C) +LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) +$(BUILD)/lib/libm/%.o: CFLAGS += -Wno-maybe-uninitialized +else +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_C) +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_SQRT_HW_C) +$(BUILD)/lib/libm_dbl/%.o: CFLAGS += -Wno-maybe-uninitialized +endif + +SHARED_SRC_C += $(addprefix shared/,\ + libc/string0.c \ + netutils/dhcpserver.c \ + netutils/netutils.c \ + netutils/trace.c \ + readline/readline.c \ + runtime/gchelper_native.c \ + runtime/interrupt_char.c \ + runtime/mpirq.c \ + runtime/pyexec.c \ + runtime/softtimer.c \ + runtime/stdout_helpers.c \ + runtime/sys_stdio_mphal.c \ + timeutils/timeutils.c \ + tinyusb/mp_usbd.c \ + tinyusb/mp_usbd_cdc.c \ + tinyusb/mp_usbd_descriptor.c \ + ) + +DRIVERS_SRC_C += $(addprefix drivers/,\ + bus/softspi.c \ + bus/softqspi.c \ + memory/spiflash.c \ + dht/dht.c \ + ) + +TINYUSB_SRC_C += \ + lib/tinyusb/src/tusb.c \ + lib/tinyusb/src/class/cdc/cdc_device.c \ + lib/tinyusb/src/class/msc/msc_device.c \ + lib/tinyusb/src/common/tusb_fifo.c \ + lib/tinyusb/src/device/usbd.c \ + lib/tinyusb/src/device/usbd_control.c \ + tinyusb_port/tusb_alif_dcd.c \ + +ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ + Device/common/source/clk.c \ + Device/common/source/mpu_M55.c \ + Device/common/source/system_M55.c \ + Device/common/source/system_utils.c \ + Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ + drivers/source/pinconf.c \ + drivers/source/uart.c \ + ospi_xip/source/ospi/ospi_drv.c \ + ) + +$(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) + +OBJ += $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_O)) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(ALIF_SRC_C:.c=.o)) +OBJ += $(GEN_PINS_SRC:.c=.o) + +################################################################################ +# Main targets + +.DELETE_ON_ERROR: + +.PHONY: all +all: $(BUILD)/firmware_toc.bin + +$(BUILD)/ensemble.ld: $(LD_FILE) + $(ECHO) "Preprocess linker script $@" + $(Q)$(CPP) -P -E $(CFLAGS) $^ > $@ + +$(BUILD)/firmware.elf: $(OBJ) $(BUILD)/ensemble.ld + $(ECHO) "Link $@" + $(Q)$(LD) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) + $(Q)$(SIZE) $@ + +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf + $(Q)$(OBJCOPY) -Obinary $^ $(BUILD)/firmware.bin + +$(BUILD)/firmware_toc.bin: $(BUILD)/firmware.bin + $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ + --filename $(abspath $(BUILD)/$(ALIF_TOC_CONFIG)) \ + --output-dir $(BUILD) \ + --firmware-dir $(BUILD) \ + --output $@ + +.PHONY: deploy +deploy: $(BUILD)/firmware_toc.bin + $(ECHO) "Writing $< to the board" + $(Q)python $(ALIF_TOOLS)/app-write-mram.py \ + --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ + --port $(PORT) \ + --pad \ + --images file:$(BUILD)/application_package.ds + +.PHONY: deploy-jlink +deploy-jlink: $(BUILD)/firmware_toc.bin + $(Q)echo -e $(JLINK_CMD) | $(JLINK_EXE) + +.PHONY: maintenance +maintenance: + $(Q)python $(ALIF_TOOLS)/maintenance.py \ + --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ + --port $(PORT) + +.PHONY: update-system-package +update-system-package: + $(Q)python $(ALIF_TOOLS)/updateSystemPackage.py \ + --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ + --port $(PORT) + +################################################################################ +# Remaining make rules + +# Use a pattern rule here so that make will only call make-pins.py once to make +# both pins_board.c and pins_board.h +$(BUILD)/%_board.c $(HEADER_BUILD)/%_board.h: $(BOARD_DIR)/%.csv $(GEN_PIN_MKPINS) $(GEN_PIN_PREFIX) | $(HEADER_BUILD) + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(GEN_PIN_MKPINS) \ + --board-csv $(GEN_PINS_BOARD_CSV) \ + --prefix $(GEN_PIN_PREFIX) \ + --output-source $(GEN_PINS_SRC) \ + --output-header $(GEN_PINS_HDR) + +include $(TOP)/py/mkrules.mk diff --git a/ports/alif/README.md b/ports/alif/README.md new file mode 100644 index 00000000000..824a63da186 --- /dev/null +++ b/ports/alif/README.md @@ -0,0 +1,21 @@ +MicroPython port to Alif Ensemble MCUs +====================================== + +This is a port of MicroPython to the Alif Ensemble series of microcontrollers. + +Initial development of this Alif port was sponsored by OpenMV LLC. + +Features currently supported: +- UART REPL. +- TinyUSB with CDC and MSC device support. +- Octal SPI flash with XIP mode. +- machine.Pin support with named pins. +- machine.UART, machine.SPI, machine.I2C, machine.RTC peripherals. +- WiFi and Bluetooth using cyw43. +- Dual core support of the HE and HP cores using Open-AMP. +- Low power modes. + +The following more advanced features will follow later: +- Ethernet support. +- SDRAM support. +- Other machine modules. diff --git a/ports/alif/alif_flash.c b/ports/alif/alif_flash.c new file mode 100644 index 00000000000..e3b2125800e --- /dev/null +++ b/ports/alif/alif_flash.c @@ -0,0 +1,160 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "py/runtime.h" +#include "extmod/vfs.h" +#include "modalif.h" +#include "ospi_flash.h" + +typedef struct _alif_flash_obj_t { + mp_obj_base_t base; + uint32_t flash_base_addr; + uint32_t flash_size; +} alif_flash_obj_t; + +static alif_flash_obj_t alif_flash_obj = { + .base = { &alif_flash_type }, + .flash_base_addr = MICROPY_HW_FLASH_STORAGE_BASE_ADDR, + .flash_size = MICROPY_HW_FLASH_STORAGE_BYTES, +}; + +static mp_obj_t alif_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Parse arguments + enum { ARG_start, ARG_len }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_start].u_int == -1 && args[ARG_len].u_int == -1) { + // Default singleton object that accesses entire flash + return MP_OBJ_FROM_PTR(&alif_flash_obj); + } + + alif_flash_obj_t *self = mp_obj_malloc(alif_flash_obj_t, &alif_flash_type); + + mp_int_t start = args[ARG_start].u_int; + if (start == -1) { + start = 0; + } else if (!(0 <= start && start < MICROPY_HW_FLASH_STORAGE_BYTES && start % MICROPY_HW_FLASH_BLOCK_SIZE_BYTES == 0)) { + mp_raise_ValueError(NULL); + } + + mp_int_t len = args[ARG_len].u_int; + if (len == -1) { + len = MICROPY_HW_FLASH_STORAGE_BYTES - start; + } else if (!(0 < len && start + len <= MICROPY_HW_FLASH_STORAGE_BYTES && len % MICROPY_HW_FLASH_BLOCK_SIZE_BYTES == 0)) { + mp_raise_ValueError(NULL); + } + + self->flash_base_addr = MICROPY_HW_FLASH_STORAGE_BASE_ADDR + start; + self->flash_size = len; + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t alif_flash_readblocks(size_t n_args, const mp_obj_t *args) { + alif_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + if (n_args == 4) { + offset += mp_obj_get_int(args[3]); + } + int ret = ospi_flash_read(self->flash_base_addr + offset, bufinfo.len, bufinfo.buf); + mp_event_handle_nowait(); + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(alif_flash_readblocks_obj, 3, 4, alif_flash_readblocks); + +static mp_obj_t alif_flash_writeblocks(size_t n_args, const mp_obj_t *args) { + alif_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + if (n_args == 3) { + uint32_t addr = self->flash_base_addr + offset; + int32_t len = bufinfo.len; + while (len > 0) { + int ret = ospi_flash_erase_sector(addr); + mp_event_handle_nowait(); + if (ret < 0) { + return MP_OBJ_NEW_SMALL_INT(ret); + } + addr += MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + len -= MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + } + } else { + offset += mp_obj_get_int(args[3]); + } + int ret = ospi_flash_write(self->flash_base_addr + offset, bufinfo.len, bufinfo.buf); + mp_event_handle_nowait(); + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(alif_flash_writeblocks_obj, 3, 4, alif_flash_writeblocks); + +static mp_obj_t alif_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + alif_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + return MP_OBJ_NEW_SMALL_INT(self->flash_size / MICROPY_HW_FLASH_BLOCK_SIZE_BYTES); + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(MICROPY_HW_FLASH_BLOCK_SIZE_BYTES); + case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: { + uint32_t offset = mp_obj_get_int(arg_in) * MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + int ret = ospi_flash_erase_sector(self->flash_base_addr + offset); + return MP_OBJ_NEW_SMALL_INT(ret); + } + default: + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_3(alif_flash_ioctl_obj, alif_flash_ioctl); + +static const mp_rom_map_elem_t alif_flash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&alif_flash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&alif_flash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&alif_flash_ioctl_obj) }, +}; +static MP_DEFINE_CONST_DICT(alif_flash_locals_dict, alif_flash_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + alif_flash_type, + MP_QSTR_Flash, + MP_TYPE_FLAG_NONE, + make_new, alif_flash_make_new, + locals_dict, &alif_flash_locals_dict + ); diff --git a/ports/alif/boards/manifest.py b/ports/alif/boards/manifest.py new file mode 100644 index 00000000000..148f1d6a6eb --- /dev/null +++ b/ports/alif/boards/manifest.py @@ -0,0 +1,5 @@ +freeze("$(PORT_DIR)/modules") +include("$(MPY_DIR)/extmod/asyncio") +require("dht") +require("neopixel") +require("onewire") diff --git a/ports/alif/fatfs_port.c b/ports/alif/fatfs_port.c new file mode 100644 index 00000000000..5883c9f3b94 --- /dev/null +++ b/ports/alif/fatfs_port.c @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "lib/oofatfs/ff.h" + +DWORD get_fattime(void) { + // TODO + int year = 2024; + int month = 1; + int day = 1; + int hour = 0; + int min = 0; + int sec = 0; + return ((year - 1980) << 25) | (month << 21) | (day << 16) | (hour << 11) | (min << 5) | (sec / 2); +} diff --git a/ports/alif/irq.h b/ports/alif/irq.h new file mode 100644 index 00000000000..723f89f1d90 --- /dev/null +++ b/ports/alif/irq.h @@ -0,0 +1,84 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2023 Damien P. George + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_IRQ_H +#define MICROPY_INCLUDED_ALIF_IRQ_H + +#include +#include ALIF_CMSIS_H + +// IRQ priority definitions. +// +// The M55-HP CPU has __NVIC_PRIO_BITS==8 bits for setting the IRQ priority. +// It uses NVIC_SetPriorityGrouping(0) which is 7 bits for preempt priority +// and 1 bit for the sub-priority. +// +// Lower number implies higher interrupt priority. + +#define NVIC_PRIORITYGROUP_7 ((uint32_t)0x00000000U) +#define IRQ_PRI_SYSTEM_TICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 0, 0) +#define IRQ_PRI_QUIET_TIMING NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) +#define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) +#define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) +#define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) + +// these states correspond to values from query_irq, enable_irq and disable_irq +#define IRQ_STATE_DISABLED (0x00000001) +#define IRQ_STATE_ENABLED (0x00000000) + +static inline uint32_t query_irq(void) { + return __get_PRIMASK(); +} + +static inline void enable_irq(uint32_t state) { + __set_PRIMASK(state); +} + +static inline uint32_t disable_irq(void) { + uint32_t state = __get_PRIMASK(); + __disable_irq(); + return state; +} + +// irqs with a priority value greater or equal to "pri" will be disabled +static inline uint32_t raise_irq_pri(uint32_t pri) { + uint32_t basepri = __get_BASEPRI(); + // If non-zero, the processor does not process any exception with a + // priority value greater than or equal to BASEPRI. + // When writing to BASEPRI_MAX the write goes to BASEPRI only if either: + // - Rn is non-zero and the current BASEPRI value is 0 + // - Rn is non-zero and less than the current BASEPRI value + pri <<= (8 - __NVIC_PRIO_BITS); + __ASM volatile ("msr basepri_max, %0" : : "r" (pri) : "memory"); + return basepri; +} + +// "basepri" should be the value returned from raise_irq_pri +static inline void restore_irq_pri(uint32_t basepri) { + __set_BASEPRI(basepri); +} + +#endif // MICROPY_INCLUDED_ALIF_IRQ_H diff --git a/ports/alif/machine_pin.c b/ports/alif/machine_pin.c new file mode 100644 index 00000000000..02c5e482adb --- /dev/null +++ b/ports/alif/machine_pin.c @@ -0,0 +1,298 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "extmod/modmachine.h" +#include "extmod/virtpin.h" +#include "shared/runtime/mpirq.h" + +extern const mp_obj_dict_t machine_pin_cpu_pins_locals_dict; +extern const mp_obj_dict_t machine_pin_board_pins_locals_dict; + +static const machine_pin_obj_t *machine_pin_find_named(const mp_obj_dict_t *named_pins, mp_obj_t name) { + const mp_map_t *named_map = &named_pins->map; + mp_map_elem_t *named_elem = mp_map_lookup((mp_map_t *)named_map, name, MP_MAP_LOOKUP); + if (named_elem != NULL && named_elem->value != NULL) { + return MP_OBJ_TO_PTR(named_elem->value); + } + return NULL; +} + +const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { + // Is already a object of the proper type + if (mp_obj_is_type(pin, &machine_pin_type)) { + return MP_OBJ_TO_PTR(pin); + } + if (mp_obj_is_str(pin)) { + // Try to find the pin in the board pins first. + const machine_pin_obj_t *self = machine_pin_find_named(&machine_pin_board_pins_locals_dict, pin); + if (self != NULL) { + return self; + } + + // If not found, try to find the pin in the cpu pins. + self = machine_pin_find_named(&machine_pin_cpu_pins_locals_dict, pin); + if (self != NULL) { + return self; + } + + // Pin name not found. + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unknown named pin \"%s\""), mp_obj_str_get_str(pin)); + } + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); +} + +static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pin_obj_t *self = self_in; + + uint8_t alt_func, pad_ctrl; + pinconf_get(self->port, self->pin, &alt_func, &pad_ctrl); + + qstr mode_qst; + if (gpio_get_direction(self->gpio, self->pin) == GPIO_PIN_DIR_INPUT) { + mode_qst = MP_QSTR_IN; + } else { + if (pad_ctrl & PADCTRL_DRIVER_OPEN_DRAIN) { + mode_qst = MP_QSTR_OPEN_DRAIN; + } else { + mode_qst = MP_QSTR_OUT; + } + } + mp_printf(print, "Pin(%q, mode=%q", self->name, mode_qst); + uint8_t pad_ctrl_pull = pad_ctrl & (PADCTRL_DRIVER_DISABLED_PULL_DOWN | PADCTRL_DRIVER_DISABLED_PULL_UP); + if (pad_ctrl_pull == PADCTRL_DRIVER_DISABLED_PULL_UP) { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); + } else if (pad_ctrl_pull == PADCTRL_DRIVER_DISABLED_PULL_DOWN) { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_DOWN); + } + if (alt_func != PINMUX_ALTERNATE_FUNCTION_0) { + mp_printf(print, ", alt=%u", alt_func); + } + mp_printf(print, ")"); +} + +enum { + ARG_mode, ARG_pull, ARG_value, ARG_alt +}; +static const mp_arg_t allowed_args[] = { + {MP_QSTR_mode, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_pull, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_alt, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0}}, +}; + +static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get initial value of pin (only valid for OUT and OPEN_DRAIN modes) + int value = -1; + if (args[ARG_value].u_obj != mp_const_none) { + value = mp_obj_is_true(args[ARG_value].u_obj); + } + + // configure mode + if (args[ARG_mode].u_obj != mp_const_none) { + mp_int_t mode = mp_obj_get_int(args[ARG_mode].u_obj); + if (mode == MP_HAL_PIN_MODE_INPUT) { + mp_hal_pin_input(self); + } else if (mode == MP_HAL_PIN_MODE_OUTPUT) { + if (value != -1) { + // set initial output value before configuring mode + mp_hal_pin_write(self, value); + } + mp_hal_pin_output(self); + } else if (mode == MP_HAL_PIN_MODE_OPEN_DRAIN) { + if (value != -1) { + // set initial output value before configuring mode + mp_hal_pin_write(self, value); + } + mp_hal_pin_open_drain(self); + } + } + + // Configure pull (unconditionally because None means no-pull). + uint32_t pull = 0; + if (args[ARG_pull].u_obj != mp_const_none) { + pull = mp_obj_get_int(args[ARG_pull].u_obj); + } + uint8_t alt_func; + uint8_t pad_ctrl; + pinconf_get(self->port, self->pin, &alt_func, &pad_ctrl); + alt_func = PINMUX_ALTERNATE_FUNCTION_0; + pad_ctrl |= PADCTRL_READ_ENABLE; + pad_ctrl &= ~(PADCTRL_DRIVER_DISABLED_PULL_DOWN | PADCTRL_DRIVER_DISABLED_PULL_UP); + if (pull & MP_HAL_PIN_PULL_UP) { + pad_ctrl |= PADCTRL_DRIVER_DISABLED_PULL_UP; + } + if (pull & MP_HAL_PIN_PULL_DOWN) { + pad_ctrl |= PADCTRL_DRIVER_DISABLED_PULL_DOWN; + } + pinconf_set(self->port, self->pin, alt_func, pad_ctrl); + + return mp_const_none; +} + +// constructor(id, ...) +mp_obj_t mp_pin_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); + + const machine_pin_obj_t *self = machine_pin_find(args[0]); + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + } + return MP_OBJ_FROM_PTR(self); +} + +// fast method for getting/setting pin value +static mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + machine_pin_obj_t *self = self_in; + if (n_args == 0) { + // get pin + return MP_OBJ_NEW_SMALL_INT(mp_hal_pin_read(self)); + } else { + // set pin + bool value = mp_obj_is_true(args[0]); + mp_hal_pin_write(self, value); + return mp_const_none; + } +} + +// pin.init(mode, pull) +static mp_obj_t machine_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_obj_init); + +// pin.value([value]) +static mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) { + return machine_pin_call(args[0], n_args - 1, 0, args + 1); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_pin_value); + +// pin.low() +static mp_obj_t machine_pin_low(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_pin_low(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_low_obj, machine_pin_low); + +// pin.high() +static mp_obj_t machine_pin_high(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_pin_high(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_high_obj, machine_pin_high); + +// pin.toggle() +static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + gpio_toggle_value(self->gpio, self->pin); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); + +static MP_DEFINE_CONST_OBJ_TYPE( + pin_cpu_pins_obj_type, + MP_QSTR_cpu, + MP_TYPE_FLAG_NONE, + locals_dict, &machine_pin_cpu_pins_locals_dict + ); + +static MP_DEFINE_CONST_OBJ_TYPE( + pin_board_pins_obj_type, + MP_QSTR_board, + MP_TYPE_FLAG_NONE, + locals_dict, &machine_pin_board_pins_locals_dict + ); + +static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&machine_pin_low_obj) }, + { MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&machine_pin_high_obj) }, + { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_low_obj) }, + { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_high_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, + + // class attributes + { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&pin_board_pins_obj_type) }, + { MP_ROM_QSTR(MP_QSTR_cpu), MP_ROM_PTR(&pin_cpu_pins_obj_type) }, + + // class constants + { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(MP_HAL_PIN_MODE_INPUT) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(MP_HAL_PIN_MODE_OUTPUT) }, + { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(MP_HAL_PIN_MODE_OPEN_DRAIN) }, + { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(MP_HAL_PIN_PULL_UP) }, + { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(MP_HAL_PIN_PULL_DOWN) }, +}; +static MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); + +static mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)errcode; + machine_pin_obj_t *self = self_in; + + switch (request) { + case MP_PIN_READ: { + return mp_hal_pin_read(self); + } + case MP_PIN_WRITE: { + mp_hal_pin_write(self, arg); + return 0; + } + } + return -1; +} + +static const mp_pin_p_t pin_pin_p = { + .ioctl = pin_ioctl, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_pin_type, + MP_QSTR_Pin, + MP_TYPE_FLAG_NONE, + make_new, mp_pin_make_new, + print, machine_pin_print, + call, machine_pin_call, + protocol, &pin_pin_p, + locals_dict, &machine_pin_locals_dict + ); + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { + return machine_pin_find(obj); +} diff --git a/ports/alif/main.c b/ports/alif/main.c new file mode 100644 index 00000000000..0eb43a38268 --- /dev/null +++ b/ports/alif/main.c @@ -0,0 +1,145 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/stackctrl.h" +#include "shared/readline/readline.h" +#include "shared/runtime/gchelper.h" +#include "shared/runtime/pyexec.h" +#include "shared/tinyusb/mp_usbd.h" +#include "tusb.h" +#include "mpuart.h" +#include "ospi_flash.h" +#include "pendsv.h" + +extern uint8_t __StackTop, __StackLimit; +extern uint8_t __GcHeapStart, __GcHeapEnd; + +NORETURN void panic(const char *msg) { + mp_hal_stdout_tx_strn("\nFATAL ERROR:\n", 14); + mp_hal_stdout_tx_strn(msg, strlen(msg)); + for (;;) { + *(volatile uint32_t *)0x4900C000 ^= 9; + for (volatile uint delay = 0; delay < 10000000; delay++) { + } + } +} + +void _start(void) { + SysTick_Config(SystemCoreClock / 1000); + + MICROPY_BOARD_STARTUP(); + + pendsv_init(); + + MICROPY_BOARD_EARLY_INIT(); + + #if MICROPY_HW_ENABLE_UART_REPL + mp_uart_init(); + #endif + + if (ospi_flash_init() != 0) { + MICROPY_BOARD_FATAL_ERROR("ospi_init failed"); + } + + #if MICROPY_HW_ENABLE_USBDEV + NVIC_ClearPendingIRQ(USB_IRQ_IRQn); + NVIC_SetPriority(USB_IRQ_IRQn, IRQ_PRI_USB); + #endif + + // Initialise stack extents and GC heap. + mp_stack_set_top(&__StackTop); + mp_stack_set_limit(&__StackTop - &__StackLimit - 1024); + gc_init(&__GcHeapStart, &__GcHeapEnd); + + for (;;) { + // Initialise MicroPython runtime. + mp_init(); + + // Initialise sub-systems. + readline_init0(); + + // Execute _boot.py to set up the filesystem. + pyexec_frozen_module("_boot.py", false); + + // Execute user scripts. + int ret = pyexec_file_if_exists("boot.py"); + #if MICROPY_HW_ENABLE_USBDEV + mp_usbd_init(); + #endif + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL && ret != 0) { + ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + } + + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + + soft_reset_exit: + mp_printf(MP_PYTHON_PRINTER, "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(); +} + +void nlr_jump_fail(void *val) { + mp_printf(&mp_plat_print, "FATAL: uncaught exception %p\n", val); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(val)); + for (;;) { + __WFE(); + } +} + +#ifndef NDEBUG +void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) { + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + panic("Assertion failed"); +} +#endif diff --git a/ports/alif/mcu/M55_HP_cfg.json b/ports/alif/mcu/M55_HP_cfg.json new file mode 100644 index 00000000000..228ddbf6c94 --- /dev/null +++ b/ports/alif/mcu/M55_HP_cfg.json @@ -0,0 +1,14 @@ +{ + "USER_APP": { + "binary": "firmware.bin", + "mramAddress": "0x80000000", + "version": "1.0.0", + "cpu_id": "M55_HP", + "flags": ["boot"], + "signed": false + }, + "DEVICE": { + "binary": "app-device-config.json", + "version" : "0.5.00" + } +} diff --git a/ports/alif/mcu/ensemble.ld.S b/ports/alif/mcu/ensemble.ld.S new file mode 100644 index 00000000000..198641ba3dc --- /dev/null +++ b/ports/alif/mcu/ensemble.ld.S @@ -0,0 +1,165 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 OpenMV LLC. + * + * 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. + */ + +// Entry Point +ENTRY(Reset_Handler) + +MEMORY +{ + ROM (rx) : ORIGIN = 0x80000000, LENGTH = 0x0057F000 + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 + #ifdef CORE_M55_HP + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00100000 + #else + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000 + #endif + SRAM0 (rwx) : ORIGIN = 0x02000000, LENGTH = 0x00400000 + SRAM1 (rwx) : ORIGIN = 0x08000000, LENGTH = 0x00280000 +} + +__STACK_SIZE = 0x00004000; +__HEAP_SIZE = 0x00004000; +__MP_HEAP_SIZE = 0x00040000; + +SECTIONS +{ + .text : ALIGN(16) + { + KEEP(*(.vectors)) + . = ALIGN(4); + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(16); + } > ROM + + .copy.table : ALIGN(4) + { + __copy_table_start__ = .; + LONG ( LOADADDR(.data) ) + LONG ( ADDR(.data) ) + LONG ( SIZEOF(.data)/4 ) + __copy_table_end__ = .; + . = ALIGN(16); + } > ROM + + .zero.table : ALIGN(4) + { + __zero_table_start__ = .; + LONG (ADDR(.bss)) + LONG (SIZEOF(.bss)/4) + LONG (ADDR(.bss.sram0)) + LONG (SIZEOF(.bss.sram0)/4) + __zero_table_end__ = .; + . = ALIGN(16); + } > ROM + + .data : ALIGN(8) + { + *(.data) + . = ALIGN(8); + *(.data.*) + . = ALIGN(16); + } > DTCM AT > ROM + + /* Peripherals in expansion master 0 (USB, Ethernet, SD/MMC) + are by default configured as non-secure, so they don't + have access to DTCMs. This can be fixed in the ToC by allowing + access to DTCMs to all bus masters, for now these peripherals + should place buffers in regular SRAM */ + .bss.sram0 (NOLOAD) : ALIGN(4) + { + * (.bss.sram0*) + } > SRAM0 + + .bss : ALIGN(4) + { + __bss_start__ = .; + *(.bss) + . = ALIGN(4); + *(.bss.*) + . = ALIGN(4); + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > DTCM + + .heap (NOLOAD) : ALIGN(4) + { + __end__ = .; + PROVIDE(end = .); + . = . + __HEAP_SIZE; + . = ALIGN(4); + __HeapLimit = .; + + /* MicroPython GC heap */ + . = ALIGN(16); + __GcHeapStart = .; + . = . + __MP_HEAP_SIZE; + __GcHeapEnd = .; + } > DTCM + + .stack (NOLOAD) : ALIGN(4) + { + __StackLimit = .; + . = . + __STACK_SIZE; + . = ALIGN(4); + __StackTop = .; + } > DTCM + PROVIDE(__stack = __StackTop); + + .init_fini_arrays : ALIGN(16) + { + KEEP(*(.init)) + KEEP(*(.fini)) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + 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 = .); + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + + KEEP(*(.eh_frame*)) + . = ALIGN(16); + } > ROM + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack") +} diff --git a/ports/alif/mcu/make-pins.py b/ports/alif/mcu/make-pins.py new file mode 100755 index 00000000000..b49db4cfe49 --- /dev/null +++ b/ports/alif/mcu/make-pins.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +import os +import re +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../../tools")) +import boardgen + +NUM_PORTS = 16 +NUM_PINS_PER_PORT = 8 + + +class AlifPin(boardgen.Pin): + # Emit the struct which contains the pin instance. + def definition(self): + port, pin = self.name()[1:].split("_") + base = "LPGPIO_BASE" if port == "15" else "GPIO{}_BASE".format(port) + return ( + "{{ " + ".base = {{ .type = &machine_pin_type }}, " + ".gpio = (GPIO_Type *){base}, " + ".port = PORT_{port}, " + ".pin = PIN_{pin}, " + ".name = MP_QSTR_P{port}_{pin} " + "}}".format(port=port, pin=pin, base=base) + ) + + # Alif cpu names must be "Pn_m". + @staticmethod + def validate_cpu_pin_name(cpu_pin_name): + boardgen.Pin.validate_cpu_pin_name(cpu_pin_name) + + if not (m := re.match("P([0-9]){1,2}_([0-9])", cpu_pin_name)): + raise boardgen.PinGeneratorError( + "Invalid cpu pin name '{}', must be 'Pn_m'".format(cpu_pin_name) + ) + + port = int(m.group(1)) + pin = int(m.group(2)) + if not (0 <= port < NUM_PORTS and 0 <= pin < NUM_PINS_PER_PORT): + raise boardgen.PinGeneratorError("Unknown cpu pin '{}'".format(cpu_pin_name)) + + +class AlifPinGenerator(boardgen.PinGenerator): + def __init__(self): + # Use custom pin type above. + super().__init__(pin_type=AlifPin) + + # Pre-define the pins (i.e. don't require them to be listed in pins.csv). + for i in range(NUM_PORTS): + for j in range(NUM_PINS_PER_PORT): + self.add_cpu_pin("P{}_{}".format(i, j)) + + # Only use pre-defined cpu pins (do not let board.csv create them). + def find_pin_by_cpu_pin_name(self, cpu_pin_name, create=True): + return super().find_pin_by_cpu_pin_name(cpu_pin_name, create=False) + + def cpu_table_size(self): + return "{} * {}".format(NUM_PORTS, NUM_PINS_PER_PORT) + + +if __name__ == "__main__": + AlifPinGenerator().main() diff --git a/ports/alif/mcu/pins_prefix.c b/ports/alif/mcu/pins_prefix.c new file mode 100644 index 00000000000..99fef5939a0 --- /dev/null +++ b/ports/alif/mcu/pins_prefix.c @@ -0,0 +1,2 @@ +#include "py/mphal.h" +#include "extmod/modmachine.h" diff --git a/ports/alif/modalif.c b/ports/alif/modalif.c new file mode 100644 index 00000000000..69b7e6c5c8e --- /dev/null +++ b/ports/alif/modalif.c @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "py/runtime.h" +#include "modalif.h" + +static const mp_rom_map_elem_t alif_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_alif) }, + { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&alif_flash_type) }, + #if MICROPY_HW_USB_MSC + // Attribute to indicate USB MSC is enabled. + { MP_ROM_QSTR(MP_QSTR_usb_msc), MP_ROM_TRUE }, + #endif +}; +static MP_DEFINE_CONST_DICT(alif_module_globals, alif_module_globals_table); + +const mp_obj_module_t mp_module_alif = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&alif_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_alif, mp_module_alif); diff --git a/ports/alif/modalif.h b/ports/alif/modalif.h new file mode 100644 index 00000000000..5812d6ba545 --- /dev/null +++ b/ports/alif/modalif.h @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_MODALIF_H +#define MICROPY_INCLUDED_ALIF_MODALIF_H + +#include "py/obj.h" + +extern const mp_obj_type_t alif_flash_type; + +#endif // MICROPY_INCLUDED_ALIF_MODALIF_H diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c new file mode 100644 index 00000000000..c4102225dab --- /dev/null +++ b/ports/alif/modmachine.c @@ -0,0 +1,79 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE. + +#define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ + { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ + +static void mp_machine_idle(void) { + mp_event_wait_indefinite(); +} + +static mp_obj_t mp_machine_unique_id(void) { + return mp_obj_new_bytes((const uint8_t *)"ABCD", 4); +} + +NORETURN static void mp_machine_reset(void) { + NVIC_SystemReset(); +} + +NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { + __disable_irq(); + + MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); + + while (1) { + ; + } +} + +static mp_int_t mp_machine_reset_cause(void) { + // TODO + return 0; +} + +static mp_obj_t mp_machine_get_freq(void) { + return MP_OBJ_NEW_SMALL_INT(SystemCoreClock); +} + +static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { + mp_raise_NotImplementedError(NULL); +} + +static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { + mp_int_t delay = -1; + if (n_args == 1) { + delay = mp_obj_get_int(args[0]); + } + mp_hal_delay_ms(delay); +} + +NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { + mp_machine_lightsleep(n_args, args); + mp_machine_reset(); +} diff --git a/ports/alif/modules/_boot.py b/ports/alif/modules/_boot.py new file mode 100644 index 00000000000..36044b461e6 --- /dev/null +++ b/ports/alif/modules/_boot.py @@ -0,0 +1,23 @@ +import sys, os, alif + + +bdev = alif.Flash() +if hasattr(alif, "usb_msc"): + try: + # This may fail on VfsFat construction, or mount. + os.mount(os.VfsFat(bdev), "/flash") + except: + os.VfsFat.mkfs(bdev) + os.mount(os.VfsFat(bdev), "/flash") +else: + try: + os.mount(os.VfsLfs2(bdev, progsize=256), "/flash") + except: + os.VfsLfs2.mkfs(bdev, progsize=256) + os.mount(os.VfsLfs2(bdev, progsize=256), "/flash") + +sys.path.append("/flash") +sys.path.append("/flash/lib") +os.chdir("/flash") + +del sys, os, alif, bdev diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h new file mode 100644 index 00000000000..2d8e3b2a09d --- /dev/null +++ b/ports/alif/mpconfigport.h @@ -0,0 +1,155 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + */ + +// Options controlling how MicroPython is built, overriding defaults in py/mpconfig.h + +#include +#include // for alloca() + +// board specific definitions +#include "mpconfigboard.h" + +#ifndef MICROPY_CONFIG_ROM_LEVEL +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) +#endif + +#define MICROPY_HW_ENABLE_UART_REPL (1) // useful if there is no USB +#define MICROPY_HW_ENABLE_USBDEV (1) + +#ifndef MICROPY_HW_USB_PRODUCT_FS_STRING +#define MICROPY_HW_USB_PRODUCT_FS_STRING "Board in HS mode" +#endif +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) +#ifndef MICROPY_HW_USB_MSC +#define MICROPY_HW_USB_MSC (0) +#endif +#ifndef MICROPY_HW_USB_VID +#define MICROPY_HW_USB_VID (0xf055) +#endif +#ifndef MICROPY_HW_USB_PID +#define MICROPY_HW_USB_PID (0x9802) // interface has CDC only +#endif + +#define MICROPY_HW_FLASH_BLOCK_SIZE_BYTES (4096) + +// Memory allocation policies +#ifndef MICROPY_ALLOC_GC_STACK_SIZE +#define MICROPY_ALLOC_GC_STACK_SIZE (128) +#endif +#define MICROPY_ALLOC_PATH_MAX (128) +#define MICROPY_QSTR_BYTES_IN_HASH (1) + +// MicroPython emitters +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_EMIT_THUMB (1) +#define MICROPY_EMIT_THUMB_ARMV7M (1) +#define MICROPY_EMIT_INLINE_THUMB (1) +#define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) + +// Optimisations +#define MICROPY_OPT_COMPUTED_GOTO (1) + +// Python internal features +#define MICROPY_READER_VFS (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_SCHEDULER_DEPTH (8) +#define MICROPY_SCHEDULER_STATIC_NODES (1) +#define MICROPY_USE_INTERNAL_ERRNO (1) + +// Fine control over Python builtins, classes, modules, etc +#define MICROPY_PY_SYS_PLATFORM "alif" + +// Extended modules +#define MICROPY_EPOCH_IS_1970 (1) +#define MICROPY_PY_OS_DUPTERM (1) +#define MICROPY_PY_OS_SEP (1) +#define MICROPY_PY_OS_SYNC (1) +#define MICROPY_PY_OS_UNAME (1) +#define MICROPY_PY_TIME (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_INCLUDEFILE "ports/alif/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) +#define MICROPY_PY_MACHINE_BOOTLOADER (1) +#define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) +#define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) +#define MICROPY_PY_MACHINE_DHT_READINTO (1) +#define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_PY_MACHINE_SOFTI2C (1) +#define MICROPY_PY_MACHINE_SOFTSPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_VFS (1) + +// fatfs configuration +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_FATFS_RPATH (2) +#if MICROPY_HW_USB_MSC +#define MICROPY_FATFS_USE_LABEL (1) +#define MICROPY_FATFS_MULTI_PARTITION (1) +// Set FatFS block size to flash sector size to avoid caching +// the flash sector in memory to support smaller block sizes. +#define MICROPY_FATFS_MAX_SS (MICROPY_HW_FLASH_BLOCK_SIZE_BYTES) +#endif + +#define MP_STATE_PORT MP_STATE_VM + +// Miscellaneous settings + +// We need an implementation of the log2 function which is not a macro +#define MP_NEED_LOG2 (1) + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) + +#define MP_SSIZE_MAX (0x7fffffff) + +// Assume that if we already defined the obj repr then we also defined these items +#ifndef MICROPY_OBJ_REPR +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size +typedef intptr_t mp_off_t; +#endif + +// Board configuration settings. + +#ifndef MICROPY_BOARD_STARTUP +#define MICROPY_BOARD_STARTUP() +#endif + +#ifndef MICROPY_BOARD_EARLY_INIT +#define MICROPY_BOARD_EARLY_INIT() +#endif + +#ifndef MICROPY_BOARD_FATAL_ERROR +extern void panic(const char *); +#define MICROPY_BOARD_FATAL_ERROR panic +#endif + +#ifndef MICROPY_BOARD_ENTER_BOOTLOADER +#define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) +#endif diff --git a/ports/alif/mpconfigport.mk b/ports/alif/mpconfigport.mk new file mode 100644 index 00000000000..3d04450a60f --- /dev/null +++ b/ports/alif/mpconfigport.mk @@ -0,0 +1,12 @@ +# Enable/disable extra modules and features + +# MicroPython feature configurations +MICROPY_ROM_TEXT_COMPRESSION ?= 1 +MICROPY_FLOAT_IMPL ?= double + +# VFS support. +MICROPY_VFS_FAT ?= 1 +MICROPY_VFS_LFS2 ?= 1 + +# File containing description of content to be frozen into firmware. +FROZEN_MANIFEST ?= boards/manifest.py diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c new file mode 100644 index 00000000000..b433aa5e155 --- /dev/null +++ b/ports/alif/mphalport.c @@ -0,0 +1,152 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Damien P. George + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "py/ringbuf.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/misc.h" +#include "shared/runtime/interrupt_char.h" +#include "shared/timeutils/timeutils.h" +#include "shared/tinyusb/mp_usbd.h" +#include "shared/tinyusb/mp_usbd_cdc.h" +#include "tusb.h" +#include "mpuart.h" + +#ifndef MICROPY_HW_STDIN_BUFFER_LEN +#define MICROPY_HW_STDIN_BUFFER_LEN 512 +#endif + +static uint8_t stdin_ringbuf_array[MICROPY_HW_STDIN_BUFFER_LEN]; +ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array) }; + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + #if MICROPY_HW_USB_CDC + ret |= mp_usbd_cdc_poll_interfaces(poll_flags); + #endif + #if MICROPY_HW_ENABLE_UART_REPL + if (poll_flags & MP_STREAM_POLL_WR) { + ret |= MP_STREAM_POLL_WR; + } + #endif + #if MICROPY_PY_OS_DUPTERM + ret |= mp_os_dupterm_poll(poll_flags); + #endif + return ret; +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + for (;;) { + #if MICROPY_HW_USB_CDC + mp_usbd_cdc_poll_interfaces(0); + #endif + int c = ringbuf_get(&stdin_ringbuf); + if (c != -1) { + return c; + } + #if MICROPY_PY_OS_DUPTERM + int dupterm_c = mp_os_dupterm_rx_chr(); + if (dupterm_c >= 0) { + return dupterm_c; + } + #endif + mp_event_wait_indefinite(); + } +} + +// Send string of given length +mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { + mp_uint_t ret = len; + bool did_write = false; + + #if MICROPY_HW_ENABLE_UART_REPL + mp_uart_write_strn(str, len); + did_write = true; + #endif + + #if MICROPY_HW_USB_CDC + mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len); + if (cdc_res > 0) { + did_write = true; + ret = MIN(cdc_res, ret); + } + #endif + + #if MICROPY_PY_OS_DUPTERM + int dupterm_res = mp_os_dupterm_tx_strn(str, len); + if (dupterm_res >= 0) { + did_write = true; + ret = MIN((mp_uint_t)dupterm_res, ret); + } + #endif + + return did_write ? ret : 0; +} + +static uint32_t volatile ticks_ms; + +void SysTick_Handler(void) { + ++ticks_ms; +} + +mp_uint_t mp_hal_ticks_cpu(void) { + if (!(DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk)) { + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + #if defined(__CORTEX_M) && __CORTEX_M == 7 + // on Cortex-M7 we must unlock the DWT before writing to its registers + DWT->LAR = 0xc5acce55; + #endif + DWT->CYCCNT = 0; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + } + return DWT->CYCCNT; +} + +mp_uint_t mp_hal_ticks_us(void) { + return ticks_ms / 1000; +} + +mp_uint_t mp_hal_ticks_ms(void) { + return ticks_ms; +} + +void mp_hal_delay_us(mp_uint_t us) { + mp_hal_delay_ms(us / 1000); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + uint32_t t0 = mp_hal_ticks_ms(); + while ((mp_hal_ticks_ms() - t0) < ms) { + __WFI(); + } +} + +uint64_t mp_hal_time_ns(void) { + return 0; +} diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h new file mode 100644 index 00000000000..1b8c854fd0b --- /dev/null +++ b/ports/alif/mphalport.h @@ -0,0 +1,152 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/ringbuf.h" +#include "shared/runtime/interrupt_char.h" +#include "irq.h" +#include ALIF_CMSIS_H + +#define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) + +// For regular code that wants to prevent "background tasks" from running. +// These background tasks (LWIP, Bluetooth) run in PENDSV context. +#define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); + +// Port level Wait-for-Event macro +// +// Do not use this macro directly, include py/runtime.h and +// call mp_event_wait_indefinite() or mp_event_wait_ms(timeout) +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) \ + do { \ + if ((TIMEOUT_MS) < 0) { \ + __WFE(); \ + } else { \ + /* TODO */ \ + __WFE(); \ + } \ + } while (0) + +// TODO requires mods to py/emitglue.c for this to be picked up +#define MP_HAL_CLEAN_DCACHE(addr, size) \ + (SCB_CleanDCache_by_Addr((uint32_t *)((uint32_t)addr & ~0x1f), \ + ((uint32_t)((uint8_t *)addr + size + 0x1f) & ~0x1f) - ((uint32_t)addr & ~0x1f))) + +extern ringbuf_t stdin_ringbuf; + +// TODO +#define mp_hal_quiet_timing_enter() 0 +#define mp_hal_quiet_timing_exit(x) (void)x +#define mp_hal_delay_us_fast mp_hal_delay_us + +/******************************************************************************/ +// C-level pin HAL + +#include "py/obj.h" +#include "gpio.h" +#include "pinconf.h" + +#define MP_HAL_PIN_FMT "%q" +#define MP_HAL_PIN_MODE_INPUT (0) +#define MP_HAL_PIN_MODE_OUTPUT (1) +#define MP_HAL_PIN_MODE_OPEN_DRAIN (2) +#define MP_HAL_PIN_PULL_NONE (0) +#define MP_HAL_PIN_PULL_UP (1) +#define MP_HAL_PIN_PULL_DOWN (2) + +#define mp_hal_pin_obj_t const machine_pin_obj_t * + +typedef struct _machine_pin_obj_t { + mp_obj_base_t base; + GPIO_Type *gpio; + uint8_t port; + uint8_t pin; + qstr name; +} machine_pin_obj_t; + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); + +static inline qstr mp_hal_pin_name(mp_hal_pin_obj_t pin) { + return pin->name; +} + +static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { + uint8_t alt_func = PINMUX_ALTERNATE_FUNCTION_0; + uint8_t pad_ctrl = PADCTRL_READ_ENABLE; + pinconf_set(pin->port, pin->pin, alt_func, pad_ctrl); + gpio_set_direction_input(pin->gpio, pin->pin); +} + +static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { + uint8_t alt_func = PINMUX_ALTERNATE_FUNCTION_0; + uint8_t pad_ctrl = PADCTRL_READ_ENABLE; + pinconf_set(pin->port, pin->pin, alt_func, pad_ctrl); + gpio_set_direction_output(pin->gpio, pin->pin); +} + +static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { + uint8_t alt_func = PINMUX_ALTERNATE_FUNCTION_0; + uint8_t pad_ctrl = PADCTRL_DRIVER_OPEN_DRAIN | PADCTRL_READ_ENABLE; + pinconf_set(pin->port, pin->pin, alt_func, pad_ctrl); + gpio_set_direction_output(pin->gpio, pin->pin); +} + +static inline void mp_hal_pin_low(mp_hal_pin_obj_t pin) { + gpio_set_value_low(pin->gpio, pin->pin); +} + +static inline void mp_hal_pin_high(mp_hal_pin_obj_t pin) { + gpio_set_value_high(pin->gpio, pin->pin); +} + +static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { + return gpio_get_value(pin->gpio, pin->pin); +} + +static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { + if (v) { + mp_hal_pin_high(pin); + } else { + mp_hal_pin_low(pin); + } +} + +static inline void mp_hal_pin_od_low(mp_hal_pin_obj_t pin) { + mp_hal_pin_low(pin); +} + +static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { + mp_hal_pin_high(pin); +} + +static inline void mp_hal_wake_main_task_from_isr(void) { + // Defined for tinyusb support, nothing needs to be done here. +} + +// Include all the pin definitions. +#include "genhdr/pins_board.h" diff --git a/ports/alif/mpuart.c b/ports/alif/mpuart.c new file mode 100644 index 00000000000..e9cfcf34fed --- /dev/null +++ b/ports/alif/mpuart.c @@ -0,0 +1,113 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/runtime.h" +#include "py/mphal.h" +#include "mpuart.h" + +#if MICROPY_HW_ENABLE_UART_REPL + +#include "pinconf.h" +#include "sys_ctrl_uart.h" +#include "uart.h" + +#define TX_PORT PORT_12 +#define TX_PIN PIN_2 +#define RX_PORT PORT_12 +#define RX_PIN PIN_1 +#define UART_ID 4 +#define UART_IRQN UART4_IRQ_IRQn +#define UART_PTR ((UART_Type *)UART4_BASE) +#define BAUDRATE 115200 +#define SYST_PCLK 100000000 + +static UART_TRANSFER transfer; + +void mp_uart_init(void) { + pinconf_set(TX_PORT, TX_PIN, PINMUX_ALTERNATE_FUNCTION_2, 0); + pinconf_set(RX_PORT, RX_PIN, PINMUX_ALTERNATE_FUNCTION_2, PADCTRL_READ_ENABLE); + select_uart_clock_syst_pclk(UART_ID); + enable_uart_clock(UART_ID); + uart_software_reset(UART_PTR); + uart_enable_fifo(UART_PTR); + uart_disable_tx_irq(UART_PTR); + uart_disable_rx_irq(UART_PTR); + uart_set_baudrate(UART_PTR, SYST_PCLK, BAUDRATE); + uart_set_data_parity_stop_bits(UART_PTR, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1); + uart_set_flow_control(UART_PTR, UART_FLOW_CONTROL_NONE); + NVIC_ClearPendingIRQ(UART_IRQN); + NVIC_SetPriority(UART_IRQN, IRQ_PRI_UART_REPL); + NVIC_EnableIRQ(UART_IRQN); + uart_set_tx_trigger(UART_PTR, UART_TX_FIFO_EMPTY); + uart_set_rx_trigger(UART_PTR, UART_RX_ONE_CHAR_IN_FIFO); + uart_enable_rx_irq(UART_PTR); +} + +void mp_uart_write_strn(const char *str, size_t len) { + memset(&transfer, 0, sizeof(transfer)); + transfer.tx_buf = (uint8_t *)str; + transfer.tx_total_num = len; + transfer.tx_curr_cnt = 0U; + transfer.status = UART_TRANSFER_STATUS_NONE; + + uart_enable_tx_irq(UART_PTR); + + uint32_t start = mp_hal_ticks_ms(); + while (transfer.status == UART_TRANSFER_STATUS_NONE) { + if (mp_hal_ticks_ms() - start > 10 * len) { + break; + } + __WFE(); + } + uart_disable_tx_irq(UART_PTR); +} + +void UART4_IRQHandler(void) { + if (UART_PTR->UART_RFL) { + for (;;) { + uint32_t rx_fifo_available_cnt = UART_PTR->UART_RFL; + if (rx_fifo_available_cnt == 0) { + break; + } + for (uint32_t i = 0; i < rx_fifo_available_cnt; ++i) { + int c = UART_PTR->UART_RBR; + #if MICROPY_KBD_EXCEPTION + if (c == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + continue; + } + #endif + ringbuf_put(&stdin_ringbuf, c); + } + } + } else { + uart_irq_handler(UART_PTR, &transfer); + } + __SEV(); +} + +#endif diff --git a/ports/alif/mpuart.h b/ports/alif/mpuart.h new file mode 100644 index 00000000000..0c2e510a94e --- /dev/null +++ b/ports/alif/mpuart.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF2_UART_H +#define MICROPY_INCLUDED_ALIF2_UART_H + +void mp_uart_init(void); +void mp_uart_write_strn(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_ALIF2_UART_H diff --git a/ports/alif/msc_disk.c b/ports/alif/msc_disk.c new file mode 100644 index 00000000000..a01494d20f0 --- /dev/null +++ b/ports/alif/msc_disk.c @@ -0,0 +1,151 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * Copyright (c) 2024 OpenMV LLC. + * + * 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/mpconfig.h" +#include "py/misc.h" +#include "ospi_flash.h" +#include "tusb.h" + +#if CFG_TUD_MSC + +#if MICROPY_FATFS_MAX_SS != MICROPY_HW_FLASH_BLOCK_SIZE_BYTES +#error MICROPY_FATFS_MAX_SS must be the same size as MICROPY_HW_FLASH_BLOCK_SIZE_BYTES +#endif + +#define BLOCK_SIZE (MICROPY_HW_FLASH_BLOCK_SIZE_BYTES) +#define BLOCK_COUNT (MICROPY_HW_FLASH_STORAGE_BYTES / BLOCK_SIZE) +#define FLASH_BASE_ADDR (MICROPY_HW_FLASH_STORAGE_BASE_ADDR) + +static bool ejected = false; + +// Invoked on SCSI_CMD_INQUIRY. +// Fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively. +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + memcpy(vendor_id, MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING), 8)); + memcpy(product_id, MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING), 16)); + memcpy(product_rev, MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING), 4)); +} + +// Invoked on Test-Unit-Ready command. +// Return true allowing host to read/write this LUN (e.g SD card inserted). +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (ejected) { + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + return true; +} + +// Invoked on SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size. +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + *block_size = BLOCK_SIZE; + *block_count = BLOCK_COUNT; +} + +// Invoked on Start-Stop-Unit command: +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + if (load_eject) { + if (start) { + // load disk storage + ejected = false; + } else { + // unload disk storage + ejected = true; + } + } + return true; +} + +// Callback invoked on READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of read bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + uint32_t addr = FLASH_BASE_ADDR + lba * BLOCK_SIZE; + uint32_t block_count = bufsize / BLOCK_SIZE; + int ret = ospi_flash_read(addr, block_count * BLOCK_SIZE, buffer); + if (ret < 0) { + return ret; + } + return block_count * BLOCK_SIZE; +} + +// Callback invoked on WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes. +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + if (bufsize < BLOCK_SIZE) { + // Workaround for issue with TinyUSB passing in a small buffer. + uint32_t addr = FLASH_BASE_ADDR + lba * BLOCK_SIZE + offset; + if (offset == 0) { + int ret = ospi_flash_erase_sector(addr); + if (ret < 0) { + return ret; + } + } + int ret = ospi_flash_write(addr, bufsize, buffer); + if (ret < 0) { + return ret; + } + return bufsize; + } + + uint32_t addr = FLASH_BASE_ADDR + lba * BLOCK_SIZE; + uint32_t block_count = bufsize / BLOCK_SIZE; + for (uint32_t block = 0; block < block_count; ++block) { + int ret = ospi_flash_erase_sector(addr); + if (ret < 0) { + return ret; + } + ret = ospi_flash_write(addr, BLOCK_SIZE, buffer); + if (ret < 0) { + return ret; + } + addr += BLOCK_SIZE; + buffer += BLOCK_SIZE; + } + return block_count * BLOCK_SIZE; +} + +// Callback invoked on a SCSI command that's not handled by TinyUSB. +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { + int32_t resplen = 0; + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Sync the logical unit if needed. + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + return resplen; +} + +#endif diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c new file mode 100644 index 00000000000..fa96053b365 --- /dev/null +++ b/ports/alif/ospi_flash.c @@ -0,0 +1,265 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/mperrno.h" +#include "py/mphal.h" +#include "ospi_flash.h" + +#include "ospi_drv.h" +#include "pinconf.h" + +#define CMD_RDSR (0x05) +#define CMD_WREN (0x06) +#define CMD_SEC_ERASE_32ADDR (0x21) // 4kiB sector erase with 32-bit address +#define CMD_WRVOL (0x81) +#define CMD_RD_DEVID (0x9f) + +#define WAIT_SR_TIMEOUT (1000000) + +// maximum bytes we can write in one SPI transfer +// limited by 256 byte FIFO buffer (can't go up to 256) +// need to use DMA to make this 256 +#define PAGE_SIZE (128) + +#define ISSI_MODE_OCTAL_DDR_DQS (0xe7) + +// All OSPI1 pins use the same alternate function. +#define OSPI1_PIN_FUNCTION PINMUX_ALTERNATE_FUNCTION_1 + +typedef struct _mp_spiflash_t { + ospi_flash_cfg_t cfg; +} mp_spiflash_t; + +static mp_spiflash_t global_flash; + +// Alif version of this function can overwrite the destination buffer. +static void ospi_recv_blocking2(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint8_t *buffer) { + uint32_t val; + + ospi_writel(ospi_cfg, data_reg, command); + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + + ospi_cfg->rx_cnt = 0; + + while (ospi_cfg->rx_cnt < ospi_cfg->rx_req) { + unsigned int timeout = 100000; + while (ospi_readl(ospi_cfg, rxflr) == 0) { + if (--timeout == 0) { + return; + } + } + val = ospi_readl(ospi_cfg, data_reg); + *buffer++ = (uint8_t)val; + ospi_cfg->rx_cnt++; + } +} + +static int mp_spiflash_read_cmd(mp_spiflash_t *self, uint8_t cmd, size_t len, uint8_t *dest) { + ospi_setup_read(&self->cfg, 0, len, 8); + ospi_recv_blocking2(&self->cfg, cmd, dest); + return 0; +} + +static int mp_spiflash_write_cmd(mp_spiflash_t *self, uint8_t cmd) { + ospi_setup_write(&self->cfg, 0); + ospi_send_blocking(&self->cfg, cmd); + return 0; +} + +static int mp_spiflash_wait_sr(mp_spiflash_t *self, uint8_t mask, uint8_t val, uint32_t timeout) { + do { + uint8_t sr; + int ret = mp_spiflash_read_cmd(self, CMD_RDSR, 1, &sr); + if (ret != 0) { + return ret; + } + if ((sr & mask) == val) { + return 0; // success + } + } while (timeout--); + + return -MP_ETIMEDOUT; +} + +static int mp_spiflash_wait_wel1(mp_spiflash_t *self) { + return mp_spiflash_wait_sr(self, 2, 2, WAIT_SR_TIMEOUT); +} + +static int mp_spiflash_wait_wip0(mp_spiflash_t *self) { + return mp_spiflash_wait_sr(self, 1, 0, WAIT_SR_TIMEOUT); +} + +static uint32_t ospi_flash_read_id(mp_spiflash_t *self) { + uint8_t buf[8]; + ospi_setup_read(&self->cfg, 0, 3, ospi_flash_settings.read_id_dummy_cycles); + ospi_recv_blocking2(&self->cfg, CMD_RD_DEVID, buf); + return buf[0] | buf[1] << 8 | buf[2] << 16; +} + +static void ospi_flash_write_reg_sdr(mp_spiflash_t *self, uint8_t cmd, uint8_t addr, uint8_t value) { + mp_spiflash_write_cmd(self, CMD_WREN); + ospi_setup_write_sdr(&self->cfg, 6); + ospi_push(&self->cfg, cmd); + ospi_push(&self->cfg, 0x00); + ospi_push(&self->cfg, 0x00); + ospi_push(&self->cfg, addr); + ospi_send_blocking(&self->cfg, value); +} + +int ospi_flash_init(void) { + mp_spiflash_t *self = &global_flash; + + uint32_t pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; + + pinconf_set(pin_OSPI1_CS->port, pin_OSPI1_CS->pin, OSPI1_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA); + pinconf_set(pin_OSPI1_SCLK->port, pin_OSPI1_SCLK->pin, OSPI1_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST); + pinconf_set(pin_OSPI1_D0->port, pin_OSPI1_D0->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D1->port, pin_OSPI1_D1->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D2->port, pin_OSPI1_D2->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D3->port, pin_OSPI1_D3->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + #if defined(pin_OSPI1_D4) + pinconf_set(pin_OSPI1_D4->port, pin_OSPI1_D4->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D5->port, pin_OSPI1_D5->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D6->port, pin_OSPI1_D6->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin_OSPI1_D7->port, pin_OSPI1_D7->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + #endif + #if defined(pin_OSPI1_RXDS) + pinconf_set(pin_OSPI1_RXDS->port, pin_OSPI1_RXDS->pin, OSPI1_PIN_FUNCTION, pad_ctrl); + #endif + + #if defined(pin_OSPI1_RXDS) + if (pin_OSPI1_RXDS->port == PORT_10 && pin_OSPI1_RXDS->pin == PIN_7) { + // Alif: P5_6 is needed to support proper alt function selection of P10_7. + pinconf_set(PORT_5, PIN_6, OSPI1_PIN_FUNCTION, pad_ctrl); + } + #endif + + // Reset the SPI flash. + mp_hal_pin_output(pin_OSPI1_RESET); + mp_hal_pin_low(pin_OSPI1_RESET); + mp_hal_delay_us(30); + mp_hal_pin_high(pin_OSPI1_RESET); + + // Configure the OSPI peripheral. + self->cfg.regs = (ssi_regs_t *)OSPI1_BASE; + self->cfg.aes_regs = (aes_regs_t *)AES1_BASE; + self->cfg.xip_base = (volatile void *)OSPI1_XIP_BASE; + self->cfg.ser = 1; + self->cfg.addrlen = 8; // 32-bit address length + self->cfg.ospi_clock = ospi_flash_settings.freq_mhz; + self->cfg.ddr_en = 0; + self->cfg.wait_cycles = 0; // used only for ospi_xip_exit + ospi_init(&self->cfg); + + if (ospi_flash_settings.is_oct && ospi_flash_settings.is_ddr) { + // Switch SPI flash to Octal DDR mode. + ospi_flash_write_reg_sdr(self, CMD_WRVOL, 0x00, ISSI_MODE_OCTAL_DDR_DQS); + self->cfg.ddr_en = 1; + } + + // Check the device ID. + if (ospi_flash_read_id(self) != ospi_flash_settings.jedec_id) { + return -1; + } + + return 0; +} + +int ospi_flash_erase_sector(uint32_t addr) { + mp_spiflash_t *self = &global_flash; + + mp_spiflash_write_cmd(self, CMD_WREN); + int ret = mp_spiflash_wait_wel1(self); + if (ret < 0) { + return ret; + } + + ospi_setup_write(&self->cfg, 8 /* 32-bit addr len*/); + ospi_push(&self->cfg, CMD_SEC_ERASE_32ADDR); + ospi_send_blocking(&self->cfg, addr); + + return mp_spiflash_wait_wip0(self); +} + +int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest) { + // OSPI FIFO is limited to 256 bytes. Need DMA to get a longer read. + mp_spiflash_t *self = &global_flash; + + while (len) { + uint32_t l = len; + if (l > 256) { + l = 256; + } + ospi_setup_read(&self->cfg, 8 /* 32-bit addr len*/, l, ospi_flash_settings.read_dummy_cycles); + ospi_push(&self->cfg, ospi_flash_settings.read_command); + ospi_recv_blocking2(&self->cfg, addr, dest); + addr += l; + len -= l; + dest += l; + } + + return 0; +} + +static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src) { + mp_spiflash_t *self = &global_flash; + + mp_spiflash_write_cmd(self, CMD_WREN); + int ret = mp_spiflash_wait_wel1(self); + if (ret < 0) { + return ret; + } + + ospi_setup_write(&self->cfg, 8 /* 32-bit addr len*/); + ospi_push(&self->cfg, ospi_flash_settings.write_command); + ospi_push(&self->cfg, addr); + while (--len) { + ospi_push(&self->cfg, *src++); + } + ospi_send_blocking(&self->cfg, *src); + + return mp_spiflash_wait_wip0(self); +} + +int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { + int ret = 0; + uint32_t offset = addr & (PAGE_SIZE - 1); + while (len) { + size_t rest = PAGE_SIZE - offset; + if (rest > len) { + rest = len; + } + ret = ospi_flash_write_page(addr, rest, src); + if (ret != 0) { + break; + } + len -= rest; + addr += rest; + src += rest; + offset = 0; + } + return ret; +} diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h new file mode 100644 index 00000000000..945dc4bb07b --- /dev/null +++ b/ports/alif/ospi_flash.h @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_OSPI_FLASH_H +#define MICROPY_INCLUDED_ALIF_OSPI_FLASH_H + +#include +#include + +typedef struct _ospi_flash_settings_t { + uint32_t jedec_id; + uint32_t freq_mhz; + bool is_quad : 1; + bool is_oct : 1; + bool is_ddr : 1; + uint8_t read_id_dummy_cycles; + uint8_t read_dummy_cycles; + uint8_t read_command; + uint8_t write_command; +} ospi_flash_settings_t; + +// Provided by the board when it enables OSPI1. +extern const ospi_flash_settings_t ospi_flash_settings; + +int ospi_flash_init(void); +int ospi_flash_erase_sector(uint32_t addr); +int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest); +int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src); + +#endif // MICROPY_INCLUDED_ALIF_OSPI_FLASH_H diff --git a/ports/alif/ospi_xip_user.h b/ports/alif/ospi_xip_user.h new file mode 100644 index 00000000000..a15baff2a8b --- /dev/null +++ b/ports/alif/ospi_xip_user.h @@ -0,0 +1,5 @@ +// This file is needed by ospi_xip/source/ospi/ospi_drv.c. +#define OSPI_XIP_ENABLE_AES_DECRYPTION (0) +#define OSPI_XIP_RX_SAMPLE_DELAY (4) +#define OSPI_XIP_DDR_DRIVE_EDGE (1) +#define OSPI_XIP_RXDS_DELAY (12) diff --git a/ports/alif/pendsv.c b/ports/alif/pendsv.c new file mode 100644 index 00000000000..965053e6c59 --- /dev/null +++ b/ports/alif/pendsv.c @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "pendsv.h" +#include "irq.h" + +static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; + +void pendsv_init(void) { + NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); +} + +void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { + pendsv_dispatch_table[slot] = f; + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; +} + +void PendSV_Handler(void) { + for (size_t i = 0; i < PENDSV_DISPATCH_NUM_SLOTS; ++i) { + if (pendsv_dispatch_table[i] != NULL) { + pendsv_dispatch_t f = pendsv_dispatch_table[i]; + pendsv_dispatch_table[i] = NULL; + f(); + } + } +} diff --git a/ports/alif/pendsv.h b/ports/alif/pendsv.h new file mode 100644 index 00000000000..43a4b7a8538 --- /dev/null +++ b/ports/alif/pendsv.h @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_PENDSV_H +#define MICROPY_INCLUDED_ALIF_PENDSV_H + +#include "py/mpconfig.h" + +#ifndef MICROPY_BOARD_PENDSV_ENTRIES +#define MICROPY_BOARD_PENDSV_ENTRIES +#endif + +enum { + PENDSV_DISPATCH_SOFT_TIMER, + MICROPY_BOARD_PENDSV_ENTRIES + PENDSV_DISPATCH_MAX +}; + +#define PENDSV_DISPATCH_NUM_SLOTS PENDSV_DISPATCH_MAX + +typedef void (*pendsv_dispatch_t)(void); + +void pendsv_init(void); +void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f); + +#endif // MICROPY_INCLUDED_ALIF_PENDSV_H diff --git a/ports/alif/qstrdefsport.h b/ports/alif/qstrdefsport.h new file mode 100644 index 00000000000..00d3e2ae3c5 --- /dev/null +++ b/ports/alif/qstrdefsport.h @@ -0,0 +1,2 @@ +// qstrs specific to this port +// *FORMAT-OFF* diff --git a/ports/alif/tinyusb_port/tusb_config.h b/ports/alif/tinyusb_port/tusb_config.h new file mode 100644 index 00000000000..d244b4c241e --- /dev/null +++ b/ports/alif/tinyusb_port/tusb_config.h @@ -0,0 +1,73 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 _ALIF_TUSB_CONFIG_H_ +#define _ALIF_TUSB_CONFIG_H_ + +// --------------------------------------------------------------------+ +// Board Specific Configuration +// --------------------------------------------------------------------+ + +#define CFG_TUSB_MCU OPT_MCU_NONE +// #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#define TUP_DCD_ENDPOINT_MAX 8 +#define TUD_OPT_RHPORT 0 + +// -------------------------------------------------------------------- +// COMMON CONFIGURATION +// -------------------------------------------------------------------- + +#define CFG_TUSB_OS OPT_OS_NONE +#define CFG_TUSB_DEBUG 0 + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + */ +#define CFG_TUSB_MEM_SECTION __attribute__((section(".bss.sram0"))) + +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(32))) + +// -------------------------------------------------------------------- +// DEVICE CONFIGURATION +// -------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +// CDC Endpoint transfer buffer size, more is faster +#define CFG_TUD_CDC_EP_BUFSIZE (4096) +#define CFG_TUD_CDC_RX_BUFSIZE (4096) +#define CFG_TUD_CDC_TX_BUFSIZE (4096) + +#include "shared/tinyusb/tusb_config.h" + +#endif /* _ALIF_TUSB_CONFIG_H_ */ diff --git a/ports/alif/usbd.c b/ports/alif/usbd.c new file mode 100644 index 00000000000..9df66106835 --- /dev/null +++ b/ports/alif/usbd.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/mpconfig.h" + +#if MICROPY_HW_ENABLE_USBDEV + +#include "shared/tinyusb/mp_usbd.h" +#include "tusb.h" + +void mp_usbd_port_get_serial_number(char *serial_buf) { + // TODO + uint8_t id[8] = "ABCDEFGH"; + MP_STATIC_ASSERT(sizeof(id) * 2 <= MICROPY_HW_USB_DESC_STR_MAX); + mp_usbd_hex_str(serial_buf, id, sizeof(id)); +} + +#endif From 40ff0c2f276883ea325ce8977453bd16a2f2029a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Jan 2024 14:37:52 +1100 Subject: [PATCH 0462/2098] alif/system_tick: Use a UTIMER for system ticks and timing. Includes an implementation of `system_tick_wfe_with_timeout_us()`. Signed-off-by: Damien George --- ports/alif/Makefile | 2 + ports/alif/main.c | 3 +- ports/alif/mphalport.c | 36 +++++------ ports/alif/system_tick.c | 128 +++++++++++++++++++++++++++++++++++++++ ports/alif/system_tick.h | 38 ++++++++++++ 5 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 ports/alif/system_tick.c create mode 100644 ports/alif/system_tick.h diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 2863ebe2c4d..cd02f4ac2d6 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -119,6 +119,7 @@ SRC_C = \ msc_disk.c \ ospi_flash.c \ pendsv.c \ + system_tick.c \ usbd.c \ $(wildcard $(BOARD_DIR)/*.c) @@ -175,6 +176,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ drivers/source/pinconf.c \ drivers/source/uart.c \ + drivers/source/utimer.c \ ospi_xip/source/ospi/ospi_drv.c \ ) diff --git a/ports/alif/main.c b/ports/alif/main.c index 0eb43a38268..9293d74c0b8 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -38,6 +38,7 @@ #include "mpuart.h" #include "ospi_flash.h" #include "pendsv.h" +#include "system_tick.h" extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -53,7 +54,7 @@ NORETURN void panic(const char *msg) { } void _start(void) { - SysTick_Config(SystemCoreClock / 1000); + system_tick_init(); MICROPY_BOARD_STARTUP(); diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index b433aa5e155..abb71aae696 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -36,6 +36,7 @@ #include "shared/tinyusb/mp_usbd_cdc.h" #include "tusb.h" #include "mpuart.h" +#include "system_tick.h" #ifndef MICROPY_HW_STDIN_BUFFER_LEN #define MICROPY_HW_STDIN_BUFFER_LEN 512 @@ -109,42 +110,33 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { return did_write ? ret : 0; } -static uint32_t volatile ticks_ms; - -void SysTick_Handler(void) { - ++ticks_ms; -} - mp_uint_t mp_hal_ticks_cpu(void) { - if (!(DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk)) { - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; - #if defined(__CORTEX_M) && __CORTEX_M == 7 - // on Cortex-M7 we must unlock the DWT before writing to its registers - DWT->LAR = 0xc5acce55; - #endif - DWT->CYCCNT = 0; - DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; - } - return DWT->CYCCNT; + return system_tick_get_u32(); } mp_uint_t mp_hal_ticks_us(void) { - return ticks_ms / 1000; + // Convert system tick to microsecond counter. + return system_tick_get_u64() / system_core_clock_mhz; } mp_uint_t mp_hal_ticks_ms(void) { - return ticks_ms; + // Convert system tick to millisecond counter. + return system_tick_get_u64() / (SystemCoreClock / 1000); } void mp_hal_delay_us(mp_uint_t us) { - mp_hal_delay_ms(us / 1000); + uint64_t ticks_delay = (uint64_t)us * system_core_clock_mhz; + uint64_t start = system_tick_get_u64(); + while (system_tick_get_u64() - start < ticks_delay) { + } } void mp_hal_delay_ms(mp_uint_t ms) { uint32_t t0 = mp_hal_ticks_ms(); - while ((mp_hal_ticks_ms() - t0) < ms) { - __WFI(); - } + do { + // TODO: power saving wait + mp_event_handle_nowait(); + } while (mp_hal_ticks_ms() - t0 < ms); } uint64_t mp_hal_time_ns(void) { diff --git a/ports/alif/system_tick.c b/ports/alif/system_tick.c new file mode 100644 index 00000000000..95d5391c038 --- /dev/null +++ b/ports/alif/system_tick.c @@ -0,0 +1,128 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "irq.h" +#include "system_tick.h" + +#include "utimer.h" + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +#define UTIMER ((UTIMER_Type *)UTIMER_BASE) +#define UTIMER_CHANNEL (11) + +uint64_t system_core_clock_mhz; +static volatile uint32_t system_tick_hi; + +static void system_tick_nvic_config(unsigned int index) { + NVIC_ClearPendingIRQ(UTIMER_IRQ0_IRQn + UTIMER_CHANNEL * 8 + index); + NVIC_SetPriority(UTIMER_IRQ0_IRQn + UTIMER_CHANNEL * 8 + index, IRQ_PRI_SYSTEM_TICK); + NVIC_EnableIRQ(UTIMER_IRQ0_IRQn + UTIMER_CHANNEL * 8 + index); +} + +void system_tick_init(void) { + system_tick_hi = 0; + system_core_clock_mhz = SystemCoreClock / 1000000; + + // Configure NVIC OVER_FLOW interrupt. + system_tick_nvic_config(7); + + utimer_clock_enable(UTIMER, UTIMER_CHANNEL); + utimer_channel_config cfg = { 0 }; + cfg.fixed_buffer = false; + utimer_config_direction(UTIMER, UTIMER_CHANNEL, UTIMER_COUNTER_UP, &cfg); + utimer_set_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR_PTR, 0xffffffff); + utimer_unmask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_OVER_FLOW_MASK); + utimer_control_enable(UTIMER, UTIMER_CHANNEL); + utimer_counter_start(UTIMER, UTIMER_CHANNEL); + + // Set up the UTIMER compare interrupt, to be used later. + system_tick_nvic_config(2); + UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_COMPARE_CTRL_A |= COMPARE_CTRL_DRV_COMPARE_EN; +} + +// COMPARE_A_BUF1 +void UTIMER_IRQ90Handler(void) { + uint32_t chan_interrupt = UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT; + if (chan_interrupt & CHAN_INTERRUPT_COMPARE_A_BUF1_MASK) { + utimer_clear_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_A_BUF1_MASK); + __SEV(); + } +} + +// OVER_FLOW +void UTIMER_IRQ95Handler(void) { + uint32_t chan_interrupt = UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT; + if (chan_interrupt & CHAN_INTERRUPT_OVER_FLOW_MASK) { + utimer_clear_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_OVER_FLOW_MASK); + ++system_tick_hi; + } +} + +uint32_t system_tick_get_u32(void) { + return utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); +} + +uint64_t system_tick_get_u64(void) { + uint32_t irq_state = disable_irq(); + uint32_t ticks_lo = utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); + uint32_t ticks_hi = system_tick_hi; + uint32_t chan_interrupt = UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT; + enable_irq(irq_state); + + if (chan_interrupt & CHAN_INTERRUPT_OVER_FLOW_MASK) { + // The timer had an overflow while interrupts were disabled. + if (ticks_lo < 0x80000000) { + // The timer had an overflow just before we sampled it. + ++ticks_hi; + } + } + + // This ticks runs at SystemCoreClock. + return (uint64_t)ticks_hi << 32 | ticks_lo; +} + +void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { + // Maximum 10 second timeout, to not overflow signed 32-bit ticks when + // system_core_clock_mhz==400. + uint32_t timeout_ticks = MIN(timeout_us, 10000000) * system_core_clock_mhz; + + // Set up the UTIMER compare interrupt to fire after the given timeout. + uint32_t cntr = utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); + utimer_set_count(UTIMER, UTIMER_CHANNEL, UTIMER_COMPARE_A_BUF1, cntr + timeout_ticks); + utimer_clear_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_A_BUF1_MASK); + utimer_unmask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_A_BUF1_MASK); + + // Wait for an event (or compare timeout event) if the timeout hasn't expired yet + // (this check handles the case of short timeouts). + uint32_t cntr2 = utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); + if ((uint32_t)(cntr2 - cntr) < timeout_ticks) { + __WFE(); + } + + // Disable the UTIMER compare interrupt. + utimer_mask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_A_BUF1_MASK); +} diff --git a/ports/alif/system_tick.h b/ports/alif/system_tick.h new file mode 100644 index 00000000000..307c87a345f --- /dev/null +++ b/ports/alif/system_tick.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_SYSTEM_TICK_H +#define MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H + +#include + +extern uint64_t system_core_clock_mhz; + +void system_tick_init(void); +uint32_t system_tick_get_u32(void); +uint64_t system_tick_get_u64(void); +void system_tick_wfe_with_timeout_us(uint32_t timeout_us); + +#endif // MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H From 975f84f2adc1f89b4c9349b6a6e83f6bc4ec2fd6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Jan 2024 16:08:45 +1100 Subject: [PATCH 0463/2098] alif/mphalport: Enable efficient events and implement quiet timing. Signed-off-by: Damien George --- ports/alif/mphalport.c | 12 ++++++++---- ports/alif/mphalport.h | 13 ++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index abb71aae696..8d0fa1b92ba 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -133,10 +133,14 @@ void mp_hal_delay_us(mp_uint_t us) { void mp_hal_delay_ms(mp_uint_t ms) { uint32_t t0 = mp_hal_ticks_ms(); - do { - // TODO: power saving wait - mp_event_handle_nowait(); - } while (mp_hal_ticks_ms() - t0 < ms); + mp_event_handle_nowait(); + for (;;) { + uint32_t t1 = mp_hal_ticks_ms(); + if (t1 - t0 >= ms) { + break; + } + mp_event_wait_ms(ms - (t1 - t0)); + } } uint64_t mp_hal_time_ns(void) { diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 1b8c854fd0b..8098c2650df 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -27,6 +27,7 @@ #include "py/ringbuf.h" #include "shared/runtime/interrupt_char.h" #include "irq.h" +#include "system_tick.h" #include ALIF_CMSIS_H #define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() @@ -47,8 +48,7 @@ if ((TIMEOUT_MS) < 0) { \ __WFE(); \ } else { \ - /* TODO */ \ - __WFE(); \ + system_tick_wfe_with_timeout_us(TIMEOUT_MS * 1000); \ } \ } while (0) @@ -57,13 +57,12 @@ (SCB_CleanDCache_by_Addr((uint32_t *)((uint32_t)addr & ~0x1f), \ ((uint32_t)((uint8_t *)addr + size + 0x1f) & ~0x1f) - ((uint32_t)addr & ~0x1f))) -extern ringbuf_t stdin_ringbuf; - -// TODO -#define mp_hal_quiet_timing_enter() 0 -#define mp_hal_quiet_timing_exit(x) (void)x +#define mp_hal_quiet_timing_enter() raise_irq_pri(IRQ_PRI_QUIET_TIMING) +#define mp_hal_quiet_timing_exit(irq_state) restore_irq_pri(irq_state) #define mp_hal_delay_us_fast mp_hal_delay_us +extern ringbuf_t stdin_ringbuf; + /******************************************************************************/ // C-level pin HAL From ada0939c5f474e7204b6d3468bd27603daea676b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Jan 2024 23:41:00 +1100 Subject: [PATCH 0464/2098] alif/system_tick: Integrate soft timer. Signed-off-by: Damien George --- ports/alif/main.c | 2 ++ ports/alif/mphalport.c | 17 ++++++++++++++++ ports/alif/system_tick.c | 44 ++++++++++++++++++++++++++++++++++++++-- ports/alif/system_tick.h | 2 ++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/ports/alif/main.c b/ports/alif/main.c index 9293d74c0b8..b4e24343999 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -33,6 +33,7 @@ #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" #include "shared/runtime/pyexec.h" +#include "shared/runtime/softtimer.h" #include "shared/tinyusb/mp_usbd.h" #include "tusb.h" #include "mpuart.h" @@ -119,6 +120,7 @@ void _start(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + soft_timer_deinit(); gc_sweep_all(); mp_deinit(); } diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 8d0fa1b92ba..ba58923f2fe 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -31,11 +31,13 @@ #include "py/stream.h" #include "extmod/misc.h" #include "shared/runtime/interrupt_char.h" +#include "shared/runtime/softtimer.h" #include "shared/timeutils/timeutils.h" #include "shared/tinyusb/mp_usbd.h" #include "shared/tinyusb/mp_usbd_cdc.h" #include "tusb.h" #include "mpuart.h" +#include "pendsv.h" #include "system_tick.h" #ifndef MICROPY_HW_STDIN_BUFFER_LEN @@ -146,3 +148,18 @@ void mp_hal_delay_ms(mp_uint_t ms) { uint64_t mp_hal_time_ns(void) { return 0; } + +void system_tick_schedule_callback(void) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); +} + +uint32_t soft_timer_get_ms(void) { + return mp_hal_ticks_ms(); +} + +void soft_timer_schedule_at_ms(uint32_t ticks_ms) { + int32_t ms = soft_timer_ticks_diff(ticks_ms, mp_hal_ticks_ms()); + ms = MAX(0, ms); + ms = MIN(ms, 4000000); // ensure ms * 1000 doesn't overflow + system_tick_schedule_after_us(ms * 1000); +} diff --git a/ports/alif/system_tick.c b/ports/alif/system_tick.c index 95d5391c038..5cdf45cba01 100644 --- a/ports/alif/system_tick.c +++ b/ports/alif/system_tick.c @@ -59,9 +59,13 @@ void system_tick_init(void) { utimer_control_enable(UTIMER, UTIMER_CHANNEL); utimer_counter_start(UTIMER, UTIMER_CHANNEL); - // Set up the UTIMER compare interrupt, to be used later. + // Set up the UTIMER compare A interrupt, to be used by system_tick_wfe_with_timeout_us. system_tick_nvic_config(2); UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_COMPARE_CTRL_A |= COMPARE_CTRL_DRV_COMPARE_EN; + + // Set up the UTIMER compare B interrupt, to be used by soft-timer. + system_tick_nvic_config(4); + UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_COMPARE_CTRL_B |= COMPARE_CTRL_DRV_COMPARE_EN; } // COMPARE_A_BUF1 @@ -73,6 +77,17 @@ void UTIMER_IRQ90Handler(void) { } } +// COMPARE_B_BUF1 +void UTIMER_IRQ92Handler(void) { + uint32_t chan_interrupt = UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT; + if (chan_interrupt & CHAN_INTERRUPT_COMPARE_B_BUF1_MASK) { + utimer_clear_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + utimer_mask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + system_tick_schedule_callback(); + __SEV(); + } +} + // OVER_FLOW void UTIMER_IRQ95Handler(void) { uint32_t chan_interrupt = UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT; @@ -106,7 +121,7 @@ uint64_t system_tick_get_u64(void) { } void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { - // Maximum 10 second timeout, to not overflow signed 32-bit ticks when + // Maximum 10 second timeout, to not overflow 32-bit ticks when // system_core_clock_mhz==400. uint32_t timeout_ticks = MIN(timeout_us, 10000000) * system_core_clock_mhz; @@ -126,3 +141,28 @@ void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { // Disable the UTIMER compare interrupt. utimer_mask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_A_BUF1_MASK); } + +void system_tick_schedule_after_us(uint32_t ticks_us) { + // Disable the interrupt in case it's still active. + utimer_mask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + + // Maximum 10 second timeout, to not overflow 32-bit ticks when + // system_core_clock_mhz==400. + uint32_t timeout_ticks = MIN(ticks_us, 10000000) * system_core_clock_mhz; + + // Set up the UTIMER compare interrupt to fire after the given timeout. + uint32_t cntr = utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); + utimer_set_count(UTIMER, UTIMER_CHANNEL, UTIMER_COMPARE_B_BUF1, cntr + timeout_ticks); + utimer_clear_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + utimer_unmask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + + // Handle the case of short timeouts. + uint32_t cntr2 = utimer_get_count(UTIMER, UTIMER_CHANNEL, UTIMER_CNTR); + if ((uint32_t)(cntr2 - cntr) >= timeout_ticks) { + if (!(UTIMER->UTIMER_CHANNEL_CFG[UTIMER_CHANNEL].UTIMER_CHAN_INTERRUPT_MASK & CHAN_INTERRUPT_COMPARE_B_BUF1_MASK)) { + // Interrupt is still enabled, so disable it and manually call the callback. + utimer_mask_interrupt(UTIMER, UTIMER_CHANNEL, CHAN_INTERRUPT_COMPARE_B_BUF1_MASK); + system_tick_schedule_callback(); + } + } +} diff --git a/ports/alif/system_tick.h b/ports/alif/system_tick.h index 307c87a345f..2373f9421de 100644 --- a/ports/alif/system_tick.h +++ b/ports/alif/system_tick.h @@ -34,5 +34,7 @@ void system_tick_init(void); uint32_t system_tick_get_u32(void); uint64_t system_tick_get_u64(void); void system_tick_wfe_with_timeout_us(uint32_t timeout_us); +void system_tick_schedule_after_us(uint32_t ticks_us); +void system_tick_schedule_callback(void); #endif // MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H From 53b7c14836b00a432855439513721dcf07392041 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Jan 2024 23:41:10 +1100 Subject: [PATCH 0465/2098] alif/modmachine: Enable machine.Timer. Signed-off-by: Damien George --- ports/alif/modmachine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index c4102225dab..e19150e010d 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -29,6 +29,7 @@ #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \ static void mp_machine_idle(void) { mp_event_wait_indefinite(); From 4e62ade4421337944a12a53c05ddd4498ba3be8f Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 26 Jan 2024 12:00:18 +1100 Subject: [PATCH 0466/2098] alif/se_services: Add SE services interface. Includes services to get random numbers, reset SoC, get unique-id, dump SoC info, and CPU control services. Signed-off-by: Damien George Signed-off-by: iabdalkader --- ports/alif/Makefile | 12 ++ ports/alif/irq.h | 1 + ports/alif/main.c | 2 + ports/alif/se_services.c | 247 +++++++++++++++++++++++++++++++++++++++ ports/alif/se_services.h | 47 ++++++++ 5 files changed, 309 insertions(+) create mode 100644 ports/alif/se_services.c create mode 100644 ports/alif/se_services.h diff --git a/ports/alif/Makefile b/ports/alif/Makefile index cd02f4ac2d6..656cdc0e4cd 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -42,6 +42,7 @@ INC += -I$(BUILD) INC += -I$(BOARD_DIR) INC += -I$(CMSIS_DIR) INC += -I$(ALIF_DFP_REL_HERE)/drivers/include/ +INC += -I$(ALIF_DFP_REL_HERE)/se_services/include/ INC += -I$(ALIF_DFP_REL_HERE)/ospi_xip/source/ospi INC += -I$(ALIF_DFP_REL_HERE)/Device/common/config/ INC += -I$(ALIF_DFP_REL_HERE)/Device/common/include/ @@ -120,6 +121,7 @@ SRC_C = \ ospi_flash.c \ pendsv.c \ system_tick.c \ + se_services.c \ usbd.c \ $(wildcard $(BOARD_DIR)/*.c) @@ -174,13 +176,23 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/common/source/system_M55.c \ Device/common/source/system_utils.c \ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ + drivers/source/mhu_driver.c \ + drivers/source/mhu_receiver.c \ + drivers/source/mhu_sender.c \ drivers/source/pinconf.c \ drivers/source/uart.c \ drivers/source/utimer.c \ ospi_xip/source/ospi/ospi_drv.c \ + se_services/source/services_host_application.c \ + se_services/source/services_host_boot.c \ + se_services/source/services_host_clocks.c \ + se_services/source/services_host_cryptocell.c \ + se_services/source/services_host_handler.c \ + se_services/source/services_host_system.c \ ) $(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 +$(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) diff --git a/ports/alif/irq.h b/ports/alif/irq.h index 723f89f1d90..e2e0685c679 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -40,6 +40,7 @@ #define NVIC_PRIORITYGROUP_7 ((uint32_t)0x00000000U) #define IRQ_PRI_SYSTEM_TICK NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 0, 0) +#define IRQ_PRI_MHU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 0, 0) #define IRQ_PRI_QUIET_TIMING NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) diff --git a/ports/alif/main.c b/ports/alif/main.c index b4e24343999..2d3134176d3 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -39,6 +39,7 @@ #include "mpuart.h" #include "ospi_flash.h" #include "pendsv.h" +#include "se_services.h" #include "system_tick.h" extern uint8_t __StackTop, __StackLimit; @@ -60,6 +61,7 @@ void _start(void) { MICROPY_BOARD_STARTUP(); pendsv_init(); + se_services_init(); MICROPY_BOARD_EARLY_INIT(); diff --git a/ports/alif/se_services.c b/ports/alif/se_services.c new file mode 100644 index 00000000000..cbf66bfac6c --- /dev/null +++ b/ports/alif/se_services.c @@ -0,0 +1,247 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 + +#include "irq.h" +#include "se_services.h" + +#include "mhu.h" +#include "services_lib_bare_metal.h" +#include "services_lib_protocol.h" + +#include "py/mphal.h" + +// MHU indices. +#define MHU_M55_SE_MHU0 0 +#define MAX_MHU 1 + +// The following timeout is implemented in se_services_handle.c as a +// simple loop busy polling on a variable set from an IRQ. +#define TIMEOUT 10000000 + +typedef struct { + volatile unsigned int RST_CTRL; // 0x1A010318 + volatile unsigned int RST_STAT; // 0x1A01031C +} CPU_Type; + +// HE CPU register flags +#define RST_CTRL_CPUWAIT_MASK (1 << 0) +#define RST_CTRL_RST_REQ_MASK (1 << 1) +#define RST_STAT_RST_ACK_MASK (3 << 1) +#define HE_CPU ((CPU_Type *)0x1A010318) + +static const uint32_t mhu_sender_base_address_list[MAX_MHU] = { + MHU_SESS_S_TX_BASE, +}; + +static const uint32_t mhu_receiver_base_address_list[MAX_MHU] = { + MHU_SESS_S_RX_BASE, +}; + +// Must be aligned as a uint32_t. +static uint32_t packet_buffer[SERVICES_MAX_PACKET_BUFFER_SIZE / sizeof(uint32_t)]; + +static mhu_driver_out_t mhu_driver_out; +static uint32_t se_services_handle; + +void MHU_SESS_S_TX_IRQHandler(void) { + mhu_driver_out.sender_irq_handler(MHU_M55_SE_MHU0); +} + +void MHU_SESS_S_RX_IRQHandler(void) { + mhu_driver_out.receiver_irq_handler(MHU_M55_SE_MHU0); +} + +int dummy_printf(const char *fmt, ...) { + (void)fmt; + return 0; +} + +void se_services_init(void) { + // Initialize MHU. + mhu_driver_in_t mhu_driver_in; + mhu_driver_in.sender_base_address_list = (uint32_t *)mhu_sender_base_address_list; + mhu_driver_in.receiver_base_address_list = (uint32_t *)mhu_receiver_base_address_list; + mhu_driver_in.mhu_count = MAX_MHU; + mhu_driver_in.send_msg_acked_callback = SERVICES_send_msg_acked_callback; + mhu_driver_in.rx_msg_callback = SERVICES_rx_msg_callback; + mhu_driver_in.debug_print = NULL; // not currently used by MHU_driver_initialize + MHU_driver_initialize(&mhu_driver_in, &mhu_driver_out); + + // Initialize SE services. + services_lib_t services_init_params = { + .packet_buffer_address = (uint32_t)packet_buffer, + .fn_send_mhu_message = mhu_driver_out.send_message, + .fn_wait_ms = NULL, // not currently used by services_host_handler.c + .wait_timeout = TIMEOUT, + .fn_print_msg = dummy_printf, + }; + SERVICES_initialize(&services_init_params); + + // Create SE services channel for sending requests. + se_services_handle = SERVICES_register_channel(MHU_M55_SE_MHU0, 0); + + // Enable MHU interrupts. + NVIC_ClearPendingIRQ(MHU_SESS_S_RX_IRQ_IRQn); + NVIC_SetPriority(MHU_SESS_S_RX_IRQ_IRQn, IRQ_PRI_MHU); + NVIC_EnableIRQ(MHU_SESS_S_RX_IRQ_IRQn); + NVIC_ClearPendingIRQ(MHU_SESS_S_TX_IRQ_IRQn); + NVIC_SetPriority(MHU_SESS_S_TX_IRQ_IRQn, IRQ_PRI_MHU); + NVIC_EnableIRQ(MHU_SESS_S_TX_IRQ_IRQn); + + // Send heartbeat services requests until one succeeds. + SERVICES_synchronize_with_se(se_services_handle); +} + +void se_services_dump_device_data(void) { + uint32_t error_code; + + uint8_t revision[80]; + SERVICES_get_se_revision(se_services_handle, revision, &error_code); + + SERVICES_version_data_t data; + SERVICES_system_get_device_data(se_services_handle, &data, &error_code); + + printf("SE revision: %s\n", revision); + printf("ALIF_PN: %s\n", data.ALIF_PN); + printf("Raw device data:\n"); + for (int i = 0; i < sizeof(data); ++i) { + printf(" %02x", ((uint8_t *)&data)[i]); + if (i % 16 == 15) { + printf("\n"); + } + } + printf("\n"); +} + +void se_services_get_unique_id(uint8_t id[5]) { + uint32_t error_code; + SERVICES_version_data_t data; + SERVICES_system_get_device_data(se_services_handle, &data, &error_code); + // The MfgData has 5 bytes of valid data, at least on REV_B2. + memcpy(id, data.MfgData, 5); +} + +__attribute__((noreturn)) void se_services_reset_soc(void) { + SERVICES_boot_reset_soc(se_services_handle); + NVIC_SystemReset(); +} + +uint64_t se_services_rand64(void) { + // If the SE core is not ready then the return value can be + // SERVICES_REQ_NOT_ACKNOWLEDGE. So retry a few times. + for (int retry = 0; retry < 100; ++retry) { + uint64_t value; + int32_t error_code; + uint32_t ret = SERVICES_cryptocell_get_rnd(se_services_handle, sizeof(uint64_t), &value, &error_code); + if (ret == SERVICES_REQ_SUCCESS) { + return value; + } + } + + // No random number available. + return 0; +} + +uint32_t se_services_enable_clock(clock_enable_t clock, bool enable) { + uint32_t error_code; + SERVICES_clocks_enable_clock(se_services_handle, clock, enable, &error_code); + return error_code; +} + +uint32_t se_services_select_pll_source(pll_source_t source, pll_target_t target) { + uint32_t error_code; + SERVICES_clocks_select_pll_source(se_services_handle, source, target, &error_code); + return error_code; +} + +uint32_t se_services_get_run_profile(run_profile_t *profile) { + uint32_t error_code; + SERVICES_get_run_cfg(se_services_handle, profile, &error_code); + return error_code; +} + +uint32_t se_services_set_run_profile(run_profile_t *profile) { + uint32_t error_code; + SERVICES_set_run_cfg(se_services_handle, profile, &error_code); + return error_code; +} + +uint32_t se_services_get_off_profile(off_profile_t *profile) { + uint32_t error_code; + SERVICES_get_off_cfg(se_services_handle, profile, &error_code); + return error_code; +} + +uint32_t se_services_set_off_profile(off_profile_t *profile) { + uint32_t error_code; + SERVICES_set_off_cfg(se_services_handle, profile, &error_code); + return error_code; +} + +uint32_t se_services_boot_process_toc_entry(const uint8_t *image_id) { + uint32_t error_code; + SERVICES_boot_process_toc_entry(se_services_handle, image_id, &error_code); + return error_code; +} + +uint32_t se_services_boot_cpu(uint32_t cpu_id, uint32_t address) { + uint32_t error_code; + SERVICES_boot_cpu(se_services_handle, cpu_id, address, &error_code); + return error_code; +} + +uint32_t se_services_boot_reset_cpu(uint32_t cpu_id) { + uint32_t error_code; + if (HE_CPU->RST_CTRL & RST_CTRL_CPUWAIT_MASK) { + // CPU held in reset + return SERVICES_REQ_SUCCESS; + } + + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { + uint32_t ret = SERVICES_boot_reset_cpu(se_services_handle, cpu_id, &error_code); + if (ret != SERVICES_REQ_SUCCESS) { + return error_code; + } + + if ((HE_CPU->RST_STAT & RST_STAT_RST_ACK_MASK) == 0x4) { + return SERVICES_REQ_SUCCESS; + } + + if ((mp_hal_ticks_ms() - start) >= 100) { + return SERVICES_REQ_TIMEOUT; + } + } + return error_code; +} + +uint32_t se_services_boot_release_cpu(uint32_t cpu_id) { + uint32_t error_code; + SERVICES_boot_release_cpu(se_services_handle, cpu_id, &error_code); + return error_code; +} diff --git a/ports/alif/se_services.h b/ports/alif/se_services.h new file mode 100644 index 00000000000..b1079130c16 --- /dev/null +++ b/ports/alif/se_services.h @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_SE_SERVICES_H +#define MICROPY_INCLUDED_ALIF_SE_SERVICES_H + +#include "services_lib_api.h" + +void se_services_init(void); +void se_services_dump_device_data(void); +void se_services_get_unique_id(uint8_t id[5]); +__attribute__((noreturn)) void se_services_reset_soc(void); +uint64_t se_services_rand64(void); +uint32_t se_services_enable_clock(clock_enable_t clock, bool enable); +uint32_t se_services_select_pll_source(pll_source_t source, pll_target_t target); +uint32_t se_services_get_run_profile(run_profile_t *profile); +uint32_t se_services_set_run_profile(run_profile_t *profile); +uint32_t se_services_get_off_profile(off_profile_t *profile); +uint32_t se_services_set_off_profile(off_profile_t *profile); + +uint32_t se_services_boot_process_toc_entry(const uint8_t *image_id); +uint32_t se_services_boot_cpu(uint32_t cpu_id, uint32_t address); +uint32_t se_services_boot_reset_cpu(uint32_t cpu_id); +uint32_t se_services_boot_release_cpu(uint32_t cpu_id); +#endif // MICROPY_INCLUDED_ALIF_SE_SERVICES_H From c6cb082ed1716396f436ae4983131be2dcbf0447 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 26 Jan 2024 12:00:37 +1100 Subject: [PATCH 0467/2098] alif/mpconfigport: Enable os.urandom(). Uses the SE services to provide random numbers. Signed-off-by: Damien George --- ports/alif/modos.c | 49 +++++++++++++++++++++++++++++++++++++++ ports/alif/mpconfigport.h | 2 ++ 2 files changed, 51 insertions(+) create mode 100644 ports/alif/modos.c diff --git a/ports/alif/modos.c b/ports/alif/modos.c new file mode 100644 index 00000000000..9c8c84268ca --- /dev/null +++ b/ports/alif/modos.c @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "se_services.h" + +#if MICROPY_PY_OS_URANDOM +static mp_obj_t mp_os_urandom(mp_obj_t num) { + mp_int_t n = mp_obj_get_int(num); + vstr_t vstr; + vstr_init_len(&vstr, n); + uint64_t rnd = 0; + size_t rnd_bits = 0; + for (int i = 0; i < n; i++) { + if (rnd_bits == 0) { + rnd = se_services_rand64(); + rnd_bits = 64; + } + vstr.buf[i] = rnd; + rnd >>= 8; + rnd_bits -= 8; + } + return mp_obj_new_bytes_from_vstr(&vstr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(mp_os_urandom_obj, mp_os_urandom); +#endif diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 2d8e3b2a09d..74cfe8edaab 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -87,10 +87,12 @@ // Extended modules #define MICROPY_EPOCH_IS_1970 (1) +#define MICROPY_PY_OS_INCLUDEFILE "ports/alif/modos.c" #define MICROPY_PY_OS_DUPTERM (1) #define MICROPY_PY_OS_SEP (1) #define MICROPY_PY_OS_SYNC (1) #define MICROPY_PY_OS_UNAME (1) +#define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/alif/modmachine.c" From 64af93e74e9de36fd6106e6bea4923fc300afb49 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 26 Jan 2024 12:03:46 +1100 Subject: [PATCH 0468/2098] alif/mpconfigport: Enable MICROPY_PY_RANDOM_SEED_INIT_FUNC. Uses the SE services to provide a random seed. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 74cfe8edaab..b01eb1908ab 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -93,6 +93,7 @@ #define MICROPY_PY_OS_SYNC (1) #define MICROPY_PY_OS_UNAME (1) #define MICROPY_PY_OS_URANDOM (1) +#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (se_services_rand64()) #define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/alif/modmachine.c" @@ -155,3 +156,6 @@ extern void panic(const char *); #ifndef MICROPY_BOARD_ENTER_BOOTLOADER #define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) #endif + +// Needed for MICROPY_PY_RANDOM_SEED_INIT_FUNC. +uint64_t se_services_rand64(void); From 2a580b05ada2ce6c87d6e94196178c511ef7acdf Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 2 Feb 2024 17:50:15 +1100 Subject: [PATCH 0469/2098] alif/modalif: Add alif.info() function. Calls the SE services to print information about the SoC. Signed-off-by: Damien George --- ports/alif/modalif.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/alif/modalif.c b/ports/alif/modalif.c index 69b7e6c5c8e..5577b53eaca 100644 --- a/ports/alif/modalif.c +++ b/ports/alif/modalif.c @@ -27,10 +27,18 @@ #include "py/mphal.h" #include "py/runtime.h" #include "modalif.h" +#include "se_services.h" + +static mp_obj_t alif_info(void) { + se_services_dump_device_data(); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_0(alif_info_obj, alif_info); static const mp_rom_map_elem_t alif_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_alif) }, { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&alif_flash_type) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&alif_info_obj) }, #if MICROPY_HW_USB_MSC // Attribute to indicate USB MSC is enabled. { MP_ROM_QSTR(MP_QSTR_usb_msc), MP_ROM_TRUE }, From 2f85a19d7d7c342aefa6e3d628c1d398f53d3adf Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 2 Feb 2024 17:50:34 +1100 Subject: [PATCH 0470/2098] alif/modmachine: Implement machine.unique_id(), fix machine.reset(). They both use SE services. Signed-off-by: Damien George --- ports/alif/modmachine.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index e19150e010d..29a70305dd5 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -27,6 +27,8 @@ // This file is never compiled standalone, it's included directly from // extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE. +#include "se_services.h" + #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \ @@ -36,11 +38,13 @@ static void mp_machine_idle(void) { } static mp_obj_t mp_machine_unique_id(void) { - return mp_obj_new_bytes((const uint8_t *)"ABCD", 4); + uint8_t id[5]; + se_services_get_unique_id(id); + return mp_obj_new_bytes(id, sizeof(id)); } NORETURN static void mp_machine_reset(void) { - NVIC_SystemReset(); + se_services_reset_soc(); } NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { From 31d18c5885fd53c6aacc80428bdfb8b0bbdcd7cb Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 2 Feb 2024 17:51:03 +1100 Subject: [PATCH 0471/2098] alif/usbd: Implement proper USB serial number. Using SE services to get the SoC unique id. Signed-off-by: Damien George --- ports/alif/usbd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/alif/usbd.c b/ports/alif/usbd.c index 9df66106835..da16905d43c 100644 --- a/ports/alif/usbd.c +++ b/ports/alif/usbd.c @@ -30,10 +30,11 @@ #include "shared/tinyusb/mp_usbd.h" #include "tusb.h" +#include "se_services.h" void mp_usbd_port_get_serial_number(char *serial_buf) { - // TODO - uint8_t id[8] = "ABCDEFGH"; + uint8_t id[5]; + se_services_get_unique_id(id); MP_STATIC_ASSERT(sizeof(id) * 2 <= MICROPY_HW_USB_DESC_STR_MAX); mp_usbd_hex_str(serial_buf, id, sizeof(id)); } From 62beb541e76e7776cb402fabc10a6116da8f2dcf Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 Feb 2024 18:05:05 +1100 Subject: [PATCH 0472/2098] alif/machine_adc: Add basic ADC support. ADC12 information has been added to pin struct. Signed-off-by: Damien George --- ports/alif/Makefile | 1 + ports/alif/irq.h | 1 + ports/alif/machine_adc.c | 225 ++++++++++++++++++++++++++++++++++++ ports/alif/mcu/make-pins.py | 33 +++++- ports/alif/mpconfigport.h | 2 + ports/alif/mphalport.h | 2 + 6 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 ports/alif/machine_adc.c diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 656cdc0e4cd..068e34575a1 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -176,6 +176,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/common/source/system_M55.c \ Device/common/source/system_utils.c \ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ + drivers/source/adc.c \ drivers/source/mhu_driver.c \ drivers/source/mhu_receiver.c \ drivers/source/mhu_sender.c \ diff --git a/ports/alif/irq.h b/ports/alif/irq.h index e2e0685c679..a20ff7aebde 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -43,6 +43,7 @@ #define IRQ_PRI_MHU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 0, 0) #define IRQ_PRI_QUIET_TIMING NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) +#define IRQ_PRI_ADC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 3, 0) #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) diff --git a/ports/alif/machine_adc.c b/ports/alif/machine_adc.c new file mode 100644 index 00000000000..91eb95b5ba0 --- /dev/null +++ b/ports/alif/machine_adc.c @@ -0,0 +1,225 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. + +#include "py/mphal.h" + +#include "analog_config.h" +#include "adc.h" +#include "sys_ctrl_adc.h" + +#define ADC_CHANNEL_TEMP_SENSOR (6) +#define ADC_CHANNEL_INT_VREF (7) + +static const uint8_t adc_instance_table[4] = { + ADC_INSTANCE_ADC12_0, + ADC_INSTANCE_ADC12_1, + ADC_INSTANCE_ADC12_2, + ADC_INSTANCE_ADC24_0, +}; + +static ADC_Type *const adc_regs_table[4] = { + (ADC_Type *)ADC120_BASE, + (ADC_Type *)ADC121_BASE, + (ADC_Type *)ADC122_BASE, + (ADC_Type *)ADC24_BASE, +}; + +static bool adc_inited[4]; +static conv_info_t adc_conv_info_table[4]; + +static void adc_nvic_config(unsigned int irq) { + NVIC_ClearPendingIRQ(irq); + NVIC_SetPriority(irq, IRQ_PRI_ADC); + NVIC_EnableIRQ(irq); +} + +static void adc_init(uint8_t adc_periph) { + if (adc_inited[adc_periph]) { + return; + } + + ADC_Type *regs = adc_regs_table[adc_periph]; + uint8_t adc_instance = adc_instance_table[adc_periph]; + conv_info_t *conv_info = &adc_conv_info_table[adc_periph]; + + adc_set_clk_control(adc_instance, true); + enable_cmp_periph_clk(); + analog_config_vbat_reg2(); + analog_config_cmp_reg2(); + adc_set_differential_ctrl(adc_instance, false); + adc_set_comparator_ctrl(adc_instance, true, 2); + + if (adc_instance == ADC_INSTANCE_ADC24_0) { + enable_adc24(); + set_adc24_output_rate(0); + set_adc24_bias(0); + } else { + adc_set_sample_width(regs, 32); + } + + adc_set_clk_div(regs, 4); + adc_set_avg_sample(regs, 32); + adc_set_n_shift_bit(regs, 1, 1); + adc_set_single_ch_scan_mode(regs, conv_info); + adc_unmask_interrupt(regs); + + adc_nvic_config(ADC120_DONE0_IRQ_IRQn); + adc_nvic_config(ADC120_DONE1_IRQ_IRQn); + adc_nvic_config(ADC121_DONE0_IRQ_IRQn); + adc_nvic_config(ADC121_DONE1_IRQ_IRQn); + adc_nvic_config(ADC122_DONE0_IRQ_IRQn); + adc_nvic_config(ADC122_DONE1_IRQ_IRQn); +} + +static uint16_t adc_config_and_read_u16(unsigned int adc_periph, uint32_t channel) { + ADC_Type *adc_regs = adc_regs_table[adc_periph]; + conv_info_t *conv_info = &adc_conv_info_table[adc_periph]; + + conv_info->status = ADC_CONV_STAT_NONE; + conv_info->mode = ADC_CONV_MODE_SINGLE_SHOT; + + // Select channel and start conversion. + adc_init_channel_select(adc_regs, channel); + adc_set_single_ch_scan_mode(adc_regs, conv_info); + adc_enable_single_shot_conv(adc_regs); + + // Wait for conversion to complete. + while (!(conv_info->status & ADC_CONV_STAT_COMPLETE)) { + __WFE(); + } + + return conv_info->sampled_value; +} + +static void adc_done0_irq_handler_helper(unsigned int adc_periph) { + adc_done0_irq_handler(adc_regs_table[adc_periph], &adc_conv_info_table[adc_periph]); +} + +static void adc_done1_irq_handler_helper(unsigned int adc_periph) { + adc_done1_irq_handler(adc_regs_table[adc_periph], &adc_conv_info_table[adc_periph]); +} + +void ADC120_DONE0_IRQHandler(void) { + adc_done0_irq_handler_helper(0); + __SEV(); +} + +void ADC120_DONE1_IRQHandler(void) { + adc_done1_irq_handler_helper(0); + __SEV(); +} + +void ADC121_DONE0_IRQHandler(void) { + adc_done0_irq_handler_helper(1); + __SEV(); +} + +void ADC121_DONE1_IRQHandler(void) { + adc_done1_irq_handler_helper(1); + __SEV(); +} + +void ADC122_DONE0_IRQHandler(void) { + adc_done0_irq_handler_helper(2); + __SEV(); +} + +void ADC122_DONE1_IRQHandler(void) { + adc_done1_irq_handler_helper(2); + __SEV(); +} + +/******************************************************************************/ +// MicroPython bindings for machine.ADC + +#define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_CHANNEL_TEMP_SENSOR) }, \ + { MP_ROM_QSTR(MP_QSTR_CORE_VREF), MP_ROM_INT(ADC_CHANNEL_INT_VREF) }, \ + +typedef struct _machine_adc_obj_t { + mp_obj_base_t base; + uint8_t adc_periph; + uint8_t adc_channel; +} machine_adc_obj_t; + +static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->adc_periph, self->adc_channel); +} + +// ADC(id) +static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_t source = all_args[0]; + + uint8_t adc_periph; + uint8_t adc_channel; + const machine_pin_obj_t *pin = NULL; + + if (mp_obj_is_int(source)) { + // Get and validate channel number. + adc_channel = mp_obj_get_int(source); + if (adc_channel < ADC_CHANNEL_TEMP_SENSOR) { + adc_periph = 0; + } else if (adc_channel == ADC_CHANNEL_TEMP_SENSOR || adc_channel == ADC_CHANNEL_INT_VREF) { + adc_periph = 2; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); + } + } else { + // Get GPIO and check it has ADC capabilities. + pin = mp_hal_get_pin_obj(source); + if (pin->adc12_periph <= 2 && pin->adc12_channel <= 5) { + // Select the ADC12 peripheral and channel. + adc_periph = pin->adc12_periph; + adc_channel = pin->adc12_channel; + // Configure the pad for analog input. + pinconf_set(pin->port, pin->pin, PINMUX_ALTERNATE_FUNCTION_7, PADCTRL_READ_ENABLE); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("Pin doesn't have ADC capabilities")); + } + } + + // Initialise the ADC peripheral. + adc_init(adc_periph); + + // Create ADC object. + machine_adc_obj_t *o = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); + o->adc_periph = adc_periph; + o->adc_channel = adc_channel; + + return MP_OBJ_FROM_PTR(o); +} + +// read_u16() +static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { + return adc_config_and_read_u16(self->adc_periph, self->adc_channel); +} diff --git a/ports/alif/mcu/make-pins.py b/ports/alif/mcu/make-pins.py index b49db4cfe49..d4fbf5d8b8d 100755 --- a/ports/alif/mcu/make-pins.py +++ b/ports/alif/mcu/make-pins.py @@ -10,11 +10,34 @@ NUM_PORTS = 16 NUM_PINS_PER_PORT = 8 +# Maps pad name to (adc12_periph, adc12_channel). +ADC12_ANA_MAP = { + "P0_0": (0, 0), + "P0_1": (0, 1), + "P0_2": (0, 2), + "P0_3": (0, 3), + "P0_4": (0, 4), + "P0_5": (0, 5), + "P0_6": (1, 0), + "P0_7": (1, 1), + "P1_0": (1, 2), + "P1_1": (1, 3), + "P1_2": (1, 4), + "P1_3": (1, 5), + "P1_4": (2, 0), + "P1_5": (2, 1), + "P1_6": (2, 2), + "P1_7": (2, 3), + "P2_0": (2, 4), + "P2_1": (2, 5), +} + class AlifPin(boardgen.Pin): # Emit the struct which contains the pin instance. def definition(self): port, pin = self.name()[1:].split("_") + adc12_periph, adc12_channel = ADC12_ANA_MAP.get(self.name(), (3, 7)) base = "LPGPIO_BASE" if port == "15" else "GPIO{}_BASE".format(port) return ( "{{ " @@ -22,8 +45,16 @@ def definition(self): ".gpio = (GPIO_Type *){base}, " ".port = PORT_{port}, " ".pin = PIN_{pin}, " + ".adc12_periph = {adc12_periph}, " + ".adc12_channel = {adc12_channel}, " ".name = MP_QSTR_P{port}_{pin} " - "}}".format(port=port, pin=pin, base=base) + "}}".format( + port=port, + pin=pin, + base=base, + adc12_periph=adc12_periph, + adc12_channel=adc12_channel, + ) ) # Alif cpu names must be "Pn_m". diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index b01eb1908ab..95b5e3ca9fa 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -101,6 +101,8 @@ #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) +#define MICROPY_PY_MACHINE_ADC (1) +#define MICROPY_PY_MACHINE_ADC_INCLUDEFILE "ports/alif/machine_adc.c" #define MICROPY_PY_MACHINE_DHT_READINTO (1) #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 8098c2650df..7b054b671bc 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -85,6 +85,8 @@ typedef struct _machine_pin_obj_t { GPIO_Type *gpio; uint8_t port; uint8_t pin; + uint8_t adc12_periph : 2; + uint8_t adc12_channel : 3; qstr name; } machine_pin_obj_t; From b7df5aa86a6f150ab3282f0525fe9c3538ceaef7 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 10 Jul 2024 20:30:02 +0300 Subject: [PATCH 0473/2098] alif/mcu: Add ToC config for dual images. Note that 128K at the beginning of MRAM is reserved for future bootloaders. Signed-off-by: iabdalkader --- ports/alif/mcu/M55_DUAL_cfg.json | 26 ++++++++++++++++++++++++++ ports/alif/mcu/M55_HE_cfg.json | 17 +++++++++++++++++ ports/alif/mcu/M55_HP_cfg.json | 17 ++++++++++------- 3 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 ports/alif/mcu/M55_DUAL_cfg.json create mode 100644 ports/alif/mcu/M55_HE_cfg.json diff --git a/ports/alif/mcu/M55_DUAL_cfg.json b/ports/alif/mcu/M55_DUAL_cfg.json new file mode 100644 index 00000000000..81beef5f036 --- /dev/null +++ b/ports/alif/mcu/M55_DUAL_cfg.json @@ -0,0 +1,26 @@ +{ + "DEVICE": { + "disabled" : false, + "binary": "app-device-config.json", + "version" : "0.5.00", + "signed": false + }, + "HP_APP": { + "disabled" : false, + "binary": "M55_HP/firmware.bin", + "mramAddress": "0x80020000", + "version": "1.0.0", + "cpu_id": "M55_HP", + "flags": ["boot"], + "signed": false + }, + "HE_APP": { + "disabled" : false, + "binary": "M55_HE/firmware.bin", + "mramAddress": "0x80320000", + "version": "1.0.0", + "cpu_id": "M55_HE", + "flags": ["deferred"], + "signed": false + } +} diff --git a/ports/alif/mcu/M55_HE_cfg.json b/ports/alif/mcu/M55_HE_cfg.json new file mode 100644 index 00000000000..83898418043 --- /dev/null +++ b/ports/alif/mcu/M55_HE_cfg.json @@ -0,0 +1,17 @@ +{ + "DEVICE": { + "disabled" : false, + "binary": "app-device-config.json", + "version" : "0.5.00", + "signed": false + }, + "HE_APP": { + "disabled" : false, + "binary": "M55_HE/firmware.bin", + "mramAddress": "0x80320000", + "version": "1.0.0", + "cpu_id": "M55_HE", + "flags": ["boot"], + "signed": false + } +} diff --git a/ports/alif/mcu/M55_HP_cfg.json b/ports/alif/mcu/M55_HP_cfg.json index 228ddbf6c94..d6710f69c77 100644 --- a/ports/alif/mcu/M55_HP_cfg.json +++ b/ports/alif/mcu/M55_HP_cfg.json @@ -1,14 +1,17 @@ { - "USER_APP": { - "binary": "firmware.bin", - "mramAddress": "0x80000000", + "DEVICE": { + "disabled" : false, + "binary": "app-device-config.json", + "version" : "0.5.00", + "signed": false + }, + "HP_APP": { + "disabled" : false, + "binary": "M55_HP/firmware.bin", + "mramAddress": "0x80020000", "version": "1.0.0", "cpu_id": "M55_HP", "flags": ["boot"], "signed": false - }, - "DEVICE": { - "binary": "app-device-config.json", - "version" : "0.5.00" } } From 8f82089bd051eb154e7c55951dd29220bde94611 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 10 Jul 2024 20:27:18 +0300 Subject: [PATCH 0474/2098] alif: Support building the port for HE or HP or both cores. With this new Makefile you can build the following: make BOARD=MY_BOARD MCU_CORE=M55_HP # build HP firmware/ToC. make BOARD=MY_BOARD MCU_CORE=M55_HE # build HE firmware/ToC. make BOARD=MY_BOARD MCU_CORE=M55_DUAL # build HE+HP firmware + ToC. Signed-off-by: iabdalkader --- ports/alif/Makefile | 269 +++++++---------------------------- ports/alif/alif.mk | 257 +++++++++++++++++++++++++++++++++ ports/alif/mcu/ensemble.ld.S | 43 ++++-- 3 files changed, 343 insertions(+), 226 deletions(-) create mode 100644 ports/alif/alif.mk diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 068e34575a1..5cc334a7cf9 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -1,242 +1,96 @@ -################################################################################ -# Initial setup of Makefile environment - BOARD ?= ALIF_ENSEMBLE BOARD_DIR ?= boards/$(BOARD) BUILD ?= build-$(BOARD) - -ifeq ($(wildcard $(BOARD_DIR)/.),) -$(error Invalid BOARD specified: $(BOARD_DIR)) -endif - -include ../../py/mkenv.mk -include mpconfigport.mk -include $(BOARD_DIR)/mpconfigboard.mk - -# qstr definitions (must come before including py.mk) -QSTR_DEFS += qstrdefsport.h - -# include py core make definitions -include $(TOP)/py/py.mk -include $(TOP)/extmod/extmod.mk - -################################################################################ -# Project specific settings and compiler/linker flags - -CROSS_COMPILE ?= arm-none-eabi- +MCU_CORE ?= M55_HP GIT_SUBMODULES += lib/tinyusb lib/alif_ensemble-cmsis-dfp lib/alif-security-toolkit PORT ?= /dev/ttyACM0 ALIF_TOOLS ?= ../../lib/alif-security-toolkit/toolkit -ALIF_DFP_REL_TOP ?= lib/alif_ensemble-cmsis-dfp -ALIF_DFP_REL_HERE ?= $(TOP)/lib/alif_ensemble-cmsis-dfp -CMSIS_DIR ?= $(TOP)/lib/cmsis/inc - -MCU_CORE ?= M55_HP -ALIF_CONFIG ?= mcu/$(MCU_CORE)_cfg.json -LD_FILE ?= mcu/ensemble.ld.S - -INC += -I. -INC += -I$(TOP) -INC += -I$(BUILD) -INC += -I$(BOARD_DIR) -INC += -I$(CMSIS_DIR) -INC += -I$(ALIF_DFP_REL_HERE)/drivers/include/ -INC += -I$(ALIF_DFP_REL_HERE)/se_services/include/ -INC += -I$(ALIF_DFP_REL_HERE)/ospi_xip/source/ospi -INC += -I$(ALIF_DFP_REL_HERE)/Device/common/config/ -INC += -I$(ALIF_DFP_REL_HERE)/Device/common/include/ -INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/config/ -INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/include/ -INC += -I$(ALIF_DFP_REL_HERE)/Device/$(MCU_SERIES)/$(MCU_VARIANT)/ -INC += -I$(TOP)/lib/tinyusb/src -INC += -Itinyusb_port - -GEN_PIN_MKPINS = mcu/make-pins.py -GEN_PIN_PREFIX = mcu/pins_prefix.c -GEN_PINS_BOARD_CSV = $(BOARD_DIR)/pins.csv -GEN_PINS_SRC = $(BUILD)/pins_board.c -GEN_PINS_HDR = $(HEADER_BUILD)/pins_board.h - -CFLAGS_FPU += -mfloat-abi=hard -mfpu=fpv5-d16 -CFLAGS_CORTEX_M55 += -mthumb -mcpu=cortex-m55 -mtune=cortex-m55 $(CFLAGS_FPU) - -CFLAGS += $(INC) -Wall -Werror -std=c99 $(CFLAGS_CORTEX_M55) -nostdlib -CFLAGS += -Wdouble-promotion -Wfloat-conversion -CFLAGS += -fdata-sections -ffunction-sections -CFLAGS += -D$(MCU_CORE) -DCORE_$(MCU_CORE) -DALIF_CMSIS_H="\"$(MCU_CORE).h\"" - -ifeq ($(MICROPY_FLOAT_IMPL),float) -CFLAGS += -fsingle-precision-constant -CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT -else -CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE -endif - -AFLAGS = -mthumb -march=armv8.1-m.main $(CFLAGS_FPU) - -LDFLAGS += -nostdlib -LDFLAGS += -T$(BUILD)/ensemble.ld -Map=$@.map --cref --gc-sections -LDFLAGS += --wrap=dcd_event_handler -# Tune for Debugging or Optimization -ifeq ($(DEBUG), 1) -CFLAGS += -Og -ggdb3 -# Disable text compression in debug builds -MICROPY_ROM_TEXT_COMPRESSION = 0 -else -CFLAGS += -O2 -DNDEBUG -endif - -LIBS += "$(shell $(CC) $(CFLAGS) -print-libgcc-file-name)" - -JLINK_CMD = '\ +JLINK_CMD_PREFIX = \ ExitOnError 1\n\ Device $(JLINK_DEV)\n\ SelectInterface SWD\n\ Speed auto\n\ Connect\n\ Reset\n\ -ShowHWStatus\n\ -LoadFile "$(BUILD)/firmware_toc.bin",0x8057f1c0\n\ -LoadFile "$(BUILD)/firmware.bin",0x80000000\n\ -Reset\n\ -Exit' - -################################################################################ -# Source files and libraries +ShowHWStatus\n -SRC_O += \ - shared/runtime/gchelper_thumb2.o +JLINK_CMD_SUFFIX = \ +Reset\n\ +Exit -SRC_C = \ - alif_flash.c \ - fatfs_port.c \ - machine_pin.c \ - main.c \ - modalif.c \ - mphalport.c \ - mpuart.c \ - msc_disk.c \ - ospi_flash.c \ - pendsv.c \ - system_tick.c \ - se_services.c \ - usbd.c \ - $(wildcard $(BOARD_DIR)/*.c) +ifeq ($(MCU_CORE),M55_HP) -ifeq ($(MICROPY_FLOAT_IMPL),float) -LIBM_SRC_C += $(SRC_LIB_LIBM_C) -LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) -$(BUILD)/lib/libm/%.o: CFLAGS += -Wno-maybe-uninitialized -else -LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_C) -LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_SQRT_HW_C) -$(BUILD)/lib/libm_dbl/%.o: CFLAGS += -Wno-maybe-uninitialized -endif +ALIF_TOC_CONFIG = mcu/M55_HP_cfg.json +ALIF_TOC_APPS = $(BUILD)/M55_HP/firmware.bin +JLINK_CMD = '\ +$(JLINK_CMD_PREFIX)\ +LoadFile "$(BUILD)/M55_HP/firmware.bin",0x80020000\n\ +$(JLINK_CMD_SUFFIX)' -SHARED_SRC_C += $(addprefix shared/,\ - libc/string0.c \ - netutils/dhcpserver.c \ - netutils/netutils.c \ - netutils/trace.c \ - readline/readline.c \ - runtime/gchelper_native.c \ - runtime/interrupt_char.c \ - runtime/mpirq.c \ - runtime/pyexec.c \ - runtime/softtimer.c \ - runtime/stdout_helpers.c \ - runtime/sys_stdio_mphal.c \ - timeutils/timeutils.c \ - tinyusb/mp_usbd.c \ - tinyusb/mp_usbd_cdc.c \ - tinyusb/mp_usbd_descriptor.c \ - ) +else ifeq ($(MCU_CORE),M55_HE) -DRIVERS_SRC_C += $(addprefix drivers/,\ - bus/softspi.c \ - bus/softqspi.c \ - memory/spiflash.c \ - dht/dht.c \ - ) +ALIF_TOC_CONFIG = mcu/M55_HE_cfg.json +ALIF_TOC_APPS = $(BUILD)/M55_HE/firmware.bin +JLINK_CMD = '\ +$(JLINK_CMD_PREFIX)\ +LoadFile "$(BUILD)/M55_HE/firmware.bin",0x80320000\n\ +$(JLINK_CMD_SUFFIX)' -TINYUSB_SRC_C += \ - lib/tinyusb/src/tusb.c \ - lib/tinyusb/src/class/cdc/cdc_device.c \ - lib/tinyusb/src/class/msc/msc_device.c \ - lib/tinyusb/src/common/tusb_fifo.c \ - lib/tinyusb/src/device/usbd.c \ - lib/tinyusb/src/device/usbd_control.c \ - tinyusb_port/tusb_alif_dcd.c \ +else ifeq ($(MCU_CORE),M55_DUAL) -ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ - Device/common/source/clk.c \ - Device/common/source/mpu_M55.c \ - Device/common/source/system_M55.c \ - Device/common/source/system_utils.c \ - Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ - drivers/source/adc.c \ - drivers/source/mhu_driver.c \ - drivers/source/mhu_receiver.c \ - drivers/source/mhu_sender.c \ - drivers/source/pinconf.c \ - drivers/source/uart.c \ - drivers/source/utimer.c \ - ospi_xip/source/ospi/ospi_drv.c \ - se_services/source/services_host_application.c \ - se_services/source/services_host_boot.c \ - se_services/source/services_host_clocks.c \ - se_services/source/services_host_cryptocell.c \ - se_services/source/services_host_handler.c \ - se_services/source/services_host_system.c \ - ) +ALIF_TOC_CONFIG = mcu/M55_DUAL_cfg.json +ALIF_TOC_APPS = $(BUILD)/M55_HP/firmware.bin $(BUILD)/M55_HE/firmware.bin +JLINK_CMD = '\ +$(JLINK_CMD_PREFIX)\ +LoadFile "$(BUILD)/M55_HP/firmware.bin",0x80020000\n\ +LoadFile "$(BUILD)/M55_HE/firmware.bin",0x80320000\n\ +$(JLINK_CMD_SUFFIX)' -$(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 -$(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation +else +$(error Invalid MCU core specified)) +endif -# List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) +include ../../py/mkenv.mk +include mpconfigport.mk +include $(BOARD_DIR)/mpconfigboard.mk -OBJ += $(PY_O) -OBJ += $(addprefix $(BUILD)/, $(SRC_O)) -OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(ALIF_SRC_C:.c=.o)) -OBJ += $(GEN_PINS_SRC:.c=.o) +# include py core make definitions +include $(TOP)/py/py.mk +include $(TOP)/extmod/extmod.mk ################################################################################ # Main targets -.DELETE_ON_ERROR: - .PHONY: all -all: $(BUILD)/firmware_toc.bin +all: $(BUILD)/firmware.toc.bin + +# Force make commands to run the targets every time +# regardless of whether firmware.toc.bin already exists +# to detect changes in the source files and rebuild. +.PHONY: $(BUILD)/M55_HE/firmware.bin +.PHONY: $(BUILD)/M55_HP/firmware.bin -$(BUILD)/ensemble.ld: $(LD_FILE) - $(ECHO) "Preprocess linker script $@" - $(Q)$(CPP) -P -E $(CFLAGS) $^ > $@ +$(BUILD): + $(MKDIR) -p $@ -$(BUILD)/firmware.elf: $(OBJ) $(BUILD)/ensemble.ld - $(ECHO) "Link $@" - $(Q)$(LD) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) - $(Q)$(SIZE) $@ +$(BUILD)/M55_HP/firmware.bin: + make -f alif.mk MCU_CORE=M55_HP -$(BUILD)/firmware.bin: $(BUILD)/firmware.elf - $(Q)$(OBJCOPY) -Obinary $^ $(BUILD)/firmware.bin +$(BUILD)/M55_HE/firmware.bin: + make -f alif.mk MCU_CORE=M55_HE -$(BUILD)/firmware_toc.bin: $(BUILD)/firmware.bin +$(BUILD)/firmware.toc.bin: $(ALIF_TOC_APPS) $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ - --filename $(abspath $(BUILD)/$(ALIF_TOC_CONFIG)) \ + --filename $(abspath $(ALIF_TOC_CONFIG)) \ + --config-dir $(BOARD_DIR) \ --output-dir $(BUILD) \ --firmware-dir $(BUILD) \ --output $@ .PHONY: deploy -deploy: $(BUILD)/firmware_toc.bin +deploy: $(BUILD)/firmware.toc.bin $(ECHO) "Writing $< to the board" $(Q)python $(ALIF_TOOLS)/app-write-mram.py \ --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ @@ -245,7 +99,7 @@ deploy: $(BUILD)/firmware_toc.bin --images file:$(BUILD)/application_package.ds .PHONY: deploy-jlink -deploy-jlink: $(BUILD)/firmware_toc.bin +deploy-jlink: $(ALIF_TOC_APPS) $(Q)echo -e $(JLINK_CMD) | $(JLINK_EXE) .PHONY: maintenance @@ -260,17 +114,4 @@ update-system-package: --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ --port $(PORT) -################################################################################ -# Remaining make rules - -# Use a pattern rule here so that make will only call make-pins.py once to make -# both pins_board.c and pins_board.h -$(BUILD)/%_board.c $(HEADER_BUILD)/%_board.h: $(BOARD_DIR)/%.csv $(GEN_PIN_MKPINS) $(GEN_PIN_PREFIX) | $(HEADER_BUILD) - $(ECHO) "GEN $@" - $(Q)$(PYTHON) $(GEN_PIN_MKPINS) \ - --board-csv $(GEN_PINS_BOARD_CSV) \ - --prefix $(GEN_PIN_PREFIX) \ - --output-source $(GEN_PINS_SRC) \ - --output-header $(GEN_PINS_HDR) - include $(TOP)/py/mkrules.mk diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk new file mode 100644 index 00000000000..ad69499eccf --- /dev/null +++ b/ports/alif/alif.mk @@ -0,0 +1,257 @@ +################################################################################ +# Initial setup of Makefile environment + +BOARD ?= ALIF_ENSEMBLE +BOARD_DIR ?= boards/$(BOARD) +BUILD ?= build-$(BOARD)/$(MCU_CORE) + +ifeq ($(wildcard $(BOARD_DIR)/.),) +$(error Invalid BOARD specified: $(BOARD_DIR)) +endif + +include ../../py/mkenv.mk +include mpconfigport.mk +include $(BOARD_DIR)/mpconfigboard.mk + +# qstr definitions (must come before including py.mk) +QSTR_DEFS += qstrdefsport.h + +# include py core make definitions +include $(TOP)/py/py.mk +include $(TOP)/extmod/extmod.mk + +################################################################################ +# Project specific settings and compiler/linker flags + +CROSS_COMPILE ?= arm-none-eabi- +ALIF_DFP_REL_TOP ?= lib/alif_ensemble-cmsis-dfp +ALIF_DFP_REL_HERE ?= $(TOP)/lib/alif_ensemble-cmsis-dfp +CMSIS_DIR ?= $(TOP)/lib/cmsis/inc + +MCU_CORE ?= M55_HP +ALIF_CONFIG ?= mcu/$(MCU_CORE)_cfg.json +LD_FILE ?= mcu/ensemble.ld.S + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) +INC += -I$(BOARD_DIR) +INC += -I$(CMSIS_DIR) +INC += -I$(ALIF_DFP_REL_HERE)/drivers/include/ +INC += -I$(ALIF_DFP_REL_HERE)/se_services/include +INC += -I$(ALIF_DFP_REL_HERE)/ospi_xip/source/ospi +INC += -I$(ALIF_DFP_REL_HERE)/Device/common/config/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/common/include/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/config/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/include/ +INC += -I$(ALIF_DFP_REL_HERE)/Device/$(MCU_SERIES)/$(MCU_VARIANT)/ +INC += -I$(TOP)/lib/tinyusb/src +INC += -Itinyusb_port + +GEN_PIN_MKPINS = mcu/make-pins.py +GEN_PIN_PREFIX = mcu/pins_prefix.c +GEN_PINS_BOARD_CSV = $(BOARD_DIR)/pins.csv +GEN_PINS_SRC = $(BUILD)/pins_board.c +GEN_PINS_HDR = $(HEADER_BUILD)/pins_board.h + +CFLAGS_FPU += -mfloat-abi=hard -mfpu=fpv5-d16 + +CFLAGS += $(INC) \ + -std=c99 \ + -Wall \ + -Werror \ + -Wdouble-promotion \ + -Wfloat-conversion \ + -mthumb \ + -mcpu=cortex-m55 \ + -mtune=cortex-m55 \ + $(CFLAGS_FPU) \ + -march=armv8.1-m.main+fp+mve.fp \ + -nostdlib \ + -fdata-sections \ + -ffunction-sections \ + -D$(MCU_CORE)=1 \ + -DCORE_$(MCU_CORE) \ + -DALIF_CMSIS_H="\"$(MCU_CORE).h\"" + +ifeq ($(MICROPY_FLOAT_IMPL),float) +CFLAGS += -fsingle-precision-constant +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT +else +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE +endif + +# Tune for Debugging or Optimization +ifeq ($(DEBUG), 1) +CFLAGS += -Og -ggdb3 +# Disable text compression in debug builds +MICROPY_ROM_TEXT_COMPRESSION = 0 +else +CFLAGS += -O2 -DNDEBUG +endif + +CFLAGS += $(CFLAGS_EXTRA) + +AFLAGS = -mthumb -march=armv8.1-m.main+fp+mve.fp $(CFLAGS_FPU) + +LDFLAGS += -nostdlib \ + -T$(BUILD)/ensemble.ld \ + -Map=$@.map \ + --cref \ + --gc-sections \ + --print-memory-usage +ifeq ($(MCU_CORE),M55_HP) +LDFLAGS += --wrap=dcd_event_handler +endif + +LIBS += "$(shell $(CC) $(CFLAGS) -print-libgcc-file-name)" + +################################################################################ +# Source files and libraries + +SRC_O += \ + shared/runtime/gchelper_thumb2.o + +SRC_C = \ + alif_flash.c \ + fatfs_port.c \ + machine_pin.c \ + main.c \ + modalif.c \ + mphalport.c \ + mpuart.c \ + msc_disk.c \ + ospi_flash.c \ + pendsv.c \ + system_tick.c \ + se_services.c \ + usbd.c \ + $(wildcard $(BOARD_DIR)/*.c) + +ifeq ($(MICROPY_FLOAT_IMPL),float) +LIBM_SRC_C += $(SRC_LIB_LIBM_C) +LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) +$(BUILD)/lib/libm/%.o: CFLAGS += -Wno-maybe-uninitialized +else +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_C) +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_SQRT_HW_C) +$(BUILD)/lib/libm_dbl/%.o: CFLAGS += -Wno-maybe-uninitialized +endif + +SHARED_SRC_C += $(addprefix shared/,\ + libc/string0.c \ + netutils/dhcpserver.c \ + netutils/netutils.c \ + netutils/trace.c \ + readline/readline.c \ + runtime/gchelper_native.c \ + runtime/interrupt_char.c \ + runtime/mpirq.c \ + runtime/pyexec.c \ + runtime/softtimer.c \ + runtime/stdout_helpers.c \ + runtime/sys_stdio_mphal.c \ + timeutils/timeutils.c \ + tinyusb/mp_usbd.c \ + tinyusb/mp_usbd_cdc.c \ + tinyusb/mp_usbd_descriptor.c \ + ) + +DRIVERS_SRC_C += $(addprefix drivers/,\ + bus/softspi.c \ + bus/softqspi.c \ + memory/spiflash.c \ + dht/dht.c \ + ) + +TINYUSB_SRC_C += \ + lib/tinyusb/src/tusb.c \ + lib/tinyusb/src/class/cdc/cdc_device.c \ + lib/tinyusb/src/class/msc/msc_device.c \ + lib/tinyusb/src/common/tusb_fifo.c \ + lib/tinyusb/src/device/usbd.c \ + lib/tinyusb/src/device/usbd_control.c \ + tinyusb_port/tusb_alif_dcd.c \ + +ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ + Device/common/source/clk.c \ + Device/common/source/mpu_M55.c \ + Device/common/source/system_M55.c \ + Device/common/source/system_utils.c \ + Device/common/source/tcm_partition.c \ + Device/common/source/tgu_M55.c \ + Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ + drivers/source/adc.c \ + drivers/source/mhu_driver.c \ + drivers/source/mhu_receiver.c \ + drivers/source/mhu_sender.c \ + drivers/source/pinconf.c \ + drivers/source/uart.c \ + drivers/source/utimer.c \ + ospi_xip/source/ospi/ospi_drv.c \ + se_services/source/services_host_application.c \ + se_services/source/services_host_boot.c \ + se_services/source/services_host_clocks.c \ + se_services/source/services_host_cryptocell.c \ + se_services/source/services_host_handler.c \ + se_services/source/services_host_system.c \ + se_services/source/services_host_power.c \ + se_services/source/services_host_maintenance.c \ + ) + +$(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 +$(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation +$(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_system.o: CFLAGS += -Wno-maybe-uninitialized + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) + +OBJ += $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_O)) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(ALIF_SRC_C:.c=.o)) +OBJ += $(GEN_PINS_SRC:.c=.o) + +################################################################################ +# Main targets + +.PHONY: all erase deploy gdb objdump release + +.DELETE_ON_ERROR: + +obj: $(OBJ) +all: $(BUILD)/firmware.bin + +$(BUILD): + $(MKDIR) -p $@ + +$(BUILD)/ensemble.ld: $(LD_FILE) + $(ECHO) "Preprocess linker script $@" + $(Q)$(CPP) -P -E $(CFLAGS) $^ > $@ + +$(BUILD)/firmware.elf: $(OBJ) $(BUILD)/ensemble.ld + $(ECHO) "Link $@" + $(Q)$(LD) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) + $(Q)$(SIZE) $@ + +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf + $(Q)$(OBJCOPY) -Obinary $^ $(BUILD)/firmware.bin + +################################################################################ +# Remaining make rules + +# Use a pattern rule here so that make will only call make-pins.py once to make +# both pins_board.c and pins_board.h +$(BUILD)/%_board.c $(HEADER_BUILD)/%_board.h: $(BOARD_DIR)/%.csv $(GEN_PIN_MKPINS) $(GEN_PIN_PREFIX) | $(HEADER_BUILD) + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(GEN_PIN_MKPINS) \ + --board-csv $(GEN_PINS_BOARD_CSV) \ + --prefix $(GEN_PIN_PREFIX) \ + --output-source $(GEN_PINS_SRC) \ + --output-header $(GEN_PINS_HDR) + +include $(TOP)/py/mkrules.mk diff --git a/ports/alif/mcu/ensemble.ld.S b/ports/alif/mcu/ensemble.ld.S index 198641ba3dc..8129a749eb2 100644 --- a/ports/alif/mcu/ensemble.ld.S +++ b/ports/alif/mcu/ensemble.ld.S @@ -24,25 +24,44 @@ * THE SOFTWARE. */ +#ifdef CORE_M55_HP +__DTCM_SIZE = 1024K; +#else +__DTCM_SIZE = 256K; +#endif + // Entry Point ENTRY(Reset_Handler) MEMORY { - ROM (rx) : ORIGIN = 0x80000000, LENGTH = 0x0057F000 - ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - #ifdef CORE_M55_HP - DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00100000 - #else - DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000 - #endif - SRAM0 (rwx) : ORIGIN = 0x02000000, LENGTH = 0x00400000 - SRAM1 (rwx) : ORIGIN = 0x08000000, LENGTH = 0x00280000 + MRAM_BL (rx) : ORIGIN = 0x80000000, LENGTH = 128K + MRAM_HP (rx) : ORIGIN = 0x80020000, LENGTH = 3072K + MRAM_HE (rx) : ORIGIN = 0x80320000, LENGTH = 1400K + MRAM_FS (rx) : ORIGIN = 0x8047e000, LENGTH = 1024K + MRAM_TOC (rx): ORIGIN = 0x8057e000, LENGTH = 8K + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 256K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = __DTCM_SIZE + SRAM0 (rwx) : ORIGIN = 0x02000000, LENGTH = 4096K + SRAM1 (rwx) : ORIGIN = 0x08000000, LENGTH = 2560K + SRAM6_A (rw) : ORIGIN = 0x62000000, LENGTH = 1024K + SRAM6_B (rw) : ORIGIN = 0x62400000, LENGTH = 1024K + SRAM7 (rw) : ORIGIN = 0x63000000, LENGTH = 512K + SRAM8 (rw) : ORIGIN = 0x63200000, LENGTH = 2048K + SRAM9_A (rw) : ORIGIN = 0x60000000, LENGTH = 256K + SRAM9_B (rw) : ORIGIN = 0x60040000, LENGTH = 512K } -__STACK_SIZE = 0x00004000; -__HEAP_SIZE = 0x00004000; -__MP_HEAP_SIZE = 0x00040000; +#ifdef CORE_M55_HP +REGION_ALIAS("ROM", MRAM_HP); +__MP_HEAP_SIZE = 256K; +#else +REGION_ALIAS("ROM", MRAM_HE); +__MP_HEAP_SIZE = 128K; +#endif + +__STACK_SIZE = 16K; +__HEAP_SIZE = 16K; SECTIONS { From 6b4d46569bf87673521645b3340e25a3336c8d40 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 17 Jul 2024 17:43:38 +0300 Subject: [PATCH 0475/2098] alif: Support running the port on the HE core. The same MicroPython firmware is built for the HE but with slightly different options, for example no USB. Signed-off-by: iabdalkader --- ports/alif/alif_flash.c | 2 ++ ports/alif/boards/manifest.py | 2 +- ports/alif/main.c | 4 ++-- ports/alif/modalif.c | 2 ++ ports/alif/modules/he/_boot.py | 21 +++++++++++++++++++++ ports/alif/modules/{ => hp}/_boot.py | 0 ports/alif/mpconfigport.h | 20 +++++++++++++++----- ports/alif/mpconfigport.mk | 1 + ports/alif/mphalport.c | 2 ++ ports/alif/ospi_flash.c | 4 +++- ports/alif/tinyusb_port/tusb_config.h | 2 +- 11 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 ports/alif/modules/he/_boot.py rename ports/alif/modules/{ => hp}/_boot.py (100%) diff --git a/ports/alif/alif_flash.c b/ports/alif/alif_flash.c index e3b2125800e..f2ea5ac85a8 100644 --- a/ports/alif/alif_flash.c +++ b/ports/alif/alif_flash.c @@ -30,6 +30,7 @@ #include "modalif.h" #include "ospi_flash.h" +#if MICROPY_HW_ENABLE_OSPI typedef struct _alif_flash_obj_t { mp_obj_base_t base; uint32_t flash_base_addr; @@ -158,3 +159,4 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, alif_flash_make_new, locals_dict, &alif_flash_locals_dict ); +#endif // MICROPY_HW_ENABLE_OSPI diff --git a/ports/alif/boards/manifest.py b/ports/alif/boards/manifest.py index 148f1d6a6eb..1dc9e179acc 100644 --- a/ports/alif/boards/manifest.py +++ b/ports/alif/boards/manifest.py @@ -1,4 +1,4 @@ -freeze("$(PORT_DIR)/modules") +freeze("$(PORT_DIR)/modules/$(MCU_CORE)") include("$(MPY_DIR)/extmod/asyncio") require("dht") require("neopixel") diff --git a/ports/alif/main.c b/ports/alif/main.c index 2d3134176d3..975ee7ed2d3 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -68,11 +68,11 @@ void _start(void) { #if MICROPY_HW_ENABLE_UART_REPL mp_uart_init(); #endif - + #if MICROPY_HW_ENABLE_OSPI if (ospi_flash_init() != 0) { MICROPY_BOARD_FATAL_ERROR("ospi_init failed"); } - + #endif #if MICROPY_HW_ENABLE_USBDEV NVIC_ClearPendingIRQ(USB_IRQ_IRQn); NVIC_SetPriority(USB_IRQ_IRQn, IRQ_PRI_USB); diff --git a/ports/alif/modalif.c b/ports/alif/modalif.c index 5577b53eaca..1c3c34394cd 100644 --- a/ports/alif/modalif.c +++ b/ports/alif/modalif.c @@ -37,7 +37,9 @@ static MP_DEFINE_CONST_FUN_OBJ_0(alif_info_obj, alif_info); static const mp_rom_map_elem_t alif_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_alif) }, + #if MICROPY_HW_ENABLE_OSPI { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&alif_flash_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&alif_info_obj) }, #if MICROPY_HW_USB_MSC // Attribute to indicate USB MSC is enabled. diff --git a/ports/alif/modules/he/_boot.py b/ports/alif/modules/he/_boot.py new file mode 100644 index 00000000000..bc1320f1906 --- /dev/null +++ b/ports/alif/modules/he/_boot.py @@ -0,0 +1,21 @@ +import openamp +import time +from machine import Pin + + +def ept_recv_callback(src_addr, data): + print("Received message on endpoint", data) + + +# Create a new RPMsg endpoint to communicate with main core. +ept = openamp.Endpoint("vuart-channel", callback=ept_recv_callback) + +pin = Pin("LED_BLUE", Pin.OUT) + +count = 0 +while True: + if ept.is_ready(): + ept.send("Hello from HE %d" % count, timeout=1000) + count += 1 + time.sleep_ms(100) + pin(not pin()) diff --git a/ports/alif/modules/_boot.py b/ports/alif/modules/hp/_boot.py similarity index 100% rename from ports/alif/modules/_boot.py rename to ports/alif/modules/hp/_boot.py diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 95b5e3ca9fa..df33b922024 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -36,13 +36,18 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) #endif -#define MICROPY_HW_ENABLE_UART_REPL (1) // useful if there is no USB -#define MICROPY_HW_ENABLE_USBDEV (1) - +#ifndef MICROPY_HW_ENABLE_OSPI +#define MICROPY_HW_ENABLE_OSPI (CORE_M55_HP) +#endif +#ifndef MICROPY_HW_ENABLE_USBDEV +#define MICROPY_HW_ENABLE_USBDEV (CORE_M55_HP) +#endif #ifndef MICROPY_HW_USB_PRODUCT_FS_STRING #define MICROPY_HW_USB_PRODUCT_FS_STRING "Board in HS mode" #endif -#define MICROPY_HW_USB_CDC (1) +#ifndef MICROPY_HW_USB_CDC +#define MICROPY_HW_USB_CDC (CORE_M55_HP) +#endif #define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) #ifndef MICROPY_HW_USB_MSC #define MICROPY_HW_USB_MSC (0) @@ -53,7 +58,9 @@ #ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID (0x9802) // interface has CDC only #endif - +#ifndef MICROPY_HW_ENABLE_UART_REPL +#define MICROPY_HW_ENABLE_UART_REPL (CORE_M55_HP) // useful if there is no USB +#endif #define MICROPY_HW_FLASH_BLOCK_SIZE_BYTES (4096) // Memory allocation policies @@ -78,6 +85,9 @@ #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#ifndef MICROPY_FLOAT_IMPL +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#endif #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_USE_INTERNAL_ERRNO (1) diff --git a/ports/alif/mpconfigport.mk b/ports/alif/mpconfigport.mk index 3d04450a60f..92d9a5db8fa 100644 --- a/ports/alif/mpconfigport.mk +++ b/ports/alif/mpconfigport.mk @@ -10,3 +10,4 @@ MICROPY_VFS_LFS2 ?= 1 # File containing description of content to be frozen into firmware. FROZEN_MANIFEST ?= boards/manifest.py +MICROPY_MANIFEST_MCU_CORE := $(shell echo $(MCU_CORE) | awk -F'_' '{print tolower($$2)}') diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index ba58923f2fe..9769ddeaeb6 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -85,8 +85,10 @@ int mp_hal_stdin_rx_chr(void) { // Send string of given length mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { + #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC || MICROPY_PY_OS_DUPTERM mp_uint_t ret = len; bool did_write = false; + #endif #if MICROPY_HW_ENABLE_UART_REPL mp_uart_write_strn(str, len); diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index fa96053b365..f84761366b3 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -26,8 +26,9 @@ #include "py/mperrno.h" #include "py/mphal.h" -#include "ospi_flash.h" +#if MICROPY_HW_ENABLE_OSPI +#include "ospi_flash.h" #include "ospi_drv.h" #include "pinconf.h" @@ -263,3 +264,4 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { } return ret; } +#endif // MICROPY_HW_ENABLE_OSPI diff --git a/ports/alif/tinyusb_port/tusb_config.h b/ports/alif/tinyusb_port/tusb_config.h index d244b4c241e..6c4ead11288 100644 --- a/ports/alif/tinyusb_port/tusb_config.h +++ b/ports/alif/tinyusb_port/tusb_config.h @@ -44,7 +44,7 @@ #define CFG_TUSB_DEBUG 0 // Enable Device stack -#define CFG_TUD_ENABLED 1 +#define CFG_TUD_ENABLED (CORE_M55_HP) // Default is max speed that hardware controller could support with on-chip PHY #define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED From 4f6f283abbdcbdec5cb2144a74fdd2937540ac71 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 17 Jul 2024 17:45:08 +0300 Subject: [PATCH 0476/2098] alif: Implement Open-AMP port backend. Signed-off-by: iabdalkader --- ports/alif/Makefile | 4 +- ports/alif/alif.mk | 11 +++ ports/alif/irq.h | 1 + ports/alif/mcu/ensemble.ld.S | 8 ++ ports/alif/mpconfigport.h | 1 + ports/alif/mpmetalport.c | 119 +++++++++++++++++++++++ ports/alif/mpmetalport.h | 89 ++++++++++++++++++ ports/alif/mpremoteprocport.c | 172 ++++++++++++++++++++++++++++++++++ 8 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 ports/alif/mpmetalport.c create mode 100644 ports/alif/mpmetalport.h create mode 100644 ports/alif/mpremoteprocport.c diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 5cc334a7cf9..7d6731dacf2 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -76,10 +76,10 @@ $(BUILD): $(MKDIR) -p $@ $(BUILD)/M55_HP/firmware.bin: - make -f alif.mk MCU_CORE=M55_HP + make -f alif.mk MCU_CORE=M55_HP MICROPY_PY_OPENAMP_MODE=0 $(BUILD)/M55_HE/firmware.bin: - make -f alif.mk MCU_CORE=M55_HE + make -f alif.mk MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 $(BUILD)/firmware.toc.bin: $(ALIF_TOC_APPS) $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index ad69499eccf..c68fc66d44c 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -203,6 +203,17 @@ $(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALI $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_system.o: CFLAGS += -Wno-maybe-uninitialized +# Add Alif-specific implementation of libmetal (and optionally OpenAMP's rproc). +# Note: libmetal code is generated via a pre-processor so ensure that runs first. +ifeq ($(MICROPY_PY_OPENAMP),1) +SRC_C += mpmetalport.c +$(BUILD)/mpmetalport.o: $(BUILD)/openamp/metal/config.h +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +SRC_C += mpremoteprocport.c +$(BUILD)/mpremoteprocport.o: $(BUILD)/openamp/metal/config.h +endif +endif + # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) diff --git a/ports/alif/irq.h b/ports/alif/irq.h index a20ff7aebde..de6d07ab6d3 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -45,6 +45,7 @@ #define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_ADC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 3, 0) #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) +#define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) // these states correspond to values from query_irq, enable_irq and disable_irq diff --git a/ports/alif/mcu/ensemble.ld.S b/ports/alif/mcu/ensemble.ld.S index 8129a749eb2..aeefdb7eacb 100644 --- a/ports/alif/mcu/ensemble.ld.S +++ b/ports/alif/mcu/ensemble.ld.S @@ -114,6 +114,14 @@ SECTIONS * (.bss.sram0*) } > SRAM0 + /* Open-AMP Shared Memory Region */ + .openamp_memory (NOLOAD) : ALIGN(32) + { + _openamp_shm_region_start = .; + . = . + 64K; + _openamp_shm_region_end = .; + } >SRAM6_A + .bss : ALIGN(4) { __bss_start__ = .; diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index df33b922024..dfe1ec25613 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -91,6 +91,7 @@ #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_USE_INTERNAL_ERRNO (1) +#define MICROPY_TRACKED_ALLOC (MICROPY_PY_OPENAMP) // Fine control over Python builtins, classes, modules, etc #define MICROPY_PY_SYS_PLATFORM "alif" diff --git a/ports/alif/mpmetalport.c b/ports/alif/mpmetalport.c new file mode 100644 index 00000000000..f80d039c26d --- /dev/null +++ b/ports/alif/mpmetalport.c @@ -0,0 +1,119 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + * + * libmetal Alif port. + */ + +#include ALIF_CMSIS_H +#include "hwsem.h" + +#include "py/mperrno.h" +#include "py/mphal.h" + +#include "metal/sys.h" +#include "metal/utilities.h" +#include "metal/device.h" + +struct metal_state _metal; +static mp_sched_node_t rproc_notify_node; + +int metal_sys_init(const struct metal_init_params *params) { + metal_unused(params); + + // Reset the hardware semaphore. + hwsem_reset(METAL_HSEM_DEVICE); + #if MICROPY_PY_OPENAMP_HOST + hwsem_reset(METAL_HSEM_REMOTE); + #endif + + // Enable the hardware semaphore IRQ. + NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); + NVIC_SetPriority(METAL_HSEM_IRQn, IRQ_PRI_HWSEM); + NVIC_EnableIRQ(METAL_HSEM_IRQn); + + // If cache management is not enabled, configure the MPU to disable + // caching for the entire Open-AMP shared memory region. + #ifndef VIRTIO_USE_DCACHE + ARM_MPU_Disable(); + // NOTE: The startup code uses the first 4 attributes. + #define MEMATTR_IDX_NORMAL_NON_CACHEABLE 4 + ARM_MPU_SetMemAttr(MEMATTR_IDX_NORMAL_NON_CACHEABLE, ARM_MPU_ATTR( + ARM_MPU_ATTR_NON_CACHEABLE, + ARM_MPU_ATTR_NON_CACHEABLE)); + MPU->RNR = METAL_MPU_REGION_ID; + MPU->RBAR = ARM_MPU_RBAR(METAL_MPU_REGION_BASE, ARM_MPU_SH_NON, 0, 1, 0); // RO-0, NP-1, XN-0 + MPU->RLAR = ARM_MPU_RLAR(METAL_MPU_REGION_BASE + METAL_MPU_REGION_SIZE - 1, MEMATTR_IDX_NORMAL_NON_CACHEABLE); + ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk); + #endif + + metal_bus_register(&metal_generic_bus); + + return 0; +} + +void metal_sys_finish(void) { + NVIC_DisableIRQ(METAL_HSEM_IRQn); + NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); + hwsem_reset(METAL_HSEM_DEVICE); + metal_bus_unregister(&metal_generic_bus); +} + +unsigned int sys_irq_save_disable(void) { + return disable_irq(); +} + +void sys_irq_restore_enable(unsigned int state) { + enable_irq(state); +} + +void *metal_machine_io_mem_map(void *va, metal_phys_addr_t pa, + size_t size, unsigned int flags) { + metal_unused(pa); + metal_unused(size); + metal_unused(flags); + return va; +} + +void metal_machine_cache_flush(void *addr, unsigned int len) { + SCB_CleanDCache_by_Addr(addr, len); +} + +void metal_machine_cache_invalidate(void *addr, unsigned int len) { + SCB_InvalidateDCache_by_Addr(addr, len); +} + +int metal_rproc_notify(void *priv, uint32_t id) { + // Release the HW semaphore to notify the other core. + hwsem_release(METAL_HSEM_REMOTE, HWSEM_MASTERID); + return 0; +} + +void METAL_HSEM_IRQ_HANDLER(void) { + // Schedule the node only if the other core released the Semaphore. + if (METAL_HSEM_DEVICE->HWSEM_REL_REG == 0) { + mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); + } + hwsem_request(METAL_HSEM_DEVICE, METAL_HSEM_REMOTE_ID); +} diff --git a/ports/alif/mpmetalport.h b/ports/alif/mpmetalport.h new file mode 100644 index 00000000000..5cd697c0e9c --- /dev/null +++ b/ports/alif/mpmetalport.h @@ -0,0 +1,89 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + * + * libmetal alif port. + */ +#ifndef MICROPY_INCLUDED_ALIF_MPMETALPORT_H +#define MICROPY_INCLUDED_ALIF_MPMETALPORT_H + +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#define METAL_HAVE_STDATOMIC_H 0 +#define METAL_HAVE_FUTEX_H 0 + +#define METAL_MAX_DEVICE_REGIONS 2 + +#if MICROPY_PY_OPENAMP_HOST +#define METAL_HSEM_DEVICE ((HWSEM_Type *)HWSEM14_BASE) +#define METAL_HSEM_REMOTE ((HWSEM_Type *)HWSEM15_BASE) +#define METAL_HSEM_REMOTE_ID (0x410FD222U) +#define METAL_HSEM_IRQn HWSEM_IRQ14_IRQn +#define METAL_HSEM_IRQ_HANDLER HWSEM_IRQ14Handler +#else +#define METAL_HSEM_DEVICE ((HWSEM_Type *)HWSEM15_BASE) +#define METAL_HSEM_REMOTE ((HWSEM_Type *)HWSEM14_BASE) +#define METAL_HSEM_REMOTE_ID (0x410FD221U) +#define METAL_HSEM_IRQn HWSEM_IRQ15_IRQn +#define METAL_HSEM_IRQ_HANDLER HWSEM_IRQ15Handler +#endif + +// Set to 1 to enable log output. +#define METAL_LOG_HANDLER_ENABLE 0 + +#define metal_cpu_yield() + +// Shared memory config +#define METAL_SHM_NAME "OPENAMP_SHM" +// Note 1K must be reserved at the start of the openamp +// shared memory region, for the shared resource table. +#define METAL_RSC_ADDR ((void *)_openamp_shm_region_start) +#define METAL_RSC_SIZE (1024) + +#define METAL_SHM_ADDR ((metal_phys_addr_t)(_openamp_shm_region_start + METAL_RSC_SIZE)) +#define METAL_SHM_SIZE ((size_t)(_openamp_shm_region_end - _openamp_shm_region_start - METAL_RSC_SIZE)) + +#define METAL_MPU_REGION_ID (9) // NOTE: The startup code uses the first 9 regions. +#define METAL_MPU_REGION_BASE ((uint32_t)_openamp_shm_region_start) +#define METAL_MPU_REGION_SIZE (0x00010000U) + +extern const char _openamp_shm_region_start[]; +extern const char _openamp_shm_region_end[]; + +int metal_rproc_notify(void *priv, uint32_t id); +extern void openamp_remoteproc_notified(mp_sched_node_t *node); + +static inline int __metal_sleep_usec(unsigned int usec) { + mp_hal_delay_us(usec); + return 0; +} + +static inline void metal_generic_default_poll(void) { + mp_event_handle_nowait(); + __WFI(); +} + +#endif // MICROPY_INCLUDED_ALIF_METAL_PORT_H diff --git a/ports/alif/mpremoteprocport.c b/ports/alif/mpremoteprocport.c new file mode 100644 index 00000000000..27210a82405 --- /dev/null +++ b/ports/alif/mpremoteprocport.c @@ -0,0 +1,172 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + * + * modremoteproc alif port. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/sys.h" +#include "metal/device.h" +#include "metal/utilities.h" +#include "extmod/modopenamp_remoteproc.h" +#include "se_services.h" + +typedef struct mmap { + uintptr_t base; + uintptr_t limit; +} mmap_t; + +static const mmap_t mmap_nocache[] = { + { .base = ITCM_BASE, .limit = ITCM_BASE + ITCM_SIZE }, + { .base = DTCM_BASE, .limit = DTCM_BASE + ITCM_SIZE }, + { .base = MRAM_BASE, .limit = MRAM_BASE + MRAM_SIZE } +}; + +static bool is_cacheable(const void *p, size_t bytes) { + uintptr_t base = (uintptr_t)p; + if (bytes == 0) { + return false; + } + uintptr_t limit = base + bytes; + for (unsigned int i = 0; i < sizeof(mmap_nocache) / sizeof(mmap_nocache[0]); i++) { + if (base >= mmap_nocache[i].base && limit < mmap_nocache[i].limit) { + return false; + } + } + return true; +} + +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg) { + metal_log(METAL_LOG_DEBUG, "rproc_init()\n"); + + rproc->ops = ops; + rproc->state = RPROC_OFFLINE; + // Allocate the image store and save it in private data. + rproc->priv = mp_openamp_remoteproc_store_alloc(); + + // Reset the remote core. + se_services_boot_reset_cpu(EXTSYS_1); + return rproc; +} + +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io) { + metal_log(METAL_LOG_DEBUG, "rproc_mmap(): pa 0x%p da 0x%p io 0x%p size %u\n", *pa, *da, *io, size); + + struct remoteproc_mem *mem; + metal_phys_addr_t lpa = *pa; + metal_phys_addr_t lda = *da; + + if (lda == METAL_BAD_PHYS) { + return NULL; + } + + if (lpa == METAL_BAD_PHYS) { + lpa = lda; + } + + // Currently this port doesn't support loading firmware to flash, + // only SD/SRAM images are supported. Check of load address is in + // the flash region, and if so return NULL. + if (lda >= MRAM_BASE && lda < (MRAM_BASE + MRAM_SIZE)) { + return NULL; + } + + mem = metal_allocate_memory(sizeof(*mem)); + if (!mem) { + return NULL; + } + + *io = metal_allocate_memory(sizeof(struct metal_io_region)); + if (!*io) { + metal_free_memory(mem); + return NULL; + } + + remoteproc_init_mem(mem, NULL, lpa, lda, size, *io); + + metal_io_init(*io, (void *)mem->da, &mem->pa, size, + sizeof(metal_phys_addr_t) << 3, attribute, NULL); + + remoteproc_add_mem(rproc, mem); + *pa = lpa; + *da = lda; + return metal_io_phys_to_virt(*io, mem->pa); +} + +int mp_openamp_remoteproc_start(struct remoteproc *rproc) { + metal_log(METAL_LOG_DEBUG, "rproc_start()\n"); + + // Flush cached areas for the remote core. + struct metal_list *node; + metal_list_for_each(&rproc->mems, node) { + struct remoteproc_mem *mem; + mem = metal_container_of(node, struct remoteproc_mem, node); + if (is_cacheable((uint32_t *)mem->pa, mem->size)) { + SCB_CleanDCache_by_Addr((uint32_t *)mem->pa, mem->size); + } + } + + se_services_boot_reset_cpu(EXTSYS_1); + se_services_boot_cpu(EXTSYS_1, (uint32_t)rproc->bootaddr); // GlobalToLocal + return 0; +} + +int mp_openamp_remoteproc_stop(struct remoteproc *rproc) { + metal_log(METAL_LOG_DEBUG, "rproc_stop()\n"); + if (rproc->state == RPROC_RUNNING) { + se_services_boot_reset_cpu(EXTSYS_1); + } + return 0; +} + +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data) { + metal_log(METAL_LOG_DEBUG, "rproc_config()\n"); + (void)rproc; + return 0; +} + +void mp_openamp_remoteproc_remove(struct remoteproc *rproc) { + metal_log(METAL_LOG_DEBUG, "rproc_remove()\n"); + (void)rproc; +} + +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc) { + metal_log(METAL_LOG_DEBUG, "rproc_shutdown()\n"); + if (rproc->state == RPROC_RUNNING) { + se_services_boot_reset_cpu(EXTSYS_1); + } + return 0; +} From cee8e111cbaedc877fcffeb7d8166b60317f7377 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 28 Jul 2024 21:51:29 +0300 Subject: [PATCH 0477/2098] alif/irq: Define more IRQ priorities. Signed-off-by: Damien George --- ports/alif/irq.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/alif/irq.h b/ports/alif/irq.h index de6d07ab6d3..59dbce97059 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -44,8 +44,10 @@ #define IRQ_PRI_QUIET_TIMING NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_ADC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 3, 0) -#define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) +#define IRQ_PRI_CSI NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) +#define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 7, 0) #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) +#define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) // these states correspond to values from query_irq, enable_irq and disable_irq From bbb8fd77fde3400cc1b9902e94f05889a7c1f49a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 14 Aug 2024 16:50:28 +1000 Subject: [PATCH 0478/2098] alif/system_tick: Implement optional LPTIMER support for systick. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 6 ++ ports/alif/mphalport.c | 12 ++++ ports/alif/system_tick.c | 137 +++++++++++++++++++++++++++++++++++++- ports/alif/system_tick.h | 6 +- 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index dfe1ec25613..b1a01910628 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -36,6 +36,12 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) #endif +// Select the low-level system tick implementation. +#if !defined(MICROPY_HW_SYSTEM_TICK_USE_LPTIMER) \ + && !defined(MICROPY_HW_SYSTEM_TICK_USE_UTIMER) +#define MICROPY_HW_SYSTEM_TICK_USE_UTIMER (1) +#endif + #ifndef MICROPY_HW_ENABLE_OSPI #define MICROPY_HW_ENABLE_OSPI (CORE_M55_HP) #endif diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 9769ddeaeb6..becac8f3b4a 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -120,16 +120,28 @@ mp_uint_t mp_hal_ticks_cpu(void) { mp_uint_t mp_hal_ticks_us(void) { // Convert system tick to microsecond counter. + #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + return system_tick_get_u64() * 1000000 / system_tick_source_hz; + #else return system_tick_get_u64() / system_core_clock_mhz; + #endif } mp_uint_t mp_hal_ticks_ms(void) { // Convert system tick to millisecond counter. + #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + return system_tick_get_u64() * 1000ULL / system_tick_source_hz; + #else return system_tick_get_u64() / (SystemCoreClock / 1000); + #endif } void mp_hal_delay_us(mp_uint_t us) { + #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + uint64_t ticks_delay = (uint64_t)us * system_tick_source_hz / 1000000; + #else uint64_t ticks_delay = (uint64_t)us * system_core_clock_mhz; + #endif uint64_t start = system_tick_get_u64(); while (system_tick_get_u64() - start < ticks_delay) { } diff --git a/ports/alif/system_tick.c b/ports/alif/system_tick.c index 5cdf45cba01..72b8d021c39 100644 --- a/ports/alif/system_tick.c +++ b/ports/alif/system_tick.c @@ -27,10 +27,141 @@ #include "irq.h" #include "system_tick.h" -#include "utimer.h" - #define MIN(x, y) ((x) < (y) ? (x) : (y)) +#if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + +#include "lptimer.h" +#include "sys_ctrl_lptimer.h" + +// Channel 0 and 1 are cascaded to make a 64-bit counter. +// Channel 2 is used for system_tick_wfe_with_timeout_us. +// Channel 3 is used for system_tick_schedule_after_us. +#define LPTIMER ((LPTIMER_Type *)LPTIMER_BASE) +#define LPTIMER_CH_A (0) +#define LPTIMER_CH_B (1) +#define LPTIMER_CH_C (2) +#define LPTIMER_CH_D (3) + +uint64_t system_tick_source_hz; + +void system_tick_init(void) { + lptimer_disable_counter(LPTIMER, LPTIMER_CH_A); + lptimer_disable_counter(LPTIMER, LPTIMER_CH_B); + + ANA_REG->MISC_CTRL |= 1 << 0; // SEL_32K, select LXFO + + select_lptimer_clk(LPTIMER_CLK_SOURCE_32K, LPTIMER_CH_A); + select_lptimer_clk(LPTIMER_CLK_SOURCE_CASCADE, LPTIMER_CH_B); + select_lptimer_clk(LPTIMER_CLK_SOURCE_32K, LPTIMER_CH_C); + select_lptimer_clk(LPTIMER_CLK_SOURCE_32K, LPTIMER_CH_D); + + lptimer_load_max_count(LPTIMER, LPTIMER_CH_A); + lptimer_set_mode_freerunning(LPTIMER, LPTIMER_CH_A); + + lptimer_load_max_count(LPTIMER, LPTIMER_CH_B); + lptimer_set_mode_freerunning(LPTIMER, LPTIMER_CH_B); + + lptimer_enable_counter(LPTIMER, LPTIMER_CH_B); + lptimer_enable_counter(LPTIMER, LPTIMER_CH_A); + + system_tick_source_hz = 32768; + + NVIC_ClearPendingIRQ(LPTIMER2_IRQ_IRQn); + NVIC_SetPriority(LPTIMER2_IRQ_IRQn, IRQ_PRI_SYSTEM_TICK); + NVIC_EnableIRQ(LPTIMER2_IRQ_IRQn); + + NVIC_ClearPendingIRQ(LPTIMER3_IRQ_IRQn); + NVIC_SetPriority(LPTIMER3_IRQ_IRQn, IRQ_PRI_SYSTEM_TICK); + NVIC_EnableIRQ(LPTIMER3_IRQ_IRQn); +} + +void LPTIMER2_IRQHandler(void) { + lptimer_clear_interrupt(LPTIMER, LPTIMER_CH_C); + __SEV(); +} + +void LPTIMER3_IRQHandler(void) { + lptimer_clear_interrupt(LPTIMER, LPTIMER_CH_D); + lptimer_mask_interrupt(LPTIMER, LPTIMER_CH_D); + lptimer_disable_counter(LPTIMER, LPTIMER_CH_D); + system_tick_schedule_callback(); + __SEV(); +} + +uint32_t system_tick_get_u32(void) { + return 0xffffffff - lptimer_get_count(LPTIMER, LPTIMER_CH_A); +} + +uint64_t system_tick_get_u64(void) { + // Get 64-bit counter value from the hardware timer. + // Sample it twice in case the low counter wraps around while sampling. + uint32_t irq_state = disable_irq(); + uint32_t lo0 = lptimer_get_count(LPTIMER, LPTIMER_CH_A); + uint32_t hi0 = lptimer_get_count(LPTIMER, LPTIMER_CH_B); + uint32_t lo1 = lptimer_get_count(LPTIMER, LPTIMER_CH_A); + uint32_t hi1 = lptimer_get_count(LPTIMER, LPTIMER_CH_B); + enable_irq(irq_state); + + if (hi0 == hi1) { + // Low counter may have wrapped around between sampling of lo0 and hi0, so prefer second sampling. + lo0 = lo1; + hi0 = hi1; + } else { + // Low counter wrapped around either between sampling of hi0 and lo1, or sampling of lo1 and hi1. + // In either case use the first sampling. + } + + // Convert from descending count to ascending. + lo0 = 0xffffffff - lo0; + hi0 = 0xffffffff - hi0; + + // Return a 64-bit value. + return ((uint64_t)hi0 << 32) | (uint64_t)lo0; +} + +void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { + // Maximum 131 second timeout, to not overflow 32-bit ticks when + // LPTIMER is clocked at 32768Hz. + uint32_t timeout_ticks = (uint64_t)MIN(timeout_us, 131000000) * system_tick_source_hz / 1000000; + + // Set up the LPTIMER interrupt to fire after the given timeout. + lptimer_disable_counter(LPTIMER, LPTIMER_CH_C); + lptimer_set_mode_userdefined(LPTIMER, LPTIMER_CH_C); + lptimer_load_count(LPTIMER, LPTIMER_CH_C, &timeout_ticks); + lptimer_clear_interrupt(LPTIMER, LPTIMER_CH_C); + lptimer_unmask_interrupt(LPTIMER, LPTIMER_CH_C); + lptimer_enable_counter(LPTIMER, LPTIMER_CH_C); + + // Wait for an event. + __WFE(); + + // Disable the LPTIMER interrupt (in case a different interrupt woke the WFE). + lptimer_mask_interrupt(LPTIMER, LPTIMER_CH_C); + lptimer_disable_counter(LPTIMER, LPTIMER_CH_C); +} + +void system_tick_schedule_after_us(uint32_t ticks_us) { + // Disable the interrupt in case it's still active. + lptimer_mask_interrupt(LPTIMER, LPTIMER_CH_D); + + // Maximum 131 second timeout, to not overflow 32-bit ticks when + // LPTIMER is clocked at 32768Hz. + uint32_t timeout_ticks = (uint64_t)MIN(ticks_us, 131000000) * system_tick_source_hz / 1000000; + + // Set up the LPTIMER interrupt to fire after the given timeout. + lptimer_disable_counter(LPTIMER, LPTIMER_CH_D); + lptimer_set_mode_userdefined(LPTIMER, LPTIMER_CH_D); + lptimer_load_count(LPTIMER, LPTIMER_CH_D, &timeout_ticks); + lptimer_clear_interrupt(LPTIMER, LPTIMER_CH_D); + lptimer_unmask_interrupt(LPTIMER, LPTIMER_CH_D); + lptimer_enable_counter(LPTIMER, LPTIMER_CH_D); +} + +#elif MICROPY_HW_SYSTEM_TICK_USE_UTIMER + +#include "utimer.h" + #define UTIMER ((UTIMER_Type *)UTIMER_BASE) #define UTIMER_CHANNEL (11) @@ -166,3 +297,5 @@ void system_tick_schedule_after_us(uint32_t ticks_us) { } } } + +#endif diff --git a/ports/alif/system_tick.h b/ports/alif/system_tick.h index 2373f9421de..d808b76322e 100644 --- a/ports/alif/system_tick.h +++ b/ports/alif/system_tick.h @@ -26,9 +26,13 @@ #ifndef MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H #define MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H -#include +#include "py/mpconfig.h" +#if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER +extern uint64_t system_tick_source_hz; +#else extern uint64_t system_core_clock_mhz; +#endif void system_tick_init(void); uint32_t system_tick_get_u32(void); From c6ebecc4c3dbeeecbb0eecabd4ecb6162b8f14bb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 Aug 2024 12:59:47 +1000 Subject: [PATCH 0479/2098] alif/system_tick: Implement optional ARM SysTick support for systick. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 6 +++- ports/alif/mphalport.c | 16 ++++++++-- ports/alif/system_tick.c | 64 ++++++++++++++++++++++++++++++++++++++- ports/alif/system_tick.h | 2 +- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index b1a01910628..0fee2c379a3 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -37,10 +37,14 @@ #endif // Select the low-level system tick implementation. -#if !defined(MICROPY_HW_SYSTEM_TICK_USE_LPTIMER) \ +#if !defined(MICROPY_HW_SYSTEM_TICK_USE_SYSTICK) \ + && !defined(MICROPY_HW_SYSTEM_TICK_USE_LPTIMER) \ && !defined(MICROPY_HW_SYSTEM_TICK_USE_UTIMER) #define MICROPY_HW_SYSTEM_TICK_USE_UTIMER (1) #endif +#if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK +#define MICROPY_SOFT_TIMER_TICKS_MS system_tick_ms_counter +#endif #ifndef MICROPY_HW_ENABLE_OSPI #define MICROPY_HW_ENABLE_OSPI (CORE_M55_HP) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index becac8f3b4a..5e3dad14190 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -120,7 +120,9 @@ mp_uint_t mp_hal_ticks_cpu(void) { mp_uint_t mp_hal_ticks_us(void) { // Convert system tick to microsecond counter. - #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK + return system_tick_get_u64(); + #elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER return system_tick_get_u64() * 1000000 / system_tick_source_hz; #else return system_tick_get_u64() / system_core_clock_mhz; @@ -129,7 +131,9 @@ mp_uint_t mp_hal_ticks_us(void) { mp_uint_t mp_hal_ticks_ms(void) { // Convert system tick to millisecond counter. - #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK + return system_tick_get_u64() / 1000ULL; + #elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER return system_tick_get_u64() * 1000ULL / system_tick_source_hz; #else return system_tick_get_u64() / (SystemCoreClock / 1000); @@ -137,7 +141,9 @@ mp_uint_t mp_hal_ticks_ms(void) { } void mp_hal_delay_us(mp_uint_t us) { - #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK + uint64_t ticks_delay = (uint64_t)us; + #elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER uint64_t ticks_delay = (uint64_t)us * system_tick_source_hz / 1000000; #else uint64_t ticks_delay = (uint64_t)us * system_core_clock_mhz; @@ -167,6 +173,8 @@ void system_tick_schedule_callback(void) { pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); } +#if !defined(MICROPY_SOFT_TIMER_TICKS_MS) + uint32_t soft_timer_get_ms(void) { return mp_hal_ticks_ms(); } @@ -177,3 +185,5 @@ void soft_timer_schedule_at_ms(uint32_t ticks_ms) { ms = MIN(ms, 4000000); // ensure ms * 1000 doesn't overflow system_tick_schedule_after_us(ms * 1000); } + +#endif diff --git a/ports/alif/system_tick.c b/ports/alif/system_tick.c index 72b8d021c39..4f161e6709e 100644 --- a/ports/alif/system_tick.c +++ b/ports/alif/system_tick.c @@ -29,7 +29,69 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) -#if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER +#if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK + +#include "shared/runtime/softtimer.h" +#include "pendsv.h" + +volatile uint32_t system_tick_ms_counter; + +void system_tick_init(void) { + // Configure SysTick to run at 1kHz (1ms interval) + SysTick_Config(SystemCoreClock / 1000); + NVIC_SetPriority(SysTick_IRQn, IRQ_PRI_SYSTEM_TICK); + NVIC_EnableIRQ(SysTick_IRQn); +} + +void SysTick_Handler(void) { + uint32_t uw_tick = system_tick_ms_counter + 1; + system_tick_ms_counter = uw_tick; + + // Read the systick control register to clear the COUNTFLAG bit. + SysTick->CTRL; + + if (soft_timer_next == uw_tick) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); + } +} + +uint32_t system_tick_get_u32(void) { + return system_tick_get_u64(); +} + +uint64_t system_tick_get_u64(void) { + mp_uint_t irq_state = disable_irq(); + uint32_t counter = SysTick->VAL; + uint32_t milliseconds = system_tick_ms_counter; + uint32_t status = SysTick->CTRL; + enable_irq(irq_state); + + // It's still possible for the COUNTFLAG bit to get set if the counter was + // reloaded between reading VAL and reading CTRL. With interrupts disabled + // it definitely takes less than 50 cycles between reading VAL and + // reading CTRL, so the test (counter > 50) is to cover the case where VAL + // is +ve and very close to zero, and the COUNTFLAG bit is also set. + if ((status & SysTick_CTRL_COUNTFLAG_Msk) && counter > 50) { + // This means that the HW reloaded VAL between the time we read VAL and the + // time we read CTRL, which implies that there is an interrupt pending + // to increment the tick counter. + milliseconds++; + } + uint32_t load = SysTick->LOAD; + counter = load - counter; // Convert from decrementing to incrementing + + // Calculate 64-bit microsecond counter. + return (uint64_t)milliseconds * 1000ULL + (uint64_t)((counter * 1000) / (load + 1)); +} + +void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { + if (timeout_us > 1000) { + // SysTick will wake us in at most 1ms. + __WFI(); + } +} + +#elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER #include "lptimer.h" #include "sys_ctrl_lptimer.h" diff --git a/ports/alif/system_tick.h b/ports/alif/system_tick.h index d808b76322e..a380808b2e0 100644 --- a/ports/alif/system_tick.h +++ b/ports/alif/system_tick.h @@ -30,7 +30,7 @@ #if MICROPY_HW_SYSTEM_TICK_USE_LPTIMER extern uint64_t system_tick_source_hz; -#else +#elif MICROPY_HW_SYSTEM_TICK_USE_UTIMER extern uint64_t system_core_clock_mhz; #endif From 58d6fe236b60daa92810abc81c8462e362a344dc Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 3 Sep 2024 17:04:56 +1000 Subject: [PATCH 0480/2098] alif/mpconfigport: Select SysTick on HE core. UTIMER is used by the HP. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 0fee2c379a3..ae3ad7a30d3 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -40,7 +40,11 @@ #if !defined(MICROPY_HW_SYSTEM_TICK_USE_SYSTICK) \ && !defined(MICROPY_HW_SYSTEM_TICK_USE_LPTIMER) \ && !defined(MICROPY_HW_SYSTEM_TICK_USE_UTIMER) +#if CORE_M55_HP #define MICROPY_HW_SYSTEM_TICK_USE_UTIMER (1) +#else +#define MICROPY_HW_SYSTEM_TICK_USE_SYSTICK (1) +#endif #endif #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK #define MICROPY_SOFT_TIMER_TICKS_MS system_tick_ms_counter From 4c4b4844df7746aa2ed4b124663e2472cb908b1a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Oct 2024 18:20:06 +1100 Subject: [PATCH 0481/2098] alif/mpu: Add custom MPU_Load_Regions function. Signed-off-by: Damien George --- ports/alif/alif.mk | 1 + ports/alif/mpu.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++ ports/alif/mpu.h | 31 ++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 ports/alif/mpu.c create mode 100644 ports/alif/mpu.h diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index c68fc66d44c..210b06c65f7 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -119,6 +119,7 @@ SRC_C = \ main.c \ modalif.c \ mphalport.c \ + mpu.c \ mpuart.c \ msc_disk.c \ ospi_flash.c \ diff --git a/ports/alif/mpu.c b/ports/alif/mpu.c new file mode 100644 index 00000000000..11c6273fcb3 --- /dev/null +++ b/ports/alif/mpu.c @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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/mpconfig.h" +#include "mpu.h" +#include ALIF_CMSIS_H + +static const ARM_MPU_Region_t mpu_table[] __STARTUP_RO_DATA_ATTRIBUTE = { + { /* SRAM0 - 4MB : RO-0, NP-1, XN-0 */ + .RBAR = ARM_MPU_RBAR(0x02000000, ARM_MPU_SH_NON, 0, 1, 0), + .RLAR = ARM_MPU_RLAR(0x023FFFFF, MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT) + }, + { /* SRAM1 - 2.5MB : RO-0, NP-1, XN-0 */ + .RBAR = ARM_MPU_RBAR(0x08000000, ARM_MPU_SH_NON, 0, 1, 0), + .RLAR = ARM_MPU_RLAR(0x0827FFFF, MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA) + }, + { /* Host Peripherals - 16MB : RO-0, NP-1, XN-1 */ + .RBAR = ARM_MPU_RBAR(0x1A000000, ARM_MPU_SH_NON, 0, 1, 1), + .RLAR = ARM_MPU_RLAR(0x1AFFFFFF, MP_MPU_ATTR_INDEX_DEVICE_nGnRE) + }, + { /* MRAM - 5.5MB : RO-1, NP-1, XN-0 */ + .RBAR = ARM_MPU_RBAR(0x80000000, ARM_MPU_SH_NON, 1, 1, 0), + .RLAR = ARM_MPU_RLAR(0x8057FFFF, MP_MPU_ATTR_INDEX_NORMAL_WT_RA) + }, + { /* OSPI Regs - 16MB : RO-0, NP-1, XN-1 */ + .RBAR = ARM_MPU_RBAR(0x83000000, ARM_MPU_SH_NON, 0, 1, 1), + .RLAR = ARM_MPU_RLAR(0x83FFFFFF, MP_MPU_ATTR_INDEX_DEVICE_nGnRE) + }, + { /* OSPI0 XIP flash - 512MB : RO-1, NP-1, XN-0 */ + .RBAR = ARM_MPU_RBAR(0xA0000000, ARM_MPU_SH_NON, 1, 1, 0), + .RLAR = ARM_MPU_RLAR(0xBFFFFFFF, MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE) + }, +}; + +void MPU_Load_Regions(void) { + // Configure memory attributes. + + ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT, + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(0, 0, 1, 0), ARM_MPU_ATTR_MEMORY_(0, 0, 1, 0))); + + ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_DEVICE_nGnRE, + ARM_MPU_ATTR(ARM_MPU_ATTR_DEVICE, ARM_MPU_ATTR_DEVICE_nGnRE)); + + ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA, + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1, 1, 1, 1), ARM_MPU_ATTR_MEMORY_(1, 1, 1, 1))); + + ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WT_RA, + ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1, 0, 1, 0), ARM_MPU_ATTR_MEMORY_(1, 0, 1, 0))); + + ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE, + ARM_MPU_ATTR(ARM_MPU_ATTR_NON_CACHEABLE, ARM_MPU_ATTR_NON_CACHEABLE)); + + // Load the MPU regions from the table. + ARM_MPU_Load(0, mpu_table, sizeof(mpu_table) / sizeof(ARM_MPU_Region_t)); +} diff --git a/ports/alif/mpu.h b/ports/alif/mpu.h new file mode 100644 index 00000000000..87a7518a126 --- /dev/null +++ b/ports/alif/mpu.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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. + */ + +#define MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT (0) +#define MP_MPU_ATTR_INDEX_DEVICE_nGnRE (1) +#define MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA (2) +#define MP_MPU_ATTR_INDEX_NORMAL_WT_RA (3) +#define MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE (4) From 84effb386ac23257e9a399bf52dc23f8f50387fe Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Oct 2024 16:06:50 +1100 Subject: [PATCH 0482/2098] alif/ospi_flash: Generalise flash driver to support MX chips. Signed-off-by: Damien George --- ports/alif/alif.mk | 1 + ports/alif/ospi_ext.c | 292 ++++++++++++++++++++++++++++++ ports/alif/ospi_ext.h | 57 ++++++ ports/alif/ospi_flash.c | 362 ++++++++++++++++++++++++------------- ports/alif/ospi_flash.h | 61 ++++++- ports/alif/ospi_xip_user.h | 5 - 6 files changed, 643 insertions(+), 135 deletions(-) create mode 100644 ports/alif/ospi_ext.c create mode 100644 ports/alif/ospi_ext.h delete mode 100644 ports/alif/ospi_xip_user.h diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 210b06c65f7..15814521958 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -122,6 +122,7 @@ SRC_C = \ mpu.c \ mpuart.c \ msc_disk.c \ + ospi_ext.c \ ospi_flash.c \ pendsv.c \ system_tick.c \ diff --git a/ports/alif/ospi_ext.c b/ports/alif/ospi_ext.c new file mode 100644 index 00000000000..48446d0622d --- /dev/null +++ b/ports/alif/ospi_ext.c @@ -0,0 +1,292 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 ALIF_CMSIS_H +#include "ospi_ext.h" +#include "ospi_xip_user.h" + +#define INST_L16bit (3) + +static void ospi_xip_disable(ospi_flash_cfg_t *ospi_cfg) { + ospi_cfg->aes_regs->aes_control &= ~AES_CONTROL_XIP_EN; +} + +static void ospi_xip_enable(ospi_flash_cfg_t *ospi_cfg) { + ospi_cfg->aes_regs->aes_control |= AES_CONTROL_XIP_EN; + #if OSPI_XIP_ENABLE_AES_DECRYPTION + ospi_cfg->aes_regs->aes_control |= (AES_CONTROL_LD_KEY | AES_CONTROL_DECRYPT_EN); + #endif +} + +// Standard SPI transfer. +void ospi_spi_transfer(ospi_flash_cfg_t *ospi_cfg, size_t len, const uint8_t *buf_out, uint8_t *buf_in) { + ospi_writel(ospi_cfg, ser, 0); + spi_disable(ospi_cfg); + + uint32_t ctrlr0 = CTRLR0_IS_MST + | (SINGLE << CTRLR0_SPI_FRF_OFFSET) + | (SPI_TMOD_TR << CTRLR0_TMOD_OFFSET) + | (CTRLR0_DFS_8bit << CTRLR0_DFS_OFFSET) + ; + + uint32_t spi_ctrlr0 = TRANS_TYPE_STANDARD; + + ospi_writel(ospi_cfg, ctrlr0, ctrlr0); + ospi_writel(ospi_cfg, ctrlr1, len - 1); + ospi_writel(ospi_cfg, spi_ctrlr0, spi_ctrlr0); + spi_enable(ospi_cfg); + + // Buffer output data in SPI FIFO. + for (int i = 0; i < len; ++i) { + ospi_writel(ospi_cfg, data_reg, buf_out[i]); + } + + // Enable the SPI peripheral. + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + + // Read in data. + for (int i = 0; i < len; ++i) { + unsigned int timeout = 100000; + while (ospi_readl(ospi_cfg, rxflr) == 0) { + if (--timeout == 0) { + return; + } + } + buf_in[i] = ospi_readl(ospi_cfg, data_reg); + } +} + +void ospi_setup_read_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_len, uint32_t addr_len, uint32_t data_len, uint32_t read_len, uint32_t wait_cycles) { + ospi_writel(ospi_cfg, ser, 0); + spi_disable(ospi_cfg); + + uint32_t val = CTRLR0_IS_MST + | (OCTAL << CTRLR0_SPI_FRF_OFFSET) + | (TMOD_RO << CTRLR0_TMOD_OFFSET) + | (data_len << CTRLR0_DFS_OFFSET); + + ospi_writel(ospi_cfg, ctrlr0, val); + ospi_writel(ospi_cfg, ctrlr1, read_len - 1); + + if (ospi_cfg->ddr_en) { + val = TRANS_TYPE_FRF_DEFINED + | (rxds << CTRLR0_SPI_RXDS_EN_OFFSET) + | (1 << CTRLR0_SPI_DDR_EN_OFFSET) + | (inst_len << CTRLR0_INST_L_OFFSET) + | (addr_len << CTRLR0_ADDR_L_OFFSET) + | (wait_cycles << CTRLR0_WAIT_CYCLES_OFFSET); + if (inst_len == OSPI_INST_L_16bit) { + val |= 1 << CTRLR0_INST_DDR_EN_OFFSET; + } + } else { + val = TRANS_TYPE_FRF_DEFINED + | (rxds << CTRLR0_SPI_RXDS_EN_OFFSET) + | (inst_len << CTRLR0_INST_L_OFFSET) + | (addr_len << CTRLR0_ADDR_L_OFFSET) + | (wait_cycles << CTRLR0_WAIT_CYCLES_OFFSET); + } + + ospi_writel(ospi_cfg, spi_ctrlr0, val); + ospi_cfg->rx_req = read_len; + spi_enable(ospi_cfg); +} + +int ospi_recv_blocking_8bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint8_t *buffer) { + ospi_writel(ospi_cfg, data_reg, command); + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + + ospi_cfg->rx_cnt = 0; + + while (ospi_cfg->rx_cnt < ospi_cfg->rx_req) { + unsigned int timeout = 100000; + while (ospi_readl(ospi_cfg, rxflr) == 0) { + if (--timeout == 0) { + return -OSPI_ETIMEDOUT; + } + } + *buffer++ = ospi_readl(ospi_cfg, data_reg); + ospi_cfg->rx_cnt++; + } + + return 0; +} + +int ospi_recv_blocking_16bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint16_t *buffer) { + ospi_writel(ospi_cfg, data_reg, command); + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + + ospi_cfg->rx_cnt = 0; + + while (ospi_cfg->rx_cnt < ospi_cfg->rx_req) { + unsigned int timeout = 100000; + while (ospi_readl(ospi_cfg, rxflr) == 0) { + if (--timeout == 0) { + return -OSPI_ETIMEDOUT; + } + } + *buffer++ = ospi_readl(ospi_cfg, data_reg); + ospi_cfg->rx_cnt++; + } + + return 0; +} + +int ospi_recv_blocking_32bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint32_t *buffer) { + ospi_writel(ospi_cfg, data_reg, command); + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + + ospi_cfg->rx_cnt = 0; + + while (ospi_cfg->rx_cnt < ospi_cfg->rx_req) { + unsigned int timeout = 100000; + while (ospi_readl(ospi_cfg, rxflr) == 0) { + if (--timeout == 0) { + return -OSPI_ETIMEDOUT; + } + } + *buffer++ = __ROR(ospi_readl(ospi_cfg, data_reg), 16); + ospi_cfg->rx_cnt++; + } + + return 0; +} + +void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_len, uint32_t addr_len, uint32_t data_len) { + ospi_writel(ospi_cfg, ser, 0); + spi_disable(ospi_cfg); + + uint32_t val = CTRLR0_IS_MST + | (OCTAL << CTRLR0_SPI_FRF_OFFSET) + | (TMOD_TO << CTRLR0_TMOD_OFFSET) + | (data_len << CTRLR0_DFS_OFFSET); + + ospi_writel(ospi_cfg, ctrlr0, val); + ospi_writel(ospi_cfg, ctrlr1, 0); + + if (ospi_cfg->ddr_en) { + val = TRANS_TYPE_FRF_DEFINED + | (rxds << CTRLR0_SPI_RXDS_EN_OFFSET) + | (1 << CTRLR0_SPI_DDR_EN_OFFSET) + | (inst_len << CTRLR0_INST_L_OFFSET) + | (addr_len << CTRLR0_ADDR_L_OFFSET) + | (0 << CTRLR0_WAIT_CYCLES_OFFSET); + if (inst_len == OSPI_INST_L_16bit) { + val |= 1 << CTRLR0_INST_DDR_EN_OFFSET; + } + } else { + val = TRANS_TYPE_FRF_DEFINED + | (rxds << CTRLR0_SPI_RXDS_EN_OFFSET) + | (inst_len << CTRLR0_INST_L_OFFSET) + | (addr_len << CTRLR0_ADDR_L_OFFSET) + | (0 << CTRLR0_WAIT_CYCLES_OFFSET); + } + + ospi_writel(ospi_cfg, spi_ctrlr0, val); + spi_enable(ospi_cfg); +} + +void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles) { + spi_disable(ospi_cfg); + + uint32_t val = CTRLR0_IS_MST + | (OCTAL << CTRLR0_SPI_FRF_OFFSET) + | (0 << CTRLR0_SCPOL_OFFSET) + | (0 << CTRLR0_SCPH_OFFSET) + | (0 << CTRLR0_SSTE_OFFSET) + | (TMOD_RO << CTRLR0_TMOD_OFFSET) + | (data_len << CTRLR0_DFS_OFFSET); + + ospi_writel(ospi_cfg, ctrlr0, val); + + val = (OCTAL << XIP_CTRL_FRF_OFFSET) + | (0x2 << XIP_CTRL_TRANS_TYPE_OFFSET) + | (ADDR_L32bit << XIP_CTRL_ADDR_L_OFFSET) + | (INST_L16bit << XIP_CTRL_INST_L_OFFSET) + | (0x0 << XIP_CTRL_MD_BITS_EN_OFFSET) + | (read_dummy_cycles << XIP_CTRL_WAIT_CYCLES_OFFSET) + | (0x1 << XIP_CTRL_DFC_HC_OFFSET) + | (ospi_cfg->ddr_en << XIP_CTRL_DDR_EN_OFFSET) + | (ospi_cfg->ddr_en << XIP_CTRL_INST_DDR_EN_OFFSET) + | (0x1 << XIP_CTRL_RXDS_EN_OFFSET) + | (0x1 << XIP_CTRL_INST_EN_OFFSET) + | (0x0 << XIP_CTRL_CONT_XFER_EN_OFFSET) + | (0x0 << XIP_CTRL_HYPERBUS_EN_OFFSET) + | (0x1 << XIP_CTRL_RXDS_SIG_EN) + | (0x0 << XIP_CTRL_XIP_MBL_OFFSET) + | (0x0 << XIP_PREFETCH_EN_OFFSET) + | (0x0 << XIP_CTRL_RXDS_VL_EN_OFFSET); + + ospi_writel(ospi_cfg, xip_ctrl, val); + + ospi_writel(ospi_cfg, rx_sample_dly, 4); + ospi_writel(ospi_cfg, txd_drive_edge, 1); + ospi_cfg->aes_regs->aes_rxds_delay = OSPI_XIP_RXDS_DELAY; + + ospi_writel(ospi_cfg, xip_mode_bits, 0x0); + ospi_writel(ospi_cfg, xip_incr_inst, incr_command); + ospi_writel(ospi_cfg, xip_wrap_inst, wrap_command); + ospi_writel(ospi_cfg, xip_ser, ospi_cfg->ser); + + spi_enable(ospi_cfg); + ospi_xip_enable(ospi_cfg); +} + +void ospi_xip_exit_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint16_t incr_command, uint16_t wrap_command) { + spi_disable(ospi_cfg); + + uint32_t val = CTRLR0_IS_MST + | (OCTAL << CTRLR0_SPI_FRF_OFFSET) + | (0 << CTRLR0_SCPOL_OFFSET) + | (0 << CTRLR0_SCPH_OFFSET) + | (0 << CTRLR0_SSTE_OFFSET) + | (TMOD_RO << CTRLR0_TMOD_OFFSET) + | (CTRLR0_DFS_32bit << CTRLR0_DFS_OFFSET); + + ospi_writel(ospi_cfg, ctrlr0, val); + + val = TRANS_TYPE_FRF_DEFINED + | ((ospi_cfg->ddr_en) << CTRLR0_SPI_DDR_EN_OFFSET) + | (2 << CTRLR0_XIP_MBL_OFFSET) + | (1 << CTRLR0_XIP_DFS_HC_OFFSET) + | (1 << CTRLR0_XIP_INST_EN_OFFSET) + | (CTRLR0_INST_L_16bit << CTRLR0_INST_L_OFFSET) + | (ospi_cfg->addrlen) << (CTRLR0_ADDR_L_OFFSET) + | (ospi_cfg->wait_cycles << CTRLR0_WAIT_CYCLES_OFFSET); + + ospi_writel(ospi_cfg, spi_ctrlr0, val); + + ospi_writel(ospi_cfg, xip_mode_bits, 0x1); + ospi_writel(ospi_cfg, xip_incr_inst, incr_command); + ospi_writel(ospi_cfg, xip_wrap_inst, wrap_command); + ospi_writel(ospi_cfg, xip_ser, ospi_cfg->ser); + ospi_writel(ospi_cfg, ser, ospi_cfg->ser); + ospi_writel(ospi_cfg, xip_cnt_time_out, 100); + + spi_enable(ospi_cfg); + + ospi_xip_enable(ospi_cfg); + ospi_xip_disable(ospi_cfg); +} diff --git a/ports/alif/ospi_ext.h b/ports/alif/ospi_ext.h new file mode 100644 index 00000000000..e467772d156 --- /dev/null +++ b/ports/alif/ospi_ext.h @@ -0,0 +1,57 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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_ALIF_OSPI_EXT_H +#define MICROPY_INCLUDED_ALIF_OSPI_EXT_H + +#include +#include "ospi_drv.h" + +#define OSPI_ETIMEDOUT (110) + +#define OSPI_INST_L_8bit (CTRLR0_INST_L_8bit) +#define OSPI_INST_L_16bit (CTRLR0_INST_L_16bit) + +#define OSPI_ADDR_L_0bit UINT32_C(0x0) +#define OSPI_ADDR_L_24bit UINT32_C(0x6) +#define OSPI_ADDR_L_32bit UINT32_C(0x8) + +#define OSPI_DATA_L_8bit (CTRLR0_DFS_8bit) +#define OSPI_DATA_L_16bit (CTRLR0_DFS_16bit) +#define OSPI_DATA_L_32bit (CTRLR0_DFS_32bit) + +void ospi_spi_transfer(ospi_flash_cfg_t *ospi_cfg, size_t len, const uint8_t *buf_out, uint8_t *buf_in); + +void ospi_setup_read_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_len, uint32_t addr_len, uint32_t data_len, uint32_t read_len, uint32_t wait_cycles); +int ospi_recv_blocking_8bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint8_t *buffer); +int ospi_recv_blocking_16bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint16_t *buffer); +int ospi_recv_blocking_32bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint32_t *buffer); + +void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_len, uint32_t addr_len, uint32_t data_len); + +void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles); +void ospi_xip_exit_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint16_t incr_command, uint16_t wrap_command); + +#endif // MICROPY_INCLUDED_ALIF_OSPI_EXT_H diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index f84761366b3..6f083de630a 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -28,72 +28,84 @@ #include "py/mphal.h" #if MICROPY_HW_ENABLE_OSPI + +#include "ospi_ext.h" #include "ospi_flash.h" #include "ospi_drv.h" +#include "ospi_xip_user.h" #include "pinconf.h" -#define CMD_RDSR (0x05) -#define CMD_WREN (0x06) -#define CMD_SEC_ERASE_32ADDR (0x21) // 4kiB sector erase with 32-bit address -#define CMD_WRVOL (0x81) -#define CMD_RD_DEVID (0x9f) - -#define WAIT_SR_TIMEOUT (1000000) +#define WAIT_SR_TIMEOUT (1000000) -// maximum bytes we can write in one SPI transfer -// limited by 256 byte FIFO buffer (can't go up to 256) -// need to use DMA to make this 256 -#define PAGE_SIZE (128) +// Generic SPI flash commands. +#define CMD_SPI_WREN (0x06) -#define ISSI_MODE_OCTAL_DDR_DQS (0xe7) +// This is the maximum number of bytes that can be written to SPI flash at once. +#define PAGE_SIZE (256) -// All OSPI1 pins use the same alternate function. -#define OSPI1_PIN_FUNCTION PINMUX_ALTERNATE_FUNCTION_1 +// All OSP0/OSPI1 pins use the same alternate function. +#define OSPI_PIN_FUNCTION PINMUX_ALTERNATE_FUNCTION_1 -typedef struct _mp_spiflash_t { +typedef struct _ospi_flash_t { + const ospi_pin_settings_t *pin; + const ospi_flash_settings_t *set; ospi_flash_cfg_t cfg; -} mp_spiflash_t; +} ospi_flash_t; -static mp_spiflash_t global_flash; +static ospi_flash_t global_flash; -// Alif version of this function can overwrite the destination buffer. -static void ospi_recv_blocking2(ospi_flash_cfg_t *ospi_cfg, uint32_t command, uint8_t *buffer) { - uint32_t val; +/******************************************************************************/ +// Generic SPI-flash helper functions. - ospi_writel(ospi_cfg, data_reg, command); - ospi_writel(ospi_cfg, ser, ospi_cfg->ser); - - ospi_cfg->rx_cnt = 0; +static void ospi_flash_wren_spi(ospi_flash_t *self) { + uint8_t buf[1] = {CMD_SPI_WREN}; + ospi_spi_transfer(&self->cfg, 1, buf, buf); +} - while (ospi_cfg->rx_cnt < ospi_cfg->rx_req) { - unsigned int timeout = 100000; - while (ospi_readl(ospi_cfg, rxflr) == 0) { - if (--timeout == 0) { - return; - } - } - val = ospi_readl(ospi_cfg, data_reg); - *buffer++ = (uint8_t)val; - ospi_cfg->rx_cnt++; +static int ospi_flash_read_cmd(ospi_flash_t *self, uint32_t cmd, uint8_t cmd_dummy_cycles, size_t len, uint8_t *dest) { + int ret = 0; + if (self->set->inst_len == OSPI_INST_L_8bit) { + ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_0bit, OSPI_DATA_L_8bit, len, cmd_dummy_cycles); + ret = ospi_recv_blocking_8bit_data(&self->cfg, cmd, dest); + } else { + uint16_t dest16 = 0; + ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_32bit, OSPI_DATA_L_16bit, len, cmd_dummy_cycles); + ospi_push(&self->cfg, cmd); + ret = ospi_recv_blocking_16bit_data(&self->cfg, 0 /* addr */, &dest16); + *dest = dest16; } + return ret; } -static int mp_spiflash_read_cmd(mp_spiflash_t *self, uint8_t cmd, size_t len, uint8_t *dest) { - ospi_setup_read(&self->cfg, 0, len, 8); - ospi_recv_blocking2(&self->cfg, cmd, dest); +static int ospi_flash_write_cmd_addr(ospi_flash_t *self, uint32_t cmd, uint32_t addr_len, uint32_t addr) { + if (self->set->inst_len == OSPI_INST_L_8bit) { + ospi_setup_write_ext(&self->cfg, false, self->set->inst_len, addr_len, OSPI_DATA_L_8bit); + } else { + ospi_setup_write_ext(&self->cfg, self->set->rxds, self->set->inst_len, addr_len, OSPI_DATA_L_16bit); + } + if (addr_len == OSPI_ADDR_L_0bit) { + ospi_send_blocking(&self->cfg, cmd); + } else { + ospi_push(&self->cfg, cmd); + ospi_send_blocking(&self->cfg, addr); + } return 0; } -static int mp_spiflash_write_cmd(mp_spiflash_t *self, uint8_t cmd) { - ospi_setup_write(&self->cfg, 0); - ospi_send_blocking(&self->cfg, cmd); - return 0; +static int ospi_flash_write_cmd(ospi_flash_t *self, uint32_t cmd) { + return ospi_flash_write_cmd_addr(self, cmd, OSPI_ADDR_L_0bit, 0); } -static int mp_spiflash_wait_sr(mp_spiflash_t *self, uint8_t mask, uint8_t val, uint32_t timeout) { +static uint32_t ospi_flash_read_id_spi(ospi_flash_t *self) { + uint8_t buf[4] = {0x9f, 0, 0, 0}; + ospi_spi_transfer(&self->cfg, 4, buf, buf); + return buf[1] | buf[2] << 8 | buf[3] << 16; +} + +static int ospi_flash_wait_sr(ospi_flash_t *self, uint8_t mask, uint8_t val, uint32_t timeout) { do { - uint8_t sr; - int ret = mp_spiflash_read_cmd(self, CMD_RDSR, 1, &sr); + uint8_t sr = 0; + int ret = ospi_flash_read_cmd(self, self->set->read_sr, self->set->read_sr_dummy_cycles, 1, &sr); if (ret != 0) { return ret; } @@ -105,144 +117,251 @@ static int mp_spiflash_wait_sr(mp_spiflash_t *self, uint8_t mask, uint8_t val, u return -MP_ETIMEDOUT; } -static int mp_spiflash_wait_wel1(mp_spiflash_t *self) { - return mp_spiflash_wait_sr(self, 2, 2, WAIT_SR_TIMEOUT); +static int ospi_flash_wait_wel1(ospi_flash_t *self) { + return ospi_flash_wait_sr(self, 2, 2, WAIT_SR_TIMEOUT); } -static int mp_spiflash_wait_wip0(mp_spiflash_t *self) { - return mp_spiflash_wait_sr(self, 1, 0, WAIT_SR_TIMEOUT); +static int ospi_flash_wait_wip0(ospi_flash_t *self) { + return ospi_flash_wait_sr(self, 1, 0, WAIT_SR_TIMEOUT); } -static uint32_t ospi_flash_read_id(mp_spiflash_t *self) { - uint8_t buf[8]; - ospi_setup_read(&self->cfg, 0, 3, ospi_flash_settings.read_id_dummy_cycles); - ospi_recv_blocking2(&self->cfg, CMD_RD_DEVID, buf); +static uint32_t ospi_flash_read_id(ospi_flash_t *self) { + uint8_t buf[4] = {0}; + if (self->set->inst_len == OSPI_INST_L_8bit) { + ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_0bit, OSPI_DATA_L_8bit, 3, self->set->read_id_dummy_cycles); + ospi_recv_blocking_8bit_data(&self->cfg, self->set->read_id, buf); + } else { + ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_32bit, OSPI_DATA_L_16bit, 4, self->set->read_id_dummy_cycles); + ospi_push(&self->cfg, self->set->read_id); + // Read 8-bit values because data is in SDR mode for read id. + ospi_recv_blocking_8bit_data(&self->cfg, 0, buf); + } return buf[0] | buf[1] << 8 | buf[2] << 16; } -static void ospi_flash_write_reg_sdr(mp_spiflash_t *self, uint8_t cmd, uint8_t addr, uint8_t value) { - mp_spiflash_write_cmd(self, CMD_WREN); - ospi_setup_write_sdr(&self->cfg, 6); - ospi_push(&self->cfg, cmd); - ospi_push(&self->cfg, 0x00); - ospi_push(&self->cfg, 0x00); +/******************************************************************************/ +// Functions specific to ISSI flash chips. + +int ospi_flash_issi_octal_switch(ospi_flash_t *self) { + // Switch SPI flash to Octal DDR mode. + const uint8_t cmd_wrvol = 0x81; + const uint8_t issi_mode_octal_ddr_dqs = 0xe7; + ospi_flash_wren_spi(self); + uint8_t buf[5] = {cmd_wrvol, 0, 0, 0, issi_mode_octal_ddr_dqs}; + ospi_spi_transfer(&self->cfg, sizeof(buf), buf, buf); + self->cfg.ddr_en = 1; + return 0; +} + +/******************************************************************************/ +// Functions specific to MX flash chips. + +int ospi_flash_mx_octal_switch(ospi_flash_t *self) { + // Switch SPI flash to Octal SDR or DDR mode (SOPI or DOPI) by writing to CR2. + const uint8_t cmd_wrcr2 = 0x72; + const uint8_t mx_mode_enable_sopi = 0x01; + const uint8_t mx_mode_enable_dopi = 0x02; + uint8_t mx_mode; + if (self->set->octal_mode == OSPI_FLASH_OCTAL_MODE_DDD) { + mx_mode = mx_mode_enable_dopi; + } else { + mx_mode = mx_mode_enable_sopi; + } + ospi_flash_wren_spi(self); + uint8_t buf[6] = {cmd_wrcr2, 0, 0, 0, 0, mx_mode}; + ospi_spi_transfer(&self->cfg, sizeof(buf), buf, buf); + if (self->set->octal_mode == OSPI_FLASH_OCTAL_MODE_DDD) { + self->cfg.ddr_en = 1; + } else { + self->cfg.ddr_en = 0; + } + return 0; +} + +static uint8_t ospi_flash_mx_read_cr_helper(ospi_flash_t *self, uint16_t command, uint32_t addr) { + // TODO: currently only works in DDR mode + + uint16_t buf[1] = {0}; + ospi_setup_read_ext(&self->cfg, self->set->rxds, OSPI_INST_L_16bit, OSPI_ADDR_L_32bit, OSPI_DATA_L_16bit, 1, 4); + ospi_push(&self->cfg, command); + ospi_recv_blocking_16bit_data(&self->cfg, addr, buf); + return buf[0] & 0xff; +} + +uint8_t ospi_flash_mx_read_cr(ospi_flash_t *self) { + return ospi_flash_mx_read_cr_helper(self, 0x15ea, 0); +} + +uint8_t ospi_flash_mx_read_cr2(ospi_flash_t *self, uint32_t addr) { + return ospi_flash_mx_read_cr_helper(self, 0x718e, addr); +} + +static int ospi_flash_mx_write_cr_helper(ospi_flash_t *self, uint16_t command, uint32_t addr, uint8_t value) { + // TODO: currently only works in DDR mode + + // Enable writes so that the register can be modified. + ospi_flash_write_cmd(self, self->set->write_en); + int ret = ospi_flash_wait_wel1(self); + if (ret < 0) { + return ret; + } + + // Do the write. + ospi_setup_write_ext(&self->cfg, self->set->rxds, OSPI_INST_L_16bit, OSPI_ADDR_L_32bit, OSPI_DATA_L_16bit); + ospi_push(&self->cfg, command); ospi_push(&self->cfg, addr); - ospi_send_blocking(&self->cfg, value); + ospi_push(&self->cfg, value << 8); // in DDR mode, MSByte contains the register value to write + ospi_writel((&self->cfg), ser, self->cfg.ser); + while ((ospi_readl((&self->cfg), sr) & (SR_TF_EMPTY | SR_BUSY)) != SR_TF_EMPTY) { + } + + // Wait for the write to finish. + return ospi_flash_wait_wip0(self); +} + +int ospi_flash_mx_write_cr(ospi_flash_t *self, uint8_t value) { + return ospi_flash_mx_write_cr_helper(self, 0x01fe, 1, value); } +int ospi_flash_mx_write_cr2(ospi_flash_t *self, uint32_t addr, uint8_t value) { + return ospi_flash_mx_write_cr_helper(self, 0x728d, addr, value); +} + +/******************************************************************************/ +// SPI flash initialisation. + int ospi_flash_init(void) { - mp_spiflash_t *self = &global_flash; + ospi_flash_t *self = &global_flash; + + const ospi_pin_settings_t *pin = &ospi_pin_settings; + const ospi_flash_settings_t *set = &ospi_flash_settings; + self->pin = pin; + self->set = set; uint32_t pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; - pinconf_set(pin_OSPI1_CS->port, pin_OSPI1_CS->pin, OSPI1_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA); - pinconf_set(pin_OSPI1_SCLK->port, pin_OSPI1_SCLK->pin, OSPI1_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST); - pinconf_set(pin_OSPI1_D0->port, pin_OSPI1_D0->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D1->port, pin_OSPI1_D1->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D2->port, pin_OSPI1_D2->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D3->port, pin_OSPI1_D3->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - #if defined(pin_OSPI1_D4) - pinconf_set(pin_OSPI1_D4->port, pin_OSPI1_D4->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D5->port, pin_OSPI1_D5->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D6->port, pin_OSPI1_D6->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin_OSPI1_D7->port, pin_OSPI1_D7->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - #endif - #if defined(pin_OSPI1_RXDS) - pinconf_set(pin_OSPI1_RXDS->port, pin_OSPI1_RXDS->pin, OSPI1_PIN_FUNCTION, pad_ctrl); - #endif - - #if defined(pin_OSPI1_RXDS) - if (pin_OSPI1_RXDS->port == PORT_10 && pin_OSPI1_RXDS->pin == PIN_7) { - // Alif: P5_6 is needed to support proper alt function selection of P10_7. - pinconf_set(PORT_5, PIN_6, OSPI1_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_cs->port, pin->pin_cs->pin, OSPI_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA); + pinconf_set(pin->pin_clk->port, pin->pin_clk->pin, OSPI_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST); + if (pin->pin_rwds != NULL) { + pinconf_set(pin->pin_rwds->port, pin->pin_rwds->pin, OSPI_PIN_FUNCTION, pad_ctrl); + if (pin->pin_rwds->port == PORT_10 && pin->pin_rwds->pin == PIN_7) { + // Alif: P5_6 is needed to support proper alt function selection of P10_7. + pinconf_set(PORT_5, PIN_6, OSPI_PIN_FUNCTION, pad_ctrl); + } + } + pinconf_set(pin->pin_d0->port, pin->pin_d0->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d1->port, pin->pin_d1->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d3->port, pin->pin_d3->pin, OSPI_PIN_FUNCTION, pad_ctrl); + if (pin->pin_d4 != NULL) { + pinconf_set(pin->pin_d4->port, pin->pin_d4->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d5->port, pin->pin_d5->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d6->port, pin->pin_d6->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d7->port, pin->pin_d7->pin, OSPI_PIN_FUNCTION, pad_ctrl); } - #endif // Reset the SPI flash. - mp_hal_pin_output(pin_OSPI1_RESET); - mp_hal_pin_low(pin_OSPI1_RESET); - mp_hal_delay_us(30); - mp_hal_pin_high(pin_OSPI1_RESET); + mp_hal_pin_output(pin->pin_reset); + mp_hal_pin_low(pin->pin_reset); + mp_hal_delay_us(100); + mp_hal_pin_high(pin->pin_reset); + mp_hal_delay_us(1000); // Configure the OSPI peripheral. - self->cfg.regs = (ssi_regs_t *)OSPI1_BASE; - self->cfg.aes_regs = (aes_regs_t *)AES1_BASE; - self->cfg.xip_base = (volatile void *)OSPI1_XIP_BASE; - self->cfg.ser = 1; + if (pin->peripheral_number == 0) { + self->cfg.regs = (ssi_regs_t *)OSPI0_BASE; + self->cfg.aes_regs = (aes_regs_t *)AES0_BASE; + self->cfg.xip_base = (volatile void *)OSPI0_XIP_BASE; + } else { + self->cfg.regs = (ssi_regs_t *)OSPI1_BASE; + self->cfg.aes_regs = (aes_regs_t *)AES1_BASE; + self->cfg.xip_base = (volatile void *)OSPI1_XIP_BASE; + } + self->cfg.ser = 1; // enable slave select self->cfg.addrlen = 8; // 32-bit address length - self->cfg.ospi_clock = ospi_flash_settings.freq_mhz; + self->cfg.ospi_clock = set->freq_hz; self->cfg.ddr_en = 0; self->cfg.wait_cycles = 0; // used only for ospi_xip_exit ospi_init(&self->cfg); - if (ospi_flash_settings.is_oct && ospi_flash_settings.is_ddr) { - // Switch SPI flash to Octal DDR mode. - ospi_flash_write_reg_sdr(self, CMD_WRVOL, 0x00, ISSI_MODE_OCTAL_DDR_DQS); - self->cfg.ddr_en = 1; + // Check the device ID before attempting to switch to octal mode (if needed). + if (ospi_flash_read_id_spi(self) != set->jedec_id) { + return -1; } - // Check the device ID. - if (ospi_flash_read_id(self) != ospi_flash_settings.jedec_id) { - return -1; + // Switch to octal mode if needed. + if (set->octal_switch != NULL) { + set->octal_switch(self); + + // Check the device ID after switching mode. + if (ospi_flash_read_id(self) != set->jedec_id) { + return -1; + } } return 0; } +/******************************************************************************/ +// Top-level read/erase/write functions. + int ospi_flash_erase_sector(uint32_t addr) { - mp_spiflash_t *self = &global_flash; + ospi_flash_t *self = &global_flash; - mp_spiflash_write_cmd(self, CMD_WREN); - int ret = mp_spiflash_wait_wel1(self); + ospi_flash_write_cmd(self, self->set->write_en); + int ret = ospi_flash_wait_wel1(self); if (ret < 0) { return ret; } - ospi_setup_write(&self->cfg, 8 /* 32-bit addr len*/); - ospi_push(&self->cfg, CMD_SEC_ERASE_32ADDR); - ospi_send_blocking(&self->cfg, addr); + ospi_flash_write_cmd_addr(self, self->set->erase_command, OSPI_ADDR_L_32bit, addr); - return mp_spiflash_wait_wip0(self); + return ospi_flash_wait_wip0(self); } int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest) { // OSPI FIFO is limited to 256 bytes. Need DMA to get a longer read. - mp_spiflash_t *self = &global_flash; + ospi_flash_t *self = &global_flash; while (len) { - uint32_t l = len; + uint32_t l = len / 4; if (l > 256) { l = 256; } - ospi_setup_read(&self->cfg, 8 /* 32-bit addr len*/, l, ospi_flash_settings.read_dummy_cycles); - ospi_push(&self->cfg, ospi_flash_settings.read_command); - ospi_recv_blocking2(&self->cfg, addr, dest); - addr += l; - len -= l; - dest += l; + ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_32bit, OSPI_DATA_L_32bit, l, self->set->read_dummy_cycles); + ospi_push(&self->cfg, self->set->read_command); + ospi_recv_blocking_32bit_data(&self->cfg, addr, (uint32_t *)dest); + addr += l * 4; + len -= l * 4; + dest += l * 4; } return 0; } static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src) { - mp_spiflash_t *self = &global_flash; + ospi_flash_t *self = &global_flash; - mp_spiflash_write_cmd(self, CMD_WREN); - int ret = mp_spiflash_wait_wel1(self); + ospi_flash_write_cmd(self, self->set->write_en); + int ret = ospi_flash_wait_wel1(self); if (ret < 0) { return ret; } - ospi_setup_write(&self->cfg, 8 /* 32-bit addr len*/); - ospi_push(&self->cfg, ospi_flash_settings.write_command); + ospi_setup_write_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_32bit, OSPI_DATA_L_32bit); + ospi_push(&self->cfg, self->set->write_command); ospi_push(&self->cfg, addr); - while (--len) { - ospi_push(&self->cfg, *src++); + + const uint32_t *src32 = (const uint32_t *)src; + for (; len; len -= 4) { + ospi_push(&self->cfg, __ROR(*src32++, 16)); } - ospi_send_blocking(&self->cfg, *src); - return mp_spiflash_wait_wip0(self); + ospi_writel((&self->cfg), ser, self->cfg.ser); + while ((ospi_readl((&self->cfg), sr) & (SR_TF_EMPTY | SR_BUSY)) != SR_TF_EMPTY) { + } + + return ospi_flash_wait_wip0(self); } int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { @@ -264,4 +383,5 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { } return ret; } + #endif // MICROPY_HW_ENABLE_OSPI diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 945dc4bb07b..6f5d8327b41 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -26,24 +26,67 @@ #ifndef MICROPY_INCLUDED_ALIF_OSPI_FLASH_H #define MICROPY_INCLUDED_ALIF_OSPI_FLASH_H -#include -#include +#include "py/mphal.h" + +// Format of command, address and data phases. +enum { + OSPI_FLASH_OCTAL_MODE_SSS, + OSPI_FLASH_OCTAL_MODE_SDD, + OSPI_FLASH_OCTAL_MODE_DDD, +}; + +struct _ospi_flash_t; + +typedef struct _ospi_pin_settings_t { + uint32_t peripheral_number; + const mp_hal_pin_obj_t pin_reset; + const mp_hal_pin_obj_t pin_cs; + const mp_hal_pin_obj_t pin_clk; + const mp_hal_pin_obj_t pin_rwds; + const mp_hal_pin_obj_t pin_d0; + const mp_hal_pin_obj_t pin_d1; + const mp_hal_pin_obj_t pin_d2; + const mp_hal_pin_obj_t pin_d3; + const mp_hal_pin_obj_t pin_d4; + const mp_hal_pin_obj_t pin_d5; + const mp_hal_pin_obj_t pin_d6; + const mp_hal_pin_obj_t pin_d7; +} ospi_pin_settings_t; typedef struct _ospi_flash_settings_t { uint32_t jedec_id; - uint32_t freq_mhz; - bool is_quad : 1; - bool is_oct : 1; - bool is_ddr : 1; + uint32_t freq_hz; + int (*octal_switch)(struct _ospi_flash_t *); + uint8_t octal_mode; + bool rxds; + uint8_t inst_len; + uint8_t xip_data_len; + uint16_t read_sr; + uint8_t read_sr_dummy_cycles; + uint16_t read_id; uint8_t read_id_dummy_cycles; + uint16_t write_en; + uint16_t read_command; uint8_t read_dummy_cycles; - uint8_t read_command; - uint8_t write_command; + uint16_t write_command; + uint16_t erase_command; } ospi_flash_settings_t; -// Provided by the board when it enables OSPI1. +// Provided by the board when it enables OSPI. +extern const ospi_pin_settings_t ospi_pin_settings; extern const ospi_flash_settings_t ospi_flash_settings; +// Functions specific to ISSI flash chips. +int ospi_flash_issi_octal_switch(struct _ospi_flash_t *self); + +// Functions specific to MX flash chips. +int ospi_flash_mx_octal_switch(struct _ospi_flash_t *self); +uint8_t ospi_flash_mx_read_cr(struct _ospi_flash_t *self); +uint8_t ospi_flash_mx_read_cr2(struct _ospi_flash_t *self, uint32_t addr); +int ospi_flash_mx_write_cr(struct _ospi_flash_t *self, uint8_t value); +int ospi_flash_mx_write_cr2(struct _ospi_flash_t *self, uint32_t addr, uint8_t value); + +// SPI flash interface. int ospi_flash_init(void); int ospi_flash_erase_sector(uint32_t addr); int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest); diff --git a/ports/alif/ospi_xip_user.h b/ports/alif/ospi_xip_user.h deleted file mode 100644 index a15baff2a8b..00000000000 --- a/ports/alif/ospi_xip_user.h +++ /dev/null @@ -1,5 +0,0 @@ -// This file is needed by ospi_xip/source/ospi/ospi_drv.c. -#define OSPI_XIP_ENABLE_AES_DECRYPTION (0) -#define OSPI_XIP_RX_SAMPLE_DELAY (4) -#define OSPI_XIP_DDR_DRIVE_EDGE (1) -#define OSPI_XIP_RXDS_DELAY (12) From 41e16886b1bec870b601d8eb14cd4b05426d8015 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Oct 2024 23:07:35 +1100 Subject: [PATCH 0483/2098] alif/ospi_flash: Enter XIP mode when flash is idle. Signed-off-by: Damien George --- ports/alif/ospi_flash.c | 47 ++++++++++++++++++++++++++++++++++++++++- ports/alif/ospi_flash.h | 1 + 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 6f083de630a..be575d9ed77 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -50,10 +50,14 @@ typedef struct _ospi_flash_t { const ospi_pin_settings_t *pin; const ospi_flash_settings_t *set; ospi_flash_cfg_t cfg; + bool xip_active; } ospi_flash_t; static ospi_flash_t global_flash; +static int ospi_flash_xip_enter(ospi_flash_t *self); +static int ospi_flash_xip_exit(ospi_flash_t *self); + /******************************************************************************/ // Generic SPI-flash helper functions. @@ -299,6 +303,34 @@ int ospi_flash_init(void) { } } + // Enter XIP mode. It will be disabled during flash read/erase/write. + ospi_flash_xip_enter(self); + + return 0; +} + +uintptr_t ospi_flash_get_xip_base(void) { + ospi_flash_t *self = &global_flash; + return (uintptr_t)self->cfg.xip_base; +} + +static int ospi_flash_xip_enter(ospi_flash_t *self) { + if (!self->xip_active) { + uint32_t irq_state = disable_irq(); + self->xip_active = true; + ospi_xip_enter_16bit_cmd(&self->cfg, self->set->xip_data_len, self->set->read_command, self->set->read_command, self->set->read_dummy_cycles); + enable_irq(irq_state); + } + return 0; +} + +static int ospi_flash_xip_exit(ospi_flash_t *self) { + if (self->xip_active) { + uint32_t irq_state = disable_irq(); + ospi_xip_exit_16bit_cmd(&self->cfg, self->set->read_command, self->set->read_command); + self->xip_active = false; + enable_irq(irq_state); + } return 0; } @@ -308,21 +340,29 @@ int ospi_flash_init(void) { int ospi_flash_erase_sector(uint32_t addr) { ospi_flash_t *self = &global_flash; + ospi_flash_xip_exit(self); + ospi_flash_write_cmd(self, self->set->write_en); int ret = ospi_flash_wait_wel1(self); if (ret < 0) { + ospi_flash_xip_enter(self); return ret; } ospi_flash_write_cmd_addr(self, self->set->erase_command, OSPI_ADDR_L_32bit, addr); + ret = ospi_flash_wait_wip0(self); - return ospi_flash_wait_wip0(self); + ospi_flash_xip_enter(self); + return ret; } int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest) { // OSPI FIFO is limited to 256 bytes. Need DMA to get a longer read. + // Note that direct reading is much faster than using XIP memory-mapped read. ospi_flash_t *self = &global_flash; + ospi_flash_xip_exit(self); + while (len) { uint32_t l = len / 4; if (l > 256) { @@ -336,6 +376,7 @@ int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest) { dest += l * 4; } + ospi_flash_xip_enter(self); return 0; } @@ -365,6 +406,8 @@ static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src } int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { + ospi_flash_xip_exit(&global_flash); + int ret = 0; uint32_t offset = addr & (PAGE_SIZE - 1); while (len) { @@ -381,6 +424,8 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { src += rest; offset = 0; } + + ospi_flash_xip_enter(&global_flash); return ret; } diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 6f5d8327b41..8fe69764a37 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -88,6 +88,7 @@ int ospi_flash_mx_write_cr2(struct _ospi_flash_t *self, uint32_t addr, uint8_t v // SPI flash interface. int ospi_flash_init(void); +uintptr_t ospi_flash_get_xip_base(void); int ospi_flash_erase_sector(uint32_t addr); int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest); int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src); From 3d17f634785f4e151d883b1d1110db3a66673efd Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 21 Nov 2024 07:55:10 +0100 Subject: [PATCH 0484/2098] alif/mpu: Define constants for MPU regions. Signed-off-by: iabdalkader --- ports/alif/mpu.c | 34 +++++++++++++++++----------------- ports/alif/mpu.h | 17 ++++++++++++----- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/ports/alif/mpu.c b/ports/alif/mpu.c index 11c6273fcb3..471eff20a2c 100644 --- a/ports/alif/mpu.c +++ b/ports/alif/mpu.c @@ -29,48 +29,48 @@ #include ALIF_CMSIS_H static const ARM_MPU_Region_t mpu_table[] __STARTUP_RO_DATA_ATTRIBUTE = { - { /* SRAM0 - 4MB : RO-0, NP-1, XN-0 */ + [MP_MPU_REGION_SRAM0] = { /* SRAM0 - 4MB : RO-0, NP-1, XN-0 */ .RBAR = ARM_MPU_RBAR(0x02000000, ARM_MPU_SH_NON, 0, 1, 0), - .RLAR = ARM_MPU_RLAR(0x023FFFFF, MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT) + .RLAR = ARM_MPU_RLAR(0x023FFFFF, MP_MPU_ATTR_NORMAL_WT_RA_TRANSIENT) }, - { /* SRAM1 - 2.5MB : RO-0, NP-1, XN-0 */ + [MP_MPU_REGION_SRAM1] = { /* SRAM1 - 2.5MB : RO-0, NP-1, XN-0 */ .RBAR = ARM_MPU_RBAR(0x08000000, ARM_MPU_SH_NON, 0, 1, 0), - .RLAR = ARM_MPU_RLAR(0x0827FFFF, MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA) + .RLAR = ARM_MPU_RLAR(0x0827FFFF, MP_MPU_ATTR_NORMAL_WB_RA_WA) }, - { /* Host Peripherals - 16MB : RO-0, NP-1, XN-1 */ + [MP_MPU_REGION_HOST_PERIPHERALS] = { /* Host Peripherals - 16MB : RO-0, NP-1, XN-1 */ .RBAR = ARM_MPU_RBAR(0x1A000000, ARM_MPU_SH_NON, 0, 1, 1), - .RLAR = ARM_MPU_RLAR(0x1AFFFFFF, MP_MPU_ATTR_INDEX_DEVICE_nGnRE) + .RLAR = ARM_MPU_RLAR(0x1AFFFFFF, MP_MPU_ATTR_DEVICE_nGnRE) }, - { /* MRAM - 5.5MB : RO-1, NP-1, XN-0 */ + [MP_MPU_REGION_MRAM] = { /* MRAM - 5.5MB : RO-1, NP-1, XN-0 */ .RBAR = ARM_MPU_RBAR(0x80000000, ARM_MPU_SH_NON, 1, 1, 0), - .RLAR = ARM_MPU_RLAR(0x8057FFFF, MP_MPU_ATTR_INDEX_NORMAL_WT_RA) + .RLAR = ARM_MPU_RLAR(0x8057FFFF, MP_MPU_ATTR_NORMAL_WT_RA) }, - { /* OSPI Regs - 16MB : RO-0, NP-1, XN-1 */ + [MP_MPU_REGION_OSPI_REGISTERS] = { /* OSPI Regs - 16MB : RO-0, NP-1, XN-1 */ .RBAR = ARM_MPU_RBAR(0x83000000, ARM_MPU_SH_NON, 0, 1, 1), - .RLAR = ARM_MPU_RLAR(0x83FFFFFF, MP_MPU_ATTR_INDEX_DEVICE_nGnRE) + .RLAR = ARM_MPU_RLAR(0x83FFFFFF, MP_MPU_ATTR_DEVICE_nGnRE) }, - { /* OSPI0 XIP flash - 512MB : RO-1, NP-1, XN-0 */ + [MP_MPU_REGION_OSPI0_XIP] = { /* OSPI0 XIP flash - 512MB : RO-1, NP-1, XN-0 */ .RBAR = ARM_MPU_RBAR(0xA0000000, ARM_MPU_SH_NON, 1, 1, 0), - .RLAR = ARM_MPU_RLAR(0xBFFFFFFF, MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE) + .RLAR = ARM_MPU_RLAR(0xBFFFFFFF, MP_MPU_ATTR_NORMAL_NON_CACHEABLE) }, }; void MPU_Load_Regions(void) { // Configure memory attributes. - ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT, + ARM_MPU_SetMemAttr(MP_MPU_ATTR_NORMAL_WT_RA_TRANSIENT, ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(0, 0, 1, 0), ARM_MPU_ATTR_MEMORY_(0, 0, 1, 0))); - ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_DEVICE_nGnRE, + ARM_MPU_SetMemAttr(MP_MPU_ATTR_DEVICE_nGnRE, ARM_MPU_ATTR(ARM_MPU_ATTR_DEVICE, ARM_MPU_ATTR_DEVICE_nGnRE)); - ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA, + ARM_MPU_SetMemAttr(MP_MPU_ATTR_NORMAL_WB_RA_WA, ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1, 1, 1, 1), ARM_MPU_ATTR_MEMORY_(1, 1, 1, 1))); - ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_WT_RA, + ARM_MPU_SetMemAttr(MP_MPU_ATTR_NORMAL_WT_RA, ARM_MPU_ATTR(ARM_MPU_ATTR_MEMORY_(1, 0, 1, 0), ARM_MPU_ATTR_MEMORY_(1, 0, 1, 0))); - ARM_MPU_SetMemAttr(MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE, + ARM_MPU_SetMemAttr(MP_MPU_ATTR_NORMAL_NON_CACHEABLE, ARM_MPU_ATTR(ARM_MPU_ATTR_NON_CACHEABLE, ARM_MPU_ATTR_NON_CACHEABLE)); // Load the MPU regions from the table. diff --git a/ports/alif/mpu.h b/ports/alif/mpu.h index 87a7518a126..c259c8585ad 100644 --- a/ports/alif/mpu.h +++ b/ports/alif/mpu.h @@ -24,8 +24,15 @@ * THE SOFTWARE. */ -#define MP_MPU_ATTR_INDEX_NORMAL_WT_RA_TRANSIENT (0) -#define MP_MPU_ATTR_INDEX_DEVICE_nGnRE (1) -#define MP_MPU_ATTR_INDEX_NORMAL_WB_RA_WA (2) -#define MP_MPU_ATTR_INDEX_NORMAL_WT_RA (3) -#define MP_MPU_ATTR_INDEX_NORMAL_NON_CACHEABLE (4) +#define MP_MPU_ATTR_NORMAL_WT_RA_TRANSIENT (0) +#define MP_MPU_ATTR_DEVICE_nGnRE (1) +#define MP_MPU_ATTR_NORMAL_WB_RA_WA (2) +#define MP_MPU_ATTR_NORMAL_WT_RA (3) +#define MP_MPU_ATTR_NORMAL_NON_CACHEABLE (4) + +#define MP_MPU_REGION_SRAM0 (0) +#define MP_MPU_REGION_SRAM1 (1) +#define MP_MPU_REGION_HOST_PERIPHERALS (2) +#define MP_MPU_REGION_MRAM (3) +#define MP_MPU_REGION_OSPI_REGISTERS (4) +#define MP_MPU_REGION_OSPI0_XIP (5) From 5152a1f04da3699bc35419bc40bf204760eac66c Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 21 Nov 2024 07:56:02 +0100 Subject: [PATCH 0485/2098] alif/mpmetalport: Add Open-AMP MPU region. Define an MPU region for Open-AMP and remove hard-coded attribute. Signed-off-by: iabdalkader --- ports/alif/mpmetalport.c | 18 ++++++------------ ports/alif/mpmetalport.h | 3 ++- ports/alif/mpu.h | 1 + 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ports/alif/mpmetalport.c b/ports/alif/mpmetalport.c index f80d039c26d..a7d4025468a 100644 --- a/ports/alif/mpmetalport.c +++ b/ports/alif/mpmetalport.c @@ -48,28 +48,22 @@ int metal_sys_init(const struct metal_init_params *params) { hwsem_reset(METAL_HSEM_REMOTE); #endif - // Enable the hardware semaphore IRQ. - NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); - NVIC_SetPriority(METAL_HSEM_IRQn, IRQ_PRI_HWSEM); - NVIC_EnableIRQ(METAL_HSEM_IRQn); - // If cache management is not enabled, configure the MPU to disable // caching for the entire Open-AMP shared memory region. #ifndef VIRTIO_USE_DCACHE ARM_MPU_Disable(); - // NOTE: The startup code uses the first 4 attributes. - #define MEMATTR_IDX_NORMAL_NON_CACHEABLE 4 - ARM_MPU_SetMemAttr(MEMATTR_IDX_NORMAL_NON_CACHEABLE, ARM_MPU_ATTR( - ARM_MPU_ATTR_NON_CACHEABLE, - ARM_MPU_ATTR_NON_CACHEABLE)); MPU->RNR = METAL_MPU_REGION_ID; - MPU->RBAR = ARM_MPU_RBAR(METAL_MPU_REGION_BASE, ARM_MPU_SH_NON, 0, 1, 0); // RO-0, NP-1, XN-0 - MPU->RLAR = ARM_MPU_RLAR(METAL_MPU_REGION_BASE + METAL_MPU_REGION_SIZE - 1, MEMATTR_IDX_NORMAL_NON_CACHEABLE); + MPU->RBAR = ARM_MPU_RBAR(METAL_MPU_REGION_BASE, ARM_MPU_SH_NON, 0, 1, 1); // RO-0, NP-1, XN-1 + MPU->RLAR = ARM_MPU_RLAR(METAL_MPU_REGION_BASE + METAL_MPU_REGION_SIZE - 1, MP_MPU_ATTR_NORMAL_NON_CACHEABLE); ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk); #endif metal_bus_register(&metal_generic_bus); + // Enable the hardware semaphore IRQ. + NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); + NVIC_SetPriority(METAL_HSEM_IRQn, IRQ_PRI_HWSEM); + NVIC_EnableIRQ(METAL_HSEM_IRQn); return 0; } diff --git a/ports/alif/mpmetalport.h b/ports/alif/mpmetalport.h index 5cd697c0e9c..7a428dbc6c6 100644 --- a/ports/alif/mpmetalport.h +++ b/ports/alif/mpmetalport.h @@ -31,6 +31,7 @@ #include #include "py/mphal.h" #include "py/runtime.h" +#include "mpu.h" #define METAL_HAVE_STDATOMIC_H 0 #define METAL_HAVE_FUTEX_H 0 @@ -66,7 +67,7 @@ #define METAL_SHM_ADDR ((metal_phys_addr_t)(_openamp_shm_region_start + METAL_RSC_SIZE)) #define METAL_SHM_SIZE ((size_t)(_openamp_shm_region_end - _openamp_shm_region_start - METAL_RSC_SIZE)) -#define METAL_MPU_REGION_ID (9) // NOTE: The startup code uses the first 9 regions. +#define METAL_MPU_REGION_ID (MP_MPU_REGION_OPENAMP) #define METAL_MPU_REGION_BASE ((uint32_t)_openamp_shm_region_start) #define METAL_MPU_REGION_SIZE (0x00010000U) diff --git a/ports/alif/mpu.h b/ports/alif/mpu.h index c259c8585ad..88fbe011206 100644 --- a/ports/alif/mpu.h +++ b/ports/alif/mpu.h @@ -36,3 +36,4 @@ #define MP_MPU_REGION_MRAM (3) #define MP_MPU_REGION_OSPI_REGISTERS (4) #define MP_MPU_REGION_OSPI0_XIP (5) +#define MP_MPU_REGION_OPENAMP (6) From 1585080ff0ee3075e49fce01effddfc51644b5ed Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 14 Dec 2024 15:47:59 +0100 Subject: [PATCH 0486/2098] alif/ospi_flash: Fix XIP for 8-bit instructions (ISSI). Disable XIP instruction DDR for 8-bit instructions. Signed-off-by: iabdalkader --- ports/alif/ospi_ext.c | 15 ++++++++------- ports/alif/ospi_ext.h | 4 ++-- ports/alif/ospi_flash.c | 5 +++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ports/alif/ospi_ext.c b/ports/alif/ospi_ext.c index 48446d0622d..3226c244697 100644 --- a/ports/alif/ospi_ext.c +++ b/ports/alif/ospi_ext.c @@ -28,8 +28,6 @@ #include "ospi_ext.h" #include "ospi_xip_user.h" -#define INST_L16bit (3) - static void ospi_xip_disable(ospi_flash_cfg_t *ospi_cfg) { ospi_cfg->aes_regs->aes_control &= ~AES_CONTROL_XIP_EN; } @@ -208,7 +206,7 @@ void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_l spi_enable(ospi_cfg); } -void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles) { +void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles) { spi_disable(ospi_cfg); uint32_t val = CTRLR0_IS_MST @@ -224,12 +222,11 @@ void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uin val = (OCTAL << XIP_CTRL_FRF_OFFSET) | (0x2 << XIP_CTRL_TRANS_TYPE_OFFSET) | (ADDR_L32bit << XIP_CTRL_ADDR_L_OFFSET) - | (INST_L16bit << XIP_CTRL_INST_L_OFFSET) + | (inst_len << XIP_CTRL_INST_L_OFFSET) | (0x0 << XIP_CTRL_MD_BITS_EN_OFFSET) | (read_dummy_cycles << XIP_CTRL_WAIT_CYCLES_OFFSET) | (0x1 << XIP_CTRL_DFC_HC_OFFSET) | (ospi_cfg->ddr_en << XIP_CTRL_DDR_EN_OFFSET) - | (ospi_cfg->ddr_en << XIP_CTRL_INST_DDR_EN_OFFSET) | (0x1 << XIP_CTRL_RXDS_EN_OFFSET) | (0x1 << XIP_CTRL_INST_EN_OFFSET) | (0x0 << XIP_CTRL_CONT_XFER_EN_OFFSET) @@ -239,6 +236,10 @@ void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uin | (0x0 << XIP_PREFETCH_EN_OFFSET) | (0x0 << XIP_CTRL_RXDS_VL_EN_OFFSET); + if (inst_len == OSPI_INST_L_16bit) { + val |= 1 << XIP_CTRL_INST_DDR_EN_OFFSET; + } + ospi_writel(ospi_cfg, xip_ctrl, val); ospi_writel(ospi_cfg, rx_sample_dly, 4); @@ -254,7 +255,7 @@ void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uin ospi_xip_enable(ospi_cfg); } -void ospi_xip_exit_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint16_t incr_command, uint16_t wrap_command) { +void ospi_xip_exit_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint16_t incr_command, uint16_t wrap_command) { spi_disable(ospi_cfg); uint32_t val = CTRLR0_IS_MST @@ -272,7 +273,7 @@ void ospi_xip_exit_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint16_t incr_command, | (2 << CTRLR0_XIP_MBL_OFFSET) | (1 << CTRLR0_XIP_DFS_HC_OFFSET) | (1 << CTRLR0_XIP_INST_EN_OFFSET) - | (CTRLR0_INST_L_16bit << CTRLR0_INST_L_OFFSET) + | (inst_len << CTRLR0_INST_L_OFFSET) | (ospi_cfg->addrlen) << (CTRLR0_ADDR_L_OFFSET) | (ospi_cfg->wait_cycles << CTRLR0_WAIT_CYCLES_OFFSET); diff --git a/ports/alif/ospi_ext.h b/ports/alif/ospi_ext.h index e467772d156..e5a2b50beb6 100644 --- a/ports/alif/ospi_ext.h +++ b/ports/alif/ospi_ext.h @@ -51,7 +51,7 @@ int ospi_recv_blocking_32bit_data(ospi_flash_cfg_t *ospi_cfg, uint32_t command, void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_len, uint32_t addr_len, uint32_t data_len); -void ospi_xip_enter_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles); -void ospi_xip_exit_16bit_cmd(ospi_flash_cfg_t *ospi_cfg, uint16_t incr_command, uint16_t wrap_command); +void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles); +void ospi_xip_exit_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint16_t incr_command, uint16_t wrap_command); #endif // MICROPY_INCLUDED_ALIF_OSPI_EXT_H diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index be575d9ed77..c3ae50a9ee4 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -318,7 +318,8 @@ static int ospi_flash_xip_enter(ospi_flash_t *self) { if (!self->xip_active) { uint32_t irq_state = disable_irq(); self->xip_active = true; - ospi_xip_enter_16bit_cmd(&self->cfg, self->set->xip_data_len, self->set->read_command, self->set->read_command, self->set->read_dummy_cycles); + ospi_xip_enter_ext(&self->cfg, self->set->inst_len, self->set->xip_data_len, + self->set->read_command, self->set->read_command, self->set->read_dummy_cycles); enable_irq(irq_state); } return 0; @@ -327,7 +328,7 @@ static int ospi_flash_xip_enter(ospi_flash_t *self) { static int ospi_flash_xip_exit(ospi_flash_t *self) { if (self->xip_active) { uint32_t irq_state = disable_irq(); - ospi_xip_exit_16bit_cmd(&self->cfg, self->set->read_command, self->set->read_command); + ospi_xip_exit_ext(&self->cfg, self->set->inst_len, self->set->read_command, self->set->read_command); self->xip_active = false; enable_irq(irq_state); } From aec030004f019155334f56f61cf230fcf63ad759 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 16 Dec 2024 15:52:09 +0100 Subject: [PATCH 0487/2098] alif/ospi_flash: Support flash device auto-detection in runtime. This commit enables detecting the flash device in runtime, and uses the settings of the detected device instead of board-defined flash settings. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 26 +++++++++-- ports/alif/ospi_flash.h | 4 +- ports/alif/ospi_flash_settings.h | 79 ++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 ports/alif/ospi_flash_settings.h diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index c3ae50a9ee4..8bffd69805c 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -238,9 +238,9 @@ int ospi_flash_init(void) { ospi_flash_t *self = &global_flash; const ospi_pin_settings_t *pin = &ospi_pin_settings; - const ospi_flash_settings_t *set = &ospi_flash_settings; + const ospi_flash_settings_t *set = NULL; + self->pin = pin; - self->set = set; uint32_t pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; @@ -283,16 +283,32 @@ int ospi_flash_init(void) { } self->cfg.ser = 1; // enable slave select self->cfg.addrlen = 8; // 32-bit address length - self->cfg.ospi_clock = set->freq_hz; self->cfg.ddr_en = 0; self->cfg.wait_cycles = 0; // used only for ospi_xip_exit + self->cfg.ospi_clock = 100000; // use 100KHz for detection. ospi_init(&self->cfg); - // Check the device ID before attempting to switch to octal mode (if needed). - if (ospi_flash_read_id_spi(self) != set->jedec_id) { + // Read the device ID. + uint32_t jedec_id = ospi_flash_read_id_spi(self); + + // Auto-detect the flash. + for (size_t i = 0; i < ospi_flash_settings_len; i++) { + set = &ospi_flash_settings[i]; + if (jedec_id == set->jedec_id) { + self->set = set; + break; + } + } + + if (self->set == NULL) { + // Flash part is not supported. return -1; } + // Switch to the higher frequency. + self->cfg.ospi_clock = set->freq_hz; + ospi_init(&self->cfg); + // Switch to octal mode if needed. if (set->octal_switch != NULL) { set->octal_switch(self); diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 8fe69764a37..20846d44713 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -27,6 +27,7 @@ #define MICROPY_INCLUDED_ALIF_OSPI_FLASH_H #include "py/mphal.h" +#include "ospi_flash_settings.h" // Format of command, address and data phases. enum { @@ -74,7 +75,8 @@ typedef struct _ospi_flash_settings_t { // Provided by the board when it enables OSPI. extern const ospi_pin_settings_t ospi_pin_settings; -extern const ospi_flash_settings_t ospi_flash_settings; +extern const ospi_flash_settings_t ospi_flash_settings[]; +extern const size_t ospi_flash_settings_len; // Functions specific to ISSI flash chips. int ospi_flash_issi_octal_switch(struct _ospi_flash_t *self); diff --git a/ports/alif/ospi_flash_settings.h b/ports/alif/ospi_flash_settings.h new file mode 100644 index 00000000000..c409f856dfa --- /dev/null +++ b/ports/alif/ospi_flash_settings.h @@ -0,0 +1,79 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 __OSPI_FLASH_SETTINGS_H__ +#define __OSPI_FLASH_SETTINGS_H__ +#include +#include "ospi_flash.h" + +// Macronix MX25 +#define OSPI_FLASH_SETTINGS_MX25 \ + .octal_switch = ospi_flash_mx_octal_switch, \ + .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ + .rxds = true, \ + .inst_len = OSPI_INST_L_16bit, \ + .xip_data_len = OSPI_DATA_L_16bit, \ + .read_sr = 0x05fa, \ + .read_sr_dummy_cycles = 4, \ + .write_en = 0x06f9, \ + .read_id = 0x9f60, \ + .read_id_dummy_cycles = 4, \ + .read_command = 0xee11, \ + .write_command = 0x12ed, \ + .erase_command = 0x21de + +// Everspin EM. +#define OSPI_FLASH_SETTINGS_EM \ + .octal_switch = ospi_flash_issi_octal_switch, \ + .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ + .rxds = false, \ + .inst_len = OSPI_INST_L_8bit, \ + .xip_data_len = OSPI_DATA_L_16bit, \ + .read_sr = 0x05, \ + .read_sr_dummy_cycles = 8, \ + .write_en = 0x06, \ + .read_id = 0x9f, \ + .read_id_dummy_cycles = 8, \ + .read_command = 0xfd, \ + .write_command = 0xc2, \ + .erase_command = 0x21 + +// ISSI IS25. +#define OSPI_FLASH_SETTINGS_IS25 \ + .octal_switch = ospi_flash_issi_octal_switch, \ + .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ + .rxds = true, \ + .inst_len = OSPI_INST_L_8bit, \ + .xip_data_len = OSPI_DATA_L_16bit, \ + .read_sr = 0x05, \ + .read_sr_dummy_cycles = 8, \ + .write_en = 0x06, \ + .read_id = 0x9f, \ + .read_id_dummy_cycles = 8, \ + .read_command = 0xfd, \ + .write_command = 0xc2, \ + .erase_command = 0x21 +#endif // __OSPI_FLASH_SETTINGS_H__ From 8807f8d01bc29d7c09a71a329877a1e8ad617fa8 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 16 Dec 2024 15:52:51 +0100 Subject: [PATCH 0488/2098] alif/ospi_flash: Configure dummy cycles. The default dummy cycles may not match the actual flash frequency supported by a certain board. For example, the MX chip uses 20 dummy cycles by default which supports up to 200MHz DDR, but the maximum frequency supported by the AE3 board is 50MHz DDR. So the dummy cycles for this board can be as low as 6. It's important to set the correct dummy cycles, as it results in doubling the XIP read speed, in the case of the AE3 board. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 43 ++++++++++++++++++++++++-------- ports/alif/ospi_flash.h | 6 ++--- ports/alif/ospi_flash_settings.h | 6 ++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 8bffd69805c..279227aa36d 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -146,13 +146,19 @@ static uint32_t ospi_flash_read_id(ospi_flash_t *self) { /******************************************************************************/ // Functions specific to ISSI flash chips. -int ospi_flash_issi_octal_switch(ospi_flash_t *self) { - // Switch SPI flash to Octal DDR mode. +int ospi_flash_issi_init(ospi_flash_t *self) { const uint8_t cmd_wrvol = 0x81; + + // Configure dummy cycles. + ospi_flash_wren_spi(self); + uint8_t buf0[5] = {cmd_wrvol, 0, 0, 1, self->set->read_dummy_cycles}; + ospi_spi_transfer(&self->cfg, sizeof(buf0), buf0, buf0); + + // Switch SPI flash to Octal DDR mode. const uint8_t issi_mode_octal_ddr_dqs = 0xe7; ospi_flash_wren_spi(self); - uint8_t buf[5] = {cmd_wrvol, 0, 0, 0, issi_mode_octal_ddr_dqs}; - ospi_spi_transfer(&self->cfg, sizeof(buf), buf, buf); + uint8_t buf1[5] = {cmd_wrvol, 0, 0, 0, issi_mode_octal_ddr_dqs}; + ospi_spi_transfer(&self->cfg, sizeof(buf1), buf1, buf1); self->cfg.ddr_en = 1; return 0; } @@ -160,8 +166,7 @@ int ospi_flash_issi_octal_switch(ospi_flash_t *self) { /******************************************************************************/ // Functions specific to MX flash chips. -int ospi_flash_mx_octal_switch(ospi_flash_t *self) { - // Switch SPI flash to Octal SDR or DDR mode (SOPI or DOPI) by writing to CR2. +int ospi_flash_mx_init(ospi_flash_t *self) { const uint8_t cmd_wrcr2 = 0x72; const uint8_t mx_mode_enable_sopi = 0x01; const uint8_t mx_mode_enable_dopi = 0x02; @@ -171,9 +176,26 @@ int ospi_flash_mx_octal_switch(ospi_flash_t *self) { } else { mx_mode = mx_mode_enable_sopi; } + + // Configure dummy cycles. + uint8_t ddc_value = 0; + const uint8_t ospi_flash_mx_ddc[][2] = { + {20, 0}, {18, 1}, {16, 2}, {14, 3}, {12, 4}, {10, 5}, {8, 6}, {6, 7} + }; + for (size_t i = 0; i < MP_ARRAY_SIZE(ospi_flash_mx_ddc); i++) { + if (self->set->read_dummy_cycles == ospi_flash_mx_ddc[i][0]) { + ddc_value = ospi_flash_mx_ddc[i][1]; + break; + } + } ospi_flash_wren_spi(self); - uint8_t buf[6] = {cmd_wrcr2, 0, 0, 0, 0, mx_mode}; - ospi_spi_transfer(&self->cfg, sizeof(buf), buf, buf); + uint8_t buf0[6] = {cmd_wrcr2, 0, 0, 3, 0, ddc_value}; + ospi_spi_transfer(&self->cfg, sizeof(buf0), buf0, buf0); + + // Switch SPI flash to Octal SDR or DDR mode. + ospi_flash_wren_spi(self); + uint8_t buf1[6] = {cmd_wrcr2, 0, 0, 0, 0, mx_mode}; + ospi_spi_transfer(&self->cfg, sizeof(buf1), buf1, buf1); if (self->set->octal_mode == OSPI_FLASH_OCTAL_MODE_DDD) { self->cfg.ddr_en = 1; } else { @@ -310,8 +332,8 @@ int ospi_flash_init(void) { ospi_init(&self->cfg); // Switch to octal mode if needed. - if (set->octal_switch != NULL) { - set->octal_switch(self); + if (set->flash_init != NULL) { + set->flash_init(self); // Check the device ID after switching mode. if (ospi_flash_read_id(self) != set->jedec_id) { @@ -321,7 +343,6 @@ int ospi_flash_init(void) { // Enter XIP mode. It will be disabled during flash read/erase/write. ospi_flash_xip_enter(self); - return 0; } diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 20846d44713..39d270d7d54 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -57,7 +57,7 @@ typedef struct _ospi_pin_settings_t { typedef struct _ospi_flash_settings_t { uint32_t jedec_id; uint32_t freq_hz; - int (*octal_switch)(struct _ospi_flash_t *); + int (*flash_init)(struct _ospi_flash_t *); uint8_t octal_mode; bool rxds; uint8_t inst_len; @@ -79,10 +79,10 @@ extern const ospi_flash_settings_t ospi_flash_settings[]; extern const size_t ospi_flash_settings_len; // Functions specific to ISSI flash chips. -int ospi_flash_issi_octal_switch(struct _ospi_flash_t *self); +int ospi_flash_issi_init(struct _ospi_flash_t *self); // Functions specific to MX flash chips. -int ospi_flash_mx_octal_switch(struct _ospi_flash_t *self); +int ospi_flash_mx_init(struct _ospi_flash_t *self); uint8_t ospi_flash_mx_read_cr(struct _ospi_flash_t *self); uint8_t ospi_flash_mx_read_cr2(struct _ospi_flash_t *self, uint32_t addr); int ospi_flash_mx_write_cr(struct _ospi_flash_t *self, uint8_t value); diff --git a/ports/alif/ospi_flash_settings.h b/ports/alif/ospi_flash_settings.h index c409f856dfa..c8605275cdc 100644 --- a/ports/alif/ospi_flash_settings.h +++ b/ports/alif/ospi_flash_settings.h @@ -31,7 +31,7 @@ // Macronix MX25 #define OSPI_FLASH_SETTINGS_MX25 \ - .octal_switch = ospi_flash_mx_octal_switch, \ + .flash_init = ospi_flash_mx_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ .rxds = true, \ .inst_len = OSPI_INST_L_16bit, \ @@ -47,7 +47,7 @@ // Everspin EM. #define OSPI_FLASH_SETTINGS_EM \ - .octal_switch = ospi_flash_issi_octal_switch, \ + .flash_init = ospi_flash_issi_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ .rxds = false, \ .inst_len = OSPI_INST_L_8bit, \ @@ -63,7 +63,7 @@ // ISSI IS25. #define OSPI_FLASH_SETTINGS_IS25 \ - .octal_switch = ospi_flash_issi_octal_switch, \ + .flash_init = ospi_flash_issi_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ .rxds = true, \ .inst_len = OSPI_INST_L_8bit, \ From 872f3d70d37d189525783137e53a50878314d37d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 14 Dec 2024 18:19:42 +0100 Subject: [PATCH 0489/2098] alif/ospi_flash: Add negative clock pin. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 28 ++++++++++++++++------------ ports/alif/ospi_flash.h | 3 ++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 279227aa36d..8eaf9235501 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -264,26 +264,30 @@ int ospi_flash_init(void) { self->pin = pin; - uint32_t pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; + uint32_t ck_pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST; + uint32_t io_pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; pinconf_set(pin->pin_cs->port, pin->pin_cs->pin, OSPI_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA); - pinconf_set(pin->pin_clk->port, pin->pin_clk->pin, OSPI_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST); + pinconf_set(pin->pin_clk_p->port, pin->pin_clk_p->pin, OSPI_PIN_FUNCTION, ck_pad_ctrl); + if (pin->pin_clk_n != NULL) { + pinconf_set(pin->pin_clk_n->port, pin->pin_clk_n->pin, OSPI_PIN_FUNCTION, ck_pad_ctrl); + } if (pin->pin_rwds != NULL) { - pinconf_set(pin->pin_rwds->port, pin->pin_rwds->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_rwds->port, pin->pin_rwds->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); if (pin->pin_rwds->port == PORT_10 && pin->pin_rwds->pin == PIN_7) { // Alif: P5_6 is needed to support proper alt function selection of P10_7. - pinconf_set(PORT_5, PIN_6, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(PORT_5, PIN_6, OSPI_PIN_FUNCTION, io_pad_ctrl); } } - pinconf_set(pin->pin_d0->port, pin->pin_d0->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d1->port, pin->pin_d1->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d3->port, pin->pin_d3->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d0->port, pin->pin_d0->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d1->port, pin->pin_d1->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d3->port, pin->pin_d3->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); if (pin->pin_d4 != NULL) { - pinconf_set(pin->pin_d4->port, pin->pin_d4->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d5->port, pin->pin_d5->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d6->port, pin->pin_d6->pin, OSPI_PIN_FUNCTION, pad_ctrl); - pinconf_set(pin->pin_d7->port, pin->pin_d7->pin, OSPI_PIN_FUNCTION, pad_ctrl); + pinconf_set(pin->pin_d4->port, pin->pin_d4->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d5->port, pin->pin_d5->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d6->port, pin->pin_d6->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d7->port, pin->pin_d7->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); } // Reset the SPI flash. diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 39d270d7d54..affa4e61022 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -42,7 +42,8 @@ typedef struct _ospi_pin_settings_t { uint32_t peripheral_number; const mp_hal_pin_obj_t pin_reset; const mp_hal_pin_obj_t pin_cs; - const mp_hal_pin_obj_t pin_clk; + const mp_hal_pin_obj_t pin_clk_p; + const mp_hal_pin_obj_t pin_clk_n; const mp_hal_pin_obj_t pin_rwds; const mp_hal_pin_obj_t pin_d0; const mp_hal_pin_obj_t pin_d1; From 0709936653895e0de499e0a7019afb80eda7425c Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 19 Dec 2024 09:47:04 +0100 Subject: [PATCH 0490/2098] alif/ospi_flash: Enable pull-up IO2/WP. Leaving this pin low in combination with the default EM settings enables flash protection for the EM flash. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 8eaf9235501..d5aa99f2b49 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -281,7 +281,7 @@ int ospi_flash_init(void) { } pinconf_set(pin->pin_d0->port, pin->pin_d0->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); pinconf_set(pin->pin_d1->port, pin->pin_d1->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, io_pad_ctrl | PADCTRL_DRIVER_DISABLED_PULL_UP); pinconf_set(pin->pin_d3->port, pin->pin_d3->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); if (pin->pin_d4 != NULL) { pinconf_set(pin->pin_d4->port, pin->pin_d4->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); From df06bf91a5a78c89eaa7b6b7d157df83c576553f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 20 Dec 2024 08:57:08 +0100 Subject: [PATCH 0491/2098] alif/ospi_ext: Optimize XIP speed. This change increases XIP read speed to ~30Mbytes/s at 50MHz DDR: - Enable continuous mode. - Remove hard-coded settings. - Set XIP continuous mode timeout. The prefetch remains disabled. Although enabling the prefetch gives the best performance for the CPU in XIP mode, it must be disabled when the NPU accesses the OSPI flash. Signed-off-by: iabdalkader --- ports/alif/ospi_ext.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/alif/ospi_ext.c b/ports/alif/ospi_ext.c index 3226c244697..e3389d8bd54 100644 --- a/ports/alif/ospi_ext.c +++ b/ports/alif/ospi_ext.c @@ -229,7 +229,7 @@ void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t | (ospi_cfg->ddr_en << XIP_CTRL_DDR_EN_OFFSET) | (0x1 << XIP_CTRL_RXDS_EN_OFFSET) | (0x1 << XIP_CTRL_INST_EN_OFFSET) - | (0x0 << XIP_CTRL_CONT_XFER_EN_OFFSET) + | (0x1 << XIP_CTRL_CONT_XFER_EN_OFFSET) | (0x0 << XIP_CTRL_HYPERBUS_EN_OFFSET) | (0x1 << XIP_CTRL_RXDS_SIG_EN) | (0x0 << XIP_CTRL_XIP_MBL_OFFSET) @@ -242,14 +242,15 @@ void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t ospi_writel(ospi_cfg, xip_ctrl, val); - ospi_writel(ospi_cfg, rx_sample_dly, 4); - ospi_writel(ospi_cfg, txd_drive_edge, 1); + ospi_writel(ospi_cfg, rx_sample_dly, OSPI_XIP_RX_SAMPLE_DELAY); + ospi_writel(ospi_cfg, txd_drive_edge, OSPI_XIP_DDR_DRIVE_EDGE); ospi_cfg->aes_regs->aes_rxds_delay = OSPI_XIP_RXDS_DELAY; ospi_writel(ospi_cfg, xip_mode_bits, 0x0); ospi_writel(ospi_cfg, xip_incr_inst, incr_command); ospi_writel(ospi_cfg, xip_wrap_inst, wrap_command); ospi_writel(ospi_cfg, xip_ser, ospi_cfg->ser); + ospi_writel(ospi_cfg, xip_cnt_time_out, 100); spi_enable(ospi_cfg); ospi_xip_enable(ospi_cfg); From 602bc86b6d8ebd200029568c060b878146050da1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 8 Jan 2025 15:12:18 +0100 Subject: [PATCH 0492/2098] alif/ospi_flash: Use OSPI in XIP mode only. The OSPI controller supports concurrent direct/XIP accesses, there's no need to disable XIP on direct access. In addition to improving the performance, this change lays the groundwork for supporting access by the HP and HE cores simultaneously. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 39 ++++++++------------------------------- ports/alif/ospi_flash.h | 4 ++++ 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index d5aa99f2b49..7400a678b22 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -55,9 +55,6 @@ typedef struct _ospi_flash_t { static ospi_flash_t global_flash; -static int ospi_flash_xip_enter(ospi_flash_t *self); -static int ospi_flash_xip_exit(ospi_flash_t *self); - /******************************************************************************/ // Generic SPI-flash helper functions. @@ -188,6 +185,7 @@ int ospi_flash_mx_init(ospi_flash_t *self) { break; } } + ospi_flash_wren_spi(self); uint8_t buf0[6] = {cmd_wrcr2, 0, 0, 3, 0, ddc_value}; ospi_spi_transfer(&self->cfg, sizeof(buf0), buf0, buf0); @@ -355,7 +353,7 @@ uintptr_t ospi_flash_get_xip_base(void) { return (uintptr_t)self->cfg.xip_base; } -static int ospi_flash_xip_enter(ospi_flash_t *self) { +int ospi_flash_xip_enter(ospi_flash_t *self) { if (!self->xip_active) { uint32_t irq_state = disable_irq(); self->xip_active = true; @@ -366,7 +364,7 @@ static int ospi_flash_xip_enter(ospi_flash_t *self) { return 0; } -static int ospi_flash_xip_exit(ospi_flash_t *self) { +int ospi_flash_xip_exit(ospi_flash_t *self) { if (self->xip_active) { uint32_t irq_state = disable_irq(); ospi_xip_exit_ext(&self->cfg, self->set->inst_len, self->set->read_command, self->set->read_command); @@ -382,43 +380,23 @@ static int ospi_flash_xip_exit(ospi_flash_t *self) { int ospi_flash_erase_sector(uint32_t addr) { ospi_flash_t *self = &global_flash; - ospi_flash_xip_exit(self); - ospi_flash_write_cmd(self, self->set->write_en); int ret = ospi_flash_wait_wel1(self); if (ret < 0) { - ospi_flash_xip_enter(self); return ret; } ospi_flash_write_cmd_addr(self, self->set->erase_command, OSPI_ADDR_L_32bit, addr); ret = ospi_flash_wait_wip0(self); - ospi_flash_xip_enter(self); + SCB_InvalidateDCache_by_Addr(global_flash.cfg.xip_base + addr, MICROPY_HW_FLASH_BLOCK_SIZE_BYTES); return ret; } int ospi_flash_read(uint32_t addr, uint32_t len, uint8_t *dest) { - // OSPI FIFO is limited to 256 bytes. Need DMA to get a longer read. - // Note that direct reading is much faster than using XIP memory-mapped read. ospi_flash_t *self = &global_flash; - - ospi_flash_xip_exit(self); - - while (len) { - uint32_t l = len / 4; - if (l > 256) { - l = 256; - } - ospi_setup_read_ext(&self->cfg, self->set->rxds, self->set->inst_len, OSPI_ADDR_L_32bit, OSPI_DATA_L_32bit, l, self->set->read_dummy_cycles); - ospi_push(&self->cfg, self->set->read_command); - ospi_recv_blocking_32bit_data(&self->cfg, addr, (uint32_t *)dest); - addr += l * 4; - len -= l * 4; - dest += l * 4; - } - - ospi_flash_xip_enter(self); + // Perform an XIP read (memcpy) + memcpy(dest, (void *)self->cfg.xip_base + addr, len); return 0; } @@ -448,10 +426,9 @@ static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src } int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { - ospi_flash_xip_exit(&global_flash); - int ret = 0; uint32_t offset = addr & (PAGE_SIZE - 1); + while (len) { size_t rest = PAGE_SIZE - offset; if (rest > len) { @@ -467,7 +444,7 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { offset = 0; } - ospi_flash_xip_enter(&global_flash); + SCB_InvalidateDCache_by_Addr(global_flash.cfg.xip_base + addr, len); return ret; } diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index affa4e61022..0095ee2d428 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -89,6 +89,10 @@ uint8_t ospi_flash_mx_read_cr2(struct _ospi_flash_t *self, uint32_t addr); int ospi_flash_mx_write_cr(struct _ospi_flash_t *self, uint8_t value); int ospi_flash_mx_write_cr2(struct _ospi_flash_t *self, uint32_t addr, uint8_t value); +// XIP control +int ospi_flash_xip_enter(struct _ospi_flash_t *self); +int ospi_flash_xip_exit(struct _ospi_flash_t *self); + // SPI flash interface. int ospi_flash_init(void); uintptr_t ospi_flash_get_xip_base(void); From 92f056d58f74cfd3986aaef1eea08f4f948fe03a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Feb 2025 10:14:34 +0100 Subject: [PATCH 0493/2098] alif/ospi_flash: Add 16-bit words swap flash setting. The byte order (endianness) seems to be swapped when read in 8D-8D-8D in XIP mode, for most flashes, with the exception of MX which seems to swap half-words. This commit adds a flash setting to allow parts to enable half-word swap when data is written, to fix this issue. By default, only endianness is fixed. Tested with both MX and ISSI parts on AE3, flash test and simple file write/read. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 12 ++++++++++-- ports/alif/ospi_flash.h | 1 + ports/alif/ospi_flash_settings.h | 5 ++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 7400a678b22..f2f95a04cf2 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -414,8 +414,16 @@ static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src ospi_push(&self->cfg, addr); const uint32_t *src32 = (const uint32_t *)src; - for (; len; len -= 4) { - ospi_push(&self->cfg, __ROR(*src32++, 16)); + if (self->set->bswap16) { + // MX flashes swap 16-bit words when read in 8D-8D-8D. + for (; len; len -= 4) { + ospi_push(&self->cfg, __ROR(*src32++, 16)); + } + } else { + // For the rest of the flashes, we just correct the endianness. + for (; len; len -= 4) { + ospi_push(&self->cfg, __REV(*src32++)); + } } ospi_writel((&self->cfg), ser, self->cfg.ser); diff --git a/ports/alif/ospi_flash.h b/ports/alif/ospi_flash.h index 0095ee2d428..f909f4a43c8 100644 --- a/ports/alif/ospi_flash.h +++ b/ports/alif/ospi_flash.h @@ -61,6 +61,7 @@ typedef struct _ospi_flash_settings_t { int (*flash_init)(struct _ospi_flash_t *); uint8_t octal_mode; bool rxds; + bool bswap16; uint8_t inst_len; uint8_t xip_data_len; uint16_t read_sr; diff --git a/ports/alif/ospi_flash_settings.h b/ports/alif/ospi_flash_settings.h index c8605275cdc..72403c26b67 100644 --- a/ports/alif/ospi_flash_settings.h +++ b/ports/alif/ospi_flash_settings.h @@ -34,6 +34,7 @@ .flash_init = ospi_flash_mx_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ .rxds = true, \ + .bswap16 = true, \ .inst_len = OSPI_INST_L_16bit, \ .xip_data_len = OSPI_DATA_L_16bit, \ .read_sr = 0x05fa, \ @@ -50,6 +51,7 @@ .flash_init = ospi_flash_issi_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ .rxds = false, \ + .bswap16 = false, \ .inst_len = OSPI_INST_L_8bit, \ .xip_data_len = OSPI_DATA_L_16bit, \ .read_sr = 0x05, \ @@ -65,7 +67,8 @@ #define OSPI_FLASH_SETTINGS_IS25 \ .flash_init = ospi_flash_issi_init, \ .octal_mode = OSPI_FLASH_OCTAL_MODE_DDD, \ - .rxds = true, \ + .rxds = false, \ + .bswap16 = false, \ .inst_len = OSPI_INST_L_8bit, \ .xip_data_len = OSPI_DATA_L_16bit, \ .read_sr = 0x05, \ From ff6ed730c534a809ea32648b0b30832b2993aaae Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 5 Feb 2025 19:57:40 +0100 Subject: [PATCH 0494/2098] alif/se_services: Use EUI extension for unique id. The right service call to get UID is SERVICES_system_get_eui_extension which returns an 8 bytes UID. Signed-off-by: iabdalkader --- ports/alif/modmachine.c | 2 +- ports/alif/se_services.c | 7 ++----- ports/alif/se_services.h | 2 +- ports/alif/usbd.c | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index 29a70305dd5..b77a207fe2c 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -38,7 +38,7 @@ static void mp_machine_idle(void) { } static mp_obj_t mp_machine_unique_id(void) { - uint8_t id[5]; + uint8_t id[8] = {0}; se_services_get_unique_id(id); return mp_obj_new_bytes(id, sizeof(id)); } diff --git a/ports/alif/se_services.c b/ports/alif/se_services.c index cbf66bfac6c..e79a5b97c89 100644 --- a/ports/alif/se_services.c +++ b/ports/alif/se_services.c @@ -139,12 +139,9 @@ void se_services_dump_device_data(void) { printf("\n"); } -void se_services_get_unique_id(uint8_t id[5]) { +void se_services_get_unique_id(uint8_t id[8]) { uint32_t error_code; - SERVICES_version_data_t data; - SERVICES_system_get_device_data(se_services_handle, &data, &error_code); - // The MfgData has 5 bytes of valid data, at least on REV_B2. - memcpy(id, data.MfgData, 5); + SERVICES_system_get_eui_extension(se_services_handle, false, id, &error_code); } __attribute__((noreturn)) void se_services_reset_soc(void) { diff --git a/ports/alif/se_services.h b/ports/alif/se_services.h index b1079130c16..87deb055929 100644 --- a/ports/alif/se_services.h +++ b/ports/alif/se_services.h @@ -30,7 +30,7 @@ void se_services_init(void); void se_services_dump_device_data(void); -void se_services_get_unique_id(uint8_t id[5]); +void se_services_get_unique_id(uint8_t id[8]); __attribute__((noreturn)) void se_services_reset_soc(void); uint64_t se_services_rand64(void); uint32_t se_services_enable_clock(clock_enable_t clock, bool enable); diff --git a/ports/alif/usbd.c b/ports/alif/usbd.c index da16905d43c..8d841d188c3 100644 --- a/ports/alif/usbd.c +++ b/ports/alif/usbd.c @@ -33,7 +33,7 @@ #include "se_services.h" void mp_usbd_port_get_serial_number(char *serial_buf) { - uint8_t id[5]; + uint8_t id[8] = {0}; se_services_get_unique_id(id); MP_STATIC_ASSERT(sizeof(id) * 2 <= MICROPY_HW_USB_DESC_STR_MAX); mp_usbd_hex_str(serial_buf, id, sizeof(id)); From 039df0c884ef0169257b3ab31255be8cd45caacf Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 3 Feb 2025 06:54:37 +0100 Subject: [PATCH 0495/2098] alif/modmachine: Implement proper low-power modes. Lightsleep current is around 23mA. Deepsleep current is sub 50uA. Signed-off-by: iabdalkader --- ports/alif/alif.mk | 1 + ports/alif/modmachine.c | 56 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 15814521958..35f316cae0e 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -182,6 +182,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/common/source/system_utils.c \ Device/common/source/tcm_partition.c \ Device/common/source/tgu_M55.c \ + Device/common/source/pm.c \ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ drivers/source/adc.c \ drivers/source/mhu_driver.c \ diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index b77a207fe2c..98d7bffbad9 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -28,6 +28,9 @@ // extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE. #include "se_services.h" +#include "tusb.h" + +extern void dcd_uninit(void); #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ @@ -70,15 +73,56 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_raise_NotImplementedError(NULL); } -static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { - mp_int_t delay = -1; - if (n_args == 1) { - delay = mp_obj_get_int(args[0]); +#if MICROPY_HW_ENABLE_USBDEV +static void mp_machine_enable_usb(bool enable) { + if (enable) { + // Initialize TinyUSB and DCD. + tusb_init(); + } else { + // Disconnect USB device. + tud_disconnect(); + // Deinitialize TinyUSB. + tud_deinit(TUD_OPT_RHPORT); + // Deinitialize DCD (disables IRQs). + dcd_uninit(); } - mp_hal_delay_ms(delay); +} +#endif + +static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { + #if MICROPY_HW_ENABLE_USBDEV + mp_machine_enable_usb(false); + #endif + + #ifdef MICROPY_BOARD_ENTER_STANDBY + MICROPY_BOARD_ENTER_STANDBY(); + #endif + + // This enters the deepest possible CPU sleep state, without + // losing CPU state. CPU and subsystem power will remain on. + pm_core_enter_deep_sleep(); + + #ifdef MICROPY_BOARD_EXIT_STANDBY + MICROPY_BOARD_EXIT_STANDBY(); + #endif + + #if MICROPY_HW_ENABLE_USBDEV + mp_machine_enable_usb(true); + #endif } NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { - mp_machine_lightsleep(n_args, args); + #if MICROPY_HW_ENABLE_USBDEV + mp_machine_enable_usb(false); + #endif + + #ifdef MICROPY_BOARD_ENTER_STOP + MICROPY_BOARD_ENTER_STOP(); + #endif + + // If power is removed from the subsystem, the function does + // not return, and the CPU will reboot when/if the subsystem + // is next powered up. + pm_core_enter_deep_sleep_request_subsys_off(); mp_machine_reset(); } From 82bae652eb43ac3ce8226d08c1ce0047ffa76191 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 9 Jan 2025 19:58:25 +0100 Subject: [PATCH 0496/2098] alif: Add support for pin alternate function selection. Signed-off-by: iabdalkader --- ports/alif/alif.mk | 2 + ports/alif/mcu/ensemble_pin_alt.csv | 130 ++++++++++++++++++++++++++++ ports/alif/mcu/make-pins.py | 28 ++++-- ports/alif/mphalport.c | 50 +++++++++++ ports/alif/mphalport.h | 41 +++++++++ 5 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 ports/alif/mcu/ensemble_pin_alt.csv diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 35f316cae0e..b338e67c66c 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -51,6 +51,7 @@ INC += -Itinyusb_port GEN_PIN_MKPINS = mcu/make-pins.py GEN_PIN_PREFIX = mcu/pins_prefix.c GEN_PINS_BOARD_CSV = $(BOARD_DIR)/pins.csv +GEN_PINS_MCU_CSV = mcu/ensemble_pin_alt.csv GEN_PINS_SRC = $(BUILD)/pins_board.c GEN_PINS_HDR = $(HEADER_BUILD)/pins_board.h @@ -263,6 +264,7 @@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(BUILD)/%_board.c $(HEADER_BUILD)/%_board.h: $(BOARD_DIR)/%.csv $(GEN_PIN_MKPINS) $(GEN_PIN_PREFIX) | $(HEADER_BUILD) $(ECHO) "GEN $@" $(Q)$(PYTHON) $(GEN_PIN_MKPINS) \ + --af-csv $(GEN_PINS_MCU_CSV) \ --board-csv $(GEN_PINS_BOARD_CSV) \ --prefix $(GEN_PIN_PREFIX) \ --output-source $(GEN_PINS_SRC) \ diff --git a/ports/alif/mcu/ensemble_pin_alt.csv b/ports/alif/mcu/ensemble_pin_alt.csv new file mode 100644 index 00000000000..10bfedc9a1d --- /dev/null +++ b/ports/alif/mcu/ensemble_pin_alt.csv @@ -0,0 +1,130 @@ +Pin,AF0,AF1,AF2,AF3,AF4,AF5,AF6,AF7 +P0_0,,OSPI,UART,I3C,UT,LPCAM,CAM,ANA +P0_1,,OSPI,UART,I3C,UT,LPCAM,CAM,ANA +P0_2,,OSPI,UART,I2C,UT,LPCAM,CAM,ANA +P0_3,,OSPI,UART,I2C,UT,LPCAM,CAM,ANA +P0_4,,OSPI,UART,PDM,I2C,UT,,ANA +P0_5,,OSPI,UART,PDM,I2C,UT,,ANA +P0_6,,OSPI,UART,PDM,I2C,UT,,ANA +P0_7,,OSPI,UART,PDM,I2C,UT,CDC,ANA +P1_0,,UART,SPI,I2C,UT,LPCAM,ETH,ANA +P1_1,,UART,SPI,I2C,UT,LPCAM,ETH,ANA +P1_2,,UART,SPI,I3C,UT,LPCAM,ETH,ANA +P1_3,,UART,SPI,I3C,UT,LPCAM,ETH,ANA +P1_4,,OSPI,UART,SPI,UT,LPCAM,ETH,ANA +P1_5,,OSPI,UART,SPI,UT,LPCAM,ETH,ANA +P1_6,,OSPI,UART,I2S,UT,LPCAM,ETH,ANA +P1_7,,OSPI,UART,I2S,UT,LPCAM,ETH,ANA +P2_0,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA +P2_1,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA +P2_2,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA +P2_3,,OSPI,UART,LPPDM,UT,LPCAM,CDC,ANA +P2_4,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA +P2_5,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA +P2_6,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA +P2_7,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA +P3_0,,OSPI,UART,PDM,I2S,QEC,LPCAM,CAM +P3_1,,OSPI,UART,PDM,I2S,QEC,LPCAM,CAM +P3_2,,OSPI,PDM,I2S,I3C,QEC,LPCAM,CAM +P3_3,,OSPI,PDM,I2S,I3C,QEC,LPCAM,CAM +P3_4,,OSPI,UART,LPPDM,I2S,I2C,QEC,CAM +P3_5,,OSPI,UART,LPPDM,SPI,I2C,QEC,CAM +P3_6,,HFXO,LPUART,LPPDM,SPI,I2C,QEC,CAM +P3_7,,JTAG,LPUART,LPPDM,SPI,I2C,QEC,CAM +P4_0,,JTAG,,I2S,SPI,QEC,CDC,CAM +P4_1,,JTAG,I2S,SPI,QEC,SD,CDC,CAM +P4_2,,JTAG,,I2S,SPI,QEC,SD,CAM +P4_3,,JTAG,,I2S,SPI,QEC,SD,CAM +P4_4,,JTAG,I2S,SPI,FAULT,,, +P4_5,,JTAG,SPI,FAULT,,,, +P4_6,,JTAG,SPI,FAULT,,,, +P4_7,,JTAG,SPI,FAULT,,,, +P5_0,,OSPI,UART,PDM,SPI,I2C,UT,SD +P5_1,,OSPI,UART,PDM,SPI,I2C,UT,SD +P5_2,,OSPI,UART,PDM,SPI,LPI2C,UT,SD +P5_3,,OSPI,UART,SPI,LPI2C,UT,SD,CDC +P5_4,,OSPI,UART,PDM,SPI,UT,SD,CDC +P5_5,,OSPI,UART,PDM,UT,SD,ETH,CDC +# P5_6 doesn't really have OSPI on AF1 but it's needed for P10_7 to be in OSPI mode +P5_6,,OSPI,UART,I2C,UT,SD,ETH,CDC +P5_7,,OSPI,UART,I2C,UT,SD,ETH, +P6_0,,OSPI,UART,PDM,UT,SD,ETH, +P6_1,,OSPI,UART,PDM,UT,SD,ETH, +P6_2,,OSPI,UART,,PDM,UT,SD,ETH +P6_3,,OSPI,UART,,PDM,UT,SD,ETH +P6_4,,OSPI,UART,,SPI,UT,SD,ETH +P6_5,,OSPI,UART,,SPI,UT,SD,ETH +P6_6,,OSPI,UART,,SPI,UT,SD,ETH +P6_7,,OSPI,UART,PDM,SPI,UT,SD,ETH +P7_0,,,CMP,SPI,I2C,UT,SD, +P7_1,,,CMP,SPI,I2C,UT,SD, +P7_2,,,UART,CMP,SPI,I2C,UT,SD +P7_3,,,UART,CMP,SPI,I2C,UT, +P7_4,,,LPUART,LPPDM,LPSPI,LPI2C,UT, +P7_5,,,LPUART,,LPPDM,LPSPI,LPI2C,UT +P7_6,,,LPUART,,LPPDM,LPSPI,I3C,UT +P7_7,,,LPUART,,LPPDM,LPSPI,I3C,UT +P8_0,,OSPI,AUDIO,FAULT,LPCAM,SD,CDC,CAM +P8_1,,I2S,FAULT,LPCAM,SD,CDC,CAM, +P8_2,,I2S,SPI,FAULT,LPCAM,SD,CDC,CAM +P8_3,,I2S,SPI,FAULT,LPCAM,SD,CDC,CAM +P8_4,,I2S,SPI,QEC,LPCAM,SD,CDC,CAM +P8_5,,,SPI,QEC,LPCAM,SD,CDC,CAM +P8_6,,,I2S,QEC,LPCAM,SD,CDC,CAM +P8_7,,,I2S,QEC,LPCAM,SD,CDC,CAM +P9_0,,,I2S,QEC,SD,CDC,CAM, +P9_1,,LPUART,I2S,QEC,SD,CDC,CAM, +P9_2,,LPUART,I2S,SPI,QEC,SD,CDC,CAM +P9_3,,HFXO,UART,I2S,SPI,QEC,CDC,CAM +P9_4,,UART,I2S,SPI,I2C,QEC,CDC,CAM +P9_5,,OSPI,I2S,SPI,I2C,QEC,CDC,CAM +P9_6,,OSPI,AUDIO,SPI,I2C,QEC,CDC,CAM +P9_7,,OSPI,UART,SPI,I2C,QEC,CDC,CAM +P10_0,,OSPI,UART,SPI,UT,LPCAM,CDC,CAM +P10_1,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM +P10_2,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM +P10_3,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM +P10_4,,OSPI,,LPI2S,I2C,UT,ETH,CDC +P10_5,,UART,I2S,SPI,I2C,UT,ETH,CDC +P10_6,,UART,I2S,SPI,I2C,UT,ETH,CDC +P10_7,,UART,I2S,SPI,I2C,UT,CDC,OSPI +P11_0,,OSPI,UART,I2S,SPI,UT,ETH,CDC +P11_1,,OSPI,UART,SPI,UT,ETH,CDC, +P11_2,,OSPI,UART,LPPDM,SPI,UT,ETH,CDC +P11_3,,OSPI,UART,LPPDM,SPI,UT,ETH,CDC +P11_4,,OSPI,UART,PDM,LPSPI,UT,ETH,CDC +P11_5,,OSPI,UART,PDM,LPSPI,UT,ETH,CDC +P11_6,,OSPI,UART,LPPDM,LPSPI,UT,ETH,CDC +P11_7,,OSPI,UART,LPPDM,LPSPI,UT,ETH,CDC +P12_0,,OSPI,AUDIO,I2S,UT,CDC,, +P12_1,,OSPI,UART,I2S,UT,CDC,, +P12_2,,OSPI,UART,I2S,UT,CDC,, +P12_3,,OSPI,UART,I2S,UT,CDC,, +P12_4,,OSPI,SPI,UT,,CDC,, +P12_5,,,SPI,UT,,CDC,, +P12_6,,,SPI,UT,,CDC,, +P12_7,,OSPI,,SPI,UT,CDC,, +P13_0,,OSPI,,SPI,QEC,SD,CDC, +P13_1,,OSPI,SPI,QEC,SD,CDC,, +P13_2,,OSPI,SPI,QEC,SD,CDC,, +P13_3,,OSPI,SPI,QEC,SD,CDC,, +P13_4,,OSPI,LPI2S,QEC,SD,CDC,, +P13_5,,OSPI,LPI2S,QEC,SD,CDC,, +P13_6,,OSPI,LPI2S,QEC,SD,CDC,, +P13_7,,OSPI,LPI2S,QEC,SD,CDC,, +P14_0,,OSPI,UART,QEC,SD,,, +P14_1,,OSPI,UART,,QEC,SD,, +P14_2,,OSPI,UART,,QEC,SD,, +P14_3,,OSPI,UART,,QEC,,, +P14_4,,CMP,SPI,FAULT,,,, +P14_5,,CMP,SPI,FAULT,,,, +P14_6,,CMP,SPI,FAULT,,,, +P14_7,,CMP,SPI,FAULT,,,, +P15_0,,LPTMR,,,,,, +P15_1,,LPTMR,,,,,, +P15_2,,LPTMR,,,,,, +P15_3,,LPTMR,,,,,, +P15_4,,,,,,,, +P15_5,,,,,,,, +P15_6,,,,,,,, +P15_7,,,,,,,, diff --git a/ports/alif/mcu/make-pins.py b/ports/alif/mcu/make-pins.py index d4fbf5d8b8d..fb000082642 100755 --- a/ports/alif/mcu/make-pins.py +++ b/ports/alif/mcu/make-pins.py @@ -34,26 +34,36 @@ class AlifPin(boardgen.Pin): + def __init__(self, cpu_pin_name): + super().__init__(cpu_pin_name) + self._afs = ["MP_HAL_PIN_ALT_NONE"] * 8 + + # Called for each AF defined in the csv file for this pin. + def add_af(self, af_idx, af_name, af): + self._afs[af_idx] = f"MP_HAL_PIN_ALT_{af}" + # Emit the struct which contains the pin instance. def definition(self): port, pin = self.name()[1:].split("_") adc12_periph, adc12_channel = ADC12_ANA_MAP.get(self.name(), (3, 7)) base = "LPGPIO_BASE" if port == "15" else "GPIO{}_BASE".format(port) return ( - "{{ " - ".base = {{ .type = &machine_pin_type }}, " - ".gpio = (GPIO_Type *){base}, " - ".port = PORT_{port}, " - ".pin = PIN_{pin}, " - ".adc12_periph = {adc12_periph}, " - ".adc12_channel = {adc12_channel}, " - ".name = MP_QSTR_P{port}_{pin} " + "{{\n" + " .name = MP_QSTR_P{port}_{pin},\n" + " .base = {{ .type = &machine_pin_type }},\n" + " .gpio = (GPIO_Type *){base},\n" + " .port = PORT_{port},\n" + " .pin = PIN_{pin},\n" + " .adc12_periph = {adc12_periph},\n" + " .adc12_channel = {adc12_channel},\n" + " .alt = {{{alt}}},\n" "}}".format( port=port, pin=pin, base=base, adc12_periph=adc12_periph, adc12_channel=adc12_channel, + alt=", ".join([f"{af}" for af in self._afs]), ) ) @@ -76,7 +86,7 @@ def validate_cpu_pin_name(cpu_pin_name): class AlifPinGenerator(boardgen.PinGenerator): def __init__(self): # Use custom pin type above. - super().__init__(pin_type=AlifPin) + super().__init__(pin_type=AlifPin, enable_af=True) # Pre-define the pins (i.e. don't require them to be listed in pins.csv). for i in range(NUM_PORTS): diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 5e3dad14190..1a6136c31ab 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -169,6 +169,56 @@ uint64_t mp_hal_time_ns(void) { return 0; } +void mp_hal_pin_config(const machine_pin_obj_t *pin, uint32_t mode, + uint32_t pull, uint32_t speed, uint32_t drive, uint32_t alt, bool ren) { + uint8_t alt_func = PINMUX_ALTERNATE_FUNCTION_0; + uint8_t pad_ctrl = drive | speed | (ren ? PADCTRL_READ_ENABLE : 0); + + // Configure pull-up or pull-down. + if (pull & MP_HAL_PIN_PULL_UP) { + pad_ctrl |= PADCTRL_DRIVER_DISABLED_PULL_UP; + } + + if (pull & MP_HAL_PIN_PULL_DOWN) { + pad_ctrl |= PADCTRL_DRIVER_DISABLED_PULL_DOWN; + } + + // Configure open-drain mode. + if (mode == MP_HAL_PIN_MODE_OPEN_DRAIN) { + pad_ctrl |= PADCTRL_DRIVER_OPEN_DRAIN; + } + + // For ALT mode, find alternate function. + if (mode == MP_HAL_PIN_MODE_ALT) { + for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(pin->alt); i++) { + if (alt == pin->alt[i]) { + alt_func = i; + break; + } + } + if (alt_func == PINMUX_ALTERNATE_FUNCTION_0) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin af: %d"), alt); + } + } + + // Set pad config. + pinconf_set(pin->port, pin->pin, alt_func, pad_ctrl); + + // For INPUT/OUTPUT/OD modes, set the GPIO direction. + switch (mode) { + case MP_HAL_PIN_MODE_INPUT: + gpio_set_direction_input(pin->gpio, pin->pin); + break; + case MP_HAL_PIN_MODE_OUTPUT: + case MP_HAL_PIN_MODE_OPEN_DRAIN: + gpio_set_direction_output(pin->gpio, pin->pin); + break; + default: + break; + } +} + + void system_tick_schedule_callback(void) { pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); } diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 7b054b671bc..3b23cb6a2c5 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -74,12 +74,49 @@ extern ringbuf_t stdin_ringbuf; #define MP_HAL_PIN_MODE_INPUT (0) #define MP_HAL_PIN_MODE_OUTPUT (1) #define MP_HAL_PIN_MODE_OPEN_DRAIN (2) +#define MP_HAL_PIN_MODE_ALT (3) #define MP_HAL_PIN_PULL_NONE (0) #define MP_HAL_PIN_PULL_UP (1) #define MP_HAL_PIN_PULL_DOWN (2) +#define MP_HAL_PIN_DRIVE_2MA (PADCTRL_OUTPUT_DRIVE_STRENGTH_2MA) +#define MP_HAL_PIN_DRIVE_4MA (PADCTRL_OUTPUT_DRIVE_STRENGTH_4MA) +#define MP_HAL_PIN_DRIVE_8MA (PADCTRL_OUTPUT_DRIVE_STRENGTH_8MA) +#define MP_HAL_PIN_DRIVE_12MA (PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA) +#define MP_HAL_PIN_SPEED_LOW (0) +#define MP_HAL_PIN_SPEED_HIGH (PADCTRL_SLEW_RATE_FAST) #define mp_hal_pin_obj_t const machine_pin_obj_t * +enum { + MP_HAL_PIN_ALT_NONE = 0, + MP_HAL_PIN_ALT_ANA, + MP_HAL_PIN_ALT_AUDIO, + MP_HAL_PIN_ALT_CAM, + MP_HAL_PIN_ALT_CDC, + MP_HAL_PIN_ALT_CMP, + MP_HAL_PIN_ALT_ETH, + MP_HAL_PIN_ALT_FAULT, + MP_HAL_PIN_ALT_HFXO, + MP_HAL_PIN_ALT_I2C, + MP_HAL_PIN_ALT_I2S, + MP_HAL_PIN_ALT_I3C, + MP_HAL_PIN_ALT_JTAG, + MP_HAL_PIN_ALT_LPCAM, + MP_HAL_PIN_ALT_LPI2C, + MP_HAL_PIN_ALT_LPI2S, + MP_HAL_PIN_ALT_LPPDM, + MP_HAL_PIN_ALT_LPSPI, + MP_HAL_PIN_ALT_LPTMR, + MP_HAL_PIN_ALT_LPUART, + MP_HAL_PIN_ALT_OSPI, + MP_HAL_PIN_ALT_PDM, + MP_HAL_PIN_ALT_QEC, + MP_HAL_PIN_ALT_SD, + MP_HAL_PIN_ALT_SPI, + MP_HAL_PIN_ALT_UART, + MP_HAL_PIN_ALT_UT, +}; + typedef struct _machine_pin_obj_t { mp_obj_base_t base; GPIO_Type *gpio; @@ -88,6 +125,7 @@ typedef struct _machine_pin_obj_t { uint8_t adc12_periph : 2; uint8_t adc12_channel : 3; qstr name; + const uint8_t alt[8]; } machine_pin_obj_t; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); @@ -149,5 +187,8 @@ static inline void mp_hal_wake_main_task_from_isr(void) { // Defined for tinyusb support, nothing needs to be done here. } +void mp_hal_pin_config(const machine_pin_obj_t *pin, uint32_t mode, + uint32_t pull, uint32_t speed, uint32_t drive, uint32_t alt, bool ren); + // Include all the pin definitions. #include "genhdr/pins_board.h" From 9073270c2e0daa1a3539aa14c0feadadd6c6b08d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 2 Jan 2025 06:29:05 +0100 Subject: [PATCH 0497/2098] alif/machine_i2c: Add machine.I2C peripheral support. Signed-off-by: iabdalkader --- ports/alif/alif.mk | 2 + ports/alif/machine_i2c.c | 301 ++++++++++++++++++++++++++++++++++++++ ports/alif/mpconfigport.h | 2 + 3 files changed, 305 insertions(+) create mode 100644 ports/alif/machine_i2c.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index b338e67c66c..5cfd37fc4b6 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -117,6 +117,7 @@ SRC_C = \ alif_flash.c \ fatfs_port.c \ machine_pin.c \ + machine_i2c.c \ main.c \ modalif.c \ mphalport.c \ @@ -186,6 +187,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/common/source/pm.c \ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ drivers/source/adc.c \ + drivers/source/i2c.c \ drivers/source/mhu_driver.c \ drivers/source/mhu_receiver.c \ drivers/source/mhu_sender.c \ diff --git a/ports/alif/machine_i2c.c b/ports/alif/machine_i2c.c new file mode 100644 index 00000000000..f117b93c2b1 --- /dev/null +++ b/ports/alif/machine_i2c.c @@ -0,0 +1,301 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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/mperrno.h" +#include "extmod/modmachine.h" + +#if MICROPY_HW_ENABLE_HW_I2C +#include "i2c.h" + +#define I2C_DEFAULT_FREQ (400000) +#define I2C_DEFAULT_TIMEOUT (50000) + +#define I2C_DAT_INDEX (0) +#define I2C_TX_FIFO_LEN (I2C_FIFO_DEPTH / 2) +#define I2C_RX_FIFO_LEN (I2C_FIFO_DEPTH / 2) + +#define I2C_SPEED(freq) \ + ((freq) <= 100000 ? I2C_SPEED_STANDARD : \ + ((freq) <= 400000 ? I2C_SPEED_FAST : \ + I2C_SPEED_FASTPLUS)) + +#define I2C_IC_CON_SPEED(freq) \ + ((freq) <= 100000 ? I2C_IC_CON_SPEED_STANDARD : \ + ((freq) <= 400000 ? I2C_IC_CON_SPEED_FAST : \ + I2C_IC_CON_SPEED_HIGH)) +#define I2C_IC_CON_MASTER_TX_EMPTY_CTRL (1 << 8) + +#define I2C_IC_STATUS_RFNE I2C_IC_STATUS_RECEIVE_FIFO_NOT_EMPTY +#define I2C_IC_STATUS_TFNF I2C_IC_STATUS_TRANSMIT_FIFO_NOT_FULL +#define I2C_STAT_ERRORS (I2C_IC_INTR_STAT_TX_ABRT | I2C_IC_INTR_STAT_TX_OVER | \ + I2C_IC_INTR_STAT_RX_OVER | I2C_IC_INTR_STAT_RX_UNDER) + +#define debug_printf(...) // mp_printf(&mp_plat_print, "i2c.c: " __VA_ARGS__) +#define I2C_CHECK_ERRORS(base) \ + if (base->I2C_RAW_INTR_STAT & I2C_STAT_ERRORS) { \ + uint32_t status = base->I2C_RAW_INTR_STAT; \ + debug_printf("status: 0x%lx raw_int: 0x%lx abort: 0x%lx line: %d\n", \ + base->I2C_STATUS, status, base->I2C_TX_ABRT_SOURCE, __LINE__); \ + (void)status; \ + (void)base->I2C_CLR_TX_ABRT; \ + (void)base->I2C_CLR_ACTIVITY; \ + return -MP_EIO; \ + } + +typedef struct _machine_i2c_obj_t { + mp_obj_base_t base; + uint32_t i2c_id; + I2C_Type *i2c; + mp_hal_pin_obj_t scl; + mp_hal_pin_obj_t sda; + uint32_t freq; + uint32_t timeout; +} machine_i2c_obj_t; + +static machine_i2c_obj_t machine_i2c_obj[] = { + #if defined(MICROPY_HW_I2C0_SCL) + [0] = {{&machine_i2c_type}, 0, (I2C_Type *)I2C0_BASE, MICROPY_HW_I2C0_SCL, MICROPY_HW_I2C0_SDA}, + #endif + #if defined(MICROPY_HW_I2C1_SCL) + [1] = {{&machine_i2c_type}, 1, (I2C_Type *)I2C1_BASE, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, + #endif + #if defined(MICROPY_HW_I2C2_SCL) + [2] = {{&machine_i2c_type}, 2, (I2C_Type *)I2C2_BASE, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, + #endif + #if defined(MICROPY_HW_I2C3_SCL) + [3] = {{&machine_i2c_type}, 3, (I2C_Type *)I2C3_BASE, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, + #endif +}; + +static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2C(%u, freq=%u, scl=%q, sda=%q, timeout=%u)", + self->i2c_id, self->freq, self->scl->name, self->sda->name, self->timeout); +} + +mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_freq, ARG_scl, ARG_sda, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_freq, MP_ARG_INT, {.u_int = I2C_DEFAULT_FREQ} }, + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus. + int i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_obj) || !machine_i2c_obj[i2c_id].i2c) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object. + machine_i2c_obj_t *self = &machine_i2c_obj[i2c_id]; + + // Set args + self->freq = args[ARG_freq].u_int; + self->timeout = args[ARG_timeout].u_int; + + // here we would check the scl/sda pins and configure them, but it's not implemented + if (args[ARG_scl].u_obj != mp_const_none || args[ARG_sda].u_obj != mp_const_none) { + mp_raise_ValueError(MP_ERROR_TEXT("explicit choice of scl/sda is not implemented")); + } + + // Disable I2C controller. + i2c_disable(self->i2c); + + // Configure I2C pins. + mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + + // Initialize I2C controller. + self->i2c->I2C_CON = I2C_IC_CON_ENABLE_MASTER_MODE | + I2C_IC_CON_MASTER_RESTART_EN | + I2C_IC_CON_MASTER_TX_EMPTY_CTRL | + I2C_IC_CON_SPEED(self->freq); + + // Configure FIFO threshold. + self->i2c->I2C_TX_TL = I2C_TX_FIFO_LEN; + self->i2c->I2C_RX_TL = I2C_RX_FIFO_LEN; + + // Configure clock. + i2c_master_set_clock(self->i2c, GetSystemAPBClock() / 1000, I2C_SPEED(self->freq)); + + // Enable I2C controller. + i2c_clear_all_interrupt(self->i2c); + i2c_enable(self->i2c); + + return MP_OBJ_FROM_PTR(self); +} + +static int machine_i2c_poll_flags(I2C_Type *base, uint32_t flags, uint32_t timeout_us) { + mp_uint_t tick_start = mp_hal_ticks_us(); + while (!(base->I2C_STATUS & flags)) { + I2C_CHECK_ERRORS(base); + if ((mp_hal_ticks_us() - tick_start) >= timeout_us) { + return -MP_ETIMEDOUT; + } + // Can't delay or handle pending events here otherwise we risk + // the FIFO getting empty, which will generate a STOP condition. + } + return 0; +} + +static int machine_i2c_write(machine_i2c_obj_t *self, uint8_t *buf, size_t tx_size) { + mp_uint_t tick_start = mp_hal_ticks_us(); + for (size_t tx_idx = 0; tx_idx < tx_size;) { + // Write data to FIFO + if (self->i2c->I2C_STATUS & I2C_IC_STATUS_TFNF) { + self->i2c->I2C_DATA_CMD = (uint16_t)buf[tx_idx++]; + I2C_CHECK_ERRORS(self->i2c); + tick_start = mp_hal_ticks_us(); + } + + // Check for timeout + if ((mp_hal_ticks_us() - tick_start) >= self->timeout) { + return -MP_ETIMEDOUT; + } + } + return 0; +} + +static int machine_i2c_read(machine_i2c_obj_t *self, uint8_t *buf, size_t rx_size) { + mp_uint_t tick_start = mp_hal_ticks_us(); + for (size_t tx_idx = 0, rx_idx = 0; rx_idx < rx_size;) { + // Write command to FIFO + if (tx_idx < rx_size && (self->i2c->I2C_STATUS & I2C_IC_STATUS_TFNF)) { + self->i2c->I2C_DATA_CMD = I2C_IC_DATA_CMD_READ_REQ; + I2C_CHECK_ERRORS(self->i2c); + ++tx_idx; + } + + // Read data from FIFO + while (rx_idx < rx_size && (self->i2c->I2C_STATUS & I2C_IC_STATUS_RFNE)) { + buf[rx_idx++] = self->i2c->I2C_DATA_CMD & 0xFF; + tick_start = mp_hal_ticks_us(); + } + + // Check for timeout + if ((mp_hal_ticks_us() - tick_start) >= self->timeout) { + return -MP_ETIMEDOUT; + } + } + return 0; +} + +int machine_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + int ret = 0; + uint32_t bytes = 0; + machine_i2c_obj_t *self = (machine_i2c_obj_t *)self_in; + + // The DesignWare I2C IP on AE3 is configured such that it auto-generates a STOP + // condition when the TX FIFO gets empty. In other words, the code can't have any + // control over STOP condition generation. The only fix for this would be to buffer + // complete read/write sequences and send them out when the STOP flag is set. + if (!(flags & MP_MACHINE_I2C_FLAG_STOP)) { + mp_raise_ValueError(MP_ERROR_TEXT("nostop flag is not supported")); + } + + i2c_clear_all_interrupt(self->i2c); + i2c_set_target_addr(self->i2c, addr, I2C_7BIT_ADDRESS, 0); + + // Workaround issue with hardware I2C not accepting zero-length writes. + if (!bufs->len) { + mp_machine_i2c_buf_t bufs = { 0 }; + + mp_machine_soft_i2c_obj_t soft_i2c = { + .base = { &mp_machine_soft_i2c_type }, + .scl = self->scl, + .sda = self->sda, + .us_timeout = self->timeout, + .us_delay = 500000 / self->freq + 1, + }; + + // Switch pins to GPIO/OD. + mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_NONE, true); + mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_NONE, true); + + // Perform the transfer. + ret = mp_machine_soft_i2c_transfer(&soft_i2c.base, addr, 1, &bufs, flags); + + // Re-configure I2C pins. + mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + + return ret; + } + + for (size_t i = 0; i < n; i++) { + mp_machine_i2c_buf_t *buf = &bufs[i]; + if (i == 0 && (flags & MP_MACHINE_I2C_FLAG_WRITE1)) { + ret = machine_i2c_write(self, buf->buf, buf->len); + } else if (flags & MP_MACHINE_I2C_FLAG_READ) { + ret = machine_i2c_read(self, buf->buf, buf->len); + } else if (bufs->len != 0) { + ret = machine_i2c_write(self, buf->buf, buf->len); + } + if (ret < 0) { + return ret; + } + bytes += bufs->len; + } + + // Wait for TX FIFO empty + ret = machine_i2c_poll_flags(self->i2c, I2C_IC_STATUS_TFE, self->timeout); + if (ret < 0) { + return ret; + } + + return bytes; +} + +static const mp_machine_i2c_p_t machine_i2c_p = { + .transfer_supports_write1 = true, + .transfer = machine_i2c_transfer, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_i2c_type, + MP_QSTR_I2C, + MP_TYPE_FLAG_NONE, + make_new, machine_i2c_make_new, + print, machine_i2c_print, + protocol, &machine_i2c_p, + locals_dict, &mp_machine_i2c_locals_dict + ); +#endif // MICROPY_HW_ENABLE_HW_I2C diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index ae3ad7a30d3..b23886637e3 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -130,6 +130,8 @@ #define MICROPY_PY_MACHINE_ADC_INCLUDEFILE "ports/alif/machine_adc.c" #define MICROPY_PY_MACHINE_DHT_READINTO (1) #define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_PY_MACHINE_I2C (MICROPY_HW_ENABLE_HW_I2C) +#define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_TIMER (1) From 280e6e2a40dbddb3aec5424c1ca1bf9dea876dd0 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 19 Jan 2025 15:59:25 +0100 Subject: [PATCH 0498/2098] alif/machine_spi: Add machine.SPI peripheral support. Signed-off-by: iabdalkader Signed-off-by: Damien George --- ports/alif/alif.mk | 3 + ports/alif/machine_spi.c | 312 ++++++++++++++++++++++++++++++++++++++ ports/alif/mpconfigport.h | 1 + 3 files changed, 316 insertions(+) create mode 100644 ports/alif/machine_spi.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 5cfd37fc4b6..63003b58a09 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -118,6 +118,7 @@ SRC_C = \ fatfs_port.c \ machine_pin.c \ machine_i2c.c \ + machine_spi.c \ main.c \ modalif.c \ mphalport.c \ @@ -188,6 +189,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ drivers/source/adc.c \ drivers/source/i2c.c \ + drivers/source/spi.c \ drivers/source/mhu_driver.c \ drivers/source/mhu_receiver.c \ drivers/source/mhu_sender.c \ @@ -206,6 +208,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ ) $(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 +$(BUILD)/$(ALIF_DFP_REL_TOP)/drivers/source/spi.o: CFLAGS += -Wno-maybe-uninitialized $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_system.o: CFLAGS += -Wno-maybe-uninitialized diff --git a/ports/alif/machine_spi.c b/ports/alif/machine_spi.c new file mode 100644 index 00000000000..d13c6368f25 --- /dev/null +++ b/ports/alif/machine_spi.c @@ -0,0 +1,312 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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/mperrno.h" +#include "extmod/modmachine.h" + +#if MICROPY_PY_MACHINE_SPI +#include "clk.h" +#include "spi.h" +#include "sys_ctrl_spi.h" + +typedef struct _machine_spi_obj_t { + mp_obj_base_t base; + uint8_t id; + SPI_Type *inst; + bool is_lp; +} machine_spi_obj_t; + +static machine_spi_obj_t machine_spi_obj[] = { + #if defined(MICROPY_HW_SPI0_SCK) + [0] = {{&machine_spi_type}, 0, (SPI_Type *)SPI0_BASE, false}, + #endif + #if defined(MICROPY_HW_SPI1_SCK) + [1] = {{&machine_spi_type}, 1, (SPI_Type *)SPI1_BASE, false}, + #endif + #if defined(MICROPY_HW_SPI2_SCK) + [2] = {{&machine_spi_type}, 2, (SPI_Type *)SPI2_BASE, false}, + #endif + #if defined(MICROPY_HW_SPI3_SCK) + [3] = {{&machine_spi_type}, 3, (SPI_Type *)SPI3_BASE, false}, + #endif + #if defined(MICROPY_HW_LPSPI0_SCK) + [4] = {{&machine_spi_type}, 4, (SPI_Type *)LPSPI0_BASE, true}, + #endif + +}; + +static inline uint32_t spi_get_clk(machine_spi_obj_t *spi) { + return spi->is_lp ? GetSystemCoreClock() : GetSystemAHBClock(); +} + +static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, + uint32_t polarity, uint32_t phase, uint32_t bits, uint32_t firstbit) { + const machine_pin_obj_t *pins[4] = { NULL, NULL, NULL, NULL }; + switch (spi->id) { + #if defined(MICROPY_HW_SPI0_SCK) + case 0: + pins[0] = MICROPY_HW_SPI0_SCK; + pins[1] = MICROPY_HW_SPI0_MISO; + pins[2] = MICROPY_HW_SPI0_MOSI; + #if defined(MICROPY_HW_SPI0_NSS) + pins[3] = MICROPY_HW_SPI0_NSS; + #endif + break; + #endif + #if defined(MICROPY_HW_SPI1_SCK) + case 1: + pins[0] = MICROPY_HW_SPI1_SCK; + pins[1] = MICROPY_HW_SPI1_MISO; + pins[2] = MICROPY_HW_SPI1_MOSI; + #if defined(MICROPY_HW_SPI1_NSS) + pins[3] = MICROPY_HW_SPI1_NSS; + #endif + break; + #endif + #if defined(MICROPY_HW_SPI2_SCK) + case 2: + pins[0] = MICROPY_HW_SPI2_SCK; + pins[1] = MICROPY_HW_SPI2_MISO; + pins[2] = MICROPY_HW_SPI2_MOSI; + #if defined(MICROPY_HW_SPI2_NSS) + pins[3] = MICROPY_HW_SPI2_NSS; + #endif + break; + #endif + #if defined(MICROPY_HW_SPI3_SCK) + case 3: + pins[0] = MICROPY_HW_SPI3_SCK; + pins[1] = MICROPY_HW_SPI3_MISO; + pins[2] = MICROPY_HW_SPI3_MOSI; + #if defined(MICROPY_HW_SPI3_NSS) + pins[3] = MICROPY_HW_SPI3_NSS; + #endif + break; + #endif + #if defined(MICROPY_HW_LPSPI0_SCK) + case 4: // LPSPI0 + pins[0] = MICROPY_HW_LPSPI0_SCK; + pins[1] = MICROPY_HW_LPSPI0_MISO; + pins[2] = MICROPY_HW_LPSPI0_MOSI; + #if defined(MICROPY_HW_LPSPI0_NSS) + pins[3] = MICROPY_HW_LPSPI0_NSS; + #endif + break; + #endif + default: + return; + } + + // Disable SPI. + spi_disable(spi->inst); + + // Enable clocks. + if (spi->is_lp) { + enable_lpspi_clk(); + } + + // Configure SPI pins. + for (size_t i = 0; i < MP_ARRAY_SIZE(pins) && pins[i]; i++) { + mp_hal_pin_config(pins[i], MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + } + + // Disable all interrupts. + spi_mask_interrupts(spi->inst); + + // Configure baudrate clock + spi_set_bus_speed(spi->inst, baudrate, spi_get_clk(spi)); + + // Configure FIFOs + spi_set_tx_threshold(spi->inst, 0); + spi_set_rx_threshold(spi->inst, 0); + if (!spi->is_lp) { + spi_set_rx_sample_delay(spi->inst, 0); + spi_set_tx_fifo_start_level(spi->inst, 0); + } + + // Configure SPI bus mode. + uint32_t spi_mode = (polarity << 1) | phase; + if (!spi->is_lp) { + spi_set_mode(spi->inst, spi_mode); + } else { + lpspi_set_mode(spi->inst, spi_mode); + } + + // Configure SPI bus protocol. + uint32_t spi_proto = SPI_PROTO_SPI; + if (!spi->is_lp) { + spi_set_protocol(spi->inst, spi_proto); + } else { + lpspi_set_protocol(spi->inst, spi_proto); + } + + // Configure SPI transfer mode. + if (!spi->is_lp) { + spi_mode_master(spi->inst); + } + + // Configure frame size. + if (!spi->is_lp) { + spi_set_dfs(spi->inst, bits); + } else { + lpspi_set_dfs(spi->inst, bits); + } + + // Configure slave select pin + spi_control_ss(spi->inst, 0, true); + if (!spi->is_lp) { + spi_set_sste(spi->inst, false); + } else { + lpspi_set_sste(spi->inst, false); + } + + // Clear IRQs. + (void)spi->inst->SPI_ICR; + + // Enable SPI. + spi_enable(spi->inst); +} + +static void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t baudrate = spi_get_bus_speed(self->inst, spi_get_clk(self)); + mp_printf(print, "SPI(%u, baudrate=%u, lp=%u)", self->id, baudrate, self->is_lp); +} + +mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get spi bus. + int spi_id = mp_obj_get_int(args[ARG_id].u_obj); + if (spi_id < 0 || spi_id >= MP_ARRAY_SIZE(machine_spi_obj) || !machine_spi_obj[spi_id].inst) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); + } + + // Get static peripheral object. + machine_spi_obj_t *self = &machine_spi_obj[spi_id]; + + // here we would check the sck/mosi/miso pins and configure them, but it's not implemented + if (args[ARG_sck].u_obj != MP_OBJ_NULL || + args[ARG_mosi].u_obj != MP_OBJ_NULL || + args[ARG_miso].u_obj != MP_OBJ_NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("explicit choice of sck/mosi/miso is not implemented")); + } + + // Initialize and configure SPI. + spi_init(self, args[ARG_baudrate].u_int, args[ARG_polarity].u_int, + args[ARG_phase].u_int, args[ARG_bits].u_int, args[ARG_firstbit].u_int); + + return MP_OBJ_FROM_PTR(self); +} + +static void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + // Parse the arguments. + machine_spi_obj_t *self = (machine_spi_obj_t *)self_in; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Initialize and configure SPI. + spi_init(self, args[ARG_baudrate].u_int, args[ARG_polarity].u_int, + args[ARG_phase].u_int, args[ARG_bits].u_int, args[ARG_firstbit].u_int); +} + +static void machine_spi_deinit(mp_obj_base_t *self_in) { + machine_spi_obj_t *self = (machine_spi_obj_t *)self_in; + // Disable all interrupts. + spi_mask_interrupts(self->inst); + // Disable SS pin. + spi_control_ss(self->inst, 0, 0); + // Disable SPI. + spi_disable(self->inst); + // Deinitialize GPIOs and clocks. + if (self->is_lp) { + disable_lpspi_clk(); + } +} + +static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { + machine_spi_obj_t *self = (machine_spi_obj_t *)self_in; + spi_transfer_t spi_xfer = { + .tx_buff = src, + .tx_total_cnt = len, + .rx_buff = dest, + .rx_total_cnt = len, + .tx_default_val = 0xFF, + .tx_default_enable = true, + .mode = SPI_TMOD_TX_AND_RX, + }; + // TODO redo transfer_blocking to timeout and poll events. + if (!self->is_lp) { + spi_transfer_blocking(self->inst, &spi_xfer); + } else { + lpspi_transfer_blocking(self->inst, &spi_xfer); + } +} + +static const mp_machine_spi_p_t machine_spi_p = { + .init = machine_spi_init, + .deinit = machine_spi_deinit, + .transfer = machine_spi_transfer, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_spi_type, + MP_QSTR_SPI, + MP_TYPE_FLAG_NONE, + make_new, machine_spi_make_new, + print, machine_spi_print, + protocol, &machine_spi_p, + locals_dict, &mp_machine_spi_locals_dict + ); + +#endif // MICROPY_PY_MACHINE_SPI diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index b23886637e3..83f808b131f 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -133,6 +133,7 @@ #define MICROPY_PY_MACHINE_I2C (MICROPY_HW_ENABLE_HW_I2C) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) +#define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_TIMER (1) #define MICROPY_VFS (1) From ec92bcfeff787a4ceb25c3b9c1703043a7e3052d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 31 Jan 2025 14:15:06 +0100 Subject: [PATCH 0499/2098] alif/machine_rtc: Add basic machine.RTC support. Signed-off-by: iabdalkader --- ports/alif/alif.mk | 1 + ports/alif/irq.h | 1 + ports/alif/machine_rtc.c | 109 +++++++++++++++++++++++++++++++++++++++ ports/alif/modmachine.c | 1 + 4 files changed, 112 insertions(+) create mode 100644 ports/alif/machine_rtc.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 63003b58a09..aa2451d7a62 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -119,6 +119,7 @@ SRC_C = \ machine_pin.c \ machine_i2c.c \ machine_spi.c \ + machine_rtc.c \ main.c \ modalif.c \ mphalport.c \ diff --git a/ports/alif/irq.h b/ports/alif/irq.h index 59dbce97059..ed454adcb46 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -48,6 +48,7 @@ #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 7, 0) #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) #define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) +#define IRQ_PRI_RTC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 100, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) // these states correspond to values from query_irq, enable_irq and disable_irq diff --git a/ports/alif/machine_rtc.c b/ports/alif/machine_rtc.c new file mode 100644 index 00000000000..6473d1d80fb --- /dev/null +++ b/ports/alif/machine_rtc.c @@ -0,0 +1,109 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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/mperrno.h" +#include "extmod/modmachine.h" +#include "rtc.h" +#include "sys_ctrl_rtc.h" + +typedef struct _machine_rtc_obj_t { + mp_obj_base_t base; + LPRTC_Type *rtc; +} machine_rtc_obj_t; + +// Singleton RTC object. +static const machine_rtc_obj_t machine_rtc = {{&machine_rtc_type}, (LPRTC_Type *)LPRTC_BASE}; + +void LPRTC_IRQHandler(void) { + lprtc_interrupt_ack(machine_rtc.rtc); + lprtc_interrupt_disable(machine_rtc.rtc); +} + +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) { + const machine_rtc_obj_t *self = &machine_rtc; + + // Check arguments. + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + enable_lprtc_clk(); + lprtc_prescaler_disable(self->rtc); + lprtc_counter_wrap_disable(self->rtc); + lprtc_interrupt_disable(self->rtc); + lprtc_interrupt_unmask(self->rtc); + + NVIC_SetPriority(LPRTC_IRQ_IRQn, IRQ_PRI_RTC); + NVIC_ClearPendingIRQ(LPRTC_IRQ_IRQn); + NVIC_EnableIRQ(LPRTC_IRQ_IRQn); + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t machine_rtc_alarm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_id, ARG_time, ARG_repeat }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_time, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_repeat, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + machine_rtc_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(args), allowed_args, args); + + if (mp_obj_is_int(args[ARG_time].u_obj)) { + uint32_t seconds = mp_obj_get_int(args[1].u_obj) / 1000; + + lprtc_counter_disable(self->rtc); + lprtc_load_count(self->rtc, 1); + lprtc_load_counter_match_register(self->rtc, seconds * 32768); + + lprtc_interrupt_ack(self->rtc); + lprtc_interrupt_enable(self->rtc); + lprtc_counter_enable(self->rtc); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s)")); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_alarm_obj, 1, machine_rtc_alarm); + +static const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&machine_rtc_alarm_obj) }, +}; +static MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_rtc_type, + MP_QSTR_RTC, + MP_TYPE_FLAG_NONE, + make_new, machine_rtc_make_new, + locals_dict, &machine_rtc_locals_dict + ); diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index 98d7bffbad9..9868abbeeaf 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -35,6 +35,7 @@ extern void dcd_uninit(void); #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \ + { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, \ static void mp_machine_idle(void) { mp_event_wait_indefinite(); From facd0b7190d48d7d6f4af1d1eaba0c3a18734356 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 7 Feb 2025 15:41:42 +0100 Subject: [PATCH 0500/2098] alif/ospi_flash: Use mp_hal_pin_config to configure OSPI pins. Signed-off-by: iabdalkader --- ports/alif/ospi_flash.c | 42 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index f2f95a04cf2..17c8be8128d 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -262,30 +262,40 @@ int ospi_flash_init(void) { self->pin = pin; - uint32_t ck_pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST; - uint32_t io_pad_ctrl = PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA | PADCTRL_SLEW_RATE_FAST | PADCTRL_READ_ENABLE; - - pinconf_set(pin->pin_cs->port, pin->pin_cs->pin, OSPI_PIN_FUNCTION, PADCTRL_OUTPUT_DRIVE_STRENGTH_12MA); - pinconf_set(pin->pin_clk_p->port, pin->pin_clk_p->pin, OSPI_PIN_FUNCTION, ck_pad_ctrl); + mp_hal_pin_config(pin->pin_cs, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); + mp_hal_pin_config(pin->pin_clk_p, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); if (pin->pin_clk_n != NULL) { - pinconf_set(pin->pin_clk_n->port, pin->pin_clk_n->pin, OSPI_PIN_FUNCTION, ck_pad_ctrl); + mp_hal_pin_config(pin->pin_clk_n, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); } if (pin->pin_rwds != NULL) { - pinconf_set(pin->pin_rwds->port, pin->pin_rwds->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + mp_hal_pin_config(pin->pin_rwds, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); if (pin->pin_rwds->port == PORT_10 && pin->pin_rwds->pin == PIN_7) { // Alif: P5_6 is needed to support proper alt function selection of P10_7. - pinconf_set(PORT_5, PIN_6, OSPI_PIN_FUNCTION, io_pad_ctrl); + mp_hal_pin_config(pin_P5_6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); } } - pinconf_set(pin->pin_d0->port, pin->pin_d0->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d1->port, pin->pin_d1->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d2->port, pin->pin_d2->pin, OSPI_PIN_FUNCTION, io_pad_ctrl | PADCTRL_DRIVER_DISABLED_PULL_UP); - pinconf_set(pin->pin_d3->port, pin->pin_d3->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + mp_hal_pin_config(pin->pin_d0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d3, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); if (pin->pin_d4 != NULL) { - pinconf_set(pin->pin_d4->port, pin->pin_d4->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d5->port, pin->pin_d5->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d6->port, pin->pin_d6->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); - pinconf_set(pin->pin_d7->port, pin->pin_d7->pin, OSPI_PIN_FUNCTION, io_pad_ctrl); + mp_hal_pin_config(pin->pin_d4, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d5, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + mp_hal_pin_config(pin->pin_d7, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); } // Reset the SPI flash. From b9e5f1ffba8551164374bf732aa165c87519a569 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 15 Feb 2025 13:53:44 +0100 Subject: [PATCH 0501/2098] alif/se_services: Add a secondary MHU channel. This channel can be used to communicate (pass messages) between the M55 cores in the RTSS. Currently it's only used to notify the cores. Signed-off-by: iabdalkader --- ports/alif/se_services.c | 120 +++++++++++++++++++++++++++++---------- ports/alif/se_services.h | 4 ++ 2 files changed, 93 insertions(+), 31 deletions(-) diff --git a/ports/alif/se_services.c b/ports/alif/se_services.c index e79a5b97c89..1ed6e3fbfda 100644 --- a/ports/alif/se_services.c +++ b/ports/alif/se_services.c @@ -37,8 +37,9 @@ #include "py/mphal.h" // MHU indices. -#define MHU_M55_SE_MHU0 0 -#define MAX_MHU 1 +#define MHU_SESS_MHU0 0 +#define MHU_RTSS_MHU0 1 +#define MAX_MHU 2 // The following timeout is implemented in se_services_handle.c as a // simple loop busy polling on a variable set from an IRQ. @@ -57,24 +58,36 @@ typedef struct { static const uint32_t mhu_sender_base_address_list[MAX_MHU] = { MHU_SESS_S_TX_BASE, + MHU_RTSS_S_TX_BASE }; static const uint32_t mhu_receiver_base_address_list[MAX_MHU] = { MHU_SESS_S_RX_BASE, + MHU_RTSS_S_RX_BASE }; // Must be aligned as a uint32_t. static uint32_t packet_buffer[SERVICES_MAX_PACKET_BUFFER_SIZE / sizeof(uint32_t)]; +static uint32_t se_sess_handle; +static uint32_t se_rtss_handle; + static mhu_driver_out_t mhu_driver_out; -static uint32_t se_services_handle; void MHU_SESS_S_TX_IRQHandler(void) { - mhu_driver_out.sender_irq_handler(MHU_M55_SE_MHU0); + mhu_driver_out.sender_irq_handler(MHU_SESS_MHU0); } void MHU_SESS_S_RX_IRQHandler(void) { - mhu_driver_out.receiver_irq_handler(MHU_M55_SE_MHU0); + mhu_driver_out.receiver_irq_handler(MHU_SESS_MHU0); +} + +void MHU_RTSS_S_TX_IRQHandler(void) { + mhu_driver_out.sender_irq_handler(MHU_RTSS_MHU0); +} + +void MHU_RTSS_S_RX_IRQHandler(void) { + mhu_driver_out.receiver_irq_handler(MHU_RTSS_MHU0); } int dummy_printf(const char *fmt, ...) { @@ -82,6 +95,33 @@ int dummy_printf(const char *fmt, ...) { return 0; } +static void se_services_irq_config(IRQn_Type irqn, bool enable) { + if (enable) { + NVIC_ClearPendingIRQ(irqn); + NVIC_SetPriority(irqn, IRQ_PRI_MHU); + NVIC_EnableIRQ(irqn); + } else { + NVIC_DisableIRQ(irqn); + NVIC_ClearPendingIRQ(irqn); + } +} + +void se_services_rx_callback(uint32_t id, uint32_t channel, uint32_t data) { + switch (id) { + case MHU_SESS_MHU0: + SERVICES_rx_msg_callback(id, channel, data); + break; + case MHU_RTSS_MHU0: + #if MICROPY_PY_OPENAMP + extern void metal_rproc_notified(void); + metal_rproc_notified(); + #endif + break; + default: + break; + } +} + void se_services_init(void) { // Initialize MHU. mhu_driver_in_t mhu_driver_in; @@ -89,7 +129,7 @@ void se_services_init(void) { mhu_driver_in.receiver_base_address_list = (uint32_t *)mhu_receiver_base_address_list; mhu_driver_in.mhu_count = MAX_MHU; mhu_driver_in.send_msg_acked_callback = SERVICES_send_msg_acked_callback; - mhu_driver_in.rx_msg_callback = SERVICES_rx_msg_callback; + mhu_driver_in.rx_msg_callback = se_services_rx_callback; mhu_driver_in.debug_print = NULL; // not currently used by MHU_driver_initialize MHU_driver_initialize(&mhu_driver_in, &mhu_driver_out); @@ -103,29 +143,38 @@ void se_services_init(void) { }; SERVICES_initialize(&services_init_params); - // Create SE services channel for sending requests. - se_services_handle = SERVICES_register_channel(MHU_M55_SE_MHU0, 0); + // Register SESS MHU channel. + se_sess_handle = SERVICES_register_channel(MHU_SESS_MHU0, 0); + se_services_irq_config(MHU_SESS_S_RX_IRQ_IRQn, true); + se_services_irq_config(MHU_SESS_S_TX_IRQ_IRQn, true); - // Enable MHU interrupts. - NVIC_ClearPendingIRQ(MHU_SESS_S_RX_IRQ_IRQn); - NVIC_SetPriority(MHU_SESS_S_RX_IRQ_IRQn, IRQ_PRI_MHU); - NVIC_EnableIRQ(MHU_SESS_S_RX_IRQ_IRQn); - NVIC_ClearPendingIRQ(MHU_SESS_S_TX_IRQ_IRQn); - NVIC_SetPriority(MHU_SESS_S_TX_IRQ_IRQn, IRQ_PRI_MHU); - NVIC_EnableIRQ(MHU_SESS_S_TX_IRQ_IRQn); + // Register RTSS MHU channel. + se_rtss_handle = SERVICES_register_channel(MHU_RTSS_MHU0, 0); + se_services_irq_config(MHU_RTSS_S_RX_IRQ_IRQn, true); + se_services_irq_config(MHU_RTSS_S_TX_IRQ_IRQn, true); // Send heartbeat services requests until one succeeds. - SERVICES_synchronize_with_se(se_services_handle); + SERVICES_synchronize_with_se(se_sess_handle); +} + +void se_services_deinit(void) { + // Disable SESS MHU channel IRQs. + se_services_irq_config(MHU_SESS_S_RX_IRQ_IRQn, false); + se_services_irq_config(MHU_SESS_S_TX_IRQ_IRQn, false); + + // Disable RTSS MHU channel IRQs. + se_services_irq_config(MHU_RTSS_S_RX_IRQ_IRQn, false); + se_services_irq_config(MHU_RTSS_S_TX_IRQ_IRQn, false); } void se_services_dump_device_data(void) { uint32_t error_code; uint8_t revision[80]; - SERVICES_get_se_revision(se_services_handle, revision, &error_code); + SERVICES_get_se_revision(se_sess_handle, revision, &error_code); SERVICES_version_data_t data; - SERVICES_system_get_device_data(se_services_handle, &data, &error_code); + SERVICES_system_get_device_data(se_sess_handle, &data, &error_code); printf("SE revision: %s\n", revision); printf("ALIF_PN: %s\n", data.ALIF_PN); @@ -141,11 +190,11 @@ void se_services_dump_device_data(void) { void se_services_get_unique_id(uint8_t id[8]) { uint32_t error_code; - SERVICES_system_get_eui_extension(se_services_handle, false, id, &error_code); + SERVICES_system_get_eui_extension(se_sess_handle, false, id, &error_code); } __attribute__((noreturn)) void se_services_reset_soc(void) { - SERVICES_boot_reset_soc(se_services_handle); + SERVICES_boot_reset_soc(se_sess_handle); NVIC_SystemReset(); } @@ -155,7 +204,7 @@ uint64_t se_services_rand64(void) { for (int retry = 0; retry < 100; ++retry) { uint64_t value; int32_t error_code; - uint32_t ret = SERVICES_cryptocell_get_rnd(se_services_handle, sizeof(uint64_t), &value, &error_code); + uint32_t ret = SERVICES_cryptocell_get_rnd(se_sess_handle, sizeof(uint64_t), &value, &error_code); if (ret == SERVICES_REQ_SUCCESS) { return value; } @@ -165,51 +214,59 @@ uint64_t se_services_rand64(void) { return 0; } +uint32_t se_services_notify(void) { + uint32_t ret = SERVICES_send_msg(se_rtss_handle, LocalToGlobal(0)); + if (ret != SERVICES_REQ_SUCCESS) { + return -1; + } + return 0; +} + uint32_t se_services_enable_clock(clock_enable_t clock, bool enable) { uint32_t error_code; - SERVICES_clocks_enable_clock(se_services_handle, clock, enable, &error_code); + SERVICES_clocks_enable_clock(se_sess_handle, clock, enable, &error_code); return error_code; } uint32_t se_services_select_pll_source(pll_source_t source, pll_target_t target) { uint32_t error_code; - SERVICES_clocks_select_pll_source(se_services_handle, source, target, &error_code); + SERVICES_clocks_select_pll_source(se_sess_handle, source, target, &error_code); return error_code; } uint32_t se_services_get_run_profile(run_profile_t *profile) { uint32_t error_code; - SERVICES_get_run_cfg(se_services_handle, profile, &error_code); + SERVICES_get_run_cfg(se_sess_handle, profile, &error_code); return error_code; } uint32_t se_services_set_run_profile(run_profile_t *profile) { uint32_t error_code; - SERVICES_set_run_cfg(se_services_handle, profile, &error_code); + SERVICES_set_run_cfg(se_sess_handle, profile, &error_code); return error_code; } uint32_t se_services_get_off_profile(off_profile_t *profile) { uint32_t error_code; - SERVICES_get_off_cfg(se_services_handle, profile, &error_code); + SERVICES_get_off_cfg(se_sess_handle, profile, &error_code); return error_code; } uint32_t se_services_set_off_profile(off_profile_t *profile) { uint32_t error_code; - SERVICES_set_off_cfg(se_services_handle, profile, &error_code); + SERVICES_set_off_cfg(se_sess_handle, profile, &error_code); return error_code; } uint32_t se_services_boot_process_toc_entry(const uint8_t *image_id) { uint32_t error_code; - SERVICES_boot_process_toc_entry(se_services_handle, image_id, &error_code); + SERVICES_boot_process_toc_entry(se_sess_handle, image_id, &error_code); return error_code; } uint32_t se_services_boot_cpu(uint32_t cpu_id, uint32_t address) { uint32_t error_code; - SERVICES_boot_cpu(se_services_handle, cpu_id, address, &error_code); + SERVICES_boot_cpu(se_sess_handle, cpu_id, address, &error_code); return error_code; } @@ -221,7 +278,7 @@ uint32_t se_services_boot_reset_cpu(uint32_t cpu_id) { } for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { - uint32_t ret = SERVICES_boot_reset_cpu(se_services_handle, cpu_id, &error_code); + uint32_t ret = SERVICES_boot_reset_cpu(se_sess_handle, cpu_id, &error_code); if (ret != SERVICES_REQ_SUCCESS) { return error_code; } @@ -234,11 +291,12 @@ uint32_t se_services_boot_reset_cpu(uint32_t cpu_id) { return SERVICES_REQ_TIMEOUT; } } + return error_code; } uint32_t se_services_boot_release_cpu(uint32_t cpu_id) { uint32_t error_code; - SERVICES_boot_release_cpu(se_services_handle, cpu_id, &error_code); + SERVICES_boot_release_cpu(se_sess_handle, cpu_id, &error_code); return error_code; } diff --git a/ports/alif/se_services.h b/ports/alif/se_services.h index 87deb055929..44b6584a200 100644 --- a/ports/alif/se_services.h +++ b/ports/alif/se_services.h @@ -29,12 +29,16 @@ #include "services_lib_api.h" void se_services_init(void); +void se_services_deinit(void); void se_services_dump_device_data(void); void se_services_get_unique_id(uint8_t id[8]); __attribute__((noreturn)) void se_services_reset_soc(void); uint64_t se_services_rand64(void); +uint32_t se_services_notify(void); + uint32_t se_services_enable_clock(clock_enable_t clock, bool enable); uint32_t se_services_select_pll_source(pll_source_t source, pll_target_t target); + uint32_t se_services_get_run_profile(run_profile_t *profile); uint32_t se_services_set_run_profile(run_profile_t *profile); uint32_t se_services_get_off_profile(off_profile_t *profile); From 182b5f3a12427d3dc21744b0164d546663ea696b Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 15 Feb 2025 14:00:42 +0100 Subject: [PATCH 0502/2098] alif/mpmetalport: Use MHU to notify remote cores. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike HWSEM, the MHU IRQ can wake up cores from low-power modes, making it better suited for notifying remote cores. Note that no special function is required to wake up a remote core—the act of sending a message alone will notify it. Signed-off-by: iabdalkader --- ports/alif/mpmetalport.c | 31 +++++++------------------------ ports/alif/mpmetalport.h | 14 -------------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/ports/alif/mpmetalport.c b/ports/alif/mpmetalport.c index a7d4025468a..b0a017fdd79 100644 --- a/ports/alif/mpmetalport.c +++ b/ports/alif/mpmetalport.c @@ -27,8 +27,6 @@ */ #include ALIF_CMSIS_H -#include "hwsem.h" - #include "py/mperrno.h" #include "py/mphal.h" @@ -36,18 +34,14 @@ #include "metal/utilities.h" #include "metal/device.h" +#include "se_services.h" + struct metal_state _metal; static mp_sched_node_t rproc_notify_node; int metal_sys_init(const struct metal_init_params *params) { metal_unused(params); - // Reset the hardware semaphore. - hwsem_reset(METAL_HSEM_DEVICE); - #if MICROPY_PY_OPENAMP_HOST - hwsem_reset(METAL_HSEM_REMOTE); - #endif - // If cache management is not enabled, configure the MPU to disable // caching for the entire Open-AMP shared memory region. #ifndef VIRTIO_USE_DCACHE @@ -59,18 +53,10 @@ int metal_sys_init(const struct metal_init_params *params) { #endif metal_bus_register(&metal_generic_bus); - - // Enable the hardware semaphore IRQ. - NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); - NVIC_SetPriority(METAL_HSEM_IRQn, IRQ_PRI_HWSEM); - NVIC_EnableIRQ(METAL_HSEM_IRQn); return 0; } void metal_sys_finish(void) { - NVIC_DisableIRQ(METAL_HSEM_IRQn); - NVIC_ClearPendingIRQ(METAL_HSEM_IRQn); - hwsem_reset(METAL_HSEM_DEVICE); metal_bus_unregister(&metal_generic_bus); } @@ -99,15 +85,12 @@ void metal_machine_cache_invalidate(void *addr, unsigned int len) { } int metal_rproc_notify(void *priv, uint32_t id) { - // Release the HW semaphore to notify the other core. - hwsem_release(METAL_HSEM_REMOTE, HWSEM_MASTERID); + // Notify the remote core. + se_services_notify(); return 0; } -void METAL_HSEM_IRQ_HANDLER(void) { - // Schedule the node only if the other core released the Semaphore. - if (METAL_HSEM_DEVICE->HWSEM_REL_REG == 0) { - mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); - } - hwsem_request(METAL_HSEM_DEVICE, METAL_HSEM_REMOTE_ID); +void metal_rproc_notified(void) { + // The remote core notified this core. + mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); } diff --git a/ports/alif/mpmetalport.h b/ports/alif/mpmetalport.h index 7a428dbc6c6..c11725a1f1e 100644 --- a/ports/alif/mpmetalport.h +++ b/ports/alif/mpmetalport.h @@ -38,20 +38,6 @@ #define METAL_MAX_DEVICE_REGIONS 2 -#if MICROPY_PY_OPENAMP_HOST -#define METAL_HSEM_DEVICE ((HWSEM_Type *)HWSEM14_BASE) -#define METAL_HSEM_REMOTE ((HWSEM_Type *)HWSEM15_BASE) -#define METAL_HSEM_REMOTE_ID (0x410FD222U) -#define METAL_HSEM_IRQn HWSEM_IRQ14_IRQn -#define METAL_HSEM_IRQ_HANDLER HWSEM_IRQ14Handler -#else -#define METAL_HSEM_DEVICE ((HWSEM_Type *)HWSEM15_BASE) -#define METAL_HSEM_REMOTE ((HWSEM_Type *)HWSEM14_BASE) -#define METAL_HSEM_REMOTE_ID (0x410FD221U) -#define METAL_HSEM_IRQn HWSEM_IRQ15_IRQn -#define METAL_HSEM_IRQ_HANDLER HWSEM_IRQ15Handler -#endif - // Set to 1 to enable log output. #define METAL_LOG_HANDLER_ENABLE 0 From 68b1dae011dd9f2696e9f6f2f914317d827c879d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 19 Feb 2025 14:42:05 +0100 Subject: [PATCH 0503/2098] alif: Link with libnosys. This allows the correct start up functions to be called by the stdlib. Signed-off-by: iabdalkader --- ports/alif/alif.mk | 22 +++++++++---------- ports/alif/main.c | 2 +- ports/alif/nosys_stubs.c | 46 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 ports/alif/nosys_stubs.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index aa2451d7a62..7e58aa5729f 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -68,9 +68,9 @@ CFLAGS += $(INC) \ -mtune=cortex-m55 \ $(CFLAGS_FPU) \ -march=armv8.1-m.main+fp+mve.fp \ - -nostdlib \ -fdata-sections \ -ffunction-sections \ + --specs=nosys.specs \ -D$(MCU_CORE)=1 \ -DCORE_$(MCU_CORE) \ -DALIF_CMSIS_H="\"$(MCU_CORE).h\"" @@ -95,18 +95,17 @@ CFLAGS += $(CFLAGS_EXTRA) AFLAGS = -mthumb -march=armv8.1-m.main+fp+mve.fp $(CFLAGS_FPU) -LDFLAGS += -nostdlib \ - -T$(BUILD)/ensemble.ld \ - -Map=$@.map \ - --cref \ - --gc-sections \ - --print-memory-usage +CFLAGS += -Wl,-T$(BUILD)/ensemble.ld \ + -Wl,-Map=$@.map \ + -Wl,--cref \ + -Wl,--gc-sections \ + -Wl,--print-memory-usage \ + -Wl,--no-warn-rwx-segment + ifeq ($(MCU_CORE),M55_HP) -LDFLAGS += --wrap=dcd_event_handler +CFLAGS += -Wl,--wrap=dcd_event_handler endif -LIBS += "$(shell $(CC) $(CFLAGS) -print-libgcc-file-name)" - ################################################################################ # Source files and libraries @@ -126,6 +125,7 @@ SRC_C = \ mpu.c \ mpuart.c \ msc_disk.c \ + nosys_stubs.c \ ospi_ext.c \ ospi_flash.c \ pendsv.c \ @@ -256,7 +256,7 @@ $(BUILD)/ensemble.ld: $(LD_FILE) $(BUILD)/firmware.elf: $(OBJ) $(BUILD)/ensemble.ld $(ECHO) "Link $@" - $(Q)$(LD) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) + $(Q)$(CC) $(CFLAGS) -o $@ $(OBJ) $(LIBS) $(Q)$(SIZE) $@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf diff --git a/ports/alif/main.c b/ports/alif/main.c index 975ee7ed2d3..793425fff2e 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -55,7 +55,7 @@ NORETURN void panic(const char *msg) { } } -void _start(void) { +int main(void) { system_tick_init(); MICROPY_BOARD_STARTUP(); diff --git a/ports/alif/nosys_stubs.c b/ports/alif/nosys_stubs.c new file mode 100644 index 00000000000..a394ec8f1cf --- /dev/null +++ b/ports/alif/nosys_stubs.c @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 + +int _write(int handle, char *buffer, int size) { + errno = ENOSYS; + return -1; +} + +int _read(int handle, char *buffer, int size) { + errno = ENOSYS; + return -1; +} + +int _close(int f) { + errno = ENOSYS; + return -1; +} + +int _lseek(int f, int ptr, int dir) { + errno = ENOSYS; + return -1; +} From 7e32c232183441e1022f59e58c31ba431c25fe2f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Mar 2025 23:29:58 +1100 Subject: [PATCH 0504/2098] alif/mpmetalport: Only notify after metal subsystem is init'd. Signed-off-by: Damien George --- ports/alif/mpmetalport.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/alif/mpmetalport.c b/ports/alif/mpmetalport.c index b0a017fdd79..9d774cb9067 100644 --- a/ports/alif/mpmetalport.c +++ b/ports/alif/mpmetalport.c @@ -37,6 +37,8 @@ #include "se_services.h" struct metal_state _metal; + +static bool metal_active; static mp_sched_node_t rproc_notify_node; int metal_sys_init(const struct metal_init_params *params) { @@ -53,10 +55,13 @@ int metal_sys_init(const struct metal_init_params *params) { #endif metal_bus_register(&metal_generic_bus); + metal_active = true; + return 0; } void metal_sys_finish(void) { + metal_active = false; metal_bus_unregister(&metal_generic_bus); } @@ -91,6 +96,9 @@ int metal_rproc_notify(void *priv, uint32_t id) { } void metal_rproc_notified(void) { + if (!metal_active) { + return; + } // The remote core notified this core. mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); } From ca3d50a096ffa999e47b98335ceb0b7f459196c2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Mar 2025 23:31:58 +1100 Subject: [PATCH 0505/2098] alif/mpuart: Use mp_hal_pin_config for TX/RX configuration. Signed-off-by: Damien George --- ports/alif/mpuart.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ports/alif/mpuart.c b/ports/alif/mpuart.c index e9cfcf34fed..69734659a41 100644 --- a/ports/alif/mpuart.c +++ b/ports/alif/mpuart.c @@ -35,10 +35,8 @@ #include "sys_ctrl_uart.h" #include "uart.h" -#define TX_PORT PORT_12 -#define TX_PIN PIN_2 -#define RX_PORT PORT_12 -#define RX_PIN PIN_1 +#define TX_PIN pin_P12_2 +#define RX_PIN pin_P12_1 #define UART_ID 4 #define UART_IRQN UART4_IRQ_IRQn #define UART_PTR ((UART_Type *)UART4_BASE) @@ -48,8 +46,8 @@ static UART_TRANSFER transfer; void mp_uart_init(void) { - pinconf_set(TX_PORT, TX_PIN, PINMUX_ALTERNATE_FUNCTION_2, 0); - pinconf_set(RX_PORT, RX_PIN, PINMUX_ALTERNATE_FUNCTION_2, PADCTRL_READ_ENABLE); + mp_hal_pin_config(TX_PIN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_UART, false); + mp_hal_pin_config(RX_PIN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_UART, true); select_uart_clock_syst_pclk(UART_ID); enable_uart_clock(UART_ID); uart_software_reset(UART_PTR); From af574a86c2ec3af02b167d449b8a8718f39f1ca8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Dec 2024 00:18:56 +1100 Subject: [PATCH 0506/2098] alif/alif_flash: Distinguish between total flash size and FS size. Signed-off-by: Damien George --- ports/alif/alif_flash.c | 8 ++++---- ports/alif/msc_disk.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/alif/alif_flash.c b/ports/alif/alif_flash.c index f2ea5ac85a8..de7b37804f9 100644 --- a/ports/alif/alif_flash.c +++ b/ports/alif/alif_flash.c @@ -37,10 +37,10 @@ typedef struct _alif_flash_obj_t { uint32_t flash_size; } alif_flash_obj_t; -static alif_flash_obj_t alif_flash_obj = { +static const alif_flash_obj_t alif_flash_fs_obj = { .base = { &alif_flash_type }, .flash_base_addr = MICROPY_HW_FLASH_STORAGE_BASE_ADDR, - .flash_size = MICROPY_HW_FLASH_STORAGE_BYTES, + .flash_size = MICROPY_HW_FLASH_STORAGE_FS_BYTES, }; static mp_obj_t alif_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -54,8 +54,8 @@ static mp_obj_t alif_flash_make_new(const mp_obj_type_t *type, size_t n_args, si mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); if (args[ARG_start].u_int == -1 && args[ARG_len].u_int == -1) { - // Default singleton object that accesses entire flash - return MP_OBJ_FROM_PTR(&alif_flash_obj); + // Default singleton object that accesses writable-filesystem flash + return MP_OBJ_FROM_PTR(&alif_flash_fs_obj); } alif_flash_obj_t *self = mp_obj_malloc(alif_flash_obj_t, &alif_flash_type); diff --git a/ports/alif/msc_disk.c b/ports/alif/msc_disk.c index a01494d20f0..0b8ddddeda2 100644 --- a/ports/alif/msc_disk.c +++ b/ports/alif/msc_disk.c @@ -37,7 +37,7 @@ #endif #define BLOCK_SIZE (MICROPY_HW_FLASH_BLOCK_SIZE_BYTES) -#define BLOCK_COUNT (MICROPY_HW_FLASH_STORAGE_BYTES / BLOCK_SIZE) +#define BLOCK_COUNT (MICROPY_HW_FLASH_STORAGE_FS_BYTES / BLOCK_SIZE) #define FLASH_BASE_ADDR (MICROPY_HW_FLASH_STORAGE_BASE_ADDR) static bool ejected = false; From d895a62b0703d28fb2b15427760aae8a67ad666f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Dec 2024 00:19:20 +1100 Subject: [PATCH 0507/2098] alif/alif_flash: Make flash respond to the buffer protocol. Signed-off-by: Damien George --- ports/alif/alif_flash.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ports/alif/alif_flash.c b/ports/alif/alif_flash.c index de7b37804f9..722afadae1f 100644 --- a/ports/alif/alif_flash.c +++ b/ports/alif/alif_flash.c @@ -80,6 +80,19 @@ static mp_obj_t alif_flash_make_new(const mp_obj_type_t *type, size_t n_args, si return MP_OBJ_FROM_PTR(self); } +static mp_int_t alif_flash_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + alif_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)(ospi_flash_get_xip_base() + self->flash_base_addr); + bufinfo->len = self->flash_size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Can't return a writable buffer. + return 1; + } +} + static mp_obj_t alif_flash_readblocks(size_t n_args, const mp_obj_t *args) { alif_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); uint32_t offset = mp_obj_get_int(args[1]) * MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; @@ -157,6 +170,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_QSTR_Flash, MP_TYPE_FLAG_NONE, make_new, alif_flash_make_new, + buffer, alif_flash_get_buffer, locals_dict, &alif_flash_locals_dict ); #endif // MICROPY_HW_ENABLE_OSPI From f83f6e7eed39f961ff9788814d3ee9104d3577a6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Mar 2025 16:30:15 +1100 Subject: [PATCH 0508/2098] alif/mpu: Add function to set read-only bit on MRAM MPU region. To allow writing to MRAM region. Signed-off-by: Damien George --- ports/alif/mpu.c | 11 +++++++++++ ports/alif/mpu.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/ports/alif/mpu.c b/ports/alif/mpu.c index 471eff20a2c..60753674ae1 100644 --- a/ports/alif/mpu.c +++ b/ports/alif/mpu.c @@ -25,6 +25,7 @@ */ #include "py/mpconfig.h" +#include "irq.h" #include "mpu.h" #include ALIF_CMSIS_H @@ -76,3 +77,13 @@ void MPU_Load_Regions(void) { // Load the MPU regions from the table. ARM_MPU_Load(0, mpu_table, sizeof(mpu_table) / sizeof(ARM_MPU_Region_t)); } + +void mpu_config_mram(bool read_only) { + uintptr_t atomic = disable_irq(); + ARM_MPU_Disable(); + MPU->RNR = MP_MPU_REGION_MRAM; + MPU->RBAR = ARM_MPU_RBAR(MRAM_BASE, ARM_MPU_SH_NON, read_only, 1, 0); + MPU->RLAR = ARM_MPU_RLAR(MRAM_BASE + MRAM_SIZE - 1, MP_MPU_ATTR_NORMAL_WT_RA); + ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk); + enable_irq(atomic); +} diff --git a/ports/alif/mpu.h b/ports/alif/mpu.h index 88fbe011206..1d3602941ef 100644 --- a/ports/alif/mpu.h +++ b/ports/alif/mpu.h @@ -24,6 +24,8 @@ * THE SOFTWARE. */ +#include + #define MP_MPU_ATTR_NORMAL_WT_RA_TRANSIENT (0) #define MP_MPU_ATTR_DEVICE_nGnRE (1) #define MP_MPU_ATTR_NORMAL_WB_RA_WA (2) @@ -37,3 +39,5 @@ #define MP_MPU_REGION_OSPI_REGISTERS (4) #define MP_MPU_REGION_OSPI0_XIP (5) #define MP_MPU_REGION_OPENAMP (6) + +void mpu_config_mram(bool read_only); From 8297c95c22fd15aa706243bfebb0688a61827855 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Mar 2025 14:51:45 +1100 Subject: [PATCH 0509/2098] alif/vfs_rom_ioctl: Add vfs_rom_ioctl with support for OSPI and MRAM. Signed-off-by: Damien George --- ports/alif/alif.mk | 3 + ports/alif/mpconfigport.h | 1 + ports/alif/vfs_rom_ioctl.c | 175 +++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 ports/alif/vfs_rom_ioctl.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 7e58aa5729f..f0ff8d91a53 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -132,6 +132,7 @@ SRC_C = \ system_tick.c \ se_services.c \ usbd.c \ + vfs_rom_ioctl.c \ $(wildcard $(BOARD_DIR)/*.c) ifeq ($(MICROPY_FLOAT_IMPL),float) @@ -194,6 +195,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ drivers/source/mhu_driver.c \ drivers/source/mhu_receiver.c \ drivers/source/mhu_sender.c \ + drivers/source/mram.c \ drivers/source/pinconf.c \ drivers/source/uart.c \ drivers/source/utimer.c \ @@ -209,6 +211,7 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ ) $(BUILD)/tinyusb_port/tusb_alif_dcd.o: CFLAGS += -Wno-unused-variable -DTUSB_ALIF_NO_IRQ_CFG=1 +$(BUILD)/$(ALIF_DFP_REL_TOP)/drivers/source/mram.o: CFLAGS += -Wno-strict-aliasing $(BUILD)/$(ALIF_DFP_REL_TOP)/drivers/source/spi.o: CFLAGS += -Wno-maybe-uninitialized $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_boot.o: CFLAGS += -Wno-stringop-truncation $(BUILD)/$(ALIF_DFP_REL_TOP)/se_services/source/services_host_system.o: CFLAGS += -Wno-maybe-uninitialized diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 83f808b131f..33f300641fb 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -137,6 +137,7 @@ #define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_TIMER (1) #define MICROPY_VFS (1) +#define MICROPY_VFS_ROM (1) // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (1) diff --git a/ports/alif/vfs_rom_ioctl.c b/ports/alif/vfs_rom_ioctl.c new file mode 100644 index 00000000000..d82ac6f8417 --- /dev/null +++ b/ports/alif/vfs_rom_ioctl.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 OpenMV LLC. + * + * 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/mperrno.h" +#include "py/objarray.h" +#include "py/runtime.h" +#include "extmod/vfs.h" +#include "modalif.h" +#include "mpu.h" +#include "mram.h" +#include "ospi_flash.h" + +#if MICROPY_VFS_ROM_IOCTL + +#if MICROPY_HW_ROMFS_ENABLE_PART0 && !defined(MICROPY_HW_ROMFS_PART0_START) +#define MICROPY_HW_ROMFS_PART0_START (uintptr_t)(&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_PART0_SIZE (uintptr_t)(&_micropy_hw_romfs_part0_size) +extern uint8_t _micropy_hw_romfs_part0_start; +extern uint8_t _micropy_hw_romfs_part0_size; +#endif + +#if MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) +#define MICROPY_HW_ROMFS_PART1_START (uintptr_t)(&_micropy_hw_romfs_part1_start) +#define MICROPY_HW_ROMFS_PART1_SIZE (uintptr_t)(&_micropy_hw_romfs_part1_size) +extern uint8_t _micropy_hw_romfs_part1_start; +extern uint8_t _micropy_hw_romfs_part1_size; +#endif + +#define ROMFS_MEMORYVIEW(base, size) {{&mp_type_memoryview}, 'B', 0, (size), (void *)(base)} + +static const mp_obj_array_t romfs_obj_table[] = { + #if MICROPY_HW_ROMFS_ENABLE_PART0 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART0_START, MICROPY_HW_ROMFS_PART0_SIZE), + #endif + #if MICROPY_HW_ROMFS_ENABLE_PART1 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART1_START, MICROPY_HW_ROMFS_PART1_SIZE), + #endif +}; + +static inline bool mram_is_valid_addr(uintptr_t addr) { + return MRAM_BASE <= addr && addr < MRAM_BASE + MRAM_SIZE; +} + +static inline bool ospi_is_valid_addr(uintptr_t xip_base, uintptr_t addr) { + MP_STATIC_ASSERT(OSPI0_XIP_SIZE == OSPI1_XIP_SIZE); + return xip_base <= addr && addr < xip_base + OSPI0_XIP_SIZE; +} + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + mp_int_t cmd = mp_obj_get_int(args[0]); + if (cmd == MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS) { + return MP_OBJ_NEW_SMALL_INT(MP_ARRAY_SIZE(romfs_obj_table)); + } + + if (n_args < 2) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + mp_int_t romfs_id = mp_obj_get_int(args[1]); + if (!(0 <= romfs_id && romfs_id < MP_ARRAY_SIZE(romfs_obj_table))) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + const mp_obj_array_t *romfs_obj = &romfs_obj_table[romfs_id]; + + if (cmd == MP_VFS_ROM_IOCTL_GET_SEGMENT) { + // Return the ROMFS memoryview object. + return MP_OBJ_FROM_PTR(romfs_obj); + } + + uintptr_t romfs_base = (uintptr_t)romfs_obj->items; + uintptr_t romfs_len = romfs_obj->len; + + #if MICROPY_HW_ENABLE_OSPI + const uintptr_t ospi_base = ospi_flash_get_xip_base(); + #endif + + if (cmd == MP_VFS_ROM_IOCTL_WRITE_PREPARE) { + if (n_args < 3) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = romfs_base; + uint32_t dest_max = dest + mp_obj_get_int(args[2]); + if (dest_max > romfs_base + romfs_len) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + if (mram_is_valid_addr(dest)) { + // No preparation needed for MRAM. + // Return the minimum write size. + return MP_OBJ_NEW_SMALL_INT(MRAM_SECTOR_SIZE); + } + + #if MICROPY_HW_ENABLE_OSPI + if (ospi_is_valid_addr(ospi_base, dest)) { + // Erase OSPI flash. + dest -= ospi_base; + dest_max -= ospi_base; + while (dest < dest_max) { + int ret = ospi_flash_erase_sector(dest); + mp_event_handle_nowait(); + if (ret < 0) { + return MP_OBJ_NEW_SMALL_INT(ret); + } + dest += MICROPY_HW_FLASH_BLOCK_SIZE_BYTES; + } + // Return the minimum write size. + return MP_OBJ_NEW_SMALL_INT(4); + } + #endif + } + + if (cmd == MP_VFS_ROM_IOCTL_WRITE) { + if (n_args < 4) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = romfs_base + mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + if (dest + bufinfo.len > romfs_base + romfs_len) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + if (mram_is_valid_addr(dest)) { + // Write data to MRAM. + mpu_config_mram(false); + const uint8_t *src = bufinfo.buf; + const uint8_t *max = src + bufinfo.len; + while (src < max) { + mram_write_128bit((uint8_t *)dest, src); + dest += MRAM_SECTOR_SIZE; + src += MRAM_SECTOR_SIZE; + } + mpu_config_mram(true); + return MP_OBJ_NEW_SMALL_INT(0); // success + } + + #if MICROPY_HW_ENABLE_OSPI + if (ospi_is_valid_addr(ospi_base, dest)) { + // Write data to OSPI flash. + dest -= ospi_base; + int ret = ospi_flash_write(dest, bufinfo.len, bufinfo.buf); + mp_event_handle_nowait(); + return MP_OBJ_NEW_SMALL_INT(ret); + } + #endif + } + + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); +} + +#endif // MICROPY_VFS_ROM_IOCTL From d1b12cb6766d8b48d0e8cdf551808c324d0599c1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 8 Mar 2025 23:19:47 +1100 Subject: [PATCH 0510/2098] alif/modules: Make HE core set /rom as current dir. This allows HE to execute code from the ROMFS in MRAM. Signed-off-by: Damien George --- ports/alif/modules/he/_boot.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/ports/alif/modules/he/_boot.py b/ports/alif/modules/he/_boot.py index bc1320f1906..3df18047090 100644 --- a/ports/alif/modules/he/_boot.py +++ b/ports/alif/modules/he/_boot.py @@ -1,21 +1,7 @@ -import openamp -import time -from machine import Pin +# Change working directory to ROMFS, so boot.py and main.py can run from there. +try: + import os - -def ept_recv_callback(src_addr, data): - print("Received message on endpoint", data) - - -# Create a new RPMsg endpoint to communicate with main core. -ept = openamp.Endpoint("vuart-channel", callback=ept_recv_callback) - -pin = Pin("LED_BLUE", Pin.OUT) - -count = 0 -while True: - if ept.is_ready(): - ept.send("Hello from HE %d" % count, timeout=1000) - count += 1 - time.sleep_ms(100) - pin(not pin()) + os.chdir("/rom") +except: + pass From 4f2a8bd99f81f88fd173aa717523345ba32083e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Oct 2024 16:25:22 +1100 Subject: [PATCH 0511/2098] alif/mphalport: Add mp_hal_pin_config_irq_falling helper. Signed-off-by: Damien George --- ports/alif/mphalport.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 3b23cb6a2c5..5f1e3fff829 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -155,6 +155,16 @@ static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { gpio_set_direction_output(pin->gpio, pin->pin); } +static inline void mp_hal_pin_config_irq_falling(mp_hal_pin_obj_t pin, bool enable) { + if (enable) { + gpio_enable_interrupt(pin->gpio, pin->pin); + gpio_interrupt_set_edge_trigger(pin->gpio, pin->pin); + gpio_interrupt_set_polarity_low(pin->gpio, pin->pin); + } else { + gpio_disable_interrupt(pin->gpio, pin->pin); + } +} + static inline void mp_hal_pin_low(mp_hal_pin_obj_t pin) { gpio_set_value_low(pin->gpio, pin->pin); } From 411146b0ed0fa2d70fc13264b1bd2cda9045c99e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Oct 2024 21:48:51 +1100 Subject: [PATCH 0512/2098] alif/mpuart: Generalise UART driver to suppot all UART instances. Signed-off-by: Damien George --- ports/alif/main.c | 2 +- ports/alif/mpconfigport.h | 2 +- ports/alif/mphalport.c | 2 +- ports/alif/mpuart.c | 294 ++++++++++++++++++++++++++++++-------- ports/alif/mpuart.h | 17 ++- 5 files changed, 251 insertions(+), 66 deletions(-) diff --git a/ports/alif/main.c b/ports/alif/main.c index 793425fff2e..77e413e1eb4 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -66,7 +66,7 @@ int main(void) { MICROPY_BOARD_EARLY_INIT(); #if MICROPY_HW_ENABLE_UART_REPL - mp_uart_init(); + mp_uart_init_repl(); #endif #if MICROPY_HW_ENABLE_OSPI if (ospi_flash_init() != 0) { diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 33f300641fb..a5fd4f6dbfa 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -73,7 +73,7 @@ #define MICROPY_HW_USB_PID (0x9802) // interface has CDC only #endif #ifndef MICROPY_HW_ENABLE_UART_REPL -#define MICROPY_HW_ENABLE_UART_REPL (CORE_M55_HP) // useful if there is no USB +#define MICROPY_HW_ENABLE_UART_REPL (0) #endif #define MICROPY_HW_FLASH_BLOCK_SIZE_BYTES (4096) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 1a6136c31ab..8f7780bb8ac 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -91,7 +91,7 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { #endif #if MICROPY_HW_ENABLE_UART_REPL - mp_uart_write_strn(str, len); + mp_uart_write_strn_repl(str, len); did_write = true; #endif diff --git a/ports/alif/mpuart.c b/ports/alif/mpuart.c index 69734659a41..7f14ff54447 100644 --- a/ports/alif/mpuart.c +++ b/ports/alif/mpuart.c @@ -24,88 +24,260 @@ * THE SOFTWARE. */ -#include -#include "py/runtime.h" #include "py/mphal.h" +#include "py/runtime.h" #include "mpuart.h" -#if MICROPY_HW_ENABLE_UART_REPL - -#include "pinconf.h" #include "sys_ctrl_uart.h" #include "uart.h" -#define TX_PIN pin_P12_2 -#define RX_PIN pin_P12_1 -#define UART_ID 4 -#define UART_IRQN UART4_IRQ_IRQn -#define UART_PTR ((UART_Type *)UART4_BASE) -#define BAUDRATE 115200 -#define SYST_PCLK 100000000 - -static UART_TRANSFER transfer; - -void mp_uart_init(void) { - mp_hal_pin_config(TX_PIN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_UART, false); - mp_hal_pin_config(RX_PIN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_UART, true); - select_uart_clock_syst_pclk(UART_ID); - enable_uart_clock(UART_ID); - uart_software_reset(UART_PTR); - uart_enable_fifo(UART_PTR); - uart_disable_tx_irq(UART_PTR); - uart_disable_rx_irq(UART_PTR); - uart_set_baudrate(UART_PTR, SYST_PCLK, BAUDRATE); - uart_set_data_parity_stop_bits(UART_PTR, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1); - uart_set_flow_control(UART_PTR, UART_FLOW_CONTROL_NONE); - NVIC_ClearPendingIRQ(UART_IRQN); - NVIC_SetPriority(UART_IRQN, IRQ_PRI_UART_REPL); - NVIC_EnableIRQ(UART_IRQN); - uart_set_tx_trigger(UART_PTR, UART_TX_FIFO_EMPTY); - uart_set_rx_trigger(UART_PTR, UART_RX_ONE_CHAR_IN_FIFO); - uart_enable_rx_irq(UART_PTR); +#define UART_MAX (8) +#define SYST_PCLK (100000000) + +typedef struct _uart_state_t { + UART_TRANSFER_STATUS status; + ringbuf_t *rx_ringbuf; + const uint8_t *tx_src; + const uint8_t *tx_src_max; + void (*irq_callback)(void); +} uart_state_t; + +static const uint8_t uart_irqn[UART_MAX] = { + UART0_IRQ_IRQn, + UART1_IRQ_IRQn, + UART2_IRQ_IRQn, + UART3_IRQ_IRQn, + UART4_IRQ_IRQn, + UART5_IRQ_IRQn, + UART6_IRQ_IRQn, + UART7_IRQ_IRQn, +}; + +static UART_Type *const uart_periph[UART_MAX] = { + (UART_Type *)UART0_BASE, + (UART_Type *)UART1_BASE, + (UART_Type *)UART2_BASE, + (UART_Type *)UART3_BASE, + (UART_Type *)UART4_BASE, + (UART_Type *)UART5_BASE, + (UART_Type *)UART6_BASE, + (UART_Type *)UART7_BASE, +}; + +static uart_state_t uart_state[UART_MAX]; + +void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf) { + UART_Type *uart = uart_periph[uart_id]; + uart_state_t *state = &uart_state[uart_id]; + + // Configure TX/RX pins. + mp_hal_pin_config(tx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, false); + mp_hal_pin_config(rx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, true); + + // Configure the UART peripheral. + select_uart_clock_syst_pclk(uart_id); + enable_uart_clock(uart_id); + uart_software_reset(uart); + uart_enable_fifo(uart); + uart_disable_tx_irq(uart); + uart_disable_rx_irq(uart); + uart_set_baudrate(uart, SYST_PCLK, baudrate); + uart_set_data_parity_stop_bits(uart, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1); + uart_set_flow_control(uart, UART_FLOW_CONTROL_NONE); + uart->UART_FCR |= UART_FCR_RCVR_FIFO_RESET; + uart_set_tx_trigger(uart, UART_TX_FIFO_EMPTY); + uart_set_rx_trigger(uart, UART_RX_ONE_CHAR_IN_FIFO); + + // Initialise the state. + state->status = UART_TRANSFER_STATUS_NONE; + state->rx_ringbuf = rx_ringbuf; + state->tx_src = NULL; + state->tx_src_max = NULL; + state->irq_callback = NULL; + + // Enable interrupts. + NVIC_ClearPendingIRQ(uart_irqn[uart_id]); + NVIC_SetPriority(uart_irqn[uart_id], IRQ_PRI_UART_REPL); + NVIC_EnableIRQ(uart_irqn[uart_id]); + uart_enable_rx_irq(uart); +} + +void mp_uart_deinit(unsigned int uart_id) { + UART_Type *uart = uart_periph[uart_id]; + + uart_disable_rx_irq(uart); + NVIC_DisableIRQ(uart_irqn[uart_id]); +} + +void mp_uart_set_irq_callback(unsigned int uart_id, void (*callback)(void)) { + uart_state_t *state = &uart_state[uart_id]; + state->irq_callback = callback; } -void mp_uart_write_strn(const char *str, size_t len) { - memset(&transfer, 0, sizeof(transfer)); - transfer.tx_buf = (uint8_t *)str; - transfer.tx_total_num = len; - transfer.tx_curr_cnt = 0U; - transfer.status = UART_TRANSFER_STATUS_NONE; +void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj_t cts) { + UART_Type *uart = uart_periph[uart_id]; - uart_enable_tx_irq(UART_PTR); + unsigned int flow = UART_FLOW_CONTROL_NONE; + if (rts != NULL) { + flow |= UART_FLOW_CONTROL_RTS; + mp_hal_pin_config(rts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, false); + } + if (cts != NULL) { + flow |= UART_FLOW_CONTROL_CTS; + mp_hal_pin_config(cts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, true); + } + uart_set_flow_control(uart, flow); +} + +void mp_uart_set_baudrate(unsigned int uart_id, uint32_t baudrate) { + UART_Type *uart = uart_periph[uart_id]; + + uart_set_baudrate(uart, SYST_PCLK, baudrate); +} + +size_t mp_uart_rx_any(unsigned int uart_id) { + uart_state_t *state = &uart_state[uart_id]; + if (state->rx_ringbuf != NULL) { + return ringbuf_avail(state->rx_ringbuf); + } + return 0; +} +int mp_uart_rx_char(unsigned int uart_id) { + uart_state_t *state = &uart_state[uart_id]; + if (state->rx_ringbuf != NULL && ringbuf_avail(state->rx_ringbuf)) { + return ringbuf_get(state->rx_ringbuf); + } + return -1; +} + +void mp_uart_tx_data_blocking(unsigned int uart_id, const uint8_t *src, size_t len) { + UART_Type *uart = uart_periph[uart_id]; + + for (size_t i = 0; i < len; ++i) { + for (;;) { + size_t tx_avail = UART_FIFO_DEPTH - uart->UART_TFL; + if (tx_avail > 0) { + break; + } + mp_event_handle_nowait(); + } + uart->UART_THR = src[i]; + } +} + +void mp_uart_tx_data(unsigned int uart_id, const uint8_t *src, size_t len) { + UART_Type *uart = uart_periph[uart_id]; + + // Configure transmission, to be handled by IRQ. + uart_state_t *state = &uart_state[uart_id]; + state->status = UART_TRANSFER_STATUS_NONE; + state->tx_src = src; + state->tx_src_max = src + len; + + // Enable TX IRQ to start transmission. + uart_enable_tx_irq(uart); + + // Wait for transfer to complete. + const uint32_t total_timeout_ms = 100 * len; uint32_t start = mp_hal_ticks_ms(); - while (transfer.status == UART_TRANSFER_STATUS_NONE) { - if (mp_hal_ticks_ms() - start > 10 * len) { + while (state->status == UART_TRANSFER_STATUS_NONE) { + if (mp_hal_ticks_ms() - start > total_timeout_ms) { break; } - __WFE(); + mp_event_wait_indefinite(); } - uart_disable_tx_irq(UART_PTR); + + // Disable TX IRQ. + uart_disable_tx_irq(uart); } -void UART4_IRQHandler(void) { - if (UART_PTR->UART_RFL) { - for (;;) { - uint32_t rx_fifo_available_cnt = UART_PTR->UART_RFL; - if (rx_fifo_available_cnt == 0) { - break; +static void mp_uart_irq_handler(unsigned int uart_id) { + UART_Type *uart = uart_periph[uart_id]; + uart_state_t *state = &uart_state[uart_id]; + + // Process pending interrupt (below is order of highest to lowest priority). + uint32_t iir = uart->UART_IIR & UART_IIR_INTERRUPT_ID_MASK; + switch (iir) { + case UART_IIR_RECEIVER_LINE_STATUS: { + uint32_t lsr = uart->UART_LSR; + if (lsr & (UART_LSR_RECEIVER_FIFO_ERR | UART_LSR_OVERRUN_ERR)) { + state->status = UART_TRANSFER_STATUS_ERROR; } - for (uint32_t i = 0; i < rx_fifo_available_cnt; ++i) { - int c = UART_PTR->UART_RBR; - #if MICROPY_KBD_EXCEPTION - if (c == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - continue; + break; + } + + case UART_IIR_RECEIVED_DATA_AVAILABLE: + case UART_IIR_CHARACTER_TIMEOUT: + while (uart->UART_USR & UART_USR_RECEIVE_FIFO_NOT_EMPTY) { + for (uint32_t rfl = uart->UART_RFL; rfl; --rfl) { + int c = uart->UART_RBR; + #if MICROPY_HW_ENABLE_UART_REPL && MICROPY_KBD_EXCEPTION + if (uart_id == MICROPY_HW_UART_REPL) { + if (c == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + continue; + } + } + #endif + if (state->rx_ringbuf != NULL) { + ringbuf_put(state->rx_ringbuf, c); + } } - #endif - ringbuf_put(&stdin_ringbuf, c); } - } - } else { - uart_irq_handler(UART_PTR, &transfer); + + if (iir == UART_IIR_CHARACTER_TIMEOUT) { + if (state->irq_callback != NULL) { + state->irq_callback(); + } + } + + break; + + case UART_IIR_TRANSMIT_HOLDING_REG_EMPTY: + while (uart->UART_USR & UART_USR_TRANSMIT_FIFO_NOT_FULL) { + if (state->tx_src < state->tx_src_max) { + uart->UART_THR = *state->tx_src++; + } else { + uart_disable_tx_irq(uart); + state->status = UART_TRANSFER_STATUS_SEND_COMPLETE; + break; + } + } + break; + + case UART_IIR_MODEM_STATUS: + (void)uart->UART_MSR; + break; } + __SEV(); } +#define DEFINE_IRQ_HANDLER(id) \ + void UART##id##_IRQHandler(void) { \ + mp_uart_irq_handler(id); \ + } + +DEFINE_IRQ_HANDLER(0) +DEFINE_IRQ_HANDLER(1) +DEFINE_IRQ_HANDLER(2) +DEFINE_IRQ_HANDLER(3) +DEFINE_IRQ_HANDLER(4) +DEFINE_IRQ_HANDLER(5) +DEFINE_IRQ_HANDLER(6) +DEFINE_IRQ_HANDLER(7) + +#if MICROPY_HW_ENABLE_UART_REPL + +#define REPL_BAUDRATE (115200) + +void mp_uart_init_repl(void) { + mp_uart_init(MICROPY_HW_UART_REPL, REPL_BAUDRATE, pin_REPL_UART_TX, pin_REPL_UART_RX, &stdin_ringbuf); +} + +void mp_uart_write_strn_repl(const char *str, size_t len) { + mp_uart_tx_data(MICROPY_HW_UART_REPL, (const uint8_t *)str, len); +} + #endif diff --git a/ports/alif/mpuart.h b/ports/alif/mpuart.h index 0c2e510a94e..1c28da46278 100644 --- a/ports/alif/mpuart.h +++ b/ports/alif/mpuart.h @@ -26,7 +26,20 @@ #ifndef MICROPY_INCLUDED_ALIF2_UART_H #define MICROPY_INCLUDED_ALIF2_UART_H -void mp_uart_init(void); -void mp_uart_write_strn(const char *str, size_t len); +#include "py/ringbuf.h" + +void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf); +void mp_uart_deinit(unsigned int uart_id); + +void mp_uart_set_irq_callback(unsigned int uart_id, void (*callback)(void)); +void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj_t cts); +void mp_uart_set_baudrate(unsigned int uart_id, uint32_t baudrate); + +size_t mp_uart_rx_any(unsigned int uart_id); +int mp_uart_rx_char(unsigned int uart_id); +void mp_uart_tx_data(unsigned int uart_id, const uint8_t *src, size_t len); + +void mp_uart_init_repl(void); +void mp_uart_write_strn_repl(const char *str, size_t len); #endif // MICROPY_INCLUDED_ALIF2_UART_H From 526c7eabcec97c5705ce43fe2e9588506c4a936a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Oct 2024 21:48:51 +1100 Subject: [PATCH 0513/2098] alif: Integrate lwIP and mbedTLS. Signed-off-by: Damien George --- ports/alif/alif.mk | 7 ++- ports/alif/boards/manifest.py | 1 + ports/alif/lwip_inc/arch/cc.h | 10 ++++ ports/alif/lwip_inc/arch/sys_arch.h | 1 + ports/alif/lwip_inc/lwipopts.h | 60 +++++++++++++++++++++ ports/alif/main.c | 17 ++++++ ports/alif/mbedtls/mbedtls_config_port.h | 41 +++++++++++++++ ports/alif/mbedtls/mbedtls_port.c | 65 +++++++++++++++++++++++ ports/alif/mpconfigport.h | 4 ++ ports/alif/mphalport.c | 30 +++++++++++ ports/alif/mphalport.h | 19 +++++++ ports/alif/mpnetworkport.c | 66 ++++++++++++++++++++++++ 12 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 ports/alif/lwip_inc/arch/cc.h create mode 100644 ports/alif/lwip_inc/arch/sys_arch.h create mode 100644 ports/alif/lwip_inc/lwipopts.h create mode 100644 ports/alif/mbedtls/mbedtls_config_port.h create mode 100644 ports/alif/mbedtls/mbedtls_port.c create mode 100644 ports/alif/mpnetworkport.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index f0ff8d91a53..e21fe74c46e 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -47,6 +47,7 @@ INC += -I$(ALIF_DFP_REL_HERE)/Device/core/$(MCU_CORE)/include/ INC += -I$(ALIF_DFP_REL_HERE)/Device/$(MCU_SERIES)/$(MCU_VARIANT)/ INC += -I$(TOP)/lib/tinyusb/src INC += -Itinyusb_port +INC += -Ilwip_inc GEN_PIN_MKPINS = mcu/make-pins.py GEN_PIN_PREFIX = mcu/pins_prefix.c @@ -122,6 +123,7 @@ SRC_C = \ main.c \ modalif.c \ mphalport.c \ + mpnetworkport.c \ mpu.c \ mpuart.c \ msc_disk.c \ @@ -135,6 +137,10 @@ SRC_C = \ vfs_rom_ioctl.c \ $(wildcard $(BOARD_DIR)/*.c) +ifeq ($(MICROPY_SSL_MBEDTLS),1) +SRC_C += mbedtls/mbedtls_port.c +endif + ifeq ($(MICROPY_FLOAT_IMPL),float) LIBM_SRC_C += $(SRC_LIB_LIBM_C) LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) @@ -148,7 +154,6 @@ endif SHARED_SRC_C += $(addprefix shared/,\ libc/string0.c \ netutils/dhcpserver.c \ - netutils/netutils.c \ netutils/trace.c \ readline/readline.c \ runtime/gchelper_native.c \ diff --git a/ports/alif/boards/manifest.py b/ports/alif/boards/manifest.py index 1dc9e179acc..834aa467027 100644 --- a/ports/alif/boards/manifest.py +++ b/ports/alif/boards/manifest.py @@ -3,3 +3,4 @@ require("dht") require("neopixel") require("onewire") +require("bundle-networking") diff --git a/ports/alif/lwip_inc/arch/cc.h b/ports/alif/lwip_inc/arch/cc.h new file mode 100644 index 00000000000..35d45afa71c --- /dev/null +++ b/ports/alif/lwip_inc/arch/cc.h @@ -0,0 +1,10 @@ +#ifndef MICROPY_INCLUDED_ALIF_LWIP_ARCH_CC_H +#define MICROPY_INCLUDED_ALIF_LWIP_ARCH_CC_H + +#include +#define LWIP_PLATFORM_DIAG(x) +#define LWIP_PLATFORM_ASSERT(x) { assert(1); } + +#define LWIP_NO_CTYPE_H 1 + +#endif // MICROPY_INCLUDED_ALIF_LWIP_ARCH_CC_H diff --git a/ports/alif/lwip_inc/arch/sys_arch.h b/ports/alif/lwip_inc/arch/sys_arch.h new file mode 100644 index 00000000000..8b1a393741c --- /dev/null +++ b/ports/alif/lwip_inc/arch/sys_arch.h @@ -0,0 +1 @@ +// empty diff --git a/ports/alif/lwip_inc/lwipopts.h b/ports/alif/lwip_inc/lwipopts.h new file mode 100644 index 00000000000..c0622225e10 --- /dev/null +++ b/ports/alif/lwip_inc/lwipopts.h @@ -0,0 +1,60 @@ +#ifndef MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H +#define MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H + +#include + +// This protection is not needed, instead we execute all lwIP code at PendSV priority +#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) +#define SYS_ARCH_PROTECT(lev) do { } while (0) +#define SYS_ARCH_UNPROTECT(lev) do { } while (0) + +#define NO_SYS 1 +#define SYS_LIGHTWEIGHT_PROT 1 +#define MEM_ALIGNMENT 4 + +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 + +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_RAW 1 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_STATS 0 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETIF_EXT_STATUS_CALLBACK 1 + +#define LWIP_LOOPIF_MULTICAST 1 +#define LWIP_LOOPBACK_MAX_PBUFS 8 + +#define LWIP_IPV6 0 +#define LWIP_DHCP 1 +#define LWIP_DHCP_CHECK_LINK_UP 1 +#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up +#define LWIP_DNS 1 +#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 +#define LWIP_MDNS_RESPONDER 1 +#define LWIP_IGMP 1 + +#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) + +#define SO_REUSE 1 +#define TCP_LISTEN_BACKLOG 1 + +extern uint64_t se_services_rand64(void); +#define LWIP_RAND() se_services_rand64() + +#define MEM_SIZE (16 * 1024) +#define TCP_MSS (1460) +#define TCP_OVERSIZE (TCP_MSS) +#define TCP_WND (8 * TCP_MSS) +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN (2 * (TCP_SND_BUF / TCP_MSS)) +#define TCP_QUEUE_OOSEQ (1) +#define MEMP_NUM_TCP_SEG (2 * TCP_SND_QUEUELEN) + +typedef uint32_t sys_prot_t; + +#endif // MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H diff --git a/ports/alif/main.c b/ports/alif/main.c index 77e413e1eb4..15a77348cf7 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -30,6 +30,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "py/stackctrl.h" +#include "extmod/modnetwork.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" #include "shared/runtime/pyexec.h" @@ -42,6 +43,11 @@ #include "se_services.h" #include "system_tick.h" +#if MICROPY_PY_LWIP +#include "lwip/init.h" +#include "lwip/apps/mdns.h" +#endif + extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -83,6 +89,17 @@ int main(void) { mp_stack_set_limit(&__StackTop - &__StackLimit - 1024); gc_init(&__GcHeapStart, &__GcHeapEnd); + #if MICROPY_PY_LWIP + // lwIP doesn't allow to reinitialise itself by subsequent calls to this function + // because the system timeout list (next_timeout) is only ever reset by BSS clearing. + // So for now we only init the lwIP stack once on power-up. + lwip_init(); + #if LWIP_MDNS_RESPONDER + mdns_resp_init(); + #endif + mod_network_lwip_init(); + #endif + for (;;) { // Initialise MicroPython runtime. mp_init(); diff --git a/ports/alif/mbedtls/mbedtls_config_port.h b/ports/alif/mbedtls/mbedtls_config_port.h new file mode 100644 index 00000000000..d9566304d28 --- /dev/null +++ b/ports/alif/mbedtls/mbedtls_config_port.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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_MBEDTLS_CONFIG_H +#define MICROPY_INCLUDED_MBEDTLS_CONFIG_H + +// Time hook. +#include +extern time_t alif_mbedtls_time(time_t *timer); +#define MBEDTLS_PLATFORM_TIME_MACRO alif_mbedtls_time +#define MBEDTLS_PLATFORM_MS_TIME_ALT undefined_and_unused + +// Set MicroPython-specific options. +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) + +// Include common mbedtls configuration. +#include "extmod/mbedtls/mbedtls_config_common.h" + +#endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ diff --git a/ports/alif/mbedtls/mbedtls_port.c b/ports/alif/mbedtls/mbedtls_port.c new file mode 100644 index 00000000000..a8c155e31ae --- /dev/null +++ b/ports/alif/mbedtls/mbedtls_port.c @@ -0,0 +1,65 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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/obj.h" +#include "se_services.h" +#include "mbedtls_config_port.h" + +#if defined(MBEDTLS_HAVE_TIME) +#include "shared/timeutils/timeutils.h" +#include "mbedtls/platform_time.h" +#endif + +int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) { + uint32_t val = 0; + int n = 0; + *olen = len; + while (len--) { + if (!n) { + val = se_services_rand64(); + n = 4; + } + *output++ = val; + val >>= 8; + --n; + } + return 0; +} + +#if defined(MBEDTLS_HAVE_TIME) + +time_t alif_mbedtls_time(time_t *timer) { + // TODO implement proper RTC time + unsigned int year = 2025; + unsigned int month = 1; + unsigned int date = 1; + unsigned int hours = 12; + unsigned int minutes = 0; + unsigned int seconds = 0; + return timeutils_seconds_since_epoch(year, month, date, hours, minutes, seconds); +} + +#endif diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index a5fd4f6dbfa..a437c1385cb 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -136,6 +136,10 @@ #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_NETWORK (CORE_M55_HP) +#ifndef MICROPY_PY_NETWORK_HOSTNAME_DEFAULT +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-alif" +#endif #define MICROPY_VFS (1) #define MICROPY_VFS_ROM (1) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 8f7780bb8ac..39528a4b9f2 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -38,6 +38,7 @@ #include "tusb.h" #include "mpuart.h" #include "pendsv.h" +#include "se_services.h" #include "system_tick.h" #ifndef MICROPY_HW_STDIN_BUFFER_LEN @@ -237,3 +238,32 @@ void soft_timer_schedule_at_ms(uint32_t ticks_ms) { } #endif + +/*******************************************************************************/ +// MAC address + +// Generate a random locally administered MAC address (LAA) +void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]) { + uint8_t id[8]; + se_services_get_unique_id(id); + buf[0] = 0x02; // LAA range + buf[1] = id[4]; + buf[2] = id[3]; + buf[3] = id[2]; + buf[4] = id[1]; + buf[5] = (id[0] << 2) | idx; +} + +// A board can override this if needed +MP_WEAK void mp_hal_get_mac(int idx, uint8_t buf[6]) { + mp_hal_generate_laa_mac(idx, buf); +} + +void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest) { + static const char hexchr[16] = "0123456789ABCDEF"; + uint8_t mac[6]; + mp_hal_get_mac(idx, mac); + for (; chr_len; ++chr_off, --chr_len) { + *dest++ = hexchr[mac[chr_off >> 1] >> (4 * (1 - (chr_off & 1))) & 0xf]; + } +} diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 5f1e3fff829..2ba62db2d2e 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -39,6 +39,11 @@ #define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); #define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); +// Prevent the "lwIP task" from running. +#define MICROPY_PY_LWIP_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_LWIP_REENTER MICROPY_PY_PENDSV_REENTER +#define MICROPY_PY_LWIP_EXIT MICROPY_PY_PENDSV_EXIT + // Port level Wait-for-Event macro // // Do not use this macro directly, include py/runtime.h and @@ -202,3 +207,17 @@ void mp_hal_pin_config(const machine_pin_obj_t *pin, uint32_t mode, // Include all the pin definitions. #include "genhdr/pins_board.h" + +/******************************************************************************/ +// Other HAL functions. + +enum { + MP_HAL_MAC_WLAN0 = 0, + MP_HAL_MAC_WLAN1, + MP_HAL_MAC_BDADDR, + MP_HAL_MAC_ETH0, +}; + +void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); +void mp_hal_get_mac(int idx, uint8_t buf[6]); +void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); diff --git a/ports/alif/mpnetworkport.c b/ports/alif/mpnetworkport.c new file mode 100644 index 00000000000..312fadf08a9 --- /dev/null +++ b/ports/alif/mpnetworkport.c @@ -0,0 +1,66 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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 "py/obj.h" + +#if MICROPY_PY_LWIP + +#include "shared/runtime/softtimer.h" +#include "lwip/timeouts.h" + +// Poll lwIP every 64ms by default +#define LWIP_TICK_RATE_MS 64 + +// Soft timer for running lwIP in the background. +static soft_timer_entry_t mp_network_soft_timer; + +u32_t sys_now(void) { + return mp_hal_ticks_ms(); +} + +// This is called by soft_timer and executes at PendSV level. +static void mp_network_soft_timer_callback(soft_timer_entry_t *self) { + // Run the lwIP internal updates. + sys_check_timeouts(); + + #if LWIP_NETIF_LOOPBACK + netif_poll_all(); + #endif +} + +void mod_network_lwip_init(void) { + soft_timer_static_init( + &mp_network_soft_timer, + SOFT_TIMER_MODE_PERIODIC, + LWIP_TICK_RATE_MS, + mp_network_soft_timer_callback + ); + + soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); +} + +#endif // MICROPY_PY_LWIP From d6e33423da471ddcba91632f032b18c5a79e9880 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 10 Mar 2025 14:09:04 +1100 Subject: [PATCH 0514/2098] alif: Integrate cyw43 WLAN driver. Signed-off-by: Damien George --- ports/alif/alif.mk | 3 +- ports/alif/cyw43_configport.h | 123 ++++++++++++++++++++++++++++ ports/alif/cyw43_port_spi.c | 150 ++++++++++++++++++++++++++++++++++ ports/alif/irq.h | 1 + ports/alif/main.c | 15 ++++ ports/alif/mpconfigport.h | 15 ++++ ports/alif/mpnetworkport.c | 14 ++++ ports/alif/pendsv.h | 3 + 8 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 ports/alif/cyw43_configport.h create mode 100644 ports/alif/cyw43_port_spi.c diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index e21fe74c46e..179df67a58c 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -115,6 +115,7 @@ SRC_O += \ SRC_C = \ alif_flash.c \ + cyw43_port_spi.c \ fatfs_port.c \ machine_pin.c \ machine_i2c.c \ @@ -196,12 +197,12 @@ ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/core/$(MCU_CORE)/source/startup_$(MCU_CORE).c \ drivers/source/adc.c \ drivers/source/i2c.c \ - drivers/source/spi.c \ drivers/source/mhu_driver.c \ drivers/source/mhu_receiver.c \ drivers/source/mhu_sender.c \ drivers/source/mram.c \ drivers/source/pinconf.c \ + drivers/source/spi.c \ drivers/source/uart.c \ drivers/source/utimer.c \ ospi_xip/source/ospi/ospi_drv.c \ diff --git a/ports/alif/cyw43_configport.h b/ports/alif/cyw43_configport.h new file mode 100644 index 00000000000..b7e4ad702a5 --- /dev/null +++ b/ports/alif/cyw43_configport.h @@ -0,0 +1,123 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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_ALIF_CYW43_CONFIGPORT_H +#define MICROPY_INCLUDED_ALIF_CYW43_CONFIGPORT_H + +// The board-level config will be included here, so it can set some CYW43 values. +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/modnetwork.h" +#include "pendsv.h" + +#ifndef static_assert +#define static_assert(expr, msg) typedef int static_assert_##__LINE__[(expr) ? 1 : -1] +#endif + +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_USE_STATS (0) +#define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) + +#if 0 +#define CYW43_VERBOSE_DEBUG (1) +#define CYW43_VDEBUG(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) +#define CYW43_DEBUG(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) +#define CYW43_INFO(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) +#define CYW43_WARN(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) +#endif + +#define CYW43_IOCTL_TIMEOUT_US (1000000) +#define CYW43_SLEEP_MAX (50) +#define CYW43_NETUTILS (1) +#define CYW43_CLEAR_SDIO_INT (1) + +#define CYW43_EPERM MP_EPERM // Operation not permitted +#define CYW43_EIO MP_EIO // I/O error +#define CYW43_EINVAL MP_EINVAL // Invalid argument +#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out + +#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER +#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT +#define CYW43_THREAD_LOCK_CHECK + +#define CYW43_HOST_NAME mod_network_hostname_data + +#define CYW43_SDPCM_SEND_COMMON_WAIT __WFE() +#define CYW43_DO_IOCTL_WAIT // __WFE() +#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() + +#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) + +#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT +#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT +#define CYW43_HAL_PIN_PULL_NONE 0 + +#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 + +#define cyw43_hal_ticks_us mp_hal_ticks_us +#define cyw43_hal_ticks_ms mp_hal_ticks_ms + +#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t +#define cyw43_hal_pin_read mp_hal_pin_read +#define cyw43_hal_pin_low mp_hal_pin_low +#define cyw43_hal_pin_high mp_hal_pin_high + +#define cyw43_hal_get_mac mp_hal_get_mac +#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii +#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac + +#define CYW43_PIN_WL_REG_ON pin_WL_REG_ON +#define CYW43_PIN_WL_IRQ pin_WL_IRQ + +#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) + +void cyw43_post_poll_hook(void); + +static inline void cyw43_delay_us(uint32_t us) { + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + } +} + +static inline void cyw43_delay_ms(uint32_t ms) { + mp_hal_delay_ms(ms); +} + +static inline void cyw43_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t alt) { + if (mode == MP_HAL_PIN_MODE_INPUT) { + mp_hal_pin_input(pin); + } else { + mp_hal_pin_output(pin); + } +} + +static inline void cyw43_hal_pin_config_irq_falling(mp_hal_pin_obj_t pin, bool enable) { + mp_hal_pin_config_irq_falling(pin, enable); +} + +#endif // MICROPY_INCLUDED_ALIF_CYW43_CONFIGPORT_H diff --git a/ports/alif/cyw43_port_spi.c b/ports/alif/cyw43_port_spi.c new file mode 100644 index 00000000000..84d84b0e1de --- /dev/null +++ b/ports/alif/cyw43_port_spi.c @@ -0,0 +1,150 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 OpenMV LLC. + * + * 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. + */ + +#if MICROPY_PY_NETWORK_CYW43 + +#include "lib/cyw43-driver/src/cyw43.h" +#include "lib/cyw43-driver/src/cyw43_internal.h" +#include "lib/cyw43-driver/src/cyw43_spi.h" + +#include "spi.h" +#include "sys_ctrl_spi.h" + +// CYW43 is connected to SPI3. +#define HW_SPI ((SPI_Type *)SPI3_BASE) +#define SPI_BAUDRATE (16000000) +#define SPI_RX_FIFO_SIZE (16) + +// WL_IRQ is on P9_6. +#define WL_IRQ_IRQN (GPIO9_IRQ6_IRQn) +#define WL_IRQ_HANDLER GPIO9_IRQ6Handler + +// Must run at IRQ priority above PendSV so it can wake cyw43-driver when PendSV is disabled. +void WL_IRQ_HANDLER(void) { + if (gpio_read_int_rawstatus(pin_WL_IRQ->gpio, pin_WL_IRQ->pin)) { + gpio_interrupt_eoi(pin_WL_IRQ->gpio, pin_WL_IRQ->pin); + pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); + __SEV(); + } +} + +static void spi_bus_init(void) { + // Configure pins. + mp_hal_pin_output(pin_WL_CS); + mp_hal_pin_high(pin_WL_CS); + // NOTE: Alif recommends enabled input read for all SPI pins. + mp_hal_pin_config(pin_WL_SCLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + mp_hal_pin_config(pin_WL_MOSI, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + mp_hal_pin_config(pin_WL_MISO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + + // Starts out clock_polarity=1, clock_phase=0. + spi_mode_master(HW_SPI); + spi_set_bus_speed(HW_SPI, SPI_BAUDRATE, GetSystemAHBClock()); + spi_set_mode(HW_SPI, SPI_MODE_2); + spi_set_protocol(HW_SPI, SPI_PROTO_SPI); + spi_set_dfs(HW_SPI, 8); + spi_set_tmod(HW_SPI, SPI_TMOD_TX_AND_RX); + spi_control_ss(HW_SPI, 0, SPI_SS_STATE_ENABLE); +} + +int cyw43_spi_init(cyw43_int_t *self) { + spi_bus_init(); + + // Configure IRQ for WL_IRQ (active low input). + NVIC_SetPriority(WL_IRQ_IRQN, IRQ_PRI_CYW43); + NVIC_ClearPendingIRQ(WL_IRQ_IRQN); + NVIC_EnableIRQ(WL_IRQ_IRQN); + + return 0; +} + +void cyw43_spi_deinit(cyw43_int_t *self) { + // Disable clock, SS and SPI. + spi_mask_interrupts(HW_SPI); + spi_control_ss(HW_SPI, 0, 0); + spi_disable(HW_SPI); + + // Disable SPI IRQ. + NVIC_DisableIRQ(WL_IRQ_IRQN); + NVIC_ClearPendingIRQ(WL_IRQ_IRQN); +} + +void cyw43_spi_gpio_setup(void) { +} + +void cyw43_spi_reset(void) { +} + +void cyw43_spi_set_polarity(cyw43_int_t *self, int pol) { + (void)self; + + if (pol == 0) { + spi_set_mode(HW_SPI, SPI_MODE_0); + } else { + spi_set_mode(HW_SPI, SPI_MODE_2); + } +} + +// tx must not be NULL. +// rx_len must be 0, or the same as tx_len. +int cyw43_spi_transfer(cyw43_int_t *self, const uint8_t *tx, size_t tx_len, uint8_t *rx, size_t rx_len) { + (void)self; + + if (tx_len == 0 && rx_len == 0) { + return 0; + } + + mp_hal_pin_low(pin_WL_CS); + + // Must read the same amount of data that is written out to SPI. + rx_len = tx_len; + + while (tx_len || rx_len) { + // Only add data to the TX FIFO if: + // - there's data to add + // - and TX is not ahead of RX by more than the RX FIFO size + // - and there's space in the TX FIFO + if (tx_len && tx_len + SPI_RX_FIFO_SIZE > rx_len && (HW_SPI->SPI_SR & SPI_SR_TFNF) != 0) { + HW_SPI->SPI_DR[0] = *tx++; + --tx_len; + } + + // Take data from the RX FIFO and store it into the output buffer (if given). + if (rx_len && (HW_SPI->SPI_SR & SPI_SR_RFNE) != 0) { + uint8_t data = HW_SPI->SPI_DR[0]; + if (rx != NULL) { + *rx++ = data; + } + --rx_len; + } + } + + mp_hal_pin_high(pin_WL_CS); + + return 0; +} + +#endif diff --git a/ports/alif/irq.h b/ports/alif/irq.h index ed454adcb46..08b8ef8086b 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -49,6 +49,7 @@ #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) #define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) #define IRQ_PRI_RTC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 100, 0) +#define IRQ_PRI_CYW43 NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 126, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) // these states correspond to values from query_irq, enable_irq and disable_irq diff --git a/ports/alif/main.c b/ports/alif/main.c index 15a77348cf7..3bff8cf4ae9 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -47,6 +47,9 @@ #include "lwip/init.h" #include "lwip/apps/mdns.h" #endif +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#endif extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -100,6 +103,18 @@ int main(void) { mod_network_lwip_init(); #endif + #if MICROPY_PY_NETWORK_CYW43 + { + cyw43_init(&cyw43_state); + uint8_t buf[8]; + memcpy(&buf[0], "ALIF", 4); + mp_hal_get_mac_ascii(MP_HAL_MAC_WLAN0, 8, 4, (char *)&buf[4]); + cyw43_wifi_ap_set_ssid(&cyw43_state, 8, buf); + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_WPA2_AES_PSK); + cyw43_wifi_ap_set_password(&cyw43_state, 8, (const uint8_t *)"alif0123"); + } + #endif + for (;;) { // Initialise MicroPython runtime. mp_init(); diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index a437c1385cb..76b603027fc 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -155,6 +155,21 @@ #define MICROPY_FATFS_MAX_SS (MICROPY_HW_FLASH_BLOCK_SIZE_BYTES) #endif +#if MICROPY_PY_NETWORK_CYW43 +extern const struct _mp_obj_type_t mp_network_cyw43_type; +#define MICROPY_HW_NIC_CYW43 { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, +#else +#define MICROPY_HW_NIC_CYW43 +#endif + +#ifndef MICROPY_BOARD_NETWORK_INTERFACES +#define MICROPY_BOARD_NETWORK_INTERFACES +#endif + +#define MICROPY_PORT_NETWORK_INTERFACES \ + MICROPY_HW_NIC_CYW43 \ + MICROPY_BOARD_NETWORK_INTERFACES \ + #define MP_STATE_PORT MP_STATE_VM // Miscellaneous settings diff --git a/ports/alif/mpnetworkport.c b/ports/alif/mpnetworkport.c index 312fadf08a9..40def7f7fd7 100644 --- a/ports/alif/mpnetworkport.c +++ b/ports/alif/mpnetworkport.c @@ -32,6 +32,10 @@ #include "shared/runtime/softtimer.h" #include "lwip/timeouts.h" +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#endif + // Poll lwIP every 64ms by default #define LWIP_TICK_RATE_MS 64 @@ -50,6 +54,16 @@ static void mp_network_soft_timer_callback(soft_timer_entry_t *self) { #if LWIP_NETIF_LOOPBACK netif_poll_all(); #endif + + #if MICROPY_PY_NETWORK_CYW43 + if (cyw43_poll) { + if (cyw43_sleep != 0) { + if (--cyw43_sleep == 0) { + cyw43_poll(); + } + } + } + #endif } void mod_network_lwip_init(void) { diff --git a/ports/alif/pendsv.h b/ports/alif/pendsv.h index 43a4b7a8538..17d7e82dfc7 100644 --- a/ports/alif/pendsv.h +++ b/ports/alif/pendsv.h @@ -34,6 +34,9 @@ enum { PENDSV_DISPATCH_SOFT_TIMER, + #if MICROPY_PY_NETWORK_CYW43 + PENDSV_DISPATCH_CYW43, + #endif MICROPY_BOARD_PENDSV_ENTRIES PENDSV_DISPATCH_MAX }; From 30dfbe5dc0d8a8ce4af3724b9b2a49b60a323a31 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 10 Mar 2025 13:57:48 +1100 Subject: [PATCH 0515/2098] alif: Integrate cyw43 Bluetooth with NimBLE. Signed-off-by: Damien George --- ports/alif/alif.mk | 4 + ports/alif/cyw43_configport.h | 18 +++++ ports/alif/main.c | 9 +++ ports/alif/mpbthciport.c | 140 ++++++++++++++++++++++++++++++++++ ports/alif/mpbthciport.h | 46 +++++++++++ ports/alif/mpconfigport.h | 6 ++ ports/alif/mpnimbleport.c | 78 +++++++++++++++++++ ports/alif/mpnimbleport.h | 32 ++++++++ 8 files changed, 333 insertions(+) create mode 100644 ports/alif/mpbthciport.c create mode 100644 ports/alif/mpbthciport.h create mode 100644 ports/alif/mpnimbleport.c create mode 100644 ports/alif/mpnimbleport.h diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 179df67a58c..eee27b3b7d6 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -142,6 +142,10 @@ ifeq ($(MICROPY_SSL_MBEDTLS),1) SRC_C += mbedtls/mbedtls_port.c endif +ifeq ($(MICROPY_PY_BLUETOOTH),1) +SRC_C += mpbthciport.c mpnimbleport.c +endif + ifeq ($(MICROPY_FLOAT_IMPL),float) LIBM_SRC_C += $(SRC_LIB_LIBM_C) LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) diff --git a/ports/alif/cyw43_configport.h b/ports/alif/cyw43_configport.h index b7e4ad702a5..4b5cce67132 100644 --- a/ports/alif/cyw43_configport.h +++ b/ports/alif/cyw43_configport.h @@ -32,6 +32,7 @@ #include "py/mphal.h" #include "py/runtime.h" #include "extmod/modnetwork.h" +#include "extmod/mpbthci.h" #include "pendsv.h" #ifndef static_assert @@ -39,6 +40,7 @@ #endif #define CYW43_USE_SPI (1) +#define CYW43_ENABLE_BLUETOOTH_OVER_UART (1) #define CYW43_LWIP (1) #define CYW43_USE_STATS (0) #define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) @@ -78,6 +80,7 @@ #define CYW43_HAL_PIN_PULL_NONE 0 #define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR #define cyw43_hal_ticks_us mp_hal_ticks_us #define cyw43_hal_ticks_ms mp_hal_ticks_ms @@ -87,6 +90,10 @@ #define cyw43_hal_pin_low mp_hal_pin_low #define cyw43_hal_pin_high mp_hal_pin_high +#define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate +#define cyw43_hal_uart_write mp_bluetooth_hci_uart_write +#define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar + #define cyw43_hal_get_mac mp_hal_get_mac #define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii #define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac @@ -94,6 +101,11 @@ #define CYW43_PIN_WL_REG_ON pin_WL_REG_ON #define CYW43_PIN_WL_IRQ pin_WL_IRQ +#define CYW43_PIN_BT_REG_ON pin_BT_REG_ON +#define CYW43_PIN_BT_HOST_WAKE pin_BT_HOST_WAKE +#define CYW43_PIN_BT_DEV_WAKE pin_BT_DEV_WAKE +#define CYW43_PIN_BT_CTS pin_BT_UART_CTS + #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) void cyw43_post_poll_hook(void); @@ -120,4 +132,10 @@ static inline void cyw43_hal_pin_config_irq_falling(mp_hal_pin_obj_t pin, bool e mp_hal_pin_config_irq_falling(pin, enable); } +#define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init +#define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit +#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe +#define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken +#define cyw43_bluetooth_controller_wakeup mp_bluetooth_hci_controller_wakeup + #endif // MICROPY_INCLUDED_ALIF_CYW43_CONFIGPORT_H diff --git a/ports/alif/main.c b/ports/alif/main.c index 3bff8cf4ae9..fd35604cbc2 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -30,6 +30,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "py/stackctrl.h" +#include "extmod/modbluetooth.h" #include "extmod/modnetwork.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -37,6 +38,7 @@ #include "shared/runtime/softtimer.h" #include "shared/tinyusb/mp_usbd.h" #include "tusb.h" +#include "mpbthciport.h" #include "mpuart.h" #include "ospi_flash.h" #include "pendsv.h" @@ -103,6 +105,10 @@ int main(void) { mod_network_lwip_init(); #endif + #if MICROPY_PY_BLUETOOTH + mp_bluetooth_hci_init(); + #endif + #if MICROPY_PY_NETWORK_CYW43 { cyw43_init(&cyw43_state); @@ -154,6 +160,9 @@ int main(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + #if MICROPY_PY_BLUETOOTH + mp_bluetooth_deinit(); + #endif soft_timer_deinit(); gc_sweep_all(); mp_deinit(); diff --git a/ports/alif/mpbthciport.c b/ports/alif/mpbthciport.c new file mode 100644 index 00000000000..a37c9621564 --- /dev/null +++ b/ports/alif/mpbthciport.c @@ -0,0 +1,140 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 OpenMV LLC. + * + * 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 "py/runtime.h" +#include "extmod/mpbthci.h" +#include "shared/runtime/softtimer.h" +#include "mpbthciport.h" + +#if MICROPY_PY_BLUETOOTH + +uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +// Soft timer and scheduling node for scheduling a HCI poll. +static soft_timer_entry_t mp_bluetooth_hci_soft_timer; +static mp_sched_node_t mp_bluetooth_hci_sched_node; + +// This is called by soft_timer and executes at IRQ_PRI_PENDSV. +static void mp_bluetooth_hci_soft_timer_callback(soft_timer_entry_t *self) { + mp_bluetooth_hci_poll_now(); +} + +void mp_bluetooth_hci_init(void) { + soft_timer_static_init( + &mp_bluetooth_hci_soft_timer, + SOFT_TIMER_MODE_ONE_SHOT, + 0, + mp_bluetooth_hci_soft_timer_callback + ); +} + +static void mp_bluetooth_hci_start_polling(void) { + mp_bluetooth_hci_poll_now(); +} + +void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { + soft_timer_reinsert(&mp_bluetooth_hci_soft_timer, ms); +} + +// For synchronous mode, we run all BLE stack code inside a scheduled task. +// This task is scheduled periodically via a timer, or immediately after UART RX IRQ. +static void run_events_scheduled_task(mp_sched_node_t *node) { + (void)node; + // This will process all buffered HCI UART data, and run any callouts or events. + mp_bluetooth_hci_poll(); +} + +// Called periodically (systick) or directly (e.g. UART RX IRQ) in order to +// request that processing happens ASAP in the scheduler. +void mp_bluetooth_hci_poll_now(void) { + mp_sched_schedule_node(&mp_bluetooth_hci_sched_node, run_events_scheduled_task); +} + +/******************************************************************************/ +// HCI over UART + +#include "mpuart.h" + +static uint32_t hci_uart_id; +static bool hci_uart_first_char; +static uint8_t hci_rx_ringbuf_array[768]; +static ringbuf_t hci_rx_ringbuf = { + .buf = hci_rx_ringbuf_array, + .size = sizeof(hci_rx_ringbuf_array), + .iget = 0, + .iput = 0, +}; + +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + hci_uart_id = port; + hci_uart_first_char = true; + + // Initialise the UART. + mp_uart_init(hci_uart_id, baudrate, pin_BT_UART_TX, pin_BT_UART_RX, &hci_rx_ringbuf); + mp_uart_set_flow(hci_uart_id, pin_BT_UART_RTS, pin_BT_UART_CTS); + mp_uart_set_irq_callback(hci_uart_id, mp_bluetooth_hci_poll_now); + + // Start the HCI polling to process any initial events/packets. + mp_bluetooth_hci_start_polling(); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + mp_uart_deinit(hci_uart_id); + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + mp_uart_set_baudrate(hci_uart_id, baudrate); + return 0; +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + mp_bluetooth_hci_controller_wakeup(); + mp_uart_tx_data(hci_uart_id, (void *)buf, len); + return 0; +} + +// This function expects the controller to be in the wake state via a previous call +// to mp_bluetooth_hci_controller_woken. +int mp_bluetooth_hci_uart_readchar(void) { + if (mp_uart_rx_any(hci_uart_id)) { + int c = mp_uart_rx_char(hci_uart_id); + if (hci_uart_first_char) { + if (c == 0) { + return -1; + } + hci_uart_first_char = false; + } + return c; + } else { + return -1; + } +} + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/alif/mpbthciport.h b/ports/alif/mpbthciport.h new file mode 100644 index 00000000000..65e9d1752a7 --- /dev/null +++ b/ports/alif/mpbthciport.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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. + */ +#ifndef MICROPY_INCLUDED_ALIF_MPBTHCIPORT_H +#define MICROPY_INCLUDED_ALIF_MPBTHCIPORT_H + +#include "py/mpconfig.h" + +// Initialise the HCI subsystem (should be called once, early on). +void mp_bluetooth_hci_init(void); + +// Call this to poll the HCI now. +void mp_bluetooth_hci_poll_now(void); + +// Call this to poll the HCI after a certain timeout. +void mp_bluetooth_hci_poll_in_ms(uint32_t ms); + +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). +// Request new data from the uart and pass to the stack, and run pending events/callouts. +// This is a low-level function and should not be called directly, use +// mp_bluetooth_hci_poll_now/mp_bluetooth_hci_poll_in_ms instead. +void mp_bluetooth_hci_poll(void); + +#endif // MICROPY_INCLUDED_ALIF_MPBTHCIPORT_H diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 76b603027fc..73abb8924c3 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -170,6 +170,12 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type; MICROPY_HW_NIC_CYW43 \ MICROPY_BOARD_NETWORK_INTERFACES \ +// Bluetooth code only runs in the scheduler, no locking/mutex required. +#define MICROPY_PY_BLUETOOTH_ENTER uint32_t atomic_state = 0; +#define MICROPY_PY_BLUETOOTH_EXIT (void)atomic_state; +#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) +#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (1) + #define MP_STATE_PORT MP_STATE_VM // Miscellaneous settings diff --git a/ports/alif/mpnimbleport.c b/ports/alif/mpnimbleport.c new file mode 100644 index 00000000000..8da5ce293c9 --- /dev/null +++ b/ports/alif/mpnimbleport.c @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2020 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/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) + +#include "host/ble_hs.h" +#include "nimble/nimble_npl.h" + +#include "extmod/mpbthci.h" +#include "extmod/modbluetooth.h" +#include "extmod/nimble/modbluetooth_nimble.h" +#include "extmod/nimble/hal/hal_uart.h" +#include "mpbthciport.h" + +// Get any pending data from the UART and send it to NimBLE's HCI buffers. +// Any further processing by NimBLE will be run via its event queue. +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); + + // Run any timers. + mp_bluetooth_nimble_os_callout_process(); + + // Process incoming UART data, and run events as they are generated. + mp_bluetooth_nimble_hci_uart_process(true); + + // Run any remaining events (e.g. if there was no UART data). + mp_bluetooth_nimble_os_eventq_run_all(); + } + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + // Call this function again in 128ms to check for new events. + // TODO: improve this by only calling back when needed. + mp_bluetooth_hci_poll_in_ms(128); + } +} + +// --- Port-specific helpers for the generic NimBLE bindings. ----------------- + +void mp_bluetooth_nimble_hci_uart_wfi(void) { + __WFE(); + + // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. + // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. + mp_bluetooth_nimble_hci_uart_process(false); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/alif/mpnimbleport.h b/ports/alif/mpnimbleport.h new file mode 100644 index 00000000000..150ec42da76 --- /dev/null +++ b/ports/alif/mpnimbleport.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * 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_ALIF_NIMBLE_PORT_H +#define MICROPY_INCLUDED_ALIF_NIMBLE_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#endif // MICROPY_INCLUDED_ALIF_NIMBLE_PORT_H From da46b4d7089e6b0be553798568bbdf9e7ea9368b Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 13 Mar 2025 14:54:49 +0100 Subject: [PATCH 0516/2098] alif/mcu: Remove json config files. They will be generated as part of the build. Signed-off-by: iabdalkader --- ports/alif/mcu/M55_DUAL_cfg.json | 26 -------------------------- ports/alif/mcu/M55_HE_cfg.json | 17 ----------------- ports/alif/mcu/M55_HP_cfg.json | 17 ----------------- 3 files changed, 60 deletions(-) delete mode 100644 ports/alif/mcu/M55_DUAL_cfg.json delete mode 100644 ports/alif/mcu/M55_HE_cfg.json delete mode 100644 ports/alif/mcu/M55_HP_cfg.json diff --git a/ports/alif/mcu/M55_DUAL_cfg.json b/ports/alif/mcu/M55_DUAL_cfg.json deleted file mode 100644 index 81beef5f036..00000000000 --- a/ports/alif/mcu/M55_DUAL_cfg.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "DEVICE": { - "disabled" : false, - "binary": "app-device-config.json", - "version" : "0.5.00", - "signed": false - }, - "HP_APP": { - "disabled" : false, - "binary": "M55_HP/firmware.bin", - "mramAddress": "0x80020000", - "version": "1.0.0", - "cpu_id": "M55_HP", - "flags": ["boot"], - "signed": false - }, - "HE_APP": { - "disabled" : false, - "binary": "M55_HE/firmware.bin", - "mramAddress": "0x80320000", - "version": "1.0.0", - "cpu_id": "M55_HE", - "flags": ["deferred"], - "signed": false - } -} diff --git a/ports/alif/mcu/M55_HE_cfg.json b/ports/alif/mcu/M55_HE_cfg.json deleted file mode 100644 index 83898418043..00000000000 --- a/ports/alif/mcu/M55_HE_cfg.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "DEVICE": { - "disabled" : false, - "binary": "app-device-config.json", - "version" : "0.5.00", - "signed": false - }, - "HE_APP": { - "disabled" : false, - "binary": "M55_HE/firmware.bin", - "mramAddress": "0x80320000", - "version": "1.0.0", - "cpu_id": "M55_HE", - "flags": ["boot"], - "signed": false - } -} diff --git a/ports/alif/mcu/M55_HP_cfg.json b/ports/alif/mcu/M55_HP_cfg.json deleted file mode 100644 index d6710f69c77..00000000000 --- a/ports/alif/mcu/M55_HP_cfg.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "DEVICE": { - "disabled" : false, - "binary": "app-device-config.json", - "version" : "0.5.00", - "signed": false - }, - "HP_APP": { - "disabled" : false, - "binary": "M55_HP/firmware.bin", - "mramAddress": "0x80020000", - "version": "1.0.0", - "cpu_id": "M55_HP", - "flags": ["boot"], - "signed": false - } -} From 19a4689c6b0b652f8d50be6b658adc649649d5d1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 13 Mar 2025 14:55:19 +0100 Subject: [PATCH 0517/2098] alif/mcu: Pre-process Alif ToC config file. Signed-off-by: iabdalkader --- ports/alif/Makefile | 24 +++++++++++------ ports/alif/mcu/alif_cfg.json.in | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 ports/alif/mcu/alif_cfg.json.in diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 7d6731dacf2..68706674365 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -20,10 +20,14 @@ JLINK_CMD_SUFFIX = \ Reset\n\ Exit +ALIF_TOC_CONFIG = alif_cfg.json +ALIF_TOC_APPS = $(BUILD)/$(ALIF_TOC_CONFIG) +ALIF_TOC_CFLAGS += -DTOC_CFG_FILE=$(ALIF_TOOLKIT_CFG_FILE) + ifeq ($(MCU_CORE),M55_HP) -ALIF_TOC_CONFIG = mcu/M55_HP_cfg.json -ALIF_TOC_APPS = $(BUILD)/M55_HP/firmware.bin +ALIF_TOC_CFLAGS += -DTOC_CORE_M55_HP_APP=1 +ALIF_TOC_APPS += $(BUILD)/M55_HP/firmware.bin JLINK_CMD = '\ $(JLINK_CMD_PREFIX)\ LoadFile "$(BUILD)/M55_HP/firmware.bin",0x80020000\n\ @@ -31,8 +35,8 @@ $(JLINK_CMD_SUFFIX)' else ifeq ($(MCU_CORE),M55_HE) -ALIF_TOC_CONFIG = mcu/M55_HE_cfg.json -ALIF_TOC_APPS = $(BUILD)/M55_HE/firmware.bin +ALIF_TOC_CFLAGS += -DTOC_CORE_M55_HE_APP=1 +ALIF_TOC_APPS += $(BUILD)/M55_HE/firmware.bin JLINK_CMD = '\ $(JLINK_CMD_PREFIX)\ LoadFile "$(BUILD)/M55_HE/firmware.bin",0x80320000\n\ @@ -40,8 +44,9 @@ $(JLINK_CMD_SUFFIX)' else ifeq ($(MCU_CORE),M55_DUAL) -ALIF_TOC_CONFIG = mcu/M55_DUAL_cfg.json -ALIF_TOC_APPS = $(BUILD)/M55_HP/firmware.bin $(BUILD)/M55_HE/firmware.bin +ALIF_TOC_CFLAGS += -DTOC_CORE_M55_HP_APP=1 +ALIF_TOC_CFLAGS += -DTOC_CORE_M55_HE_APP=1 +ALIF_TOC_APPS += $(BUILD)/M55_HP/firmware.bin $(BUILD)/M55_HE/firmware.bin JLINK_CMD = '\ $(JLINK_CMD_PREFIX)\ LoadFile "$(BUILD)/M55_HP/firmware.bin",0x80020000\n\ @@ -81,10 +86,13 @@ $(BUILD)/M55_HP/firmware.bin: $(BUILD)/M55_HE/firmware.bin: make -f alif.mk MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 +$(BUILD)/$(ALIF_TOC_CONFIG): mcu/$(ALIF_TOC_CONFIG).in | $(BUILD) + $(ECHO) "Preprocess toc config $@" + $(Q)$(CPP) -P -E $(ALIF_TOC_CFLAGS) - < mcu/$(ALIF_TOC_CONFIG).in > $@ + $(BUILD)/firmware.toc.bin: $(ALIF_TOC_APPS) $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ - --filename $(abspath $(ALIF_TOC_CONFIG)) \ - --config-dir $(BOARD_DIR) \ + --filename $(abspath $(BUILD)/$(ALIF_TOC_CONFIG)) \ --output-dir $(BUILD) \ --firmware-dir $(BUILD) \ --output $@ diff --git a/ports/alif/mcu/alif_cfg.json.in b/ports/alif/mcu/alif_cfg.json.in new file mode 100644 index 00000000000..7760bd91d2e --- /dev/null +++ b/ports/alif/mcu/alif_cfg.json.in @@ -0,0 +1,47 @@ +{ + "DEVICE": { + "disabled" : false, + "binary": TOC_CFG_FILE, + "version" : "0.5.00", + "signed": false + }, + "HP_BOOT": { + #if TOC_CORE_M55_HP_BOOT + "disabled" : false, + #else + "disabled" : true, + #endif + "binary": "bootloader.bin", + "mramAddress": "0x80000000", + "version": "1.0.0", + "cpu_id": "M55_HP", + "flags": ["boot"], + "signed": false + }, + "HP_APP": { + #if TOC_CORE_M55_HP_APP + "disabled" : false, + #else + "disabled" : true, + #endif + "binary": "M55_HP/firmware.bin", + "mramAddress": "0x80020000", + "version": "1.0.0", + "cpu_id": "M55_HP", + "flags": ["boot"], + "signed": false + }, + "HE_APP": { + #if TOC_CORE_M55_HE_APP + "disabled" : false, + #else + "disabled" : true, + #endif + "binary": "M55_HE/firmware.bin", + "mramAddress": "0x80320000", + "version": "1.0.0", + "cpu_id": "M55_HE", + "flags": ["deferred"], + "signed": false + } +} From 293e8db9d739c4e32420a48304ac9d2251e63570 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 17 Mar 2025 13:19:07 +1100 Subject: [PATCH 0518/2098] alif/mpuart: Enhance UART to support bits/parity/stop and more IRQs. Signed-off-by: Damien George --- ports/alif/mpbthciport.c | 10 ++++- ports/alif/mpuart.c | 85 ++++++++++++++++++++++++++++++++++------ ports/alif/mpuart.h | 14 ++++++- 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/ports/alif/mpbthciport.c b/ports/alif/mpbthciport.c index a37c9621564..10a0505fe02 100644 --- a/ports/alif/mpbthciport.c +++ b/ports/alif/mpbthciport.c @@ -89,14 +89,20 @@ static ringbuf_t hci_rx_ringbuf = { .iput = 0, }; +static void mp_bluetooth_hci_uart_irq_callback(unsigned int uart_id, unsigned int trigger) { + if (trigger == MP_UART_IRQ_RXIDLE) { + mp_bluetooth_hci_poll_now(); + } +} + int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { hci_uart_id = port; hci_uart_first_char = true; // Initialise the UART. - mp_uart_init(hci_uart_id, baudrate, pin_BT_UART_TX, pin_BT_UART_RX, &hci_rx_ringbuf); + mp_uart_init(hci_uart_id, baudrate, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1, pin_BT_UART_TX, pin_BT_UART_RX, &hci_rx_ringbuf); mp_uart_set_flow(hci_uart_id, pin_BT_UART_RTS, pin_BT_UART_CTS); - mp_uart_set_irq_callback(hci_uart_id, mp_bluetooth_hci_poll_now); + mp_uart_set_irq_callback(hci_uart_id, MP_UART_IRQ_RXIDLE, mp_bluetooth_hci_uart_irq_callback); // Start the HCI polling to process any initial events/packets. mp_bluetooth_hci_start_polling(); diff --git a/ports/alif/mpuart.c b/ports/alif/mpuart.c index 7f14ff54447..4fe82ce1231 100644 --- a/ports/alif/mpuart.c +++ b/ports/alif/mpuart.c @@ -26,20 +26,28 @@ #include "py/mphal.h" #include "py/runtime.h" +#include "shared/runtime/softtimer.h" #include "mpuart.h" - #include "sys_ctrl_uart.h" -#include "uart.h" -#define UART_MAX (8) +#define mp_container_of(ptr, structure, member) (void *)((uintptr_t)(ptr) - offsetof(structure, member)) + +#define UART_LSR_TEMT_Pos (6) #define SYST_PCLK (100000000) +#define CALCULATE_BITS_PER_CHAR(data_bits, parity, stop_bits) \ + (1 + ((data_bits) + 5) + ((parity) != UART_PARITY_NONE) + ((stop_bits) + 1)) + typedef struct _uart_state_t { UART_TRANSFER_STATUS status; + uint32_t baudrate; + uint32_t bits_per_char; ringbuf_t *rx_ringbuf; const uint8_t *tx_src; const uint8_t *tx_src_max; - void (*irq_callback)(void); + soft_timer_entry_t rx_idle_timer; + unsigned int irq_trigger; + void (*irq_callback)(unsigned int uart_id, unsigned int trigger); } uart_state_t; static const uint8_t uart_irqn[UART_MAX] = { @@ -66,7 +74,19 @@ static UART_Type *const uart_periph[UART_MAX] = { static uart_state_t uart_state[UART_MAX]; -void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf) { +// This is called by soft_timer and executes at IRQ_PRI_PENDSV. +static void rx_idle_timer_callback(soft_timer_entry_t *self) { + uart_state_t *state = mp_container_of(self, uart_state_t, rx_idle_timer); + if (state->irq_callback != NULL && state->irq_trigger & MP_UART_IRQ_RXIDLE) { + unsigned int uart_id = state - &uart_state[0]; + state->irq_callback(uart_id, MP_UART_IRQ_RXIDLE); + } +} + +void mp_uart_init(unsigned int uart_id, uint32_t baudrate, + UART_DATA_BITS data_bits, UART_PARITY parity, UART_STOP_BITS stop_bits, + mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf) { + UART_Type *uart = uart_periph[uart_id]; uart_state_t *state = &uart_state[uart_id]; @@ -82,17 +102,20 @@ void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, uart_disable_tx_irq(uart); uart_disable_rx_irq(uart); uart_set_baudrate(uart, SYST_PCLK, baudrate); - uart_set_data_parity_stop_bits(uart, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1); + uart_set_data_parity_stop_bits(uart, data_bits, parity, stop_bits); uart_set_flow_control(uart, UART_FLOW_CONTROL_NONE); uart->UART_FCR |= UART_FCR_RCVR_FIFO_RESET; uart_set_tx_trigger(uart, UART_TX_FIFO_EMPTY); - uart_set_rx_trigger(uart, UART_RX_ONE_CHAR_IN_FIFO); + uart_set_rx_trigger(uart, UART_RX_FIFO_QUARTER_FULL); // Initialise the state. state->status = UART_TRANSFER_STATUS_NONE; + state->baudrate = baudrate; + state->bits_per_char = CALCULATE_BITS_PER_CHAR(data_bits, parity, stop_bits); state->rx_ringbuf = rx_ringbuf; state->tx_src = NULL; state->tx_src_max = NULL; + state->irq_trigger = 0; state->irq_callback = NULL; // Enable interrupts. @@ -100,6 +123,8 @@ void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, NVIC_SetPriority(uart_irqn[uart_id], IRQ_PRI_UART_REPL); NVIC_EnableIRQ(uart_irqn[uart_id]); uart_enable_rx_irq(uart); + + soft_timer_static_init(&state->rx_idle_timer, SOFT_TIMER_MODE_ONE_SHOT, 0, rx_idle_timer_callback); } void mp_uart_deinit(unsigned int uart_id) { @@ -109,9 +134,16 @@ void mp_uart_deinit(unsigned int uart_id) { NVIC_DisableIRQ(uart_irqn[uart_id]); } -void mp_uart_set_irq_callback(unsigned int uart_id, void (*callback)(void)) { +void mp_uart_set_irq_callback(unsigned int uart_id, unsigned int trigger, void (*callback)(unsigned int uart_id, unsigned int trigger)) { + UART_Type *uart = uart_periph[uart_id]; uart_state_t *state = &uart_state[uart_id]; + state->irq_trigger = trigger; state->irq_callback = callback; + if (trigger & MP_UART_IRQ_RX) { + uart_set_rx_trigger(uart, UART_RX_ONE_CHAR_IN_FIFO); + } else { + uart_set_rx_trigger(uart, UART_RX_FIFO_QUARTER_FULL); + } } void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj_t cts) { @@ -131,10 +163,20 @@ void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj void mp_uart_set_baudrate(unsigned int uart_id, uint32_t baudrate) { UART_Type *uart = uart_periph[uart_id]; + uart_state_t *state = &uart_state[uart_id]; + state->baudrate = baudrate; uart_set_baudrate(uart, SYST_PCLK, baudrate); } +void mp_uart_set_bits_parity_stop(unsigned int uart_id, UART_DATA_BITS data_bits, UART_PARITY parity, UART_STOP_BITS stop_bits) { + UART_Type *uart = uart_periph[uart_id]; + uart_state_t *state = &uart_state[uart_id]; + + state->bits_per_char = CALCULATE_BITS_PER_CHAR(data_bits, parity, stop_bits); + uart_set_data_parity_stop_bits(uart, data_bits, parity, stop_bits); +} + size_t mp_uart_rx_any(unsigned int uart_id) { uart_state_t *state = &uart_state[uart_id]; if (state->rx_ringbuf != NULL) { @@ -143,6 +185,11 @@ size_t mp_uart_rx_any(unsigned int uart_id) { return 0; } +size_t mp_uart_tx_any(unsigned int uart_id) { + UART_Type *uart = uart_periph[uart_id]; + return uart->UART_TFL + !((uart->UART_LSR >> UART_LSR_TEMT_Pos) & 1); +} + int mp_uart_rx_char(unsigned int uart_id) { uart_state_t *state = &uart_state[uart_id]; if (state->rx_ringbuf != NULL && ringbuf_avail(state->rx_ringbuf)) { @@ -208,9 +255,11 @@ static void mp_uart_irq_handler(unsigned int uart_id) { } case UART_IIR_RECEIVED_DATA_AVAILABLE: - case UART_IIR_CHARACTER_TIMEOUT: + case UART_IIR_CHARACTER_TIMEOUT: { + bool had_char = false; while (uart->UART_USR & UART_USR_RECEIVE_FIFO_NOT_EMPTY) { for (uint32_t rfl = uart->UART_RFL; rfl; --rfl) { + had_char = true; int c = uart->UART_RBR; #if MICROPY_HW_ENABLE_UART_REPL && MICROPY_KBD_EXCEPTION if (uart_id == MICROPY_HW_UART_REPL) { @@ -226,13 +275,18 @@ static void mp_uart_irq_handler(unsigned int uart_id) { } } - if (iir == UART_IIR_CHARACTER_TIMEOUT) { - if (state->irq_callback != NULL) { - state->irq_callback(); + if (had_char) { + if (state->irq_callback != NULL && state->irq_trigger & MP_UART_IRQ_RX) { + state->irq_callback(uart_id, MP_UART_IRQ_RX); } } + if (state->irq_trigger & MP_UART_IRQ_RXIDLE) { + // Wait for 2 characters worth of time before triggering the RXIDLE event. + soft_timer_reinsert(&state->rx_idle_timer, 2000 * state->bits_per_char / state->baudrate + 1); + } break; + } case UART_IIR_TRANSMIT_HOLDING_REG_EMPTY: while (uart->UART_USR & UART_USR_TRANSMIT_FIFO_NOT_FULL) { @@ -241,6 +295,9 @@ static void mp_uart_irq_handler(unsigned int uart_id) { } else { uart_disable_tx_irq(uart); state->status = UART_TRANSFER_STATUS_SEND_COMPLETE; + if (state->irq_callback != NULL && state->irq_trigger & MP_UART_IRQ_TXIDLE) { + state->irq_callback(uart_id, MP_UART_IRQ_TXIDLE); + } break; } } @@ -273,7 +330,9 @@ DEFINE_IRQ_HANDLER(7) #define REPL_BAUDRATE (115200) void mp_uart_init_repl(void) { - mp_uart_init(MICROPY_HW_UART_REPL, REPL_BAUDRATE, pin_REPL_UART_TX, pin_REPL_UART_RX, &stdin_ringbuf); + mp_uart_init(MICROPY_HW_UART_REPL, + REPL_BAUDRATE, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1, + pin_REPL_UART_TX, pin_REPL_UART_RX, &stdin_ringbuf); } void mp_uart_write_strn_repl(const char *str, size_t len) { diff --git a/ports/alif/mpuart.h b/ports/alif/mpuart.h index 1c28da46278..76dadaab380 100644 --- a/ports/alif/mpuart.h +++ b/ports/alif/mpuart.h @@ -27,15 +27,25 @@ #define MICROPY_INCLUDED_ALIF2_UART_H #include "py/ringbuf.h" +#include "uart.h" -void mp_uart_init(unsigned int uart_id, uint32_t baudrate, mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf); +#define UART_MAX (8) +#define MP_UART_IRQ_RX (1) +#define MP_UART_IRQ_RXIDLE (2) +#define MP_UART_IRQ_TXIDLE (4) + +void mp_uart_init(unsigned int uart_id, uint32_t baudrate, + UART_DATA_BITS data_bits, UART_PARITY parity, UART_STOP_BITS stop_bits, + mp_hal_pin_obj_t tx, mp_hal_pin_obj_t rx, ringbuf_t *rx_ringbuf); void mp_uart_deinit(unsigned int uart_id); -void mp_uart_set_irq_callback(unsigned int uart_id, void (*callback)(void)); +void mp_uart_set_irq_callback(unsigned int uart_id, unsigned int trigger, void (*callback)(unsigned int uart_id, unsigned int trigger)); void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj_t cts); void mp_uart_set_baudrate(unsigned int uart_id, uint32_t baudrate); +void mp_uart_set_bits_parity_stop(unsigned int uart_id, UART_DATA_BITS data_bits, UART_PARITY parity, UART_STOP_BITS stop_bits); size_t mp_uart_rx_any(unsigned int uart_id); +size_t mp_uart_tx_any(unsigned int uart_id); int mp_uart_rx_char(unsigned int uart_id); void mp_uart_tx_data(unsigned int uart_id, const uint8_t *src, size_t len); From 29a873ec0778107bcc9b25b0ab8a32a7f9e5bba9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Mar 2025 23:38:56 +1100 Subject: [PATCH 0519/2098] alif/machine_uart: Add machine.UART peripheral support. Signed-off-by: Damien George --- ports/alif/machine_uart.c | 560 ++++++++++++++++++++++++++++++++++++++ ports/alif/mpconfigport.h | 3 + 2 files changed, 563 insertions(+) create mode 100644 ports/alif/machine_uart.c diff --git a/ports/alif/machine_uart.c b/ports/alif/machine_uart.c new file mode 100644 index 00000000000..746c86677f9 --- /dev/null +++ b/ports/alif/machine_uart.c @@ -0,0 +1,560 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 OpenMV LLC. + * + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_uart.c via MICROPY_PY_MACHINE_UART_INCLUDEFILE. + +#include "py/mphal.h" +#include "py/mperrno.h" +#include "mpuart.h" + +#define DEFAULT_UART_BAUDRATE (115200) +#define DEFAULT_UART_BITS (8) +#define DEFAULT_UART_PARITY (MP_ROM_NONE) +#define DEFAULT_UART_STOP (1) +#define DEFAULT_UART_RX_BUFFER_SIZE (256) + +typedef struct _machine_uart_obj_t { + mp_obj_base_t base; + unsigned int uart_id; + uint32_t baudrate; + UART_DATA_BITS bits; + UART_PARITY parity; + UART_STOP_BITS stop; + mp_hal_pin_obj_t tx; + mp_hal_pin_obj_t rx; + mp_hal_pin_obj_t rts; + mp_hal_pin_obj_t cts; + uint32_t flow; + uint16_t timeout; // timeout waiting for first char (in ms) + uint16_t timeout_char; // timeout waiting between chars (in ms) + uint8_t *rx_ringbuf_array; + ringbuf_t rx_ringbuf; + uint32_t irq_flags; + uint32_t irq_trigger; + mp_irq_obj_t irq_obj; +} machine_uart_obj_t; + +typedef struct _machine_uart_default_pins_t { + mp_hal_pin_obj_t tx; + mp_hal_pin_obj_t rx; + mp_hal_pin_obj_t rts; + mp_hal_pin_obj_t cts; +} machine_uart_default_pins_t; + +static const machine_uart_default_pins_t machine_uart_default_pins[UART_MAX] = { + [0] = { + #if defined(MICROPY_HW_UART0_TX) && defined(MICROPY_HW_UART0_RX) + MICROPY_HW_UART0_TX, MICROPY_HW_UART0_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART0_RTS) && defined(MICROPY_HW_UART0_CTS) + MICROPY_HW_UART0_RTS, MICROPY_HW_UART0_CTS, + #else + NULL, NULL, + #endif + }, + [1] = { + #if defined(MICROPY_HW_UART1_TX) && defined(MICROPY_HW_UART1_RX) + MICROPY_HW_UART1_TX, MICROPY_HW_UART1_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART1_RTS) && defined(MICROPY_HW_UART1_CTS) + MICROPY_HW_UART1_RTS, MICROPY_HW_UART1_CTS, + #else + NULL, NULL, + #endif + }, + [2] = { + #if defined(MICROPY_HW_UART2_TX) && defined(MICROPY_HW_UART2_RX) + MICROPY_HW_UART2_TX, MICROPY_HW_UART2_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART2_RTS) && defined(MICROPY_HW_UART2_CTS) + MICROPY_HW_UART2_RTS, MICROPY_HW_UART2_CTS, + #else + NULL, NULL, + #endif + }, + [3] = { + #if defined(MICROPY_HW_UART3_TX) && defined(MICROPY_HW_UART3_RX) + MICROPY_HW_UART3_TX, MICROPY_HW_UART3_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART3_RTS) && defined(MICROPY_HW_UART3_CTS) + MICROPY_HW_UART3_RTS, MICROPY_HW_UART3_CTS, + #else + NULL, NULL, + #endif + }, + [4] = { + #if defined(MICROPY_HW_UART4_TX) && defined(MICROPY_HW_UART4_RX) + MICROPY_HW_UART4_TX, MICROPY_HW_UART4_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART4_RTS) && defined(MICROPY_HW_UART4_CTS) + MICROPY_HW_UART4_RTS, MICROPY_HW_UART4_CTS, + #else + NULL, NULL, + #endif + }, + [5] = { + #if defined(MICROPY_HW_UART5_TX) && defined(MICROPY_HW_UART5_RX) + MICROPY_HW_UART5_TX, MICROPY_HW_UART5_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART5_RTS) && defined(MICROPY_HW_UART5_CTS) + MICROPY_HW_UART5_RTS, MICROPY_HW_UART5_CTS, + #else + NULL, NULL, + #endif + }, + [6] = { + #if defined(MICROPY_HW_UART6_TX) && defined(MICROPY_HW_UART6_RX) + MICROPY_HW_UART6_TX, MICROPY_HW_UART6_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART6_RTS) && defined(MICROPY_HW_UART6_CTS) + MICROPY_HW_UART6_RTS, MICROPY_HW_UART6_CTS, + #else + NULL, NULL, + #endif + }, + [7] = { + #if defined(MICROPY_HW_UART7_TX) && defined(MICROPY_HW_UART7_RX) + MICROPY_HW_UART7_TX, MICROPY_HW_UART7_RX, + #else + NULL, NULL, + #endif + #if defined(MICROPY_HW_UART7_RTS) && defined(MICROPY_HW_UART7_CTS) + MICROPY_HW_UART7_RTS, MICROPY_HW_UART7_CTS, + #else + NULL, NULL, + #endif + }, +}; + +static void machine_uart_irq_callback(unsigned int uart_id, unsigned int trigger); + +MP_REGISTER_ROOT_POINTER(struct _machine_uart_obj_t *machine_uart_obj_all[UART_MAX]); + +/******************************************************************************/ +// MicroPython bindings for UART + +#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_uart___del___obj) }, \ + { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_FLOW_CONTROL_RTS) }, \ + { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_FLOW_CONTROL_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(MP_UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(MP_UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(MP_UART_IRQ_TXIDLE) }, \ + +#define GET_PIN_WITH_DEFAULT(uart_id_, pin_name, pin_selection) \ + (pin_selection == mp_const_none ? machine_uart_default_pins[uart_id_].pin_name : mp_hal_get_pin_obj(pin_selection)) + +static void machine_uart_set_bits(machine_uart_obj_t *self, mp_int_t bits) { + if (!(5 <= bits && bits <= 8)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + } + self->bits = UART_DATA_BITS_5 + (bits - 5); +} + +static void machine_uart_set_parity(machine_uart_obj_t *self, mp_obj_t parity) { + if (parity == mp_const_none) { + self->parity = UART_PARITY_NONE; + } else if (mp_obj_get_int(parity) & 1) { + self->parity = UART_PARITY_ODD; + } else { + self->parity = UART_PARITY_EVEN; + } +} + +static void machine_uart_set_stop(machine_uart_obj_t *self, mp_int_t stop) { + if (!(1 <= stop && stop <= 2)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid stop")); + } + self->stop = UART_STOP_BITS_1 + (stop - 1); +} + +static void machine_uart_set_timeout_char(machine_uart_obj_t *self, mp_int_t timeout_char) { + // Make sure timeout_char is at least as long as a whole character (13 bits to be safe). + uint32_t min_timeout_char = 13000 / self->baudrate + 1; + self->timeout_char = MAX(min_timeout_char, timeout_char); +} + +static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + static const char *parity_name[] = {[UART_PARITY_NONE] = "None", [UART_PARITY_EVEN] = "0", [UART_PARITY_ODD] = "1"}; + + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + unsigned int bits = 5 + (self->bits - UART_DATA_BITS_5); + const char *parity = parity_name[self->parity]; + unsigned int stop = 1 + (self->stop - UART_STOP_BITS_1); + + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " + "tx=%q, rx=%q, ", + self->uart_id, self->baudrate, bits, parity, stop, + mp_hal_pin_name(self->tx), mp_hal_pin_name(self->rx)); + if (self->rts != NULL) { + mp_printf(print, "rts=%q, ", mp_hal_pin_name(self->rts)); + } + if (self->cts != NULL) { + mp_printf(print, "cts=%q, ", mp_hal_pin_name(self->cts)); + } + mp_printf(print, "flow=", self->rts); + if (self->flow == 0) { + mp_printf(print, "0"); + } else { + if (self->flow & UART_FLOW_CONTROL_RTS) { + mp_printf(print, "RTS"); + if (self->flow & UART_FLOW_CONTROL_CTS) { + mp_printf(print, "|"); + } + } + if (self->flow & UART_FLOW_CONTROL_CTS) { + mp_printf(print, "CTS"); + } + } + mp_printf(print, ", timeout=%u, timeout_char=%u, rxbuf=%u)", + self->timeout, self->timeout_char, self->rx_ringbuf.size - 1); +} + +static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { + ARG_id, + ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, + ARG_tx, ARG_rx, ARG_rts, ARG_cts, + ARG_flow, ARG_timeout, ARG_timeout_char, ARG_rxbuf, + }; + static const mp_arg_t allowed_args[] = { + #if !defined(MICROPY_HW_DEFAULT_UART_ID) + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + #else + { MP_QSTR_id, MP_ARG_INT, {.u_int = MICROPY_HW_DEFAULT_UART_ID} }, + #endif + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = DEFAULT_UART_BAUDRATE} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = DEFAULT_UART_BITS} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_rom_obj = DEFAULT_UART_PARITY} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = DEFAULT_UART_STOP} }, + { MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { 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} }, + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_UART_RX_BUFFER_SIZE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get UART bus. + int uart_id = args[ARG_id].u_int; + if (uart_id < 0 || uart_id >= UART_MAX) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), uart_id); + } + + machine_uart_obj_t *self; + bool need_init; + if (MP_STATE_PORT(machine_uart_obj_all)[uart_id] == NULL) { + // Create new UART object. + self = mp_obj_malloc_with_finaliser(machine_uart_obj_t, &machine_uart_type); + self->uart_id = uart_id; + need_init = true; + } else { + // Reference existing UART object. + self = MP_STATE_PORT(machine_uart_obj_all)[uart_id]; + need_init = n_args > 1 || n_kw > 0; + } + + if (need_init) { + // Set the UART parameters. + self->baudrate = args[ARG_baudrate].u_int; + machine_uart_set_bits(self, args[ARG_bits].u_int); + machine_uart_set_parity(self, args[ARG_parity].u_obj); + machine_uart_set_stop(self, args[ARG_stop].u_int); + self->tx = GET_PIN_WITH_DEFAULT(self->uart_id, tx, args[ARG_tx].u_obj); + self->rx = GET_PIN_WITH_DEFAULT(self->uart_id, rx, args[ARG_rx].u_obj); + self->rts = GET_PIN_WITH_DEFAULT(self->uart_id, rts, args[ARG_rts].u_obj); + self->cts = GET_PIN_WITH_DEFAULT(self->uart_id, cts, args[ARG_cts].u_obj); + self->flow = args[ARG_flow].u_int; + self->timeout = args[ARG_timeout].u_int; + uint32_t min_timeout_char = 13000 / self->baudrate + 1; // 13 bits to be safe + self->timeout = MAX(min_timeout_char, args[ARG_timeout_char].u_int); + + // Check TX/RX pins are given. + if (self->tx == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("tx not given")); + } + if (self->rx == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("rx not given")); + } + + // Configure flow control. + mp_hal_pin_obj_t rts = NULL; + mp_hal_pin_obj_t cts = NULL; + if (self->flow & UART_FLOW_CONTROL_RTS) { + if (self->rts == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("rts not given")); + } + rts = self->rts; + } + if (self->flow & UART_FLOW_CONTROL_CTS) { + if (self->cts == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("cts not given")); + } + cts = self->cts; + } + + // Allocate the RX buffer. + size_t rxbuf_len = args[ARG_rxbuf].u_int; + self->rx_ringbuf_array = m_new(uint8_t, rxbuf_len + 1); + self->rx_ringbuf.buf = self->rx_ringbuf_array; + self->rx_ringbuf.size = rxbuf_len + 1; + self->rx_ringbuf.iput = 0; + self->rx_ringbuf.iget = 0; + + // Reset the IRQ state + self->irq_flags = 0; + self->irq_trigger = 0; + self->irq_obj.base.type = NULL; + + // Initialize the UART hardware. + mp_uart_init(self->uart_id, self->baudrate, self->bits, self->parity, self->stop, self->tx, self->rx, &self->rx_ringbuf); + + mp_uart_set_flow(self->uart_id, rts, cts); + } + + MP_STATE_PORT(machine_uart_obj_all)[uart_id] = self; + + return MP_OBJ_FROM_PTR(self); +} + +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) { + enum { + ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, + ARG_timeout, ARG_timeout_char, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = -1} }, + { 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} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + bool change_baudrate = false; + bool change_bits_parity_stop = false; + + // Change baudrate if requested. + if (args[ARG_baudrate].u_int > 0) { + self->baudrate = args[ARG_baudrate].u_int; + change_baudrate = true; + } + + // Change data bits if requested. + if (args[ARG_bits].u_int > 0) { + machine_uart_set_bits(self, args[ARG_bits].u_int); + change_bits_parity_stop = true; + } + + // Change parity if requested. + if (args[ARG_parity].u_obj != MP_OBJ_NEW_SMALL_INT(-1)) { + machine_uart_set_parity(self, args[ARG_parity].u_obj); + change_bits_parity_stop = true; + } + + // Change stop bits if requested. + if (args[ARG_stop].u_int > 0) { + machine_uart_set_stop(self, args[ARG_stop].u_int); + change_bits_parity_stop = true; + } + + // Change timeout if requested. + if (args[ARG_timeout].u_int >= 0) { + self->timeout = args[ARG_timeout].u_int; + } + + // Change timeout_char if requested. + if (args[ARG_timeout_char].u_int >= 0) { + machine_uart_set_timeout_char(self, args[ARG_timeout_char].u_int); + } + + // Reconfigure the hardware parameters that have changed. + if (change_baudrate) { + mp_uart_set_baudrate(self->uart_id, self->baudrate); + } + if (change_bits_parity_stop) { + mp_uart_set_bits_parity_stop(self->uart_id, self->bits, self->parity, self->stop); + } +} + +static mp_obj_t machine_uart___del__(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uart_deinit(self->uart_id); + MP_STATE_PORT(machine_uart_obj_all)[self->uart_id] = NULL; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_uart___del___obj, machine_uart___del__); + +// Turn off the UART bus. +static void mp_machine_uart_deinit(machine_uart_obj_t *self) { + mp_uart_deinit(self->uart_id); +} + +// Return number of characters waiting. +static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { + return mp_uart_rx_any(self->uart_id); +} + +// Since uart.write() waits up to the last byte, uart.txdone() always returns True. +static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { + return !!mp_uart_tx_any(self->uart_id); +} + +// Change the trigger for the UART IRQ. +static mp_uint_t machine_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->irq_trigger = new_trigger; + mp_uart_set_irq_callback(self->uart_id, self->irq_trigger, machine_uart_irq_callback); + return 0; +} + +// Get UART IRQ state. +static mp_uint_t machine_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->irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t machine_uart_irq_methods = { + .trigger = machine_uart_irq_trigger, + .info = machine_uart_irq_info, +}; + +// Retrieve and configure the UART IRQ object. +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->irq_obj.base.type == NULL) { + mp_irq_init(&self->irq_obj, &machine_uart_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + self->irq_obj.handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + self->irq_obj.ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + machine_uart_irq_trigger(MP_OBJ_FROM_PTR(self), args[MP_IRQ_ARG_INIT_trigger].u_int); + } + + return &self->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(); + mp_uint_t timeout = self->timeout; + uint8_t *dest = buf_in; + + for (size_t i = 0; i < size; i++) { + // Wait for the first/next character + while (!mp_uart_rx_any(self->uart_id)) { + mp_uint_t elapsed = mp_hal_ticks_ms() - start; + if (elapsed > timeout) { // timed out + if (i <= 0) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } else { + return i; + } + } + mp_event_handle_nowait(); + } + *dest++ = mp_uart_rx_char(self->uart_id); + start = mp_hal_ticks_ms(); // Inter-character timeout + timeout = self->timeout_char; + } + return size; +} + +static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uart_tx_data(self->uart_id, buf_in, size); + return size; +} + +static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + machine_uart_obj_t *self = self_in; + mp_uint_t ret; + if (request == MP_STREAM_POLL) { + uintptr_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && mp_uart_rx_any(self->uart_id)) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR)) { + ret |= MP_STREAM_POLL_WR; + } + } else if (request == MP_STREAM_FLUSH) { + uint32_t t0 = mp_hal_ticks_ms(); + const uint32_t timeout_ms = 100 + 10 * mp_uart_tx_any(self->uart_id); + while (mp_uart_tx_any(self->uart_id)) { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { + *errcode = MP_ETIMEDOUT; + ret = MP_STREAM_ERROR; + } + mp_event_handle_nowait(); + } + return 0; + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +static void machine_uart_irq_callback(unsigned int uart_id, unsigned int trigger) { + machine_uart_obj_t *self = MP_STATE_PORT(machine_uart_obj_all)[uart_id]; + self->irq_flags = trigger; + if (self->irq_obj.base.type != NULL && (self->irq_flags & self->irq_trigger)) { + mp_irq_handler(&self->irq_obj); + } +} diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 73abb8924c3..6c9e6af635f 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -136,6 +136,9 @@ #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/alif/machine_uart.c" +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_NETWORK (CORE_M55_HP) #ifndef MICROPY_PY_NETWORK_HOSTNAME_DEFAULT #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-alif" From 737acef5cbd8ffb5a7c0ef605741debd70e72606 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 20 Mar 2025 14:28:57 +1100 Subject: [PATCH 0520/2098] alif: Support more fine-grained pin alternate function selection. Now raises an exception if the pin doesn't support the alternate function unit number and line type, eg UART0_TX (previously it only checked the peripheral). Signed-off-by: Damien George --- ports/alif/cyw43_port_spi.c | 10 +- ports/alif/machine_i2c.c | 12 +- ports/alif/machine_spi.c | 22 ++- ports/alif/mcu/ensemble_pin_alt.csv | 258 ++++++++++++++-------------- ports/alif/mcu/make-pins.py | 14 +- ports/alif/mphalport.h | 201 +++++++++++++++++++--- ports/alif/mpuart.c | 12 +- ports/alif/ospi_flash.c | 27 +-- 8 files changed, 371 insertions(+), 185 deletions(-) diff --git a/ports/alif/cyw43_port_spi.c b/ports/alif/cyw43_port_spi.c index 84d84b0e1de..6ebcaa057dc 100644 --- a/ports/alif/cyw43_port_spi.c +++ b/ports/alif/cyw43_port_spi.c @@ -34,6 +34,7 @@ #include "sys_ctrl_spi.h" // CYW43 is connected to SPI3. +#define HW_SPI_UNIT (3) #define HW_SPI ((SPI_Type *)SPI3_BASE) #define SPI_BAUDRATE (16000000) #define SPI_RX_FIFO_SIZE (16) @@ -56,9 +57,12 @@ static void spi_bus_init(void) { mp_hal_pin_output(pin_WL_CS); mp_hal_pin_high(pin_WL_CS); // NOTE: Alif recommends enabled input read for all SPI pins. - mp_hal_pin_config(pin_WL_SCLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); - mp_hal_pin_config(pin_WL_MOSI, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); - mp_hal_pin_config(pin_WL_MISO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + mp_hal_pin_config(pin_WL_SCLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(SPI_SCLK, HW_SPI_UNIT), true); + mp_hal_pin_config(pin_WL_MOSI, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(SPI_MOSI, HW_SPI_UNIT), true); + mp_hal_pin_config(pin_WL_MISO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(SPI_MISO, HW_SPI_UNIT), true); // Starts out clock_polarity=1, clock_phase=0. spi_mode_master(HW_SPI); diff --git a/ports/alif/machine_i2c.c b/ports/alif/machine_i2c.c index f117b93c2b1..a710aeeb011 100644 --- a/ports/alif/machine_i2c.c +++ b/ports/alif/machine_i2c.c @@ -135,9 +135,9 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n // Configure I2C pins. mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SCL, self->i2c_id), true); mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SDA, self->i2c_id), true); // Initialize I2C controller. self->i2c->I2C_CON = I2C_IC_CON_ENABLE_MASTER_MODE | @@ -244,18 +244,18 @@ int machine_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_mac // Switch pins to GPIO/OD. mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_NONE, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, 0, true); mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_NONE, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, 0, true); // Perform the transfer. ret = mp_machine_soft_i2c_transfer(&soft_i2c.base, addr, 1, &bufs, flags); // Re-configure I2C pins. mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SCL, self->i2c_id), true); mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_I2C, true); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SDA, self->i2c_id), true); return ret; } diff --git a/ports/alif/machine_spi.c b/ports/alif/machine_spi.c index d13c6368f25..abb60bc4e83 100644 --- a/ports/alif/machine_spi.c +++ b/ports/alif/machine_spi.c @@ -60,6 +60,20 @@ static machine_spi_obj_t machine_spi_obj[] = { }; +static const uint8_t spi_pin_alt[] = { + MP_HAL_PIN_ALT_SPI_SCLK, + MP_HAL_PIN_ALT_SPI_MISO, + MP_HAL_PIN_ALT_SPI_MOSI, + MP_HAL_PIN_ALT_SPI_SS0, +}; + +static const uint8_t lpspi_pin_alt[] = { + MP_HAL_PIN_ALT_LPSPI_SCLK, + MP_HAL_PIN_ALT_LPSPI_MISO, + MP_HAL_PIN_ALT_LPSPI_MOSI, + MP_HAL_PIN_ALT_LPSPI_SS, +}; + static inline uint32_t spi_get_clk(machine_spi_obj_t *spi) { return spi->is_lp ? GetSystemCoreClock() : GetSystemAHBClock(); } @@ -131,9 +145,15 @@ static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, } // Configure SPI pins. + const uint8_t *alt; + if (spi->id <= 3) { + alt = spi_pin_alt; + } else { + alt = lpspi_pin_alt; + } for (size_t i = 0; i < MP_ARRAY_SIZE(pins) && pins[i]; i++) { mp_hal_pin_config(pins[i], MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_SPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_MAKE(alt[i], spi->id), true); } // Disable all interrupts. diff --git a/ports/alif/mcu/ensemble_pin_alt.csv b/ports/alif/mcu/ensemble_pin_alt.csv index 10bfedc9a1d..34f13e86f6d 100644 --- a/ports/alif/mcu/ensemble_pin_alt.csv +++ b/ports/alif/mcu/ensemble_pin_alt.csv @@ -1,130 +1,130 @@ Pin,AF0,AF1,AF2,AF3,AF4,AF5,AF6,AF7 -P0_0,,OSPI,UART,I3C,UT,LPCAM,CAM,ANA -P0_1,,OSPI,UART,I3C,UT,LPCAM,CAM,ANA -P0_2,,OSPI,UART,I2C,UT,LPCAM,CAM,ANA -P0_3,,OSPI,UART,I2C,UT,LPCAM,CAM,ANA -P0_4,,OSPI,UART,PDM,I2C,UT,,ANA -P0_5,,OSPI,UART,PDM,I2C,UT,,ANA -P0_6,,OSPI,UART,PDM,I2C,UT,,ANA -P0_7,,OSPI,UART,PDM,I2C,UT,CDC,ANA -P1_0,,UART,SPI,I2C,UT,LPCAM,ETH,ANA -P1_1,,UART,SPI,I2C,UT,LPCAM,ETH,ANA -P1_2,,UART,SPI,I3C,UT,LPCAM,ETH,ANA -P1_3,,UART,SPI,I3C,UT,LPCAM,ETH,ANA -P1_4,,OSPI,UART,SPI,UT,LPCAM,ETH,ANA -P1_5,,OSPI,UART,SPI,UT,LPCAM,ETH,ANA -P1_6,,OSPI,UART,I2S,UT,LPCAM,ETH,ANA -P1_7,,OSPI,UART,I2S,UT,LPCAM,ETH,ANA -P2_0,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA -P2_1,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA -P2_2,,OSPI,UART,LPPDM,UT,LPCAM,ETH,ANA -P2_3,,OSPI,UART,LPPDM,UT,LPCAM,CDC,ANA -P2_4,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA -P2_5,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA -P2_6,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA -P2_7,,OSPI,LPI2S,SPI,UT,LPCAM,CAM,ANA -P3_0,,OSPI,UART,PDM,I2S,QEC,LPCAM,CAM -P3_1,,OSPI,UART,PDM,I2S,QEC,LPCAM,CAM -P3_2,,OSPI,PDM,I2S,I3C,QEC,LPCAM,CAM -P3_3,,OSPI,PDM,I2S,I3C,QEC,LPCAM,CAM -P3_4,,OSPI,UART,LPPDM,I2S,I2C,QEC,CAM -P3_5,,OSPI,UART,LPPDM,SPI,I2C,QEC,CAM -P3_6,,HFXO,LPUART,LPPDM,SPI,I2C,QEC,CAM -P3_7,,JTAG,LPUART,LPPDM,SPI,I2C,QEC,CAM -P4_0,,JTAG,,I2S,SPI,QEC,CDC,CAM -P4_1,,JTAG,I2S,SPI,QEC,SD,CDC,CAM -P4_2,,JTAG,,I2S,SPI,QEC,SD,CAM -P4_3,,JTAG,,I2S,SPI,QEC,SD,CAM -P4_4,,JTAG,I2S,SPI,FAULT,,, -P4_5,,JTAG,SPI,FAULT,,,, -P4_6,,JTAG,SPI,FAULT,,,, -P4_7,,JTAG,SPI,FAULT,,,, -P5_0,,OSPI,UART,PDM,SPI,I2C,UT,SD -P5_1,,OSPI,UART,PDM,SPI,I2C,UT,SD -P5_2,,OSPI,UART,PDM,SPI,LPI2C,UT,SD -P5_3,,OSPI,UART,SPI,LPI2C,UT,SD,CDC -P5_4,,OSPI,UART,PDM,SPI,UT,SD,CDC -P5_5,,OSPI,UART,PDM,UT,SD,ETH,CDC -# P5_6 doesn't really have OSPI on AF1 but it's needed for P10_7 to be in OSPI mode -P5_6,,OSPI,UART,I2C,UT,SD,ETH,CDC -P5_7,,OSPI,UART,I2C,UT,SD,ETH, -P6_0,,OSPI,UART,PDM,UT,SD,ETH, -P6_1,,OSPI,UART,PDM,UT,SD,ETH, -P6_2,,OSPI,UART,,PDM,UT,SD,ETH -P6_3,,OSPI,UART,,PDM,UT,SD,ETH -P6_4,,OSPI,UART,,SPI,UT,SD,ETH -P6_5,,OSPI,UART,,SPI,UT,SD,ETH -P6_6,,OSPI,UART,,SPI,UT,SD,ETH -P6_7,,OSPI,UART,PDM,SPI,UT,SD,ETH -P7_0,,,CMP,SPI,I2C,UT,SD, -P7_1,,,CMP,SPI,I2C,UT,SD, -P7_2,,,UART,CMP,SPI,I2C,UT,SD -P7_3,,,UART,CMP,SPI,I2C,UT, -P7_4,,,LPUART,LPPDM,LPSPI,LPI2C,UT, -P7_5,,,LPUART,,LPPDM,LPSPI,LPI2C,UT -P7_6,,,LPUART,,LPPDM,LPSPI,I3C,UT -P7_7,,,LPUART,,LPPDM,LPSPI,I3C,UT -P8_0,,OSPI,AUDIO,FAULT,LPCAM,SD,CDC,CAM -P8_1,,I2S,FAULT,LPCAM,SD,CDC,CAM, -P8_2,,I2S,SPI,FAULT,LPCAM,SD,CDC,CAM -P8_3,,I2S,SPI,FAULT,LPCAM,SD,CDC,CAM -P8_4,,I2S,SPI,QEC,LPCAM,SD,CDC,CAM -P8_5,,,SPI,QEC,LPCAM,SD,CDC,CAM -P8_6,,,I2S,QEC,LPCAM,SD,CDC,CAM -P8_7,,,I2S,QEC,LPCAM,SD,CDC,CAM -P9_0,,,I2S,QEC,SD,CDC,CAM, -P9_1,,LPUART,I2S,QEC,SD,CDC,CAM, -P9_2,,LPUART,I2S,SPI,QEC,SD,CDC,CAM -P9_3,,HFXO,UART,I2S,SPI,QEC,CDC,CAM -P9_4,,UART,I2S,SPI,I2C,QEC,CDC,CAM -P9_5,,OSPI,I2S,SPI,I2C,QEC,CDC,CAM -P9_6,,OSPI,AUDIO,SPI,I2C,QEC,CDC,CAM -P9_7,,OSPI,UART,SPI,I2C,QEC,CDC,CAM -P10_0,,OSPI,UART,SPI,UT,LPCAM,CDC,CAM -P10_1,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM -P10_2,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM -P10_3,,OSPI,,LPI2S,UT,LPCAM,CDC,CAM -P10_4,,OSPI,,LPI2S,I2C,UT,ETH,CDC -P10_5,,UART,I2S,SPI,I2C,UT,ETH,CDC -P10_6,,UART,I2S,SPI,I2C,UT,ETH,CDC -P10_7,,UART,I2S,SPI,I2C,UT,CDC,OSPI -P11_0,,OSPI,UART,I2S,SPI,UT,ETH,CDC -P11_1,,OSPI,UART,SPI,UT,ETH,CDC, -P11_2,,OSPI,UART,LPPDM,SPI,UT,ETH,CDC -P11_3,,OSPI,UART,LPPDM,SPI,UT,ETH,CDC -P11_4,,OSPI,UART,PDM,LPSPI,UT,ETH,CDC -P11_5,,OSPI,UART,PDM,LPSPI,UT,ETH,CDC -P11_6,,OSPI,UART,LPPDM,LPSPI,UT,ETH,CDC -P11_7,,OSPI,UART,LPPDM,LPSPI,UT,ETH,CDC -P12_0,,OSPI,AUDIO,I2S,UT,CDC,, -P12_1,,OSPI,UART,I2S,UT,CDC,, -P12_2,,OSPI,UART,I2S,UT,CDC,, -P12_3,,OSPI,UART,I2S,UT,CDC,, -P12_4,,OSPI,SPI,UT,,CDC,, -P12_5,,,SPI,UT,,CDC,, -P12_6,,,SPI,UT,,CDC,, -P12_7,,OSPI,,SPI,UT,CDC,, -P13_0,,OSPI,,SPI,QEC,SD,CDC, -P13_1,,OSPI,SPI,QEC,SD,CDC,, -P13_2,,OSPI,SPI,QEC,SD,CDC,, -P13_3,,OSPI,SPI,QEC,SD,CDC,, -P13_4,,OSPI,LPI2S,QEC,SD,CDC,, -P13_5,,OSPI,LPI2S,QEC,SD,CDC,, -P13_6,,OSPI,LPI2S,QEC,SD,CDC,, -P13_7,,OSPI,LPI2S,QEC,SD,CDC,, -P14_0,,OSPI,UART,QEC,SD,,, -P14_1,,OSPI,UART,,QEC,SD,, -P14_2,,OSPI,UART,,QEC,SD,, -P14_3,,OSPI,UART,,QEC,,, -P14_4,,CMP,SPI,FAULT,,,, -P14_5,,CMP,SPI,FAULT,,,, -P14_6,,CMP,SPI,FAULT,,,, -P14_7,,CMP,SPI,FAULT,,,, -P15_0,,LPTMR,,,,,, -P15_1,,LPTMR,,,,,, -P15_2,,LPTMR,,,,,, -P15_3,,LPTMR,,,,,, -P15_4,,,,,,,, -P15_5,,,,,,,, -P15_6,,,,,,,, -P15_7,,,,,,,, +P0_0,GPIO,OSPI0_D0,UART0_RX,I3C_SDA,UT0_T0,LPCAM_HSYNC,CAM_HSYNC,ANA_S0 +P0_1,GPIO,OSPI0_D1,UART0_TX,I3C_SCL,UT0_T1,LPCAM_VSYNC,CAM_VSYNC,ANA_S1 +P0_2,GPIO,OSPI0_D2,UART0_CTS,I2C0_SDA,UT1_T0,LPCAM_PCLK,CAM_PCLK,ANA_S2 +P0_3,GPIO,OSPI0_D3,UART0_RTS,I2C0_SCL,UT1_T1,LPCAM_XVCLK,CAM_XVCLK,ANA_S3 +P0_4,GPIO,OSPI0_D4,UART1_RX,PDM_D0,I2C1_SDA,UT2_T0,,ANA_S4 +P0_5,GPIO,OSPI0_D5,UART1_TX,PDM_C0,I2C1_SCL,UT2_T1,,ANA_S5 +P0_6,GPIO,OSPI0_D6,UART1_CTS,PDM_D1,I2C2_SCL,UT3_T0,,ANA_S6 +P0_7,GPIO,OSPI0_D7,UART1_RTS,PDM_C1,I2C2_SDA,UT3_T1,CDC_DE,ANA_S7 +P1_0,GPIO,UART2_RX,SPI0_MISO,I2C3_SDA,UT4_T0,LPCAM_HSYNC,ETH_RXD0,ANA_S8 +P1_1,GPIO,UART2_TX,SPI0_MOSI,I2C3_SCL,UT4_T1,LPCAM_VSYNC,ETH_RXD1,ANA_S9 +P1_2,GPIO,UART3_RX,SPI0_SCLK,I3C_SDA,UT5_T0,LPCAM_PCLK,ETH_RST,ANA_S10 +P1_3,GPIO,UART3_TX,SPI0_SS0,I3C_SCL,UT5_T1,LPCAM_XVCLK,ETH_TXD0,ANA_S11 +P1_4,GPIO,OSPI0_SS0,UART0_RX,SPI0_SS1,UT6_T0,LPCAM_D0,ETH_TXD1,ANA_S12 +P1_5,GPIO,OSPI0_SS1,UART0_TX,SPI0_SS2,UT6_T1,LPCAM_D1,ETH_TXEN,ANA_S13 +P1_6,GPIO,OSPI0_RXDS,UART1_RX,I2S0_SDI,UT7_T0,LPCAM_D2,ETH_IRQ,ANA_S14 +P1_7,GPIO,OSPI0_SCLK,UART1_TX,I2S0_SDO,UT7_T1,LPCAM_D3,ETH_REFCLK,ANA_S15 +P2_0,GPIO,OSPI0_D0,UART2_RX,LPPDM_D0,UT8_T0,LPCAM_D4,ETH_MDIO,ANA_S16 +P2_1,GPIO,OSPI0_D1,UART2_TX,LPPDM_C0,UT8_T1,LPCAM_D5,ETH_MDC,ANA_S17 +P2_2,GPIO,OSPI0_D2,UART3_RX,LPPDM_D1,UT9_T0,LPCAM_D6,ETH_CRS_DV_C,ANA_S18 +P2_3,GPIO,OSPI0_D3,UART3_TX,LPPDM_C1,UT9_T1,LPCAM_D7,CDC_PCLK,ANA_S19 +P2_4,GPIO,OSPI0_D4,LPI2S_SDI,SPI1_MISO,UT10_T0,LPCAM_D0,CAM_D0,ANA_S20 +P2_5,GPIO,OSPI0_D5,LPI2S_SDO,SPI1_MOSI,UT10_T1,LPCAM_D1,CAM_D1,ANA_S21 +P2_6,GPIO,OSPI0_D6,LPI2S_SCLK,SPI1_SCLK,UT11_T0,LPCAM_D2,CAM_D2,ANA_S22 +P2_7,GPIO,OSPI0_D7,LPI2S_WS,SPI1_SS0,UT11_T1,LPCAM_D3,CAM_D3,ANA_S23 +P3_0,GPIO,OSPI0_SCLK,UART4_RX,PDM_D0,I2S0_SCLK,QEC0_X,LPCAM_D4,CAM_D4 +P3_1,GPIO,OSPI0_SCLKN,UART4_TX,PDM_C0,I2S0_WS,QEC0_Y,LPCAM_D5,CAM_D5 +P3_2,GPIO,OSPI0_SS0,PDM_D1,I2S1_SDI,I3C_SDA,QEC0_Z,LPCAM_D6,CAM_D6 +P3_3,GPIO,OSPI0_SS1,PDM_C1,I2S1_SDO,I3C_SCL,QEC1_X,LPCAM_D7,CAM_D7 +P3_4,GPIO,OSPI0_RXDS,UART5_RX,LPPDM_C0,I2S1_SCLK,I2C0_SCL,QEC1_Y,CAM_D8 +P3_5,GPIO,OSPI0_SCLKN,UART5_TX,LPPDM_D0,SPI0_SS1,I2C0_SDA,QEC1_Z,CAM_D9 +P3_6,GPIO,HFXO_OUT,LPUART_CTS,LPPDM_C1,SPI0_SS2,I2C1_SDA,QEC2_X,CAM_D10 +P3_7,GPIO,JTAG_TRACECLK,LPUART_RTS,LPPDM_D1,SPI1_SS1,I2C1_SCL,QEC2_Y,CAM_D11 +P4_0,GPIO,JTAG_TDATA0,,I2S1_WS,SPI1_SS2,QEC2_Z,CDC_VSYNC,CAM_D12 +P4_1,GPIO,JTAG_TDATA1,I2S0_SDI,SPI1_SS3,QEC3_X,SD_CLK,CDC_HSYNC,CAM_D13 +P4_2,GPIO,JTAG_TDATA2,,I2S0_SDO,SPI2_MISO,QEC3_Y,SD_CMD,CAM_D14 +P4_3,GPIO,JTAG_TDATA3,,I2S0_SCLK,SPI2_MOSI,QEC3_Z,SD_RST,CAM_D15 +P4_4,GPIO,JTAG_TCK,I2S0_WS,SPI2_SCLK,FAULT0_A,,, +P4_5,GPIO,JTAG_TMS,SPI2_SS0,FAULT1_A,,,, +P4_6,GPIO,JTAG_TDI,SPI2_SS1,FAULT2_A,,,, +P4_7,GPIO,JTAG_TDO,SPI2_SS2,FAULT3_A,,,, +P5_0,GPIO,OSPI1_RXDS,UART4_RX,PDM_D2,SPI0_MISO,I2C2_SDA,UT0_T0,SD_D0 +P5_1,GPIO,OSPI1_SS0,UART4_TX,PDM_D3,SPI0_MOSI,I2C2_SCL,UT0_T1,SD_D1 +P5_2,GPIO,OSPI1_SCLKN,UART5_RX,PDM_C3,SPI0_SS0,LPI2C_SCL,UT1_T0,SD_D2 +P5_3,GPIO,OSPI1_SCLK,UART5_TX,SPI0_SCLK,LPI2C_SDA,UT1_T1,SD_D3,CDC_PCLK +P5_4,GPIO,OSPI1_SS1,UART3_CTS,PDM_D2,SPI0_SS3,UT2_T0,SD_D4,CDC_DE +P5_5,GPIO,OSPI1_SCLK,UART3_RTS,PDM_D3,UT2_T1,SD_D5,ETH_RXD0,CDC_HSYNC +# P5_6 doesn't really have OSPI on AF1 but it's needed for P10_7 to be in OSPI1_RXDS mode +P5_6,GPIO,OSPI1_RXDS,UART1_CTS,I2C2_SCL,UT3_T0,SD_D6,ETH_RXD1,CDC_VSYNC +P5_7,GPIO,OSPI1_SS0,UART1_RTS,I2C2_SDA,UT3_T1,SD_D7,ETH_RST, +P6_0,GPIO,OSPI0_D0,UART4_DE,PDM_D0,UT4_T0,SD_D0,ETH_TXD0, +P6_1,GPIO,OSPI0_D1,UART5_DE,PDM_C0,UT4_T1,SD_D1,ETH_TXD1, +P6_2,GPIO,OSPI0_D2,UART2_CTS,,PDM_D1,UT5_T0,SD_D2,ETH_TXEN +P6_3,GPIO,OSPI0_D3,UART2_RTS,,PDM_C1,UT5_T1,SD_D3,ETH_IRQ +P6_4,GPIO,OSPI0_D4,UART2_CTS,,SPI1_SS0,UT6_T0,SD_D4,ETH_REFCLK +P6_5,GPIO,OSPI0_D5,UART2_RTS,,SPI1_SS1,UT6_T1,SD_D5,ETH_MDIO +P6_6,GPIO,OSPI0_D6,UART0_CTS,,SPI1_SS2,UT7_T0,SD_D6,ETH_MDC +P6_7,GPIO,OSPI0_D7,UART0_RTS,PDM_C2,SPI1_SS3,UT7_T1,SD_D7,ETH_CRS_DV_A +P7_0,GPIO,,CMP3_OUT,SPI0_MISO,I2C0_SDA,UT8_T0,SD_CMD, +P7_1,GPIO,,CMP2_OUT,SPI0_MOSI,I2C0_SCL,UT8_T1,SD_CLK, +P7_2,GPIO,,UART3_CTS,CMP1_OUT,SPI0_SCLK,I2C1_SDA,UT9_T0,SD_RST +P7_3,GPIO,,UART3_RTS,CMP0_OUT,SPI0_SS0,I2C1_SCL,UT9_T1, +P7_4,GPIO,,LPUART_CTS,LPPDM_C2,LPSPI_MISO,LPI2C_SCL,UT10_T0, +P7_5,GPIO,,LPUART_RTS,,LPPDM_D2,LPSPI_MOSI,LPI2C_SDA,UT10_T1 +P7_6,GPIO,,LPUART_RX,,LPPDM_C3,LPSPI_SCLK,I3C_SDA,UT11_T0 +P7_7,GPIO,,LPUART_TX,,LPPDM_D3,LPSPI_SS,I3C_SCL,UT11_T1 +P8_0,GPIO,OSPI1_SCLKN,AUDIO_CLK,FAULT0_B,LPCAM_D0,SD_D0,CDC_D0,CAM_D0 +P8_1,GPIO,I2S2_SDI,FAULT1_B,LPCAM_D1,SD_D1,CDC_D1,CAM_D1, +P8_2,GPIO,I2S2_SDO,SPI0_SS3,FAULT2_B,LPCAM_D2,SD_D2,CDC_D2,CAM_D2 +P8_3,GPIO,I2S2_SCLK,SPI1_MISO,FAULT3_B,LPCAM_D3,SD_D3,CDC_D3,CAM_D3 +P8_4,GPIO,I2S2_WS,SPI1_MOSI,QEC0_X,LPCAM_D4,SD_D4,CDC_D4,CAM_D4 +P8_5,GPIO,,SPI1_SCLK,QEC0_Y,LPCAM_D5,SD_D5,CDC_D5,CAM_D5 +P8_6,GPIO,,I2S3_SCLK,QEC0_Z,LPCAM_D6,SD_D6,CDC_D6,CAM_D6 +P8_7,GPIO,,I2S3_WS,QEC1_X,LPCAM_D7,SD_D7,CDC_D7,CAM_D7 +P9_0,GPIO,,I2S3_SDI,QEC1_Y,SD_CMD,CDC_D8,CAM_D8, +P9_1,GPIO,LPUART_RX,I2S3_SDO,QEC1_Z,SD_CLK,CDC_D9,CAM_D9, +P9_2,GPIO,LPUART_TX,I2S3_SDI,SPI2_MISO,QEC2_X,SD_RST,CDC_D10,CAM_D10 +P9_3,GPIO,HFXO_OUT,UART7_RX,I2S3_SDO,SPI2_MOSI,QEC2_Y,CDC_D11,CAM_D11 +P9_4,GPIO,UART7_TX,I2S3_SCLK,SPI2_SCLK,I2C3_SDA,QEC2_Z,CDC_D12,CAM_D12 +P9_5,GPIO,OSPI1_D0,I2S3_WS,SPI2_SS0,I2C3_SCL,QEC3_X,CDC_D13,CAM_D13 +P9_6,GPIO,OSPI1_D1,AUDIO_CLK,SPI2_SS1,I2C3_SDA,QEC3_Y,CDC_D14,CAM_D14 +P9_7,GPIO,OSPI1_D2,UART7_DE,SPI2_SS2,I2C3_SCL,QEC3_Z,CDC_D15,CAM_D15 +P10_0,GPIO,OSPI1_D3,UART6_DE,SPI2_SS3,UT0_T0,LPCAM_HSYNC,CDC_D16,CAM_HSYNC +P10_1,GPIO,OSPI1_D4,,LPI2S_SDI,UT0_T1,LPCAM_VSYNC,CDC_D17,CAM_VSYNC +P10_2,GPIO,OSPI1_D5,,LPI2S_SDO,UT1_T0,LPCAM_PCLK,CDC_D18,CAM_PCLK +P10_3,GPIO,OSPI1_D6,,LPI2S_SCLK,UT1_T1,LPCAM_XVCLK,CDC_D19,CAM_XVCLK +P10_4,GPIO,OSPI1_D7,,LPI2S_WS,I2C0_SDA,UT2_T0,ETH_TXD0,CDC_D20 +P10_5,GPIO,UART6_RX,I2S2_SDI,SPI3_MISO,I2C0_SCL,UT2_T1,ETH_TXD1,CDC_D21 +P10_6,GPIO,UART6_TX,I2S2_SDO,SPI3_MOSI,I2C1_SDA,UT3_T0,ETH_TXEN,CDC_D22 +P10_7,GPIO,UART7_RX,I2S2_SCLK,SPI3_SCLK,I2C1_SCL,UT3_T1,CDC_D23,OSPI1_RXDS +P11_0,GPIO,OSPI1_D0,UART7_TX,I2S2_WS,SPI3_SS0,UT4_T0,ETH_REFCLK,CDC_D0 +P11_1,GPIO,OSPI1_D1,UART7_DE,SPI3_SS1,UT4_T1,ETH_MDIO,CDC_D1, +P11_2,GPIO,OSPI1_D2,UART6_DE,LPPDM_C2,SPI3_SS2,UT5_T0,ETH_MDC,CDC_D2 +P11_3,GPIO,OSPI1_D3,UART5_RX,LPPDM_C3,SPI3_SS3,UT5_T1,ETH_RXD0,CDC_D3 +P11_4,GPIO,OSPI1_D4,UART5_TX,PDM_C2,LPSPI_MISO,UT6_T0,ETH_RXD1,CDC_D4 +P11_5,GPIO,OSPI1_D5,UART6_RX,PDM_C3,LPSPI_MOSI,UT6_T1,ETH_CRS_DV_B,CDC_D5 +P11_6,GPIO,OSPI1_D6,UART6_TX,LPPDM_D2,LPSPI_SCLK,UT7_T0,ETH_RST,CDC_D6 +P11_7,GPIO,OSPI1_D7,UART5_DE,LPPDM_D3,LPSPI_SS,UT7_T1,ETH_IRQ,CDC_D7 +P12_0,GPIO,OSPI0_SCLK,AUDIO_CLK,I2S1_SDI,UT8_T0,CDC_D8,, +P12_1,GPIO,OSPI0_SCLKN,UART4_RX,I2S1_SDO,UT8_T1,CDC_D9,, +P12_2,GPIO,OSPI0_RXDS,UART4_TX,I2S1_SCLK,UT9_T0,CDC_D10,, +P12_3,GPIO,OSPI0_SS0,UART4_DE,I2S1_WS,UT9_T1,CDC_D11,, +P12_4,GPIO,OSPI0_SS1,SPI3_MISO,UT10_T0,,CDC_D12,, +P12_5,GPIO,,SPI3_MOSI,UT10_T1,,CDC_D13,, +P12_6,GPIO,,SPI3_SCLK,UT11_T0,,CDC_D14,, +P12_7,GPIO,OSPI1_RXDS,,SPI3_SS0,UT11_T1,CDC_D15,, +P13_0,GPIO,OSPI1_D0,,SPI3_SS1,QEC0_X,SD_D0,CDC_D16, +P13_1,GPIO,OSPI1_D1,SPI3_SS2,QEC0_Y,SD_D1,CDC_D17,, +P13_2,GPIO,OSPI1_D2,SPI3_SS3,QEC0_Z,SD_D2,CDC_D18,, +P13_3,GPIO,OSPI1_D3,SPI2_SS3,QEC1_X,SD_D3,CDC_D19,, +P13_4,GPIO,OSPI1_D4,LPI2S_SDI,QEC1_Y,SD_D4,CDC_D20,, +P13_5,GPIO,OSPI1_D5,LPI2S_SDO,QEC1_Z,SD_D5,CDC_D21,, +P13_6,GPIO,OSPI1_D6,LPI2S_SCLK,QEC2_X,SD_D6,CDC_D22,, +P13_7,GPIO,OSPI1_D7,LPI2S_WS,QEC2_Y,SD_D7,CDC_D23,, +P14_0,GPIO,OSPI1_SCLK,UART6_RX,QEC2_Z,SD_CMD,,, +P14_1,GPIO,OSPI1_SCLKN,UART6_TX,,QEC3_X,SD_CLK,, +P14_2,GPIO,OSPI1_SS0,UART7_RX,,QEC3_Y,SD_RST,, +P14_3,GPIO,OSPI1_SS1,UART7_TX,,QEC3_Z,,, +P14_4,GPIO,CMP3_OUT,SPI1_MISO,FAULT0_C,,,, +P14_5,GPIO,CMP2_OUT,SPI1_MOSI,FAULT1_C,,,, +P14_6,GPIO,CMP1_OUT,SPI1_SCLK,FAULT2_C,,,, +P14_7,GPIO,CMP0_OUT,SPI1_SS0,FAULT3_C,,,, +P15_0,GPIO,LPTMR0_CLK,,,,,, +P15_1,GPIO,LPTMR1_CLK,,,,,, +P15_2,GPIO,LPTMR2_CLK,,,,,, +P15_3,GPIO,LPTMR3_CLK,,,,,, +P15_4,GPIO,,,,,,, +P15_5,GPIO,,,,,,, +P15_6,GPIO,,,,,,, +P15_7,GPIO,,,,,,, diff --git a/ports/alif/mcu/make-pins.py b/ports/alif/mcu/make-pins.py index fb000082642..3666953e613 100755 --- a/ports/alif/mcu/make-pins.py +++ b/ports/alif/mcu/make-pins.py @@ -36,11 +36,19 @@ class AlifPin(boardgen.Pin): def __init__(self, cpu_pin_name): super().__init__(cpu_pin_name) - self._afs = ["MP_HAL_PIN_ALT_NONE"] * 8 + self._afs = [("NONE", -1)] * 8 # Called for each AF defined in the csv file for this pin. def add_af(self, af_idx, af_name, af): - self._afs[af_idx] = f"MP_HAL_PIN_ALT_{af}" + if af == "GPIO": + self._afs[af_idx] = "GPIO", -1 + elif af.startswith("ANA_S"): + self._afs[af_idx] = "ANA", int(af[5:]) + else: + m = re.match(r"([A-Z0-9]+[A-Z])(\d*)_([A-Z0-9_]+)$", af) + periph, unit, line = m.groups() + unit = -1 if unit == "" else int(unit) + self._afs[af_idx] = f"{periph}_{line}", unit # Emit the struct which contains the pin instance. def definition(self): @@ -63,7 +71,7 @@ def definition(self): base=base, adc12_periph=adc12_periph, adc12_channel=adc12_channel, - alt=", ".join([f"{af}" for af in self._afs]), + alt=", ".join([f"MP_HAL_PIN_ALT({func}, {unit})" for func, unit in self._afs]), ) ) diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 2ba62db2d2e..4f81439b549 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -92,34 +92,183 @@ extern ringbuf_t stdin_ringbuf; #define mp_hal_pin_obj_t const machine_pin_obj_t * +#define MP_HAL_PIN_ALT(function, unit) (MP_HAL_PIN_ALT_MAKE((MP_HAL_PIN_ALT_##function), (unit))) +#define MP_HAL_PIN_ALT_MAKE(function, unit) ((function) | ((unit) << 8)) +#define MP_HAL_PIN_ALT_GET_FUNC(alt) ((alt) & 0xff) +#define MP_HAL_PIN_ALT_GET_UNIT(alt) ((alt) >> 8) + enum { MP_HAL_PIN_ALT_NONE = 0, + MP_HAL_PIN_ALT_GPIO, MP_HAL_PIN_ALT_ANA, - MP_HAL_PIN_ALT_AUDIO, - MP_HAL_PIN_ALT_CAM, - MP_HAL_PIN_ALT_CDC, - MP_HAL_PIN_ALT_CMP, - MP_HAL_PIN_ALT_ETH, - MP_HAL_PIN_ALT_FAULT, - MP_HAL_PIN_ALT_HFXO, - MP_HAL_PIN_ALT_I2C, - MP_HAL_PIN_ALT_I2S, - MP_HAL_PIN_ALT_I3C, - MP_HAL_PIN_ALT_JTAG, - MP_HAL_PIN_ALT_LPCAM, - MP_HAL_PIN_ALT_LPI2C, - MP_HAL_PIN_ALT_LPI2S, - MP_HAL_PIN_ALT_LPPDM, - MP_HAL_PIN_ALT_LPSPI, - MP_HAL_PIN_ALT_LPTMR, - MP_HAL_PIN_ALT_LPUART, - MP_HAL_PIN_ALT_OSPI, - MP_HAL_PIN_ALT_PDM, - MP_HAL_PIN_ALT_QEC, - MP_HAL_PIN_ALT_SD, - MP_HAL_PIN_ALT_SPI, - MP_HAL_PIN_ALT_UART, - MP_HAL_PIN_ALT_UT, + MP_HAL_PIN_ALT_AUDIO_CLK, + MP_HAL_PIN_ALT_CAM_D0, + MP_HAL_PIN_ALT_CAM_D1, + MP_HAL_PIN_ALT_CAM_D2, + MP_HAL_PIN_ALT_CAM_D3, + MP_HAL_PIN_ALT_CAM_D4, + MP_HAL_PIN_ALT_CAM_D5, + MP_HAL_PIN_ALT_CAM_D6, + MP_HAL_PIN_ALT_CAM_D7, + MP_HAL_PIN_ALT_CAM_D8, + MP_HAL_PIN_ALT_CAM_D9, + MP_HAL_PIN_ALT_CAM_D10, + MP_HAL_PIN_ALT_CAM_D11, + MP_HAL_PIN_ALT_CAM_D12, + MP_HAL_PIN_ALT_CAM_D13, + MP_HAL_PIN_ALT_CAM_D14, + MP_HAL_PIN_ALT_CAM_D15, + MP_HAL_PIN_ALT_CAM_HSYNC, + MP_HAL_PIN_ALT_CAM_PCLK, + MP_HAL_PIN_ALT_CAM_VSYNC, + MP_HAL_PIN_ALT_CAM_XVCLK, + MP_HAL_PIN_ALT_CDC_D0, + MP_HAL_PIN_ALT_CDC_D1, + MP_HAL_PIN_ALT_CDC_D2, + MP_HAL_PIN_ALT_CDC_D3, + MP_HAL_PIN_ALT_CDC_D4, + MP_HAL_PIN_ALT_CDC_D5, + MP_HAL_PIN_ALT_CDC_D6, + MP_HAL_PIN_ALT_CDC_D7, + MP_HAL_PIN_ALT_CDC_D8, + MP_HAL_PIN_ALT_CDC_D9, + MP_HAL_PIN_ALT_CDC_D10, + MP_HAL_PIN_ALT_CDC_D11, + MP_HAL_PIN_ALT_CDC_D12, + MP_HAL_PIN_ALT_CDC_D13, + MP_HAL_PIN_ALT_CDC_D14, + MP_HAL_PIN_ALT_CDC_D15, + MP_HAL_PIN_ALT_CDC_D16, + MP_HAL_PIN_ALT_CDC_D17, + MP_HAL_PIN_ALT_CDC_D18, + MP_HAL_PIN_ALT_CDC_D19, + MP_HAL_PIN_ALT_CDC_D20, + MP_HAL_PIN_ALT_CDC_D21, + MP_HAL_PIN_ALT_CDC_D22, + MP_HAL_PIN_ALT_CDC_D23, + MP_HAL_PIN_ALT_CDC_DE, + MP_HAL_PIN_ALT_CDC_HSYNC, + MP_HAL_PIN_ALT_CDC_PCLK, + MP_HAL_PIN_ALT_CDC_VSYNC, + MP_HAL_PIN_ALT_CMP_OUT, + MP_HAL_PIN_ALT_ETH_CRS_DV_A, + MP_HAL_PIN_ALT_ETH_CRS_DV_B, + MP_HAL_PIN_ALT_ETH_CRS_DV_C, + MP_HAL_PIN_ALT_ETH_IRQ, + MP_HAL_PIN_ALT_ETH_MDC, + MP_HAL_PIN_ALT_ETH_MDIO, + MP_HAL_PIN_ALT_ETH_REFCLK, + MP_HAL_PIN_ALT_ETH_RST, + MP_HAL_PIN_ALT_ETH_RXD0, + MP_HAL_PIN_ALT_ETH_RXD1, + MP_HAL_PIN_ALT_ETH_TXD0, + MP_HAL_PIN_ALT_ETH_TXD1, + MP_HAL_PIN_ALT_ETH_TXEN, + MP_HAL_PIN_ALT_FAULT_A, + MP_HAL_PIN_ALT_FAULT_B, + MP_HAL_PIN_ALT_FAULT_C, + MP_HAL_PIN_ALT_HFXO_OUT, + MP_HAL_PIN_ALT_I2C_SCL, + MP_HAL_PIN_ALT_I2C_SDA, + MP_HAL_PIN_ALT_I2S_SCLK, + MP_HAL_PIN_ALT_I2S_SDI, + MP_HAL_PIN_ALT_I2S_SDO, + MP_HAL_PIN_ALT_I2S_WS, + MP_HAL_PIN_ALT_I3C_SCL, + MP_HAL_PIN_ALT_I3C_SDA, + MP_HAL_PIN_ALT_JTAG_TCK, + MP_HAL_PIN_ALT_JTAG_TDATA0, + MP_HAL_PIN_ALT_JTAG_TDATA1, + MP_HAL_PIN_ALT_JTAG_TDATA2, + MP_HAL_PIN_ALT_JTAG_TDATA3, + MP_HAL_PIN_ALT_JTAG_TDI, + MP_HAL_PIN_ALT_JTAG_TDO, + MP_HAL_PIN_ALT_JTAG_TMS, + MP_HAL_PIN_ALT_JTAG_TRACECLK, + MP_HAL_PIN_ALT_LPCAM_D0, + MP_HAL_PIN_ALT_LPCAM_D1, + MP_HAL_PIN_ALT_LPCAM_D2, + MP_HAL_PIN_ALT_LPCAM_D3, + MP_HAL_PIN_ALT_LPCAM_D4, + MP_HAL_PIN_ALT_LPCAM_D5, + MP_HAL_PIN_ALT_LPCAM_D6, + MP_HAL_PIN_ALT_LPCAM_D7, + MP_HAL_PIN_ALT_LPCAM_HSYNC, + MP_HAL_PIN_ALT_LPCAM_PCLK, + MP_HAL_PIN_ALT_LPCAM_VSYNC, + MP_HAL_PIN_ALT_LPCAM_XVCLK, + MP_HAL_PIN_ALT_LPI2C_SCL, + MP_HAL_PIN_ALT_LPI2C_SDA, + MP_HAL_PIN_ALT_LPI2S_SCLK, + MP_HAL_PIN_ALT_LPI2S_SDI, + MP_HAL_PIN_ALT_LPI2S_SDO, + MP_HAL_PIN_ALT_LPI2S_WS, + MP_HAL_PIN_ALT_LPPDM_C0, + MP_HAL_PIN_ALT_LPPDM_C1, + MP_HAL_PIN_ALT_LPPDM_C2, + MP_HAL_PIN_ALT_LPPDM_C3, + MP_HAL_PIN_ALT_LPPDM_D0, + MP_HAL_PIN_ALT_LPPDM_D1, + MP_HAL_PIN_ALT_LPPDM_D2, + MP_HAL_PIN_ALT_LPPDM_D3, + MP_HAL_PIN_ALT_LPSPI_MISO, + MP_HAL_PIN_ALT_LPSPI_MOSI, + MP_HAL_PIN_ALT_LPSPI_SCLK, + MP_HAL_PIN_ALT_LPSPI_SS, + MP_HAL_PIN_ALT_LPTMR_CLK, + MP_HAL_PIN_ALT_LPUART_CTS, + MP_HAL_PIN_ALT_LPUART_RTS, + MP_HAL_PIN_ALT_LPUART_RX, + MP_HAL_PIN_ALT_LPUART_TX, + MP_HAL_PIN_ALT_OSPI_D0, + MP_HAL_PIN_ALT_OSPI_D1, + MP_HAL_PIN_ALT_OSPI_D2, + MP_HAL_PIN_ALT_OSPI_D3, + MP_HAL_PIN_ALT_OSPI_D4, + MP_HAL_PIN_ALT_OSPI_D5, + MP_HAL_PIN_ALT_OSPI_D6, + MP_HAL_PIN_ALT_OSPI_D7, + MP_HAL_PIN_ALT_OSPI_RXDS, + MP_HAL_PIN_ALT_OSPI_SCLK, + MP_HAL_PIN_ALT_OSPI_SCLKN, + MP_HAL_PIN_ALT_OSPI_SS0, + MP_HAL_PIN_ALT_OSPI_SS1, + MP_HAL_PIN_ALT_PDM_C0, + MP_HAL_PIN_ALT_PDM_C1, + MP_HAL_PIN_ALT_PDM_C2, + MP_HAL_PIN_ALT_PDM_C3, + MP_HAL_PIN_ALT_PDM_D0, + MP_HAL_PIN_ALT_PDM_D1, + MP_HAL_PIN_ALT_PDM_D2, + MP_HAL_PIN_ALT_PDM_D3, + MP_HAL_PIN_ALT_QEC_X, + MP_HAL_PIN_ALT_QEC_Y, + MP_HAL_PIN_ALT_QEC_Z, + MP_HAL_PIN_ALT_SD_CLK, + MP_HAL_PIN_ALT_SD_CMD, + MP_HAL_PIN_ALT_SD_D0, + MP_HAL_PIN_ALT_SD_D1, + MP_HAL_PIN_ALT_SD_D2, + MP_HAL_PIN_ALT_SD_D3, + MP_HAL_PIN_ALT_SD_D4, + MP_HAL_PIN_ALT_SD_D5, + MP_HAL_PIN_ALT_SD_D6, + MP_HAL_PIN_ALT_SD_D7, + MP_HAL_PIN_ALT_SD_RST, + MP_HAL_PIN_ALT_SPI_MISO, + MP_HAL_PIN_ALT_SPI_MOSI, + MP_HAL_PIN_ALT_SPI_SCLK, + MP_HAL_PIN_ALT_SPI_SS0, + MP_HAL_PIN_ALT_SPI_SS1, + MP_HAL_PIN_ALT_SPI_SS2, + MP_HAL_PIN_ALT_SPI_SS3, + MP_HAL_PIN_ALT_UART_CTS, + MP_HAL_PIN_ALT_UART_DE, + MP_HAL_PIN_ALT_UART_RTS, + MP_HAL_PIN_ALT_UART_RX, + MP_HAL_PIN_ALT_UART_TX, + MP_HAL_PIN_ALT_UT_T0, + MP_HAL_PIN_ALT_UT_T1, }; typedef struct _machine_pin_obj_t { @@ -130,7 +279,7 @@ typedef struct _machine_pin_obj_t { uint8_t adc12_periph : 2; uint8_t adc12_channel : 3; qstr name; - const uint8_t alt[8]; + const uint16_t alt[8]; // holds result of MP_HAL_PIN_ALT_MAKE(function, unit) } machine_pin_obj_t; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); diff --git a/ports/alif/mpuart.c b/ports/alif/mpuart.c index 4fe82ce1231..b646e6f2be4 100644 --- a/ports/alif/mpuart.c +++ b/ports/alif/mpuart.c @@ -91,8 +91,10 @@ void mp_uart_init(unsigned int uart_id, uint32_t baudrate, uart_state_t *state = &uart_state[uart_id]; // Configure TX/RX pins. - mp_hal_pin_config(tx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, false); - mp_hal_pin_config(rx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, true); + mp_hal_pin_config(tx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(UART_TX, uart_id), false); + mp_hal_pin_config(rx, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(UART_RX, uart_id), true); // Configure the UART peripheral. select_uart_clock_syst_pclk(uart_id); @@ -152,11 +154,13 @@ void mp_uart_set_flow(unsigned int uart_id, mp_hal_pin_obj_t rts, mp_hal_pin_obj unsigned int flow = UART_FLOW_CONTROL_NONE; if (rts != NULL) { flow |= UART_FLOW_CONTROL_RTS; - mp_hal_pin_config(rts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, false); + mp_hal_pin_config(rts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(UART_RTS, uart_id), false); } if (cts != NULL) { flow |= UART_FLOW_CONTROL_CTS; - mp_hal_pin_config(cts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT_UART, true); + mp_hal_pin_config(cts, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(UART_CTS, uart_id), true); } uart_set_flow_control(uart, flow); } diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 17c8be8128d..9ba7ebe6eca 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -262,40 +262,41 @@ int ospi_flash_init(void) { self->pin = pin; + unsigned int unit = pin->peripheral_number; mp_hal_pin_config(pin->pin_cs, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_SS0, unit), false); mp_hal_pin_config(pin->pin_clk_p, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_SCLK, unit), false); if (pin->pin_clk_n != NULL) { mp_hal_pin_config(pin->pin_clk_n, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, false); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_SCLKN, unit), false); } if (pin->pin_rwds != NULL) { mp_hal_pin_config(pin->pin_rwds, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_RXDS, unit), true); if (pin->pin_rwds->port == PORT_10 && pin->pin_rwds->pin == PIN_7) { // Alif: P5_6 is needed to support proper alt function selection of P10_7. mp_hal_pin_config(pin_P5_6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_RXDS, unit), true); } } mp_hal_pin_config(pin->pin_d0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D0, unit), true); mp_hal_pin_config(pin->pin_d1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D1, unit), true); mp_hal_pin_config(pin->pin_d2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D2, unit), true); mp_hal_pin_config(pin->pin_d3, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D3, unit), true); if (pin->pin_d4 != NULL) { mp_hal_pin_config(pin->pin_d4, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D4, unit), true); mp_hal_pin_config(pin->pin_d5, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D5, unit), true); mp_hal_pin_config(pin->pin_d6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D6, unit), true); mp_hal_pin_config(pin->pin_d7, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT_OSPI, true); + MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D7, unit), true); } // Reset the SPI flash. From 3564ce5bd8c22254c39a556232891f9d43201eac Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 31 Mar 2025 12:35:26 +1100 Subject: [PATCH 0521/2098] alif/ospi_flash: Don't invalidate cache after erasing/writing. It's not needed, the MPU configures the XIP as non-cacheable. Signed-off-by: Damien George --- ports/alif/ospi_flash.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 9ba7ebe6eca..8bcd1fb5b13 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -393,14 +393,11 @@ int ospi_flash_erase_sector(uint32_t addr) { ospi_flash_write_cmd(self, self->set->write_en); int ret = ospi_flash_wait_wel1(self); - if (ret < 0) { - return ret; + if (ret == 0) { + ospi_flash_write_cmd_addr(self, self->set->erase_command, OSPI_ADDR_L_32bit, addr); + ret = ospi_flash_wait_wip0(self); } - ospi_flash_write_cmd_addr(self, self->set->erase_command, OSPI_ADDR_L_32bit, addr); - ret = ospi_flash_wait_wip0(self); - - SCB_InvalidateDCache_by_Addr(global_flash.cfg.xip_base + addr, MICROPY_HW_FLASH_BLOCK_SIZE_BYTES); return ret; } @@ -463,7 +460,6 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { offset = 0; } - SCB_InvalidateDCache_by_Addr(global_flash.cfg.xip_base + addr, len); return ret; } From df5e4ced762aac83200906c34e6dfcda7b7ccd6e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 31 Mar 2025 12:35:50 +1100 Subject: [PATCH 0522/2098] alif/ospi_flash_settings: Use 8-bit DFS for XIP. To match the instruction length, so the DFS is restored to the XIP value after an erase or write (due to the final wait WIP). Signed-off-by: Damien George --- ports/alif/ospi_flash_settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/alif/ospi_flash_settings.h b/ports/alif/ospi_flash_settings.h index 72403c26b67..89686c4946c 100644 --- a/ports/alif/ospi_flash_settings.h +++ b/ports/alif/ospi_flash_settings.h @@ -53,7 +53,7 @@ .rxds = false, \ .bswap16 = false, \ .inst_len = OSPI_INST_L_8bit, \ - .xip_data_len = OSPI_DATA_L_16bit, \ + .xip_data_len = OSPI_DATA_L_8bit, \ .read_sr = 0x05, \ .read_sr_dummy_cycles = 8, \ .write_en = 0x06, \ @@ -70,7 +70,7 @@ .rxds = false, \ .bswap16 = false, \ .inst_len = OSPI_INST_L_8bit, \ - .xip_data_len = OSPI_DATA_L_16bit, \ + .xip_data_len = OSPI_DATA_L_8bit, \ .read_sr = 0x05, \ .read_sr_dummy_cycles = 8, \ .write_en = 0x06, \ From c395f5ebb07a2ebcafba32178f5d7680771969a3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Apr 2025 23:07:27 +1100 Subject: [PATCH 0523/2098] alif/ospi_flash: Restore XIP settings after erase and write. Signed-off-by: Damien George --- ports/alif/ospi_ext.c | 28 ++++++++++++++++++---------- ports/alif/ospi_ext.h | 1 + ports/alif/ospi_flash.c | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/ports/alif/ospi_ext.c b/ports/alif/ospi_ext.c index e3389d8bd54..6ddaaf4e5a3 100644 --- a/ports/alif/ospi_ext.c +++ b/ports/alif/ospi_ext.c @@ -206,20 +206,22 @@ void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_l spi_enable(ospi_cfg); } +static inline uint32_t ospi_xip_ctrlr0(uint32_t data_len) { + return CTRLR0_IS_MST + | (OCTAL << CTRLR0_SPI_FRF_OFFSET) + | (0 << CTRLR0_SCPOL_OFFSET) + | (0 << CTRLR0_SCPH_OFFSET) + | (0 << CTRLR0_SSTE_OFFSET) + | (TMOD_RO << CTRLR0_TMOD_OFFSET) + | (data_len << CTRLR0_DFS_OFFSET); +} + void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles) { spi_disable(ospi_cfg); - uint32_t val = CTRLR0_IS_MST - | (OCTAL << CTRLR0_SPI_FRF_OFFSET) - | (0 << CTRLR0_SCPOL_OFFSET) - | (0 << CTRLR0_SCPH_OFFSET) - | (0 << CTRLR0_SSTE_OFFSET) - | (TMOD_RO << CTRLR0_TMOD_OFFSET) - | (data_len << CTRLR0_DFS_OFFSET); - - ospi_writel(ospi_cfg, ctrlr0, val); + ospi_writel(ospi_cfg, ctrlr0, ospi_xip_ctrlr0(data_len)); - val = (OCTAL << XIP_CTRL_FRF_OFFSET) + uint32_t val = (OCTAL << XIP_CTRL_FRF_OFFSET) | (0x2 << XIP_CTRL_TRANS_TYPE_OFFSET) | (ADDR_L32bit << XIP_CTRL_ADDR_L_OFFSET) | (inst_len << XIP_CTRL_INST_L_OFFSET) @@ -292,3 +294,9 @@ void ospi_xip_exit_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint16_t i ospi_xip_enable(ospi_cfg); ospi_xip_disable(ospi_cfg); } + +void ospi_xip_restore_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len) { + spi_disable(ospi_cfg); + ospi_writel(ospi_cfg, ctrlr0, ospi_xip_ctrlr0(data_len)); + spi_enable(ospi_cfg); +} diff --git a/ports/alif/ospi_ext.h b/ports/alif/ospi_ext.h index e5a2b50beb6..65e2ced1ff7 100644 --- a/ports/alif/ospi_ext.h +++ b/ports/alif/ospi_ext.h @@ -53,5 +53,6 @@ void ospi_setup_write_ext(ospi_flash_cfg_t *ospi_cfg, bool rxds, uint32_t inst_l void ospi_xip_enter_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint32_t data_len, uint16_t incr_command, uint16_t wrap_command, uint16_t read_dummy_cycles); void ospi_xip_exit_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t inst_len, uint16_t incr_command, uint16_t wrap_command); +void ospi_xip_restore_ext(ospi_flash_cfg_t *ospi_cfg, uint32_t data_len); #endif // MICROPY_INCLUDED_ALIF_OSPI_EXT_H diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index 8bcd1fb5b13..f78002eb0ef 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -354,8 +354,9 @@ int ospi_flash_init(void) { } } - // Enter XIP mode. It will be disabled during flash read/erase/write. + // Enter XIP mode. ospi_flash_xip_enter(self); + return 0; } @@ -385,6 +386,13 @@ int ospi_flash_xip_exit(ospi_flash_t *self) { return 0; } +int ospi_flash_xip_restore(ospi_flash_t *self) { + if (self->xip_active) { + ospi_xip_restore_ext(&self->cfg, self->set->xip_data_len); + } + return 0; +} + /******************************************************************************/ // Top-level read/erase/write functions. @@ -398,6 +406,8 @@ int ospi_flash_erase_sector(uint32_t addr) { ret = ospi_flash_wait_wip0(self); } + ospi_flash_xip_restore(self); + return ret; } @@ -442,6 +452,8 @@ static int ospi_flash_write_page(uint32_t addr, uint32_t len, const uint8_t *src } int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { + ospi_flash_t *self = &global_flash; + int ret = 0; uint32_t offset = addr & (PAGE_SIZE - 1); @@ -460,6 +472,8 @@ int ospi_flash_write(uint32_t addr, uint32_t len, const uint8_t *src) { offset = 0; } + ospi_flash_xip_restore(self); + return ret; } From b79b64a726d9f1796605f75980d2ae52595863f8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 31 Mar 2025 22:40:58 +1100 Subject: [PATCH 0524/2098] alif/mpu: Add MPU region for OSPI1 XIP memory range. Signed-off-by: Damien George --- ports/alif/mpu.c | 4 ++++ ports/alif/mpu.h | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/alif/mpu.c b/ports/alif/mpu.c index 60753674ae1..9a051794de8 100644 --- a/ports/alif/mpu.c +++ b/ports/alif/mpu.c @@ -54,6 +54,10 @@ static const ARM_MPU_Region_t mpu_table[] __STARTUP_RO_DATA_ATTRIBUTE = { .RBAR = ARM_MPU_RBAR(0xA0000000, ARM_MPU_SH_NON, 1, 1, 0), .RLAR = ARM_MPU_RLAR(0xBFFFFFFF, MP_MPU_ATTR_NORMAL_NON_CACHEABLE) }, + [MP_MPU_REGION_OSPI1_XIP] = { /* OSPI1 XIP flash - 512MB : RO-1, NP-1, XN-0 */ + .RBAR = ARM_MPU_RBAR(0xC0000000, ARM_MPU_SH_NON, 1, 1, 0), + .RLAR = ARM_MPU_RLAR(0xDFFFFFFF, MP_MPU_ATTR_NORMAL_NON_CACHEABLE) + }, }; void MPU_Load_Regions(void) { diff --git a/ports/alif/mpu.h b/ports/alif/mpu.h index 1d3602941ef..f4df496683e 100644 --- a/ports/alif/mpu.h +++ b/ports/alif/mpu.h @@ -38,6 +38,7 @@ #define MP_MPU_REGION_MRAM (3) #define MP_MPU_REGION_OSPI_REGISTERS (4) #define MP_MPU_REGION_OSPI0_XIP (5) -#define MP_MPU_REGION_OPENAMP (6) +#define MP_MPU_REGION_OSPI1_XIP (6) +#define MP_MPU_REGION_OPENAMP (7) void mpu_config_mram(bool read_only); From 7c216d17b6e1d8394d05fd5eaae1c812c3846963 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 17 Dec 2023 12:12:06 +0100 Subject: [PATCH 0525/2098] alif/boards/ALIF_ENSEMBLE: Add Alif Ensemble board config. Signed-off-by: iabdalkader Signed-off-by: Damien George --- ports/alif/boards/ALIF_ENSEMBLE/board.c | 56 +++++++++++++++++++ ports/alif/boards/ALIF_ENSEMBLE/board.ld.S | 14 +++++ .../alif/boards/ALIF_ENSEMBLE/mpconfigboard.h | 56 +++++++++++++++++++ .../boards/ALIF_ENSEMBLE/mpconfigboard.mk | 11 ++++ .../alif/boards/ALIF_ENSEMBLE/ospi_xip_user.h | 5 ++ ports/alif/boards/ALIF_ENSEMBLE/pins.csv | 30 ++++++++++ 6 files changed, 172 insertions(+) create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/board.c create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/board.ld.S create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.h create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.mk create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/ospi_xip_user.h create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/pins.csv diff --git a/ports/alif/boards/ALIF_ENSEMBLE/board.c b/ports/alif/boards/ALIF_ENSEMBLE/board.c new file mode 100644 index 00000000000..72b93e31ffb --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/board.c @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "ospi_ext.h" +#include "ospi_flash.h" + +const ospi_pin_settings_t ospi_pin_settings = { + .peripheral_number = 1, + .pin_reset = pin_OSPI1_RESET, + .pin_cs = pin_OSPI1_CS, + .pin_clk_p = pin_OSPI1_SCLK, + .pin_clk_n = NULL, + .pin_rwds = pin_OSPI1_RXDS, + .pin_d0 = pin_OSPI1_D0, + .pin_d1 = pin_OSPI1_D1, + .pin_d2 = pin_OSPI1_D2, + .pin_d3 = pin_OSPI1_D3, + .pin_d4 = pin_OSPI1_D4, + .pin_d5 = pin_OSPI1_D5, + .pin_d6 = pin_OSPI1_D6, + .pin_d7 = pin_OSPI1_D7, +}; + +const ospi_flash_settings_t ospi_flash_settings[] = { + { + .jedec_id = 0x1a5b9d, + .freq_hz = 100000000, + .read_dummy_cycles = 9, + OSPI_FLASH_SETTINGS_IS25, + }, +}; +const size_t ospi_flash_settings_len = 1; diff --git a/ports/alif/boards/ALIF_ENSEMBLE/board.ld.S b/ports/alif/boards/ALIF_ENSEMBLE/board.ld.S new file mode 100644 index 00000000000..7adcdbbcc37 --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/board.ld.S @@ -0,0 +1,14 @@ +#include "mcu/ensemble.ld.S" + +/* Define ROMFS partition locations. */ +#if CORE_M55_HP +/* The HP core has access to the external OSPI1 flash and MRAM ROMFS partitions. */ +_micropy_hw_romfs_part0_start = 0xc1000000; +_micropy_hw_romfs_part0_size = 16M; +_micropy_hw_romfs_part1_start = ORIGIN(MRAM_FS); +_micropy_hw_romfs_part1_size = LENGTH(MRAM_FS); +#else +/* The HP core has access to the MRAM ROMFS partition. */ +_micropy_hw_romfs_part0_start = ORIGIN(MRAM_FS); +_micropy_hw_romfs_part0_size = LENGTH(MRAM_FS); +#endif diff --git a/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.h b/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.h new file mode 100644 index 00000000000..8a6003ebbfb --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.h @@ -0,0 +1,56 @@ +#define MICROPY_HW_BOARD_NAME "Alif Ensemble DevKit" +#define MICROPY_HW_MCU_NAME "AE722F80F55D5XX" + +#define MICROPY_HW_ENABLE_UART_REPL (CORE_M55_HP) +#define MICROPY_HW_UART_REPL (4) +#define MICROPY_HW_USB_MSC (1) +#define MICROPY_HW_ENABLE_HW_I2C (1) + +// ROMFS partitions +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) +#define MICROPY_HW_ROMFS_ENABLE_PART1 (CORE_M55_HP) + +// I2C buses +#define MICROPY_HW_I2C0_SCL (pin_P0_3) +#define MICROPY_HW_I2C0_SDA (pin_P0_2) +#define MICROPY_HW_I2C1_SCL (pin_P3_7) +#define MICROPY_HW_I2C1_SDA (pin_P3_6) +#define MICROPY_HW_I2C2_SCL (pin_P5_1) +#define MICROPY_HW_I2C2_SDA (pin_P5_0) +#define MICROPY_HW_I2C3_SCL (pin_P1_1) +#define MICROPY_HW_I2C3_SDA (pin_P1_0) + +// SPI buses +#define MICROPY_HW_SPI0_MISO (pin_P1_0) +#define MICROPY_HW_SPI0_MOSI (pin_P1_1) +#define MICROPY_HW_SPI0_SCK (pin_P1_2) +#define MICROPY_HW_SPI1_MISO (pin_P2_4) +#define MICROPY_HW_SPI1_MOSI (pin_P2_5) +#define MICROPY_HW_SPI1_SCK (pin_P2_6) +#define MICROPY_HW_SPI2_MISO (pin_P4_2) +#define MICROPY_HW_SPI2_MOSI (pin_P4_3) +#define MICROPY_HW_SPI2_SCK (pin_P4_4) +#define MICROPY_HW_SPI3_MISO (pin_P12_4) +#define MICROPY_HW_SPI3_MOSI (pin_P12_5) +#define MICROPY_HW_SPI3_SCK (pin_P12_6) +#define MICROPY_HW_LPSPI0_MISO (pin_P7_4) +#define MICROPY_HW_LPSPI0_MOSI (pin_P7_5) +#define MICROPY_HW_LPSPI0_SCK (pin_P7_6) + +// UART buses +#define MICROPY_HW_UART0_TX (pin_P0_1) +#define MICROPY_HW_UART0_RX (pin_P0_0) +#define MICROPY_HW_UART0_RTS (pin_P0_3) +#define MICROPY_HW_UART0_CTS (pin_P0_2) +#define MICROPY_HW_UART1_TX (pin_P0_5) +#define MICROPY_HW_UART1_RX (pin_P0_4) +#define MICROPY_HW_UART1_RTS (pin_P0_7) +#define MICROPY_HW_UART1_CTS (pin_P0_6) +#define MICROPY_HW_REPL_UART_TX (pin_P12_2) +#define MICROPY_HW_REPL_UART_RX (pin_P12_1) + +// This is used for alif.Flash() and USB MSC. +#define MICROPY_HW_FLASH_STORAGE_BASE_ADDR (0) +#define MICROPY_HW_FLASH_STORAGE_BYTES (32 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_FS_BYTES (16 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_ROMFS_BYTES (16 * 1024 * 1024) diff --git a/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.mk b/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.mk new file mode 100644 index 00000000000..d35a7aad84a --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/mpconfigboard.mk @@ -0,0 +1,11 @@ +MCU_SERIES = E7 +MCU_VARIANT = AE722F80F55D5XX +JLINK_DEV = AE722F80F55D5_HP +LD_FILE = boards/ALIF_ENSEMBLE/board.ld.S + +ALIF_TOOLKIT_CFG_PART = AE722F80F55D5LS +ALIF_TOOLKIT_CFG_FILE = \"app-device-config-ae7.json\" + +MICROPY_FLOAT_IMPL = float +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 diff --git a/ports/alif/boards/ALIF_ENSEMBLE/ospi_xip_user.h b/ports/alif/boards/ALIF_ENSEMBLE/ospi_xip_user.h new file mode 100644 index 00000000000..49921bdd22d --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/ospi_xip_user.h @@ -0,0 +1,5 @@ +// This file is needed by ospi_xip/source/ospi/ospi_drv.c. +#define OSPI_XIP_ENABLE_AES_DECRYPTION (0) +#define OSPI_XIP_RX_SAMPLE_DELAY (3) +#define OSPI_XIP_DDR_DRIVE_EDGE (1) +#define OSPI_XIP_RXDS_DELAY (12) diff --git a/ports/alif/boards/ALIF_ENSEMBLE/pins.csv b/ports/alif/boards/ALIF_ENSEMBLE/pins.csv new file mode 100644 index 00000000000..ec397d9cf60 --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/pins.csv @@ -0,0 +1,30 @@ +# OSP1 flash +OSPI1_RESET,P15_7 +OSPI1_CS,P5_7 +OSPI1_SCLK,P5_5 +OSPI1_D0,P9_5 +OSPI1_D1,P9_6 +OSPI1_D2,P9_7 +OSPI1_D3,P10_0 +OSPI1_D4,P10_1 +OSPI1_D5,P10_2 +OSPI1_D6,P10_3 +OSPI1_D7,P10_4 +OSPI1_RXDS,P10_7 + +LED_BLUE,P12_0 +LED_RED,P12_3 +JOY_LEFT,P15_0 +JOY_RIGHT,P15_1 + +# UART buses +UART0_TX,P0_1 +UART0_RX,P0_0 +UART0_RTS,P0_3 +UART0_CTS,P0_2 +UART1_TX,P0_5 +UART1_RX,P0_4 +UART1_RTS,P0_7 +UART1_CTS,P0_6 +REPL_UART_TX,P12_2 +REPL_UART_RX,P12_1 From 704d2f2d57b3e30b3b0d0cf02849e9784b60ccb6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Sep 2024 21:24:47 +1000 Subject: [PATCH 0526/2098] alif/boards/OPENMV_AE3: Add OpenMV AE3 board definition. Supports Murata 1YN for WiFi and BLE. Signed-off-by: iabdalkader Signed-off-by: Damien George --- ports/alif/boards/OPENMV_AE3/board.c | 192 ++++++++++++++++++ ports/alif/boards/OPENMV_AE3/board.ld.S | 14 ++ ports/alif/boards/OPENMV_AE3/mpconfigboard.h | 85 ++++++++ ports/alif/boards/OPENMV_AE3/mpconfigboard.mk | 22 ++ ports/alif/boards/OPENMV_AE3/ospi_xip_user.h | 6 + ports/alif/boards/OPENMV_AE3/pins.csv | 69 +++++++ 6 files changed, 388 insertions(+) create mode 100644 ports/alif/boards/OPENMV_AE3/board.c create mode 100644 ports/alif/boards/OPENMV_AE3/board.ld.S create mode 100644 ports/alif/boards/OPENMV_AE3/mpconfigboard.h create mode 100644 ports/alif/boards/OPENMV_AE3/mpconfigboard.mk create mode 100644 ports/alif/boards/OPENMV_AE3/ospi_xip_user.h create mode 100644 ports/alif/boards/OPENMV_AE3/pins.csv diff --git a/ports/alif/boards/OPENMV_AE3/board.c b/ports/alif/boards/OPENMV_AE3/board.c new file mode 100644 index 00000000000..65da152d853 --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/board.c @@ -0,0 +1,192 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 OpenMV LLC. + * + * 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 "ospi_ext.h" +#include "ospi_flash.h" +#include "se_services.h" + +#define OMV_BOOT_MAGIC_ADDR (0x200FFFFCU) +#define OMV_BOOT_MAGIC_VALUE (0xB00710ADU) + +#if CORE_M55_HP +#define NPU_IRQ_NUMBER NPU_HP_IRQ_IRQn +#define NPU_BASE_ADDRESS (void *)NPU_HP_BASE +#else +#define NPU_IRQ_NUMBER NPU_HE_IRQ_IRQn +#define NPU_BASE_ADDRESS (void *)NPU_HE_BASE +#endif + +typedef struct { + volatile uint32_t ID; // 0x0 + volatile uint32_t STATUS; // 0x4 + volatile uint32_t CMD; // 0x8 + volatile uint32_t RESET; // 0xC +} npu_regs_t; + +#define NPU ((npu_regs_t *)NPU_BASE_ADDRESS) + +const ospi_pin_settings_t ospi_pin_settings = { + .peripheral_number = 0, + .pin_reset = pin_FLASH_RESET, + .pin_cs = pin_FLASH_CS, + .pin_clk_p = pin_FLASH_SCLK_P, + .pin_clk_n = pin_FLASH_SCLK_N, + .pin_rwds = pin_FLASH_DQSM, + .pin_d0 = pin_FLASH_D0, + .pin_d1 = pin_FLASH_D1, + .pin_d2 = pin_FLASH_D2, + .pin_d3 = pin_FLASH_D3, + .pin_d4 = pin_FLASH_D4, + .pin_d5 = pin_FLASH_D5, + .pin_d6 = pin_FLASH_D6, + .pin_d7 = pin_FLASH_D7, +}; + +const ospi_flash_settings_t ospi_flash_settings[] = { + { + .jedec_id = 0x3980c2, + .freq_hz = 100000000, + .read_dummy_cycles = 10, + OSPI_FLASH_SETTINGS_MX25, + }, + { + .jedec_id = 0x195b9d, + .freq_hz = 100000000, + .read_dummy_cycles = 9, + OSPI_FLASH_SETTINGS_IS25, + }, + { + .jedec_id = 0x17bb6b, + .freq_hz = 100000000, + .read_dummy_cycles = 7, + OSPI_FLASH_SETTINGS_EM, + }, +}; +const size_t ospi_flash_settings_len = 3; + +void board_startup(void) { + // Switch the USB multiplexer to use the Alif USB port. + mp_hal_pin_output(pin_USB_D_SEL); + mp_hal_pin_high(pin_USB_D_SEL); +} + +void board_enter_bootloader(void) { + *((uint32_t *)OMV_BOOT_MAGIC_ADDR) = OMV_BOOT_MAGIC_VALUE; + NVIC_SystemReset(); +} + +void board_early_init(void) { + // Set default run profile + run_profile_t run_profile = { + .dcdc_mode = DCDC_MODE_PWM, + .dcdc_voltage = DCDC_VOUT_0825, + // CLK_SRC_LFRC or CLK_SRC_LFXO + .aon_clk_src = CLK_SRC_LFXO, + // CLK_SRC_HFRC, CLK_SRC_HFXO or CLK_SRC_PLL + .run_clk_src = CLK_SRC_PLL, + #if CORE_M55_HP + .cpu_clk_freq = CLOCK_FREQUENCY_400MHZ, + #else + .cpu_clk_freq = CLOCK_FREQUENCY_160MHZ, + #endif + .scaled_clk_freq = SCALED_FREQ_XO_HIGH_DIV_38_4_MHZ, + // AON, modem aon, SSE-700 AON, modem, SYSTOP, DEBUG, SE + .power_domains = PD_VBAT_AON_MASK | PD_SSE700_AON_MASK | PD_SYST_MASK | + PD_DBSS_MASK | PD_SESS_MASK | PD_SRAMS_MASK | PD_SRAM_CTRL_AON_MASK, + // Add all memories + .memory_blocks = SERAM_MASK | SRAM0_MASK | SRAM1_MASK | MRAM_MASK | BACKUP4K_MASK | + SRAM6A_MASK | SRAM6B_MASK | SRAM7_1_MASK | SRAM7_2_MASK | SRAM7_3_MASK | + SRAM8_MASK | SRAM9_MASK | FWRAM_MASK, + .phy_pwr_gating = LDO_PHY_MASK | USB_PHY_MASK | MIPI_TX_DPHY_MASK | MIPI_RX_DPHY_MASK | + MIPI_PLL_DPHY_MASK, + .vdd_ioflex_3V3 = IOFLEX_LEVEL_3V3, + }; + + if (se_services_set_run_profile(&run_profile)) { + MICROPY_BOARD_FATAL_ERROR("se_services_set_run_profile"); + } + + // Set default off profile + off_profile_t off_profile = { + .dcdc_mode = DCDC_MODE_PWM, + .dcdc_voltage = DCDC_VOUT_0825, + // CLK_SRC_LFRC or CLK_SRC_LFXO + .aon_clk_src = CLK_SRC_LFXO, + // CLK_SRC_HFRC, CLK_SRC_HFXO or CLK_SRC_PLL + .stby_clk_src = CLK_SRC_HFRC, + .stby_clk_freq = SCALED_FREQ_RC_STDBY_76_8_MHZ, + // Disable all power domains. + .power_domains = 0, + // Add all memories + .memory_blocks = SERAM_MASK | SRAM0_MASK | SRAM1_MASK | MRAM_MASK | BACKUP4K_MASK | + SRAM6A_MASK | SRAM6B_MASK | SRAM7_1_MASK | SRAM7_2_MASK | SRAM7_3_MASK | + SRAM8_MASK | SRAM9_MASK | FWRAM_MASK, + .phy_pwr_gating = LDO_PHY_MASK | USB_PHY_MASK | MIPI_TX_DPHY_MASK | MIPI_RX_DPHY_MASK | + MIPI_PLL_DPHY_MASK, + .vdd_ioflex_3V3 = IOFLEX_LEVEL_3V3, + .vtor_address = SCB->VTOR, + .vtor_address_ns = SCB->VTOR, + .ewic_cfg = EWIC_RTC_A, + .wakeup_events = WE_LPRTC, + }; + + if (se_services_set_off_profile(&off_profile)) { + MICROPY_BOARD_FATAL_ERROR("se_services_set_off_profile"); + } + + // Select PLL for PD4 memory. + if (se_services_select_pll_source(PLL_SOURCE_PLL, PLL_TARGET_PD4_SRAM)) { + MICROPY_BOARD_FATAL_ERROR("se_services_select_pll_source"); + } +} + +MP_WEAK void board_enter_stop(void) { + // Disable NPU interrupt + NVIC_DisableIRQ(NPU_IRQ_NUMBER); + NVIC_ClearPendingIRQ(NPU_IRQ_NUMBER); + + // Soft-reset NPU + NPU->RESET = 0x00000000; + + // Wait until reset + uint32_t data = 0; + do { + // Poll channel0 status registers + data = NPU->STATUS; + } while (data); + + // Set default value, enables off for clocks and power. + NPU->CMD = 0x0000000C; +} + +MP_WEAK void board_enter_standby(void) { + +} + +MP_WEAK void board_exit_standby(void) { + +} diff --git a/ports/alif/boards/OPENMV_AE3/board.ld.S b/ports/alif/boards/OPENMV_AE3/board.ld.S new file mode 100644 index 00000000000..0d09bb15f87 --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/board.ld.S @@ -0,0 +1,14 @@ +#include "mcu/ensemble.ld.S" + +/* Define ROMFS partition locations. */ +#if CORE_M55_HP +/* The HP core has access to the external OSPI flash and MRAM ROMFS partitions. */ +_micropy_hw_romfs_part0_start = 0xa1000000; +_micropy_hw_romfs_part0_size = 16M; +_micropy_hw_romfs_part1_start = ORIGIN(MRAM_FS); +_micropy_hw_romfs_part1_size = LENGTH(MRAM_FS); +#else +/* The HP core has access to the MRAM ROMFS partition. */ +_micropy_hw_romfs_part0_start = ORIGIN(MRAM_FS); +_micropy_hw_romfs_part0_size = LENGTH(MRAM_FS); +#endif diff --git a/ports/alif/boards/OPENMV_AE3/mpconfigboard.h b/ports/alif/boards/OPENMV_AE3/mpconfigboard.h new file mode 100644 index 00000000000..cbdbd063ed3 --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/mpconfigboard.h @@ -0,0 +1,85 @@ +#define MICROPY_HW_BOARD_NAME "OpenMV-AE3" +#define MICROPY_HW_MCU_NAME "AE302F80F55D5AE" + +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size +typedef intptr_t mp_off_t; + +#define MICROPY_HW_USB_MSC (CORE_M55_HP) +#define MICROPY_HW_ENABLE_HW_I2C (1) +#define MICROPY_HW_ENABLE_OSPI (CORE_M55_HP) + +// ROMFS partitions +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) +#define MICROPY_HW_ROMFS_ENABLE_PART1 (CORE_M55_HP) + +// I2C buses +#define MICROPY_HW_I2C1_SCL (pin_P0_5) +#define MICROPY_HW_I2C1_SDA (pin_P0_4) + +#define MICROPY_HW_I2C2_SCL (pin_P5_1) +#define MICROPY_HW_I2C2_SDA (pin_P5_0) + +#define MICROPY_HW_I2C3_SCL (pin_P1_1) +#define MICROPY_HW_I2C3_SDA (pin_P1_0) + +// SPI buses +#define MICROPY_HW_SPI0_MISO (pin_P5_0) +#define MICROPY_HW_SPI0_MOSI (pin_P5_1) +// #define MICROPY_HW_SPI0_NSS (pin_P5_2) +#define MICROPY_HW_SPI0_SCK (pin_P5_3) + +// UART buses +#define MICROPY_HW_UART1_TX (pin_P0_5) +#define MICROPY_HW_UART1_RX (pin_P0_4) +#define MICROPY_HW_UART3_TX (pin_P1_3) +#define MICROPY_HW_UART3_RX (pin_P1_2) +#define MICROPY_HW_UART3_RTS (pin_P7_3) +#define MICROPY_HW_UART3_CTS (pin_P7_2) +#define MICROPY_HW_UART4_TX (pin_P5_1) +#define MICROPY_HW_UART4_RX (pin_P5_0) +#define MICROPY_HW_UART5_TX (pin_P5_3) +#define MICROPY_HW_UART5_RX (pin_P5_2) + +#define MICROPY_HW_USB_VID 0x37C5 +#define MICROPY_HW_USB_PID 0x16E3 + +#define MICROPY_HW_USB_MANUFACTURER_STRING "OpenMV" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "OpenMV Camera" +#define MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING "OpenMV" + +extern void board_startup(void); +#define MICROPY_BOARD_STARTUP board_startup + +extern void board_early_init(void); +#define MICROPY_BOARD_EARLY_INIT board_early_init + +extern void board_enter_bootloader(void); +#define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) board_enter_bootloader() + +extern void board_enter_stop(void); +#define MICROPY_BOARD_ENTER_STOP board_enter_stop + +extern void board_enter_standby(void); +#define MICROPY_BOARD_ENTER_STANDBY board_enter_standby + +extern void board_exit_standby(void); +#define MICROPY_BOARD_EXIT_STANDBY board_exit_standby + +// This is used for alif.Flash() and USB MSC. +#define MICROPY_HW_FLASH_STORAGE_BASE_ADDR (0) +#define MICROPY_HW_FLASH_STORAGE_BYTES (32 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_FS_BYTES (16 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_ROMFS_BYTES (16 * 1024 * 1024) + +// Murata 1YN configuration +#define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/w43439_sdio_1yn_7_95_59_combined.h" +#define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1yn.h" +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_1yn.h" +#define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE (2000000) +#define CYW43_BT_UART_BAUDRATE_ACTIVE_USE (2000000) + +// Bluetooth config +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (115200) diff --git a/ports/alif/boards/OPENMV_AE3/mpconfigboard.mk b/ports/alif/boards/OPENMV_AE3/mpconfigboard.mk new file mode 100644 index 00000000000..83cc17dd15e --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/mpconfigboard.mk @@ -0,0 +1,22 @@ +# TODO: alif_ensemble-cmsis-dfp only supports AE722F80F55D5XX at the moment. +MCU_SERIES = E7 +MCU_VARIANT = AE722F80F55D5XX +JLINK_DEV = AE302F80F55D5_HP +LD_FILE = boards/OPENMV_AE3/board.ld.S +PORT = /dev/ttyUSB0 + +ALIF_TOOLKIT_CFG_PART = AE302F80F55D5AE +ALIF_TOOLKIT_CFG_FILE = \"app-device-config-ae3.json\" + +CORE_M55_HP := $(if $(filter M55_HP,$(MCU_CORE)),1,0) + +# MicroPython settings +MICROPY_FLOAT_IMPL = float +MICROPY_PY_BLUETOOTH = $(CORE_M55_HP) +MICROPY_BLUETOOTH_NIMBLE = $(CORE_M55_HP) +MICROPY_PY_LWIP = $(CORE_M55_HP) +MICROPY_PY_NETWORK_CYW43 = $(CORE_M55_HP) +MICROPY_PY_SSL = $(CORE_M55_HP) +MICROPY_SSL_MBEDTLS = $(CORE_M55_HP) +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 diff --git a/ports/alif/boards/OPENMV_AE3/ospi_xip_user.h b/ports/alif/boards/OPENMV_AE3/ospi_xip_user.h new file mode 100644 index 00000000000..c0bd9afeb5c --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/ospi_xip_user.h @@ -0,0 +1,6 @@ +// This file is needed by ospi_xip/source/ospi/ospi_drv.c. +#define OSPI_XIP_ENABLE_AES_DECRYPTION (0) +#define OSPI_XIP_RX_SAMPLE_DELAY (4) +#define OSPI_XIP_DDR_DRIVE_EDGE (0) +// floor(1/4 OSPI clock cycle + 3.6ns) * 2 +#define OSPI_XIP_RXDS_DELAY (12) diff --git a/ports/alif/boards/OPENMV_AE3/pins.csv b/ports/alif/boards/OPENMV_AE3/pins.csv new file mode 100644 index 00000000000..ab4f1a34f7e --- /dev/null +++ b/ports/alif/boards/OPENMV_AE3/pins.csv @@ -0,0 +1,69 @@ +# USB multiplexer +USB_D_SEL,P15_0 +USB_D_SEL_FB,P15_1 + +# LEDs +LED_RED,P0_0 +LED_GREEN,P6_3 +LED_BLUE,P6_0 + +# User switch +USR_SW,P15_7 + +# Flash on OSPI0 +FLASH_RESET,P3_3 +FLASH_CS,P3_2 +FLASH_SCLK_P,P3_0 +FLASH_SCLK_N,P3_1 +FLASH_DQSM,P1_6 +FLASH_D0,P2_0 +FLASH_D1,P2_1 +FLASH_D2,P2_2 +FLASH_D3,P2_3 +FLASH_D4,P2_4 +FLASH_D5,P2_5 +FLASH_D6,P2_6 +FLASH_D7,P2_7 + +# Murata 1YN +BT_UART_RX,P1_4 +BT_UART_TX,P1_5 +BT_UART_CTS,P6_6 +BT_UART_RTS,P6_7 +BT_REG_ON,P5_7 +BT_DEV_WAKE,P9_2 +BT_HOST_WAKE,P13_3 +WL_REG_ON,P10_4 +WL_HOST_WAKE,P11_0 +WL_I2S_SDI,P12_0 +WL_I2S_SDO,P12_1 +WL_I2S_SCLK,P12_2 +WL_I2S_WS,P12_3 +WL_IRQ,P9_6 +WL_MISO,P12_4 +WL_MOSI,P12_5 +WL_SCLK,P12_6 +WL_CS,P12_7 + +P0,P5_1 +P1,P5_0 +P2,P5_3 +P3,P5_2 +P4,P0_5 +P5,P0_4 +P6,P7_2 +P7,P7_3 +P8,P1_2 +P9,P1_3 + +# UART buses +UART1_TX,P0_5 +UART1_RX,P0_4 +UART3_TX,P1_3 +UART3_RX,P1_2 +UART3_RTS,P7_3 +UART3_CTS,P7_2 +UART4_TX,P5_1 +UART4_RX,P5_0 +UART5_TX,P5_3 +UART5_RX,P5_2 From 547207ddc889a395f831a45f508d676aff197e17 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 20 Feb 2025 12:24:19 +0100 Subject: [PATCH 0527/2098] github/workflows: Add Alif port to CI. Signed-off-by: iabdalkader --- .github/workflows/ports_alif.yml | 33 ++++++++++++++++++++++++++++++++ tools/ci.sh | 15 +++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/workflows/ports_alif.yml diff --git a/.github/workflows/ports_alif.yml b/.github/workflows/ports_alif.yml new file mode 100644 index 00000000000..0e96e7d816e --- /dev/null +++ b/.github/workflows/ports_alif.yml @@ -0,0 +1,33 @@ +name: alif port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'shared/**' + - 'lib/**' + - 'drivers/**' + - 'ports/alif/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_alif: + strategy: + fail-fast: false + matrix: + ci_func: # names are functions in ci.sh + - alif_ae3_build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_alif_setup + - name: Build ci_${{matrix.ci_func }} + run: source tools/ci.sh && ci_${{ matrix.ci_func }} diff --git a/tools/ci.sh b/tools/ci.sh index 948eeeaef2d..cfc9754837f 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -871,3 +871,18 @@ function ci_zephyr_run_tests { # - inf_nan_arith fails pow(-1, nan) test (cd tests && ./run-tests.py -t 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) } + +######################################################################################## +# ports/alif + +function ci_alif_setup { + ci_gcc_arm_setup +} + +function ci_alif_ae3_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HP submodules + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HE submodules + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_DUAL + make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL +} From 2ad592530256ebfdab6459e918748b729a3de7de Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Oct 2024 13:31:57 +1100 Subject: [PATCH 0528/2098] tests/ports/alif_hardware: Add flash testing script. This test is not intended to be run automatically and does not have a corresponding .exp file. Signed-off-by: Damien George --- tests/ports/alif_hardware/flash_test.py | 149 ++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/ports/alif_hardware/flash_test.py diff --git a/tests/ports/alif_hardware/flash_test.py b/tests/ports/alif_hardware/flash_test.py new file mode 100644 index 00000000000..5be3fc2d02f --- /dev/null +++ b/tests/ports/alif_hardware/flash_test.py @@ -0,0 +1,149 @@ +# MIT license; Copyright (c) 2024 OpenMV LLC. +# +# Note: this test completely erases the filesystem! +# It is intended to be run manually. + +import alif +import hashlib +import os +import sys +import time +import vfs + +hash_algo = "sha256" + + +def flash_make_filesystem(): + try: + vfs.umount("/flash") + except: + pass + bdev = alif.Flash() + vfs.VfsFat.mkfs(bdev) + vfs.mount(vfs.VfsFat(bdev), "/flash") + sys.path.append("/flash") + sys.path.append("/flash/lib") + os.chdir("/flash") + + +def flash_block_test(): + try: + vfs.umount("/flash") + except: + pass + dev = alif.Flash() + + data512 = os.urandom(512) + buf512 = bytearray(512) + block_numbers = tuple(range(32)) + tuple(range(250, 266)) + + print("Block read/write integrity: ", end="") + ok = True + for block_n in block_numbers: + dev.writeblocks(block_n, data512) + dev.readblocks(block_n, buf512) + if buf512 != data512: + ok = False + print(ok) + + print("Block read back integrity: ", end="") + ok = True + for block_n in block_numbers: + dev.readblocks(block_n, buf512) + if buf512 != data512: + ok = False + print(ok) + + N = 16 * 1024 + data_big = os.urandom(N) + t0 = time.ticks_us() + dev.writeblocks(0, data_big) + dt = time.ticks_diff(time.ticks_us(), t0) + print(f"Block write speed: {len(data_big) / 1024 / dt * 1_000_000} KiB/sec") + + buf_big = bytearray(N) + t0 = time.ticks_us() + dev.readblocks(0, buf_big) + dt = time.ticks_diff(time.ticks_us(), t0) + print(f"Block read speed: {len(buf_big) / 1024 / dt * 1_000_000} KiB/sec") + + if buf_big != data_big: + raise RuntimeError("big block read-back failed") + + try: + import uctypes + + xip = memoryview(dev) + except: + xip = None + if xip is not None: + t0 = time.ticks_us() + buf_big[:] = xip[: len(buf_big)] + dt = time.ticks_diff(time.ticks_us(), t0) + print(f"XIP read speed: {len(buf_big) / 1024 / dt * 1_000_000} KiB/sec") + + if buf_big != data_big: + raise RuntimeError("XIP read-back failed") + for i in range(len(buf_big)): + if xip[i] != data_big[i]: + raise RuntimeError("XIP byte-wise read-back failed") + + +def flash_write_verify(path, size=1024 * 1024, block_size=1024): + hash_sum = getattr(hashlib, hash_algo)() + block_count = size // block_size + + print("-" * 16) + print(f"Writing file {size=} {block_size=}") + t0 = time.ticks_ms() + total_size = 0 + with open(path, "wb") as file: + for i in range(block_count): + buf = os.urandom(block_size) + # Update digest + hash_sum.update(buf) + total_size += file.write(buf) + if i % (block_count // 16) == 0: + print(f"{i}/{block_count}", end="\r") + dt = time.ticks_diff(time.ticks_ms(), t0) + print(f"Flash write finished: {total_size / 1024 / dt * 1000} KiB/sec") + digest = hash_sum.digest() + + print("Reading file... ", end="") + hash_sum = getattr(hashlib, hash_algo)() + buf = bytearray(block_size) + t0 = time.ticks_ms() + total_size = 0 + with open(path, "rb") as file: + for i in range(block_count): + total_size += file.readinto(buf) + dt = time.ticks_diff(time.ticks_ms(), t0) + print(f"finished: {total_size / 1024 / dt * 1000} KiB/sec") + + print("Verifying file... ", end="") + hash_sum = getattr(hashlib, hash_algo)() + t0 = time.ticks_ms() + total_size = 0 + with open(path, "rb") as file: + for i in range(block_count): + buf = file.read(block_size) + total_size += len(buf) + # Update digest + hash_sum.update(buf) + dt = time.ticks_diff(time.ticks_ms(), t0) + print(f"finished: {total_size / 1024 / dt * 1000} KiB/sec; ", end="") + + if digest != hash_sum.digest(): + raise RuntimeError(f"{hash_algo} checksum verify failed") + + print(f"{hash_algo} checksum verified") + + +if __name__ == "__main__": + flash_block_test() + flash_make_filesystem() + flash_write_verify("test0.bin", size=64 * 1024, block_size=1024) + flash_write_verify("test1.bin", size=128 * 1024, block_size=1024) + flash_write_verify("test2.bin", size=64 * 1024, block_size=2048) + flash_write_verify("test4.bin", size=256 * 1024, block_size=4096) + flash_write_verify("test4.bin", size=512 * 1024, block_size=16384) From 037f2dad72a7d11d461330873b50bb9ccf4fed69 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 17 Mar 2025 13:18:20 +1100 Subject: [PATCH 0529/2098] tests: Update UART and SPI tests to work on Alif boards. Signed-off-by: Damien George --- tests/extmod/machine_spi_rate.py | 5 ++++- tests/extmod/machine_uart_irq_txidle.py | 5 ++++- tests/extmod/machine_uart_tx.py | 6 +++++- tests/extmod_hardware/machine_uart_irq_rx.py | 6 +++++- tests/extmod_hardware/machine_uart_irq_rxidle.py | 6 +++++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index 7022955a3da..c65095f22a1 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -13,7 +13,10 @@ # Configure pins based on the port/board details. # Values are tuples of (spi_id, sck, mosi, miso) -if "pyboard" in sys.platform: +if "alif" in sys.platform: + MAX_DELTA_MS = 20 + spi_instances = ((0, None, None, None),) +elif "pyboard" in sys.platform: spi_instances = ( (1, None, None, None), # "explicit choice of sck/mosi/miso is not implemented" (2, None, None, None), diff --git a/tests/extmod/machine_uart_irq_txidle.py b/tests/extmod/machine_uart_irq_txidle.py index feb95fabaa4..084e9825768 100644 --- a/tests/extmod/machine_uart_irq_txidle.py +++ b/tests/extmod/machine_uart_irq_txidle.py @@ -12,7 +12,10 @@ import time, sys # Configure pins based on the target. -if "rp2" in sys.platform: +if "alif" in sys.platform: + uart_id = 1 + tx_pin = None +elif "rp2" in sys.platform: uart_id = 0 tx_pin = "GPIO0" rx_pin = "GPIO1" diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index e9652f3c7ac..f0cc912da66 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -14,7 +14,11 @@ timing_margin_us = 100 # Configure pins based on the target. -if "esp32" in sys.platform: +if "alif" in sys.platform: + uart_id = 1 + pins = {} + bit_margin = 1 +elif "esp32" in sys.platform: uart_id = 1 pins = {} timing_margin_us = 400 diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py index bf34900bd08..ecc95e62ae5 100644 --- a/tests/extmod_hardware/machine_uart_irq_rx.py +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -15,7 +15,11 @@ byte_by_byte = False # Configure pins based on the target. -if "esp32" in sys.platform: +if "alif" in sys.platform: + uart_id = 1 + tx_pin = None + rx_pin = None +elif "esp32" in sys.platform: uart_id = 1 tx_pin = 4 rx_pin = 5 diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index 182ab24ebe0..af2412c75ee 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -14,7 +14,11 @@ import time, sys # Configure pins based on the target. -if "esp32" in sys.platform: +if "alif" in sys.platform: + uart_id = 1 + tx_pin = None + rx_pin = None +elif "esp32" in sys.platform: uart_id = 1 tx_pin = 4 rx_pin = 5 From 1aa9b3d94bd66a625173b6182df8a5308279b6d0 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 7 Apr 2025 23:01:06 +0200 Subject: [PATCH 0530/2098] tools/mpremote: Add recursive remove functionality to filesystem cmds. mpremote now supports `mpremote rm -r`. Addresses #9802 and #16845. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/commands.py | 28 +++++++++++++++++++++++++++- tools/mpremote/mpremote/main.py | 2 +- tools/mpremote/mpremote/transport.py | 6 +++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index e385d050927..690b2ea723e 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -1,4 +1,5 @@ import binascii +import errno import hashlib import os import sys @@ -300,6 +301,28 @@ def _mkdir(a, *b): do_filesystem_cp(state, src_path_joined, dest_path_joined, False, check_hash) +def do_filesystem_recursive_rm(state, path, args): + if state.transport.fs_isdir(path): + for entry in state.transport.fs_listdir(path): + do_filesystem_recursive_rm(state, _remote_path_join(path, entry.name), args) + if path: + try: + state.transport.fs_rmdir(path) + if args.verbose: + print(f"removed directory: '{path}'") + except OSError as e: + if e.errno != errno.EINVAL: # not vfs mountpoint + raise CommandError( + f"rm -r: cannot remove :{path} {os.strerror(e.errno) if e.errno else ''}" + ) from e + if args.verbose: + print(f"skipped: '{path}' (vfs mountpoint)") + else: + state.transport.fs_rmfile(path) + if args.verbose: + print(f"removed: '{path}'") + + def do_filesystem(state, args): state.ensure_raw_repl() state.did_action() @@ -352,7 +375,10 @@ def do_filesystem(state, args): elif command == "mkdir": state.transport.fs_mkdir(path) elif command == "rm": - state.transport.fs_rmfile(path) + if args.recursive: + do_filesystem_recursive_rm(state, path, args) + else: + state.transport.fs_rmfile(path) elif command == "rmdir": state.transport.fs_rmdir(path) elif command == "touch": diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index bdae8f16365..b30a1a21354 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -182,7 +182,7 @@ def argparse_rtc(): def argparse_filesystem(): cmd_parser = argparse.ArgumentParser(description="execute filesystem commands on the device") - _bool_flag(cmd_parser, "recursive", "r", False, "recursive copy (for cp command only)") + _bool_flag(cmd_parser, "recursive", "r", False, "recursive (for cp and rm commands)") _bool_flag( cmd_parser, "force", diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index c8fdc54a889..8d30c7f517f 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -24,7 +24,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import ast, hashlib, os, sys +import ast, errno, hashlib, os, sys from collections import namedtuple @@ -63,6 +63,10 @@ def _convert_filesystem_error(e, info): return FileExistsError(info) if "OSError" in e.error_output and "ENODEV" in e.error_output: return FileNotFoundError(info) + if "OSError" in e.error_output and "EINVAL" in e.error_output: + return OSError(errno.EINVAL, info) + if "OSError" in e.error_output and "EPERM" in e.error_output: + return OSError(errno.EPERM, info) return e From 72d4c409418b2d35be41b7b04f1802457309bc6d Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 7 Apr 2025 23:01:21 +0200 Subject: [PATCH 0531/2098] tools/mpremote/tests: Add tests for mpremote rm -r. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/test_filesystem.sh | 64 +++++++++++++++++ tools/mpremote/tests/test_filesystem.sh.exp | 76 +++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index afeb7c91da8..a20d77dfea3 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -170,3 +170,67 @@ EOF $MPREMOTE resume cp -r "${TMP}/package" : $MPREMOTE resume ls : :package :package/subpackage $MPREMOTE resume exec "import package; package.x(); package.y()" + +echo ----- +# Test rm -r functionality +# start with a fresh ramdisk before each test +# rm -r MCU current working directory +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume touch :a.py +$MPREMOTE resume touch :b.py +$MPREMOTE resume cp -r "${TMP}/package" : +$MPREMOTE resume rm -r -v : +$MPREMOTE resume ls : +$MPREMOTE resume ls :/ramdisk + +echo ----- +# rm -r relative subfolder +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume touch :a.py +$MPREMOTE resume mkdir :testdir +$MPREMOTE resume cp -r "${TMP}/package" :testdir/package +$MPREMOTE resume ls :testdir +$MPREMOTE resume ls :testdir/package +$MPREMOTE resume rm -r :testdir/package +$MPREMOTE resume ls :/ramdisk +$MPREMOTE resume ls :testdir + +echo ----- +# rm -r non-existent path +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume ls : +$MPREMOTE resume rm -r :nonexistent || echo "expect error" + +echo ----- +# rm -r absolute root +# no -v to generate same output on stm32 and other ports +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume touch :a.py +$MPREMOTE resume touch :b.py +$MPREMOTE resume cp -r "${TMP}/package" : +$MPREMOTE resume cp -r "${TMP}/package" :package2 +$MPREMOTE resume rm -r :/ || echo "expect error" +$MPREMOTE resume ls : +$MPREMOTE resume ls :/ramdisk + +echo ----- +# rm -r relative mountpoint +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume touch :a.py +$MPREMOTE resume touch :b.py +$MPREMOTE resume cp -r "${TMP}/package" : +$MPREMOTE resume exec "import os;os.chdir('/')" +$MPREMOTE resume rm -r -v :ramdisk +$MPREMOTE resume ls :/ramdisk + +echo ----- +# rm -r absolute mountpoint +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume touch :a.py +$MPREMOTE resume touch :b.py +$MPREMOTE resume cp -r "${TMP}/package" : +$MPREMOTE resume exec "import os;os.chdir('/')" +$MPREMOTE resume rm -r -v :/ramdisk +$MPREMOTE resume ls :/ramdisk + +echo ----- \ No newline at end of file diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 82fe7d6bf78..16d98c4ded8 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -191,3 +191,79 @@ ls :package/subpackage 23 y.py x y2 +----- +touch :a.py +touch :b.py +cp ${TMP}/package : +rm : +removed: './a.py' +removed: './b.py' +removed: './package/subpackage/__init__.py' +removed: './package/subpackage/y.py' +removed directory: './package/subpackage' +removed: './package/__init__.py' +removed: './package/x.py' +removed directory: './package' +ls : +ls :/ramdisk +----- +touch :a.py +mkdir :testdir +cp ${TMP}/package :testdir/package +ls :testdir + 0 package/ +ls :testdir/package + 0 subpackage/ + 43 __init__.py + 22 x.py +rm :testdir/package +ls :/ramdisk + 0 a.py + 0 testdir/ +ls :testdir +----- +ls : +rm :nonexistent +mpremote: rm: nonexistent: No such file or directory. +expect error +----- +touch :a.py +touch :b.py +cp ${TMP}/package : +cp ${TMP}/package :package2 +rm :/ +mpremote: rm -r: cannot remove :/ Operation not permitted +expect error +ls : +ls :/ramdisk +----- +touch :a.py +touch :b.py +cp ${TMP}/package : +rm :ramdisk +removed: 'ramdisk/a.py' +removed: 'ramdisk/b.py' +removed: 'ramdisk/package/subpackage/__init__.py' +removed: 'ramdisk/package/subpackage/y.py' +removed directory: 'ramdisk/package/subpackage' +removed: 'ramdisk/package/__init__.py' +removed: 'ramdisk/package/x.py' +removed directory: 'ramdisk/package' +skipped: 'ramdisk' (vfs mountpoint) +ls :/ramdisk +----- +touch :a.py +touch :b.py +cp ${TMP}/package : +rm :/ramdisk +removed: '/ramdisk/a.py' +removed: '/ramdisk/b.py' +removed: '/ramdisk/package/subpackage/__init__.py' +removed: '/ramdisk/package/subpackage/y.py' +removed directory: '/ramdisk/package/subpackage' +removed: '/ramdisk/package/__init__.py' +removed: '/ramdisk/package/x.py' +removed directory: '/ramdisk/package' +skipped: '/ramdisk' (vfs mountpoint) +ls :/ramdisk +----- From ef8282c717be8363e9a04ebf2b9d843e2485604f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 7 Apr 2025 23:14:17 +0200 Subject: [PATCH 0532/2098] docs/reference/mpremote: Update docs for mpremote rm -r. Signed-off-by: Jos Verlinde --- docs/reference/mpremote.rst | 28 ++++++++++++++++++++++++---- tools/mpremote/tests/README.md | 15 +++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index ef23cd85c21..32ca5c246a7 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -229,7 +229,7 @@ The full list of supported commands are: - ``ls`` to list the current directory - ``ls `` to list the given directories - ``cp [-rf] `` to copy files - - ``rm `` to remove files on the device + - ``rm [-r] `` to remove files or folders on the device - ``mkdir `` to create directories on the device - ``rmdir `` to remove directories on the device - ``touch `` to create the files (if they don't already exist) @@ -238,15 +238,35 @@ The full list of supported commands are: The ``cp`` command uses a convention where a leading ``:`` represents a remote path. Without a leading ``:`` means a local path. This is based on the convention used by the `Secure Copy Protocol (scp) client - `_. All other commands - implicitly assume the path is a remote path, but the ``:`` can be optionally - used for clarity. + `_. So for example, ``mpremote fs cp main.py :main.py`` copies ``main.py`` from the current local directory to the remote filesystem, whereas ``mpremote fs cp :main.py main.py`` copies ``main.py`` from the device back to the current directory. + The ``mpremote rm -r`` command accepts both relative and absolute paths. + Use ``:`` to refer to the current remote working directory (cwd) to allow a + directory tree to be removed from the device's default path (eg ``/flash``, ``/``). + Use ``-v/--verbose`` to see the files being removed. + + For example: + + - ``mpremote rm -r :libs`` will remove the ``libs`` directory and all its + child items from the device. + - ``mpremote rm -rv :/sd`` will remove all files from a mounted SDCard and result + in a non-blocking warning. The mount will be retained. + - ``mpremote rm -rv :/`` will remove all files on the device, including any + located in mounted vfs such as ``/sd`` or ``/flash``. After removing all folders + and files, this will also return an error to mimic unix ``rm -rf /`` behaviour. + + .. warning:: + There is no supported way to undelete files removed by ``mpremote rm -r :``. + Please use with caution. + + All other commands implicitly assume the path is a remote path, but the ``:`` + can be optionally used for clarity. + All of the filesystem sub-commands take multiple path arguments, so if there is another command in the sequence, you must use ``+`` to terminate the arguments, e.g. diff --git a/tools/mpremote/tests/README.md b/tools/mpremote/tests/README.md index 5b924d2fb87..d1d77f1fb52 100644 --- a/tools/mpremote/tests/README.md +++ b/tools/mpremote/tests/README.md @@ -4,11 +4,26 @@ This directory contains a set of tests for `mpremote`. Requirements: - A device running MicroPython connected to a serial port on the host. +- The device you are testing against must be flashed with a firmware of the same build + as `mpremote`. +- If the device has an SDcard or other vfs mounted, the vfs's filesystem must be empty + to pass the filesystem test. - Python 3.x, `bash` and various Unix tools such as `find`, `mktemp`, `sed`, `sort`, `tr`. +- To test on Windows, you can either: + - Run the (Linux) tests in WSL2 against a USB device that is passed though to WSL2. + - Use the `Git Bash` terminal to run the tests against a device connected to a COM + port. _Note:_ While the tests will run in `Git Bash`, several will throw false + positive errors due to differences in the way that TMP files are logged and and + several other details. To run the tests do: + $ cd tools/mpremote/tests $ ./run-mpremote-tests.sh +To run a single test do: + + $ ./run-mpremote-tests.sh test_filesystem.sh + Each test should print "OK" if it passed. Otherwise it will print "CRASH", or "FAIL" and a diff of the expected and actual test output. From e7edf0783e945f4107dcc7f68543ed05e92189a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 9 Apr 2025 21:30:17 +1000 Subject: [PATCH 0533/2098] drivers/memory/spiflash: Allow a board/port to configure chip params. This commit allows the user of this driver to dynamically configure the SPI flash chip parameters. For this, enable `MICROPY_HW_SPIFLASH_CHIP_PARAMS` and then set the `mp_spiflash_t::chip_params` element to point to a valid `mp_spiflash_chip_params_t` struct. Signed-off-by: Damien George --- drivers/memory/spiflash.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/memory/spiflash.h b/drivers/memory/spiflash.h index edd7d49330d..a22742fc6f2 100644 --- a/drivers/memory/spiflash.h +++ b/drivers/memory/spiflash.h @@ -29,6 +29,11 @@ #include "drivers/bus/spi.h" #include "drivers/bus/qspi.h" +// Whether to enable dynamic configuration of SPI flash through mp_spiflash_chip_params_t. +#ifndef MICROPY_HW_SPIFLASH_CHIP_PARAMS +#define MICROPY_HW_SPIFLASH_CHIP_PARAMS (0) +#endif + #define MP_SPIFLASH_ERASE_BLOCK_SIZE (4096) // must be a power of 2 enum { @@ -66,8 +71,20 @@ typedef struct _mp_spiflash_config_t { #endif } mp_spiflash_config_t; +#if MICROPY_HW_SPIFLASH_CHIP_PARAMS +typedef struct _mp_spiflash_chip_params_t { + uint32_t jedec_id; + uint8_t memory_size_bytes_log2; + uint8_t qspi_prescaler; + uint8_t qread_num_dummy; +} mp_spiflash_chip_params_t; +#endif + typedef struct _mp_spiflash_t { const mp_spiflash_config_t *config; + #if MICROPY_HW_SPIFLASH_CHIP_PARAMS + const mp_spiflash_chip_params_t *chip_params; + #endif volatile uint32_t flags; } mp_spiflash_t; From b078569cffb4b32585806b42ef7a0501bc4d0bca Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Apr 2025 12:47:48 +1100 Subject: [PATCH 0534/2098] drivers/memory/spiflash: Allow a board/port to detect SPI flash. This commit allows the user of this driver to intercept the SPI flash initialisation routine and possibly take some action based on the JEDEC id, for example change the `mp_spiflash_t::chip_params` element. To do this, enable `MICROPY_HW_SPIFLASH_DETECT_DEVICE` and define a function called `mp_spiflash_detect()`. Signed-off-by: Damien George --- drivers/memory/spiflash.c | 12 +++++++++--- drivers/memory/spiflash.h | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index 09ab7157b88..b4133fe2f06 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -31,6 +31,10 @@ #include "py/mphal.h" #include "drivers/memory/spiflash.h" +#if defined(CHECK_DEVID) +#error "CHECK_DEVID no longer supported, use MICROPY_HW_SPIFLASH_DETECT_DEVICE instead" +#endif + #define QSPI_QE_MASK (0x02) #define USE_WR_DELAY (1) @@ -198,11 +202,13 @@ void mp_spiflash_init(mp_spiflash_t *self) { mp_hal_delay_ms(1); #endif - #if defined(CHECK_DEVID) - // Validate device id + #if MICROPY_HW_SPIFLASH_DETECT_DEVICE + // Attempt to detect SPI flash based on its JEDEC id. uint32_t devid; int ret = mp_spiflash_read_cmd(self, CMD_RD_DEVID, 3, &devid); - if (ret != 0 || devid != CHECK_DEVID) { + ret = mp_spiflash_detect(self, ret, devid); + if (ret != 0) { + // Could not read device id. mp_spiflash_release_bus(self); return; } diff --git a/drivers/memory/spiflash.h b/drivers/memory/spiflash.h index a22742fc6f2..d98047c89f0 100644 --- a/drivers/memory/spiflash.h +++ b/drivers/memory/spiflash.h @@ -34,6 +34,11 @@ #define MICROPY_HW_SPIFLASH_CHIP_PARAMS (0) #endif +// Whether to enable detection of SPI flash during initialisation. +#ifndef MICROPY_HW_SPIFLASH_DETECT_DEVICE +#define MICROPY_HW_SPIFLASH_DETECT_DEVICE (0) +#endif + #define MP_SPIFLASH_ERASE_BLOCK_SIZE (4096) // must be a power of 2 enum { @@ -91,6 +96,11 @@ typedef struct _mp_spiflash_t { void mp_spiflash_init(mp_spiflash_t *self); void mp_spiflash_deepsleep(mp_spiflash_t *self, int value); +#if MICROPY_HW_SPIFLASH_DETECT_DEVICE +// A board/port should define this function to perform actions based on the JEDEC id. +int mp_spiflash_detect(mp_spiflash_t *spiflash, int ret, uint32_t devid); +#endif + // These functions go direct to the SPI flash device int mp_spiflash_erase_block(mp_spiflash_t *self, uint32_t addr); int mp_spiflash_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest); From 2c0240e068b66b5f7b063125a7977cbca03d9483 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Apr 2025 12:47:40 +1100 Subject: [PATCH 0535/2098] drivers/bus/qspi: Make num_dummy configurable for quad reads. Signed-off-by: Damien George --- drivers/bus/qspi.h | 2 +- drivers/bus/softqspi.c | 6 +++--- drivers/memory/spiflash.c | 14 ++++++++++++-- ports/stm32/boards/STM32F769DISC/board_init.c | 4 +++- ports/stm32/octospi.c | 4 ++-- ports/stm32/qspi.c | 14 +++++++++----- ports/stm32/qspi.h | 2 +- 7 files changed, 31 insertions(+), 15 deletions(-) diff --git a/drivers/bus/qspi.h b/drivers/bus/qspi.h index 7ba2e750943..32b2890e3fc 100644 --- a/drivers/bus/qspi.h +++ b/drivers/bus/qspi.h @@ -45,7 +45,7 @@ typedef struct _mp_qspi_proto_t { int (*write_cmd_data)(void *self, uint8_t cmd, size_t len, uint32_t data); int (*write_cmd_addr_data)(void *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src); int (*read_cmd)(void *self, uint8_t cmd, size_t len, uint32_t *dest); - int (*read_cmd_qaddr_qdata)(void *self, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest); + int (*read_cmd_qaddr_qdata)(void *self, uint8_t cmd, uint32_t addr, uint8_t num_dummy, size_t len, uint8_t *dest); } mp_qspi_proto_t; typedef struct _mp_soft_qspi_obj_t { diff --git a/drivers/bus/softqspi.c b/drivers/bus/softqspi.c index 5cfc4db5e33..06dcd03b02d 100644 --- a/drivers/bus/softqspi.c +++ b/drivers/bus/softqspi.c @@ -189,13 +189,13 @@ static int mp_soft_qspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_ return 0; } -static int mp_soft_qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int mp_soft_qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, uint8_t num_dummy, size_t len, uint8_t *dest) { mp_soft_qspi_obj_t *self = (mp_soft_qspi_obj_t*)self_in; - uint8_t cmd_buf[7] = {cmd}; + uint8_t cmd_buf[16] = {cmd}; uint8_t addr_len = mp_spi_set_addr_buff(&cmd_buf[1], addr); CS_LOW(self); mp_soft_qspi_transfer(self, 1, cmd_buf, NULL); - mp_soft_qspi_qwrite(self, addr_len + 3, &cmd_buf[1]); // 3/4 addr bytes, 1 extra byte (0), 2 dummy bytes (4 dummy cycles) + mp_soft_qspi_qwrite(self, addr_len + 1 + num_dummy, &cmd_buf[1]); // 3/4 addr bytes, 1 extra byte (0), N dummy bytes (2*N dummy cycles) mp_soft_qspi_qread(self, len, dest); CS_HIGH(self); return 0; diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index b4133fe2f06..1ae0bbbc679 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -35,6 +35,14 @@ #error "CHECK_DEVID no longer supported, use MICROPY_HW_SPIFLASH_DETECT_DEVICE instead" #endif +// The default number of dummy bytes for quad-read is 2. This can be changed by enabling +// MICROPY_HW_SPIFLASH_CHIP_PARAMS and configuring the value in mp_spiflash_chip_params_t. +#if MICROPY_HW_SPIFLASH_CHIP_PARAMS +#define MICROPY_HW_SPIFLASH_QREAD_NUM_DUMMY(spiflash) (spiflash->chip_params->qread_num_dummy) +#else +#define MICROPY_HW_SPIFLASH_QREAD_NUM_DUMMY(spiflash) (2) +#endif + #define QSPI_QE_MASK (0x02) #define USE_WR_DELAY (1) @@ -115,7 +123,8 @@ static int mp_spiflash_transfer_cmd_addr_data(mp_spiflash_t *self, uint8_t cmd, mp_hal_pin_write(c->bus.u_spi.cs, 1); } else { if (dest != NULL) { - ret = c->bus.u_qspi.proto->read_cmd_qaddr_qdata(c->bus.u_qspi.data, cmd, addr, len, dest); + uint8_t num_dummy = MICROPY_HW_SPIFLASH_QREAD_NUM_DUMMY(self); + ret = c->bus.u_qspi.proto->read_cmd_qaddr_qdata(c->bus.u_qspi.data, cmd, addr, num_dummy, len, dest); } else { ret = c->bus.u_qspi.proto->write_cmd_addr_data(c->bus.u_qspi.data, cmd, addr, len, src); } @@ -186,7 +195,8 @@ void mp_spiflash_init(mp_spiflash_t *self) { mp_hal_pin_output(self->config->bus.u_spi.cs); self->config->bus.u_spi.proto->ioctl(self->config->bus.u_spi.data, MP_SPI_IOCTL_INIT); } else { - self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, 0); + uint8_t num_dummy = MICROPY_HW_SPIFLASH_QREAD_NUM_DUMMY(self); + self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, num_dummy); } mp_spiflash_acquire_bus(self); diff --git a/ports/stm32/boards/STM32F769DISC/board_init.c b/ports/stm32/boards/STM32F769DISC/board_init.c index 578dc9adb6c..001b18bc067 100644 --- a/ports/stm32/boards/STM32F769DISC/board_init.c +++ b/ports/stm32/boards/STM32F769DISC/board_init.c @@ -3,6 +3,8 @@ // This configuration is needed for mboot to be able to write to the external QSPI flash +#define QSPI_QREAD_NUM_DUMMY (2) + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE static mp_spiflash_cache_t spi_bdev_cache; #endif @@ -21,6 +23,6 @@ spi_bdev_t spi_bdev; // This init function is needed to memory map the QSPI flash early in the boot process void board_early_init(void) { - qspi_init(); + qspi_init(QSPI_QREAD_NUM_DUMMY); qspi_memory_map(); } diff --git a/ports/stm32/octospi.c b/ports/stm32/octospi.c index 345c4a23741..861941325c6 100644 --- a/ports/stm32/octospi.c +++ b/ports/stm32/octospi.c @@ -283,7 +283,7 @@ static int octospi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *de return 0; } -static int octospi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int octospi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, uint8_t num_dummy, size_t len, uint8_t *dest) { (void)self_in; #if defined(MICROPY_HW_OSPIFLASH_IO1) && !defined(MICROPY_HW_OSPIFLASH_IO2) && !defined(MICROPY_HW_OSPIFLASH_IO4) @@ -293,7 +293,7 @@ static int octospi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t add uint32_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; uint32_t dmode = 2; // data on 2-lines uint32_t admode = 2; // address on 2-lines - uint32_t dcyc = 4; // 4 dummy cycles + uint32_t dcyc = 2 * num_dummy; // 2N dummy cycles if (cmd == 0xeb || cmd == 0xec) { // Convert to 2-line command. diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 7334ece4f21..1360d9b490d 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -62,6 +62,8 @@ #define QSPI_ADSIZE 2 #endif +static uint8_t qspi_num_dummy; + static inline void qspi_mpu_disable_all(void) { // Configure MPU to disable access to entire QSPI region, to prevent CPU // speculative execution from accessing this region and modifying QSPI registers. @@ -110,7 +112,9 @@ static inline void qspi_mpu_enable_mapped(void) { mpu_config_end(irq_state); } -void qspi_init(void) { +void qspi_init(uint8_t num_dummy) { + qspi_num_dummy = num_dummy; + qspi_mpu_disable_all(); // Configure pins @@ -158,7 +162,7 @@ void qspi_memory_map(void) { | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction | 3 << QUADSPI_CCR_FMODE_Pos // memory-mapped mode | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines - | 4 << QUADSPI_CCR_DCYC_Pos // 4 dummy cycles + | (2 * qspi_num_dummy) << QUADSPI_CCR_DCYC_Pos // 2N dummy cycles | 0 << QUADSPI_CCR_ABSIZE_Pos // 8-bit alternate byte | 3 << QUADSPI_CCR_ABMODE_Pos // alternate byte on 4 lines | QSPI_ADSIZE << QUADSPI_CCR_ADSIZE_Pos @@ -193,7 +197,7 @@ static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { (void)self_in; switch (cmd) { case MP_QSPI_IOCTL_INIT: - qspi_init(); + qspi_init(arg); break; case MP_QSPI_IOCTL_BUS_ACQUIRE: // Disable memory-mapped region during bus access @@ -369,7 +373,7 @@ static int qspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *dest) return 0; } -static int qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, uint8_t num_dummy, size_t len, uint8_t *dest) { (void)self_in; uint8_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; @@ -383,7 +387,7 @@ static int qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction | 1 << QUADSPI_CCR_FMODE_Pos // indirect read mode | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines - | 4 << QUADSPI_CCR_DCYC_Pos // 4 dummy cycles + | (2 * num_dummy) << QUADSPI_CCR_DCYC_Pos // 2N dummy cycles | 0 << QUADSPI_CCR_ABSIZE_Pos // 8-bit alternate byte | 3 << QUADSPI_CCR_ABMODE_Pos // alternate byte on 4 lines | adsize << QUADSPI_CCR_ADSIZE_Pos // 32 or 24-bit address size diff --git a/ports/stm32/qspi.h b/ports/stm32/qspi.h index a2d6b9f328d..c8a3181653d 100644 --- a/ports/stm32/qspi.h +++ b/ports/stm32/qspi.h @@ -33,7 +33,7 @@ extern const mp_qspi_proto_t qspi_proto; -void qspi_init(void); +void qspi_init(uint8_t num_dummy); void qspi_memory_map(void); void qspi_memory_map_exit(void); void qspi_memory_map_restart(void); From 1d83c8175680b911ab24ba834b7f8bcadaa5aa79 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Apr 2025 14:31:26 +1100 Subject: [PATCH 0536/2098] stm32/vfs_rom_ioctl: Allow ROMFS configuration to be dynamic. Options for a board to configure ROMFS are: - Leave ROMFS disabled, do nothing. - Enable by defining `MICROPY_HW_ROMFS_ENABLE_PARTx` to 1 and then in the linker script define `_micropy_hw_romfs_partX_start` and `_micropy_hw_romfs_partX_size`. - Enable by defining `MICROPY_HW_ROMFS_ENABLE_PARTx` to 1 and also define `MICROPY_HW_ROMFS_PARTx_START` and `MICROPY_HW_ROMFS_PARTx_SIZE` which can be arbitrary expressions (not necessarily static) Signed-off-by: Damien George --- ports/stm32/vfs_rom_ioctl.c | 41 ++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/ports/stm32/vfs_rom_ioctl.c b/ports/stm32/vfs_rom_ioctl.c index 1fa4408c537..7592aa22d62 100644 --- a/ports/stm32/vfs_rom_ioctl.c +++ b/ports/stm32/vfs_rom_ioctl.c @@ -36,28 +36,32 @@ #if MICROPY_VFS_ROM_IOCTL -#if MICROPY_HW_ROMFS_ENABLE_PART0 && !defined(MICROPY_HW_ROMFS_PART0_START) -#define MICROPY_HW_ROMFS_PART0_START (uintptr_t)(&_micropy_hw_romfs_part0_start) -#define MICROPY_HW_ROMFS_PART0_SIZE (uintptr_t)(&_micropy_hw_romfs_part0_size) +#if MICROPY_HW_ROMFS_ENABLE_PART0 && defined(MICROPY_HW_ROMFS_PART0_START) +#define ROMFS0_DYNAMIC (1) +static MP_DEFINE_MEMORYVIEW_OBJ(romfs0_obj, 'B', 0, (uintptr_t)-1, (void *)-1); +#elif MICROPY_HW_ROMFS_ENABLE_PART0 && !defined(MICROPY_HW_ROMFS_PART0_START) +#define ROMFS0_DYNAMIC (0) extern uint8_t _micropy_hw_romfs_part0_start; extern uint8_t _micropy_hw_romfs_part0_size; +static const MP_DEFINE_MEMORYVIEW_OBJ(romfs0_obj, 'B', 0, (uintptr_t)&_micropy_hw_romfs_part0_size, (void *)&_micropy_hw_romfs_part0_start); #endif -#if MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) -#define MICROPY_HW_ROMFS_PART1_START (uintptr_t)(&_micropy_hw_romfs_part1_start) -#define MICROPY_HW_ROMFS_PART1_SIZE (uintptr_t)(&_micropy_hw_romfs_part1_size) +#if MICROPY_HW_ROMFS_ENABLE_PART1 && defined(MICROPY_HW_ROMFS_PART1_START) +#define ROMFS1_DYNAMIC (1) +static MP_DEFINE_MEMORYVIEW_OBJ(romfs1_obj, 'B', 0, (uintptr_t)-1, (void *)-1); +#elif MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) +#define ROMFS1_DYNAMIC (0) extern uint8_t _micropy_hw_romfs_part1_start; extern uint8_t _micropy_hw_romfs_part1_size; +static const MP_DEFINE_MEMORYVIEW_OBJ(romfs1_obj, 'B', 0, (uintptr_t)&_micropy_hw_romfs_part1_size, (void *)&_micropy_hw_romfs_part1_start); #endif -#define ROMFS_MEMORYVIEW(base, size) {{&mp_type_memoryview}, 'B', 0, (size), (void *)(base)} - -static const mp_obj_array_t romfs_obj_table[] = { +static const mp_obj_array_t *romfs_obj_table[] = { #if MICROPY_HW_ROMFS_ENABLE_PART0 - ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART0_START, MICROPY_HW_ROMFS_PART0_SIZE), + &romfs0_obj, #endif #if MICROPY_HW_ROMFS_ENABLE_PART1 - ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART1_START, MICROPY_HW_ROMFS_PART1_SIZE), + &romfs1_obj, #endif }; @@ -76,7 +80,20 @@ mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); } - const mp_obj_array_t *romfs_obj = &romfs_obj_table[romfs_id]; + #if ROMFS0_DYNAMIC + if (romfs_id == 0) { + romfs0_obj.items = (void *)MICROPY_HW_ROMFS_PART0_START; + romfs0_obj.len = MICROPY_HW_ROMFS_PART0_SIZE; + } + #endif + #if ROMFS1_DYNAMIC + if (romfs_id == 1) { + romfs1_obj.items = (void *)MICROPY_HW_ROMFS_PART1_START; + romfs1_obj.len = MICROPY_HW_ROMFS_PART1_SIZE; + } + #endif + + const mp_obj_array_t *romfs_obj = romfs_obj_table[romfs_id]; uintptr_t romfs_base = (uintptr_t)romfs_obj->items; uintptr_t romfs_len = romfs_obj->len; From aa0945698b714c25f85b1234e0b03f34ac76c24c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 11:09:01 +1100 Subject: [PATCH 0537/2098] stm32/qspi: Allow SPI flash size to be decided at runtime. Allows `MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2` and `MICROPY_HW_QSPI_MPU_REGION_SIZE` to be arbitrary expressions, eg function calls. The `storage.h` header needs to be included in case access to `spi_bdev_t` is needed by the macros. Signed-off-by: Damien George --- ports/stm32/qspi.c | 77 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 1360d9b490d..781aae803ef 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -31,6 +31,7 @@ #include "mpu.h" #include "qspi.h" #include "pin_static_af.h" +#include "storage.h" #if defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) @@ -50,18 +51,11 @@ #define MICROPY_HW_QSPI_CS_HIGH_CYCLES 2 // nCS stays high for 2 cycles #endif +// Region size in units of 1024*1024 bytes. #ifndef MICROPY_HW_QSPI_MPU_REGION_SIZE #define MICROPY_HW_QSPI_MPU_REGION_SIZE ((1 << (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3)) >> 20) #endif -#if (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) >= 24 -#define QSPI_CMD 0xec -#define QSPI_ADSIZE 3 -#else -#define QSPI_CMD 0xeb -#define QSPI_ADSIZE 2 -#endif - static uint8_t qspi_num_dummy; static inline void qspi_mpu_disable_all(void) { @@ -83,32 +77,32 @@ static inline void qspi_mpu_enable_mapped(void) { // other enabled region overlaps the disabled subregion, and the access is // unprivileged or the background region is disabled, the MPU issues a fault. uint32_t irq_state = mpu_config_start(); - #if MICROPY_HW_QSPI_MPU_REGION_SIZE > 128 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0xFF, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 64 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 32 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 16 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 8 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 4 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 2 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 1 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); - mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_16MB)); - #else - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); - mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_4MB)); - #endif + if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 128) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0xFF, MPU_REGION_SIZE_256MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 64) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_256MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 32) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_256MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 16) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 8) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 4) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_32MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 2) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); + } else if (MICROPY_HW_QSPI_MPU_REGION_SIZE > 1) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); + mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_16MB)); + } else { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); + mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_4MB)); + } mpu_config_end(irq_state); } @@ -155,6 +149,17 @@ void qspi_init(uint8_t num_dummy) { void qspi_memory_map(void) { // Enable memory-mapped mode + // Work out command to use for reads, based on size of the memory. + uint8_t cmd; + uint8_t adsize; + if ((MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) >= 24) { + cmd = 0xec; + adsize = 3; + } else { + cmd = 0xeb; + adsize = 2; + } + QUADSPI->ABR = 0; // disable continuous read mode QUADSPI->CCR = @@ -165,10 +170,10 @@ void qspi_memory_map(void) { | (2 * qspi_num_dummy) << QUADSPI_CCR_DCYC_Pos // 2N dummy cycles | 0 << QUADSPI_CCR_ABSIZE_Pos // 8-bit alternate byte | 3 << QUADSPI_CCR_ABMODE_Pos // alternate byte on 4 lines - | QSPI_ADSIZE << QUADSPI_CCR_ADSIZE_Pos + | adsize << QUADSPI_CCR_ADSIZE_Pos | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line - | QSPI_CMD << QUADSPI_CCR_INSTRUCTION_Pos + | cmd << QUADSPI_CCR_INSTRUCTION_Pos ; qspi_mpu_enable_mapped(); From de08190cb736f3bb1027d15bdf88135e49603c23 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Apr 2025 12:49:54 +1100 Subject: [PATCH 0538/2098] stm32/mboot: Allow USB strings to be dynamic. Signed-off-by: Damien George --- ports/stm32/mboot/main.c | 76 +++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 9bb2dcfcaf4..01f8892a514 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -429,38 +429,82 @@ void mp_hal_pin_config_speed(uint32_t port_pin, uint32_t speed) { || defined(STM32F723xx) \ || defined(STM32F732xx) \ || defined(STM32F733xx) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg" #elif defined(STM32F765xx) || defined(STM32F767xx) || defined(STM32F769xx) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*032Kg,01*128Kg,07*256Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/04*032Kg,01*128Kg,07*256Kg" #elif defined(STM32G0) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/256*02Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/256*02Kg" #elif defined(STM32H5) -#define FLASH_LAYOUT_TEMPLATE "@Internal Flash /0x08000000/???*08Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/???*08Kg" +#define INTERNAL_FLASH_LAYOUT_HAS_TEMPLATE (1) #elif defined(STM32H743xx) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/16*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/16*128Kg" #elif defined(STM32H750xx) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/01*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/01*128Kg" #elif defined(STM32WB) -#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/256*04Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/256*04Kg" #endif -#if !defined(FLASH_LAYOUT_STR) +#if INTERNAL_FLASH_LAYOUT_HAS_TEMPLATE \ + || defined(MBOOT_SPIFLASH_LAYOUT_DYNAMIC_MAX_LEN) \ + || defined(MBOOT_SPIFLASH2_LAYOUT_DYNAMIC_MAX_LEN) -#define FLASH_LAYOUT_STR_ALLOC (sizeof(FLASH_LAYOUT_TEMPLATE)) +#ifndef MBOOT_SPIFLASH_LAYOUT_DYNAMIC_MAX_LEN +#define MBOOT_SPIFLASH_LAYOUT_DYNAMIC_MAX_LEN (sizeof(MBOOT_SPIFLASH_LAYOUT) - 1) +#endif + +#ifndef MBOOT_SPIFLASH2_LAYOUT_DYNAMIC_MAX_LEN +#define MBOOT_SPIFLASH2_LAYOUT_DYNAMIC_MAX_LEN (sizeof(MBOOT_SPIFLASH2_LAYOUT) - 1) +#endif + +#define FLASH_LAYOUT_STR_ALLOC \ + ( \ + (sizeof(INTERNAL_FLASH_LAYOUT) - 1) \ + + MBOOT_SPIFLASH_LAYOUT_DYNAMIC_MAX_LEN \ + + MBOOT_SPIFLASH2_LAYOUT_DYNAMIC_MAX_LEN \ + + 1 \ + ) // Build the flash layout string from a template with total flash size inserted. -static size_t build_flash_layout_str(char *buf) { - size_t len = FLASH_LAYOUT_STR_ALLOC - 1; - memcpy(buf, FLASH_LAYOUT_TEMPLATE, len + 1); +static size_t build_flash_layout_str(uint8_t *buf) { + const char *internal_layout = INTERNAL_FLASH_LAYOUT; + size_t internal_layout_len = strlen(internal_layout); + + const char *spiflash_layout = MBOOT_SPIFLASH_LAYOUT; + size_t spiflash_layout_len = strlen(spiflash_layout); + + const char *spiflash2_layout = MBOOT_SPIFLASH2_LAYOUT; + size_t spiflash2_layout_len = strlen(spiflash2_layout); + + uint8_t *buf_orig = buf; + + memcpy(buf, internal_layout, internal_layout_len); + buf += internal_layout_len; + + #if INTERNAL_FLASH_LAYOUT_HAS_TEMPLATE unsigned int num_sectors = FLASH_SIZE / FLASH_SECTOR_SIZE; - buf += 31; // location of "???" in FLASH_LAYOUT_TEMPLATE + uint8_t *buf_size = buf_orig + 31; // location of "???" in FLASH_LAYOUT_TEMPLATE for (unsigned int i = 0; i < 3; ++i) { - *buf-- = '0' + num_sectors % 10; + *buf_size-- = '0' + num_sectors % 10; num_sectors /= 10; } - return len; + #endif + + memcpy(buf, spiflash_layout, spiflash_layout_len); + buf += spiflash_layout_len; + + memcpy(buf, spiflash2_layout, spiflash2_layout_len); + buf += spiflash2_layout_len; + + *buf++ = '\0'; + + return buf - buf_orig; } +#else + +#define FLASH_LAYOUT_STR INTERNAL_FLASH_LAYOUT MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT + #endif static bool flash_is_modifiable_addr_range(uint32_t addr, uint32_t len) { @@ -1188,7 +1232,7 @@ static uint8_t *pyb_usbdd_StrDescriptor(USBD_HandleTypeDef *pdev, uint8_t idx, u USBD_GetString((uint8_t *)FLASH_LAYOUT_STR, str_desc, length); #else { - char buf[FLASH_LAYOUT_STR_ALLOC]; + uint8_t buf[FLASH_LAYOUT_STR_ALLOC]; build_flash_layout_str(buf); USBD_GetString((uint8_t *)buf, str_desc, length); } From ed4833d495e1a328ef35edda1666799a9c84802c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Apr 2025 12:50:23 +1100 Subject: [PATCH 0539/2098] stm32/modmachine: Add SPI flash size to machine.info dump. Signed-off-by: Damien George --- ports/stm32/modmachine.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 7c1c4da60bf..620ae468cb9 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -247,6 +247,14 @@ static mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) { mp_printf(print, " 1=%u 2=%u m=%u\n", info.num_1block, info.num_2block, info.max_block); } + // SPI flash size + #if defined(MICROPY_HW_SPIFLASH_SIZE_BITS) + mp_printf(print, "SPI flash size: %d\n", MICROPY_HW_SPIFLASH_SIZE_BITS / 8); + #endif + #if defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) + mp_printf(print, "QSPI flash size: %d\n", 1 << (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3)); + #endif + // free space on flash { #if MICROPY_VFS_FAT From db854270719bfa4dda003d38893c1b8f39bf58de Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Apr 2025 12:48:48 +1100 Subject: [PATCH 0540/2098] stm32/boards/PYBD_SF6: Support boards with larger SPI flash. There are some newer PYBD_SF6 being produced which have a larger flash, namely two of 8MiB (instead of the older ones with two of 2MiB). This commit adds support for these boards. The idea is to have the same PYBD_SF6 firmware run on both old and new boards. That means autodetecting the flash at start-up and configuring all the relevant SPI/QSPI parameters, including for ROMFS and mboot. Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF6/board_init.c | 88 +++++++++++++++++++++ ports/stm32/boards/PYBD_SF6/f767.ld | 3 +- ports/stm32/boards/PYBD_SF6/mpconfigboard.h | 50 ++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/PYBD_SF6/board_init.c b/ports/stm32/boards/PYBD_SF6/board_init.c index c7a9f28006b..836c4db8b5d 100644 --- a/ports/stm32/boards/PYBD_SF6/board_init.c +++ b/ports/stm32/boards/PYBD_SF6/board_init.c @@ -1 +1,89 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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 +#include "boardctrl.h" +#include "qspi.h" + +#if BUILDING_MBOOT +#include "mboot/mboot.h" +#endif + +// Use PYBD_SF2 as base configuration. #include "boards/PYBD_SF2/board_init.c" + +// Adesto AT25SF161 16-MBit. +static const mp_spiflash_chip_params_t chip_params_at25sf161 = { + .jedec_id = 0x01861f, + .memory_size_bytes_log2 = 21, + .qspi_prescaler = 3, // maximum frequency 104MHz + .qread_num_dummy = 2, +}; + +// Infineon S25FL064 64-MBit. +static const mp_spiflash_chip_params_t chip_params_s25fl064 = { + .jedec_id = 0x176001, + .memory_size_bytes_log2 = 23, + .qspi_prescaler = 2, // maximum frequency 108MHz + .qread_num_dummy = 4, +}; + +// Selection of possible SPI flash chips. +static const mp_spiflash_chip_params_t *const chip_params_table[] = { + &chip_params_at25sf161, + &chip_params_s25fl064, +}; + +void board_early_init_sf6(void) { + // Initialise default SPI flash parameters. + MICROPY_BOARD_SPIFLASH_CHIP_PARAMS0 = &chip_params_at25sf161; + MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1 = &chip_params_at25sf161; + + // Continue with standard board early init. + board_early_init(); +} + +int mp_spiflash_detect(mp_spiflash_t *spiflash, int ret, uint32_t devid) { + if (ret != 0) { + // Could not identify flash. Succeed anyway using default chip parameters. + return 0; + } + + // Try to detect the SPI flash based on the JEDEC id. + for (size_t i = 0; i < MP_ARRAY_SIZE(chip_params_table); ++i) { + if (devid == chip_params_table[i]->jedec_id) { + spiflash->chip_params = chip_params_table[i]; + if (spiflash->config->bus_kind == MP_SPIFLASH_BUS_QSPI) { + // Reinitialise the QSPI but to set new size, prescaler and dummy bytes. + uint8_t num_dummy = spiflash->chip_params->qread_num_dummy; + spiflash->config->bus.u_qspi.proto->ioctl(spiflash->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, num_dummy); + } + break; + } + } + + return 0; +} diff --git a/ports/stm32/boards/PYBD_SF6/f767.ld b/ports/stm32/boards/PYBD_SF6/f767.ld index 0ee6cf76355..37fb3715d67 100644 --- a/ports/stm32/boards/PYBD_SF6/f767.ld +++ b/ports/stm32/boards/PYBD_SF6/f767.ld @@ -18,7 +18,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 2016K /* sectors 1-11 3x32K 1*128K 7*256K */ - FLASH_ROMFS (rx): ORIGIN = 0x90000000, LENGTH = 2048K /* external QSPI */ + FLASH_ROMFS (rx): ORIGIN = 0x90000000, LENGTH = 2048K /* external QSPI, at least 2MiB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512K /* DTCM=128k, SRAM1=368K, SRAM2=16K */ } @@ -39,6 +39,5 @@ _heap_end = _sstack; /* ROMFS location */ _micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); -_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); INCLUDE common_bl.ld diff --git a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h index 45261523bda..8cd1e52d3bd 100644 --- a/ports/stm32/boards/PYBD_SF6/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF6/mpconfigboard.h @@ -69,3 +69,53 @@ #define MICROPY_HW_ETH_RMII_TX_EN (pyb_pin_W8) #define MICROPY_HW_ETH_RMII_TXD0 (pyb_pin_W45) #define MICROPY_HW_ETH_RMII_TXD1 (pyb_pin_W49) + +// The below code reconfigures SPI flash for dynamic size detection. + +#undef MICROPY_BOARD_EARLY_INIT +#undef MICROPY_HW_SPIFLASH_SIZE_BITS +#undef MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 +#undef MBOOT_SPIFLASH_BYTE_SIZE +#undef MBOOT_SPIFLASH_LAYOUT +#undef MBOOT_SPIFLASH_ERASE_BLOCKS_PER_PAGE +#undef MBOOT_SPIFLASH2_BYTE_SIZE +#undef MBOOT_SPIFLASH2_LAYOUT +#undef MBOOT_SPIFLASH2_ERASE_BLOCKS_PER_PAGE + +// These are convenience macros to refer to the SPI flash chip parameters for the external SPI flash. +#define MICROPY_BOARD_SPIFLASH_CHIP_PARAMS0 (spi_bdev.spiflash.chip_params) // SPI flash #1, R/W storage +#define MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1 (spi_bdev2.spiflash.chip_params) // SPI flash #2, memory mapped + +// Early init is needed to initialise the default SPI flash chip parameters. +#define MICROPY_BOARD_EARLY_INIT board_early_init_sf6 + +// Enable dynamic detection of SPI flash. +#define MICROPY_HW_SPIFLASH_CHIP_PARAMS (1) +#define MICROPY_HW_SPIFLASH_DETECT_DEVICE (1) + +// Settings for SPI flash #1. +#define MICROPY_HW_SPIFLASH_SIZE_BITS (1 << (MICROPY_BOARD_SPIFLASH_CHIP_PARAMS0->memory_size_bytes_log2 + 3)) + +// Settings for SPI flash #2. +#define MICROPY_HW_QSPI_PRESCALER (MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1->qspi_prescaler) +#define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1->memory_size_bytes_log2 + 3) + +// ROMFS partition 0 is dynamically sized (SPI flash #2). +#define MICROPY_HW_ROMFS_PART0_START (uintptr_t)(&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_PART0_SIZE (1 << MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1->memory_size_bytes_log2) + +// Mboot SPI flash #1 configuration. +#define MBOOT_SPIFLASH_LAYOUT_DYNAMIC_MAX_LEN (20) +#define MBOOT_SPIFLASH_LAYOUT (MICROPY_BOARD_SPIFLASH_CHIP_PARAMS0->memory_size_bytes_log2 == 21 ? "/0x80000000/512*4Kg" : "/0x80000000/2048*4Kg") +#define MBOOT_SPIFLASH_BYTE_SIZE (1 << MICROPY_BOARD_SPIFLASH_CHIP_PARAMS0->memory_size_bytes_log2) +#define MBOOT_SPIFLASH_ERASE_BLOCKS_PER_PAGE (1) + +// Mboot SPI flash #2 configuration. +#define MBOOT_SPIFLASH2_LAYOUT_DYNAMIC_MAX_LEN (20) +#define MBOOT_SPIFLASH2_LAYOUT (MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1->memory_size_bytes_log2 == 21 ? "/0x90000000/512*4Kg" : "/0x90000000/2048*4Kg") +#define MBOOT_SPIFLASH2_BYTE_SIZE (1 << MICROPY_BOARD_SPIFLASH_CHIP_PARAMS1->memory_size_bytes_log2) +#define MBOOT_SPIFLASH2_ERASE_BLOCKS_PER_PAGE (1) + +extern unsigned char _micropy_hw_romfs_part0_start; + +void board_early_init_sf6(void); From 0b3ad98ea97bc504bb594639f5fcced5b5397eec Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 13:54:01 +1000 Subject: [PATCH 0541/2098] mimxrt/Makefile: Fix dependencies for generation of flexram_config.s. Prior to this fix the following would fail: $ make build-TEENSY40/flexram_config.s because it didn't create the build directory before generating the file. Also, make `hal/resethandler_MIMXRT10xx.S` have an explicit dependency on `flexram_config.s` rather than the latter just being forced to be built before everything else. Signed-off-by: Damien George --- ports/mimxrt/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 5c542950280..c0358387f97 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -327,6 +327,8 @@ SRC_SS = \ SRC_S += shared/runtime/gchelper_thumb2.s \ +hal/resethandler_MIMXRT10xx.S: $(GEN_FLEXRAM_CONFIG_SRC) + # ============================================================================= # QSTR Sources # ============================================================================= @@ -521,16 +523,16 @@ $(BUILD)/firmware.uf2: $(BUILD)/firmware.elf # any of the objects. The normal dependency generation will deal with the # case when pins.h is modified. But when it doesn't exist, we don't know # which source files might need it. -$(OBJ): | $(GEN_PINS_HDR) $(GEN_FLEXRAM_CONFIG_SRC) +$(OBJ): | $(GEN_PINS_HDR) # With conditional pins, we may need to regenerate qstrdefs.h when config # options change. $(HEADER_BUILD)/qstrdefs.generated.h: $(BOARD_DIR)/mpconfigboard.h -$(GEN_FLEXRAM_CONFIG_SRC): +$(GEN_FLEXRAM_CONFIG_SRC): $(HEADER_BUILD) $(ECHO) "Create $@" $(Q)$(PYTHON) $(MAKE_FLEXRAM_LD) -d $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE).h \ - -f $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE)_features.h -l boards/$(MCU_SERIES).ld -c $(MCU_SERIES) > $(GEN_FLEXRAM_CONFIG_SRC) + -f $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE)_features.h -l boards/$(MCU_SERIES).ld -c $(MCU_SERIES) > $@ # Use a pattern rule here so that make will only call make-pins.py once to make # both pins_gen.c and pins.h From 9ee2ef5108102ce2f5851fba06da3dcab585f501 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 15:35:39 +1000 Subject: [PATCH 0542/2098] py/emitinlinerv32: Move include of asmrv32.h to within feature guard. Otherwise, when compiling on 16-bit systems (where `mp_uint_t` is 16 bits wide) the compiler warns about "left shift count >= width of type", from the static inline functions that have RV32_ENCODE_TYPE_xxx macros which do a lot of bit shifting. Signed-off-by: Damien George --- py/emitinlinerv32.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index a9a81ddf16c..a539242b84d 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -30,12 +30,13 @@ #include #include -#include "py/asmrv32.h" #include "py/emit.h" #include "py/misc.h" #if MICROPY_EMIT_INLINE_RV32 +#include "py/asmrv32.h" + typedef enum { // define rules with a compile function #define DEF_RULE(rule, comp, kind, ...) PN_##rule, From 9f3062799633dc8fa4cc556fc48a135cd5d4c2d4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Apr 2025 14:32:41 +1000 Subject: [PATCH 0543/2098] lib/micropython-lib: Update submodule to latest. This brings in: - requests: do not leak header modifications when calling request - mip: allow relative URLs in package.json - mip: make mip.install() skip /rom*/lib directories - umqtt.simple: restore legacy ssl/ssl_params arguments - nrf24l01: increase startup delay - nrf24l01: properly handle timeout - nrf24l01: optimize status reading - lora-sx126x: fix invert_iq_rx / invert_iq_tx behaviour - unix-ffi/json: accept both str and bytes as arg for json.loads() - unix-ffi/machine: use libc if librt is not present - requests: use the host in the redirect url, not the one in headers - aiohttp: fix header case sensitivity - aiohttp: allow headers to be passed to a WebSocketClient - usb-device-cdc: optimise writing small data so it doesn't require alloc - inspect: fix isgenerator logic - inspect: implement iscoroutinefunction and iscoroutine Signed-off-by: Damien George --- lib/micropython-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/micropython-lib b/lib/micropython-lib index e4cf09527bc..5b496e944ec 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit e4cf09527bce7569f5db742cf6ae9db68d50c6a9 +Subproject commit 5b496e944ec045177afa1620920a168410b7f60b From f498a16c7db6d4b2de200b3e0856528dfe0613c3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Apr 2025 00:28:30 +1000 Subject: [PATCH 0544/2098] all: Bump version to 1.25.0. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index dfa32ebf761..dcf0e4f05c9 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -32,7 +32,7 @@ #define MICROPY_VERSION_MAJOR 1 #define MICROPY_VERSION_MINOR 25 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 1 +#define MICROPY_VERSION_PRERELEASE 0 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 28901b2c300e8c9dacd2236502bb00c5f3f24958 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Apr 2025 17:07:30 +1000 Subject: [PATCH 0545/2098] all: Bump version to 1.26.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index dcf0e4f05c9..d06932a77b7 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -30,9 +30,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 25 +#define MICROPY_VERSION_MINOR 26 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 760b962924ef5df72310832f87552341565d9325 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Mar 2025 21:29:14 -0500 Subject: [PATCH 0546/2098] tests/basics/builtin_range.py: Add more tests for range slicing. Signed-off-by: Jeff Epler --- tests/basics/builtin_range.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/basics/builtin_range.py b/tests/basics/builtin_range.py index 66226bad16a..728679bc9a1 100644 --- a/tests/basics/builtin_range.py +++ b/tests/basics/builtin_range.py @@ -32,11 +32,27 @@ print(range(1, 4)[0:]) print(range(1, 4)[1:]) print(range(1, 4)[:-1]) +print(range(4, 1)[:]) +print(range(4, 1)[0:]) +print(range(4, 1)[1:]) +print(range(4, 1)[:-1]) print(range(7, -2, -4)[:]) print(range(1, 100, 5)[5:15:3]) print(range(1, 100, 5)[15:5:-3]) print(range(100, 1, -5)[5:15:3]) print(range(100, 1, -5)[15:5:-3]) +print(range(1, 100, 5)[5:15:-3]) +print(range(1, 100, 5)[15:5:3]) +print(range(100, 1, -5)[5:15:-3]) +print(range(100, 1, -5)[15:5:3]) +print(range(1, 100, 5)[5:15:3]) +print(range(1, 100, 5)[15:5:-3]) +print(range(1, 100, -5)[5:15:3]) +print(range(1, 100, -5)[15:5:-3]) +print(range(1, 100, 5)[5:15:-3]) +print(range(1, 100, 5)[15:5:3]) +print(range(1, 100, -5)[5:15:-3]) +print(range(1, 100, -5)[15:5:3]) # for this case uPy gives a different stop value but the listed elements are still correct print(list(range(7, -2, -4)[2:-2:])) From 8faa6bafdc76a6ee307551ef4d88fd2d54db04b2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Mar 2025 21:29:01 -0500 Subject: [PATCH 0547/2098] py/objrange: Match CPython range slicing. The "index fixing" behavior of get_fast_slice_indexes are not desired here; the underlying behavior of mp_obj_slice_indexes actually is. Fixes issue #17016. Signed-off-by: Jeff Epler --- py/objrange.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/py/objrange.c b/py/objrange.c index 5ccb04fba60..1cc575f33e7 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -164,15 +164,11 @@ static mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; - mp_seq_get_fast_slice_indexes(len, index, &slice); + mp_obj_slice_indices(index, len, &slice); mp_obj_range_t *o = mp_obj_malloc(mp_obj_range_t, &mp_type_range); o->start = self->start + slice.start * self->step; o->stop = self->start + slice.stop * self->step; o->step = slice.step * self->step; - if (slice.step < 0) { - // Negative slice steps have inclusive stop, so adjust for exclusive - o->stop -= self->step; - } return MP_OBJ_FROM_PTR(o); } #endif From 0d2c18c299936a34cdeb43e2ef9cbdaecb35c610 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 20 Mar 2025 12:03:59 +0100 Subject: [PATCH 0548/2098] py/objstr: Fix handling of OP_MODULO with namedtuple. This fix handles attrtuple as well, eg. os.uname(). A test case has been added in basics/attrtuple2.py. Fixes issue #16969. Signed-off-by: Yoctopuce dev --- py/objstr.c | 4 ++-- py/objtuple.c | 3 --- py/objtuple.h | 3 +++ tests/basics/attrtuple2.py | 25 +++++++++++++++++++++++++ tests/basics/namedtuple1.py | 3 +++ 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 tests/basics/attrtuple2.py diff --git a/py/objstr.c b/py/objstr.c index 307e956a152..a160ab415c6 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -33,6 +33,7 @@ #include "py/objlist.h" #include "py/runtime.h" #include "py/cstack.h" +#include "py/objtuple.h" #if MICROPY_PY_BUILTINS_STR_OP_MODULO static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict); @@ -357,8 +358,7 @@ mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i mp_obj_t *args = &rhs_in; size_t n_args = 1; mp_obj_t dict = MP_OBJ_NULL; - if (mp_obj_is_type(rhs_in, &mp_type_tuple)) { - // TODO: Support tuple subclasses? + if (mp_obj_is_tuple_compatible(rhs_in)) { mp_obj_tuple_get(rhs_in, &n_args, &args); } else if (mp_obj_is_type(rhs_in, &mp_type_dict)) { dict = rhs_in; diff --git a/py/objtuple.c b/py/objtuple.c index 2cbcc0e502f..a8bd11c7e27 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -31,9 +31,6 @@ #include "py/objtuple.h" #include "py/runtime.h" -// type check is done on getiter method to allow tuple, namedtuple, attrtuple -#define mp_obj_is_tuple_compatible(o) (MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_obj_get_type(o), iter) == mp_obj_tuple_getiter) - /******************************************************************************/ /* tuple */ diff --git a/py/objtuple.h b/py/objtuple.h index cc42aa6df39..034814b8266 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -61,4 +61,7 @@ void mp_obj_attrtuple_print_helper(const mp_print_t *print, const qstr *fields, mp_obj_t mp_obj_new_attrtuple(const qstr *fields, size_t n, const mp_obj_t *items); +// type check is done on getiter method to allow tuple, namedtuple, attrtuple +#define mp_obj_is_tuple_compatible(o) (MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_obj_get_type(o), iter) == mp_obj_tuple_getiter) + #endif // MICROPY_INCLUDED_PY_OBJTUPLE_H diff --git a/tests/basics/attrtuple2.py b/tests/basics/attrtuple2.py new file mode 100644 index 00000000000..081d24b6ae9 --- /dev/null +++ b/tests/basics/attrtuple2.py @@ -0,0 +1,25 @@ +# test os.uname() attrtuple, if available +try: + import os +except ImportError: + print("SKIP") + raise SystemExit + +try: + u = os.uname() +except AttributeError: + print("SKIP") + raise SystemExit + +# test printing of attrtuple +print(str(u).find("machine=") > 0) + +# test read attr +print(isinstance(u.machine, str)) + +# test str modulo operator for attrtuple +impl_str = ("%s " * len(u)) % u +test_str = "" +for val in u: + test_str += val + " " +print(impl_str == test_str) diff --git a/tests/basics/namedtuple1.py b/tests/basics/namedtuple1.py index 362c60583ec..b9689b58423 100644 --- a/tests/basics/namedtuple1.py +++ b/tests/basics/namedtuple1.py @@ -21,6 +21,9 @@ print(isinstance(t, tuple)) + # a NamedTuple can be used as a tuple + print("(%d, %d)" % t) + # Check tuple can compare equal to namedtuple with same elements print(t == (t[0], t[1]), (t[0], t[1]) == t) From 52ca8268800416538368dea1976d0922ff07543f Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 20 Mar 2025 12:03:59 +0100 Subject: [PATCH 0549/2098] unix/variants: Enable os.uname() in coverage build for tests. In order to provide test coverage for the previous commit, `os.uname()` support is added to the unix coverage build. Signed-off-by: Yoctopuce dev --- ports/unix/variants/coverage/mpconfigvariant.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index a72e533e74c..04b5b8ae1ae 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -44,3 +44,10 @@ #undef MICROPY_VFS_ROM_IOCTL #define MICROPY_VFS_ROM_IOCTL (1) #define MICROPY_PY_CRYPTOLIB_CTR (1) + +// Enable os.uname for attrtuple coverage test +#define MICROPY_PY_OS_UNAME (1) +#define MICROPY_HW_BOARD_NAME "a machine" +#define MICROPY_HW_MCU_NAME MICROPY_PY_SYS_PLATFORM +// Keep the standard banner message +#define MICROPY_BANNER_MACHINE MICROPY_PY_SYS_PLATFORM " [" MICROPY_PLATFORM_COMPILER "] version" From 6025b78d01a883ed24c2f983c1d7b6a4a05d3774 Mon Sep 17 00:00:00 2001 From: David Yang Date: Fri, 21 Mar 2025 10:25:40 +0800 Subject: [PATCH 0550/2098] unix/main: Remove PATH_MAX from realpath. POSIX.1-2008 ensures realpath() give a dynamically allocated buffer if NULL is passed (which is also true for ports/windows/realpath.c), avoiding an explicit call to malloc() and use of PATH_MAX, which may be undefined on some systems. Signed-off-by: David Yang --- ports/unix/main.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 0d137fe1b58..9f51573fbfb 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -453,10 +453,13 @@ static void set_sys_argv(char *argv[], int argc, int start_arg) { #if MICROPY_PY_SYS_EXECUTABLE extern mp_obj_str_t mp_sys_executable_obj; -static char executable_path[MICROPY_ALLOC_PATH_MAX]; +static char *executable_path = NULL; static void sys_set_excecutable(char *argv0) { - if (realpath(argv0, executable_path)) { + if (executable_path == NULL) { + executable_path = realpath(argv0, NULL); + } + if (executable_path != NULL) { mp_obj_str_set_data(&mp_sys_executable_obj, (byte *)executable_path, strlen(executable_path)); } } @@ -721,11 +724,9 @@ MP_NOINLINE int main_(int argc, char **argv) { return invalid_args(); } } else { - char *pathbuf = malloc(PATH_MAX); - char *basedir = realpath(argv[a], pathbuf); + char *basedir = realpath(argv[a], NULL); if (basedir == NULL) { mp_printf(&mp_stderr_print, "%s: can't open file '%s': [Errno %d] %s\n", argv[0], argv[a], errno, strerror(errno)); - free(pathbuf); // CPython exits with 2 in such case ret = 2; break; @@ -734,7 +735,7 @@ MP_NOINLINE int main_(int argc, char **argv) { // Set base dir of the script as first entry in sys.path. char *p = strrchr(basedir, '/'); mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir)); - free(pathbuf); + free(basedir); set_sys_argv(argv, argc, a); ret = do_file(argv[a]); @@ -800,6 +801,11 @@ MP_NOINLINE int main_(int argc, char **argv) { #endif #endif + #if MICROPY_PY_SYS_EXECUTABLE && !defined(NDEBUG) + // Again, make memory leak detector happy + free(executable_path); + #endif + // printf("total bytes = %d\n", m_get_total_bytes_allocated()); return ret & 0xff; } From 38a3873310c571c32e52edc2a7d4de7c80ff35d5 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 18 Nov 2024 05:35:48 +0100 Subject: [PATCH 0551/2098] unix/mpthreadport: Work around lack of thread cancellation on Android. This commit fixes thread-related compilation issues under Android using Termux as its runtime environment. On Android's libc (Bionic) thread cancellation is not implemented, but the Unix port uses that mechanism to provide asynchronous thread termination. In this commit there is a workaround for that, by adding a new signal handler to each newly created thread, whose callback simply exits the thread. Threads are then sent the new signal rather than being explicitly cancelled, which in turn trigger the signal handler to stop the thread execution at the next possible occasion. This makes the cancellation behaviour differ slightly on Android, as threads are probably going to linger a little bit more since the method introduced in this commit is equivalent to setting PTHREAD_CANCEL_DEFERRED as the thread cancellation type. On the other hand there are no guarantees of immediate cancellation using PTHREAD_CANCEL_ASYNCHRONOUS either. This fixes the pthread-related issues reported in #16259. Signed-off-by: Alessandro Gatti --- ports/unix/mpthreadport.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 5172645bc14..ded3bd14aff 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -45,8 +45,14 @@ // potential conflict with other uses of the more commonly used SIGUSR1. #ifdef SIGRTMIN #define MP_THREAD_GC_SIGNAL (SIGRTMIN + 5) +#ifdef __ANDROID__ +#define MP_THREAD_TERMINATE_SIGNAL (SIGRTMIN + 6) +#endif #else #define MP_THREAD_GC_SIGNAL (SIGUSR1) +#ifdef __ANDROID__ +#define MP_THREAD_TERMINATE_SIGNAL (SIGUSR2) +#endif #endif // This value seems to be about right for both 32-bit and 64-bit builds. @@ -107,6 +113,18 @@ static void mp_thread_gc(int signo, siginfo_t *info, void *context) { } } +// On Android, pthread_cancel and pthread_setcanceltype are not implemented. +// To achieve that result a new signal handler responding on either +// (SIGRTMIN + 6) or SIGUSR2 is installed on every child thread. The sole +// purpose of this new signal handler is to terminate the thread in a safe +// asynchronous manner. + +#ifdef __ANDROID__ +static void mp_thread_terminate(int signo, siginfo_t *info, void *context) { + pthread_exit(NULL); +} +#endif + void mp_thread_init(void) { pthread_key_create(&tls_key, NULL); pthread_setspecific(tls_key, &mp_state_ctx.thread); @@ -135,6 +153,14 @@ void mp_thread_init(void) { sa.sa_sigaction = mp_thread_gc; sigemptyset(&sa.sa_mask); sigaction(MP_THREAD_GC_SIGNAL, &sa, NULL); + + // Install a signal handler for asynchronous termination if needed. + #if defined(__ANDROID__) + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = mp_thread_terminate; + sigemptyset(&sa.sa_mask); + sigaction(MP_THREAD_TERMINATE_SIGNAL, &sa, NULL); + #endif } void mp_thread_deinit(void) { @@ -142,7 +168,11 @@ void mp_thread_deinit(void) { while (thread->next != NULL) { mp_thread_t *th = thread; thread = thread->next; + #if defined(__ANDROID__) + pthread_kill(th->id, MP_THREAD_TERMINATE_SIGNAL); + #else pthread_cancel(th->id); + #endif free(th); } mp_thread_unix_end_atomic_section(); @@ -200,7 +230,9 @@ void mp_thread_start(void) { } #endif + #if !defined(__ANDROID__) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + #endif mp_thread_unix_begin_atomic_section(); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == pthread_self()) { From 27f4351f5fcd649fde12314d874083fcf967c01b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 19 Mar 2025 11:38:29 +1100 Subject: [PATCH 0552/2098] CODECONVENTIONS: Document the static naming conventions. Goal is to document what's most commonly already in use, not to come up with a new standard. Also reformat the doc a bit for easier deep linking. Signed-off-by: Angus Gratton --- CODECONVENTIONS.md | 52 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/CODECONVENTIONS.md b/CODECONVENTIONS.md index d6af0418e42..d3f71cb083d 100644 --- a/CODECONVENTIONS.md +++ b/CODECONVENTIONS.md @@ -206,14 +206,21 @@ adhere to the existing style and use `tools/codeformat.py` to check any changes. The main conventions, and things not enforceable via the auto-formatter, are described below. -White space: +As the MicroPython code base is over ten years old, not every source file +conforms fully to these conventions. If making small changes to existing code, +then it's usually acceptable to follow the existing code's style. New code or +major changes should follow the conventions described here. + +## White space + - Expand tabs to 4 spaces. - Don't leave trailing whitespace at the end of a line. - For control blocks (if, for, while), put 1 space between the keyword and the opening parenthesis. - Put 1 space after a comma, and 1 space around operators. -Braces: +## Braces + - Use braces for all blocks, even no-line and single-line pieces of code. - Put opening braces on the end of the line it belongs to, not on @@ -221,18 +228,43 @@ Braces: - For else-statements, put the else on the same line as the previous closing brace. -Header files: +## Header files + - Header files should be protected from multiple inclusion with #if directives. See an existing header for naming convention. -Names: +## Names + - Use underscore_case, not camelCase for all names. - Use CAPS_WITH_UNDERSCORE for enums and macros. - When defining a type use underscore_case and put '_t' after it. -Integer types: MicroPython runs on 16, 32, and 64 bit machines, so it's -important to use the correctly-sized (and signed) integer types. The -general guidelines are: +### Public names (declared in headers) + +- MicroPython-specific names (especially any declared in `py/` and `extmod/` + directories) should generally start with `mp_` or `MP_`. +- Functions and variables declared in a header should generally share a longer + common prefix. Usually the prefix matches the file name (i.e. items defined in + `py/obj.c` are declared in `py/obj.h` and should be prefixed `mp_obj_`). There + are exceptions, for example where one header file contains declarations + implemented in multiple source files for expediency. + +### Private names (specific to a single .c file) + +- For static functions and variables exposed to Python (i.e. a static C function + that is wrapped in `MP_DEFINE_CONST_FUN_...` and attached to a module), use + the file-level shared common prefix, i.e. name them as if the function or + variable was not static. +- Other static definitions in source files (i.e. functions or variables defined + in a .c file that are only used within that .c file) don't need any prefix + (specifically: no `s_` or `_` prefix, and generally avoid adding the + file-level common prefix). + +## Integer types + +MicroPython runs on 16, 32, and 64 bit machines, so it's important to use the +correctly-sized (and signed) integer types. The general guidelines are: + - For most cases use mp_int_t for signed and mp_uint_t for unsigned integer values. These are guaranteed to be machine-word sized and therefore big enough to hold the value from a MicroPython small-int @@ -241,11 +273,13 @@ general guidelines are: - You can use int/uint, but remember that they may be 16-bits wide. - If in doubt, use mp_int_t/mp_uint_t. -Comments: +## Comments + - Be concise and only write comments for things that are not obvious. - Use `// ` prefix, NOT `/* ... */`. No extra fluff. -Memory allocation: +## Memory allocation + - Use m_new, m_renew, m_del (and friends) to allocate and free heap memory. These macros are defined in py/misc.h. From ba4179bb66ad2dabc385de4584e7b57c9bffb347 Mon Sep 17 00:00:00 2001 From: dubiousjim Date: Thu, 20 Mar 2025 05:55:55 -0400 Subject: [PATCH 0553/2098] py/dynruntime.mk: Fix use of musl's libm.a when LINK_RUNTIME=1. Like PICOLIBC, MUSL also has its math functions in libc.a. There is a libm.a, but it's empty. Signed-off-by: dubiousjim --- py/dynruntime.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 807befb464a..1ef521bd9ae 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -124,6 +124,10 @@ else $(error architecture '$(ARCH)' not supported) endif +ifneq ($(findstring -musl,$(shell $(CROSS)gcc -dumpmachine)),) +USE_MUSL := 1 +endif + MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]') CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) @@ -147,6 +151,8 @@ ifeq ($(LINK_RUNTIME),1) # distribution. ifeq ($(USE_PICOLIBC),1) LIBM_NAME := libc.a +else ifeq ($(USE_MUSL),1) +LIBM_NAME := libc.a else LIBM_NAME := libm.a endif From 7c7a9bdb344bd83db9bc4e5a41114283a624095c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 09:43:50 +1100 Subject: [PATCH 0554/2098] drivers/ninaw10/machine_pin_nina: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- drivers/ninaw10/machine_pin_nina.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/ninaw10/machine_pin_nina.c b/drivers/ninaw10/machine_pin_nina.c index c72ecee3dbb..fe6eb5c03af 100644 --- a/drivers/ninaw10/machine_pin_nina.c +++ b/drivers/ninaw10/machine_pin_nina.c @@ -114,13 +114,13 @@ void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { mode = NINA_GPIO_OUTPUT; self->is_output = true; } else { - mp_raise_ValueError("only Pin.OUT and Pin.IN are supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.OUT and Pin.IN are supported for this pin")); } if (self->id >= 0 && self->id < MICROPY_HW_PIN_EXT_COUNT) { uint8_t buf[] = {pin_map[self->id], mode}; if (mode == NINA_GPIO_OUTPUT) { if (NINA_GPIO_IS_INPUT_ONLY(self->id)) { - mp_raise_ValueError("only Pin.IN is supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.IN is supported for this pin")); } machine_pin_ext_set(self, value); } From b6dbc47664e0b5dc3516f59a24322d26d2ad2617 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:19:40 +1100 Subject: [PATCH 0555/2098] extmod/machine_usb_device: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- extmod/machine_usb_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index 5c4e84c38d1..5019cd98779 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -108,7 +108,7 @@ static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buff // // This C layer doesn't otherwise keep track of which endpoints the host // is aware of (or not). - mp_raise_ValueError("ep"); + mp_raise_ValueError(MP_ERROR_TEXT("ep")); } if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { From 9bde1970044b7b0a6667d2f4a675c8c3f7816706 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:19:53 +1100 Subject: [PATCH 0556/2098] rp2: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- ports/rp2/machine_pin.c | 6 +++--- ports/rp2/machine_pin_cyw43.c | 2 +- ports/rp2/rp2_pio.c | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 8ba0b44a684..d9ca3f8ce37 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -193,7 +193,7 @@ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { return &machine_pin_obj_table[wanted_pin]; } } - mp_raise_ValueError("invalid pin"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); } static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -259,11 +259,11 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); if (is_ext_pin(self) && args[ARG_pull].u_obj != mp_const_none) { - mp_raise_ValueError("pulls are not supported for external pins"); + mp_raise_ValueError(MP_ERROR_TEXT("pulls are not supported for external pins")); } if (is_ext_pin(self) && args[ARG_alt].u_int != GPIO_FUNC_SIO) { - mp_raise_ValueError("alternate functions are not supported for external pins"); + mp_raise_ValueError(MP_ERROR_TEXT("alternate functions are not supported for external pins")); } // get initial value of pin (only valid for OUT and OPEN_DRAIN modes) diff --git a/ports/rp2/machine_pin_cyw43.c b/ports/rp2/machine_pin_cyw43.c index 9fc262454f8..f79d932f7a5 100644 --- a/ports/rp2/machine_pin_cyw43.c +++ b/ports/rp2/machine_pin_cyw43.c @@ -70,7 +70,7 @@ void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { self->is_output = true; } } else { - mp_raise_ValueError("only Pin.OUT and Pin.IN are supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.OUT and Pin.IN are supported for this pin")); } if (value != -1) { diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 56c576581e0..d936553b555 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -126,7 +126,7 @@ void rp2_pio_irq_set_exclusive_handler(PIO pio, uint irq) { irq_handler_t current = irq_get_exclusive_handler(irq); // If the IRQ is set and isn't our handler, or a shared handler is set, then raise an error if ((current && current != rp2_pio_get_irq_handler(pio)) || irq_has_shared_handler(irq)) { - mp_raise_ValueError("irq claimed by external resource"); + mp_raise_ValueError(MP_ERROR_TEXT("irq claimed by external resource")); // If the IRQ is not set, add our handler } else if (!current) { irq_set_exclusive_handler(irq, rp2_pio_get_irq_handler(pio)); @@ -304,7 +304,7 @@ static mp_obj_t rp2_pio_make_new(const mp_obj_type_t *type, size_t n_args, size_ // Get the PIO object. int pio_id = mp_obj_get_int(args[0]); if (!(0 <= pio_id && pio_id < MP_ARRAY_SIZE(rp2_pio_obj))) { - mp_raise_ValueError("invalid PIO"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid PIO")); } const rp2_pio_obj_t *self = &rp2_pio_obj[pio_id]; @@ -353,7 +353,7 @@ static mp_obj_t rp2_pio_remove_program(size_t n_args, const mp_obj_t *args) { length = bufinfo.len / 2; offset = mp_obj_get_int(prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)]); if (offset < 0) { - mp_raise_ValueError("prog not in instruction memory"); + mp_raise_ValueError(MP_ERROR_TEXT("prog not in instruction memory")); } // Invalidate the program offset in the program object. prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)] = MP_OBJ_NEW_SMALL_INT(-1); @@ -374,7 +374,7 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m // Get and verify the state machine id. mp_int_t sm_id = mp_obj_get_int(pos_args[1]); if (!(0 <= sm_id && sm_id < 4)) { - mp_raise_ValueError("invalid StateMachine"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid StateMachine")); } // Return the correct StateMachine object. @@ -400,7 +400,7 @@ static mp_obj_t rp2_pio_gpio_base(size_t n_args, const mp_obj_t *args) { // 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"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid GPIO base")); } if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { @@ -556,14 +556,14 @@ static const rp2_state_machine_obj_t rp2_state_machine_obj[] = { static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id) { if (!(0 <= sm_id && sm_id < MP_ARRAY_SIZE(rp2_state_machine_obj))) { - mp_raise_ValueError("invalid StateMachine"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid StateMachine")); } const rp2_state_machine_obj_t *sm_obj = &rp2_state_machine_obj[sm_id]; if (!(rp2_state_machine_claimed_mask & (1 << sm_id))) { if (pio_sm_is_claimed(sm_obj->pio, sm_obj->sm)) { - mp_raise_ValueError("StateMachine claimed by external resource"); + mp_raise_ValueError(MP_ERROR_TEXT("StateMachine claimed by external resource")); } pio_sm_claim(sm_obj->pio, sm_obj->sm); rp2_state_machine_claimed_mask |= 1 << sm_id; @@ -830,7 +830,7 @@ static mp_obj_t rp2_state_machine_get(size_t n_args, const mp_obj_t *args) { *(uint32_t *)dest = value; dest += sizeof(uint32_t); } else { - mp_raise_ValueError("unsupported buffer type"); + mp_raise_ValueError(MP_ERROR_TEXT("unsupported buffer type")); } if (dest >= dest_top) { return args[1]; @@ -868,7 +868,7 @@ static mp_obj_t rp2_state_machine_put(size_t n_args, const mp_obj_t *args) { value = *(uint32_t *)src; src += sizeof(uint32_t); } else { - mp_raise_ValueError("unsupported buffer type"); + mp_raise_ValueError(MP_ERROR_TEXT("unsupported buffer type")); } while (pio_sm_is_tx_fifo_full(self->pio, self->sm)) { // This delay must be fast. From 569d472bc7f6f6fb20f69fcdb3ff19f525dec071 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:21:52 +1100 Subject: [PATCH 0557/2098] extmod/modbluetooth: Use newer mp_map_slot_is_filled function. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- extmod/modbluetooth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 7049c35dfdf..b95c42a4ee1 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -340,7 +340,7 @@ static mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map } for (size_t i = 0; i < kwargs->alloc; ++i) { - if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + if (mp_map_slot_is_filled(kwargs, i)) { mp_map_elem_t *e = &kwargs->table[i]; switch (mp_obj_str_get_qstr(e->key)) { case MP_QSTR_gap_name: { From d6c673f28f12978625577a4d1214ebf450d57c8d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:23:55 +1100 Subject: [PATCH 0558/2098] stm32/main: Replace mp_stack_set calls with new mp_cstack_init_with_top. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- ports/stm32/main.c | 7 ++----- ports/stm32/mpconfigport.h | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 0f904a8ba95..e8395013b91 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -515,11 +515,8 @@ void stm32_main(uint32_t reset_mode) { mp_thread_init(); #endif - // Stack limit should be less than real stack size, so we have a chance - // to recover from limit hit. (Limit is measured in bytes.) - // Note: stack control relies on main thread being initialised above - mp_stack_set_top(&_estack); - mp_stack_set_limit((char *)&_estack - (char *)&_sstack - 1024); + // Stack limit init. + mp_cstack_init_with_top(&_estack, (char *)&_estack - (char *)&_sstack); // GC init gc_init(MICROPY_HEAP_START, MICROPY_HEAP_END); diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index da86fa64162..d1d6fe249bd 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -43,6 +43,7 @@ #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #endif #endif +#define MICROPY_STACK_CHECK_MARGIN (1024) #define MICROPY_ALLOC_PATH_MAX (128) // optimisations From bb1489965f74cfe839c3452e1b6a04e82c695bf5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:18:48 +1100 Subject: [PATCH 0559/2098] py/mkrules.cmake: Add CMake support for compressed error messages. Signed-off-by: Damien George --- py/mkrules.cmake | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index 4374b8b4da3..27d8b24f67a 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -14,6 +14,9 @@ set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") set(MICROPY_ROOT_POINTERS_SPLIT "${MICROPY_GENHDR_DIR}/root_pointers.split") set(MICROPY_ROOT_POINTERS_COLLECTED "${MICROPY_GENHDR_DIR}/root_pointers.collected") set(MICROPY_ROOT_POINTERS "${MICROPY_GENHDR_DIR}/root_pointers.h") +set(MICROPY_COMPRESSED_SPLIT "${MICROPY_GENHDR_DIR}/compressed.split") +set(MICROPY_COMPRESSED_COLLECTED "${MICROPY_GENHDR_DIR}/compressed.collected") +set(MICROPY_COMPRESSED_DATA "${MICROPY_GENHDR_DIR}/compressed.data.h") if(NOT MICROPY_PREVIEW_VERSION_2) set(MICROPY_PREVIEW_VERSION_2 0) @@ -32,6 +35,13 @@ if(MICROPY_BOARD) ) endif() +# Need to do this before extracting MICROPY_CPP_DEF below. +if(MICROPY_ROM_TEXT_COMPRESSION) + target_compile_definitions(${MICROPY_TARGET} PUBLIC + MICROPY_ROM_TEXT_COMPRESSION=\(1\) + ) +endif() + # Need to do this before extracting MICROPY_CPP_DEF below. Rest of frozen # manifest handling is at the end of this file. if(MICROPY_FROZEN_MANIFEST) @@ -84,6 +94,12 @@ target_sources(${MICROPY_TARGET} PRIVATE ${MICROPY_ROOT_POINTERS} ) +if(MICROPY_ROM_TEXT_COMPRESSION) + target_sources(${MICROPY_TARGET} PRIVATE + ${MICROPY_COMPRESSED_DATA} + ) +endif() + # Command to force the build of another command # Generate mpversion.h @@ -197,6 +213,32 @@ add_custom_command( DEPENDS ${MICROPY_ROOT_POINTERS_COLLECTED} ${MICROPY_PY_DIR}/make_root_pointers.py ) +# Generate compressed.data.h + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_SPLIT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py split compress ${MICROPY_QSTRDEFS_LAST} ${MICROPY_GENHDR_DIR}/compress _ + COMMAND touch ${MICROPY_COMPRESSED_SPLIT} + DEPENDS ${MICROPY_QSTRDEFS_LAST} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_COLLECTED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py cat compress _ ${MICROPY_GENHDR_DIR}/compress ${MICROPY_COMPRESSED_COLLECTED} + BYPRODUCTS "${MICROPY_COMPRESSED_COLLECTED}.hash" + DEPENDS ${MICROPY_COMPRESSED_SPLIT} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_DATA} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makecompresseddata.py ${MICROPY_COMPRESSED_COLLECTED} > ${MICROPY_COMPRESSED_DATA} + DEPENDS ${MICROPY_COMPRESSED_COLLECTED} ${MICROPY_PY_DIR}/makecompresseddata.py +) + # Build frozen code if enabled if(MICROPY_FROZEN_MANIFEST) From bfe16ef09bd06acf359a214f97a1b21f5fc71d66 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:19:08 +1100 Subject: [PATCH 0560/2098] esp32: Enable compressed error messages by default. Reduces firmware size by about 3300 bytes. Signed-off-by: Damien George --- ports/esp32/esp32_common.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index dedef6d782c..a9827249bd3 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -28,6 +28,11 @@ if(NOT DEFINED MICROPY_PY_TINYUSB) endif() endif() +# Enable error text compression by default. +if(NOT MICROPY_ROM_TEXT_COMPRESSION) + set(MICROPY_ROM_TEXT_COMPRESSION ON) +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) From 048ccccee01cdb1f36242d4ee5bba43a6900c891 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:19:24 +1100 Subject: [PATCH 0561/2098] rp2: Enable compressed error messages by default. Reduces firmware size by about 3000 bytes. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 1a5029c1504..fcc1a1e1b9b 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -66,6 +66,11 @@ if(NOT MICROPY_C_HEAP_SIZE) set(MICROPY_C_HEAP_SIZE 0) endif() +# Enable error text compression by default. +if(NOT MICROPY_ROM_TEXT_COMPRESSION) + set(MICROPY_ROM_TEXT_COMPRESSION ON) +endif() + # Enable extmod components that will be configured by extmod.cmake. # A board may also have enabled additional components. set(MICROPY_SSL_MBEDTLS ON) From e3c2cf7a040052143b19ef140fd666f86ec0fbda Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 21 Mar 2025 02:12:31 +0100 Subject: [PATCH 0562/2098] esp32/esp32_common.cmake: Skip BTree module when requested. This commit makes the BTree module truly optional, as it was unconditionally enabled in the shared CMake script for the port. This meant that if a board/variant did explicitly turn BTree off said request was not honoured by the build system and the BTree module would still be brought in. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index a9827249bd3..c54f5a54032 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -42,7 +42,9 @@ include(${MICROPY_DIR}/py/py.cmake) if(NOT CMAKE_BUILD_EARLY_EXPANSION) # Enable extmod components that will be configured by extmod.cmake. # A board may also have enabled additional components. - set(MICROPY_PY_BTREE ON) + if (NOT DEFINED MICROPY_PY_BTREE) + set(MICROPY_PY_BTREE ON) + endif() include(${MICROPY_DIR}/py/usermod.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) @@ -276,7 +278,9 @@ target_include_directories(${MICROPY_TARGET} PUBLIC ) # Add additional extmod and usermod components. -target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +if (MICROPY_PY_BTREE) + target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +endif() target_link_libraries(${MICROPY_TARGET} usermod) # Extra linker options From 193460d18f018e7306812fb41283fc241342eb1a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 28 Feb 2025 09:48:52 +0100 Subject: [PATCH 0563/2098] drivers/esp-hosted: Rename Bluetooth HCI backend driver. Rename `bthci` to `bthci_uart` for consistency with the CYW43 driver and to distinguish it from HCI backends that use a different transport. Signed-off-by: iabdalkader --- .../esp-hosted/{esp_hosted_bthci.c => esp_hosted_bthci_uart.c} | 0 extmod/extmod.mk | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename drivers/esp-hosted/{esp_hosted_bthci.c => esp_hosted_bthci_uart.c} (100%) diff --git a/drivers/esp-hosted/esp_hosted_bthci.c b/drivers/esp-hosted/esp_hosted_bthci_uart.c similarity index 100% rename from drivers/esp-hosted/esp_hosted_bthci.c rename to drivers/esp-hosted/esp_hosted_bthci_uart.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 859f610c90a..b2a0f490b6e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -504,7 +504,7 @@ ESP_HOSTED_SRC_C = $(addprefix $(ESP_HOSTED_DIR)/,\ ) ifeq ($(MICROPY_PY_BLUETOOTH),1) -ESP_HOSTED_SRC_C += $(ESP_HOSTED_DIR)/esp_hosted_bthci.c +ESP_HOSTED_SRC_C += $(ESP_HOSTED_DIR)/esp_hosted_bthci_uart.c endif # Include the protobuf-c support functions From fa76d52edbbecf4b3dbc27238d4ec83fb88a55ff Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 28 Feb 2025 09:52:19 +0100 Subject: [PATCH 0564/2098] drivers/ninaw10: Rename Bluetooth HCI backend driver. Rename `bt_hci` to `bthci_uart` for consistency with the CYW43 driver and to distinguish it from HCI backends that use a different transport. Signed-off-by: iabdalkader --- drivers/ninaw10/{nina_bt_hci.c => nina_bthci_uart.c} | 4 ++-- ports/rp2/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename drivers/ninaw10/{nina_bt_hci.c => nina_bthci_uart.c} (98%) diff --git a/drivers/ninaw10/nina_bt_hci.c b/drivers/ninaw10/nina_bthci_uart.c similarity index 98% rename from drivers/ninaw10/nina_bt_hci.c rename to drivers/ninaw10/nina_bthci_uart.c index f0d1b9bc898..50631711097 100644 --- a/drivers/ninaw10/nina_bt_hci.c +++ b/drivers/ninaw10/nina_bthci_uart.c @@ -50,8 +50,8 @@ #define OCF_SET_EVENT_MASK (0x0001) #define OCF_RESET (0x0003) -#define error_printf(...) // mp_printf(&mp_plat_print, "nina_bt_hci.c: " __VA_ARGS__) -#define debug_printf(...) // mp_printf(&mp_plat_print, "nina_bt_hci.c: " __VA_ARGS__) +#define error_printf(...) // mp_printf(&mp_plat_print, "nina_bthci_uart.c: " __VA_ARGS__) +#define debug_printf(...) // mp_printf(&mp_plat_print, "nina_bthci_uart.c: " __VA_ARGS__) // Provided by the port, and also possibly shared with the stack. extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index fcc1a1e1b9b..53d00c7dfda 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -431,7 +431,7 @@ if (MICROPY_PY_NETWORK_NINAW10) # Enable NINA-W10 WiFi and Bluetooth drivers. list(APPEND MICROPY_SOURCE_DRIVERS - ${MICROPY_DIR}/drivers/ninaw10/nina_bt_hci.c + ${MICROPY_DIR}/drivers/ninaw10/nina_bthci_uart.c ${MICROPY_DIR}/drivers/ninaw10/nina_wifi_drv.c ${MICROPY_DIR}/drivers/ninaw10/nina_wifi_bsp.c ${MICROPY_DIR}/drivers/ninaw10/machine_pin_nina.c From 399c10dc28eba4ddd163dd0e930529f1b81d514a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 3 Mar 2025 17:32:41 +0100 Subject: [PATCH 0565/2098] mimxrt/machine_uart: Enable CTS SION so it can be read. The new CYW43 BTHCI UART backend requires CTS pin to be defined and readable. This patch enables the CTS pin SION bit to allow it to be read regardless of mux mode. Signed-off-by: iabdalkader --- ports/mimxrt/machine_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 4dcaf72f986..107af72297d 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -136,7 +136,7 @@ bool lpuart_set_iomux_cts(int8_t uart) { int index = (uart - 1) * 2; if (CTS.muxRegister != 0) { - IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 0U); + IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 1U); IOMUXC_SetPinConfig(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, pin_generate_config(PIN_PULL_UP_100K, PIN_MODE_IN, PIN_DRIVE_6, CTS.configRegister)); return true; From 3bbed952fdc884d85112a15715152c9c4f558c14 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 3 Mar 2025 17:46:56 +0100 Subject: [PATCH 0566/2098] mimxrt/cyw43_configport: Update cyw43 config to use new BTHCI UART. Update the cyw43 configuration to use the new BTHCI UART backend provided by cyw43-driver. Signed-off-by: iabdalkader --- ports/mimxrt/Makefile | 1 - ports/mimxrt/cyw43_configport.h | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index c0358387f97..7788b54ca57 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -280,7 +280,6 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c -DRIVERS_SRC_C += drivers/cyw43/cywbt.c endif # MICROPY_PY_BLUETOOTH ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h index ab535a47776..cf2f5d4b9fb 100644 --- a/ports/mimxrt/cyw43_configport.h +++ b/ports/mimxrt/cyw43_configport.h @@ -32,10 +32,12 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "extmod/modnetwork.h" +#include "extmod/mpbthci.h" #include "pendsv.h" #include "sdio.h" #define CYW43_USE_SPI (0) +#define CYW43_ENABLE_BLUETOOTH_OVER_UART (1) #define CYW43_LWIP (1) #define CYW43_USE_STATS (0) @@ -47,6 +49,18 @@ #define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1dx.h" #endif +#ifndef CYW43_BT_FIRMWARE_INCLUDE_FILE +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#define CYW43_BT_UART_BAUDRATE_ACTIVE_USE MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#endif + #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (50) #define CYW43_NETUTILS (1) @@ -75,6 +89,7 @@ #define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN #define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR #define cyw43_hal_ticks_us mp_hal_ticks_us #define cyw43_hal_ticks_ms mp_hal_ticks_ms @@ -88,9 +103,19 @@ #define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii #define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac +#define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate +#define cyw43_hal_uart_write mp_bluetooth_hci_uart_write +#define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar + #define cyw43_delay_us mp_hal_delay_us #define cyw43_delay_ms mp_hal_delay_ms +#define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init +#define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit +#define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken +#define cyw43_bluetooth_controller_wakeup mp_bluetooth_hci_controller_wakeup +#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe + #define CYW43_PIN_WL_REG_ON MICROPY_HW_WL_REG_ON #define CYW43_PIN_WL_HOST_WAKE MICROPY_HW_WL_HOST_WAKE #define CYW43_PIN_WL_SDIO_1 MICROPY_HW_SDIO_D1 From 0f360880aa01ca87c637eaa3517a90a78c2dbfd2 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 30 Mar 2025 09:29:57 +0200 Subject: [PATCH 0567/2098] stm32/cyw43_configport: Update cyw43 config to use new BTHCI UART. Update the cyw43 configuration to use the new BTHCI UART backend provided by cyw43-driver. Signed-off-by: iabdalkader --- ports/stm32/Makefile | 1 - ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 2 +- ports/stm32/cyw43_configport.h | 27 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index c938dcbda7d..8ac9a8af03d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -432,7 +432,6 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c -DRIVERS_SRC_C += drivers/cyw43/cywbt.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) SRC_C += mpnimbleport.c diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index f5e6769834a..354c1919b41 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -54,7 +54,7 @@ SECTIONS *lib/mbedtls/*(.text* .rodata*) *lib/mynewt-nimble/*(.text* .rodata*) *lib/cyw43-driver/*(.rodata.w4343*_combined) - *drivers/cyw43/*(.rodata.cyw43_btfw_*) + *lib/cyw43-driver/*(.rodata.cyw43_btfw_*) . = ALIGN(4); } >FLASH_EXT } diff --git a/ports/stm32/cyw43_configport.h b/ports/stm32/cyw43_configport.h index 59505118033..c26c5547616 100644 --- a/ports/stm32/cyw43_configport.h +++ b/ports/stm32/cyw43_configport.h @@ -32,11 +32,13 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "extmod/modnetwork.h" +#include "extmod/mpbthci.h" #include "extint.h" #include "pendsv.h" #include "sdio.h" #define CYW43_USE_SPI (0) +#define CYW43_ENABLE_BLUETOOTH_OVER_UART (1) #define CYW43_LWIP (1) #define CYW43_USE_STATS (0) @@ -48,6 +50,18 @@ #define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1dx.h" #endif +#ifndef CYW43_BT_FIRMWARE_INCLUDE_FILE +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#define CYW43_BT_UART_BAUDRATE_ACTIVE_USE MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#endif + #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (50) #define CYW43_NETUTILS (1) @@ -66,6 +80,7 @@ #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); +#define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT __WFI() #define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) @@ -76,6 +91,7 @@ #define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN #define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR #define cyw43_hal_ticks_us mp_hal_ticks_us #define cyw43_hal_ticks_ms mp_hal_ticks_ms @@ -90,6 +106,16 @@ #define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii #define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac +#define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate +#define cyw43_hal_uart_write mp_bluetooth_hci_uart_write +#define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar + +#define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init +#define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit +#define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken +#define cyw43_bluetooth_controller_wakeup mp_bluetooth_hci_controller_wakeup +#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe + #define CYW43_PIN_WL_REG_ON pyb_pin_WL_REG_ON #define CYW43_PIN_WL_HOST_WAKE pyb_pin_WL_HOST_WAKE #define CYW43_PIN_WL_SDIO_1 pyb_pin_WL_SDIO_1 @@ -103,6 +129,7 @@ #if MICROPY_HW_ENABLE_RF_SWITCH #define CYW43_PIN_RFSW_VDD pyb_pin_WL_RFSW_VDD +#define CYW43_PIN_RFSW_SELECT pyb_pin_WL_GPIO_1 #endif #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) From 0b729623366dab3aa2575eeab934b2629f8e52c5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 30 Mar 2025 09:31:26 +0200 Subject: [PATCH 0568/2098] drivers/cyw43: Remove old BTHCI UART backend. It has been completely replaced by equivalent code in cyw43-driver. Signed-off-by: iabdalkader --- drivers/cyw43/README.md | 17 --- drivers/cyw43/cywbt.c | 304 ---------------------------------------- 2 files changed, 321 deletions(-) delete mode 100644 drivers/cyw43/README.md delete mode 100644 drivers/cyw43/cywbt.c diff --git a/drivers/cyw43/README.md b/drivers/cyw43/README.md deleted file mode 100644 index 5af6f655806..00000000000 --- a/drivers/cyw43/README.md +++ /dev/null @@ -1,17 +0,0 @@ -CYW43xx WiFi SoC driver -======================= - -This is a driver for the CYW43xx WiFi SoC. - -There are four layers to the driver: - -1. SDIO bus interface, provided by the host device/system. - -2. Low-level CYW43xx interface, managing the bus, control messages, Ethernet - frames and asynchronous events. Includes download of SoC firmware. The - header file `cyw43_ll.h` defines the interface to this layer. - -3. Mid-level CYW43xx control, to control and set WiFi parameters and manage - events. See `cyw43_ctrl.c`. - -4. TCP/IP bindings to lwIP. See `cyw43_lwip.c`. diff --git a/drivers/cyw43/cywbt.c b/drivers/cyw43/cywbt.c deleted file mode 100644 index 0ee69a07ecd..00000000000 --- a/drivers/cyw43/cywbt.c +++ /dev/null @@ -1,304 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019-2020 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 -#include - -#include "py/runtime.h" -#include "py/mphal.h" -#include "extmod/mpbthci.h" - -#if MICROPY_PY_NETWORK_CYW43 - -#include "lib/cyw43-driver/src/cyw43_config.h" -#include "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" - -// Provided by the port, and also possibly shared with the stack. -extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; - -/******************************************************************************/ -// CYW BT HCI low-level driver - -#ifdef CYW43_PIN_BT_CTS -// This code is not portable and currently only builds on stm32 port. - -#include "pin_static_af.h" -#include "uart.h" - -// Provided by the port. -extern machine_uart_obj_t mp_bluetooth_hci_uart_obj; - -static void cywbt_wait_cts_low(void) { - mp_hal_pin_config(CYW43_PIN_BT_CTS, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); - for (int i = 0; i < 200; ++i) { - if (mp_hal_pin_read(CYW43_PIN_BT_CTS) == 0) { - break; - } - mp_hal_delay_ms(1); - } - mp_hal_pin_config_alt(CYW43_PIN_BT_CTS, MP_HAL_PIN_MODE_ALT, - MP_HAL_PIN_PULL_UP, AF_FN_UART, mp_bluetooth_hci_uart_obj.uart_id); -} -#endif - -static int cywbt_hci_cmd_raw(size_t len, uint8_t *buf) { - mp_bluetooth_hci_uart_write((void *)buf, len); - for (int c, i = 0; i < 6; ++i) { - while ((c = mp_bluetooth_hci_uart_readchar()) == -1) { - mp_event_wait_indefinite(); - } - buf[i] = c; - } - - // expect a command complete event (event 0x0e) - if (buf[0] != 0x04 || buf[1] != 0x0e) { - printf("unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); - return -1; - } - - /* - if buf[3:6] != cmd[:3]: - print('response doesn\'t match cmd:', cmd, ev) - return b'' - */ - - int sz = buf[2] - 3; - for (int c, i = 0; i < sz; ++i) { - while ((c = mp_bluetooth_hci_uart_readchar()) == -1) { - mp_event_wait_indefinite(); - } - buf[i] = c; - } - - return 0; -} - -static int cywbt_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) { - uint8_t *buf = mp_bluetooth_hci_cmd_buf; - buf[0] = 0x01; - buf[1] = ocf; - buf[2] = ogf << 2 | ocf >> 8; - buf[3] = param_len; - if (param_len) { - memcpy(buf + 4, param_buf, param_len); - } - return cywbt_hci_cmd_raw(4 + param_len, buf); -} - -static void put_le16(uint8_t *buf, uint16_t val) { - buf[0] = val; - buf[1] = val >> 8; -} - -static void put_le32(uint8_t *buf, uint32_t val) { - buf[0] = val; - buf[1] = val >> 8; - buf[2] = val >> 16; - buf[3] = val >> 24; -} - -static int cywbt_set_baudrate(uint32_t baudrate) { - uint8_t buf[6]; - put_le16(buf, 0); - put_le32(buf + 2, baudrate); - return cywbt_hci_cmd(0x3f, 0x18, 6, buf); -} - -// download firmware -static int cywbt_download_firmware(const uint8_t *firmware) { - cywbt_hci_cmd(0x3f, 0x2e, 0, NULL); - - bool last_packet = false; - while (!last_packet) { - uint8_t *buf = mp_bluetooth_hci_cmd_buf; - memcpy(buf + 1, firmware, 3); - firmware += 3; - last_packet = buf[1] == 0x4e; - if (buf[2] != 0xfc) { - printf("fail1 %02x\n", buf[2]); - break; - } - uint8_t len = buf[3]; - - memcpy(buf + 4, firmware, len); - firmware += len; - - buf[0] = 1; - cywbt_hci_cmd_raw(4 + len, buf); - if (buf[0] != 0) { - printf("fail3 %02x\n", buf[0]); - break; - } - } - - // RF switch must select high path during BT patch boot - #if MICROPY_HW_ENABLE_RF_SWITCH - mp_hal_pin_config(CYW43_PIN_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); - #endif - mp_hal_delay_ms(10); // give some time for CTS to go high - #ifdef CYW43_PIN_BT_CTS - cywbt_wait_cts_low(); - #endif - #if MICROPY_HW_ENABLE_RF_SWITCH - // Select chip antenna (could also select external) - mp_hal_pin_config(CYW43_PIN_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_DOWN, 0); - #endif - - mp_bluetooth_hci_uart_set_baudrate(115200); - cywbt_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY); - mp_bluetooth_hci_uart_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY); - - return 0; -} - -int mp_bluetooth_hci_controller_init(void) { - // This is called immediately after the UART is initialised during stack initialisation. - - mp_hal_pin_output(CYW43_PIN_BT_REG_ON); - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - #ifdef CYW43_PIN_BT_HOST_WAKE - mp_hal_pin_input(CYW43_PIN_BT_HOST_WAKE); - #endif - #ifdef CYW43_PIN_BT_DEV_WAKE - mp_hal_pin_output(CYW43_PIN_BT_DEV_WAKE); - mp_hal_pin_low(CYW43_PIN_BT_DEV_WAKE); - #endif - - #if MICROPY_HW_ENABLE_RF_SWITCH - // TODO don't select antenna if wifi is enabled - mp_hal_pin_config(CYW43_PIN_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power - mp_hal_pin_high(CYW43_PIN_WL_GPIO_4); // Turn the RF-switch on - #endif - - uint8_t buf[256]; - - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - mp_bluetooth_hci_uart_set_baudrate(115200); - mp_hal_delay_ms(100); - mp_hal_pin_high(CYW43_PIN_BT_REG_ON); - #ifdef CYW43_PIN_BT_CTS - cywbt_wait_cts_low(); - #else - mp_hal_delay_ms(100); - #endif - - // Reset - cywbt_hci_cmd(0x03, 0x0003, 0, NULL); - - #ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE - // Change baudrate - cywbt_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE); - mp_bluetooth_hci_uart_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE); - #endif - - cywbt_download_firmware((const uint8_t *)&cyw43_btfw_4343A1[0]); - - // Reset - cywbt_hci_cmd(0x03, 0x0003, 0, NULL); - - // Set BD_ADDR (sent as little endian) - uint8_t bdaddr[6]; - mp_hal_get_mac(MP_HAL_MAC_BDADDR, bdaddr); - buf[0] = bdaddr[5]; - buf[1] = bdaddr[4]; - buf[2] = bdaddr[3]; - buf[3] = bdaddr[2]; - buf[4] = bdaddr[1]; - buf[5] = bdaddr[0]; - cywbt_hci_cmd(0x3f, 0x0001, 6, buf); - - // Set local name - // memset(buf, 0, 248); - // memcpy(buf, "PYBD-BLE", 8); - // cywbt_hci_cmd(0x03, 0x0013, 248, buf); - - // Configure sleep mode - cywbt_hci_cmd(0x3f, 0x27, 12, (const uint8_t *)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00"); - - // HCI_Write_LE_Host_Support - cywbt_hci_cmd(3, 109, 2, (const uint8_t *)"\x01\x00"); - - #ifdef CYW43_PIN_BT_DEV_WAKE - mp_hal_pin_high(CYW43_PIN_BT_DEV_WAKE); // let sleep - #endif - - return 0; -} - -int mp_bluetooth_hci_controller_deinit(void) { - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - - return 0; -} - -#ifdef CYW43_PIN_BT_DEV_WAKE -static uint32_t bt_sleep_ticks; -#endif - -int mp_bluetooth_hci_controller_sleep_maybe(void) { - #ifdef CYW43_PIN_BT_DEV_WAKE - if (mp_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) == 0) { - if (mp_hal_ticks_ms() - bt_sleep_ticks > 500) { - mp_hal_pin_high(CYW43_PIN_BT_DEV_WAKE); // let sleep - } - } - #endif - return 0; -} - -bool mp_bluetooth_hci_controller_woken(void) { - #ifdef CYW43_PIN_BT_HOST_WAKE - bool host_wake = mp_hal_pin_read(CYW43_PIN_BT_HOST_WAKE); - /* - // this is just for info/tracing purposes - static bool last_host_wake = false; - if (host_wake != last_host_wake) { - printf("HOST_WAKE change %d -> %d\n", last_host_wake, host_wake); - last_host_wake = host_wake; - } - */ - return host_wake; - #else - return true; - #endif -} - -int mp_bluetooth_hci_controller_wakeup(void) { - #ifdef CYW43_PIN_BT_DEV_WAKE - bt_sleep_ticks = mp_hal_ticks_ms(); - - if (mp_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) == 1) { - mp_hal_pin_low(CYW43_PIN_BT_DEV_WAKE); // wake up - // Use delay_us rather than delay_ms to prevent running the scheduler (which - // might result in more BLE operations). - mp_hal_delay_us(5000); // can't go lower than this - } - #endif - - return 0; -} - -#endif From f5cb9eb9746bea3dc49cc1ae0d424058fb7a09f2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 10:43:38 +1000 Subject: [PATCH 0569/2098] top: Bump Ruff version to v0.11.6. Brings it into sync with a matching change to micropython-lib (which was much older). Includes one small automatic fix. Signed-off-by: Angus Gratton --- .github/workflows/ruff.yml | 4 ++-- .pre-commit-config.yaml | 4 ++-- tools/mpy_ld.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 9265c25bcba..4c4a2a3162e 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # ruff version should be kept in sync with .pre-commit-config.yaml - - run: pipx install ruff==0.9.6 + # ruff version should be kept in sync with .pre-commit-config.yaml & also micropython-lib + - run: pipx install ruff==0.11.6 - run: ruff check --output-format=github . - run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d07f9b0fda2..ac9785bb592 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,8 +12,8 @@ repos: verbose: true stages: [commit-msg] - repo: https://github.com/charliermarsh/ruff-pre-commit - # Version should be kept in sync with .github/workflows/ruff.yml - rev: v0.9.6 + # Version should be kept in sync with .github/workflows/ruff.yml & also micropython-lib + rev: v0.11.6 hooks: - id: ruff - id: ruff-format diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 70cab2b894b..a47653f900c 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -1131,7 +1131,7 @@ def load_object_file(env, f, felf): elif sym.entry["st_shndx"] == "SHN_UNDEF" and sym["st_info"]["bind"] == "STB_GLOBAL": # Undefined global symbol, needs resolving env.unresolved_syms.append(sym) - if len(dup_errors): + if dup_errors: raise LinkError("\n".join(dup_errors)) @@ -1214,7 +1214,7 @@ def link_objects(env, native_qstr_vals_len): else: undef_errors.append("{}: undefined symbol: {}".format(sym.filename, sym.name)) - if len(undef_errors): + if undef_errors: raise LinkError("\n".join(undef_errors)) # Align sections, assign their addresses, and create full_text From c83e907d9dba3d6be2305b2e2e6b646dd51c495a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:20:19 +1000 Subject: [PATCH 0570/2098] tests/extmod: Skip binascii tests when hexlify/unhexlify don't exist. These functions are only available when `MICROPY_PY_BUILTINS_BYTES_HEX` is enabled. Signed-off-by: Damien George --- tests/extmod/binascii_hexlify.py | 8 ++++---- tests/extmod/binascii_unhexlify.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/extmod/binascii_hexlify.py b/tests/extmod/binascii_hexlify.py index d06029aabaf..ae90b673365 100644 --- a/tests/extmod/binascii_hexlify.py +++ b/tests/extmod/binascii_hexlify.py @@ -1,5 +1,5 @@ try: - import binascii + from binascii import hexlify except ImportError: print("SKIP") raise SystemExit @@ -10,10 +10,10 @@ b"\x7f\x80\xff", b"1234ABCDabcd", ): - print(binascii.hexlify(x)) + print(hexlify(x)) # Two-argument version (now supported in CPython) -print(binascii.hexlify(b"123", ":")) +print(hexlify(b"123", ":")) # zero length buffer -print(binascii.hexlify(b"", b":")) +print(hexlify(b"", b":")) diff --git a/tests/extmod/binascii_unhexlify.py b/tests/extmod/binascii_unhexlify.py index bb663bc5b0c..ec31fb2325a 100644 --- a/tests/extmod/binascii_unhexlify.py +++ b/tests/extmod/binascii_unhexlify.py @@ -1,5 +1,5 @@ try: - import binascii + from binascii import unhexlify except ImportError: print("SKIP") raise SystemExit @@ -10,14 +10,14 @@ b"7f80ff", b"313233344142434461626364", ): - print(binascii.unhexlify(x)) + print(unhexlify(x)) try: - a = binascii.unhexlify(b"0") # odd buffer length + a = unhexlify(b"0") # odd buffer length except ValueError: print("ValueError") try: - a = binascii.unhexlify(b"gg") # digit not hex + a = unhexlify(b"gg") # digit not hex except ValueError: print("ValueError") From a081b2e151565550508ac782ef15db8e9d2a7eff Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:21:04 +1000 Subject: [PATCH 0571/2098] tests/extmod/vfs_lfs_ilistdir_del.py: Skip test if not enough memory. Signed-off-by: Damien George --- tests/extmod/vfs_lfs_ilistdir_del.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/extmod/vfs_lfs_ilistdir_del.py b/tests/extmod/vfs_lfs_ilistdir_del.py index 7b59bc412d9..f463b84b224 100644 --- a/tests/extmod/vfs_lfs_ilistdir_del.py +++ b/tests/extmod/vfs_lfs_ilistdir_del.py @@ -71,5 +71,10 @@ def test(bdev, vfs_class): fs.open("/test", "w").close() -bdev = RAMBlockDevice(30) +try: + bdev = RAMBlockDevice(30) +except MemoryError: + print("SKIP") + raise SystemExit + test(bdev, vfs.VfsLfs2) From 898c04ae0e24ca523a630eecc9ac4b4787e55a7e Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:21:27 +1000 Subject: [PATCH 0572/2098] tests/extmod/vfs_mountinfo.py: Don't import unused errno module. Signed-off-by: Damien George --- tests/extmod/vfs_mountinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/extmod/vfs_mountinfo.py b/tests/extmod/vfs_mountinfo.py index f674e807634..b31dc60ce76 100644 --- a/tests/extmod/vfs_mountinfo.py +++ b/tests/extmod/vfs_mountinfo.py @@ -5,7 +5,6 @@ except ImportError: print("SKIP") raise SystemExit -import errno class Filesystem: From ce7f65f96703bbbe6039ba29e079023a32dccef0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Apr 2025 05:07:28 +0200 Subject: [PATCH 0573/2098] tests/extmod/vfs_posix.py: Fix test on Android. This commit makes a slight change to the vfs_posix test suite to let it pass on Android. On Android, non-root processes can perform most filesystem operations only on a restricted set of directories. The vfs_posix test suite attempted to enumerate the filesystem root directory, and said directory happens to be restricted for non-root processes. This would raise an EACCES OSError and terminate the test with a unexpected failure. To fix this, rather than enumerating the filesystem root directory the enumeration target is the internal shared storage area root - which doesn't have enumeration restrictions for non-root processes. The path is hardcoded because it is guaranteed to be there on pretty much any recent-ish device for now (it stayed the same for more than a decade for compatibility reasons). The proper way would be to query the storage subsystem via a JNI round-trip call, but this introduces too much complexity for something that is unlikely to break going forward. Signed-off-by: Alessandro Gatti --- tests/extmod/vfs_posix.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py index d060c0b9c84..b3ca2753ba9 100644 --- a/tests/extmod/vfs_posix.py +++ b/tests/extmod/vfs_posix.py @@ -29,7 +29,21 @@ print(type(os.stat("/"))) # listdir and ilistdir -print(type(os.listdir("/"))) +target = "/" +try: + import platform + + # On Android non-root users are permitted full filesystem access only to + # selected directories. To let this test pass on bionic, the internal + # user-accessible storage area root is enumerated instead of the + # filesystem root. "/storage/emulated/0" should be there on pretty much + # any recent-ish device; querying the proper location requires a JNI + # round-trip, not really worth it. + if platform.platform().startswith("Android-"): + target = "/storage/emulated/0" +except ImportError: + pass +print(type(os.listdir(target))) # mkdir os.mkdir(temp_dir) From 584fa8800b833b53c6d6f9eb7572774a67304d2f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 17:52:43 +1000 Subject: [PATCH 0574/2098] esp32/tools: Update metrics_esp32 script for ESP-IDF >=v5.4.x. The output of 'idf.py size' has changed, plus some other cleanups around build dir name, etc. Can now run on v5.2.2 and v5.4.1, probably other versions. Signed-off-by: Angus Gratton --- ports/esp32/tools/metrics_esp32.py | 46 +++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/ports/esp32/tools/metrics_esp32.py b/ports/esp32/tools/metrics_esp32.py index 66a6a588ba2..5e65a78c97a 100755 --- a/ports/esp32/tools/metrics_esp32.py +++ b/ports/esp32/tools/metrics_esp32.py @@ -32,6 +32,7 @@ # column of the table is really D/IRAM. import os import re +import shutil import sys import subprocess from dataclasses import dataclass @@ -47,6 +48,13 @@ ) +def rmtree(path): + try: + shutil.rmtree(path) + except FileNotFoundError: + pass + + @dataclass class BuildSizes: idf_ver: str @@ -99,7 +107,7 @@ def __lt__(self, other): def build_dir(self): if self.variant: - return f"build-{self.board}_{self.variant}" + return f"build-{self.board}-{self.variant}" else: return f"build-{self.board}" @@ -124,12 +132,23 @@ def run_make(self, target): def make_size(self): try: size_out = self.run_make("size") - # "Used static DRAM:" or "Used stat D/IRAM:" - RE_DRAM = r"Used stat(?:ic)? D.*: *(\d+) bytes" - RE_IRAM = r"Used static IRAM: *(\d+) bytes" + try: + # pre IDF v5.4 size output + # "Used static DRAM:" or "Used stat D/IRAM:" + RE_DRAM = r"Used stat(?:ic)? D.*: *(\d+) bytes" + RE_IRAM = r"Used static IRAM: *(\d+) bytes" + self.dram_size = re.search(RE_DRAM, size_out).group(1) + self.iram_size = re.search(RE_IRAM, size_out).group(1) + except AttributeError: + # IDF v5.4 size output is much nicer formatted + # Note the pipes in these expressions are not the ASCII/RE | + RE_DRAM = r"│ *DI?RAM *│ *(\d+)" + RE_IRAM = r"│ *IRAM *│ *(\d+)" + self.dram_size = re.search(RE_DRAM, size_out).group(1) + self.iram_size = re.search(RE_IRAM, size_out).group(1) + + # This line is the same on before/after versions RE_BIN = r"Total image size: *(\d+) bytes" - self.dram_size = re.search(RE_DRAM, size_out).group(1) - self.iram_size = re.search(RE_IRAM, size_out).group(1) self.bin_size = re.search(RE_BIN, size_out).group(1) except subprocess.CalledProcessError: self.bin_size = "build failed" @@ -139,13 +158,26 @@ def main(do_clean): if "IDF_PATH" not in os.environ: raise RuntimeError("IDF_PATH must be set") + if not os.path.exists("Makefile"): + raise RuntimeError( + "This script must be run from the ports/esp32 directory, i.e. as ./tools/metrics_esp32.py" + ) + + if "IDF_PYTHON_ENV_PATH" in os.environ: + raise RuntimeError( + "Run this script without any existing ESP-IDF environment active/exported." + ) + sizes = [] for idf_ver in IDF_VERS: switch_ver(idf_ver) + rmtree("managed_components") for board, variant in BUILDS: print(f"Building '{board}'/'{variant}'...", file=sys.stderr) result = BuildSizes(idf_ver, board, variant) - result.run_make("clean") + # Rather than running the 'clean' target, delete the build directory to avoid + # environment version mismatches, etc. + rmtree(result.build_dir()) result.make_size() result.print_summary() sizes.append(result) From dcca3ff602c821e5b252cbe44e669806b0a6f416 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Apr 2025 10:20:33 +1000 Subject: [PATCH 0575/2098] tools/mpremote: Use zlib.compressobj instead of zlib.compress. Because the `wbits` parameter was only added to `zlib.compress` in CPython 3.11. Using `zlib.compressobj` makes the code compatible with much older CPython versions. Fixes issue #17140. Signed-off-by: Damien George --- tools/mpremote/mpremote/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 690b2ea723e..1e13b33afe7 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -649,7 +649,9 @@ def _do_romfs_deploy(state, args): romfs_chunk += bytes(chunk_size - len(romfs_chunk)) if has_deflate_io: # Needs: binascii.a2b_base64, io.BytesIO, deflate.DeflateIO. - romfs_chunk_compressed = zlib.compress(romfs_chunk, wbits=-9) + compressor = zlib.compressobj(wbits=-9) + romfs_chunk_compressed = compressor.compress(romfs_chunk) + romfs_chunk_compressed += compressor.flush() buf = binascii.b2a_base64(romfs_chunk_compressed).strip() transport.exec(f"buf=DeflateIO(BytesIO(a2b_base64({buf})),RAW,9).read()") elif has_a2b_base64: From 076e07197e35cdc0e23bd6f45fd21a36e2162e88 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Mar 2025 11:44:09 +1100 Subject: [PATCH 0576/2098] lib/lwip: Update lwIP to STABLE-2_2_1_RELEASE. This updates lwIP from STABLE-2_2_0_RELEASE, which was released in September 2023. The latest STABLE-2_2_1_RELEASE was released in February 2025. Signed-off-by: Damien George --- lib/lwip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lwip b/lib/lwip index 0a0452b2c39..77dcd25a725 160000 --- a/lib/lwip +++ b/lib/lwip @@ -1 +1 @@ -Subproject commit 0a0452b2c39bdd91e252aef045c115f88f6ca773 +Subproject commit 77dcd25a72509eb83f72b033d219b1d40cd8eb95 From 02eea0da2448f2be9bcc04f340a6a0a1fae18f7b Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 22 Apr 2025 14:53:07 +0200 Subject: [PATCH 0577/2098] py: Make struct-initializing macros compatible with C++. This requires explicitly naming and initializing all members so add that where needed and possible. For MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1 this would require initializing the .callback member, but that's a bit of a waste since the macro is always followed by a call to nlr_push_jump_callback() to initialize exactly that member, so rewrite the macro without initializers. Signed-off-by: stijn --- py/obj.h | 14 +++++++------- py/objtuple.h | 2 +- py/runtime.h | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/py/obj.h b/py/obj.h index 93d1e0ee343..07362522474 100644 --- a/py/obj.h +++ b/py/obj.h @@ -371,25 +371,25 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_0}, .fun._0 = fun_name} + {.base = {.type = &mp_type_fun_builtin_0}, .fun = {._0 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_1}, .fun._1 = fun_name} + {.base = {.type = &mp_type_fun_builtin_1}, .fun = {._1 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_2}, .fun._2 = fun_name} + {.base = {.type = &mp_type_fun_builtin_2}, .fun = {._2 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_3}, .fun._3 = fun_name} + {.base = {.type = &mp_type_fun_builtin_3}, .fun = {._3 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun.var = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun = {.var = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun.var = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun = {.var = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun = {.kw = fun_name}} // These macros are used to define constant map/dict objects // You can put "static" in front of the definition to make it local diff --git a/py/objtuple.h b/py/objtuple.h index 034814b8266..3c82a9edcf3 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -50,7 +50,7 @@ extern const mp_obj_type_t mp_type_attrtuple; #define MP_DEFINE_ATTRTUPLE(tuple_obj_name, fields, nitems, ...) \ const mp_rom_obj_tuple_t tuple_obj_name = { \ - .base = {&mp_type_attrtuple}, \ + .base = {.type = &mp_type_attrtuple}, \ .len = nitems, \ .items = { __VA_ARGS__, MP_ROM_PTR((void *)fields) } \ } diff --git a/py/runtime.h b/py/runtime.h index e8e5a758f8b..77cdd0e203d 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -30,12 +30,12 @@ #include "py/pystack.h" #include "py/cstack.h" -// For use with mp_call_function_1_from_nlr_jump_callback. +// Initialize an nlr_jump_callback_node_call_function_1_t struct for use with +// nlr_push_jump_callback(&ctx.callback, mp_call_function_1_from_nlr_jump_callback); #define MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, f, a) \ - nlr_jump_callback_node_call_function_1_t ctx = { \ - .func = (void (*)(void *))(f), \ - .arg = (a), \ - } + nlr_jump_callback_node_call_function_1_t ctx; \ + ctx.func = (void (*)(void *))(f); \ + ctx.arg = (a) typedef enum { MP_VM_RETURN_NORMAL, From 9a377801dc5a99825d6d613f6affcafe059306c0 Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 22 Apr 2025 13:31:45 +0200 Subject: [PATCH 0578/2098] unix/coveragecpp: Verify struct-initializing macros' C++-compatibility. Add code using all relevant macros to make sure they initialize structs correctly. Signed-off-by: stijn --- ports/unix/coveragecpp.cpp | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/ports/unix/coveragecpp.cpp b/ports/unix/coveragecpp.cpp index 23c3955ae9d..8ba308f6468 100644 --- a/ports/unix/coveragecpp.cpp +++ b/ports/unix/coveragecpp.cpp @@ -17,10 +17,61 @@ extern "C" { #include } +// Invoke all (except one, see below) public API macros which initialize structs to make sure +// they are C++-compatible, meaning they explicitly initialize all struct members. +mp_obj_t f0(); +MP_DEFINE_CONST_FUN_OBJ_0(f0_obj, f0); +mp_obj_t f1(mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_1(f1_obj, f1); +mp_obj_t f2(mp_obj_t, mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_2(f2_obj, f2); +mp_obj_t f3(mp_obj_t, mp_obj_t, mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_3(f3_obj, f3); +mp_obj_t fvar(size_t, const mp_obj_t *); +MP_DEFINE_CONST_FUN_OBJ_VAR(fvar_obj, 1, fvar); +mp_obj_t fvarbetween(size_t, const mp_obj_t *); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fvarbetween_obj, 1, 2, fvarbetween); +mp_obj_t fkw(size_t, const mp_obj_t *, mp_map_t *); +MP_DEFINE_CONST_FUN_OBJ_KW(fkw_obj, 1, fkw); + +static const mp_rom_map_elem_t table[] = { + { MP_ROM_QSTR(MP_QSTR_f0), MP_ROM_PTR(&f0_obj) }, +}; +MP_DEFINE_CONST_MAP(map, table); +MP_DEFINE_CONST_DICT(dict, table); + +static const qstr attrtuple_fields[] = { + MP_QSTR_f0, +}; +MP_DEFINE_ATTRTUPLE(attrtuple, attrtuple_fields, 1, MP_ROM_PTR(&f0_obj)); + +void nlr_cb(void *); +void nlr_cb(void *){ +} + +// The MP_DEFINE_CONST_OBJ_TYPE macro is not C++-compatible because each of the +// MP_DEFINE_CONST_OBJ_TYPE_NARGS_X macros only initializes some of _mp_obj_type_t's +// .slot_index_xxx members but that cannot be fixed to be done in a deterministic way. + + #if defined(MICROPY_UNIX_COVERAGE) // Just to test building of C++ code. static mp_obj_t extra_cpp_coverage_impl() { + MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, nlr_cb, (void *) nlr_cb); + + // To avoid 'error: unused variable [-Werror,-Wunused-const-variable]'. + (void) ctx; + (void) f0_obj; + (void) f1_obj; + (void) f2_obj; + (void) f3_obj; + (void) fvar_obj; + (void) fvarbetween_obj; + (void) fkw_obj; + (void) map; + (void) dict; + (void) attrtuple; return mp_const_none; } From cee0419021396a87bd38bb3231b06311f604fad4 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 22 Apr 2025 09:08:14 -0400 Subject: [PATCH 0579/2098] tools/mpremote/tests: Add tests for errno behavior. Signed-off-by: Anson Mansfield --- tools/mpremote/tests/test_errno.sh | 38 ++++++++++++++++++++++++++ tools/mpremote/tests/test_errno.sh.exp | 25 +++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100755 tools/mpremote/tests/test_errno.sh create mode 100644 tools/mpremote/tests/test_errno.sh.exp diff --git a/tools/mpremote/tests/test_errno.sh b/tools/mpremote/tests/test_errno.sh new file mode 100755 index 00000000000..0899706552d --- /dev/null +++ b/tools/mpremote/tests/test_errno.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# Special ErrorFS so the test can induce arbitrary filesystem errors. +cat << EOF > "${TMP}/fs.py" +import os, vfs, errno + +class ErrorFS: + def mount(self, *a, **k): + pass + def umount(self, *a, **k): + pass + def chdir(self, *a, **k): + pass + def open(self, *a, **k): + raise self.error + +fs = ErrorFS() +vfs.mount(fs, '/fs') +os.chdir('/fs') +EOF + +$MPREMOTE run "${TMP}/fs.py" + +echo ----- +$MPREMOTE resume exec "fs.error = Exception()" +( + $MPREMOTE resume cat :Exception.py || echo "expect error" +) 2> >(head -n1 >&2) # discard traceback specifics but keep main error message + +for errno in ENOENT EISDIR EEXIST ENODEV EINVAL EPERM EOPNOTSUPP ; do +echo ----- +$MPREMOTE resume exec "fs.error = OSError(errno.$errno, '')" +$MPREMOTE resume cat :$errno.py || echo "expect error" +done + +echo ----- +$MPREMOTE resume exec "vfs.umount('/fs')" diff --git a/tools/mpremote/tests/test_errno.sh.exp b/tools/mpremote/tests/test_errno.sh.exp new file mode 100644 index 00000000000..deda52f5fb0 --- /dev/null +++ b/tools/mpremote/tests/test_errno.sh.exp @@ -0,0 +1,25 @@ +----- +mpremote: Error with transport: +expect error +----- +mpremote: cat: ENOENT.py: No such file or directory. +expect error +----- +mpremote: cat: EISDIR.py: Is a directory. +expect error +----- +mpremote: cat: EEXIST.py: File exists. +expect error +----- +mpremote: cat: ENODEV.py: No such device. +expect error +----- +mpremote: cat: EINVAL.py: Invalid argument. +expect error +----- +mpremote: cat: EPERM.py: Operation not permitted. +expect error +----- +mpremote: cat: EOPNOTSUPP.py: Operation not supported. +expect error +----- From 805fe083a3dea76b0acd36ecc436c2b76b808059 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 10:08:36 -0400 Subject: [PATCH 0580/2098] tools/mpremote: Refactor error handling to apply generally to any errno. This rewrites the code that previously manually emitted and caught various OSError subclasses with equivalent code that uses the errno name dictionary to do this generically, and updates the exception handler in do_filesystem to catch them in a similarly-generic fashion using os.strerror to retrieve an appropriate error message text equivalent to the current messages. Note that in the CPython environments where mpremote runs, the call to the OSError constructor already returns an instance of the corresponding mapped exception subtype. Signed-off-by: Anson Mansfield --- tools/mpremote/mpremote/commands.py | 8 ++------ tools/mpremote/mpremote/transport.py | 16 ++++------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 1e13b33afe7..b1e5d3c7ed6 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -393,12 +393,8 @@ def do_filesystem(state, args): ) else: do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force) - except FileNotFoundError as er: - raise CommandError("{}: {}: No such file or directory.".format(command, er.args[0])) - except IsADirectoryError as er: - raise CommandError("{}: {}: Is a directory.".format(command, er.args[0])) - except FileExistsError as er: - raise CommandError("{}: {}: File exists.".format(command, er.args[0])) + except OSError as er: + raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno))) except TransportError as er: raise CommandError("Error with transport:\n{}".format(er.args[0])) diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index 8d30c7f517f..df8ef209aec 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -55,18 +55,10 @@ def __init__(self, status_code, error_output): # Takes a Transport error (containing the text of an OSError traceback) and # raises it as the corresponding OSError-derived exception. def _convert_filesystem_error(e, info): - if "OSError" in e.error_output and "ENOENT" in e.error_output: - return FileNotFoundError(info) - if "OSError" in e.error_output and "EISDIR" in e.error_output: - return IsADirectoryError(info) - if "OSError" in e.error_output and "EEXIST" in e.error_output: - return FileExistsError(info) - if "OSError" in e.error_output and "ENODEV" in e.error_output: - return FileNotFoundError(info) - if "OSError" in e.error_output and "EINVAL" in e.error_output: - return OSError(errno.EINVAL, info) - if "OSError" in e.error_output and "EPERM" in e.error_output: - return OSError(errno.EPERM, info) + if "OSError" in e.error_output: + for code, estr in errno.errorcode.items(): + if estr in e.error_output: + return OSError(code, info) return e From dc46cf15c17ab5bd8371c00e11ee9743229b7868 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 13:52:57 -0400 Subject: [PATCH 0581/2098] tools/mpremote: Fix possibly-missing EOPNOTSUPP errno name. Signed-off-by: Anson Mansfield --- tools/mpremote/mpremote/transport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index df8ef209aec..1b70f9b2edc 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -56,7 +56,10 @@ def __init__(self, status_code, error_output): # raises it as the corresponding OSError-derived exception. def _convert_filesystem_error(e, info): if "OSError" in e.error_output: - for code, estr in errno.errorcode.items(): + for code, estr in [ + *errno.errorcode.items(), + (errno.EOPNOTSUPP, "EOPNOTSUPP"), + ]: if estr in e.error_output: return OSError(code, info) return e From 37fe3f66c3eaac084d9da5b92fabdf84977142f9 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 17 Apr 2025 17:14:16 +0200 Subject: [PATCH 0582/2098] tools/mpremote/tests: Add test for rm -r on /remote vfs. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/test_filesystem.sh | 5 ++++- tools/mpremote/tests/test_filesystem.sh.exp | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index a20d77dfea3..a29015e9872 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -233,4 +233,7 @@ $MPREMOTE resume exec "import os;os.chdir('/')" $MPREMOTE resume rm -r -v :/ramdisk $MPREMOTE resume ls :/ramdisk -echo ----- \ No newline at end of file +echo ----- +# try to delete existing folder in mounted filesystem +$MPREMOTE mount "${TMP}" + rm -rv :package || echo "expect error" +echo ----- diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 16d98c4ded8..3d9d0fe9ae8 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -267,3 +267,8 @@ removed directory: '/ramdisk/package' skipped: '/ramdisk' (vfs mountpoint) ls :/ramdisk ----- +Local directory ${TMP} is mounted at /remote +rm :package +mpremote: rm -r not permitted on /remote directory +expect error +----- From 6406afb1f3ad8216ef205098880b2e602104af5f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 17 Apr 2025 16:52:23 +0200 Subject: [PATCH 0583/2098] tools/mpremote: Prevent deletion of /remote files via rm -r. Removes the risk of inadvertently deleting files on the host by preventing the deletion of files via `rm -r` on the `/remote` vfs mount point. Fixes issue #17147. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/commands.py | 11 +++++++++++ tools/mpremote/mpremote/transport_serial.py | 16 +++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index b1e5d3c7ed6..452384728ab 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -303,6 +303,17 @@ def _mkdir(a, *b): def do_filesystem_recursive_rm(state, path, args): if state.transport.fs_isdir(path): + if state.transport.mounted: + r_cwd = state.transport.eval("os.getcwd()") + abs_path = os.path.normpath( + os.path.join(r_cwd, path) if not os.path.isabs(path) else path + ) + if isinstance(state.transport, SerialTransport) and abs_path.startswith( + f'{SerialTransport.fs_hook_mount}/' + ): + raise CommandError( + f"rm -r not permitted on {SerialTransport.fs_hook_mount} directory" + ) for entry in state.transport.fs_listdir(path): do_filesystem_recursive_rm(state, _remote_path_join(path, entry.name), args) if path: diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index 6aed0bb496b..53fc48553b1 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -42,6 +42,8 @@ class SerialTransport(Transport): + fs_hook_mount = "/remote" # MUST match the mount point in fs_hook_code + def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None): self.in_raw_repl = False self.use_raw_paste = True @@ -375,7 +377,11 @@ def write_ctrl_d(self, out_callback): self.serial = self.serial.orig_serial # Provide a message about the remount. - out_callback(bytes(f"\r\nRemount local directory {self.cmd.root} at /remote\r\n", "utf8")) + out_callback( + bytes( + f"\r\nRemount local directory {self.cmd.root} at {self.fs_hook_mount}\r\n", "utf8" + ) + ) # Enter raw REPL and re-mount the remote filesystem. self.serial.write(b"\x01") @@ -392,7 +398,7 @@ def write_ctrl_d(self, out_callback): def umount_local(self): if self.mounted: - self.exec('os.umount("/remote")') + self.exec(f'os.umount("{self.fs_hook_mount}")') self.mounted = False self.serial = self.serial.orig_serial @@ -413,7 +419,7 @@ def umount_local(self): "CMD_RMDIR": 13, } -fs_hook_code = """\ +fs_hook_code = f"""\ import os, io, struct, micropython SEEK_SET = 0 @@ -746,8 +752,8 @@ def open(self, path, mode): def __mount(): - os.mount(RemoteFS(RemoteCommand()), '/remote') - os.chdir('/remote') + os.mount(RemoteFS(RemoteCommand()), '{SerialTransport.fs_hook_mount}') + os.chdir('{SerialTransport.fs_hook_mount}') """ # Apply basic compression on hook code. From cd71db0172c9f0905fd3d3438b1f2cf70da11dfa Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:44:39 +0100 Subject: [PATCH 0584/2098] zephyr: Fix prj.conf for v4.1-rc1. The (deprecated) kconfig option NET_SOCKETS_POSIX_NAMES was removed in commit abad505bdeed6102061767f45acd63323973f564 so remove it from our configuration. As the option has been deprecated longer, this also works for v3.7 and v4.0 the other still supported versions. Signed-off-by: Detlev Zundel --- ports/zephyr/prj.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index ae37c3ff076..0325cddd206 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -29,7 +29,6 @@ CONFIG_NET_IPV6=y CONFIG_NET_UDP=y CONFIG_NET_TCP=y CONFIG_NET_SOCKETS=y -CONFIG_NET_SOCKETS_POSIX_NAMES=n CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_NET_CONFIG_SETTINGS=y From cd3eaad05cf94cd86e9762c854c68a4d4b5c2fab Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:45:25 +0100 Subject: [PATCH 0585/2098] zephyr: Fix call to thread_analyzer_print for v4.0. Commit 1b6e0f64796dfd6f86a8679ea6d24e1fca1e63a8 for Zephyr v4.0.0 changed the function "thread_analyzer_print" to require a cpu argument and allow thread analysis on each cpu separately. The argument is ignored when THREAD_ANALYZER_AUTO_SEPARATE_CORES=n which is the default on single core machines. Promote this change to the MicroPython zephyr module. Signed-off-by: Detlev Zundel Signed-off-by: Maureen Helm --- docs/library/zephyr.rst | 8 ++++++-- ports/zephyr/modzephyr.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/library/zephyr.rst b/docs/library/zephyr.rst index 10676d90852..1a106d50ea7 100644 --- a/docs/library/zephyr.rst +++ b/docs/library/zephyr.rst @@ -22,9 +22,10 @@ Functions Returns the thread id of the current thread, which is used to reference the thread. -.. function:: thread_analyze() +.. function:: thread_analyze(cpu) - Runs the Zephyr debug thread analyzer on the current thread and prints stack size statistics in the format: + Runs the Zephyr debug thread analyzer on the current thread on the given cpu + and prints stack size statistics in the format: "``thread_name``-20s: STACK: unused ``available_stack_space`` usage ``stack_space_used`` / ``stack_size`` (``percent_stack_space_used`` %); CPU: ``cpu_utilization`` %" @@ -35,6 +36,9 @@ Functions For more information, see documentation for Zephyr `thread analyzer `_. + Note that the ``cpu`` argument is only used in Zephyr v4.0.0 and + newer and ignored otherwise. + .. function:: shell_exec(cmd_in) Executes the given command on an UART backend. This function can only be accessed if ``CONFIG_SHELL_BACKEND_SERIAL`` diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index c059c7e3957..08fdf5c5aa4 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -48,11 +49,16 @@ static mp_obj_t mod_current_tid(void) { static MP_DEFINE_CONST_FUN_OBJ_0(mod_current_tid_obj, mod_current_tid); #ifdef CONFIG_THREAD_ANALYZER -static mp_obj_t mod_thread_analyze(void) { +static mp_obj_t mod_thread_analyze(mp_obj_t cpu_in) { + #if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(4, 0, 0) + unsigned int cpu = mp_obj_get_int(cpu_in); + thread_analyzer_print(cpu); + #else thread_analyzer_print(); + #endif return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_analyze_obj, mod_thread_analyze); +static MP_DEFINE_CONST_FUN_OBJ_1(mod_thread_analyze_obj, mod_thread_analyze); #endif #ifdef CONFIG_SHELL_BACKEND_SERIAL From b83606fe337614543f18f0ddf4a3250974b93d3c Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:40:14 +0100 Subject: [PATCH 0586/2098] zephyr: Remove reference to CONFIG_MMC_VOLUME_NAME for v4.0. Commit 07a8e3253a2d8a2076c9c83c4ed4158fa3fbb2a2 removes CONFIG_MMC_VOLUME_NAME from the Kconfig space. Instead we need to use the device tree to find the "disk-name" property of "zephyr,mmc-disk" devices. Signed-off-by: Detlev Zundel --- ports/zephyr/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 9fae2b3a873..206b7f92d39 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -30,6 +30,7 @@ #include #include +#include #ifdef CONFIG_NETWORKING #include #endif @@ -97,7 +98,11 @@ static void vfs_init(void) { int ret = 0; #ifdef CONFIG_DISK_DRIVER_SDMMC + #if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(4, 0, 0) + mp_obj_t args[] = { mp_obj_new_str_from_cstr(DT_PROP(DT_INST(0, zephyr_sdmmc_disk), disk_name)) }; + #else mp_obj_t args[] = { mp_obj_new_str_from_cstr(CONFIG_SDMMC_VOLUME_NAME) }; + #endif bdev = MP_OBJ_TYPE_GET_SLOT(&zephyr_disk_access_type, make_new)(&zephyr_disk_access_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/sd"; #elif defined(CONFIG_FLASH_MAP) && FIXED_PARTITION_EXISTS(storage_partition) From 3c8d1b13f5b7e60e491e3a19734c237e1485e413 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Sun, 10 Nov 2024 08:24:02 -0600 Subject: [PATCH 0587/2098] zephyr: Upgrade to Zephyr v4.0.0. Updates the Zephyr port build instructions. The CI is updated to use Zephyr docker image 0.27.4, SDK 0.17.0 and the latest Zephyr release tag. Tested on max32690fthr and frdm_k64f. Signed-off-by: Maureen Helm Signed-off-by: Detlev Zundel --- docs/zephyr/tutorial/repl.rst | 2 +- ports/zephyr/README.md | 15 +++++++++------ tools/ci.sh | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/zephyr/tutorial/repl.rst b/docs/zephyr/tutorial/repl.rst index db7b7333d24..199dda2b7ae 100644 --- a/docs/zephyr/tutorial/repl.rst +++ b/docs/zephyr/tutorial/repl.rst @@ -31,7 +31,7 @@ With your serial program open (PuTTY, screen, picocom, etc) you may see a blank screen with a flashing cursor. Press Enter (or reset the board) and you should be presented with the following text:: - *** Booting Zephyr OS build v3.7.0 *** + *** Booting Zephyr OS build v4.0.0 *** MicroPython v1.24.0-preview.179.g5b85b24bd on 2024-08-05; zephyr-frdm_k64f with mk64f12 Type "help()" for more information. >>> diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 4590eb7199c..84adf963952 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -4,10 +4,13 @@ MicroPython port to Zephyr RTOS This is a work-in-progress port of MicroPython to Zephyr RTOS (http://zephyrproject.org). -This port requires Zephyr version v3.7.0, and may also work on higher -versions. All boards supported -by Zephyr (with standard level of features support, like UART console) -should work with MicroPython (but not all were tested). +This port tries to support all Zephyr versions supported upstream, +i.e. currently v3.7 (LTS), v4.0 and the development branch. The CI is +setup to use the latest version, i.e. v4.0. + +All boards supported by Zephyr (with standard level of features +support, like UART console) should work with MicroPython (but not all +were tested). Features supported at this time: @@ -39,13 +42,13 @@ setup is correct. If you already have Zephyr installed but are having issues building the MicroPython port then try installing the correct version of Zephyr via: - $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v3.7.0 + $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v4.0.0 Alternatively, you don't have to redo the Zephyr installation to just switch from master to a tagged release, you can instead do: $ cd zephyrproject/zephyr - $ git checkout v3.7.0 + $ git checkout v4.0.0 $ west update With Zephyr installed you may then need to configure your environment, diff --git a/tools/ci.sh b/tools/ci.sh index cfc9754837f..6f8d1cb80c4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -818,9 +818,9 @@ function ci_windows_build { ######################################################################################## # ports/zephyr -ZEPHYR_DOCKER_VERSION=v0.26.13 -ZEPHYR_SDK_VERSION=0.16.8 -ZEPHYR_VERSION=v3.7.0 +ZEPHYR_DOCKER_VERSION=v0.27.4 +ZEPHYR_SDK_VERSION=0.17.0 +ZEPHYR_VERSION=v4.0.0 function ci_zephyr_setup { IMAGE=ghcr.io/zephyrproject-rtos/ci:${ZEPHYR_DOCKER_VERSION} From f4a7e713ea8b374de04c471d877e442bf6ff48e8 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 12:53:33 +0530 Subject: [PATCH 0588/2098] zephyr/machine_pwm: Implement PWM support. Implement PWM support using standard zephyr APIs, exposed as the standard MicroPython `machine.PWM` class. Signed-off-by: Ayush Singh --- ports/zephyr/README.md | 1 + ports/zephyr/machine_pwm.c | 205 ++++++++++++++++++++++++++++++++++++ ports/zephyr/mpconfigport.h | 2 + 3 files changed, 208 insertions(+) create mode 100644 ports/zephyr/machine_pwm.c diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 84adf963952..fc18d25c0aa 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -19,6 +19,7 @@ Features supported at this time: * `machine.Pin` class for GPIO control, with IRQ support. * `machine.I2C` class for I2C control. * `machine.SPI` class for SPI control. +* `machine.PWM` class for PWM control * `socket` module for networking (IPv4/IPv6). * "Frozen modules" support to allow to bundle Python modules together with firmware. Including complete applications, including with diff --git a/ports/zephyr/machine_pwm.c b/ports/zephyr/machine_pwm.c new file mode 100644 index 00000000000..5720dac9caf --- /dev/null +++ b/ports/zephyr/machine_pwm.c @@ -0,0 +1,205 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Ayush Singh + * + * 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 "extmod/modmachine.h" +#include "py/runtime.h" +#include "stdint.h" +#include "zephyr/drivers/pwm.h" +#include "zephyr_device.h" + +#define VALUE_NOT_SET (-1) + +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + const struct device *pwm; + uint32_t channel; + int32_t freq; + int32_t duty_u16; + int32_t duty_ns; + pwm_flags_t flags; +} machine_pwm_obj_t; + +static void configure_pwm(machine_pwm_obj_t *self) { + const uint32_t period = NSEC_PER_SEC / self->freq; + + if ((self->duty_u16 == VALUE_NOT_SET && self->duty_ns == VALUE_NOT_SET) || + self->freq == VALUE_NOT_SET) { + mp_raise_ValueError(MP_ERROR_TEXT("Frequency and duty values must be set")); + } + + if (self->duty_ns == VALUE_NOT_SET) { + self->duty_ns = (self->duty_u16 * period) / UINT16_MAX; + } + + if (self->duty_ns < 0) { + self->duty_ns = 0; + } else if (self->duty_ns > period) { + self->duty_ns = period; + } + + pwm_set(self->pwm, self->channel, period, self->duty_ns, self->flags); +} + +/******************************************************************************/ +// MicroPython bindings for PWM + +static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, + mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "pwm, self->channel); + + if (self->duty_ns != VALUE_NOT_SET) { + mp_printf(print, " duty_ns=%d", self->duty_ns); + } else { + mp_printf(print, " duty_u16=%d", self->duty_u16); + } + + mp_printf(print, " freq=%d, flags=%u>", self->freq, self->flags); +} + +// This called from pwm.init() method +static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, + const mp_obj_t *pos_args, + mp_map_t *kw_args) { + + enum { + ARG_freq, + ARG_duty_u16, + ARG_duty_ns, + ARG_invert, + }; + static const mp_arg_t allowed_args[] = { + {MP_QSTR_freq, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_duty_u16, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_duty_ns, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), + allowed_args, args); + + // Maybe change PWM timer + if (args[ARG_freq].u_int > 0) { + self->freq = args[ARG_freq].u_int; + } + + // Set duty_u16 cycle? + int32_t duty = args[ARG_duty_u16].u_int; + if (duty >= 0) { + self->duty_u16 = duty; + self->duty_ns = VALUE_NOT_SET; + } + // Set duty_ns value? + duty = args[ARG_duty_ns].u_int; + if (duty >= 0) { + self->duty_ns = duty; + self->duty_u16 = VALUE_NOT_SET; + } + + self->flags = 0; + if (args[ARG_invert].u_int >= 0) { + self->flags |= PWM_POLARITY_INVERTED; + } + + configure_pwm(self); +} + +// PWM(pin-tuple, freq, [args]) +static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, + const mp_obj_t *args) { + mp_obj_t *items; + uint32_t wanted_chan; + const struct device *wanted_pwm; + + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Get referred Pin object(s) + if (!mp_obj_is_type(args[0], &mp_type_tuple)) { + mp_raise_ValueError( + MP_ERROR_TEXT("Pin id must be tuple of (\"pwm_x\", channel#)")); + } + + mp_obj_get_array_fixed_n(args[0], 2, &items); + wanted_pwm = zephyr_device_find(items[0]); + wanted_chan = mp_obj_get_int(items[1]); + + machine_pwm_obj_t *pwm = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + pwm->pwm = wanted_pwm; + pwm->channel = wanted_chan; + pwm->duty_ns = VALUE_NOT_SET; + pwm->duty_u16 = VALUE_NOT_SET; + pwm->freq = VALUE_NOT_SET; + pwm->flags = 0; + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_pwm_init_helper(pwm, n_args - 1, args + 1, &kw_args); + } + + return (mp_obj_t)pwm; +} + +// This called from pwm.deinit() method +static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + self->duty_ns = 0; + self->duty_u16 = VALUE_NOT_SET; + configure_pwm(self); +} + +static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->freq); +} + +static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + self->freq = freq; + configure_pwm(self); +} + +static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, + mp_int_t duty_ns) { + self->duty_ns = duty_ns; + self->duty_u16 = VALUE_NOT_SET; + configure_pwm(self); +} + +static mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->duty_ns); +} + +static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->duty_u16); +} + +static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, + mp_int_t duty_u16) { + self->duty_ns = VALUE_NOT_SET; + self->duty_u16 = duty_u16; + configure_pwm(self); +} diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index c4fa6c5b954..e015776a4eb 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -72,6 +72,8 @@ #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/zephyr/machine_wdt.c" #endif +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/zephyr/machine_pwm.c" #define MICROPY_PY_STRUCT (0) #ifdef CONFIG_NETWORKING // If we have networking, we likely want errno comfort From c9c39b88afe1d51b9bb4984cb19b787ac5834c52 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 13:13:39 +0530 Subject: [PATCH 0589/2098] docs/zephyr: Add quick reference for PWM support. Add docs for PWM support. Signed-off-by: Ayush Singh --- docs/zephyr/quickref.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/zephyr/quickref.rst b/docs/zephyr/quickref.rst index 63d4bced039..a7ae6986077 100644 --- a/docs/zephyr/quickref.rst +++ b/docs/zephyr/quickref.rst @@ -56,6 +56,21 @@ Use the :ref:`machine.Pin ` class:: switch = Pin(("gpioc", 6), Pin.IN) # create input pin for a switch switch.irq(lambda t: print("SW2 changed")) # enable an interrupt when switch state is changed +PWM +--- + +Use the :ref:`machine.PWM ` class:: + + from machine import PWM + + pwm = PWM(("pwm0", 0), freq=3921568, duty_ns=200, invert=True) # create pwm on PWM0 + print(pwm) # print pwm + + print(pwm.duty_ns()) # print pwm duty cycle in nanoseconds + pwm.duty_ns(255) # set new pwm duty cycle in nanoseconds + + pwm.deinit() + Hardware I2C bus ---------------- From f9a755c91c8b002407ffb930d2bdaa7e85b2c350 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 12:27:14 +0530 Subject: [PATCH 0590/2098] zephyr/boards: Enable PWM on beagleconnect_freedom. Enable PWM config for bcf. Signed-off-by: Ayush Singh --- .../zephyr/boards/beagleconnect_freedom.conf | 1 + .../boards/beagleconnect_freedom.overlay | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ports/zephyr/boards/beagleconnect_freedom.overlay diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf index 14ce9c526e0..1e3f6037bd8 100644 --- a/ports/zephyr/boards/beagleconnect_freedom.conf +++ b/ports/zephyr/boards/beagleconnect_freedom.conf @@ -1,4 +1,5 @@ # Hardware features +CONFIG_PWM=y CONFIG_I2C=y CONFIG_SPI=y diff --git a/ports/zephyr/boards/beagleconnect_freedom.overlay b/ports/zephyr/boards/beagleconnect_freedom.overlay new file mode 100644 index 00000000000..7dd4469c992 --- /dev/null +++ b/ports/zephyr/boards/beagleconnect_freedom.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Ayush Singh + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&pinctrl { + /* MB1 PWM */ + pwm0_default: pwm0_default { + pinmux = <17 IOC_PORT_MCU_PORT_EVENT1>; + bias-disable; + drive-strength = <2>; + }; + + /* MB2 PWM */ + pwm1_default: pwm1_default { + pinmux = <19 IOC_PORT_MCU_PORT_EVENT3>; + bias-disable; + drive-strength = <2>; + }; +}; + +&gpt0 { + status = "okay"; +}; + +&gpt1 { + status = "okay"; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-names = "default"; +}; + +&pwm1 { + status = "okay"; + pinctrl-0 = <&pwm1_default>; + pinctrl-names = "default"; +}; From d939511dae6b29c0d65a066a5eafafdcb4a5524b Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 29 Nov 2024 08:58:21 +0100 Subject: [PATCH 0591/2098] zephyr: Create options to enable frozen modules. Enables the ability to use frozen modules in the zephyr port. Enabled by adding `CONFIG_MICROPY_FROZEN_MODULES` to the board configuration file. Manually set manifest path with `CONFIG_MICROPY_FROZEN_MANIFEST`. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 10 ++++++++-- ports/zephyr/Kconfig | 8 ++++++++ ports/zephyr/boards/manifest.py | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 ports/zephyr/boards/manifest.py diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index b9550890667..4f457f4a58c 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -34,6 +34,11 @@ set(MICROPY_TARGET micropython) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) +if (CONFIG_MICROPY_FROZEN_MODULES) + cmake_path(ABSOLUTE_PATH CONFIG_MICROPY_FROZEN_MANIFEST BASE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + set(MICROPY_FROZEN_MANIFEST ${CONFIG_MICROPY_FROZEN_MANIFEST}) +endif() + set(MICROPY_SOURCE_PORT main.c help.c @@ -115,10 +120,11 @@ zephyr_library_compile_definitions( zephyr_library_sources(${MICROPY_SOURCE_QSTR}) zephyr_library_link_libraries(kernel) -add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) - include(${MICROPY_DIR}/py/mkrules.cmake) +add_dependencies(BUILD_VERSION_HEADER zephyr_generated_headers) +add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) + target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index 227e943bcd1..f8d15431559 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -41,6 +41,14 @@ config MICROPY_VFS_LFS1 config MICROPY_VFS_LFS2 bool "LittleFs version 2 file system" +config MICROPY_FROZEN_MODULES + bool "Enable Frozen Modules" + +config MICROPY_FROZEN_MANIFEST + string "Path to Frozen Modules manifest.py" + depends on MICROPY_FROZEN_MODULES + default "boards/manifest.py" + endmenu # MicroPython Options source "Kconfig.zephyr" diff --git a/ports/zephyr/boards/manifest.py b/ports/zephyr/boards/manifest.py new file mode 100644 index 00000000000..df1169c081c --- /dev/null +++ b/ports/zephyr/boards/manifest.py @@ -0,0 +1,7 @@ +# This is an example frozen module manifest. Enable this by configuring +# the Zephyr project and enabling the frozen modules config feature. + +freeze("$(PORT_DIR)/modules") + +# Require a micropython-lib module. +require("upysh") From e3d9d8ef51d3668ce51ab3cd7e610109a8f9db98 Mon Sep 17 00:00:00 2001 From: Patrick Joy Date: Sat, 8 Mar 2025 22:11:35 +1100 Subject: [PATCH 0592/2098] zephyr/boards: Add nrf5340dk board configuration. Add support for the nrf5340dk. This DK has a MX25R64 8mb external QSPI flash chip. Compile using: $ west build -b nrf5340dk/nrf5340/cpuapp Signed-off-by: Patrick Joy --- .../zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf | 2 ++ .../boards/nrf5340dk_nrf5340_cpuapp.overlay | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf create mode 100644 ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay diff --git a/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 00000000000..b4aed364a74 --- /dev/null +++ b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,2 @@ +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000..7310d1e55f7 --- /dev/null +++ b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,16 @@ +// Replace default internal storage partition with external flash + +/delete-node/ &storage_partition; + +&mx25r64 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage_partition: partition@0 { + reg = <0x00000000 0x800000>; + label = "storage"; + }; + }; +}; From 62479f2cb60ae73dcb81d0b0e2b2d147813e0604 Mon Sep 17 00:00:00 2001 From: Patrick Joy Date: Sat, 8 Mar 2025 23:55:53 +1100 Subject: [PATCH 0593/2098] zephyr/boards: Add nrf9151dk board configuration. Add support for the nrf9151dk. This DK has a GD25WB256 32mb external QSPI flash chip. Signed-off-by: Patrick Joy --- ports/zephyr/boards/nrf9151dk_nrf9151.conf | 7 ++++++ ports/zephyr/boards/nrf9151dk_nrf9151.overlay | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 ports/zephyr/boards/nrf9151dk_nrf9151.conf create mode 100644 ports/zephyr/boards/nrf9151dk_nrf9151.overlay diff --git a/ports/zephyr/boards/nrf9151dk_nrf9151.conf b/ports/zephyr/boards/nrf9151dk_nrf9151.conf new file mode 100644 index 00000000000..e89f332ba13 --- /dev/null +++ b/ports/zephyr/boards/nrf9151dk_nrf9151.conf @@ -0,0 +1,7 @@ +# Enable external flash +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y + +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/boards/nrf9151dk_nrf9151.overlay b/ports/zephyr/boards/nrf9151dk_nrf9151.overlay new file mode 100644 index 00000000000..85cab574148 --- /dev/null +++ b/ports/zephyr/boards/nrf9151dk_nrf9151.overlay @@ -0,0 +1,22 @@ +/ { + /* Configure partition manager to use gd25wb256 as the external flash */ + chosen { + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +/delete-node/ &storage_partition; + +&gd25wb256 { + status = "okay"; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage_partition: partition@0 { + reg = <0x00000000 0x2000000>; + label = "storage"; + }; + }; +}; From 3f1df4bacb2aa8f9018011c6a61eff9247248e1b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Apr 2025 10:24:52 +1000 Subject: [PATCH 0594/2098] tests/net_hosted: Only run network loopback test on supported targets. Only a few ports have TCP/IP loopback enabled in their network stack, and this test will only pass on those ports. There's not really any good way to do a feature check for loopback mode without actually running the test and seeing if it passes/fails, so add an explicit check that the test is running on a port known to support loopback. (Enabling loopback on lwIP, eg RPI_PICO_W, costs +568 code and +272 bss and is a rarely used feature, so not worth unconditionally enabling.) Signed-off-by: Damien George --- tests/net_hosted/asyncio_loopback.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/net_hosted/asyncio_loopback.py b/tests/net_hosted/asyncio_loopback.py index fd4674544ce..03513ae6244 100644 --- a/tests/net_hosted/asyncio_loopback.py +++ b/tests/net_hosted/asyncio_loopback.py @@ -1,5 +1,12 @@ # Test network loopback behaviour +import sys + +# Only certain platforms can do TCP/IP loopback. +if sys.platform not in ("darwin", "esp32", "linux"): + print("SKIP") + raise SystemExit + try: import asyncio except ImportError: From ffd7e0e28d030225dcfdfc8af6fb6eb79fbdd56f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 13:19:39 +1000 Subject: [PATCH 0595/2098] tests/ports/rp2: Convert rp2.DMA test to a unittest. This test is rather complicated and benefits from being a unittest. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 233 +++++++++++++++++++-------------- tests/ports/rp2/rp2_dma.py.exp | 31 ----- 2 files changed, 136 insertions(+), 128 deletions(-) delete mode 100644 tests/ports/rp2/rp2_dma.py.exp diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index 62ec1dcf24d..bd7f17913ea 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -4,103 +4,142 @@ import time import machine import rp2 +import unittest is_rp2350 = "RP2350" in sys.implementation._machine -src = bytes(i & 0xFF for i in range(16 * 1024)) - -print("# test basic usage") - -dma = rp2.DMA() - -# Test printing. -print(dma) - -# Test pack_ctrl/unpack_ctrl. -ctrl_dict = rp2.DMA.unpack_ctrl(dma.pack_ctrl()) -if is_rp2350: - for entry in ("inc_read_rev", "inc_write_rev"): - assert entry in ctrl_dict - del ctrl_dict[entry] -for key, value in sorted(ctrl_dict.items()): - print(key, value) - -# Test register access. -dma.read = 0 -dma.write = 0 -dma.count = 0 -dma.ctrl = dma.pack_ctrl() -print(dma.read, dma.write, dma.count, dma.ctrl & 0x01F, dma.channel, dma.registers) -dma.close() - -# Test closing when already closed. -dma.close() - -# Test using when closed. -try: - dma.active() - assert False -except ValueError: - print("ValueError") - -# Test simple memory copy. -print("# test memory copy") -dest = bytearray(1024) -dma = rp2.DMA() -dma.config(read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=False) -print(not any(dest)) -dma.active(True) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() - - -# Test time taken for a large memory copy. -def run_and_time_dma(dma): - ticks_us = time.ticks_us - irq_state = machine.disable_irq() - t0 = ticks_us() - dma.active(True) - while dma.active(): - pass - t1 = ticks_us() - machine.enable_irq(irq_state) - return time.ticks_diff(t1, t0) - - -print("# test timing") -dest = bytearray(16 * 1024) -dma = rp2.DMA() -dma.read = src -dma.write = dest -dma.count = len(dest) // 4 -dma.ctrl = dma.pack_ctrl() -dt = run_and_time_dma(dma) -expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) -print(dt in expected_dt_range) -print(dest[:8], dest[-8:]) -dma.close() - -# Test using .config(trigger=True). -print("# test immediate trigger") -dest = bytearray(1024) -dma = rp2.DMA() -dma.config(read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=True) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() - -# Test the DMA.irq() method. -print("# test irq") -dest = bytearray(1024) -dma = rp2.DMA() -dma.irq(lambda dma: print("irq fired", dma.irq().flags())) -dma.config( - read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(irq_quiet=0), trigger=True -) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() +SRC = bytes(i & 0xFF for i in range(16 * 1024)) + + +class Test(unittest.TestCase): + def setUp(self): + self.dma = rp2.DMA() + + def tearDown(self): + self.dma.close() + + def test_printing(self): + dma = self.dma + self.assertEqual(str(dma), "DMA(0)") + + def test_pack_unpack_ctrl(self): + dma = self.dma + ctrl_dict = rp2.DMA.unpack_ctrl(dma.pack_ctrl()) + if is_rp2350: + self.assertEqual(len(ctrl_dict), 18) + self.assertTrue("inc_read_rev" in ctrl_dict) + self.assertTrue("inc_write_rev" in ctrl_dict) + else: + self.assertEqual(len(ctrl_dict), 16) + self.assertEqual(ctrl_dict["ahb_err"], 0) + self.assertEqual(ctrl_dict["bswap"], 0) + self.assertEqual(ctrl_dict["busy"], 0) + self.assertEqual(ctrl_dict["chain_to"], 0) + self.assertEqual(ctrl_dict["enable"], 1) + self.assertEqual(ctrl_dict["high_pri"], 0) + self.assertEqual(ctrl_dict["inc_read"], 1) + self.assertEqual(ctrl_dict["inc_write"], 1) + self.assertEqual(ctrl_dict["irq_quiet"], 1) + self.assertEqual(ctrl_dict["read_err"], 0) + self.assertEqual(ctrl_dict["ring_sel"], 0) + self.assertEqual(ctrl_dict["ring_size"], 0) + self.assertEqual(ctrl_dict["size"], 2) + self.assertEqual(ctrl_dict["sniff_en"], 0) + self.assertEqual(ctrl_dict["treq_sel"], 63) + self.assertEqual(ctrl_dict["write_err"], 0) + + def test_register_access(self): + dma = self.dma + dma.read = 0 + dma.write = 0 + dma.count = 0 + dma.ctrl = dma.pack_ctrl() + self.assertEqual(dma.read, 0) + self.assertEqual(dma.write, 0) + self.assertEqual(dma.count, 0) + self.assertEqual(dma.ctrl & 0x01F, 25) + self.assertEqual(dma.channel, 0) + self.assertIsInstance(dma.registers, memoryview) + + def test_close(self): + dma = self.dma + dma.close() + + # Test closing when already closed. + dma.close() + + # Test using when closed. + with self.assertRaises(ValueError): + dma.active() + + def test_simple_memory_copy(self): + dma = self.dma + dest = bytearray(1024) + dma.config(read=SRC, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=False) + self.assertFalse(any(dest)) + dma.active(True) + while dma.active(): + pass + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_time_taken_for_large_memory_copy(self): + def run_and_time_dma(dma): + ticks_us = time.ticks_us + irq_state = machine.disable_irq() + t0 = ticks_us() + dma.active(True) + while dma.active(): + pass + t1 = ticks_us() + machine.enable_irq(irq_state) + return time.ticks_diff(t1, t0) + + dma = self.dma + dest = bytearray(16 * 1024) + dma.read = SRC + dma.write = dest + dma.count = len(dest) // 4 + dma.ctrl = dma.pack_ctrl() + dt = run_and_time_dma(dma) + expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) + self.assertIn(dt, expected_dt_range) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_config_trigger(self): + # Test using .config(trigger=True) to start DMA immediately. + dma = self.dma + dest = bytearray(1024) + dma.config(read=SRC, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=True) + while dma.active(): + pass + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_irq(self): + def callback(dma): + nonlocal irq_flags + print("irq fired") + irq_flags = dma.irq().flags() + + dma = self.dma + irq_flags = None + dest = bytearray(1024) + dma.irq(callback) + dma.config( + read=SRC, + write=dest, + count=len(dest) // 4, + ctrl=dma.pack_ctrl(irq_quiet=0), + trigger=True, + ) + while dma.active(): + pass + self.assertEqual(irq_flags, 1) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ports/rp2/rp2_dma.py.exp b/tests/ports/rp2/rp2_dma.py.exp deleted file mode 100644 index 6fad5429b29..00000000000 --- a/tests/ports/rp2/rp2_dma.py.exp +++ /dev/null @@ -1,31 +0,0 @@ -# test basic usage -DMA(0) -ahb_err 0 -bswap 0 -busy 0 -chain_to 0 -enable 1 -high_pri 0 -inc_read 1 -inc_write 1 -irq_quiet 1 -read_err 0 -ring_sel 0 -ring_size 0 -size 2 -sniff_en 0 -treq_sel 63 -write_err 0 -0 0 0 25 0 -ValueError -# test memory copy -True -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test timing -True -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test immediate trigger -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test irq -irq fired 1 -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') From 00a0cd70f54241d4c8fb04c396a636cb0d3a1535 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 13:28:15 +1000 Subject: [PATCH 0596/2098] tests/ports/rp2: Tune rp2.DMA test so it runs in all configurations. Changes in this commit: - Allow the DMA instance to be any instance, not just DMA(0); eg WLAN may be using DMA(0). - Make the DMA timing test run a little faster by preloading `dma.active`. - Run the DMA timing test 10 times and take the average time taken as the test result, to eliminate any big effects of caching. - Change the expected time to `range(30, 80)` to cover RP2040, RP2350, RISC-V variants, and both bytecode and native emitter. - Add a `sleep_ms(1)` after waiting for the IRQ to fire, so that any scheduled code gets a chance to run when the test is compiled with the native emitter. With these changes this test passes reliably on RPI_PICO, RPI_PICO_W, RPI_PICO2, RPI_PICO2_W, RPI_PICO2-RISCV and RPI_PICO2_W-RISCV, in both bytecode and native emitter mode, with and without WLAN enabled. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index bd7f17913ea..436e5ee48ec 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -20,7 +20,7 @@ def tearDown(self): def test_printing(self): dma = self.dma - self.assertEqual(str(dma), "DMA(0)") + self.assertEqual(str(dma), "DMA({})".format(dma.channel)) def test_pack_unpack_ctrl(self): dma = self.dma @@ -34,7 +34,7 @@ def test_pack_unpack_ctrl(self): self.assertEqual(ctrl_dict["ahb_err"], 0) self.assertEqual(ctrl_dict["bswap"], 0) self.assertEqual(ctrl_dict["busy"], 0) - self.assertEqual(ctrl_dict["chain_to"], 0) + self.assertEqual(ctrl_dict["chain_to"], dma.channel) self.assertEqual(ctrl_dict["enable"], 1) self.assertEqual(ctrl_dict["high_pri"], 0) self.assertEqual(ctrl_dict["inc_read"], 1) @@ -58,7 +58,7 @@ def test_register_access(self): self.assertEqual(dma.write, 0) self.assertEqual(dma.count, 0) self.assertEqual(dma.ctrl & 0x01F, 25) - self.assertEqual(dma.channel, 0) + self.assertIn(dma.channel, range(16)) self.assertIsInstance(dma.registers, memoryview) def test_close(self): @@ -86,26 +86,32 @@ def test_simple_memory_copy(self): def test_time_taken_for_large_memory_copy(self): def run_and_time_dma(dma): ticks_us = time.ticks_us + active = dma.active irq_state = machine.disable_irq() t0 = ticks_us() - dma.active(True) - while dma.active(): + active(True) + while active(): pass t1 = ticks_us() machine.enable_irq(irq_state) return time.ticks_diff(t1, t0) - dma = self.dma - dest = bytearray(16 * 1024) - dma.read = SRC - dma.write = dest - dma.count = len(dest) // 4 - dma.ctrl = dma.pack_ctrl() - dt = run_and_time_dma(dma) - expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) - self.assertIn(dt, expected_dt_range) - self.assertEqual(dest[:8], SRC[:8]) - self.assertEqual(dest[-8:], SRC[-8:]) + num_average = 10 + dt_sum = 0 + for _ in range(num_average): + dma = self.dma + dest = bytearray(16 * 1024) + dma.read = SRC + dma.write = dest + dma.count = len(dest) // 4 + dma.ctrl = dma.pack_ctrl() + dt_sum += run_and_time_dma(dma) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + self.tearDown() + self.setUp() + dt = dt_sum // num_average + self.assertIn(dt, range(30, 80)) def test_config_trigger(self): # Test using .config(trigger=True) to start DMA immediately. @@ -136,6 +142,7 @@ def callback(dma): ) while dma.active(): pass + time.sleep_ms(1) # when running as native code, give the scheduler a chance to run self.assertEqual(irq_flags, 1) self.assertEqual(dest[:8], SRC[:8]) self.assertEqual(dest[-8:], SRC[-8:]) From 4117a2d9b5a2e0f3e84e7d49a8f05ae04fac8b39 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Apr 2025 11:54:01 +1000 Subject: [PATCH 0597/2098] tools/ci.sh: Update URL for xtensa-lx106-elf-standalone.tar.gz. The https://github.com/jepler/esp-open-sdk repository has been removed, so use the file hosted at micropython.org (it's the same file). Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 6f8d1cb80c4..a4bd43567c8 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -209,7 +209,7 @@ function ci_esp32_build_s3_c3 { function ci_esp8266_setup { sudo pip3 install pyserial esptool==3.3.1 pyelftools ar - wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz + wget https://micropython.org/resources/xtensa-lx106-elf-standalone.tar.gz zcat xtensa-lx106-elf-standalone.tar.gz | tar x # Remove this esptool.py so pip version is used instead rm xtensa-lx106-elf/bin/esptool.py From e53f262a85349b4871a38d899a30b05b2ed4b62f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 16:00:21 +1000 Subject: [PATCH 0598/2098] tools/mpremote: For mip install, use hash to skip files that exist. When using `mip install`, if a file that needs to be downloaded already exists locally, then the hash of that local file will be computed and if it matches the known hash of the remote file it will not be downloaded. Hashes in mip are guaranteed unique, so this change should never leave stale files on the filesystem. This behaviour follows that of the `mip` package in `micropython-lib`. Signed-off-by: Damien George --- tools/mpremote/mpremote/mip.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index 26ae8bec5ec..fa7974053f4 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -34,6 +34,15 @@ def _ensure_path_exists(transport, path): prefix += "/" +# Check if the specified path exists and matches the hash. +def _check_exists(transport, path, short_hash): + try: + remote_hash = transport.fs_hashfile(path, "sha256") + except FileNotFoundError: + return False + return remote_hash.hex()[: len(short_hash)] == short_hash + + def _rewrite_url(url, branch=None): if not branch: branch = "HEAD" @@ -115,8 +124,11 @@ def _install_json(transport, package_json_url, index, target, version, mpy): raise CommandError(f"Invalid url for package: {package_json_url}") for target_path, short_hash in package_json.get("hashes", ()): fs_target_path = target + "/" + target_path - file_url = f"{index}/file/{short_hash[:2]}/{short_hash}" - _download_file(transport, file_url, fs_target_path) + if _check_exists(transport, fs_target_path, short_hash): + print("Exists:", fs_target_path) + else: + file_url = f"{index}/file/{short_hash[:2]}/{short_hash}" + _download_file(transport, file_url, fs_target_path) for target_path, url in package_json.get("urls", ()): fs_target_path = target + "/" + target_path if base_url and not url.startswith(allowed_mip_url_prefixes): From 6601d4d7ebd9c6ce11a28f63efa0ec0a6a2fa27a Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 29 Nov 2024 09:37:31 +0100 Subject: [PATCH 0599/2098] zephyr: Create ability to use device_next with CDC ACM as REPL. This enables using the newer USB stack and its CDC ACM for the REPL. To switch to it, board file must contain `CONFIG_USB_DEVICE_STACK_NEXT=y` and `CONFIG_USBD_CDC_ACM_CLASS=y`. In the case of a board that is a platform that supports the older device stack, `CONFIG_USB_DEVICE_STACK=n` may be necessary. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 1 + ports/zephyr/Kconfig | 8 ++ ports/zephyr/main.c | 8 ++ ports/zephyr/src/usbd.c | 183 ++++++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 ports/zephyr/src/usbd.c diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 4f457f4a58c..debf2bd2c15 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -128,6 +128,7 @@ add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c + src/usbd.c ) target_link_libraries(app PRIVATE ${MICROPY_TARGET}) diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index f8d15431559..6db0133f4f1 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -49,6 +49,14 @@ config MICROPY_FROZEN_MANIFEST depends on MICROPY_FROZEN_MODULES default "boards/manifest.py" +config MICROPY_USB_DEVICE_VID + hex "USB VID" + default 0x2fe3 + +config MICROPY_USB_DEVICE_PID + hex "USB PID" + default 0x0001 + endmenu # MicroPython Options source "Kconfig.zephyr" diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 206b7f92d39..d4498c1079c 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -63,6 +63,10 @@ static char heap[MICROPY_HEAP_SIZE]; +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) +extern int mp_usbd_init(void); +#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) + void init_zephyr(void) { // We now rely on CONFIG_NET_APP_SETTINGS to set up bootstrap // network addresses. @@ -143,6 +147,10 @@ int real_main(void) { usb_enable(NULL); #endif + #ifdef CONFIG_USB_DEVICE_STACK_NEXT + mp_usbd_init(); + #endif + #if MICROPY_VFS vfs_init(); #endif diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c new file mode 100644 index 00000000000..2444706cbea --- /dev/null +++ b/ports/zephyr/src/usbd.c @@ -0,0 +1,183 @@ +/* +* This file is part of the MicroPython project, http://micropython.org/ +* +* The MIT License (MIT) +* +* Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +* +* 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 +#include +#include + +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) + +#include +LOG_MODULE_REGISTER(mp_usbd); + +/* By default, do not register the USB DFU class DFU mode instance. */ +static const char *const blocklist[] = { + "dfu_dfu", + NULL, +}; + +USBD_DEVICE_DEFINE(mp_usbd, + DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), + CONFIG_MICROPY_USB_DEVICE_VID, CONFIG_MICROPY_USB_DEVICE_PID); + +USBD_DESC_LANG_DEFINE(mp_lang); +USBD_DESC_MANUFACTURER_DEFINE(mp_mfr, "Zephyr Project"); +USBD_DESC_PRODUCT_DEFINE(mp_product, "Micropython on Zephyr RTOS"); +USBD_DESC_SERIAL_NUMBER_DEFINE(mp_sn); + +USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration"); +USBD_DESC_CONFIG_DEFINE(hs_cfg_desc, "HS Configuration"); + +/* not self-powered, no remote wakeup */ +static const uint8_t attributes = 0; + +/* Full speed configuration +* power = 250 * 2 mA = 500mA +*/ +USBD_CONFIGURATION_DEFINE(mp_fs_config, + attributes, + 250, &fs_cfg_desc); + +/* High speed configuration */ +USBD_CONFIGURATION_DEFINE(mp_hs_config, + attributes, + 250, &hs_cfg_desc); + +static void mp_fix_code_triple(struct usbd_context *uds_ctx, + const enum usbd_speed speed) { + /* Always use class code information from Interface Descriptors */ + if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) || + IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS)) { + /* + * Class with multiple interfaces have an Interface + * Association Descriptor available, use an appropriate triple + * to indicate it. + */ + usbd_device_set_code_triple(uds_ctx, speed, + USB_BCC_MISCELLANEOUS, 0x02, 0x01); + } else { + usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0); + } +} + +struct usbd_context *mp_usbd_init_device(usbd_msg_cb_t msg_cb) { + int err; + + err = usbd_add_descriptor(&mp_usbd, &mp_lang); + if (err) { + LOG_ERR("Failed to initialize language descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_mfr); + if (err) { + LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_product); + if (err) { + LOG_ERR("Failed to initialize product descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_sn); + if (err) { + LOG_ERR("Failed to initialize SN descriptor (%d)", err); + return NULL; + } + + if (usbd_caps_speed(&mp_usbd) == USBD_SPEED_HS) { + err = usbd_add_configuration(&mp_usbd, USBD_SPEED_HS, + &mp_hs_config); + if (err) { + LOG_ERR("Failed to add High-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_HS, 1, blocklist); + if (err) { + LOG_ERR("Failed to add register classes"); + return NULL; + } + + mp_fix_code_triple(&mp_usbd, USBD_SPEED_HS); + } + + err = usbd_add_configuration(&mp_usbd, USBD_SPEED_FS, + &mp_fs_config); + if (err) { + LOG_ERR("Failed to add Full-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_FS, 1, blocklist); + if (err) { + LOG_ERR("Failed to add register classes"); + return NULL; + } + + mp_fix_code_triple(&mp_usbd, USBD_SPEED_FS); + + if (msg_cb != NULL) { + err = usbd_msg_register_cb(&mp_usbd, msg_cb); + if (err) { + LOG_ERR("Failed to register message callback"); + return NULL; + } + } + + err = usbd_init(&mp_usbd); + if (err) { + LOG_ERR("Failed to initialize device support"); + return NULL; + } + + return &mp_usbd; +} + +static struct usbd_context *mp_usbd_context; + +int mp_usbd_init(void) { + int err; + + mp_usbd_context = mp_usbd_init_device(NULL); + if (mp_usbd_context == NULL) { + return -ENODEV; + } + + err = usbd_enable(mp_usbd_context); + if (err) { + return err; + } + + return 0; +} + +#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) From 8728db3e414bdddebed5f9439b971a40c62eab9b Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 7 Mar 2025 14:17:42 +0100 Subject: [PATCH 0600/2098] zephyr: Introduce auto-listing of FlashArea Partitions. This enables listing all flash area partitions automagically instead of just sotrage_partitions. It uses the label, and the ID when not present. Signed-off-by: Vdragon --- ports/zephyr/zephyr_storage.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index 484feb11301..40bcef73384 100644 --- a/ports/zephyr/zephyr_storage.c +++ b/ports/zephyr/zephyr_storage.c @@ -139,6 +139,20 @@ MP_DEFINE_CONST_OBJ_TYPE( #endif // CONFIG_DISK_ACCESS #ifdef CONFIG_FLASH_MAP + +#define FLASH_AREA_DEFINE_LABEL(part) CONCAT(MP_QSTR_ID_, DT_STRING_TOKEN(part, label)) +#define FLASH_AREA_DEFINE_NB(part) CONCAT(MP_QSTR_ID_, DT_FIXED_PARTITION_ID(part)) + +#define FLASH_AREA_DEFINE_GETNAME(part) COND_CODE_1(DT_NODE_HAS_PROP(part, label), \ + (FLASH_AREA_DEFINE_LABEL(part)), (FLASH_AREA_DEFINE_NB(part))) + +#define FLASH_AREA_DEFINE_DEFINE(part) { MP_ROM_QSTR(FLASH_AREA_DEFINE_GETNAME(part)), MP_ROM_INT(DT_FIXED_PARTITION_ID(part)) }, + +#define FLASH_AREA_DEFINE(part) COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_MTD_FROM_FIXED_PARTITION(part)), \ + (FLASH_AREA_DEFINE_DEFINE(part)), ()) + +#define FOREACH_PARTITION(n) DT_FOREACH_CHILD(n, FLASH_AREA_DEFINE) + const mp_obj_type_t zephyr_flash_area_type; typedef struct _zephyr_flash_area_obj_t { @@ -244,9 +258,8 @@ static const mp_rom_map_elem_t zephyr_flash_area_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&zephyr_flash_area_readblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&zephyr_flash_area_writeblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&zephyr_flash_area_ioctl_obj) }, - #if FIXED_PARTITION_EXISTS(storage_partition) - { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(FIXED_PARTITION_ID(storage_partition)) }, - #endif + /* Generate list of partition IDs from Zephyr Devicetree */ + DT_FOREACH_STATUS_OKAY(fixed_partitions, FOREACH_PARTITION) }; static MP_DEFINE_CONST_DICT(zephyr_flash_area_locals_dict, zephyr_flash_area_locals_dict_table); From ced7ebb873f923397aa5c9ddc7b286e32eb4e80a Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 30 Apr 2025 15:40:26 +0200 Subject: [PATCH 0601/2098] docs/zephyr: Add zephyr FlashArea IDs docs. Signed-off-by: Vdragon --- docs/zephyr/quickref.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/zephyr/quickref.rst b/docs/zephyr/quickref.rst index a7ae6986077..0e985c70b3e 100644 --- a/docs/zephyr/quickref.rst +++ b/docs/zephyr/quickref.rst @@ -153,6 +153,8 @@ Use the :ref:`zephyr.FlashArea ` class to support filesystem:: f.write('Hello world') # write to the file print(open('/flash/hello.txt').read()) # print contents of the file +The FlashAreas' IDs that are available are listed in the FlashArea module, as ID_*. + Sensor ------ From bee1fd5e7887d48ad2b217f5c4746f6b518f3fd8 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Wed, 16 Oct 2024 17:08:49 +0530 Subject: [PATCH 0602/2098] zephyr/boards: Enable ADC on beagleconnect_freedom. Enable Analog inputs. Requires Zephyr >= v3.8.0. Signed-off-by: Ayush Singh --- ports/zephyr/boards/beagleconnect_freedom.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf index 1e3f6037bd8..8fe2583205b 100644 --- a/ports/zephyr/boards/beagleconnect_freedom.conf +++ b/ports/zephyr/boards/beagleconnect_freedom.conf @@ -1,4 +1,5 @@ # Hardware features +CONFIG_ADC=y CONFIG_PWM=y CONFIG_I2C=y CONFIG_SPI=y From 70ed3151933635429a66937bae2701958b6b47dd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Oct 2024 13:28:37 +1100 Subject: [PATCH 0603/2098] py/malloc: Add mutex for tracked allocations. Fixes thread safety issue that could cause memory corruption on ports with (MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL) - currently only rp2 and unix have this configuration. Adds unit test for TLS sockets that exercises this code path. I wasn't able to make this fail on rp2, the race condition window is pretty narrow and may not have a direct impact on a quiet system. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/malloc.c | 33 ++++++++++++++++++++- tests/extmod/ssl_threads.py | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/extmod/ssl_threads.py diff --git a/py/malloc.c b/py/malloc.c index f557ade44f5..05daeb35d09 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -209,6 +209,31 @@ void m_free(void *ptr) #if MICROPY_TRACKED_ALLOC +#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL +// If there's no GIL, use the GC recursive mutex to protect the tracked node linked list +// under m_tracked_head. +// +// (For ports with GIL, the expectation is to only call tracked alloc functions +// while holding the GIL.) + +static inline void m_tracked_node_lock(void) { + mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1); +} + +static inline void m_tracked_node_unlock(void) { + mp_thread_recursive_mutex_unlock(&MP_STATE_MEM(gc_mutex)); +} + +#else + +static inline void m_tracked_node_lock(void) { +} + +static inline void m_tracked_node_unlock(void) { +} + +#endif + #define MICROPY_TRACKED_ALLOC_STORE_SIZE (!MICROPY_ENABLE_GC) typedef struct _m_tracked_node_t { @@ -222,6 +247,7 @@ typedef struct _m_tracked_node_t { #if MICROPY_DEBUG_VERBOSE static size_t m_tracked_count_links(size_t *nb) { + m_tracked_node_lock(); m_tracked_node_t *node = MP_STATE_VM(m_tracked_head); size_t n = 0; *nb = 0; @@ -234,6 +260,7 @@ static size_t m_tracked_count_links(size_t *nb) { #endif node = node->next; } + m_tracked_node_unlock(); return n; } #endif @@ -248,12 +275,14 @@ void *m_tracked_calloc(size_t nmemb, size_t size) { size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_calloc(%u, %u) -> (%u;%u) %p\n", (int)nmemb, (int)size, (int)n, (int)nb, node); #endif + m_tracked_node_lock(); if (MP_STATE_VM(m_tracked_head) != NULL) { MP_STATE_VM(m_tracked_head)->prev = node; } node->prev = NULL; node->next = MP_STATE_VM(m_tracked_head); MP_STATE_VM(m_tracked_head) = node; + m_tracked_node_unlock(); #if MICROPY_TRACKED_ALLOC_STORE_SIZE node->size = nmemb * size; #endif @@ -278,7 +307,8 @@ void m_tracked_free(void *ptr_in) { size_t nb; size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb); - #endif + #endif // MICROPY_DEBUG_VERBOSE + m_tracked_node_lock(); if (node->next != NULL) { node->next->prev = node->prev; } @@ -287,6 +317,7 @@ void m_tracked_free(void *ptr_in) { } else { MP_STATE_VM(m_tracked_head) = node->next; } + m_tracked_node_unlock(); m_free(node #if MICROPY_MALLOC_USES_ALLOCATED_SIZE #if MICROPY_TRACKED_ALLOC_STORE_SIZE diff --git a/tests/extmod/ssl_threads.py b/tests/extmod/ssl_threads.py new file mode 100644 index 00000000000..4564abd3d80 --- /dev/null +++ b/tests/extmod/ssl_threads.py @@ -0,0 +1,57 @@ +# Ensure that SSL sockets can be allocated from multiple +# threads without thread safety issues +import unittest + +try: + import _thread + import io + import tls + import time +except ImportError: + print("SKIP") + raise SystemExit + + +class TestSocket(io.IOBase): + def write(self, buf): + return len(buf) + + def readinto(self, buf): + return 0 + + def ioctl(self, cmd, arg): + return 0 + + def setblocking(self, value): + pass + + +ITERS = 256 + + +class TLSThreads(unittest.TestCase): + def test_sslsocket_threaded(self): + self.done = False + # only run in two threads: too much RAM demand otherwise, and rp2 only + # supports two anyhow + _thread.start_new_thread(self._alloc_many_sockets, (True,)) + self._alloc_many_sockets(False) + while not self.done: + time.sleep(0.1) + print("done") + + def _alloc_many_sockets(self, set_done_flag): + print("start", _thread.get_ident()) + ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + ctx.verify_mode = tls.CERT_NONE + for n in range(ITERS): + s = TestSocket() + s = ctx.wrap_socket(s, do_handshake_on_connect=False) + s.close() # Free associated resources now from thread, not in a GC pass + print("done", _thread.get_ident()) + if set_done_flag: + self.done = True + + +if __name__ == "__main__": + unittest.main() From 79abdad9e97f18f45650e1abce64ee51c3372953 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 1 May 2025 16:24:13 +1000 Subject: [PATCH 0604/2098] tests/extmod: Rename ssl tests that only use the tls module. Signed-off-by: Angus Gratton --- tests/extmod/{ssl_noleak.py => tls_noleak.py} | 0 tests/extmod/{ssl_threads.py => tls_threads.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/extmod/{ssl_noleak.py => tls_noleak.py} (100%) rename tests/extmod/{ssl_threads.py => tls_threads.py} (100%) diff --git a/tests/extmod/ssl_noleak.py b/tests/extmod/tls_noleak.py similarity index 100% rename from tests/extmod/ssl_noleak.py rename to tests/extmod/tls_noleak.py diff --git a/tests/extmod/ssl_threads.py b/tests/extmod/tls_threads.py similarity index 100% rename from tests/extmod/ssl_threads.py rename to tests/extmod/tls_threads.py From bdb7e036d2400c25beeb2b4252ff51c5255c4020 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Sat, 15 Feb 2025 15:24:50 +0100 Subject: [PATCH 0605/2098] extmod/asyncio: Fix early exit of asyncio scheduler. This commit fixes three open issues related to the asyncio scheduler exiting prematurely when the main task queue is empty, in cases where CPython would not exit (for example, because the main task is not done because it's on a different queue). In the first case, the scheduler exits because running a task via `run_until_complete` did not schedule any dependent tasks. In the other two cases, the scheduler exits because the tasks are queued in an event queue. Tests have been added which reproduce the original issues. These test cases document the unauthorized use of `Event.set()` from a soft IRQ, and are skipped in unsupported environments (webassembly and native emitter). Fixes issues #16759, #16569 and #16318. Signed-off-by: Yoctopuce dev --- extmod/asyncio/core.py | 36 +++++--- tests/extmod/asyncio_event_queue.py | 64 +++++++++++++++ tests/extmod/asyncio_event_queue.py.exp | 5 ++ tests/extmod/asyncio_iterator_event.py | 86 ++++++++++++++++++++ tests/extmod/asyncio_iterator_event.py.exp | 5 ++ tests/extmod/asyncio_wait_for_linked_task.py | 66 +++++++++++++++ tests/run-tests.py | 5 ++ 7 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 tests/extmod/asyncio_event_queue.py create mode 100644 tests/extmod/asyncio_event_queue.py.exp create mode 100644 tests/extmod/asyncio_iterator_event.py create mode 100644 tests/extmod/asyncio_iterator_event.py.exp create mode 100644 tests/extmod/asyncio_wait_for_linked_task.py diff --git a/extmod/asyncio/core.py b/extmod/asyncio/core.py index 8aad234514b..5d46b4b80e2 100644 --- a/extmod/asyncio/core.py +++ b/extmod/asyncio/core.py @@ -163,9 +163,16 @@ def run_until_complete(main_task=None): # A task waiting on _task_queue; "ph_key" is time to schedule task at dt = max(0, ticks_diff(t.ph_key, ticks())) elif not _io_queue.map: - # No tasks can be woken so finished running + # No tasks can be woken cur_task = None - return + if not main_task or not main_task.state: + # no main_task, or main_task is done so finished running + return + # At this point, there is theoretically nothing that could wake the + # scheduler, but it is not allowed to exit either. We keep the code + # running so that a hypothetical debugger (or other such meta-process) + # can get a view of what is happening and possibly abort. + dt = 3 # print('(poll {})'.format(dt), len(_io_queue.map)) _io_queue.wait_io_event(dt) @@ -187,31 +194,33 @@ def run_until_complete(main_task=None): except excs_all as er: # Check the task is not on any event queue assert t.data is None - # This task is done, check if it's the main task and then loop should stop - if t is main_task: + # If it's the main task, it is considered as awaited by the caller + awaited = t is main_task + if awaited: cur_task = None - if isinstance(er, StopIteration): - return er.value - raise er + if not isinstance(er, StopIteration): + t.state = False + raise er + if t.state is None: + t.state = False if t.state: # Task was running but is now finished. - waiting = False if t.state is True: # "None" indicates that the task is complete and not await'ed on (yet). - t.state = None + t.state = False if awaited else None elif callable(t.state): # The task has a callback registered to be called on completion. t.state(t, er) t.state = False - waiting = True + awaited = True else: # Schedule any other tasks waiting on the completion of this task. while t.state.peek(): _task_queue.push(t.state.pop()) - waiting = True + awaited = True # "False" indicates that the task is complete and has been await'ed on. t.state = False - if not waiting and not isinstance(er, excs_stop): + if not awaited and not isinstance(er, excs_stop): # An exception ended this detached task, so queue it for later # execution to handle the uncaught exception if no other task retrieves # the exception in the meantime (this is handled by Task.throw). @@ -229,6 +238,9 @@ def run_until_complete(main_task=None): _exc_context["exception"] = exc _exc_context["future"] = t Loop.call_exception_handler(_exc_context) + # If it's the main task then the loop should stop + if t is main_task: + return er.value # Create a new task from a coroutine and run it until it finishes diff --git a/tests/extmod/asyncio_event_queue.py b/tests/extmod/asyncio_event_queue.py new file mode 100644 index 00000000000..e0125b1aefe --- /dev/null +++ b/tests/extmod/asyncio_event_queue.py @@ -0,0 +1,64 @@ +# Ensure that an asyncio task can wait on an Event when the +# _task_queue is empty +# https://github.com/micropython/micropython/issues/16569 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + +# This test requires checking that the asyncio scheduler +# remains active "indefinitely" when the task queue is empty. +# +# To check this, we need another independent scheduler that +# can wait for a certain amount of time. So we have to +# create one using micropython.schedule() and time.ticks_ms() +# +# Technically, this code breaks the rules, as it is clearly +# documented that Event.set() should _NOT_ be called from a +# schedule (soft IRQ) because in some cases, a race condition +# can occur, resulting in a crash. However: +# - since the risk of a race condition in that specific +# case has been analysed and excluded +# - given that there is no other simple alternative to +# write this test case, +# an exception to the rule was deemed acceptable. See +# https://github.com/micropython/micropython/pull/16772 + +import micropython, time + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + + +evt = asyncio.Event() + + +def schedule_watchdog(end_ticks): + if time.ticks_diff(end_ticks, time.ticks_ms()) <= 0: + print("asyncio still pending, unlocking event") + # Caution: about to call Event.set() from a schedule + # (see the note in the comment above) + evt.set() + return + micropython.schedule(schedule_watchdog, end_ticks) + + +async def foo(): + print("foo waiting") + schedule_watchdog(time.ticks_add(time.ticks_ms(), 100)) + await evt.wait() + print("foo done") + + +async def main(): + print("main started") + await foo() + print("main done") + + +asyncio.run(main()) diff --git a/tests/extmod/asyncio_event_queue.py.exp b/tests/extmod/asyncio_event_queue.py.exp new file mode 100644 index 00000000000..ee42c96d83e --- /dev/null +++ b/tests/extmod/asyncio_event_queue.py.exp @@ -0,0 +1,5 @@ +main started +foo waiting +asyncio still pending, unlocking event +foo done +main done diff --git a/tests/extmod/asyncio_iterator_event.py b/tests/extmod/asyncio_iterator_event.py new file mode 100644 index 00000000000..6efa6b86456 --- /dev/null +++ b/tests/extmod/asyncio_iterator_event.py @@ -0,0 +1,86 @@ +# Ensure that an asyncio task can wait on an Event when the +# _task_queue is empty, in the context of an async iterator +# https://github.com/micropython/micropython/issues/16318 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + +# This test requires checking that the asyncio scheduler +# remains active "indefinitely" when the task queue is empty. +# +# To check this, we need another independent scheduler that +# can wait for a certain amount of time. So we have to +# create one using micropython.schedule() and time.ticks_ms() +# +# Technically, this code breaks the rules, as it is clearly +# documented that Event.set() should _NOT_ be called from a +# schedule (soft IRQ) because in some cases, a race condition +# can occur, resulting in a crash. However: +# - since the risk of a race condition in that specific +# case has been analysed and excluded +# - given that there is no other simple alternative to +# write this test case, +# an exception to the rule was deemed acceptable. See +# https://github.com/micropython/micropython/pull/16772 + +import micropython, time + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + +ai = None + + +def schedule_watchdog(end_ticks): + if time.ticks_diff(end_ticks, time.ticks_ms()) <= 0: + print("good: asyncio iterator is still pending, exiting") + # Caution: ai.fetch_data() will invoke Event.set() + # (see the note in the comment above) + ai.fetch_data(None) + return + micropython.schedule(schedule_watchdog, end_ticks) + + +async def test(ai): + for x in range(3): + await asyncio.sleep(0.1) + ai.fetch_data(f"bar {x}") + + +class AsyncIterable: + def __init__(self): + self.message = None + self.evt = asyncio.Event() + + def __aiter__(self): + return self + + async def __anext__(self): + await self.evt.wait() + self.evt.clear() + if self.message is None: + raise StopAsyncIteration + return self.message + + def fetch_data(self, message): + self.message = message + self.evt.set() + + +async def main(): + global ai + ai = AsyncIterable() + asyncio.create_task(test(ai)) + schedule_watchdog(time.ticks_add(time.ticks_ms(), 500)) + async for message in ai: + print(message) + print("end main") + + +asyncio.run(main()) diff --git a/tests/extmod/asyncio_iterator_event.py.exp b/tests/extmod/asyncio_iterator_event.py.exp new file mode 100644 index 00000000000..a1893197d02 --- /dev/null +++ b/tests/extmod/asyncio_iterator_event.py.exp @@ -0,0 +1,5 @@ +bar 0 +bar 1 +bar 2 +good: asyncio iterator is still pending, exiting +end main diff --git a/tests/extmod/asyncio_wait_for_linked_task.py b/tests/extmod/asyncio_wait_for_linked_task.py new file mode 100644 index 00000000000..4dda62d5476 --- /dev/null +++ b/tests/extmod/asyncio_wait_for_linked_task.py @@ -0,0 +1,66 @@ +# Test asyncio.wait_for, with dependent tasks +# https://github.com/micropython/micropython/issues/16759 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + + +# CPython 3.12 deprecated calling get_event_loop() when there is no current event +# loop, so to make this test run on CPython requires setting the event loop. +if hasattr(asyncio, "set_event_loop"): + asyncio.set_event_loop(asyncio.new_event_loop()) + + +class Worker: + def __init__(self): + self._eventLoop = None + self._tasks = [] + + def launchTask(self, asyncJob): + if self._eventLoop is None: + self._eventLoop = asyncio.get_event_loop() + return self._eventLoop.create_task(asyncJob) + + async def job(self, prerequisite, taskName): + if prerequisite: + await prerequisite + await asyncio.sleep(0.1) + print(taskName, "work completed") + + def planTasks(self): + self._tasks.append(self.launchTask(self.job(None, "task0"))) + self._tasks.append(self.launchTask(self.job(self._tasks[0], "task1"))) + self._tasks.append(self.launchTask(self.job(self._tasks[1], "task2"))) + + async def waitForTask(self, taskIdx): + return await self._tasks[taskIdx] + + def syncWaitForTask(self, taskIdx): + return self._eventLoop.run_until_complete(self._tasks[taskIdx]) + + +async def async_test(): + print("--- async test") + worker = Worker() + worker.planTasks() + await worker.waitForTask(0) + print("-> task0 done") + await worker.waitForTask(2) + print("-> task2 done") + + +def sync_test(): + print("--- sync test") + worker = Worker() + worker.planTasks() + worker.syncWaitForTask(0) + print("-> task0 done") + worker.syncWaitForTask(2) + print("-> task2 done") + + +asyncio.get_event_loop().run_until_complete(async_test()) +sync_test() diff --git a/tests/run-tests.py b/tests/run-tests.py index 9e7cab4689a..ac411a0be6a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -162,6 +162,9 @@ def open(self, path, mode): "extmod/asyncio_new_event_loop.py", "extmod/asyncio_threadsafeflag.py", "extmod/asyncio_wait_for_fwd.py", + "extmod/asyncio_event_queue.py", + "extmod/asyncio_iterator_event.py", + "extmod/asyncio_wait_for_linked_task.py", "extmod/binascii_a2b_base64.py", "extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory "extmod/re_stack_overflow.py", @@ -843,6 +846,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): ) # native doesn't have proper traceback info skip_tests.add("micropython/schedule.py") # native code doesn't check pending events skip_tests.add("stress/bytecode_limit.py") # bytecode specific test + skip_tests.add("extmod/asyncio_event_queue.py") # native can't run schedule + skip_tests.add("extmod/asyncio_iterator_event.py") # native can't run schedule def run_one_test(test_file): test_file = test_file.replace("\\", "/") From 288dce26bd1f6c4bf664aefe11103840e458ebb2 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 6 May 2025 16:25:53 -0600 Subject: [PATCH 0606/2098] docs,ports: Fix SparkFun capitalization. This is a follow-up to 1e92bdd206f6f87ba65ea05c5b2623fca0b926cd correcting more of the instances where "Sparkfun" should be "SparkFun". Signed-off-by: Damien George --- docs/library/wm8960.rst | 8 ++++---- docs/samd/pinout.rst | 2 +- ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json | 4 ++-- .../boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h | 2 +- ports/samd/modmachine.c | 2 +- ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json | 2 +- .../stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/library/wm8960.rst b/docs/library/wm8960.rst index 5abfb6a8a01..4115d207581 100644 --- a/docs/library/wm8960.rst +++ b/docs/library/wm8960.rst @@ -358,13 +358,13 @@ Run WM8960 on a MIMXRT10xx_DEV board in secondary mode (default):: sysclk_source=wm8960.SYSCLK_MCLK) -Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: +Record with a SparkFun WM8960 breakout board with Teensy in secondary mode (default):: # Micro_python WM8960 Codec driver # # The breakout board uses a fixed 24MHz MCLK. Therefore the internal # PLL must be used as sysclk, which is the master audio clock. - # The Sparkfun board has the WS pins for RX and TX connected on the + # The SparkFun board has the WS pins for RX and TX connected on the # board. Therefore adc_sync must be set to sync_adc, to configure # it's ADCLRC pin as input. # @@ -379,11 +379,11 @@ Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (defa right_input=wm8960.INPUT_CLOSED) -Play with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: +Play with a SparkFun WM8960 breakout board with Teensy in secondary mode (default):: # The breakout board uses a fixed 24MHz MCLK. Therefore the internal # PLL must be used as sysclk, which is the master audio clock. - # The Sparkfun board has the WS pins for RX and TX connected on the + # The SparkFun board has the WS pins for RX and TX connected on the # board. Therefore adc_sync must be set to sync_adc, to configure # it's ADCLRC pin as input. diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 3945e2bf2ff..1ad8f558840 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -892,7 +892,7 @@ Default pin assignments: There seems to be no default pin assignment for this board. -Sparkfun SAMD51 Thing Plus pin assignment table +SparkFun SAMD51 Thing Plus pin assignment table ------------------------------------------------ === ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json index ee9ca9d3689..51d124e751d 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json @@ -13,8 +13,8 @@ "sparkfun_samd51_thing_plus.jpg" ], "mcu": "samd51", - "product": "Sparkfun SAMD51 Thing Plus", + "product": "SparkFun SAMD51 Thing Plus", "thumbnail": "", "url": "https://www.sparkfun.com/products/14713", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h index fe2226a59eb..9e0db7875f2 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h @@ -1,4 +1,4 @@ -#define MICROPY_HW_BOARD_NAME "Sparkfun SAMD51 Thing Plus" +#define MICROPY_HW_BOARD_NAME "SparkFun SAMD51 Thing Plus" #define MICROPY_HW_MCU_NAME "SAMD51J20A" #define MICROPY_HW_XOSC32K (1) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 65dbf8a7f50..82e7b7c0620 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -42,7 +42,7 @@ #define DBL_TAP_ADDR ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 4)) #endif // A board may define a DPL_TAP_ADDR_ALT, which will be set as well -// Needed at the moment for Sparkfun SAMD51 Thing Plus +// Needed at the moment for SparkFun SAMD51 Thing Plus #define DBL_TAP_MAGIC_LOADER 0xf01669ef #define DBL_TAP_MAGIC_RESET 0xf02669ef diff --git a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json index 0bd3573d644..01ed363cf37 100644 --- a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json +++ b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json @@ -11,5 +11,5 @@ "product": "Micromod STM32", "thumbnail": "", "url": "", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h index d2f44cf6ccc..1e17905bb0d 100644 --- a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h +++ b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h @@ -1,4 +1,4 @@ -// The Sparkfun MicroMod spec uses a zero-based peripheral numbering scheme. +// The SparkFun MicroMod spec uses a zero-based peripheral numbering scheme. // In cases where the 0th peripheral is the default, the "0" is omitted from // the name (e.g. "I2C" instead of "I2C0"). // From 8e328da9bf96b4347bd4746afc337fe28f2e75b1 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Feb 2025 12:49:45 +0100 Subject: [PATCH 0607/2098] samd/boards/SAMD_GENERIC_Dxxx: Add Microchip URL to board.json. To the Microchip Web site. Thanks to Matt Trentini for suggesting this site. Signed-off-by: robert-hh --- ports/samd/boards/SAMD_GENERIC_D21X18/board.json | 1 + ports/samd/boards/SAMD_GENERIC_D51X19/board.json | 1 + ports/samd/boards/SAMD_GENERIC_D51X20/board.json | 1 + 3 files changed, 3 insertions(+) diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/board.json b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json index a14730bd1bf..c00d78addd4 100644 --- a/ports/samd/boards/SAMD_GENERIC_D21X18/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd21", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD21J18", "thumbnail": "" } diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/board.json b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json index 21cb114bf65..2be5f20851e 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X19/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd51", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD51P19", "thumbnail": "" } diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/board.json b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json index ae4f83472ec..83844843278 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X20/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd51", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD51P20", "thumbnail": "" } From 2fda4bbe05799ccf387051acec600c6ac2e243e2 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 12 Mar 2025 20:28:51 +0100 Subject: [PATCH 0608/2098] samd/machine_i2c: Add the timeout keyword argument to the constructor. To make it compliant with the documentation. The default value is 50000us. Signed-off-by: robert-hh --- ports/samd/machine_i2c.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 03af3d29ed6..172518523d2 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -37,9 +37,9 @@ #include "genhdr/pins.h" #include "clock_config.h" -#define DEFAULT_I2C_FREQ (400000) -#define RISETIME_NS (200) -#define I2C_TIMEOUT (100) +#define DEFAULT_I2C_FREQ (400000) +#define RISETIME_NS (200) +#define DEFAULT_I2C_TIMEOUT (50000) #define IS_BUS_BUSY (i2c->I2CM.STATUS.bit.BUSSTATE == 3) #define NACK_RECVD (i2c->I2CM.STATUS.bit.RXNACK == 1) @@ -67,6 +67,7 @@ typedef struct _machine_i2c_obj_t { uint8_t state; uint32_t freq; uint32_t timeout; + uint32_t timer; size_t len; uint8_t *buf; } machine_i2c_obj_t; @@ -88,7 +89,7 @@ void common_i2c_irq_handler(int i2c_id) { if (self->len > 0) { *(self->buf)++ = i2c->I2CM.DATA.reg; self->len--; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; } if (self->len > 0) { // no ACK at the last byte PREPARE_ACK; // Send ACK @@ -105,7 +106,7 @@ void common_i2c_irq_handler(int i2c_id) { } else if (self->len > 0) { // data to be sent i2c->I2CM.DATA.bit.DATA = *(self->buf)++; self->len--; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; } else { // No data left, if there was any. self->state = state_done; i2c->I2CM.INTFLAG.reg |= SERCOM_I2CM_INTFLAG_MB; @@ -120,12 +121,13 @@ void common_i2c_irq_handler(int i2c_id) { static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "I2C(%u, freq=%u, scl=\"%q\", sda=\"%q\")", - self->id, self->freq, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name); + mp_printf(print, "I2C(%u, freq=%u, scl=\"%q\", sda=\"%q\", timeout=%u)", + self->id, self->freq, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name, + self->timeout * 1000); } mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_freq, ARG_scl, ARG_sda }; + enum { ARG_id, ARG_freq, ARG_scl, ARG_sda, ARG_timeout }; static const mp_arg_t allowed_args[] = { #if MICROPY_HW_DEFAULT_I2C_ID < 0 { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, @@ -140,6 +142,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, #endif + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_TIMEOUT} }, }; // Parse args. @@ -168,6 +171,8 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n } MP_STATE_PORT(sercom_table[self->id]) = self; self->freq = args[ARG_freq].u_int; + // The unit for ARG_timeout is us, but the code uses ms. + self->timeout = args[ARG_timeout].u_int / 1000; // Configure the Pin mux. mp_hal_set_pin_mux(self->scl, scl_pad_config.alt_fct); @@ -224,13 +229,13 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si machine_i2c_obj_t *self = (machine_i2c_obj_t *)self_in; Sercom *i2c = self->instance; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; self->len = len; self->buf = buf; // Wait a while if the bus is busy - while (IS_BUS_BUSY && self->timeout) { + while (IS_BUS_BUSY && self->timer) { MICROPY_EVENT_POLL_HOOK - if (--self->timeout == 0) { + if (--self->timer == 0) { return -MP_ETIMEDOUT; } } @@ -242,9 +247,9 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si i2c->I2CM.ADDR.bit.ADDR = (addr << 1) | READ_MODE; // Transfer the data - self->timeout = I2C_TIMEOUT; - while (self->state == state_busy && self->timeout) { - self->timeout--; + self->timer = self->timeout; + while (self->state == state_busy && self->timer) { + self->timer--; MICROPY_EVENT_POLL_HOOK } i2c->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR; @@ -256,7 +261,7 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si } else if (self->state == state_buserr) { SET_STOP_STATE; return -MP_EIO; - } else if (self->timeout == 0) { + } else if (self->timer == 0) { SET_STOP_STATE; return -MP_ETIMEDOUT; } From ff9e01782b7e0f21d8d085bdc4c917a7faf6c17e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 26 Mar 2025 09:24:19 +0100 Subject: [PATCH 0609/2098] samd/modtime: Change time.time_ns() to follow the RTC time. That is done by adding the offset to epoch, following the scheme from the RP2 port. RTC and `ticks_us()` are not precisely in sync, and so the difference between `time.time_ns()/1e9` and `time.time()` will increase by more than 9 seconds/24h. So applications should avoid using `time.time()` and `time.time_ns()` in the same context. Signed-off-by: robert-hh --- ports/samd/machine_rtc.c | 1 + ports/samd/main.c | 2 ++ ports/samd/modtime.c | 14 ++++++++++++++ ports/samd/mphalport.h | 5 +---- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ports/samd/machine_rtc.c b/ports/samd/machine_rtc.c index 74c2266d609..4b03488b71b 100644 --- a/ports/samd/machine_rtc.c +++ b/ports/samd/machine_rtc.c @@ -133,6 +133,7 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args, } #endif + mp_hal_time_ns_set_from_rtc(); return mp_const_none; } } diff --git a/ports/samd/main.c b/ports/samd/main.c index 2bbaf63e6e4..a7da95582f7 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -28,6 +28,7 @@ #include "py/runtime.h" #include "py/gc.h" #include "py/mperrno.h" +#include "py/mphal.h" #include "py/stackctrl.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -45,6 +46,7 @@ extern void sercom_deinit_all(void); void samd_main(void) { mp_stack_set_top(&_estack); mp_stack_set_limit(&_estack - &_sstack - 1024); + mp_hal_time_ns_set_from_rtc(); for (;;) { gc_init(&_sheap, &_eheap); diff --git a/ports/samd/modtime.c b/ports/samd/modtime.c index 83072dd16fe..0bed3cb83a8 100644 --- a/ports/samd/modtime.c +++ b/ports/samd/modtime.c @@ -28,6 +28,8 @@ #include "shared/timeutils/timeutils.h" #include "modmachine.h" +static uint64_t time_us_64_offset_from_epoch; + // Return the localtime as an 8-tuple. static mp_obj_t mp_time_localtime_get(void) { timeutils_struct_time_t tm; @@ -54,3 +56,15 @@ static mp_obj_t mp_time_time_get(void) { return mp_obj_new_int_from_uint(timeutils_mktime( tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec)); } + +void mp_hal_time_ns_set_from_rtc(void) { + timeutils_struct_time_t tm; + rtc_gettime(&tm); + uint64_t time_us = (uint64_t)timeutils_mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec) * 1000000ULL; + time_us_64_offset_from_epoch = time_us - mp_hal_ticks_us_64(); +} + +uint64_t mp_hal_time_ns(void) { + return (time_us_64_offset_from_epoch + mp_hal_ticks_us_64()) * 1000ULL; +} diff --git a/ports/samd/mphalport.h b/ports/samd/mphalport.h index 6c9e525bb91..69e2b606408 100644 --- a/ports/samd/mphalport.h +++ b/ports/samd/mphalport.h @@ -47,6 +47,7 @@ extern int mp_interrupt_char; extern ringbuf_t stdin_ringbuf; extern volatile uint32_t systick_ms; uint64_t mp_hal_ticks_us_64(void); +void mp_hal_time_ns_set_from_rtc(void); void mp_hal_set_interrupt_char(int c); @@ -94,10 +95,6 @@ static inline mp_uint_t mp_hal_ticks_cpu(void) { } #endif -static inline uint64_t mp_hal_time_ns(void) { - return mp_hal_ticks_us_64() * 1000; -} - // C-level pin HAL #include "py/obj.h" From c22c2c806694dbce55cfe484b1b9e55af33bb950 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 15 Apr 2025 16:57:53 +0200 Subject: [PATCH 0610/2098] samd/boards/SAMD_GENERIC_D51xxx: Fix VFS settings for internal flash. Fixes in this commit: - The wrong loader script was assigned for SAMD_GENERIC_D51X20, causing the VFS block count to be wrong. - Change the VFS block size from 1536 to 2048. With the setting of 1536, writing more that 1536 bytes at once failed. This applies to SAMD_GENERIC_D51X19 and SAMD_GENERIC_D51X20. No other SAMD51 board uses the internal flash for the file system. Signed-off-by: robert-hh --- ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk | 6 ++---- ports/samd/mcu/samd51/mpconfigmcu.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk index ddba3dcbbad..b240c2587f8 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk @@ -1,13 +1,11 @@ MCU_SERIES = SAMD51 CMSIS_MCU = SAMD51P20A -LD_FILES = boards/samd51x19a.ld sections.ld +LD_FILES = boards/samd51x20a.ld sections.ld TEXT0 = 0x4000 - # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings # The size of a MCU flash filesystem will be -# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_VFSROMSIZE +# 1008k - MICROPY_HW_CODESIZE # The default for MICROPY_HW_VFSROMSIZE is 64K MICROPY_HW_CODESIZE ?= 752K -MICROPY_HW_VFSROMSIZE ?= 128K diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 831949bab56..8cce90b886c 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -23,7 +23,7 @@ unsigned long trng_random_u32(void); #define MICROPY_FATFS_MAX_SS (4096) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ -#define VFS_BLOCK_SIZE_BYTES (1536) // +#define VFS_BLOCK_SIZE_BYTES (2048) // #ifndef MICROPY_HW_UART_TXBUF #define MICROPY_HW_UART_TXBUF (1) From 2e6df08e05f4832806f062c56f78c5412fd37ceb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 9 Apr 2025 18:07:24 +0200 Subject: [PATCH 0611/2098] samd/samd_spiflash: Improve the flash type detection. Changes in this commit: - Check for the proper SFDP header. - Use the flash size information from SFDP, if present. - Add two more special flash chips <= 1 MByte without SFDP. JEDEC-ID table for special flash types instead of a series of conditional statements. - Add a compile flag `MICROPY_HW_SPIFLASH_SIZE` to set the size in `mpconfigboard.h`, which replaces getting the size from the JEDEC ID or the SFDP record. Signed-off-by: robert-hh --- ports/samd/samd_spiflash.c | 65 +++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c index 8ada7e96092..63dc0a83045 100644 --- a/ports/samd/samd_spiflash.c +++ b/ports/samd/samd_spiflash.c @@ -67,6 +67,19 @@ typedef struct _spiflash_obj_t { extern const mp_obj_type_t samd_spiflash_type; +typedef struct _spiflash_jedec_id_t { + uint32_t jedec_id; + uint32_t mask; + uint32_t size; +} spiflash_jedec_id_t; + +spiflash_jedec_id_t jedec_id_table[] = { + { 0x1f8401, 0xffff00, 512 * 1024 }, // Adesto/Renesas 4 MBit + { 0x1f2400, 0xffff00, 512 * 1024 }, // Adesto 4 MBit + { 0x1f4501, 0xffff00, 1024 * 1024 }, // Adesto/Renesas/Atmel 8 MBit + { 0xc84013, 0xffffff, 512 * 1024 }, // Gigadevices 4 MBit +}; + // The SPIflash object is a singleton static spiflash_obj_t spiflash_obj = { { &samd_spiflash_type }, NULL, 0, false, PAGE_SIZE, SECTOR_SIZE, NULL, 0 @@ -124,6 +137,7 @@ static void write_enable(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 1); } +#if !defined(MICROPY_HW_SPIFLASH_SIZE) // Write status register 1 static void write_sr1(spiflash_obj_t *self, uint8_t value) { uint8_t msg[2]; @@ -134,6 +148,7 @@ static void write_sr1(spiflash_obj_t *self, uint8_t value) { spi_transfer(self->spi, 2, msg, NULL); mp_hal_pin_write(self->cs, 1); } +#endif static void get_sfdp(spiflash_obj_t *self, uint32_t addr, uint8_t *buffer, int size) { uint8_t dummy[1]; @@ -170,32 +185,46 @@ static mp_obj_t spiflash_make_new(const mp_obj_type_t *type, size_t n_args, size // Get the flash size from the device ID (default) uint8_t id[3]; get_id(self, id); - bool read_sfdp = true; - - if (id[1] == 0x84 && id[2] == 1) { // Adesto - self->size = 512 * 1024; - } else if (id[0] == 0x1f && id[1] == 0x45 && id[2] == 1) { // Adesto/Renesas 8 MBit - self->size = 1024 * 1024; - read_sfdp = false; - self->sectorsize = 4096; - self->addr_is_32bit = false; - // Globally unlock the sectors, which are locked after power on. - write_enable(self); - write_sr1(self, 0); - } else { - self->size = 1 << id[2]; + + #if defined(MICROPY_HW_SPIFLASH_SIZE) + self->size = MICROPY_HW_SPIFLASH_SIZE; + #else + // Assume as default that the size is coded into the last JEDEC ID byte. + self->size = 1 << id[2]; + // Look for specific flash devices with different encoding. + uint32_t jedec_id = (id[0] << 16) | (id[1] << 8) | id[2]; + for (int i = 0; i < MP_ARRAY_SIZE(jedec_id_table); i++) { + if (jedec_id_table[i].jedec_id == (jedec_id & jedec_id_table[i].mask)) { + self->size = jedec_id_table[i].size; + // Globally unlock the sectors, which may be locked after power on. + write_enable(self); + write_sr1(self, 0); + break; + } } + #endif - // Get the addr_is_32bit flag and the sector size - if (read_sfdp) { - uint8_t buffer[128]; - get_sfdp(self, 0, buffer, 16); // get the header + // Get the flash size, addr_is_32bit flag and sector size from SFDP, if present. + uint8_t buffer[128]; + get_sfdp(self, 0, buffer, 16); // get the header + if (*(uint32_t *)buffer == 0x50444653) { // Header signature "SFDP" int len = MIN(buffer[11] * 4, sizeof(buffer)); if (len >= 29) { int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table self->sectorsize = 1 << buffer[28]; self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + #if !defined(MICROPY_HW_SPIFLASH_SIZE) + // Get the bit size from the SFDP data + uint32_t size = *(uint32_t *)(buffer + 4); + if (size & 0x8000000) { + // Byte size is 2 ** lower_31_bits / 8 + self->size = 1 << ((size & 0x7fffffff) >> 3); + } else { + // Byte size is lower_31_bits / 8 + 1 + self->size = ((size & 0x7fffffff) >> 3) + 1; + } + #endif } } self->commands = self->addr_is_32bit ? _COMMANDS_32BIT : _COMMANDS_24BIT; From 80d03b77804da44d56b7b9446520ffafde7e3eea Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 16 Apr 2025 21:46:15 +0200 Subject: [PATCH 0612/2098] samd/samd_qspiflash: Remove the attempt to handle a unknown device. Since all QSPI flash device used by this port are defined, this code was only used unintentionally. Besides that it was incomplete, so better drop it. Note: The flash type for Mini-SAM had to be changed too. Signed-off-by: robert-hh --- ports/samd/boards/MINISAM_M4/mpconfigboard.h | 2 +- ports/samd/samd_qspiflash.c | 32 +------------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h index 6d908bdcb81..30a7a804542 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.h +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.h @@ -7,4 +7,4 @@ #define MICROPY_HW_DEFAULT_I2C_ID (2) #define MICROPY_HW_DEFAULT_SPI_ID (1) -#define MICROPY_HW_QSPIFLASH GD25Q16C +#define MICROPY_HW_QSPIFLASH W25Q16JV_IQ diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c index e25ddf21efe..f314a955197 100644 --- a/ports/samd/samd_qspiflash.c +++ b/ports/samd/samd_qspiflash.c @@ -105,7 +105,6 @@ static const external_flash_device possible_devices[] = { #define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices) static external_flash_device const *flash_device; -static external_flash_device generic_config = GENERIC; extern const mp_obj_type_t samd_qspiflash_type; // The QSPIflash object is a singleton @@ -248,15 +247,6 @@ static uint8_t get_baud(int32_t freq_mhz) { return baud; } -int get_sfdp_table(uint8_t *table, int maxlen) { - uint8_t header[16]; - read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, 0, header, sizeof(header)); - int len = MIN(header[11] * 4, maxlen); - int addr = header[12] + (header[13] << 8) + (header[14] << 16); - read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, addr, table, len); - return len; -} - static mp_obj_t samd_qspiflash_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, 0, 0, false); @@ -297,19 +287,6 @@ static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args uint8_t jedec_ids[3]; read_command(QSPI_CMD_READ_JEDEC_ID, jedec_ids, sizeof(jedec_ids)); - // Read the common sfdp table - // Check the device addr length, support of 1-1-4 mode and get the sector size - uint8_t sfdp_table[128]; - int len = get_sfdp_table(sfdp_table, sizeof(sfdp_table)); - if (len >= 29) { - self->sectorsize = 1 << sfdp_table[28]; - bool addr4b = ((sfdp_table[2] >> 1) & 0x03) == 0x02; - bool supports_qspi_114 = (sfdp_table[2] & 0x40) != 0; - if (addr4b || !supports_qspi_114) { - mp_raise_ValueError(MP_ERROR_TEXT("QSPI mode not supported")); - } - } - // Check, if the flash device is known and get it's properties. flash_device = NULL; for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { @@ -321,15 +298,8 @@ static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args break; } } - - // If the flash device is not known, try generic config options if (flash_device == NULL) { - if (jedec_ids[0] == 0xc2) { // Macronix devices - generic_config.quad_enable_bit_mask = 0x04; - generic_config.single_status_byte = true; - } - generic_config.total_size = 1 << jedec_ids[2]; - flash_device = &generic_config; + mp_raise_ValueError(MP_ERROR_TEXT("QSPI device not supported")); } self->size = flash_device->total_size; From 9287a1e6eaa9c6dc1bb9be27466acd3e639d8113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 27 Mar 2024 20:16:13 +0100 Subject: [PATCH 0613/2098] lib/littlefs: Update LittleFS to v2.10.2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Going above the root directory (/../foo) now gives an error. This is an intentional change made by LittleFS. It required a update of the testsuite and is a (minor) compatibility break. Signed-off-by: Daniël van de Giessen --- lib/littlefs/lfs2.c | 785 ++++++++++++++++++++++++--------------- lib/littlefs/lfs2.h | 94 +++-- lib/littlefs/lfs2_util.c | 3 + lib/littlefs/lfs2_util.h | 40 +- tests/extmod/vfs_lfs.py | 2 +- 5 files changed, 585 insertions(+), 339 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index d89c42fd59a..f9ce2cf2906 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -282,6 +282,21 @@ static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { /// Small type-level utilities /// + +// some operations on paths +static inline lfs2_size_t lfs2_path_namelen(const char *path) { + return strcspn(path, "/"); +} + +static inline bool lfs2_path_islast(const char *path) { + lfs2_size_t namelen = lfs2_path_namelen(path); + return path[namelen + strspn(path + namelen, "/")] == '\0'; +} + +static inline bool lfs2_path_isdir(const char *path) { + return path[lfs2_path_namelen(path)] != '\0'; +} + // operations on block pairs static inline void lfs2_pair_swap(lfs2_block_t pair[2]) { lfs2_block_t t = pair[0]; @@ -389,18 +404,15 @@ struct lfs2_diskoff { // operations on global state static inline void lfs2_gstate_xor(lfs2_gstate_t *a, const lfs2_gstate_t *b) { - for (int i = 0; i < 3; i++) { - ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; - } + a->tag ^= b->tag; + a->pair[0] ^= b->pair[0]; + a->pair[1] ^= b->pair[1]; } static inline bool lfs2_gstate_iszero(const lfs2_gstate_t *a) { - for (int i = 0; i < 3; i++) { - if (((uint32_t*)a)[i] != 0) { - return false; - } - } - return true; + return a->tag == 0 + && a->pair[0] == 0 + && a->pair[1] == 0; } #ifndef LFS2_READONLY @@ -550,9 +562,9 @@ static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *source, uint16_t begin, uint16_t end); static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); -static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); -static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file); +static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file); @@ -574,65 +586,72 @@ static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); #endif -static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir); +static int lfs2_dir_rewind_(lfs2_t *lfs2, lfs2_dir_t *dir); static lfs2_ssize_t lfs2_file_flushedread(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); -static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_read_(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); -static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file); -static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file); +static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file); +static lfs2_soff_t lfs2_file_size_(lfs2_t *lfs2, lfs2_file_t *file); -static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2); -static int lfs2_fs_rawtraverse(lfs2_t *lfs2, +static lfs2_ssize_t lfs2_fs_size_(lfs2_t *lfs2); +static int lfs2_fs_traverse_(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans); static int lfs2_deinit(lfs2_t *lfs2); -static int lfs2_rawunmount(lfs2_t *lfs2); +static int lfs2_unmount_(lfs2_t *lfs2); /// Block allocator /// + +// allocations should call this when all allocated blocks are committed to +// the filesystem +// +// after a checkpoint, the block allocator may realloc any untracked blocks +static void lfs2_alloc_ckpoint(lfs2_t *lfs2) { + lfs2->lookahead.ckpoint = lfs2->block_count; +} + +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs2_alloc_drop(lfs2_t *lfs2) { + lfs2->lookahead.size = 0; + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); +} + #ifndef LFS2_READONLY static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; - lfs2_block_t off = ((block - lfs2->free.off) + lfs2_block_t off = ((block - lfs2->lookahead.start) + lfs2->block_count) % lfs2->block_count; - if (off < lfs2->free.size) { - lfs2->free.buffer[off / 32] |= 1U << (off % 32); + if (off < lfs2->lookahead.size) { + lfs2->lookahead.buffer[off / 8] |= 1U << (off % 8); } return 0; } #endif -// indicate allocated blocks have been committed into the filesystem, this -// is to prevent blocks from being garbage collected in the middle of a -// commit operation -static void lfs2_alloc_ack(lfs2_t *lfs2) { - lfs2->free.ack = lfs2->block_count; -} - -// drop the lookahead buffer, this is done during mounting and failed -// traversals in order to avoid invalid lookahead state -static void lfs2_alloc_drop(lfs2_t *lfs2) { - lfs2->free.size = 0; - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); -} - #ifndef LFS2_READONLY -static int lfs2_fs_rawgc(lfs2_t *lfs2) { - // Move free offset at the first unused block (lfs2->free.i) - // lfs2->free.i is equal lfs2->free.size when all blocks are used - lfs2->free.off = (lfs2->free.off + lfs2->free.i) % lfs2->block_count; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); - lfs2->free.i = 0; +static int lfs2_alloc_scan(lfs2_t *lfs2) { + // move lookahead buffer to the first unused block + // + // note we limit the lookahead buffer to at most the amount of blocks + // checkpointed, this prevents the math in lfs2_alloc from underflowing + lfs2->lookahead.start = (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count; + lfs2->lookahead.next = 0; + lfs2->lookahead.size = lfs2_min( + 8*lfs2->cfg->lookahead_size, + lfs2->lookahead.ckpoint); // find mask of free blocks from tree - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); + memset(lfs2->lookahead.buffer, 0, lfs2->cfg->lookahead_size); + int err = lfs2_fs_traverse_(lfs2, lfs2_alloc_lookahead, lfs2, true); if (err) { lfs2_alloc_drop(lfs2); return err; @@ -645,36 +664,49 @@ static int lfs2_fs_rawgc(lfs2_t *lfs2) { #ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { - while (lfs2->free.i != lfs2->free.size) { - lfs2_block_t off = lfs2->free.i; - lfs2->free.i += 1; - lfs2->free.ack -= 1; - - if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) { + // scan our lookahead buffer for free blocks + while (lfs2->lookahead.next < lfs2->lookahead.size) { + if (!(lfs2->lookahead.buffer[lfs2->lookahead.next / 8] + & (1U << (lfs2->lookahead.next % 8)))) { // found a free block - *block = (lfs2->free.off + off) % lfs2->block_count; - - // eagerly find next off so an alloc ack can - // discredit old lookahead blocks - while (lfs2->free.i != lfs2->free.size && - (lfs2->free.buffer[lfs2->free.i / 32] - & (1U << (lfs2->free.i % 32)))) { - lfs2->free.i += 1; - lfs2->free.ack -= 1; + *block = (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count; + + // eagerly find next free block to maximize how many blocks + // lfs2_alloc_ckpoint makes available for scanning + while (true) { + lfs2->lookahead.next += 1; + lfs2->lookahead.ckpoint -= 1; + + if (lfs2->lookahead.next >= lfs2->lookahead.size + || !(lfs2->lookahead.buffer[lfs2->lookahead.next / 8] + & (1U << (lfs2->lookahead.next % 8)))) { + return 0; + } } - - return 0; } + + lfs2->lookahead.next += 1; + lfs2->lookahead.ckpoint -= 1; } - // check if we have looked at all blocks since last ack - if (lfs2->free.ack == 0) { - LFS2_ERROR("No more free space %"PRIu32, - lfs2->free.i + lfs2->free.off); + // In order to keep our block allocator from spinning forever when our + // filesystem is full, we mark points where there are no in-flight + // allocations with a checkpoint before starting a set of allocations. + // + // If we've looked at all blocks since the last checkpoint, we report + // the filesystem as out of storage. + // + if (lfs2->lookahead.ckpoint <= 0) { + LFS2_ERROR("No more free space 0x%"PRIx32, + (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count); return LFS2_ERR_NOSPC; } - int err = lfs2_fs_rawgc(lfs2); + // No blocks in our lookahead buffer, we need to scan the filesystem for + // unused blocks in the next lookahead window. + int err = lfs2_alloc_scan(lfs2); if(err) { return err; } @@ -690,11 +722,14 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t ntag = dir->etag; lfs2_stag_t gdiff = 0; + // synthetic moves if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair) && - lfs2_tag_id(gmask) != 0 && - lfs2_tag_id(lfs2->gdisk.tag) <= lfs2_tag_id(gtag)) { - // synthetic moves - gdiff -= LFS2_MKTAG(0, 1, 0); + lfs2_tag_id(gmask) != 0) { + if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(gtag)) { + return LFS2_ERR_NOENT; + } else if (lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(gtag)) { + gdiff -= LFS2_MKTAG(0, 1, 0); + } } // iterate over dir block backwards (for faster lookups) @@ -1438,32 +1473,46 @@ static int lfs2_dir_find_match(void *data, return LFS2_CMP_EQ; } +// lfs2_dir_find tries to set path and id even if file is not found +// +// returns: +// - 0 if file is found +// - LFS2_ERR_NOENT if file or parent is not found +// - LFS2_ERR_NOTDIR if parent is not a dir static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, const char **path, uint16_t *id) { // we reduce path to a single name if we can find it const char *name = *path; - if (id) { - *id = 0x3ff; - } // default to root dir lfs2_stag_t tag = LFS2_MKTAG(LFS2_TYPE_DIR, 0x3ff, 0); dir->tail[0] = lfs2->root[0]; dir->tail[1] = lfs2->root[1]; + // empty paths are not allowed + if (*name == '\0') { + return LFS2_ERR_INVAL; + } + while (true) { nextname: - // skip slashes - name += strspn(name, "/"); + // skip slashes if we're a directory + if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { + name += strspn(name, "/"); + } lfs2_size_t namelen = strcspn(name, "/"); - // skip '.' and root '..' - if ((namelen == 1 && memcmp(name, ".", 1) == 0) || - (namelen == 2 && memcmp(name, "..", 2) == 0)) { + // skip '.' + if (namelen == 1 && memcmp(name, ".", 1) == 0) { name += namelen; goto nextname; } + // error on unmatched '..', trying to go above root? + if (namelen == 2 && memcmp(name, "..", 2) == 0) { + return LFS2_ERR_INVAL; + } + // skip if matched by '..' in name const char *suffix = name + namelen; lfs2_size_t sufflen; @@ -1475,7 +1524,9 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, break; } - if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) { + // noop + } else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { depth -= 1; if (depth == 0) { name = suffix + sufflen; @@ -1489,14 +1540,14 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, } // found path - if (name[0] == '\0') { + if (*name == '\0') { return tag; } // update what we've found so far *path = name; - // only continue if we hit a directory + // only continue if we're a directory if (lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { return LFS2_ERR_NOTDIR; } @@ -1516,8 +1567,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, tag = lfs2_dir_fetchmatch(lfs2, dir, dir->tail, LFS2_MKTAG(0x780, 0, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, namelen), - // are we last name? - (strchr(name, '/') == NULL) ? id : NULL, + id, lfs2_dir_find_match, &(struct lfs2_dir_find_match){ lfs2, name, namelen}); if (tag < 0) { @@ -2105,13 +2155,14 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // And we cap at half a block to avoid degenerate cases with // nearly-full metadata blocks. // + lfs2_size_t metadata_max = (lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size; if (end - split < 0xff && size <= lfs2_min( - lfs2->cfg->block_size - 40, + metadata_max - 40, lfs2_alignup( - (lfs2->cfg->metadata_max - ? lfs2->cfg->metadata_max - : lfs2->cfg->block_size)/2, + metadata_max/2, lfs2->cfg->prog_size))) { break; } @@ -2146,14 +2197,16 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, && lfs2_pair_cmp(dir->pair, (const lfs2_block_t[2]){0, 1}) == 0) { // oh no! we're writing too much to the superblock, // should we expand? - lfs2_ssize_t size = lfs2_fs_rawsize(lfs2); + lfs2_ssize_t size = lfs2_fs_size_(lfs2); if (size < 0) { return size; } - // do we have extra space? littlefs can't reclaim this space - // by itself, so expand cautiously - if ((lfs2_size_t)size < lfs2->block_count/2) { + // littlefs cannot reclaim expanded superblocks, so expand cautiously + // + // if our filesystem is more than ~88% full, don't expand, this is + // somewhat arbitrary + if (lfs2->block_count - size > lfs2->block_count/8) { LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); int err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin, end); @@ -2166,7 +2219,8 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // we can do, we'll error later if we've become frozen LFS2_WARN("Unable to expand superblock"); } else { - end = begin; + // duplicate the superblock entry into the new superblock + end = 1; } } } @@ -2312,7 +2366,8 @@ fixmlist:; if (d->m.pair != pair) { for (int i = 0; i < attrcount; i++) { if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && - d->id == lfs2_tag_id(attrs[i].tag)) { + d->id == lfs2_tag_id(attrs[i].tag) && + d->type != LFS2_TYPE_DIR) { d->m.pair[0] = LFS2_BLOCK_NULL; d->m.pair[1] = LFS2_BLOCK_NULL; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && @@ -2333,7 +2388,9 @@ fixmlist:; while (d->id >= d->m.count && d->m.split) { // we split and id is on tail now - d->id -= d->m.count; + if (lfs2_pair_cmp(d->m.tail, lfs2->root) != 0) { + d->id -= d->m.count; + } int err = lfs2_dir_fetch(lfs2, &d->m, d->m.tail); if (err) { return err; @@ -2499,7 +2556,7 @@ static int lfs2_dir_orphaningcommit(lfs2_t *lfs2, lfs2_mdir_t *dir, if (err != LFS2_ERR_NOENT) { if (lfs2_gstate_hasorphans(&lfs2->gstate)) { // next step, clean up orphans - err = lfs2_fs_preporphans(lfs2, -hasparent); + err = lfs2_fs_preporphans(lfs2, -(int8_t)hasparent); if (err) { return err; } @@ -2564,7 +2621,7 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, /// Top level directory operations /// #ifndef LFS2_READONLY -static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { +static int lfs2_mkdir_(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -2575,18 +2632,18 @@ static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { cwd.next = lfs2->mlist; uint16_t id; err = lfs2_dir_find(lfs2, &cwd.m, &path, &id); - if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { + if (!(err == LFS2_ERR_NOENT && lfs2_path_islast(path))) { return (err < 0) ? err : LFS2_ERR_EXIST; } // check that name fits - lfs2_size_t nlen = strlen(path); + lfs2_size_t nlen = lfs2_path_namelen(path); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } // build up new directory - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); lfs2_mdir_t dir; err = lfs2_dir_alloc(lfs2, &dir); if (err) { @@ -2660,7 +2717,7 @@ static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { } #endif -static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { +static int lfs2_dir_open_(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { lfs2_stag_t tag = lfs2_dir_find(lfs2, &dir->m, &path, NULL); if (tag < 0) { return tag; @@ -2704,14 +2761,14 @@ static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { return 0; } -static int lfs2_dir_rawclose(lfs2_t *lfs2, lfs2_dir_t *dir) { +static int lfs2_dir_close_(lfs2_t *lfs2, lfs2_dir_t *dir) { // remove from list of mdirs lfs2_mlist_remove(lfs2, (struct lfs2_mlist *)dir); return 0; } -static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { +static int lfs2_dir_read_(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { memset(info, 0, sizeof(*info)); // special offset for '.' and '..' @@ -2756,9 +2813,9 @@ static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *inf return true; } -static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { +static int lfs2_dir_seek_(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { // simply walk from head dir - int err = lfs2_dir_rawrewind(lfs2, dir); + int err = lfs2_dir_rewind_(lfs2, dir); if (err) { return err; } @@ -2793,12 +2850,12 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { return 0; } -static lfs2_soff_t lfs2_dir_rawtell(lfs2_t *lfs2, lfs2_dir_t *dir) { +static lfs2_soff_t lfs2_dir_tell_(lfs2_t *lfs2, lfs2_dir_t *dir) { (void)lfs2; return dir->pos; } -static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir) { +static int lfs2_dir_rewind_(lfs2_t *lfs2, lfs2_dir_t *dir) { // reload the head dir int err = lfs2_dir_fetch(lfs2, &dir->m, dir->head); if (err) { @@ -3004,7 +3061,7 @@ static int lfs2_ctz_traverse(lfs2_t *lfs2, /// Top level file operations /// -static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_opencfg_(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags, const struct lfs2_file_config *cfg) { #ifndef LFS2_READONLY @@ -3029,7 +3086,7 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, // allocate entry for file if it doesn't exist lfs2_stag_t tag = lfs2_dir_find(lfs2, &file->m, &path, &file->id); - if (tag < 0 && !(tag == LFS2_ERR_NOENT && file->id != 0x3ff)) { + if (tag < 0 && !(tag == LFS2_ERR_NOENT && lfs2_path_islast(path))) { err = tag; goto cleanup; } @@ -3049,8 +3106,14 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, goto cleanup; } + // don't allow trailing slashes + if (lfs2_path_isdir(path)) { + err = LFS2_ERR_NOTDIR; + goto cleanup; + } + // check that name fits - lfs2_size_t nlen = strlen(path); + lfs2_size_t nlen = lfs2_path_namelen(path); if (nlen > lfs2->name_max) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; @@ -3166,22 +3229,22 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, #ifndef LFS2_READONLY file->flags |= LFS2_F_ERRED; #endif - lfs2_file_rawclose(lfs2, file); + lfs2_file_close_(lfs2, file); return err; } #ifndef LFS2_NO_MALLOC -static int lfs2_file_rawopen(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_open_(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { static const struct lfs2_file_config defaults = {0}; - int err = lfs2_file_rawopencfg(lfs2, file, path, flags, &defaults); + int err = lfs2_file_opencfg_(lfs2, file, path, flags, &defaults); return err; } #endif -static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file) { +static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY - int err = lfs2_file_rawsync(lfs2, file); + int err = lfs2_file_sync_(lfs2, file); #else int err = 0; #endif @@ -3272,7 +3335,7 @@ static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->off = file->pos; - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); int err = lfs2_file_relocate(lfs2, file); if (err) { return err; @@ -3364,7 +3427,7 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { } #ifndef LFS2_READONLY -static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { +static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_ERRED) { // it's not safe to do anything if our file errored return 0; @@ -3379,6 +3442,15 @@ static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { + // before we commit metadata, we need sync the disk to make sure + // data writes don't complete after metadata writes + if (!(file->flags & LFS2_F_INLINE)) { + err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); + if (err) { + return err; + } + } + // update dir entry uint16_t type; const void *buffer; @@ -3477,7 +3549,7 @@ static lfs2_ssize_t lfs2_file_flushedread(lfs2_t *lfs2, lfs2_file_t *file, return size; } -static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_read_(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); @@ -3502,11 +3574,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, lfs2_size_t nsize = size; if ((file->flags & LFS2_F_INLINE) && - lfs2_max(file->pos+nsize, file->ctz.size) > - lfs2_min(0x3fe, lfs2_min( - lfs2->cfg->cache_size, - (lfs2->cfg->metadata_max ? - lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) { + lfs2_max(file->pos+nsize, file->ctz.size) > lfs2->inline_max) { // inline file doesn't fit anymore int err = lfs2_file_outline(lfs2, file); if (err) { @@ -3535,7 +3603,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, } // extend file with new blocks - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); int err = lfs2_ctz_extend(lfs2, &file->cache, &lfs2->rcache, file->block, file->pos, &file->block, &file->off); @@ -3578,13 +3646,13 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, data += diff; nsize -= diff; - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); } return size; } -static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); @@ -3628,25 +3696,19 @@ static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, } #endif -static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_soff_t lfs2_file_seek_(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { // find new pos + // + // fortunately for us, littlefs is limited to 31-bit file sizes, so we + // don't have to worry too much about integer overflow lfs2_off_t npos = file->pos; if (whence == LFS2_SEEK_SET) { npos = off; } else if (whence == LFS2_SEEK_CUR) { - if ((lfs2_soff_t)file->pos + off < 0) { - return LFS2_ERR_INVAL; - } else { - npos = file->pos + off; - } + npos = file->pos + (lfs2_off_t)off; } else if (whence == LFS2_SEEK_END) { - lfs2_soff_t res = lfs2_file_rawsize(lfs2, file) + off; - if (res < 0) { - return LFS2_ERR_INVAL; - } else { - npos = res; - } + npos = (lfs2_off_t)lfs2_file_size_(lfs2, file) + (lfs2_off_t)off; } if (npos > lfs2->file_max) { @@ -3661,13 +3723,8 @@ static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, // if we're only reading and our new offset is still in the file's cache // we can avoid flushing and needing to reread the data - if ( -#ifndef LFS2_READONLY - !(file->flags & LFS2_F_WRITING) -#else - true -#endif - ) { + if ((file->flags & LFS2_F_READING) + && file->off != lfs2->cfg->block_size) { int oindex = lfs2_ctz_index(lfs2, &(lfs2_off_t){file->pos}); lfs2_off_t noff = npos; int nindex = lfs2_ctz_index(lfs2, &noff); @@ -3692,7 +3749,7 @@ static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, } #ifndef LFS2_READONLY -static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { +static int lfs2_file_truncate_(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); if (size > LFS2_FILE_MAX) { @@ -3700,15 +3757,12 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } lfs2_off_t pos = file->pos; - lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file); + lfs2_off_t oldsize = lfs2_file_size_(lfs2, file); if (size < oldsize) { // revert to inline file? - if (size <= lfs2_min(0x3fe, lfs2_min( - lfs2->cfg->cache_size, - (lfs2->cfg->metadata_max ? - lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) { + if (size <= lfs2->inline_max) { // flush+seek to head - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3753,14 +3807,14 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } } else if (size > oldsize) { // flush+seek if not already at end - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_END); if (res < 0) { return (int)res; } // fill with zeros while (file->pos < size) { - res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); + res = lfs2_file_write_(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { return (int)res; } @@ -3768,7 +3822,7 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } // restore pos - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, pos, LFS2_SEEK_SET); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, pos, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3777,13 +3831,13 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } #endif -static lfs2_soff_t lfs2_file_rawtell(lfs2_t *lfs2, lfs2_file_t *file) { +static lfs2_soff_t lfs2_file_tell_(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; return file->pos; } -static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); +static int lfs2_file_rewind_(lfs2_t *lfs2, lfs2_file_t *file) { + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3791,7 +3845,7 @@ static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { return 0; } -static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { +static lfs2_soff_t lfs2_file_size_(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; #ifndef LFS2_READONLY @@ -3805,18 +3859,24 @@ static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { /// General fs operations /// -static int lfs2_rawstat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { +static int lfs2_stat_(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { return (int)tag; } + // only allow trailing slashes on dirs + if (strchr(path, '/') != NULL + && lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { + return LFS2_ERR_NOTDIR; + } + return lfs2_dir_getinfo(lfs2, &cwd, lfs2_tag_id(tag), info); } #ifndef LFS2_READONLY -static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { +static int lfs2_remove_(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -3895,7 +3955,7 @@ static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { #endif #ifndef LFS2_READONLY -static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { +static int lfs2_rename_(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -3914,7 +3974,7 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath uint16_t newid; lfs2_stag_t prevtag = lfs2_dir_find(lfs2, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs2_tag_id(prevtag) == 0x3ff) && - !(prevtag == LFS2_ERR_NOENT && newid != 0x3ff)) { + !(prevtag == LFS2_ERR_NOENT && lfs2_path_islast(newpath))) { return (prevtag < 0) ? (int)prevtag : LFS2_ERR_INVAL; } @@ -3925,8 +3985,14 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath struct lfs2_mlist prevdir; prevdir.next = lfs2->mlist; if (prevtag == LFS2_ERR_NOENT) { + // if we're a file, don't allow trailing slashes + if (lfs2_path_isdir(newpath) + && lfs2_tag_type3(oldtag) != LFS2_TYPE_DIR) { + return LFS2_ERR_NOTDIR; + } + // check that name fits - lfs2_size_t nlen = strlen(newpath); + lfs2_size_t nlen = lfs2_path_namelen(newpath); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } @@ -3938,7 +4004,9 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath newoldid += 1; } } else if (lfs2_tag_type3(prevtag) != lfs2_tag_type3(oldtag)) { - return LFS2_ERR_ISDIR; + return (lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) + ? LFS2_ERR_ISDIR + : LFS2_ERR_NOTDIR; } else if (samepair && newid == newoldid) { // we're renaming to ourselves?? return 0; @@ -3984,7 +4052,8 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath {LFS2_MKTAG_IF(prevtag != LFS2_ERR_NOENT, LFS2_TYPE_DELETE, newid, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0), NULL}, - {LFS2_MKTAG(lfs2_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS2_MKTAG(lfs2_tag_type3(oldtag), + newid, lfs2_path_namelen(newpath)), newpath}, {LFS2_MKTAG(LFS2_FROM_MOVE, newid, lfs2_tag_id(oldtag)), &oldcwd}, {LFS2_MKTAG_IF(samepair, LFS2_TYPE_DELETE, newoldid, 0), NULL})); @@ -4030,7 +4099,7 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath } #endif -static lfs2_ssize_t lfs2_rawgetattr(lfs2_t *lfs2, const char *path, +static lfs2_ssize_t lfs2_getattr_(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); @@ -4088,7 +4157,7 @@ static int lfs2_commitattr(lfs2_t *lfs2, const char *path, #endif #ifndef LFS2_READONLY -static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, +static int lfs2_setattr_(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { if (size > lfs2->attr_max) { return LFS2_ERR_NOSPC; @@ -4099,13 +4168,28 @@ static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, #endif #ifndef LFS2_READONLY -static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { +static int lfs2_removeattr_(lfs2_t *lfs2, const char *path, uint8_t type) { return lfs2_commitattr(lfs2, path, type, NULL, 0x3ff); } #endif /// Filesystem operations /// + +// compile time checks, see lfs2.h for why these limits exist +#if LFS2_NAME_MAX > 1022 +#error "Invalid LFS2_NAME_MAX, must be <= 1022" +#endif + +#if LFS2_FILE_MAX > 2147483647 +#error "Invalid LFS2_FILE_MAX, must be <= 2147483647" +#endif + +#if LFS2_ATTR_MAX > 1022 +#error "Invalid LFS2_ATTR_MAX, must be <= 1022" +#endif + +// common filesystem initialization static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; lfs2->block_count = cfg->block_count; // May be 0 @@ -4126,6 +4210,14 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { // which littlefs currently does not support LFS2_ASSERT((bool)0x80000000); + // check that the required io functions are provided + LFS2_ASSERT(lfs2->cfg->read != NULL); +#ifndef LFS2_READONLY + LFS2_ASSERT(lfs2->cfg->prog != NULL); + LFS2_ASSERT(lfs2->cfg->erase != NULL); + LFS2_ASSERT(lfs2->cfg->sync != NULL); +#endif + // validate that the lfs2-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS2_ASSERT(lfs2->cfg->read_size != 0); @@ -4153,6 +4245,23 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { // wear-leveling. LFS2_ASSERT(lfs2->cfg->block_cycles != 0); + // check that compact_thresh makes sense + // + // metadata can't be compacted below block_size/2, and metadata can't + // exceed a block_size + LFS2_ASSERT(lfs2->cfg->compact_thresh == 0 + || lfs2->cfg->compact_thresh >= lfs2->cfg->block_size/2); + LFS2_ASSERT(lfs2->cfg->compact_thresh == (lfs2_size_t)-1 + || lfs2->cfg->compact_thresh <= lfs2->cfg->block_size); + + // check that metadata_max is a multiple of read_size and prog_size, + // and a factor of the block_size + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->metadata_max % lfs2->cfg->read_size == 0); + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->metadata_max % lfs2->cfg->prog_size == 0); + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->block_size % lfs2->cfg->metadata_max == 0); // setup read cache if (lfs2->cfg->read_buffer) { @@ -4180,15 +4289,14 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_cache_zero(lfs2, &lfs2->rcache); lfs2_cache_zero(lfs2, &lfs2->pcache); - // setup lookahead, must be multiple of 64-bits, 32-bit aligned + // setup lookahead buffer, note mount finishes initializing this after + // we establish a decent pseudo-random seed LFS2_ASSERT(lfs2->cfg->lookahead_size > 0); - LFS2_ASSERT(lfs2->cfg->lookahead_size % 8 == 0 && - (uintptr_t)lfs2->cfg->lookahead_buffer % 4 == 0); if (lfs2->cfg->lookahead_buffer) { - lfs2->free.buffer = lfs2->cfg->lookahead_buffer; + lfs2->lookahead.buffer = lfs2->cfg->lookahead_buffer; } else { - lfs2->free.buffer = lfs2_malloc(lfs2->cfg->lookahead_size); - if (!lfs2->free.buffer) { + lfs2->lookahead.buffer = lfs2_malloc(lfs2->cfg->lookahead_size); + if (!lfs2->lookahead.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } @@ -4215,6 +4323,27 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { LFS2_ASSERT(lfs2->cfg->metadata_max <= lfs2->cfg->block_size); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= lfs2->cfg->cache_size); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= lfs2->attr_max); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= ((lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size)/8); + lfs2->inline_max = lfs2->cfg->inline_max; + if (lfs2->inline_max == (lfs2_size_t)-1) { + lfs2->inline_max = 0; + } else if (lfs2->inline_max == 0) { + lfs2->inline_max = lfs2_min( + lfs2->cfg->cache_size, + lfs2_min( + lfs2->attr_max, + ((lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size)/8)); + } + // setup default state lfs2->root[0] = LFS2_BLOCK_NULL; lfs2->root[1] = LFS2_BLOCK_NULL; @@ -4245,7 +4374,7 @@ static int lfs2_deinit(lfs2_t *lfs2) { } if (!lfs2->cfg->lookahead_buffer) { - lfs2_free(lfs2->free.buffer); + lfs2_free(lfs2->lookahead.buffer); } return 0; @@ -4254,7 +4383,7 @@ static int lfs2_deinit(lfs2_t *lfs2) { #ifndef LFS2_READONLY -static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { +static int lfs2_format_(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; { err = lfs2_init(lfs2, cfg); @@ -4265,12 +4394,12 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { LFS2_ASSERT(cfg->block_count != 0); // create free lookahead - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - lfs2->free.off = 0; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, + memset(lfs2->lookahead.buffer, 0, lfs2->cfg->lookahead_size); + lfs2->lookahead.start = 0; + lfs2->lookahead.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->block_count); - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); // create root dir lfs2_mdir_t root; @@ -4321,7 +4450,31 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { } #endif -static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { +struct lfs2_tortoise_t { + lfs2_block_t pair[2]; + lfs2_size_t i; + lfs2_size_t period; +}; + +static int lfs2_tortoise_detectcycles( + const lfs2_mdir_t *dir, struct lfs2_tortoise_t *tortoise) { + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(dir->tail, tortoise->pair)) { + LFS2_WARN("Cycle detected in tail list"); + return LFS2_ERR_CORRUPT; + } + if (tortoise->i == tortoise->period) { + tortoise->pair[0] = dir->tail[0]; + tortoise->pair[1] = dir->tail[1]; + tortoise->i = 0; + tortoise->period *= 2; + } + tortoise->i += 1; + + return LFS2_ERR_OK; +} + +static int lfs2_mount_(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = lfs2_init(lfs2, cfg); if (err) { return err; @@ -4329,23 +4482,16 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // scan directory blocks for superblock and any global updates lfs2_mdir_t dir = {.tail = {0, 1}}; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; while (!lfs2_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(dir.tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); - err = LFS2_ERR_CORRUPT; + err = lfs2_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { goto cleanup; } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; // fetch next block in tail list lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, &dir, dir.tail, @@ -4394,6 +4540,7 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // found older minor version? set an in-device only bit in the // gstate so we know we need to rewrite the superblock before // the first write + bool needssuperblock = false; if (minor_version < lfs2_fs_disk_version_minor(lfs2)) { LFS2_DEBUG("Found older minor version " "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, @@ -4401,10 +4548,11 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { minor_version, lfs2_fs_disk_version_major(lfs2), lfs2_fs_disk_version_minor(lfs2)); - // note this bit is reserved on disk, so fetching more gstate - // will not interfere here - lfs2_fs_prepsuperblock(lfs2, true); + needssuperblock = true; } + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs2_fs_prepsuperblock(lfs2, needssuperblock); // check superblock configuration if (superblock.name_max) { @@ -4438,6 +4586,9 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } lfs2->attr_max = superblock.attr_max; + + // we also need to update inline_max in case attr_max changed + lfs2->inline_max = lfs2_min(lfs2->inline_max, lfs2->attr_max); } // this is where we get the block_count from disk if block_count=0 @@ -4478,23 +4629,23 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // setup free lookahead, to distribute allocations uniformly across // boots, we start the allocator at a random location - lfs2->free.off = lfs2->seed % lfs2->block_count; + lfs2->lookahead.start = lfs2->seed % lfs2->block_count; lfs2_alloc_drop(lfs2); return 0; cleanup: - lfs2_rawunmount(lfs2); + lfs2_unmount_(lfs2); return err; } -static int lfs2_rawunmount(lfs2_t *lfs2) { +static int lfs2_unmount_(lfs2_t *lfs2) { return lfs2_deinit(lfs2); } /// Filesystem filesystem operations /// -static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { +static int lfs2_fs_stat_(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { // if the superblock is up-to-date, we must be on the most recent // minor version of littlefs if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) { @@ -4534,7 +4685,7 @@ static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { return 0; } -int lfs2_fs_rawtraverse(lfs2_t *lfs2, +int lfs2_fs_traverse_(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { // iterate over metadata pairs @@ -4553,22 +4704,17 @@ int lfs2_fs_rawtraverse(lfs2_t *lfs2, } #endif - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(dir.tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); + err = lfs2_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { return LFS2_ERR_CORRUPT; } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); @@ -4647,22 +4793,17 @@ static int lfs2_fs_pred(lfs2_t *lfs2, // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(pdir->tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(pdir->tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); + err = lfs2_tortoise_detectcycles(pdir, &tortoise); + if (err < 0) { return LFS2_ERR_CORRUPT; } - if (tortoise_i == tortoise_period) { - tortoise[0] = pdir->tail[0]; - tortoise[1] = pdir->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; if (lfs2_pair_cmp(pdir->tail, pair) == 0) { return 0; @@ -4712,22 +4853,17 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(parent->tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(parent->tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); - return LFS2_ERR_CORRUPT; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = parent->tail[0]; - tortoise[1] = parent->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; + err = lfs2_tortoise_detectcycles(parent, &tortoise); + if (err < 0) { + return err; } - tortoise_i += 1; lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, parent, parent->tail, LFS2_MKTAG(0x7ff, 0, 0x3ff), @@ -4999,7 +5135,7 @@ static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { #endif #ifndef LFS2_READONLY -static int lfs2_fs_rawmkconsistent(lfs2_t *lfs2) { +static int lfs2_fs_mkconsistent_(lfs2_t *lfs2) { // lfs2_fs_forceconsistency does most of the work here int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -5035,9 +5171,9 @@ static int lfs2_fs_size_count(void *p, lfs2_block_t block) { return 0; } -static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { +static lfs2_ssize_t lfs2_fs_size_(lfs2_t *lfs2) { lfs2_size_t size = 0; - int err = lfs2_fs_rawtraverse(lfs2, lfs2_fs_size_count, &size, false); + int err = lfs2_fs_traverse_(lfs2, lfs2_fs_size_count, &size, false); if (err) { return err; } @@ -5045,8 +5181,59 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { return size; } +// explicit garbage collection #ifndef LFS2_READONLY -static int lfs2_fs_rawgrow(lfs2_t *lfs2, lfs2_size_t block_count) { +static int lfs2_fs_gc_(lfs2_t *lfs2) { + // force consistency, even if we're not necessarily going to write, + // because this function is supposed to take care of janitorial work + // isn't it? + int err = lfs2_fs_forceconsistency(lfs2); + if (err) { + return err; + } + + // try to compact metadata pairs, note we can't really accomplish + // anything if compact_thresh doesn't at least leave a prog_size + // available + if (lfs2->cfg->compact_thresh + < lfs2->cfg->block_size - lfs2->cfg->prog_size) { + // iterate over all mdirs + lfs2_mdir_t mdir = {.tail = {0, 1}}; + while (!lfs2_pair_isnull(mdir.tail)) { + err = lfs2_dir_fetch(lfs2, &mdir, mdir.tail); + if (err) { + return err; + } + + // not erased? exceeds our compaction threshold? + if (!mdir.erased || ((lfs2->cfg->compact_thresh == 0) + ? mdir.off > lfs2->cfg->block_size - lfs2->cfg->block_size/8 + : mdir.off > lfs2->cfg->compact_thresh)) { + // the easiest way to trigger a compaction is to mark + // the mdir as unerased and add an empty commit + mdir.erased = false; + err = lfs2_dir_commit(lfs2, &mdir, NULL, 0); + if (err) { + return err; + } + } + } + } + + // try to populate the lookahead buffer, unless it's already full + if (lfs2->lookahead.size < 8*lfs2->cfg->lookahead_size) { + err = lfs2_alloc_scan(lfs2); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS2_READONLY +static int lfs2_fs_grow_(lfs2_t *lfs2, lfs2_size_t block_count) { // shrinking is not supported LFS2_ASSERT(block_count >= lfs2->block_count); @@ -5451,10 +5638,10 @@ static int lfs21_mount(lfs2_t *lfs2, struct lfs21 *lfs21, lfs2->lfs21->root[1] = LFS2_BLOCK_NULL; // setup free lookahead - lfs2->free.off = 0; - lfs2->free.size = 0; - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); + lfs2->lookahead.start = 0; + lfs2->lookahead.size = 0; + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); // load superblock lfs21_dir_t dir; @@ -5505,7 +5692,7 @@ static int lfs21_unmount(lfs2_t *lfs2) { } /// v1 migration /// -static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { +static int lfs2_migrate_(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; // Indeterminate filesystem size not allowed for migration. @@ -5759,7 +5946,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -5772,7 +5959,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawformat(lfs2, cfg); + err = lfs2_format_(lfs2, cfg); LFS2_TRACE("lfs2_format -> %d", err); LFS2_UNLOCK(cfg); @@ -5789,7 +5976,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -5802,7 +5989,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawmount(lfs2, cfg); + err = lfs2_mount_(lfs2, cfg); LFS2_TRACE("lfs2_mount -> %d", err); LFS2_UNLOCK(cfg); @@ -5816,7 +6003,7 @@ int lfs2_unmount(lfs2_t *lfs2) { } LFS2_TRACE("lfs2_unmount(%p)", (void*)lfs2); - err = lfs2_rawunmount(lfs2); + err = lfs2_unmount_(lfs2); LFS2_TRACE("lfs2_unmount -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5831,7 +6018,7 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { } LFS2_TRACE("lfs2_remove(%p, \"%s\")", (void*)lfs2, path); - err = lfs2_rawremove(lfs2, path); + err = lfs2_remove_(lfs2, path); LFS2_TRACE("lfs2_remove -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5847,7 +6034,7 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { } LFS2_TRACE("lfs2_rename(%p, \"%s\", \"%s\")", (void*)lfs2, oldpath, newpath); - err = lfs2_rawrename(lfs2, oldpath, newpath); + err = lfs2_rename_(lfs2, oldpath, newpath); LFS2_TRACE("lfs2_rename -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5862,7 +6049,7 @@ int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { } LFS2_TRACE("lfs2_stat(%p, \"%s\", %p)", (void*)lfs2, path, (void*)info); - err = lfs2_rawstat(lfs2, path, info); + err = lfs2_stat_(lfs2, path, info); LFS2_TRACE("lfs2_stat -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5878,7 +6065,7 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, LFS2_TRACE("lfs2_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); - lfs2_ssize_t res = lfs2_rawgetattr(lfs2, path, type, buffer, size); + lfs2_ssize_t res = lfs2_getattr_(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_getattr -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -5895,7 +6082,7 @@ int lfs2_setattr(lfs2_t *lfs2, const char *path, LFS2_TRACE("lfs2_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); - err = lfs2_rawsetattr(lfs2, path, type, buffer, size); + err = lfs2_setattr_(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_setattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5911,7 +6098,7 @@ int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type) { } LFS2_TRACE("lfs2_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs2, path, type); - err = lfs2_rawremoveattr(lfs2, path, type); + err = lfs2_removeattr_(lfs2, path, type); LFS2_TRACE("lfs2_removeattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5926,10 +6113,10 @@ int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) return err; } LFS2_TRACE("lfs2_file_open(%p, %p, \"%s\", %x)", - (void*)lfs2, (void*)file, path, flags); + (void*)lfs2, (void*)file, path, (unsigned)flags); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawopen(lfs2, file, path, flags); + err = lfs2_file_open_(lfs2, file, path, flags); LFS2_TRACE("lfs2_file_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5946,11 +6133,11 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, } LFS2_TRACE("lfs2_file_opencfg(%p, %p, \"%s\", %x, %p {" ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", - (void*)lfs2, (void*)file, path, flags, + (void*)lfs2, (void*)file, path, (unsigned)flags, (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawopencfg(lfs2, file, path, flags, cfg); + err = lfs2_file_opencfg_(lfs2, file, path, flags, cfg); LFS2_TRACE("lfs2_file_opencfg -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5965,7 +6152,7 @@ int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_close(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawclose(lfs2, file); + err = lfs2_file_close_(lfs2, file); LFS2_TRACE("lfs2_file_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5981,7 +6168,7 @@ int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawsync(lfs2, file); + err = lfs2_file_sync_(lfs2, file); LFS2_TRACE("lfs2_file_sync -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5999,7 +6186,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_ssize_t res = lfs2_file_rawread(lfs2, file, buffer, size); + lfs2_ssize_t res = lfs2_file_read_(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_read -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6017,7 +6204,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, buffer, size); + lfs2_ssize_t res = lfs2_file_write_(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_write -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6035,7 +6222,7 @@ lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, off, whence); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, off, whence); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, off, whence); LFS2_TRACE("lfs2_file_seek -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6052,7 +6239,7 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { (void*)lfs2, (void*)file, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawtruncate(lfs2, file, size); + err = lfs2_file_truncate_(lfs2, file, size); LFS2_TRACE("lfs2_file_truncate -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6068,7 +6255,7 @@ lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_tell(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawtell(lfs2, file); + lfs2_soff_t res = lfs2_file_tell_(lfs2, file); LFS2_TRACE("lfs2_file_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6082,7 +6269,7 @@ int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { } LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); - err = lfs2_file_rawrewind(lfs2, file); + err = lfs2_file_rewind_(lfs2, file); LFS2_TRACE("lfs2_file_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6097,9 +6284,9 @@ lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_size(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawsize(lfs2, file); + lfs2_soff_t res = lfs2_file_size_(lfs2, file); - LFS2_TRACE("lfs2_file_size -> %"PRId32, res); + LFS2_TRACE("lfs2_file_size -> %"PRIu32, res); LFS2_UNLOCK(lfs2->cfg); return res; } @@ -6112,7 +6299,7 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { } LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); - err = lfs2_rawmkdir(lfs2, path); + err = lfs2_mkdir_(lfs2, path); LFS2_TRACE("lfs2_mkdir -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6128,7 +6315,7 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)dir)); - err = lfs2_dir_rawopen(lfs2, dir, path); + err = lfs2_dir_open_(lfs2, dir, path); LFS2_TRACE("lfs2_dir_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6142,7 +6329,7 @@ int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_close(%p, %p)", (void*)lfs2, (void*)dir); - err = lfs2_dir_rawclose(lfs2, dir); + err = lfs2_dir_close_(lfs2, dir); LFS2_TRACE("lfs2_dir_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6157,7 +6344,7 @@ int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { LFS2_TRACE("lfs2_dir_read(%p, %p, %p)", (void*)lfs2, (void*)dir, (void*)info); - err = lfs2_dir_rawread(lfs2, dir, info); + err = lfs2_dir_read_(lfs2, dir, info); LFS2_TRACE("lfs2_dir_read -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6172,7 +6359,7 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { LFS2_TRACE("lfs2_dir_seek(%p, %p, %"PRIu32")", (void*)lfs2, (void*)dir, off); - err = lfs2_dir_rawseek(lfs2, dir, off); + err = lfs2_dir_seek_(lfs2, dir, off); LFS2_TRACE("lfs2_dir_seek -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6186,7 +6373,7 @@ lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_tell(%p, %p)", (void*)lfs2, (void*)dir); - lfs2_soff_t res = lfs2_dir_rawtell(lfs2, dir); + lfs2_soff_t res = lfs2_dir_tell_(lfs2, dir); LFS2_TRACE("lfs2_dir_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6200,7 +6387,7 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_rewind(%p, %p)", (void*)lfs2, (void*)dir); - err = lfs2_dir_rawrewind(lfs2, dir); + err = lfs2_dir_rewind_(lfs2, dir); LFS2_TRACE("lfs2_dir_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6214,7 +6401,7 @@ int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { } LFS2_TRACE("lfs2_fs_stat(%p, %p)", (void*)lfs2, (void*)fsinfo); - err = lfs2_fs_rawstat(lfs2, fsinfo); + err = lfs2_fs_stat_(lfs2, fsinfo); LFS2_TRACE("lfs2_fs_stat -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6228,7 +6415,7 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { } LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); - lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); + lfs2_ssize_t res = lfs2_fs_size_(lfs2); LFS2_TRACE("lfs2_fs_size -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6243,7 +6430,7 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", (void*)lfs2, (void*)(uintptr_t)cb, data); - err = lfs2_fs_rawtraverse(lfs2, cb, data, true); + err = lfs2_fs_traverse_(lfs2, cb, data, true); LFS2_TRACE("lfs2_fs_traverse -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6251,32 +6438,32 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) } #ifndef LFS2_READONLY -int lfs2_fs_gc(lfs2_t *lfs2) { +int lfs2_fs_mkconsistent(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } - LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); + LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2); - err = lfs2_fs_rawgc(lfs2); + err = lfs2_fs_mkconsistent_(lfs2); - LFS2_TRACE("lfs2_fs_gc -> %d", err); + LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif #ifndef LFS2_READONLY -int lfs2_fs_mkconsistent(lfs2_t *lfs2) { +int lfs2_fs_gc(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } - LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2); + LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); - err = lfs2_fs_rawmkconsistent(lfs2); + err = lfs2_fs_gc_(lfs2); - LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err); + LFS2_TRACE("lfs2_fs_gc -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } @@ -6290,7 +6477,7 @@ int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count) { } LFS2_TRACE("lfs2_fs_grow(%p, %"PRIu32")", (void*)lfs2, block_count); - err = lfs2_fs_rawgrow(lfs2, block_count); + err = lfs2_fs_grow_(lfs2, block_count); LFS2_TRACE("lfs2_fs_grow -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6308,7 +6495,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -6321,7 +6508,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawmigrate(lfs2, cfg); + err = lfs2_migrate_(lfs2, cfg); LFS2_TRACE("lfs2_migrate -> %d", err); LFS2_UNLOCK(cfg); diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index 4c426fc4c70..f503fd00bcf 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020008 +#define LFS2_VERSION 0x0002000a #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -52,16 +52,15 @@ typedef uint32_t lfs2_block_t; #endif // Maximum size of a file in bytes, may be redefined to limit to support other -// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the -// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return -// incorrect values due to using signed integers. Stored in superblock and -// must be respected by other littlefs drivers. +// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be +// respected by other littlefs drivers. #ifndef LFS2_FILE_MAX #define LFS2_FILE_MAX 2147483647 #endif // Maximum size of custom attributes in bytes, may be redefined, but there is -// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022. +// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022. Stored +// in superblock and must be respected by other littlefs drivers. #ifndef LFS2_ATTR_MAX #define LFS2_ATTR_MAX 1022 #endif @@ -205,7 +204,8 @@ struct lfs2_config { // program sizes. lfs2_size_t block_size; - // Number of erasable blocks on the device. + // Number of erasable blocks on the device. Defaults to block_count stored + // on disk when zero. lfs2_size_t block_count; // Number of erase cycles before littlefs evicts metadata logs and moves @@ -226,9 +226,20 @@ struct lfs2_config { // Size of the lookahead buffer in bytes. A larger lookahead buffer // increases the number of blocks found during an allocation pass. The // lookahead buffer is stored as a compact bitmap, so each byte of RAM - // can track 8 blocks. Must be a multiple of 8. + // can track 8 blocks. lfs2_size_t lookahead_size; + // Threshold for metadata compaction during lfs2_fs_gc in bytes. Metadata + // pairs that exceed this threshold will be compacted during lfs2_fs_gc. + // Defaults to ~88% block_size when zero, though the default may change + // in the future. + // + // Note this only affects lfs2_fs_gc. Normal compactions still only occur + // when full. + // + // Set to -1 to disable metadata compaction during lfs2_fs_gc. + lfs2_size_t compact_thresh; + // Optional statically allocated read buffer. Must be cache_size. // By default lfs2_malloc is used to allocate this buffer. void *read_buffer; @@ -237,25 +248,24 @@ struct lfs2_config { // By default lfs2_malloc is used to allocate this buffer. void *prog_buffer; - // Optional statically allocated lookahead buffer. Must be lookahead_size - // and aligned to a 32-bit boundary. By default lfs2_malloc is used to - // allocate this buffer. + // Optional statically allocated lookahead buffer. Must be lookahead_size. + // By default lfs2_malloc is used to allocate this buffer. void *lookahead_buffer; // Optional upper limit on length of file names in bytes. No downside for // larger names except the size of the info struct which is controlled by - // the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in - // superblock and must be respected by other littlefs drivers. + // the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX or name_max stored on + // disk when zero. lfs2_size_t name_max; // Optional upper limit on files in bytes. No downside for larger files - // but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored - // in superblock and must be respected by other littlefs drivers. + // but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX or file_max stored + // on disk when zero. lfs2_size_t file_max; // Optional upper limit on custom attributes in bytes. No downside for // larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to - // LFS2_ATTR_MAX when zero. + // LFS2_ATTR_MAX or attr_max stored on disk when zero. lfs2_size_t attr_max; // Optional upper limit on total space given to metadata pairs in bytes. On @@ -264,6 +274,15 @@ struct lfs2_config { // Defaults to block_size when zero. lfs2_size_t metadata_max; + // Optional upper limit on inlined files in bytes. Inlined files live in + // metadata and decrease storage requirements, but may be limited to + // improve metadata-related performance. Must be <= cache_size, <= + // attr_max, and <= block_size/8. Defaults to the largest possible + // inline_max when zero. + // + // Set to -1 to disable inlined files. + lfs2_size_t inline_max; + #ifdef LFS2_MULTIVERSION // On-disk version to use when writing in the form of 16-bit major version // + 16-bit minor version. This limiting metadata to what is supported by @@ -430,19 +449,20 @@ typedef struct lfs2 { lfs2_gstate_t gdisk; lfs2_gstate_t gdelta; - struct lfs2_free { - lfs2_block_t off; + struct lfs2_lookahead { + lfs2_block_t start; lfs2_block_t size; - lfs2_block_t i; - lfs2_block_t ack; - uint32_t *buffer; - } free; + lfs2_block_t next; + lfs2_block_t ckpoint; + uint8_t *buffer; + } lookahead; const struct lfs2_config *cfg; lfs2_size_t block_count; lfs2_size_t name_max; lfs2_size_t file_max; lfs2_size_t attr_max; + lfs2_size_t inline_max; #ifdef LFS2_MIGRATE struct lfs21 *lfs21; @@ -712,18 +732,6 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2); // Returns a negative error code on failure. int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); -// Attempt to proactively find free blocks -// -// Calling this function is not required, but may allowing the offloading of -// the expensive block allocation scan to a less time-critical code path. -// -// Note: littlefs currently does not persist any found free blocks to disk. -// This may change in the future. -// -// Returns a negative error code on failure. Finding no free blocks is -// not an error. -int lfs2_fs_gc(lfs2_t *lfs2); - #ifndef LFS2_READONLY // Attempt to make the filesystem consistent and ready for writing // @@ -736,6 +744,24 @@ int lfs2_fs_gc(lfs2_t *lfs2); int lfs2_fs_mkconsistent(lfs2_t *lfs2); #endif +#ifndef LFS2_READONLY +// Attempt any janitorial work +// +// This currently: +// 1. Calls mkconsistent if not already consistent +// 2. Compacts metadata > compact_thresh +// 3. Populates the block allocator +// +// Though additional janitorial work may be added in the future. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. Accomplishing nothing is not +// an error. +int lfs2_fs_gc(lfs2_t *lfs2); +#endif + #ifndef LFS2_READONLY // Grows the filesystem to a new size, updating the superblock with the new // block count. diff --git a/lib/littlefs/lfs2_util.c b/lib/littlefs/lfs2_util.c index c9850e78869..4fe7e5340ce 100644 --- a/lib/littlefs/lfs2_util.c +++ b/lib/littlefs/lfs2_util.c @@ -11,6 +11,8 @@ #ifndef LFS2_CONFIG +// If user provides their own CRC impl we don't need this +#ifndef LFS2_CRC // Software CRC implementation with small lookup table uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { static const uint32_t rtable[16] = { @@ -29,6 +31,7 @@ uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { return crc; } +#endif #endif diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index dd2cbcc106d..48d9f4c572a 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -8,6 +8,9 @@ #ifndef LFS2_UTIL_H #define LFS2_UTIL_H +#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x) +#define LFS2_STRINGIZE2(x) #x + // Users can override lfs2_util.h with their own configuration by defining // LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h). // @@ -15,11 +18,26 @@ // provided by the config file. To start, I would suggest copying lfs2_util.h // and modifying as needed. #ifdef LFS2_CONFIG -#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x) -#define LFS2_STRINGIZE2(x) #x #include LFS2_STRINGIZE(LFS2_CONFIG) #else +// Alternatively, users can provide a header file which defines +// macros and other things consumed by littlefs. +// +// For example, provide my_defines.h, which contains +// something like: +// +// #include +// extern void *my_malloc(size_t sz); +// #define LFS2_MALLOC(sz) my_malloc(sz) +// +// And build littlefs with the header by defining LFS2_DEFINES. +// (-DLFS2_DEFINES=my_defines.h) + +#ifdef LFS2_DEFINES +#include LFS2_STRINGIZE(LFS2_DEFINES) +#endif + // System includes #include #include @@ -212,12 +230,22 @@ static inline uint32_t lfs2_tobe32(uint32_t a) { } // Calculate CRC-32 with polynomial = 0x04c11db7 +#ifdef LFS2_CRC +uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS2_CRC(crc, buffer, size) +} +#else uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); +#endif // Allocate memory, only used if buffers are not provided to littlefs -// Note, memory must be 64-bit aligned +// +// littlefs current has no alignment requirements, as it only allocates +// byte-level buffers. static inline void *lfs2_malloc(size_t size) { -#ifndef LFS2_NO_MALLOC +#if defined(LFS2_MALLOC) + return LFS2_MALLOC(size); +#elif !defined(LFS2_NO_MALLOC) return malloc(size); #else (void)size; @@ -227,7 +255,9 @@ static inline void *lfs2_malloc(size_t size) { // Deallocate memory, only used if buffers are not provided to littlefs static inline void lfs2_free(void *p) { -#ifndef LFS2_NO_MALLOC +#if defined(LFS2_FREE) + LFS2_FREE(p); +#elif !defined(LFS2_NO_MALLOC) free(p); #else (void)p; diff --git a/tests/extmod/vfs_lfs.py b/tests/extmod/vfs_lfs.py index 3ad57fd9c38..40d58e9c9f7 100644 --- a/tests/extmod/vfs_lfs.py +++ b/tests/extmod/vfs_lfs.py @@ -136,7 +136,7 @@ def test(bdev, vfs_class): print(fs.getcwd()) fs.chdir("../testdir") print(fs.getcwd()) - fs.chdir("../..") + fs.chdir("..") print(fs.getcwd()) fs.chdir(".//testdir") print(fs.getcwd()) From 2b29b1b8f9dc7014c27d9df43ca0ebd3ccc86060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 16 Apr 2025 18:26:37 +0200 Subject: [PATCH 0614/2098] lib/littlefs: Reuse existing CRC32 function to save space. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Getting this to work required fixing a small issue in `lfs2_util.h`, which has been submitted upstream. Signed-off-by: Daniël van de Giessen --- extmod/extmod.mk | 2 +- extmod/littlefs-include/lfs2_defines.h | 12 ++++++++++++ lib/littlefs/lfs2_util.h | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 extmod/littlefs-include/lfs2_defines.h diff --git a/extmod/extmod.mk b/extmod/extmod.mk index b2a0f490b6e..997dd3ba98e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -206,7 +206,7 @@ endif ifeq ($(MICROPY_VFS_LFS2),1) CFLAGS_EXTMOD += -DMICROPY_VFS_LFS2=1 -CFLAGS_THIRDPARTY += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT +CFLAGS_THIRDPARTY += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT -DLFS2_DEFINES=extmod/littlefs-include/lfs2_defines.h SRC_THIRDPARTY_C += $(addprefix $(LITTLEFS_DIR)/,\ lfs2.c \ lfs2_util.c \ diff --git a/extmod/littlefs-include/lfs2_defines.h b/extmod/littlefs-include/lfs2_defines.h new file mode 100644 index 00000000000..4ae566f508a --- /dev/null +++ b/extmod/littlefs-include/lfs2_defines.h @@ -0,0 +1,12 @@ +#ifndef LFS2_DEFINES_H +#define LFS2_DEFINES_H + +#include "py/mpconfig.h" + +#if MICROPY_PY_DEFLATE +// We reuse the CRC32 implementation from uzlib to save a few bytes +#include "lib/uzlib/uzlib.h" +#define LFS2_CRC(crc, buffer, size) uzlib_crc32(buffer, size, crc) +#endif + +#endif \ No newline at end of file diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 48d9f4c572a..3b191f6885f 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -231,8 +231,8 @@ static inline uint32_t lfs2_tobe32(uint32_t a) { // Calculate CRC-32 with polynomial = 0x04c11db7 #ifdef LFS2_CRC -uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { - return LFS2_CRC(crc, buffer, size) +static inline uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS2_CRC(crc, buffer, size); } #else uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); From aedaa405953f6fad29ecd5e16d10de126ecb1af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 16 Apr 2025 14:17:14 +0200 Subject: [PATCH 0615/2098] py/modthread: Initialize thread state nlr_top to NULL. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures the check in MP_NLR_JUMP_HEAD works as expected and nlr_jump_fail gets called so we get a bit better error message. Signed-off-by: Daniël van de Giessen --- py/runtime.h | 1 + 1 file changed, 1 insertion(+) diff --git a/py/runtime.h b/py/runtime.h index 77cdd0e203d..ffbc3972a32 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -165,6 +165,7 @@ static inline void mp_thread_init_state(mp_state_thread_t *ts, size_t stack_size ts->gc_lock_depth = 0; // There are no pending jump callbacks or exceptions yet + ts->nlr_top = NULL; ts->nlr_jump_callback_top = NULL; ts->mp_pending_exception = MP_OBJ_NULL; From 1b123579a2c987901c3ddb9c342b041f1526943f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Wed, 9 Apr 2025 13:39:51 +0200 Subject: [PATCH 0616/2098] py/makeversionhdr.py: Change utcfromtimestamp() to fromtimestamp(). The former is deprecated. Signed-off-by: Jos Verlinde --- py/makeversionhdr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index cb7325e416c..406a061a094 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -117,8 +117,8 @@ def make_version_header(repo_path, filename): build_date = datetime.date.today() if "SOURCE_DATE_EPOCH" in os.environ: - build_date = datetime.datetime.utcfromtimestamp( - int(os.environ["SOURCE_DATE_EPOCH"]) + build_date = datetime.datetime.fromtimestamp( + int(os.environ["SOURCE_DATE_EPOCH"]), datetime.timezone.utc ).date() # Generate the file with the git and version info From fe28cd78fefde7da1bc7614982223ce9e522143a Mon Sep 17 00:00:00 2001 From: danicampora Date: Thu, 3 Oct 2024 09:50:15 +0200 Subject: [PATCH 0617/2098] zephyr/modbluetooth_zephyr: Allow BLE to create services at runtime. This commit adds the required functionality for a peripheral to create services at runtime, using `BLE.register_services()`. The feature is enabled on the nrf52840dk_nrf52840 board. Note that the `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n` option must be used so that BLE notifications/indications can be sent even if not subscribed. Signed-off-by: danicampora --- ports/zephyr/boards/nrf52840dk_nrf52840.conf | 5 + ports/zephyr/modbluetooth_zephyr.c | 611 ++++++++++++++++++- 2 files changed, 604 insertions(+), 12 deletions(-) diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.conf b/ports/zephyr/boards/nrf52840dk_nrf52840.conf index e9ec78b64ff..dc2ed1c4b49 100644 --- a/ports/zephyr/boards/nrf52840dk_nrf52840.conf +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.conf @@ -1,8 +1,13 @@ CONFIG_NETWORKING=n CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_CENTRAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_L2CAP_TX_MTU=252 +CONFIG_BT_BUF_ACL_RX_SIZE=256 +CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n CONFIG_MICROPY_HEAP_SIZE=98304 CONFIG_MAIN_STACK_SIZE=8192 diff --git a/ports/zephyr/modbluetooth_zephyr.c b/ports/zephyr/modbluetooth_zephyr.c index 8947bdf5356..cdbeb7fc353 100644 --- a/ports/zephyr/modbluetooth_zephyr.c +++ b/ports/zephyr/modbluetooth_zephyr.c @@ -31,8 +31,12 @@ #if MICROPY_PY_BLUETOOTH +#include #include #include +#include +#include +#include #include "extmod/modbluetooth.h" #define DEBUG_printf(...) // printk("BLE: " __VA_ARGS__) @@ -44,6 +48,23 @@ #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV +#define MP_BLUETOOTH_ZEPHYR_MAX_SERVICES (8) + +/* This masks Permission bits from GATT API */ +#define GATT_PERM_MASK (BT_GATT_PERM_READ | \ + BT_GATT_PERM_READ_AUTHEN | \ + BT_GATT_PERM_READ_ENCRYPT | \ + BT_GATT_PERM_WRITE | \ + BT_GATT_PERM_WRITE_AUTHEN | \ + BT_GATT_PERM_WRITE_ENCRYPT | \ + BT_GATT_PERM_PREPARE_WRITE) + +#define GATT_PERM_ENC_READ_MASK (BT_GATT_PERM_READ_ENCRYPT | \ + BT_GATT_PERM_READ_AUTHEN) + +#define GATT_PERM_ENC_WRITE_MASK (BT_GATT_PERM_WRITE_ENCRYPT | \ + BT_GATT_PERM_WRITE_AUTHEN) + enum { MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF, MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE, @@ -56,9 +77,42 @@ enum { MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE, }; +union uuid_u { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; +}; + +struct add_characteristic { + uint8_t properties; + uint8_t permissions; + const struct bt_uuid *uuid; +}; + +struct add_descriptor { + uint8_t permissions; + const struct bt_uuid *uuid; +}; + +typedef struct _mp_bt_zephyr_conn_t { + struct bt_conn *conn; + struct _mp_bt_zephyr_conn_t *next; +} mp_bt_zephyr_conn_t; + typedef struct _mp_bluetooth_zephyr_root_pointers_t { + // list of objects to be tracked by the gc + mp_obj_t objs_list; + // Characteristic (and descriptor) value storage. mp_gatts_db_t gatts_db; + + // Service definitions. + size_t n_services; + struct bt_gatt_service *services[MP_BLUETOOTH_ZEPHYR_MAX_SERVICES]; + + // active connections + mp_bt_zephyr_conn_t *connections; } mp_bluetooth_zephyr_root_pointers_t; static int mp_bluetooth_zephyr_ble_state; @@ -69,6 +123,98 @@ static struct k_timer mp_bluetooth_zephyr_gap_scan_timer; static struct bt_le_scan_cb mp_bluetooth_zephyr_gap_scan_cb_struct; #endif +static struct bt_data bt_ad_data[8]; +static size_t bt_ad_len = 0; +static struct bt_data bt_sd_data[8]; +static size_t bt_sd_len = 0; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_next_conn; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle); +static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection); +static void mp_bt_zephyr_remove_connection(uint8_t conn_handle); +static void mp_bt_zephyr_connected(struct bt_conn *connected, uint8_t err); +static void mp_bt_zephyr_disconnected(struct bt_conn *disconn, uint8_t reason); +static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid); +static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len); +static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr); +static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value); +static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc); +static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc); +static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc); +static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err); +static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle); + +static struct bt_conn_cb mp_bt_zephyr_conn_callbacks = { + .connected = mp_bt_zephyr_connected, + .disconnected = mp_bt_zephyr_disconnected, +}; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle) { + struct bt_conn_info info; + for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { + if (connection->conn) { + bt_conn_get_info(connection->conn, &info); + if (info.id == conn_handle) { + return connection; + } + } + } + return NULL; +} + +static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection) { + connection->next = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection; +} + +static void mp_bt_zephyr_remove_connection(uint8_t conn_handle) { + struct bt_conn_info info; + mp_bt_zephyr_conn_t *prev = NULL; + for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { + if (connection->conn) { + bt_conn_get_info(connection->conn, &info); + if (info.id == conn_handle) { + // unlink this item and the gc will eventually collect it + if (prev != NULL) { + prev->next = connection->next; + } else { + // move the start pointer + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection->next; + } + break; + } else { + prev = connection; + } + } + } +} + +static void mp_bt_zephyr_connected(struct bt_conn *conn, uint8_t err) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + + if (err) { + uint8_t addr[6] = {0}; + DEBUG_printf("Connection from central failed (err %u)\n", err); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, 0xff, addr); + } else { + DEBUG_printf("Central connected with id %d\n", info.id); + mp_bt_zephyr_next_conn->conn = bt_conn_ref(conn); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_CONNECT, info.id, info.le.dst->type, info.le.dst->a.val); + mp_bt_zephyr_insert_connection(mp_bt_zephyr_next_conn); + } +} + +static void mp_bt_zephyr_disconnected(struct bt_conn *conn, uint8_t reason) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + DEBUG_printf("Central disconnected (id %d reason %u)\n", info.id, reason); + bt_conn_unref(conn); + mp_bt_zephyr_remove_connection(info.id); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, info.le.dst->type, info.le.dst->a.val); +} + static int bt_err_to_errno(int err) { // Zephyr uses errno codes directly, but they are negative. return -err; @@ -128,6 +274,11 @@ int mp_bluetooth_init(void) { MP_STATE_PORT(bluetooth_zephyr_root_pointers) = m_new0(mp_bluetooth_zephyr_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; + mp_bt_zephyr_next_conn = NULL; + + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; k_timer_init(&mp_bluetooth_zephyr_gap_scan_timer, gap_scan_cb_timeout, NULL); @@ -137,6 +288,9 @@ int mp_bluetooth_init(void) { #endif if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) { + + bt_conn_cb_register(&mp_bt_zephyr_conn_callbacks); + // bt_enable can only be called once. int ret = bt_enable(NULL); if (ret) { @@ -160,6 +314,13 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_gap_advertise_stop(); + #if CONFIG_BT_GATT_DYNAMIC_DB + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { + bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; + } + #endif + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE mp_bluetooth_gap_scan_stop(); bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct); @@ -170,6 +331,7 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED; MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL; + mp_bt_zephyr_next_conn = NULL; } bool mp_bluetooth_is_active(void) { @@ -191,7 +353,7 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { } void mp_bluetooth_set_address_mode(uint8_t addr_mode) { - // TODO: implement + mp_raise_OSError(MP_EOPNOTSUPP); } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { @@ -232,15 +394,11 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons mp_bluetooth_gap_advertise_stop(); - struct bt_data bt_ad_data[8]; - size_t bt_ad_len = 0; if (adv_data) { bt_ad_len = MP_ARRAY_SIZE(bt_ad_data); mp_bluetooth_prepare_bt_data(adv_data, adv_data_len, bt_ad_data, &bt_ad_len); } - struct bt_data bt_sd_data[8]; - size_t bt_sd_len = 0; if (sr_data) { bt_sd_len = MP_ARRAY_SIZE(bt_sd_data); mp_bluetooth_prepare_bt_data(sr_data, sr_data_len, bt_sd_data, &bt_sd_len); @@ -259,6 +417,10 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons .peer = NULL, }; + // pre-allocate a new connection structure as we cannot allocate this inside the connection callback + mp_bt_zephyr_next_conn = m_new0(mp_bt_zephyr_conn_t, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(mp_bt_zephyr_next_conn)); + return bt_err_to_errno(bt_le_adv_start(¶m, bt_ad_data, bt_ad_len, bt_sd_data, bt_sd_len)); } @@ -271,6 +433,8 @@ void mp_bluetooth_gap_advertise_stop(void) { } int mp_bluetooth_gatts_register_service_begin(bool append) { + #if CONFIG_BT_GATT_DYNAMIC_DB + if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -280,25 +444,190 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { return MP_EOPNOTSUPP; } + // Unregister and unref any previous service definitions. + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { + bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; + } + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services = 0; + // Reset the gatt characteristic value db. mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); + mp_bt_zephyr_next_conn = NULL; + + return 0; + #else return MP_EOPNOTSUPP; + #endif } int mp_bluetooth_gatts_register_service_end(void) { - return MP_EOPNOTSUPP; + return 0; } int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { + #if CONFIG_BT_GATT_DYNAMIC_DB + if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services >= MP_BLUETOOTH_ZEPHYR_MAX_SERVICES) { + return MP_E2BIG; + } + + // first of all allocate the entire memory for all the attributes that this service is composed of + // 1 for the service itself, 2 for each characteristic (the declaration and the value), and one for each descriptor + size_t total_descriptors = 0; + for (size_t i = 0; i < num_characteristics; ++i) { + total_descriptors += num_descriptors[i]; + // we have to add the CCC manually + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + total_descriptors += 1; + } + } + size_t total_attributes = 1 + (num_characteristics * 2) + total_descriptors; + + // allocate one extra so that we can know later where the final attribute is + struct bt_gatt_attr *svc_attributes = m_new(struct bt_gatt_attr, total_attributes + 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(svc_attributes)); + + size_t handle_index = 0; + size_t descriptor_index = 0; + size_t attr_index = 0; + // bitfield of the handles we should ignore, should be more than enough for most applications + uint64_t attrs_to_ignore = 0; + uint64_t attrs_are_chrs = 0; + uint64_t chr_has_ccc = 0; + + add_service(create_zephyr_uuid(service_uuid), &svc_attributes[attr_index]); + attr_index += 1; + + for (size_t i = 0; i < num_characteristics; ++i) { + + struct add_characteristic add_char; + add_char.uuid = create_zephyr_uuid(characteristic_uuids[i]); + add_char.permissions = 0; + add_char.properties = 0; + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { + add_char.permissions |= BT_GATT_PERM_READ; + add_char.properties |= BT_GATT_CHRC_READ; + } + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) { + add_char.properties |= BT_GATT_CHRC_NOTIFY; + } + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) { + add_char.properties |= BT_GATT_CHRC_INDICATE; + } + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { + add_char.permissions |= BT_GATT_PERM_WRITE; + add_char.properties |= (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP); + } + + add_characteristic(&add_char, &svc_attributes[attr_index], &svc_attributes[attr_index + 1]); + + struct bt_gatt_attr *curr_char = &svc_attributes[attr_index]; + attrs_are_chrs |= (1 << attr_index); + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + chr_has_ccc |= (1 << attr_index); + } + attr_index += 1; + attrs_to_ignore |= (1 << attr_index); // ignore the value handle + attr_index += 1; + + if (num_descriptors[i] > 0) { + for (size_t j = 0; j < num_descriptors[i]; ++j) { + + struct add_descriptor add_desc; + add_desc.uuid = create_zephyr_uuid(descriptor_uuids[descriptor_index]); + add_desc.permissions = 0; + if (descriptor_flags[descriptor_index] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { + add_desc.permissions |= BT_GATT_PERM_READ; + } + if (descriptor_flags[descriptor_index] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { + add_desc.permissions |= BT_GATT_PERM_WRITE; + } + + add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); + attr_index += 1; + + descriptor_index++; + } + } + + // to support indications and notifications we must add the CCC descriptor manually + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + struct add_descriptor add_desc; + mp_obj_bluetooth_uuid_t ccc_uuid; + ccc_uuid.base.type = &mp_type_bluetooth_uuid; + ccc_uuid.data[0] = BT_UUID_GATT_CCC_VAL & 0xff; + ccc_uuid.data[1] = (BT_UUID_GATT_CCC_VAL >> 8) & 0xff; + ccc_uuid.type = MP_BLUETOOTH_UUID_TYPE_16; + add_desc.uuid = create_zephyr_uuid(&ccc_uuid); + add_desc.permissions = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE; + + attrs_to_ignore |= (1 << attr_index); + + add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); + attr_index += 1; + } + } + + struct bt_gatt_service *service = m_new(struct bt_gatt_service, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(service)); + service->attrs = svc_attributes; + service->attr_count = attr_index; + // invalidate the last attribute uuid pointer so that we new this is the end of attributes for this service + svc_attributes[attr_index].uuid = NULL; + + // Note: advertising must be stopped for gatts registration to work + + int err = bt_gatt_service_register(service); + if (err) { + return bt_err_to_errno(err); + } + + // now that the service has been registered, we can assign the handles for the characteristics and the descriptors + // we are not interested in the handle of the service itself, so we start the loop from index 1 + for (int i = 1; i < total_attributes; i++) { + // store all the relevant handles (characteristics and descriptors defined in Python) + if (!((uint64_t)(attrs_to_ignore >> i) & (uint64_t)0x01)) { + if (svc_attributes[i].user_data == NULL) { + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); + svc_attributes[i].user_data = entry->data; + } else if (((uint64_t)(attrs_are_chrs >> i) & (uint64_t)0x01)) { + if (svc_attributes[i + 1].user_data == NULL) { + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); + svc_attributes[i + 1].user_data = entry->data; + + if (((uint64_t)(chr_has_ccc >> i) & (uint64_t)0x01)) { + // create another database entry for the ccc of this characteristic + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle + 2, 1); + } + } + } + handles[handle_index++] = svc_attributes[i].handle; + } + } + + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services++] = service; + + return 0; + + #else return MP_EOPNOTSUPP; + #endif } int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); + if (connection) { + return bt_conn_disconnect(connection->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + return MP_ENOENT; } int mp_bluetooth_gatts_read(uint16_t value_handle, const uint8_t **value, size_t *value_len) { @@ -312,24 +641,155 @@ int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - if (send_update) { - return MP_EOPNOTSUPP; + + int err = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); + + if ((err == 0) && send_update) { + struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); + mp_bluetooth_gatts_db_entry_t *ccc_entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle + 2); + + if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_NOTIFY)) { + err = bt_gatt_notify(NULL, attr_val, value, value_len); + } else if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_INDICATE)) { + struct bt_gatt_indicate_params params = { + .uuid = NULL, + .attr = attr_val, + .func = mp_bt_zephyr_gatt_indicate_done, + .destroy = NULL, + .data = value, + .len = value_len + }; + err = bt_gatt_indicate(NULL, ¶ms); + } + } + return err; +} + +static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + uint16_t chr_handle = params->attr->handle - 1; + mp_bluetooth_gatts_on_indicate_complete(info.id, chr_handle, err); +} + +static ssize_t mp_bt_zephyr_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { + // we receive the value handle, but to look up in the gatts db we need the characteristic handle, and that is is the value handle minus 1 + uint16_t _handle = attr->handle - 1; + + DEBUG_printf("BLE attr read for handle %d\n", attr->handle); + + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + // it could be a descriptor instead + _handle = attr->handle; + entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + } + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, entry->data, entry->data_len); +} + +static ssize_t mp_bt_zephyr_gatts_attr_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + + DEBUG_printf("BLE attr write for handle %d\n", attr->handle); + + // the characteristic handle is the value handle minus 1 + uint16_t _handle = attr->handle - 1; + + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + // it could be a descriptor instead + _handle = attr->handle; + entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + } + } + + // Don't write anything if prepare flag is set + if (flags & BT_GATT_WRITE_FLAG_PREPARE) { + return 0; } - return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); + + if (offset > entry->data_alloc) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if ((offset + len) > entry->data_alloc) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (entry->append) { + offset = entry->data_len; + } + + // copy the data into the buffer in the gatts database + memcpy(&entry->data[offset], buf, len); + entry->data_len = offset + len; + + mp_bluetooth_gatts_on_write(info.id, _handle); + + return len; +} + +static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle) { + for (int i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; i++) { + int j = 0; + while (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].uuid != NULL) { + if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].handle == value_handle) { + return &MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j]; + } + j++; + } + } + return NULL; } int mp_bluetooth_gatts_notify_indicate(uint16_t conn_handle, uint16_t value_handle, int gatts_op, const uint8_t *value, size_t value_len) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + + int err = MP_ENOENT; + mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); + + if (connection) { + struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); + + if (attr_val) { + switch (gatts_op) { + case MP_BLUETOOTH_GATTS_OP_NOTIFY: { + err = bt_gatt_notify(connection->conn, attr_val, value, value_len); + break; + } + case MP_BLUETOOTH_GATTS_OP_INDICATE: { + struct bt_gatt_indicate_params params = { + .uuid = NULL, + .attr = attr_val, + .func = mp_bt_zephyr_gatt_indicate_done, + .destroy = NULL, + .data = value, + .len = value_len + }; + err = bt_gatt_indicate(connection->conn, ¶ms); + break; + } + } + } + } + + return err; } int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, len, append); } int mp_bluetooth_get_preferred_mtu(void) { @@ -404,6 +864,133 @@ int mp_bluetooth_gap_peripheral_connect_cancel(void) { #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +// Note: modbluetooth UUIDs store their data in LE. +static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid) { + struct bt_uuid *result = (struct bt_uuid *)m_new(union uuid_u, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(result)); + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + bt_uuid_create(result, uuid->data, 2); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) { + bt_uuid_create(result, uuid->data, 4); + } else { // MP_BLUETOOTH_UUID_TYPE_128 + bt_uuid_create(result, uuid->data, 16); + } + return result; +} + +static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len) { + const union uuid_u *u = CONTAINER_OF(pattern->uuid, union uuid_u, uuid); + size_t uuid_size = sizeof(u->u16); + + if (u->uuid.type == BT_UUID_TYPE_32) { + uuid_size = sizeof(u->u32); + } else if (u->uuid.type == BT_UUID_TYPE_128) { + uuid_size = sizeof(u->u128); + } + + memcpy(attr, pattern, sizeof(*attr)); + + // Store the UUID. + attr->uuid = (const struct bt_uuid *)m_new(union uuid_u, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->uuid)); + memcpy((void *)attr->uuid, &u->uuid, uuid_size); + + // Copy user_data to the buffer. + if (user_data_len) { + attr->user_data = m_new(uint8_t, user_data_len); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->user_data)); + memcpy(attr->user_data, pattern->user_data, user_data_len); + } +} + +static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr) { + union uuid_u *uuid = (union uuid_u *)u; + + size_t uuid_size = sizeof(uuid->u16); + + if (uuid->uuid.type == BT_UUID_TYPE_32) { + uuid_size = sizeof(uuid->u32); + } else if (uuid->uuid.type == BT_UUID_TYPE_128) { + uuid_size = sizeof(uuid->u128); + } + + gatt_db_add(&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&uuid->uuid), attr, uuid_size); +} + +static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value) { + struct bt_gatt_chrc *chrc_data; + + // Add Characteristic Declaration + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, + BT_GATT_PERM_READ, + bt_gatt_attr_read_chrc, NULL, + (&(struct bt_gatt_chrc) {})), attr_chrc, sizeof(*chrc_data)); + + // Allow prepare writes + ch->permissions |= BT_GATT_PERM_PREPARE_WRITE; + + // Add Characteristic Value + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_ATTRIBUTE(ch->uuid, + ch->permissions & GATT_PERM_MASK, + mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_value, 0); + + chrc_data = attr_chrc->user_data; + chrc_data->properties = ch->properties; + chrc_data->uuid = attr_value->uuid; +} + +static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, attr->handle); + entry->data[0] = value; +} + +static struct bt_gatt_attr ccc_definition = BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE); + +static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc) { + struct bt_gatt_chrc *chrc = attr->user_data; + + // Check characteristic properties + if (!(chrc->properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) { + mp_raise_OSError(MP_EINVAL); + } + + // Add CCC descriptor to GATT database + gatt_db_add(&ccc_definition, attr_desc, 0); +} + +static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc) { + struct bt_gatt_chrc *chrc = attr_chrc->user_data; + struct bt_gatt_cep cep_value; + + // Extended Properties bit shall be set + if (!(chrc->properties & BT_GATT_CHRC_EXT_PROP)) { + mp_raise_OSError(MP_EINVAL); + } + + cep_value.properties = 0x0000; + + // Add CEP descriptor to GATT database + gatt_db_add(&(struct bt_gatt_attr)BT_GATT_CEP(&cep_value), attr_desc, sizeof(cep_value)); +} + +static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc) { + if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CEP)) { + add_cep(chrc, attr_desc); + } else if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CCC)) { + add_ccc(chrc, attr_desc); + } else { + // Allow prepare writes + d->permissions |= BT_GATT_PERM_PREPARE_WRITE; + + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_DESCRIPTOR(d->uuid, + d->permissions & GATT_PERM_MASK, + mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_desc, 0); + } +} + MP_REGISTER_ROOT_POINTER(struct _mp_bluetooth_zephyr_root_pointers_t *bluetooth_zephyr_root_pointers); #endif // MICROPY_PY_BLUETOOTH From 45e4deb96daf23e648d1cb52100d12da275cdfe8 Mon Sep 17 00:00:00 2001 From: Anton Blanchard Date: Thu, 31 Oct 2024 15:41:28 +1100 Subject: [PATCH 0618/2098] zephyr/mpconfigport: Fix mp_int_t and mp_uint_t to work on 64-bit archs. These both need to fit a pointer, so make them `intptr_t` and `uintptr_t`, similar to other ports. Signed-off-by: Anton Blanchard --- ports/zephyr/mpconfigport.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index e015776a4eb..3a6e9348622 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -139,8 +139,8 @@ void mp_hal_signal_event(void); #define MICROPY_HW_MCU_NAME "unknown-cpu" #endif -typedef int mp_int_t; // must be pointer size -typedef unsigned mp_uint_t; // must be pointer size +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM From b2cda6c6046132738fdf7d354b811f216c91bf52 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 1 Apr 2025 16:38:47 +1100 Subject: [PATCH 0619/2098] extmod,alif,mimxrt,rp2,stm32: Create common cyw43 driver config header. This is only a surface level refactor, some deeper refactoring would be possible with (for example) the SDIO interface in mimxrt and stm32, or the BTHCI interface which is is similar on supported ports. But sticking to cases where the macros are the same across all ports. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/cyw43_config_common.h | 110 ++++++++++++++++++++++++++++++++ extmod/mpbthci.h | 4 ++ ports/alif/cyw43_configport.h | 54 +--------------- ports/mimxrt/cyw43_configport.h | 50 +-------------- ports/rp2/cyw43_configport.h | 94 +++++---------------------- ports/stm32/cyw43_configport.h | 67 +------------------ 6 files changed, 135 insertions(+), 244 deletions(-) create mode 100644 extmod/cyw43_config_common.h diff --git a/extmod/cyw43_config_common.h b/extmod/cyw43_config_common.h new file mode 100644 index 00000000000..091b6d3e650 --- /dev/null +++ b/extmod/cyw43_config_common.h @@ -0,0 +1,110 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Damien P. George, 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. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H +#define MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H + +// The board-level config will be included here, so it can set some CYW43 values. +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/modnetwork.h" +#include "lwip/apps/mdns.h" +#include "pendsv.h" + +// This file is included at the top of port-specific cyw43_configport.h files, +// and holds the MicroPython-specific but non-port-specific parts. +// +// It's included into both MicroPython sources and the lib/cyw43-driver sources. + +#define CYW43_IOCTL_TIMEOUT_US (1000000) +#define CYW43_NETUTILS (1) +#define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) + +#define CYW43_EPERM MP_EPERM // Operation not permitted +#define CYW43_EIO MP_EIO // I/O error +#define CYW43_EINVAL MP_EINVAL // Invalid argument +#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out + +#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER +#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT +#define CYW43_THREAD_LOCK_CHECK + +#define CYW43_HOST_NAME mod_network_hostname_data + +#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) + +#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT +#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT +#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE +#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP +#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN + +#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR + +#define cyw43_hal_ticks_us mp_hal_ticks_us +#define cyw43_hal_ticks_ms mp_hal_ticks_ms + +#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t +#define cyw43_hal_pin_config mp_hal_pin_config +#define cyw43_hal_pin_read mp_hal_pin_read +#define cyw43_hal_pin_low mp_hal_pin_low +#define cyw43_hal_pin_high mp_hal_pin_high + +#define cyw43_hal_get_mac mp_hal_get_mac +#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii +#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac + +#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) + +// Note: this function is only called if CYW43_POST_POLL_HOOK is defined in cyw43_configport.h +void cyw43_post_poll_hook(void); + +#ifdef MICROPY_EVENT_POLL_HOOK +// Older style hook macros on some ports +#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK +#else +// Newer style hooks on other ports +#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() +#endif + +static inline void cyw43_delay_us(uint32_t us) { + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + } +} + +static inline void cyw43_delay_ms(uint32_t ms) { + // PendSV may be disabled via CYW43_THREAD_ENTER, so this delay is a busy loop. + uint32_t us = ms * 1000; + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + CYW43_EVENT_POLL_HOOK; + } +} + +#endif // MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H diff --git a/extmod/mpbthci.h b/extmod/mpbthci.h index c10f99c3dfd..3ff8294c4c6 100644 --- a/extmod/mpbthci.h +++ b/extmod/mpbthci.h @@ -27,6 +27,10 @@ #ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H #define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#include +#include +#include + #define MICROPY_PY_BLUETOOTH_HCI_READ_MODE_BYTE (0) #define MICROPY_PY_BLUETOOTH_HCI_READ_MODE_PACKET (1) diff --git a/ports/alif/cyw43_configport.h b/ports/alif/cyw43_configport.h index 4b5cce67132..bfd4364ab90 100644 --- a/ports/alif/cyw43_configport.h +++ b/ports/alif/cyw43_configport.h @@ -27,13 +27,8 @@ #define MICROPY_INCLUDED_ALIF_CYW43_CONFIGPORT_H // The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/runtime.h" -#include "extmod/modnetwork.h" #include "extmod/mpbthci.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #ifndef static_assert #define static_assert(expr, msg) typedef int static_assert_##__LINE__[(expr) ? 1 : -1] @@ -53,51 +48,15 @@ #define CYW43_WARN(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFE() #define CYW43_DO_IOCTL_WAIT // __WFE() -#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() - -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE 0 - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define CYW43_PIN_WL_REG_ON pin_WL_REG_ON #define CYW43_PIN_WL_IRQ pin_WL_IRQ @@ -110,15 +69,7 @@ void cyw43_post_poll_hook(void); -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - mp_hal_delay_ms(ms); -} +#undef cyw43_hal_pin_config // mp_hal_pin_config on alif port is not API compatible static inline void cyw43_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t alt) { if (mode == MP_HAL_PIN_MODE_INPUT) { @@ -127,7 +78,6 @@ static inline void cyw43_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uin mp_hal_pin_output(pin); } } - static inline void cyw43_hal_pin_config_irq_falling(mp_hal_pin_obj_t pin, bool enable) { mp_hal_pin_config_irq_falling(pin, enable); } diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h index cf2f5d4b9fb..b1dd6fbe6ae 100644 --- a/ports/mimxrt/cyw43_configport.h +++ b/ports/mimxrt/cyw43_configport.h @@ -28,12 +28,8 @@ #define MICROPY_INCLUDED_MIMXRT_CYW43_CONFIGPORT_H // The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "extmod/modnetwork.h" #include "extmod/mpbthci.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #include "sdio.h" #define CYW43_USE_SPI (0) @@ -61,55 +57,15 @@ #define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar -#define cyw43_delay_us mp_hal_delay_us -#define cyw43_delay_ms mp_hal_delay_ms - #define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init #define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit #define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken @@ -135,7 +91,7 @@ #define CYW43_PIN_RFSW_VDD pin_WL_RFSW_VDD #endif -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) +#undef cyw43_hal_pin_config // mp_hal_pin_config not yet implemented on this port static inline void cyw43_hal_pin_config(cyw43_hal_pin_obj_t pin, uint8_t mode, uint8_t pull, uint8_t alt) { machine_pin_set_mode(pin, mode); @@ -175,6 +131,4 @@ static inline int cyw43_sdio_transfer_cmd53(bool write, uint32_t block_size, uin return sdio_transfer_cmd53(write, block_size, arg, len, buf); } -#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK - #endif // MICROPY_INCLUDED_MIMXRT_CYW43_CONFIGPORT_H diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 55233057423..ef35cfafc24 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -26,34 +26,27 @@ #ifndef MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H #define MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H -// The board-level config will be included here, so it can set some CYW43 values. -#include -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/runtime.h" -#include "extmod/modnetwork.h" -#include "lwip/apps/mdns.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (1) #define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (10) -#define CYW43_NETUTILS (1) +#define CYW43_SLEEP_MAX (10) // Unclear why rp2 port overrides the default here #define CYW43_USE_OTP_MAC (1) -#define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out +static inline bool cyw43_poll_is_pending(void) { + return pendsv_is_pending(PENDSV_DISPATCH_CYW43); +} -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK +static inline void cyw43_yield(void) { + if (!cyw43_poll_is_pending()) { + best_effort_wfe_or_timeout(make_timeout_time_ms(1)); + } +} -#define CYW43_HOST_NAME mod_network_hostname_data +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); + +// set in SDK board header +#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT #if CYW43_PIN_WL_DYNAMIC @@ -100,36 +93,6 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); cyw43_yield(); \ } \ -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 - -// set in SDK board header -#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT - -#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_config mp_hal_pin_config -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) - // Bluetooth requires dynamic memory allocation to load its firmware (the allocation // call is made from pico-sdk). This allocation is always done at thread-level, not // from an IRQ, so is safe to delegate to the MicroPython GC heap. @@ -140,34 +103,6 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #define cyw43_free m_tracked_free #endif -void cyw43_post_poll_hook(void); -static inline bool cyw43_poll_is_pending(void) { - return pendsv_is_pending(PENDSV_DISPATCH_CYW43); -} - -static inline void cyw43_yield(void) { - if (!cyw43_poll_is_pending()) { - best_effort_wfe_or_timeout(make_timeout_time_ms(1)); - } -} - -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - // PendSV may be disabled via CYW43_THREAD_ENTER, so this delay is a busy loop. - uint32_t us = ms * 1000; - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - mp_event_handle_nowait(); - } -} - -#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() - #if LWIP_MDNS_RESPONDER == 1 // Hook for any additional TCP/IP initialization than needs to be done. @@ -183,4 +118,5 @@ static inline void cyw43_delay_ms(uint32_t ms) { #endif #endif + #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H diff --git a/ports/stm32/cyw43_configport.h b/ports/stm32/cyw43_configport.h index c26c5547616..6528dbc625e 100644 --- a/ports/stm32/cyw43_configport.h +++ b/ports/stm32/cyw43_configport.h @@ -27,14 +27,9 @@ #ifndef MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H #define MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H -// The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "extmod/modnetwork.h" -#include "extmod/mpbthci.h" #include "extint.h" -#include "pendsv.h" +#include "extmod/mpbthci.h" +#include "extmod/cyw43_config_common.h" #include "sdio.h" #define CYW43_USE_SPI (0) @@ -62,50 +57,12 @@ #define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); #define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT __WFI() -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_config mp_hal_pin_config -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar @@ -132,24 +89,6 @@ #define CYW43_PIN_RFSW_SELECT pyb_pin_WL_GPIO_1 #endif -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) - -void cyw43_post_poll_hook(void); - -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - uint32_t us = ms * 1000; - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - MICROPY_EVENT_POLL_HOOK; - } -} - static inline void cyw43_hal_pin_config_irq_falling(cyw43_hal_pin_obj_t pin, int enable) { if (enable) { extint_set(pin, GPIO_MODE_IT_FALLING); @@ -184,6 +123,4 @@ static inline int cyw43_sdio_transfer_cmd53(bool write, uint32_t block_size, uin return sdio_transfer_cmd53(write, block_size, arg, len, buf); } -#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK - #endif // MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H From d00eab4a3056ebaf47a1d2fdf8c21d132c465055 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 8 Apr 2025 10:23:11 +1000 Subject: [PATCH 0620/2098] rp2,extmod/cyw43: Move the LWIP responder fix into common CYW43 config. This means the fix from dd1465e7 will also apply to stm32 and mimxrt ports that use CYW43. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/cyw43_config_common.h | 16 ++++++++++++++++ ports/rp2/cyw43_configport.h | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extmod/cyw43_config_common.h b/extmod/cyw43_config_common.h index 091b6d3e650..595af37d713 100644 --- a/extmod/cyw43_config_common.h +++ b/extmod/cyw43_config_common.h @@ -107,4 +107,20 @@ static inline void cyw43_delay_ms(uint32_t ms) { } } +#if LWIP_MDNS_RESPONDER == 1 + +// Hook for any additional TCP/IP initialization than needs to be done. +// Called after the netif specified by `itf` has been set up. +#ifndef CYW43_CB_TCPIP_INIT_EXTRA +#define CYW43_CB_TCPIP_INIT_EXTRA(self, itf) mdns_resp_add_netif(&self->netif[itf], mod_network_hostname_data) +#endif + +// Hook for any additional TCP/IP deinitialization than needs to be done. +// Called before the netif specified by `itf` is removed. +#ifndef CYW43_CB_TCPIP_DEINIT_EXTRA +#define CYW43_CB_TCPIP_DEINIT_EXTRA(self, itf) mdns_resp_remove_netif(&self->netif[itf]) +#endif + +#endif + #endif // MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index ef35cfafc24..2da0b1f8a49 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -103,20 +103,4 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #define cyw43_free m_tracked_free #endif -#if LWIP_MDNS_RESPONDER == 1 - -// Hook for any additional TCP/IP initialization than needs to be done. -// Called after the netif specified by `itf` has been set up. -#ifndef CYW43_CB_TCPIP_INIT_EXTRA -#define CYW43_CB_TCPIP_INIT_EXTRA(self, itf) mdns_resp_add_netif(&self->netif[itf], mod_network_hostname_data) -#endif - -// Hook for any additional TCP/IP deinitialization than needs to be done. -// Called before the netif specified by `itf` is removed. -#ifndef CYW43_CB_TCPIP_DEINIT_EXTRA -#define CYW43_CB_TCPIP_DEINIT_EXTRA(self, itf) mdns_resp_remove_netif(&self->netif[itf]) -#endif - -#endif - #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H From 928466d74c10c09570e63030419804889f8e0e98 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 12:25:05 +1000 Subject: [PATCH 0621/2098] rp2/Makefile: Add deploy target that uses picotool load. This is a convenient way to deploy firmware to an RP2xxx-based board. Signed-off-by: Damien George --- ports/rp2/Makefile | 3 +++ ports/rp2/README.md | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 76b5698d2c8..2895faaca61 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -67,6 +67,9 @@ all: clean: $(RM) -rf $(BUILD) +deploy: all + $(Q)picotool load -x $(BUILD)/firmware.elf + # First ensure that pico-sdk is initialised, then run CMake with the # UPDATE_SUBMODULES flag to update necessary submodules for this board. # diff --git a/ports/rp2/README.md b/ports/rp2/README.md index 911d797fe01..41fca97f95e 100644 --- a/ports/rp2/README.md +++ b/ports/rp2/README.md @@ -47,8 +47,10 @@ pass the board name to the build; e.g. for Raspberry Pi Pico W: ## Deploying firmware to the device Firmware can be deployed to the device by putting it into bootloader mode -(hold down BOOTSEL while powering on or resetting) and then copying -`firmware.uf2` to the USB mass storage device that appears. +(hold down BOOTSEL while powering on or resetting) and then either copying +`firmware.uf2` to the USB mass storage device that appears, or using +`picotool load -x firmware.elf`. The latter command can be accessed +conveniently via `make deploy`. If MicroPython is already installed then the bootloader can be entered by executing `import machine; machine.bootloader()` at the REPL. From 9c8c219f8af4128822dc525cc7d934533bd4675a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 12:51:51 +1000 Subject: [PATCH 0622/2098] rp2/rp2_dma: Fix default value used in pack_ctrl on RP2350. The bit position of CHAIN_TO is not the same as on RP2040. Signed-off-by: Damien George --- ports/rp2/rp2_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 78f69e64527..94c61e226e6 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -315,7 +315,7 @@ static mp_obj_t rp2_dma_pack_ctrl(size_t n_pos_args, const mp_obj_t *pos_args, m // Pack keyword settings into a control register value, using either the default for this // DMA channel or the provided defaults rp2_dma_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - mp_uint_t value = DEFAULT_DMA_CONFIG | ((self->channel & 0xf) << 11); + mp_uint_t value = DEFAULT_DMA_CONFIG | ((self->channel & 0xf) << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); if (n_pos_args > 1) { mp_raise_TypeError(MP_ERROR_TEXT("pack_ctrl only takes keyword arguments")); From d01a981a9b924ccb8b25b8d13621c3bba3d04bb4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:49:12 +1000 Subject: [PATCH 0623/2098] py/mpconfig: Enable io.IOBase at core feature level. IOBase is quite an important building block of other parts of the system, such as `mpremote mount` and running .mpy and native tests. This feature costs +244 bytes of firmware size on ARM Thumb2 architectures, which is worth the cost for the extra features it enables. The change here means that `io.IOBase` is now enabled on all nrf boards, (previously it was only nRF52840 and nRF9160) and also B_L072Z_LRWAN1 (there is no change to other ports or boards). Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index d06932a77b7..9001b8983bf 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1469,7 +1469,7 @@ typedef double mp_float_t; // Whether to provide "io.IOBase" class to support user streams #ifndef MICROPY_PY_IO_IOBASE -#define MICROPY_PY_IO_IOBASE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#define MICROPY_PY_IO_IOBASE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "io.BytesIO" class From 3fa77bdc7d413a410fb85a8943d417bca3b562e7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 14:01:48 +1000 Subject: [PATCH 0624/2098] rp2: Add temporary workaround for GCC 15.1 build failure. This is a workaround for this upstream issue: https://github.com/raspberrypi/pico-sdk/issues/2448 Can be removed after the next pico-sdk update. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/CMakeLists.txt | 3 ++ ports/rp2/tools_patch/Findpioasm.cmake | 58 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 ports/rp2/tools_patch/Findpioasm.cmake diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 53d00c7dfda..7cb0c60aa21 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -84,6 +84,9 @@ endif() list(APPEND GIT_SUBMODULES lib/mbedtls) list(APPEND GIT_SUBMODULES lib/tinyusb) +# Workaround for pico-sdk host toolchain issue, see directory for details +list(APPEND CMAKE_MODULE_PATH "${MICROPY_PORT_DIR}/tools_patch") + # Include component cmake fragments include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) diff --git a/ports/rp2/tools_patch/Findpioasm.cmake b/ports/rp2/tools_patch/Findpioasm.cmake new file mode 100644 index 00000000000..72e6bf6fb61 --- /dev/null +++ b/ports/rp2/tools_patch/Findpioasm.cmake @@ -0,0 +1,58 @@ +# Finds (or builds) the pioasm executable +# +# This will define the following imported targets +# +# pioasm +# + +# This is a temporary patched copy of pico-sdk file Findpioasm.cmake to work around +# a host toolchain issue with GCC 15.1: +# https://github.com/raspberrypi/pico-sdk/issues/2448 + +if (NOT TARGET pioasm) + # todo we would like to use pckgconfig to look for it first + # see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ + + include(ExternalProject) + + set(PIOASM_SOURCE_DIR ${PICO_SDK_PATH}/tools/pioasm) + set(PIOASM_BINARY_DIR ${CMAKE_BINARY_DIR}/pioasm) + set(PIOASM_INSTALL_DIR ${CMAKE_BINARY_DIR}/pioasm-install CACHE PATH "Directory where pioasm has been installed" FORCE) + + set(pioasmBuild_TARGET pioasmBuild) + set(pioasm_TARGET pioasm) + + if (NOT TARGET ${pioasmBuild_TARGET}) + pico_message_debug("PIOASM will need to be built") +# message("Adding external project ${pioasmBuild_Target} in ${CMAKE_CURRENT_LIST_DIR}}") + ExternalProject_Add(${pioasmBuild_TARGET} + PREFIX pioasm + SOURCE_DIR ${PIOASM_SOURCE_DIR} + BINARY_DIR ${PIOASM_BINARY_DIR} + INSTALL_DIR ${PIOASM_INSTALL_DIR} + CMAKE_ARGS + "--no-warn-unused-cli" + "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}" + "-DPIOASM_FLAT_INSTALL=1" + "-DCMAKE_INSTALL_PREFIX=${PIOASM_INSTALL_DIR}" + "-DCMAKE_RULE_MESSAGES=OFF" # quieten the build + "-DCMAKE_INSTALL_MESSAGE=NEVER" # quieten the install + # Toolchain workaround follows + "-DCMAKE_CXX_FLAGS=-include cstdint" + CMAKE_CACHE_ARGS "-DPIOASM_EXTRA_SOURCE_FILES:STRING=${PIOASM_EXTRA_SOURCE_FILES}" + BUILD_ALWAYS 1 # force dependency checking + EXCLUDE_FROM_ALL TRUE + ) + endif() + + if (CMAKE_HOST_WIN32) + set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm.exe) + else() + set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm) + endif() + add_executable(${pioasm_TARGET} IMPORTED GLOBAL) + set_property(TARGET ${pioasm_TARGET} PROPERTY IMPORTED_LOCATION + ${pioasm_EXECUTABLE}) + + add_dependencies(${pioasm_TARGET} ${pioasmBuild_TARGET}) +endif() From 7d5aba0523cac0a23dc63a5bb4b64a8eab845836 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 13:34:37 +1000 Subject: [PATCH 0625/2098] extmod/moductypes: Refactor string literal as array initializer. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. Also split out the duplicate string to a top-level array (probably the duplicate string literal was interned, so unlikely to have any impact.) This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/moductypes.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index bf45797658f..54abce79e06 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -277,15 +277,18 @@ static mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uctypes_struct_sizeof_obj, 1, 2, uctypes_struct_sizeof); +static const char type2char[16] = { + 'B', 'b', 'H', 'h', 'I', 'i', 'Q', 'q', + '-', '-', '-', '-', '-', '-', 'f', 'd' +}; + static inline mp_obj_t get_unaligned(uint val_type, byte *p, int big_endian) { char struct_type = big_endian ? '>' : '<'; - static const char type2char[16] = "BbHhIiQq------fd"; return mp_binary_get_val(struct_type, type2char[val_type], p, &p); } static inline void set_unaligned(uint val_type, byte *p, int big_endian, mp_obj_t val) { char struct_type = big_endian ? '>' : '<'; - static const char type2char[16] = "BbHhIiQq------fd"; mp_binary_set_val(struct_type, type2char[val_type], val, p, &p); } From 9f8600588584cb58fcc5593f18b090622a406565 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 13:36:05 +1000 Subject: [PATCH 0626/2098] py/emitinlinethumb: Refactor string literal as array initializer. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/emitinlinethumb.c | 108 +++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 7818bb4f46d..d6596337ae5 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -150,27 +150,27 @@ typedef struct _reg_name_t { byte reg; byte name[3]; } reg_name_t; static const reg_name_t reg_name_table[] = { - {0, "r0\0"}, - {1, "r1\0"}, - {2, "r2\0"}, - {3, "r3\0"}, - {4, "r4\0"}, - {5, "r5\0"}, - {6, "r6\0"}, - {7, "r7\0"}, - {8, "r8\0"}, - {9, "r9\0"}, - {10, "r10"}, - {11, "r11"}, - {12, "r12"}, - {13, "r13"}, - {14, "r14"}, - {15, "r15"}, - {10, "sl\0"}, - {11, "fp\0"}, - {13, "sp\0"}, - {14, "lr\0"}, - {15, "pc\0"}, + {0, {'r', '0' }}, + {1, {'r', '1' }}, + {2, {'r', '2' }}, + {3, {'r', '3' }}, + {4, {'r', '4' }}, + {5, {'r', '5' }}, + {6, {'r', '6' }}, + {7, {'r', '7' }}, + {8, {'r', '8' }}, + {9, {'r', '9' }}, + {10, {'r', '1', '0' }}, + {11, {'r', '1', '1' }}, + {12, {'r', '1', '2' }}, + {13, {'r', '1', '3' }}, + {14, {'r', '1', '4' }}, + {15, {'r', '1', '5' }}, + {10, {'s', 'l' }}, + {11, {'f', 'p' }}, + {13, {'s', 'p' }}, + {14, {'l', 'r' }}, + {15, {'p', 'c' }}, }; #define MAX_SPECIAL_REGISTER_NAME_LENGTH 7 @@ -368,20 +368,20 @@ typedef struct _cc_name_t { byte cc; byte name[2]; } cc_name_t; static const cc_name_t cc_name_table[] = { - { ASM_THUMB_CC_EQ, "eq" }, - { ASM_THUMB_CC_NE, "ne" }, - { ASM_THUMB_CC_CS, "cs" }, - { ASM_THUMB_CC_CC, "cc" }, - { ASM_THUMB_CC_MI, "mi" }, - { ASM_THUMB_CC_PL, "pl" }, - { ASM_THUMB_CC_VS, "vs" }, - { ASM_THUMB_CC_VC, "vc" }, - { ASM_THUMB_CC_HI, "hi" }, - { ASM_THUMB_CC_LS, "ls" }, - { ASM_THUMB_CC_GE, "ge" }, - { ASM_THUMB_CC_LT, "lt" }, - { ASM_THUMB_CC_GT, "gt" }, - { ASM_THUMB_CC_LE, "le" }, + { ASM_THUMB_CC_EQ, { 'e', 'q' }}, + { ASM_THUMB_CC_NE, { 'n', 'e' }}, + { ASM_THUMB_CC_CS, { 'c', 's' }}, + { ASM_THUMB_CC_CC, { 'c', 'c' }}, + { ASM_THUMB_CC_MI, { 'm', 'i' }}, + { ASM_THUMB_CC_PL, { 'p', 'l' }}, + { ASM_THUMB_CC_VS, { 'v', 's' }}, + { ASM_THUMB_CC_VC, { 'v', 'c' }}, + { ASM_THUMB_CC_HI, { 'h', 'i' }}, + { ASM_THUMB_CC_LS, { 'l', 's' }}, + { ASM_THUMB_CC_GE, { 'g', 'e' }}, + { ASM_THUMB_CC_LT, { 'l', 't' }}, + { ASM_THUMB_CC_GT, { 'g', 't' }}, + { ASM_THUMB_CC_LE, { 'l', 'e' }}, }; typedef struct _format_4_op_t { byte op; @@ -389,21 +389,21 @@ typedef struct _format_4_op_t { byte op; } format_4_op_t; #define X(x) (((x) >> 4) & 0xff) // only need 1 byte to distinguish these ops static const format_4_op_t format_4_op_table[] = { - { X(ASM_THUMB_FORMAT_4_EOR), "eor" }, - { X(ASM_THUMB_FORMAT_4_LSL), "lsl" }, - { X(ASM_THUMB_FORMAT_4_LSR), "lsr" }, - { X(ASM_THUMB_FORMAT_4_ASR), "asr" }, - { X(ASM_THUMB_FORMAT_4_ADC), "adc" }, - { X(ASM_THUMB_FORMAT_4_SBC), "sbc" }, - { X(ASM_THUMB_FORMAT_4_ROR), "ror" }, - { X(ASM_THUMB_FORMAT_4_TST), "tst" }, - { X(ASM_THUMB_FORMAT_4_NEG), "neg" }, - { X(ASM_THUMB_FORMAT_4_CMP), "cmp" }, - { X(ASM_THUMB_FORMAT_4_CMN), "cmn" }, - { X(ASM_THUMB_FORMAT_4_ORR), "orr" }, - { X(ASM_THUMB_FORMAT_4_MUL), "mul" }, - { X(ASM_THUMB_FORMAT_4_BIC), "bic" }, - { X(ASM_THUMB_FORMAT_4_MVN), "mvn" }, + { X(ASM_THUMB_FORMAT_4_EOR), {'e', 'o', 'r' }}, + { X(ASM_THUMB_FORMAT_4_LSL), {'l', 's', 'l' }}, + { X(ASM_THUMB_FORMAT_4_LSR), {'l', 's', 'r' }}, + { X(ASM_THUMB_FORMAT_4_ASR), {'a', 's', 'r' }}, + { X(ASM_THUMB_FORMAT_4_ADC), {'a', 'd', 'c' }}, + { X(ASM_THUMB_FORMAT_4_SBC), {'s', 'b', 'c' }}, + { X(ASM_THUMB_FORMAT_4_ROR), {'r', 'o', 'r' }}, + { X(ASM_THUMB_FORMAT_4_TST), {'t', 's', 't' }}, + { X(ASM_THUMB_FORMAT_4_NEG), {'n', 'e', 'g' }}, + { X(ASM_THUMB_FORMAT_4_CMP), {'c', 'm', 'p' }}, + { X(ASM_THUMB_FORMAT_4_CMN), {'c', 'm', 'n' }}, + { X(ASM_THUMB_FORMAT_4_ORR), {'o', 'r', 'r' }}, + { X(ASM_THUMB_FORMAT_4_MUL), {'m', 'u', 'l' }}, + { X(ASM_THUMB_FORMAT_4_BIC), {'b', 'i', 'c' }}, + { X(ASM_THUMB_FORMAT_4_MVN), {'m', 'v', 'n' }}, }; #undef X @@ -428,10 +428,10 @@ typedef struct _format_vfp_op_t { char name[3]; } format_vfp_op_t; static const format_vfp_op_t format_vfp_op_table[] = { - { 0x30, "add" }, - { 0x34, "sub" }, - { 0x20, "mul" }, - { 0x80, "div" }, + { 0x30, {'a', 'd', 'd' }}, + { 0x34, {'s', 'u', 'b' }}, + { 0x20, {'m', 'u', 'l' }}, + { 0x80, {'d', 'i', 'v' }}, }; // shorthand alias for whether we allow ARMv7-M instructions From ae6062a45a776e193e8c02ece62d058b64f2b4f5 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 14:34:09 +1000 Subject: [PATCH 0627/2098] lib/littlefs: Fix string initializer in lfs1.c. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. It would be preferable to just disable this warning, but Clang -Wunknown-warning-option kicks in even when disabling warnings so this becomes fiddly to apply. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- lib/littlefs/lfs1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/littlefs/lfs1.c b/lib/littlefs/lfs1.c index 6a3fd670012..ec18dc47025 100644 --- a/lib/littlefs/lfs1.c +++ b/lib/littlefs/lfs1.c @@ -2141,7 +2141,7 @@ int lfs1_format(lfs1_t *lfs1, const struct lfs1_config *cfg) { .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, .d.nlen = sizeof(superblock.d.magic), .d.version = LFS1_DISK_VERSION, - .d.magic = {"littlefs"}, + .d.magic = {'l', 'i', 't', 't', 'l', 'e', 'f', 's'}, .d.block_size = lfs1->cfg->block_size, .d.block_count = lfs1->cfg->block_count, .d.root = {lfs1->root[0], lfs1->root[1]}, From f7c53cd221a4d8b1a7b3098ab71db06d00da51ad Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 1 May 2025 12:39:29 +1000 Subject: [PATCH 0628/2098] tests/net_inet: Update micropython.org certificate for SSL tests. The Let's Encrypt root certificate has changed so needs updating in these tests. Also use `bytes.fromhex()` instead of `binascii.unhexlify()`, to eliminate the need for the `binascii` module. Both of these features are controlled by `MICROPY_PY_BUILTINS_BYTES_HEX`, so the test will still work on the same targets that it previously did. Signed-off-by: Damien George --- tests/net_inet/mpycert.der | Bin 1290 -> 1289 bytes tests/net_inet/ssl_cert.py | 79 +++++++++++------------ tests/net_inet/test_sslcontext_client.py | 10 ++- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/tests/net_inet/mpycert.der b/tests/net_inet/mpycert.der index 0b0eabc9bc8135de25785cb8574ab78e03947aa9..ac22dcf9e8b888a98c10bd12f2108feb696b68c8 100644 GIT binary patch delta 849 zcmV-X1Frmv3W*9IFoFdIFoFW@paTK{0s;_As8W;nnlGYr21}>}jdjqGAhZE6k<}r8 z&sXK<#8eXPv`7BQCs=kyP$-NYK8W81oz=A%DZSmbTEF`C&ZEAs03l0aT3S2-FU42` z_KE+F5ku?2wcr#7mXB{A*XO!{ISF-Tx5BR&TQ?>s=cDuoD{UJ8t6031u0a3k%U8s; zTYq>B7-75@=TyeKNs-NN7}0=*?xtmbHoSD|yiiQ){2}u=7L$~^u+wWe2F(@{ddgCD z$gOs=!kk^0MO<%Td&Vv9JQGre0ohmPI2_-NwI7m^n&(O!%tfejRq(LpqX+E$ryFLT zKS+ylgmGBOI{NQk@>@hJJap%evPZ!OYR#Ir4VL#3fhJK@3hZI__x0lPipZ4$4ULob z0a+Bgyu(MO5xQ~qk&w{ z!(IKopm5c(mM9_%(()+J4CmD5LyROM9l=79(Fw1gOZ3QaPaqbtTHOKmANU_(YZNE2 za*boI?&8i%&o|w>qtwmktx&DKzvJvaVm5G@F{kK_-RffE60Y@})IBT=s?eNyzUn=H zTtse`F=JYN)_zYUpi2dRUT@OW>R&TU6V_L@%HC19U84ztbp6M8p0l~E7TQWHmczD- zVvqTB8KRy8-sx|fun*FpTK0up!UVU-)lM?RzXV;pDfah)j|^QrQ^`P(i~F_+Wx1pw z)d}SjgoHPO&JZa4cEScFdPY)B+Ea3Wq$fXOaIC&*NC5k158GRz=+^@wE!WBRnL;^( zL*I7_5(x1n*1D7#R2Yb*#g5?`nUi^I>>T+qK(Nd*0N-9e zOJBmbMm1772Ee_ltWv=uphTCM_Y9yRr=e_dM-~?WXu!&*CS?SCv#!bKlvToiZ<{zB buW(=WzWV|ozh)x2WvF=ksq`HF_F|E zf4YafTpIv6%gPGP)MpU+0X(HnrD4k^%2N9k0k^p*^(Y?gC$l6NIcEtB2<+fGuse4S zz4PTcR6z_*u0htBbly?h?|c7Py{NYn_TDko@^Z;?Y72vOm7GQZWE7e6vDqTo&8U*9 z942}{{_j`?dkB?*+i?$$`{OYQy9vHfe@$PHk#;>=ot}g8>BMHU;Ra?yNEm=+pf?zr zn}jl#v9i6y_!R)1Cy4vvV;7cenxdX~KE%NnGG0P~pV9p#`jaKy3iD!a+Z$ zShz36T$`o437+x(>#&76{iq7>MZ|dPWh%nYUp(I9TumGI3nwe83qp5OPkCw`0*Bd? z^#NHF#m`2h>h#0FdTf=%EwEmaFDB!;lVSp=e@^0wT?zvnkT}rwANg@y_`{E~H;8&~ zuq7YuzPH$h29D_ogw1llf%E*z&r?Y4z4mEB)TI$VHOLdPk{tzK>MqpS_({X9^_iBO z?8TQTRbT9 z47O}D2u;^G?MN*y1!%5-UGGfX*dRe)Xz{yR&EpQR3^9kBL*7Y4;m8C((L5W6MKEco zsd5f^5*#tuJ|kD%r%#~p9D&hyA8*#uxu~-P7J)fSpH;5XYUH!O>ZR46OQXrwe>-yX zO7>K%4E#9vV1VClHOh6NROBzbZWqZKr8l*rn!4=tz;#AXcc|!DR*_~cZr&+@)~1&Q zUSvhSEY9j=LPW5`Pp$B@+yly)n+bxIf(>JeX89R%_|>_I5aNlL2{rkm9KMosBXsGV cAJ;odnuJ&o!t/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER +# Then convert to hex format using: for i in range(0,len(data),40):print(data[i:i+40].hex()) -ca_cert_chain = binascii.unhexlify( - b"30820506308202eea0030201020211008a7d3e13d62f30ef2386bd29076b34f8300d06092a864886" - b"f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65" - b"742053656375726974792052657365617263682047726f7570311530130603550403130c49535247" - b"20526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a" - b"3033310b300906035504061302555331163014060355040a130d4c6574277320456e637279707431" - b"0c300a0603550403130352313130820122300d06092a864886f70d01010105000382010f00308201" - b"0a0282010100ba87bc5c1b0039cbca0acdd46710f9013ca54ea561cb26ca52fb1501b7b928f5281e" - b"ed27b324183967090c08ece03ab03b770ebdf3e53954410c4eae41d69974de51dbef7bff58bda8b7" - b"13f6de31d5f272c9726a0b8374959c4600641499f3b1d922d9cda892aa1c267a3ffeef58057b0895" - b"81db710f8efbe33109bb09be504d5f8f91763d5a9d9e83f2e9c466b3e106664348188065a037189a" - b"9b843297b1b2bdc4f815009d2788fbe26317966c9b27674bc4db285e69c279f0495ce02450e1c4bc" - b"a105ac7b406d00b4c2413fa758b82fc55c9ba5bb099ef1feebb08539fda80aef45c478eb652ac2cf" - b"5f3cdee35c4d1bf70b272baa0b4277534f796a1d87d90203010001a381f83081f5300e0603551d0f" - b"0101ff040403020186301d0603551d250416301406082b0601050507030206082b06010505070301" - b"30120603551d130101ff040830060101ff020100301d0603551d0e04160414c5cf46a4eaf4c3c07a" - b"6c95c42db05e922f26e3b9301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" - b"f6e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f" - b"2f78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c010201302706" - b"03551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d" - b"06092a864886f70d01010b050003820201004ee2895d0a031c9038d0f51ff9715cf8c38fb237887a" - b"6fb0251fedbeb7d886068ee90984cd72bf81f3fccacf5348edbdf66942d4a5113e35c813b2921d05" - b"5fea2ed4d8f849c3adf599969cef26d8e1b4240b48204dfcd354b4a9c621c8e1361bff77642917b9" - b"f04bef5deacd79d0bf90bfbe23b290da4aa9483174a9440be1e2f62d8371a4757bd294c10519461c" - b"b98ff3c47448252a0de5f5db43e2db939bb919b41f2fdf6a0e8f31d3630fbb29dcdd662c3fb01b67" - b"51f8413ce44db9acb8a49c6663f5ab85231dcc53b6ab71aedcc50171da36ee0a182a32fd09317c8f" - b"f673e79c9cb54a156a77825acfda8d45fe1f2a6405303e73c2c60cb9d63b634aab4603fe99c04640" - b"276063df503a0747d8154a9fea471f995a08620cb66c33084dd738ed482d2e0568ae805def4cdcd8" - b"20415f68f1bb5acde30eb00c31879b43de4943e1c8043fd13c1b87453069a8a9720e79121c31d83e" - b"2357dda74fa0f01c81d1771f6fd6d2b9a8b3031681394b9f55aed26ae4b3bfeaa5d59f4ba3c9d63b" - b"72f34af654ab0cfc38f76080df6e35ca75a154e42fbc6e17c91aa537b5a29abaecf4c075464f77a8" - b"e8595691662d6ede2981d6a697055e6445be2cceea644244b0c34fadf0b4dc03ca999b098295820d" - b"638a66f91972f8d5b98910e289980935f9a21cbe92732374e99d1fd73b4a9a845810c2f3a7e235ec" - b"7e3b45ce3046526bc0c0" +ca_cert_chain = bytes.fromhex( + "30820505308202eda00302010202104ba85293f79a2fa273064ba8048d75d0300d06092a864886f7" + "0d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e6574" + "2053656375726974792052657365617263682047726f7570311530130603550403130c4953524720" + "526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a30" + "33310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074310c" + "300a0603550403130352313030820122300d06092a864886f70d01010105000382010f003082010a" + "0282010100cf57e5e6c45412edb447fec92758764650288c1d3e88df059dd5b51829bdddb55abffa" + "f6cea3beaf00214b625a5a3c012fc55803f689ff8e1143ebc1b5e01407968f6f1fd7e7ba81390975" + "65b7c2af185b372628e7a3f4072b6d1affab58bc95ae40ffe9cb57c4b55b7f780d1861bc17e754c6" + "bb4991cd6e18d18085eea66536bc74eabc504ceafc21f338169394bab0d36b3806cd16127aca5275" + "c8ad76b2c29c5d98455c6f617bc62dee3c13528601d957e6381cdf8db51f92919ae74a1ccc45a872" + "55f0b0e6a307ecfda71b669e3f488b71847158c93afaef5ef25b442b3c74e78fb247c1076acd9ab7" + "0d96f712812651540aec61f6f7f5e2f28ac8950d8d0203010001a381f83081f5300e0603551d0f01" + "01ff040403020186301d0603551d250416301406082b0601050507030206082b0601050507030130" + "120603551d130101ff040830060101ff020100301d0603551d0e04160414bbbcc347a5e4bca9c6c3" + "a4720c108da235e1c8e8301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6" + "e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f2f" + "78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c01020130270603" + "551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d06" + "092a864886f70d01010b0500038202010092b1e74137eb799d81e6cde225e13a20e9904495a3815c" + "cfc35dfdbda070d5b19628220bd2f228cf0ce7d4e6438c24221dc14292d109af9f4bf4c8704f2016" + "b15add01f61ff81f616b1427b0728d63aeeee2ce4bcf37ddbba3d4cde7ad50adbdbfe3ec3e623670" + "9931a7e88dddea62e212aef59cd43d2c0caad09c79beea3d5c446e9631635a7dd67e4f24a04b057f" + "5e6fd2d4ea5f334b13d657b6cade51b85da3098274fdc7789eb3b9ac16da4a2b96c3b68b628ff974" + "19a29e03dee96f9bb00fd2a05af6855cc204b7c8d54e32c4bf045dbc29f6f7818f0c5d3c53c94090" + "8bfbb60865b9a421d509e51384843782ce1028fc76c206257a46524dda5372a4273f6270acbe6948" + "00fb670fdb5ba1e8d703212dd7c9f69942398343df770a1208f125d6ba9419541888a5c58ee11a99" + "93796bec1cf93140b0cc3200df9f5ee7b492ab9082918d0de01e95ba593b2e4b5fc2b74635523906" + "c0bdaaac52c122a0449799f70ca021a7a16c714716170168c0caa62665047cb3aec9e79455c26f9b" + "3c1ca9f92ec5201af076e0beec18d64fd825fb7611e8bfe6210fe8e8ccb5b6a7d5b8f79f41cf6122" + "466a83b668972e7cea4e95db23eb2ec82b2884a460e949f4442e3bf9ca625701e25d9016f9c9fc7a" + "23488ea6d58172f128fa5dcefbed4e738f942ed241949899dba7af705ff5befb0220bf66276cb4ad" + "fa75120b2b3ece039e" ) diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 30ec0ac7c83..119a42721fa 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -5,14 +5,12 @@ # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). - +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER ca_cert_chain = "mpycert.der" try: From 487c94c2532ebb1b346afb92cb79a6b561e064be Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:56:01 +1000 Subject: [PATCH 0629/2098] tests/extmod/vfs_rom.py: Clear sys.path before running test. Otherwise if the target has certain files/directories (such as "test") in its filesystem then these interfere with the unit tests. Signed-off-by: Damien George --- tests/extmod/vfs_rom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index 770b6863b9c..cd14542ea6b 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -394,6 +394,7 @@ class TestMounted(TestBase): def setUp(self): self.orig_sys_path = list(sys.path) self.orig_cwd = os.getcwd() + sys.path = [] vfs.mount(vfs.VfsRom(self.romfs), "/test_rom") def tearDown(self): From 61eedbbd1126f895f729a1cf5f9e52e42ddcd471 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Sep 2024 12:43:16 +1000 Subject: [PATCH 0630/2098] tools/verifygitlog.py: Apply stricter rules on git subject line. There is a bit of ambiguity as to how the prefix of the git subject line should look like. Eg `py/vm: ...` vs `py/vm.c: ...` (whether the extension should be there or not). This commit makes the existing CI check of the git commit message stricter, by applying extra rules to the prefix, the bit before the : in the subject line. It now checks that the subject prefix: - doesn't start with unwanted bits: ., /, ports/ - doesn't have an extension: .c, .h, .cpp, .js, .rst or .md Full error messages are given when a rule does not pass. This helps to reduce maintainer burden by applying stricter rules, to keep the git commit history consistent. Signed-off-by: Damien George --- tools/verifygitlog.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index 67215d5c5d0..5234611983b 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -96,6 +96,9 @@ def verify_message_body(raw_body, err): if len(subject_line) >= 73: err.error("Subject line must be 72 or fewer characters: " + subject_line) + # Do additional checks on the prefix of the subject line. + verify_subject_line_prefix(subject_line.split(": ")[0], err) + # Second one divides subject and body. if len(raw_body) > 1 and raw_body[1]: err.error("Second message line must be empty: " + raw_body[1]) @@ -110,6 +113,26 @@ def verify_message_body(raw_body, err): err.error('Message must be signed-off. Use "git commit -s".') +def verify_subject_line_prefix(prefix, err): + ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") + + if prefix.startswith("."): + err.error('Subject prefix cannot begin with ".".') + + if prefix.endswith("/"): + err.error('Subject prefix cannot end with "/".') + + if prefix.startswith("ports/"): + err.error( + 'Subject prefix cannot begin with "ports/", start with the name of the port instead.' + ) + + if prefix.endswith(ext): + err.error( + "Subject prefix cannot end with a file extension, use the main part of the filename without the extension." + ) + + def run(args): verbose("run", *args) From 26e978e7bc0b799d8b75d7cfe0219d2b788affa6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:05:13 +1000 Subject: [PATCH 0631/2098] extmod/modlwip: Implement a queue of incoming UDP/raw packets. The bare-metal lwIP socket interface is currently quite limited when used for UDP streams, because it only allows one outstanding incoming UDP packet. If one UDP packet is waiting to be socket.recv'd and another one comes along, then the second one is simply dropped. This commit implements a queue for incoming UDP and raw packets. The queue depth is fixed at compile time, and is currently 4. This allows better use of UDP connections, eg more efficient. It also makes DTLS work better which sometimes has a queue of UDP packets (eg during the connection phase). Signed-off-by: Damien George --- extmod/modlwip.c | 167 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 53 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index f109e0029bb..961803f5e84 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -286,6 +286,15 @@ static const int error_lookup_table[] = { #define MOD_NETWORK_SOCK_DGRAM (2) #define MOD_NETWORK_SOCK_RAW (3) +// Total queue length for buffered UDP/raw incoming packets. +#define LWIP_INCOMING_PACKET_QUEUE_LEN (4) + +typedef struct _lwip_incoming_packet_t { + struct pbuf *pbuf; + ip_addr_t peer_addr; + uint16_t peer_port; +} lwip_incoming_packet_t; + typedef struct _lwip_socket_obj_t { mp_obj_base_t base; @@ -294,8 +303,11 @@ typedef struct _lwip_socket_obj_t { struct udp_pcb *udp; struct raw_pcb *raw; } pcb; + + // Data structure that holds incoming pbuf's. + // Each socket type has different state that it needs to keep track of. volatile union { - struct pbuf *pbuf; + // TCP listening sockets have a queue of incoming connections, implemented as a ringbuffer. struct { uint8_t alloc; uint8_t iget; @@ -305,10 +317,23 @@ typedef struct _lwip_socket_obj_t { struct tcp_pcb **array; // if alloc != 0 } tcp; } connection; + + // Connected TCP sockets have a single incoming pbuf that new data is appended to. + struct { + struct pbuf *pbuf; + } tcp; + + // UDP and raw sockets have a queue of incoming pbuf's, implemented as a ringbuffer. + struct { + uint8_t iget; // ringbuffer read index + uint8_t iput; // ringbuffer write index + lwip_incoming_packet_t *array; + } udp_raw; } incoming; + mp_obj_t callback; - ip_addr_t peer; - mp_uint_t peer_port; + ip_addr_t tcp_peer_addr; + mp_uint_t tcp_peer_port; mp_uint_t timeout; uint16_t recv_offset; @@ -347,9 +372,21 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { && socket->pcb.tcp->state == LISTEN; if (!socket_is_listener) { - if (socket->incoming.pbuf != NULL) { - pbuf_free(socket->incoming.pbuf); - socket->incoming.pbuf = NULL; + if (socket->type == MOD_NETWORK_SOCK_STREAM) { + if (socket->incoming.tcp.pbuf != NULL) { + pbuf_free(socket->incoming.tcp.pbuf); + socket->incoming.tcp.pbuf = NULL; + } + } else { + for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[i]; + if (slot->pbuf != NULL) { + pbuf_free(slot->pbuf); + slot->pbuf = NULL; + } + } + socket->incoming.udp_raw.iget = 0; + socket->incoming.udp_raw.iput = 0; } } else { uint8_t alloc = socket->incoming.connection.alloc; @@ -407,6 +444,19 @@ static inline void exec_user_callback(lwip_socket_obj_t *socket) { } } +static void udp_raw_incoming(lwip_socket_obj_t *socket, struct pbuf *p, const ip_addr_t *addr, u16_t port) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iput]; + if (slot->pbuf != NULL) { + // No room in the inn, drop the packet. + pbuf_free(p); + } else { + slot->pbuf = p; + slot->peer_addr = *addr; + slot->peer_port = port; + socket->incoming.udp_raw.iput = (socket->incoming.udp_raw.iput + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; + } +} + #if MICROPY_PY_LWIP_SOCK_RAW // Callback for incoming raw packets. #if LWIP_VERSION_MAJOR < 2 @@ -416,13 +466,7 @@ static u8_t _lwip_raw_incoming(void *arg, struct raw_pcb *pcb, struct pbuf *p, c #endif { lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg; - - if (socket->incoming.pbuf != NULL) { - pbuf_free(p); - } else { - socket->incoming.pbuf = p; - memcpy(&socket->peer, addr, sizeof(socket->peer)); - } + udp_raw_incoming(socket, p, addr, 0); return 1; // we ate the packet } #endif @@ -436,15 +480,7 @@ static void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p, #endif { lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg; - - if (socket->incoming.pbuf != NULL) { - // That's why they call it "unreliable". No room in the inn, drop the packet. - pbuf_free(p); - } else { - socket->incoming.pbuf = p; - socket->peer_port = (mp_uint_t)port; - memcpy(&socket->peer, addr, sizeof(socket->peer)); - } + udp_raw_incoming(socket, p, addr, port); } // Callback for general tcp errors. @@ -562,13 +598,13 @@ static err_t _lwip_tcp_recv(void *arg, struct tcp_pcb *tcpb, struct pbuf *p, err return ERR_OK; } - if (socket->incoming.pbuf == NULL) { - socket->incoming.pbuf = p; + if (socket->incoming.tcp.pbuf == NULL) { + socket->incoming.tcp.pbuf = p; } else { #ifdef SOCKET_SINGLE_PBUF return ERR_BUF; #else - pbuf_cat(socket->incoming.pbuf, p); + pbuf_cat(socket->incoming.tcp.pbuf, p); #endif } @@ -639,7 +675,9 @@ static mp_uint_t lwip_raw_udp_send(lwip_socket_obj_t *socket, const byte *buf, m // Helper function for recv/recvfrom to handle raw/UDP packets static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, ip_addr_t *ip, mp_uint_t *port, int *_errno) { - if (socket->incoming.pbuf == NULL) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iget]; + + if (slot->pbuf == NULL) { if (socket->timeout == 0) { // Non-blocking socket. *_errno = MP_EAGAIN; @@ -648,7 +686,7 @@ static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_u // Wait for data to arrive on UDP socket. mp_uint_t start = mp_hal_ticks_ms(); - while (socket->incoming.pbuf == NULL) { + while (slot->pbuf == NULL) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; return -1; @@ -658,17 +696,18 @@ static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_u } if (ip != NULL) { - memcpy(ip, &socket->peer, sizeof(socket->peer)); - *port = socket->peer_port; + *ip = slot->peer_addr; + *port = slot->peer_port; } - struct pbuf *p = socket->incoming.pbuf; + struct pbuf *p = slot->pbuf; MICROPY_PY_LWIP_ENTER u16_t result = pbuf_copy_partial(p, buf, ((p->tot_len > len) ? len : p->tot_len), 0); pbuf_free(p); - socket->incoming.pbuf = NULL; + slot->pbuf = NULL; + socket->incoming.udp_raw.iget = (socket->incoming.udp_raw.iget + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; MICROPY_PY_LWIP_EXIT @@ -780,7 +819,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ // Check for any pending errors STREAM_ERROR_CHECK(socket); - if (socket->incoming.pbuf == NULL) { + if (socket->incoming.tcp.pbuf == NULL) { // Non-blocking socket if (socket->timeout == 0) { @@ -792,7 +831,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } mp_uint_t start = mp_hal_ticks_ms(); - while (socket->state == STATE_CONNECTED && socket->incoming.pbuf == NULL) { + while (socket->state == STATE_CONNECTED && socket->incoming.tcp.pbuf == NULL) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; return -1; @@ -801,7 +840,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } if (socket->state == STATE_PEER_CLOSED) { - if (socket->incoming.pbuf == NULL) { + if (socket->incoming.tcp.pbuf == NULL) { // socket closed and no data left in buffer return 0; } @@ -819,7 +858,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ assert(socket->pcb.tcp != NULL); - struct pbuf *p = socket->incoming.pbuf; + struct pbuf *p = socket->incoming.tcp.pbuf; mp_uint_t remaining = p->len - socket->recv_offset; if (len > remaining) { @@ -830,7 +869,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ remaining -= len; if (remaining == 0) { - socket->incoming.pbuf = p->next; + socket->incoming.tcp.pbuf = p->next; // If we don't ref here, free() will free the entire chain, // if we ref, it does what we need: frees 1st buf, and decrements // next buf's refcount back to 1. @@ -854,8 +893,18 @@ static const mp_obj_type_t lwip_socket_type; static void lwip_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { lwip_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", self->state, self->timeout, - self->incoming.pbuf, self->recv_offset); + mp_printf(print, "", self->incoming.tcp.pbuf, self->recv_offset); + } else { + int num_in_queue = 0; + for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) { + if (self->incoming.udp_raw.array[i].pbuf != NULL) { + ++num_in_queue; + } + } + mp_printf(print, "%d>", num_in_queue); + } } // FIXME: Only supports two arguments at present @@ -884,16 +933,22 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s socket->incoming.connection.tcp.item = NULL; break; case MOD_NETWORK_SOCK_DGRAM: - socket->pcb.udp = udp_new(); - socket->incoming.pbuf = NULL; - break; #if MICROPY_PY_LWIP_SOCK_RAW - case MOD_NETWORK_SOCK_RAW: { - mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]); - socket->pcb.raw = raw_new(proto); - break; - } + case MOD_NETWORK_SOCK_RAW: #endif + if (socket->type == MOD_NETWORK_SOCK_DGRAM) { + socket->pcb.udp = udp_new(); + } + #if MICROPY_PY_LWIP_SOCK_RAW + else { + mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]); + socket->pcb.raw = raw_new(proto); + } + #endif + socket->incoming.udp_raw.iget = 0; + socket->incoming.udp_raw.iput = 0; + socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN); + break; default: mp_raise_OSError(MP_EINVAL); } @@ -1075,7 +1130,7 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { // ...and set up the new socket for it. socket2->domain = MOD_NETWORK_AF_INET; socket2->type = MOD_NETWORK_SOCK_STREAM; - socket2->incoming.pbuf = NULL; + socket2->incoming.tcp.pbuf = NULL; socket2->timeout = socket->timeout; socket2->state = STATE_CONNECTED; socket2->recv_offset = 0; @@ -1130,8 +1185,8 @@ static mp_obj_t lwip_socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { socket->state = STATE_NEW; mp_raise_OSError(error_lookup_table[-err]); } - socket->peer_port = (mp_uint_t)port; - memcpy(&socket->peer, &dest, sizeof(socket->peer)); + socket->tcp_peer_addr = dest; + socket->tcp_peer_port = (mp_uint_t)port; MICROPY_PY_LWIP_EXIT // And now we wait... @@ -1299,8 +1354,8 @@ static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { mp_uint_t ret = 0; switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: { - memcpy(&ip, &socket->peer, sizeof(socket->peer)); - port = (mp_uint_t)socket->peer_port; + ip = socket->tcp_peer_addr; + port = (mp_uint_t)socket->tcp_peer_port; ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, &_errno); break; } @@ -1537,9 +1592,15 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ if (lwip_socket_incoming_array(socket)[socket->incoming.connection.iget] != NULL) { ret |= MP_STREAM_POLL_RD; } + } else if (socket->type == MOD_NETWORK_SOCK_STREAM) { + // For TCP sockets there is just one slot for incoming data + if (socket->incoming.tcp.pbuf != NULL) { + ret |= MP_STREAM_POLL_RD; + } } else { - // Otherwise there is just one slot for incoming data - if (socket->incoming.pbuf != NULL) { + // Otherwise for UDP/raw there is a queue of incoming data + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iget]; + if (slot->pbuf != NULL) { ret |= MP_STREAM_POLL_RD; } } From a05766f47b34136a405a90f9c670f8f886c2e0b4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:06:00 +1000 Subject: [PATCH 0632/2098] tests/multi_net: Add test that requires queuing UDP packets. This commit adds a new network multi-test which sends a burst of UDP packets from the client, and the server doesn't recv them until they have all been sent. Signed-off-by: Damien George --- tests/multi_net/udp_data_multi.py | 69 +++++++++++++++++++++++++++ tests/multi_net/udp_data_multi.py.exp | 15 ++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/multi_net/udp_data_multi.py create mode 100644 tests/multi_net/udp_data_multi.py.exp diff --git a/tests/multi_net/udp_data_multi.py b/tests/multi_net/udp_data_multi.py new file mode 100644 index 00000000000..5d7b13e5188 --- /dev/null +++ b/tests/multi_net/udp_data_multi.py @@ -0,0 +1,69 @@ +# Test UDP reception when there are multiple incoming UDP packets that need to be +# queued internally in the TCP/IP stack. + +import socket + +NUM_NEW_SOCKETS = 4 +NUM_PACKET_BURSTS = 6 +NUM_PACKET_GROUPS = 5 +TOTAL_PACKET_BURSTS = NUM_NEW_SOCKETS * NUM_PACKET_BURSTS +# The tast passes if more than 75% of packets are received in each group. +PACKET_RECV_THRESH = 0.75 * TOTAL_PACKET_BURSTS +PORT = 8000 + + +# Server +def instance0(): + recv_count = {i: 0 for i in range(NUM_PACKET_GROUPS)} + multitest.globals(IP=multitest.get_network_ip()) + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT + i)[0][-1]) + s.settimeout(0.250) + multitest.broadcast("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Wait for all packets to be sent, without receiving any yet. + multitest.wait("data sent burst={}".format(burst)) + # Try to receive all packets (they should be waiting in the queue). + for group in range(NUM_PACKET_GROUPS): + try: + data, addr = s.recvfrom(1000) + except: + continue + recv_burst, recv_group = data.split(b":") + recv_burst = int(recv_burst) + recv_group = int(recv_group) + if recv_burst == burst: + recv_count[recv_group] += 1 + # Inform the client that all data was received. + multitest.broadcast("data received burst={}".format(burst)) + s.close() + + # Check how many packets were received. + for group, count in recv_count.items(): + if count >= PACKET_RECV_THRESH: + print("pass group={}".format(group)) + else: + print("fail group={} received={}%".format(group, 100 * count // TOTAL_PACKET_BURSTS)) + + +# Client +def instance1(): + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + ai = socket.getaddrinfo(IP, PORT + i)[0][-1] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + multitest.wait("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Send a bunch of packets all in a row. + for group in range(NUM_PACKET_GROUPS): + s.sendto(b"%d:%d" % (burst, group), ai) + # Inform the server that the data has been sent. + multitest.broadcast("data sent burst={}".format(burst)) + # Wait for the server to finish receiving. + multitest.wait("data received burst={}".format(burst)) + s.close() diff --git a/tests/multi_net/udp_data_multi.py.exp b/tests/multi_net/udp_data_multi.py.exp new file mode 100644 index 00000000000..bc67c6ab0cf --- /dev/null +++ b/tests/multi_net/udp_data_multi.py.exp @@ -0,0 +1,15 @@ +--- instance0 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 +pass group=0 +pass group=1 +pass group=2 +pass group=3 +pass group=4 +--- instance1 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 From ee2c78cd972ddd91543d2e73b81b83895ca4cfae Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 30 Sep 2024 13:05:22 +0100 Subject: [PATCH 0633/2098] rp2: Use pico-sdk alarm pool instead of soft timer for sleep. Stop using soft timer for `mp_wfe_or_timeout`. Now uses the alarm pool again as issues with this code have been fixed. This resolves the "sev" issue that stops the RP2350 going idle. Also, change the lightsleep code to use the hardware timer library and alarm 1, as alarm 2 is used by and soft timers and alarm 3 is used by the alarm pool. Signed-off-by: Peter Harper --- ports/rp2/modmachine.c | 35 ++++++++++++++++++++--------------- ports/rp2/mpconfigport.h | 3 ++- ports/rp2/mphalport.c | 12 +----------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 58a3a8ae4d8..742d20f1f8a 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -137,6 +137,9 @@ static void mp_machine_idle(void) { MICROPY_INTERNAL_WFE(1); } +static void alarm_sleep_callback(uint alarm_id) { +} + static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_int_t delay_ms = 0; bool use_timer_alarm = false; @@ -206,6 +209,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Disable ROSC. rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + bool alarm_armed = false; if (n_args == 0) { #if MICROPY_PY_NETWORK_CYW43 gpio_set_dormant_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); @@ -214,16 +218,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } else { uint32_t save_sleep_en0 = clocks_hw->sleep_en0; uint32_t save_sleep_en1 = clocks_hw->sleep_en1; - bool timer3_enabled = irq_is_enabled(3); - - 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 - if (!timer3_enabled) { - 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 @@ -233,8 +228,11 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #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; + hardware_alarm_claim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, alarm_sleep_callback); + if (hardware_alarm_set_target(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, make_timeout_time_ms(delay_ms)) == PICO_OK) { + alarm_armed = true; + } } else { // TODO: Use RTC alarm to wake. clocks_hw->sleep_en0 = 0x0; @@ -264,10 +262,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif // Go into low-power mode. - __wfi(); - - if (!timer3_enabled) { - irq_set_enabled(irq_num, false); + if (alarm_armed) { + __wfi(); } clocks_hw->sleep_en0 = save_sleep_en0; clocks_hw->sleep_en1 = save_sleep_en1; @@ -282,6 +278,15 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Re-sync mp_hal_time_ns() counter with aon timer. mp_hal_time_ns_set_from_rtc(); + + // Note: This must be done after MICROPY_END_ATOMIC_SECTION + if (use_timer_alarm) { + if (alarm_armed) { + hardware_alarm_cancel(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + } + hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL); + hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + } } NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 3d65737266b..877cea08717 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -198,8 +198,9 @@ #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) // Hardware timer alarm index. Available range 0-3. -// Number 3 is currently used by pico-sdk (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM) +// Number 3 is currently used by pico-sdk alarm pool (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM) #define MICROPY_HW_SOFT_TIMER_ALARM_NUM (2) +#define MICROPY_HW_LIGHTSLEEP_ALARM_NUM (1) // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (2) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index caecb695099..b581b3b59f9 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -266,17 +266,7 @@ void soft_timer_init(void) { } void mp_wfe_or_timeout(uint32_t timeout_ms) { - soft_timer_entry_t timer; - - // Note the timer doesn't have an associated callback, it just exists to create a - // hardware interrupt to wake the CPU - soft_timer_static_init(&timer, SOFT_TIMER_MODE_ONE_SHOT, 0, NULL); - soft_timer_insert(&timer, timeout_ms); - - __wfe(); - - // Clean up the timer node if it's not already - soft_timer_remove(&timer); + best_effort_wfe_or_timeout(delayed_by_ms(get_absolute_time(), timeout_ms)); } int mp_hal_is_pin_reserved(int n) { From 2a4f1c9f0fd4f582cb22b25e6fcf7521bfa4f268 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 18 Mar 2025 15:32:02 +0000 Subject: [PATCH 0634/2098] rp2/modmachine: Add debug code for mp_machine_lightsleep. Add some debug code that can be enabled to determine why lightsleep is returning early. Signed-off-by: Peter Harper --- ports/rp2/modmachine.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 742d20f1f8a..fd2912c80f8 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -140,6 +140,9 @@ static void mp_machine_idle(void) { static void alarm_sleep_callback(uint alarm_id) { } +// Set this to 1 to enable some debug of the interrupt that woke the device +#define DEBUG_LIGHTSLEEP 0 + static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_int_t delay_ms = 0; bool use_timer_alarm = false; @@ -209,6 +212,14 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Disable ROSC. rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + #if DEBUG_LIGHTSLEEP + #if PICO_RP2040 + uint32_t pending_intr = 0; + #else + uint32_t pending_intr[2] = { 0 }; + #endif + #endif + bool alarm_armed = false; if (n_args == 0) { #if MICROPY_PY_NETWORK_CYW43 @@ -264,6 +275,15 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Go into low-power mode. if (alarm_armed) { __wfi(); + + #if DEBUG_LIGHTSLEEP + #if PICO_RP2040 + pending_intr = nvic_hw->ispr; + #else + pending_intr[0] = nvic_hw->ispr[0]; + pending_intr[1] = nvic_hw->ispr[1]; + #endif + #endif } clocks_hw->sleep_en0 = save_sleep_en0; clocks_hw->sleep_en1 = save_sleep_en1; @@ -286,6 +306,19 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL); hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + + #if DEBUG_LIGHTSLEEP + // Check irq.h for the list of IRQ's + // for rp2040 00000042: TIMER_IRQ_1 woke the device as expected + // 00000020: USBCTRL_IRQ woke the device (probably early) + // For rp2350 00000000:00000002: TIMER0_IRQ_1 woke the device as expected + // 00000000:00004000: USBCTRL_IRQ woke the device (probably early) + #if PICO_RP2040 + mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx\n", pending_intr); + #else + mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx:%08lx\n", pending_intr[1], pending_intr[0]); + #endif + #endif } } From 03da15575f088085e66be641c7e5ffd5cb2cc318 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 18 Mar 2025 17:45:48 +0000 Subject: [PATCH 0635/2098] tests/ports/rp2: Update machine idle test to revert skip for RP2350. This reverts commit b42bb911c663dc90575d6a7fe3ea4760b6559372. Signed-off-by: Peter Harper --- tests/ports/rp2/rp2_machine_idle.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ports/rp2/rp2_machine_idle.py b/tests/ports/rp2/rp2_machine_idle.py index 3135110b820..f9c28284782 100644 --- a/tests/ports/rp2/rp2_machine_idle.py +++ b/tests/ports/rp2/rp2_machine_idle.py @@ -1,4 +1,3 @@ -import sys import machine import time @@ -18,11 +17,6 @@ # Verification uses the average idle time, as individual iterations will always # have outliers due to interrupts, scheduler, etc. -# RP2350 currently fails this test because machine.idle() resumes immediately. -if "RP2350" in sys.implementation._machine: - print("SKIP") - raise SystemExit - ITERATIONS = 500 total = 0 From 977fd94856dfb909b816a93e9d962d4c467f253a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Dec 2024 16:27:48 +1100 Subject: [PATCH 0636/2098] tests/ports/rp2: Add a test case for light sleeping from CPU1. Not currently passing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/ports/rp2/rp2_lightsleep_thread.py | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/ports/rp2/rp2_lightsleep_thread.py diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py new file mode 100644 index 00000000000..2aaa34bbe39 --- /dev/null +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -0,0 +1,54 @@ +# Verify that a thread running on CPU1 can go to lightsleep +# and wake up in the expected timeframe +import _thread +import time +import unittest +from machine import lightsleep + +N_SLEEPS = 5 +SLEEP_MS = 250 + +IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS +MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS +MAX_DELTA = 20 + + +class LightSleepInThread(unittest.TestCase): + def thread_entry(self, is_thread=True): + for _ in range(N_SLEEPS): + lightsleep(SLEEP_MS) + if is_thread: + self.thread_done = True + + def elapsed_ms(self): + return time.ticks_diff(time.ticks_ms(), self.t0) + + def setUp(self): + self.thread_done = False + self.t0 = time.ticks_ms() + + def test_cpu0_busy(self): + _thread.start_new_thread(self.thread_entry, ()) + # CPU0 is busy-waiting not asleep itself + while not self.thread_done: + self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME) + self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_sleeping(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep_ms(MAX_RUNTIME) + self.assertTrue(self.thread_done) + self.assertAlmostEqual(self.elapsed_ms(), MAX_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_also_lightsleep(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep(0.050) # account for any delay in starting the thread + self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag + self.assertTrue(self.thread_done) + # only one thread can actually be in lightsleep at a time to avoid races, so the total + # runtime is doubled by doing it on both CPUs + self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME) + + +if __name__ == "__main__": + unittest.main() From 69993daa5c2eae6055f0b3b2330e95e78d6e3738 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 15:59:16 +1000 Subject: [PATCH 0637/2098] rp2/modmachine: Add mutual exclusion for machine.lightsleep(). There's no specified behaviour for what should happen if both CPUs call `lightsleep()` together, but the latest changes could cause a permanent hang due to a race in the timer cleanup code. Add a flag to prevent hangs if two threads accidentally lightsleep, at least. This allows the new lightsleep test to pass on RPI_PICO and RPI_PICO2, and even have much tighter time deltas. However, the test still fails on wireless boards where the lwIP tick wakes them up too frequently. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/modmachine.c | 16 ++++++++++++++++ tests/ports/rp2/rp2_lightsleep_thread.py | 23 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index fd2912c80f8..3625d404cdc 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -173,6 +173,16 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } #endif + #if MICROPY_PY_THREAD + static bool in_lightsleep; + if (in_lightsleep) { + // The other CPU is also in machine.lightsleep() + MICROPY_END_ATOMIC_SECTION(my_interrupts); + return; + } + in_lightsleep = true; + #endif + #if MICROPY_HW_ENABLE_USBDEV // Only disable the USB clock if a USB host has not configured the device // or if going to DORMANT mode. @@ -320,6 +330,12 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif #endif } + + #if MICROPY_PY_THREAD + // Clearing the flag here is atomic, and we know we're the ones who set it + // (higher up, inside the critical section) + in_lightsleep = false; + #endif } NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py index 2aaa34bbe39..494ead42231 100644 --- a/tests/ports/rp2/rp2_lightsleep_thread.py +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -42,12 +42,25 @@ def test_cpu0_sleeping(self): def test_cpu0_also_lightsleep(self): _thread.start_new_thread(self.thread_entry, ()) - time.sleep(0.050) # account for any delay in starting the thread + time.sleep_ms(50) # account for any delay in starting the thread self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag - self.assertTrue(self.thread_done) - # only one thread can actually be in lightsleep at a time to avoid races, so the total - # runtime is doubled by doing it on both CPUs - self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME) + while not self.thread_done: + time.sleep_ms(10) + # + # Only one thread can actually be in lightsleep at a time to avoid + # races, but otherwise the behaviour when both threads call lightsleep() + # is unspecified. + # + # Currently, the other thread will return immediately if one is already + # in lightsleep. Therefore, runtime can be between IDEAL_RUNTIME and + # IDEAL_RUNTIME * 2 depending on how many times the calls to lightsleep() race + # each other. + # + # Note this test case is really only here to ensure that the rp2 hasn't + # hung or failed to sleep at all - not to verify any correct behaviour + # when there's a race to call lightsleep(). + self.assertGreaterEqual(self.elapsed_ms(), IDEAL_RUNTIME - MAX_DELTA) + self.assertLessEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2 + MAX_DELTA) if __name__ == "__main__": From f47e214cdcf11c2067936cb4b4e4f9deab73f6fc Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Jan 2025 16:25:55 +0100 Subject: [PATCH 0638/2098] all: Rename the "NORETURN" macro to "MP_NORETURN". This commit renames the NORETURN macro, indicating to the compiler that a function does not return, into MP_NORETURN to maintain the same naming convention of other similar macros. To maintain compaitiblity with existing code NORETURN is aliased to MP_NORETURN, but it is also deprecated for MicroPython v2. This changeset was created using a similar process to decf8e6a8bb940d5829ca3296790631fcece7b21 ("all: Remove the "STATIC" macro and just use "static" instead."), with no documentation or python scripts to change to reflect the new macro name. Signed-off-by: Alessandro Gatti --- extmod/modmachine.c | 12 ++++----- extmod/modmachine.h | 2 +- extmod/modtls_axtls.c | 2 +- extmod/modtls_mbedtls.c | 2 +- extmod/moductypes.c | 2 +- mpy-cross/mpconfigport.h | 2 +- ports/alif/main.c | 2 +- ports/alif/modmachine.c | 6 ++--- ports/cc3200/misc/mperror.c | 2 +- ports/cc3200/misc/mperror.h | 2 +- ports/cc3200/mods/modmachine.c | 4 +-- ports/cc3200/mods/pybsleep.c | 4 +-- ports/esp32/modmachine.c | 8 +++--- ports/esp32/modnetwork.h | 2 +- ports/esp32/network_common.c | 2 +- ports/esp8266/esp_init_data.c | 2 +- ports/esp8266/modmachine.c | 4 +-- ports/mimxrt/modmachine.c | 6 ++--- ports/minimal/main.c | 2 +- ports/nrf/main.c | 4 +-- ports/nrf/modules/machine/modmachine.c | 6 ++--- ports/nrf/mphalport.c | 2 +- ports/nrf/mphalport.h | 2 +- ports/pic16bit/main.c | 2 +- ports/powerpc/main.c | 2 +- ports/qemu/mcu/arm/errorhandler.c | 20 +++++++-------- ports/qemu/mcu/arm/startup.c | 2 +- ports/renesas-ra/main.c | 2 +- ports/renesas-ra/modmachine.c | 6 ++--- ports/renesas-ra/mphalport.c | 2 +- ports/renesas-ra/mphalport.h | 2 +- ports/renesas-ra/powerctrl.c | 6 ++--- ports/renesas-ra/powerctrl.h | 6 ++--- ports/renesas-ra/uart.c | 2 +- ports/rp2/modmachine.c | 6 ++--- ports/samd/modmachine.c | 6 ++--- ports/stm32/boardctrl.c | 2 +- ports/stm32/boardctrl.h | 2 +- ports/stm32/mboot/main.c | 4 +-- ports/stm32/mboot/mboot.h | 2 +- ports/stm32/modmachine.c | 4 +-- ports/stm32/mphalport.c | 2 +- ports/stm32/mphalport.h | 2 +- ports/stm32/powerctrl.c | 8 +++--- ports/stm32/powerctrl.h | 6 ++--- ports/webassembly/main.c | 2 +- ports/windows/mpconfigport.h | 2 +- ports/zephyr/main.c | 2 +- py/argcheck.c | 4 +-- py/bc.c | 2 +- py/dynruntime.h | 4 +-- py/misc.h | 2 +- py/modmath.c | 2 +- py/mpconfig.h | 8 ++++-- py/nativeglue.h | 2 +- py/nlr.c | 2 +- py/nlr.h | 6 ++--- py/nlraarch64.c | 2 +- py/nlrmips.c | 2 +- py/nlrpowerpc.c | 4 +-- py/nlrrv32.c | 2 +- py/nlrrv64.c | 2 +- py/nlrthumb.c | 2 +- py/nlrx64.c | 2 +- py/nlrx86.c | 2 +- py/nlrxtensa.c | 2 +- py/objstr.c | 6 ++--- py/parsenum.c | 2 +- py/runtime.c | 32 ++++++++++++------------ py/runtime.h | 34 +++++++++++++------------- shared/libc/abort_.c | 4 +-- 71 files changed, 159 insertions(+), 155 deletions(-) diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 59068359498..f2570123e37 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -45,11 +45,11 @@ static void mp_machine_idle(void); #if MICROPY_PY_MACHINE_BOOTLOADER -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); #endif #if MICROPY_PY_MACHINE_RESET -NORETURN static void mp_machine_reset(void); +MP_NORETURN static void mp_machine_reset(void); static mp_int_t mp_machine_reset_cause(void); #endif @@ -58,7 +58,7 @@ static mp_obj_t mp_machine_unique_id(void); static mp_obj_t mp_machine_get_freq(void); static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args); static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args); -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); #endif // The port can provide additional machine-module implementation in this file. @@ -67,7 +67,7 @@ NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); #endif #if MICROPY_PY_MACHINE_BOOTLOADER -NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { mp_machine_bootloader(n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj, 0, 1, machine_bootloader); @@ -81,7 +81,7 @@ static MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); #if MICROPY_PY_MACHINE_RESET -NORETURN static mp_obj_t machine_reset(void) { +MP_NORETURN static mp_obj_t machine_reset(void) { mp_machine_reset(); } MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); @@ -116,7 +116,7 @@ static mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); -NORETURN static mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_deepsleep(n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj, 0, 1, machine_deepsleep); diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee..26010be8e18 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -242,7 +242,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align); uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); #endif -NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); +MP_NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); diff --git a/extmod/modtls_axtls.c b/extmod/modtls_axtls.c index 0e49fde2d84..ba28c13fce2 100644 --- a/extmod/modtls_axtls.c +++ b/extmod/modtls_axtls.c @@ -105,7 +105,7 @@ static const char *const ssl_error_tab2[] = { "NOT_SUPPORTED", }; -static NORETURN void ssl_raise_error(int err) { +static MP_NORETURN void ssl_raise_error(int err) { MP_STATIC_ASSERT(SSL_NOT_OK - 3 == SSL_EAGAIN); MP_STATIC_ASSERT(SSL_ERROR_CONN_LOST - 18 == SSL_ERROR_NOT_SUPPORTED); diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 6c34805da42..71a14adcff1 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -147,7 +147,7 @@ static const unsigned char *asn1_get_data(mp_obj_t obj, size_t *out_len) { return (const unsigned char *)str; } -static NORETURN void mbedtls_raise_error(int err) { +static MP_NORETURN void mbedtls_raise_error(int err) { // Handle special cases. if (err == MBEDTLS_ERR_SSL_ALLOC_FAILED) { mp_raise_OSError(MP_ENOMEM); diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 54abce79e06..eb72f441bbb 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -89,7 +89,7 @@ typedef struct _mp_obj_uctypes_struct_t { uint32_t flags; } mp_obj_uctypes_struct_t; -static NORETURN void syntax_error(void) { +static MP_NORETURN void syntax_error(void) { mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor")); } diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index d3805f03073..94a598c9954 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -137,7 +137,7 @@ typedef long mp_off_t; #ifdef _MSC_VER #define MP_ENDIANNESS_LITTLE (1) -#define NORETURN __declspec(noreturn) +#define MP_NORETURN __declspec(noreturn) #define MP_NOINLINE __declspec(noinline) #define MP_ALWAYSINLINE __forceinline #define MP_LIKELY(x) (x) diff --git a/ports/alif/main.c b/ports/alif/main.c index fd35604cbc2..47836113577 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -56,7 +56,7 @@ extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; -NORETURN void panic(const char *msg) { +MP_NORETURN void panic(const char *msg) { mp_hal_stdout_tx_strn("\nFATAL ERROR:\n", 14); mp_hal_stdout_tx_strn(msg, strlen(msg)); for (;;) { diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index 9868abbeeaf..71b5be5193d 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -47,11 +47,11 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id, sizeof(id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { se_services_reset_soc(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { __disable_irq(); MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); @@ -112,7 +112,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_USBDEV mp_machine_enable_usb(false); #endif diff --git a/ports/cc3200/misc/mperror.c b/ports/cc3200/misc/mperror.c index 6d6c0ff0bae..69f0a25371c 100644 --- a/ports/cc3200/misc/mperror.c +++ b/ports/cc3200/misc/mperror.c @@ -159,7 +159,7 @@ void mperror_heartbeat_signal (void) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { #ifdef DEBUG if (msg != NULL) { // wait for 20ms diff --git a/ports/cc3200/misc/mperror.h b/ports/cc3200/misc/mperror.h index 1c3eb626970..30effbbf911 100644 --- a/ports/cc3200/misc/mperror.h +++ b/ports/cc3200/misc/mperror.h @@ -27,7 +27,7 @@ #ifndef MICROPY_INCLUDED_CC3200_MISC_MPERROR_H #define MICROPY_INCLUDED_CC3200_MISC_MPERROR_H -extern void NORETURN __fatal_error(const char *msg); +extern void MP_NORETURN __fatal_error(const char *msg); void mperror_init0 (void); void mperror_bootloader_check_reset_cause (void); diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index f7184df4427..8986635ac65 100644 --- a/ports/cc3200/mods/modmachine.c +++ b/ports/cc3200/mods/modmachine.c @@ -93,7 +93,7 @@ extern OsiTaskHandle xSimpleLinkSpawnTaskHndl; /******************************************************************************/ // MicroPython bindings; -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { // disable wlan wlan_stop(SL_STOP_TIMEOUT_LONG); // reset the cpu and it's peripherals @@ -161,7 +161,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { pyb_sleep_sleep(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { pyb_sleep_deepsleep(); for (;;) { } diff --git a/ports/cc3200/mods/pybsleep.c b/ports/cc3200/mods/pybsleep.c index 9d04dd411dd..291860de8d6 100644 --- a/ports/cc3200/mods/pybsleep.c +++ b/ports/cc3200/mods/pybsleep.c @@ -136,7 +136,7 @@ static MP_DEFINE_CONST_OBJ_TYPE( ******************************************************************************/ static pyb_sleep_obj_t *pyb_sleep_find (mp_obj_t obj); static void pyb_sleep_flash_powerdown (void); -static NORETURN void pyb_sleep_suspend_enter (void); +static MP_NORETURN void pyb_sleep_suspend_enter (void); void pyb_sleep_suspend_exit (void); static void pyb_sleep_obj_wakeup (void); static void PRCMInterruptHandler (void); @@ -360,7 +360,7 @@ static void pyb_sleep_flash_powerdown (void) { MAP_SPICSDisable(SSPI_BASE); } -static NORETURN void pyb_sleep_suspend_enter (void) { +static MP_NORETURN void pyb_sleep_suspend_enter (void) { // enable full RAM retention MAP_PRCMSRAMRetentionEnable(PRCM_SRAM_COL_1 | PRCM_SRAM_COL_2 | PRCM_SRAM_COL_3 | PRCM_SRAM_COL_4, PRCM_SRAM_LPDS_RET); diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 0c1b94d02d9..0d7ea44c669 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -178,7 +178,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { machine_sleep_helper(MACHINE_WAKE_SLEEP, n_args, args); }; -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { machine_sleep_helper(MACHINE_WAKE_DEEPSLEEP, n_args, args); mp_machine_reset(); }; @@ -221,7 +221,7 @@ static mp_int_t mp_machine_reset_cause(void) { #include "esp32s3/rom/usb/chip_usb_dw_wrapper.h" #endif -NORETURN static void machine_bootloader_rtc(void) { +MP_NORETURN static void machine_bootloader_rtc(void) { #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_USB_CDC usb_usj_mode(); usb_dc_prepare_persist(); @@ -233,7 +233,7 @@ NORETURN static void machine_bootloader_rtc(void) { #endif #ifdef MICROPY_BOARD_ENTER_BOOTLOADER -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); for (;;) { } @@ -254,7 +254,7 @@ static mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_ } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_wake_reason_obj, 0, machine_wake_reason); -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { esp_restart(); } diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 387f961976d..308a515c6e2 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -61,7 +61,7 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj); mp_obj_t esp_ifname(esp_netif_t *netif); -NORETURN void esp_exceptions_helper(esp_err_t e); +MP_NORETURN void esp_exceptions_helper(esp_err_t e); static inline void esp_exceptions(esp_err_t e) { if (e != ESP_OK) { diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index fce8a0304c8..e07d7a993f2 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -45,7 +45,7 @@ #include "lwip/sockets.h" #include "lwip/dns.h" -NORETURN void esp_exceptions_helper(esp_err_t e) { +MP_NORETURN void esp_exceptions_helper(esp_err_t e) { switch (e) { case ESP_ERR_WIFI_NOT_INIT: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Initialized")); diff --git a/ports/esp8266/esp_init_data.c b/ports/esp8266/esp_init_data.c index c369ed58f51..a3dd6ffed1e 100644 --- a/ports/esp8266/esp_init_data.c +++ b/ports/esp8266/esp_init_data.c @@ -30,7 +30,7 @@ #include "user_interface.h" #include "extmod/misc.h" -NORETURN void call_user_start(void); +MP_NORETURN void call_user_start(void); void ets_printf(const char *fmt, ...); extern char flashchip; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 23ccf8cebfb..6ac17da4576 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -71,7 +71,7 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { system_update_cpu_freq(freq); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { system_restart(); // we must not return @@ -114,7 +114,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { // default to sleep forever uint32_t sleep_us = 0; diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 204699bcc42..ad078ff3ac6 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -87,7 +87,7 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id, sizeof(id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { WDOG_TriggerSystemSoftwareReset(WDOG1); while (true) { ; @@ -131,7 +131,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_raise_NotImplementedError(NULL); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_int_t seconds = mp_obj_get_int(args[0]) / 1000; if (seconds > 0) { @@ -159,7 +159,7 @@ NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { } } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if defined(MICROPY_BOARD_ENTER_BOOTLOADER) // If a board has a custom bootloader, call it first. MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); diff --git a/ports/minimal/main.c b/ports/minimal/main.c index 5f472c1afd6..b9e9034bf51 100644 --- a/ports/minimal/main.c +++ b/ports/minimal/main.c @@ -87,7 +87,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 29550bd77a7..99ed39895eb 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -107,7 +107,7 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) { extern uint32_t _heap_start; extern uint32_t _heap_end; -void NORETURN _start(void) { +void MP_NORETURN _start(void) { // Hook for a board to run code at start up, for example check if a // bootloader should be entered instead of the main application. MICROPY_BOARD_STARTUP(); @@ -353,7 +353,7 @@ void HardFault_Handler(void) { #endif } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index de1d0e31246..aa1f5a8a4a0 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -181,11 +181,11 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the board in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { NVIC_SystemReset(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); for (;;) { } @@ -199,7 +199,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { __WFE(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_reset(); } diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 9bb51deb121..3a3d3ad7a68 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -202,7 +202,7 @@ const byte mp_hal_status_to_errno_table[4] = { [HAL_TIMEOUT] = MP_ETIMEDOUT, }; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index a0bca58a625..3a89636aec6 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -47,7 +47,7 @@ extern const unsigned char mp_hal_status_to_errno_table[4]; extern ringbuf_t stdin_ringbuf; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable int mp_hal_stdin_rx_chr(void); diff --git a/ports/pic16bit/main.c b/ports/pic16bit/main.c index b1fe7321f0d..437f91ca7c7 100644 --- a/ports/pic16bit/main.c +++ b/ports/pic16bit/main.c @@ -121,7 +121,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/powerpc/main.c b/ports/powerpc/main.c index 4b668c861c9..bbd2082b42b 100644 --- a/ports/powerpc/main.c +++ b/ports/powerpc/main.c @@ -130,7 +130,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/qemu/mcu/arm/errorhandler.c b/ports/qemu/mcu/arm/errorhandler.c index a0e7ef930f0..a647c0e1536 100644 --- a/ports/qemu/mcu/arm/errorhandler.c +++ b/ports/qemu/mcu/arm/errorhandler.c @@ -63,7 +63,7 @@ static const char *EXCEPTION_NAMES_TABLE[] = { // R0-R15, PSR, Kind uintptr_t registers_copy[18] = { 0 }; -__attribute__((naked)) NORETURN void exception_handler(uintptr_t kind) { +__attribute__((naked)) MP_NORETURN void exception_handler(uintptr_t kind) { // Save registers __asm volatile ( "ldr r1, =registers_copy \n" @@ -137,39 +137,39 @@ __attribute__((naked)) NORETURN void exception_handler(uintptr_t kind) { for (;;) {} } -__attribute__((naked)) NORETURN void NMI_Handler(void) { +__attribute__((naked)) MP_NORETURN void NMI_Handler(void) { exception_handler(NMI); } -__attribute__((naked)) NORETURN void HardFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void HardFault_Handler(void) { exception_handler(HARD_FAULT); } -__attribute__((naked)) NORETURN void MemManage_Handler(void) { +__attribute__((naked)) MP_NORETURN void MemManage_Handler(void) { exception_handler(MEM_MANAGE); } -__attribute__((naked)) NORETURN void BusFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void BusFault_Handler(void) { exception_handler(BUS_FAULT); } -__attribute__((naked)) NORETURN void UsageFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void UsageFault_Handler(void) { exception_handler(USAGE_FAULT); } -__attribute__((naked)) NORETURN void SVC_Handler(void) { +__attribute__((naked)) MP_NORETURN void SVC_Handler(void) { exception_handler(SV_CALL); } -__attribute__((naked)) NORETURN void DebugMon_Handler(void) { +__attribute__((naked)) MP_NORETURN void DebugMon_Handler(void) { exception_handler(DEBUG_MONITOR); } -__attribute__((naked)) NORETURN void PendSV_Handler(void) { +__attribute__((naked)) MP_NORETURN void PendSV_Handler(void) { exception_handler(PENDING_SV); } -__attribute__((naked)) NORETURN void SysTick_Handler(void) { +__attribute__((naked)) MP_NORETURN void SysTick_Handler(void) { exception_handler(SYSTEM_TICK); } diff --git a/ports/qemu/mcu/arm/startup.c b/ports/qemu/mcu/arm/startup.c index dbb75e33922..a7e9efb9fba 100644 --- a/ports/qemu/mcu/arm/startup.c +++ b/ports/qemu/mcu/arm/startup.c @@ -52,7 +52,7 @@ __attribute__((naked)) void Reset_Handler(void) { _start(); } -NORETURN void Default_Handler(void) { +MP_NORETURN void Default_Handler(void) { for (;;) { } } diff --git a/ports/renesas-ra/main.c b/ports/renesas-ra/main.c index febb7b6d7aa..cfc4611ecb5 100644 --- a/ports/renesas-ra/main.c +++ b/ports/renesas-ra/main.c @@ -88,7 +88,7 @@ static machine_uart_obj_t machine_uart_repl_obj; static uint8_t machine_uart_repl_rxbuf[MICROPY_HW_UART_REPL_RXBUF]; #endif -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { for (volatile uint delay = 0; delay < 1000000; delay++) { } led_state(1, 1); diff --git a/ports/renesas-ra/modmachine.c b/ports/renesas-ra/modmachine.c index dc38d809dd7..c23ce7e4695 100644 --- a/ports/renesas-ra/modmachine.c +++ b/ports/renesas-ra/modmachine.c @@ -184,12 +184,12 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the pyboard in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { powerctrl_mcu_reset(); } // Activate the bootloader without BOOT* pins. -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_STORAGE storage_flush(); #endif @@ -232,7 +232,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { powerctrl_enter_stop_mode(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_obj_t args2[2] = {MP_OBJ_NULL, args[0]}; machine_rtc_wakeup(2, args2); diff --git a/ports/renesas-ra/mphalport.c b/ports/renesas-ra/mphalport.c index 1c62c23dd70..b2587af06de 100644 --- a/ports/renesas-ra/mphalport.c +++ b/ports/renesas-ra/mphalport.c @@ -61,7 +61,7 @@ const byte mp_hal_status_to_errno_table[4] = { [HAL_TIMEOUT] = MP_ETIMEDOUT, }; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/renesas-ra/mphalport.h b/ports/renesas-ra/mphalport.h index 0819abeaf61..1dab67f9a76 100644 --- a/ports/renesas-ra/mphalport.h +++ b/ports/renesas-ra/mphalport.h @@ -49,7 +49,7 @@ static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { return -mp_hal_status_to_errno_table[status]; } -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable static inline void mp_hal_wake_main_task_from_isr(void) { diff --git a/ports/renesas-ra/powerctrl.c b/ports/renesas-ra/powerctrl.c index 548d31679ad..04c39b35110 100644 --- a/ports/renesas-ra/powerctrl.c +++ b/ports/renesas-ra/powerctrl.c @@ -174,7 +174,7 @@ const lpm_instance_t g_lpm_standby = { #endif -NORETURN void powerctrl_mcu_reset(void) { +MP_NORETURN void powerctrl_mcu_reset(void) { #if BSP_TZ_SECURE_BUILD R_BSP_NonSecureEnter(); #else @@ -185,7 +185,7 @@ NORETURN void powerctrl_mcu_reset(void) { } } -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { while (1) { ; } @@ -245,7 +245,7 @@ void powerctrl_enter_stop_mode(void) { enable_irq(irq_state); } -NORETURN void powerctrl_enter_standby_mode(void) { +MP_NORETURN void powerctrl_enter_standby_mode(void) { rtc_init_finalise(); #if defined(MICROPY_BOARD_ENTER_STANDBY) diff --git a/ports/renesas-ra/powerctrl.h b/ports/renesas-ra/powerctrl.h index 34c40ea1adc..37932b1002b 100644 --- a/ports/renesas-ra/powerctrl.h +++ b/ports/renesas-ra/powerctrl.h @@ -32,11 +32,11 @@ void SystemClock_Config(void); -NORETURN void powerctrl_mcu_reset(void); -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); +MP_NORETURN void powerctrl_mcu_reset(void); +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); void powerctrl_check_enter_bootloader(void); void powerctrl_enter_stop_mode(void); -NORETURN void powerctrl_enter_standby_mode(void); +MP_NORETURN void powerctrl_enter_standby_mode(void); #endif // MICROPY_INCLUDED_RA_POWERCTRL_H diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index c319b4c0a8d..4800e0ea03f 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -42,7 +42,7 @@ typedef int (*KEYEX_CB)(uint32_t d); -extern void NORETURN __fatal_error(const char *msg); +extern void MP_NORETURN __fatal_error(const char *msg); #if MICROPY_KBD_EXCEPTION extern int mp_interrupt_char; static KEYEX_CB keyex_cb[MICROPY_HW_MAX_UART] = {(KEYEX_CB)NULL}; diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 3625d404cdc..1f1b0e2f59b 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -65,7 +65,7 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id.id, sizeof(id.id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { watchdog_reboot(0, SRAM_END, 0); for (;;) { __wfi(); @@ -82,7 +82,7 @@ static mp_int_t mp_machine_reset_cause(void) { return reset_cause; } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; reset_usb_boot(0, 0); @@ -338,7 +338,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_lightsleep(n_args, args); mp_machine_reset(); } diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 82e7b7c0620..c20ce8d641d 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -65,7 +65,7 @@ extern bool EIC_occured; extern uint32_t _dbl_tap_addr; -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_RESET; #ifdef DBL_TAP_ADDR_ALT *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_RESET; @@ -73,7 +73,7 @@ NORETURN static void mp_machine_reset(void) { NVIC_SystemReset(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_LOADER; #ifdef DBL_TAP_ADDR_ALT *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_LOADER; @@ -164,7 +164,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { set_cpu_freq(freq); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_lightsleep(n_args, args); mp_machine_reset(); } diff --git a/ports/stm32/boardctrl.c b/ports/stm32/boardctrl.c index 8f0d066f372..ea95c8d2d59 100644 --- a/ports/stm32/boardctrl.c +++ b/ports/stm32/boardctrl.c @@ -35,7 +35,7 @@ #include "led.h" #include "usrsw.h" -NORETURN void boardctrl_fatal_error(const char *msg) { +MP_NORETURN void boardctrl_fatal_error(const char *msg) { for (volatile uint delay = 0; delay < 10000000; delay++) { } led_state(1, 1); diff --git a/ports/stm32/boardctrl.h b/ports/stm32/boardctrl.h index 8f4ce30eff8..1a03925ef46 100644 --- a/ports/stm32/boardctrl.h +++ b/ports/stm32/boardctrl.h @@ -114,7 +114,7 @@ typedef struct _boardctrl_state_t { bool log_soft_reset; } boardctrl_state_t; -NORETURN void boardctrl_fatal_error(const char *msg); +MP_NORETURN void boardctrl_fatal_error(const char *msg); void boardctrl_maybe_enter_mboot(size_t n_args, const void *args); void boardctrl_before_soft_reset_loop(boardctrl_state_t *state); void boardctrl_top_soft_reset_loop(boardctrl_state_t *state); diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 01f8892a514..ff44dac630a 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -176,7 +176,7 @@ void HAL_Delay(uint32_t ms) { mp_hal_delay_ms(ms); } -NORETURN static void __fatal_error(const char *msg) { +MP_NORETURN static void __fatal_error(const char *msg) { NVIC_SystemReset(); for (;;) { } @@ -1443,7 +1443,7 @@ static int pyb_usbdd_shutdown(void) { /******************************************************************************/ // main -NORETURN static __attribute__((naked)) void branch_to_application(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN static __attribute__((naked)) void branch_to_application(uint32_t r0, uint32_t bl_addr) { __asm volatile ( "ldr r2, [r1, #0]\n" // get address of stack pointer "msr msp, r2\n" // set stack pointer diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index 1fabff0080b..bc1320c5cdb 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -36,7 +36,7 @@ #define ELEM_DATA_START (&_estack[0]) #define ELEM_DATA_MAX (&_estack[ELEM_DATA_SIZE]) -#define NORETURN __attribute__((noreturn)) +#define MP_NORETURN __attribute__((noreturn)) #define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) // The default UI code in ui.c only works if there is at least one LED configured. diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 620ae468cb9..f3bdf1bc502 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -291,12 +291,12 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the pyboard in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { powerctrl_mcu_reset(); } // Activate the bootloader without BOOT* pins. -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_USB pyb_usb_dev_deinit(); #endif diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c index dfd50cebd3d..b4b2267fa02 100644 --- a/ports/stm32/mphalport.c +++ b/ports/stm32/mphalport.c @@ -20,7 +20,7 @@ const byte mp_hal_status_to_errno_table[4] = { uint8_t mp_hal_unique_id_address[12]; #endif -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index e520bc54ae6..03b0f8e7722 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -37,7 +37,7 @@ static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { return -mp_hal_status_to_errno_table[status]; } -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable // Atomic section helpers. diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index eea009e2d78..e3e2fcdd44e 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -115,7 +115,7 @@ static inline void powerctrl_disable_hsi_if_unused(void) { #endif } -NORETURN void powerctrl_mcu_reset(void) { +MP_NORETURN void powerctrl_mcu_reset(void) { #if MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET *BL_STATE_PTR = BL_STATE_INVALID; #if __DCACHE_PRESENT == 1 @@ -125,7 +125,7 @@ NORETURN void powerctrl_mcu_reset(void) { NVIC_SystemReset(); } -NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, uint32_t bl_addr) { __asm volatile ( "ldr r2, [r1, #0]\n" // get address of stack pointer "msr msp, r2\n" // get stack pointer @@ -135,7 +135,7 @@ NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, ui MP_UNREACHABLE; } -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { #if MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET // Enter the bootloader via a reset, so everything is reset (including WDT). @@ -1016,7 +1016,7 @@ void powerctrl_enter_stop_mode(void) { enable_irq(irq_state); } -NORETURN void powerctrl_enter_standby_mode(void) { +MP_NORETURN void powerctrl_enter_standby_mode(void) { rtc_init_finalise(); #if defined(MICROPY_BOARD_ENTER_STANDBY) diff --git a/ports/stm32/powerctrl.h b/ports/stm32/powerctrl.h index 5b924056118..05a70e52c6a 100644 --- a/ports/stm32/powerctrl.h +++ b/ports/stm32/powerctrl.h @@ -39,14 +39,14 @@ static inline void stm32_system_init(void) { void SystemClock_Config(void); -NORETURN void powerctrl_mcu_reset(void); -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); +MP_NORETURN void powerctrl_mcu_reset(void); +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); void powerctrl_check_enter_bootloader(void); void powerctrl_config_systick(void); int powerctrl_rcc_clock_config_pll(RCC_ClkInitTypeDef *rcc_init, uint32_t sysclk_mhz, bool need_pllsai); int powerctrl_set_sysclk(uint32_t sysclk, uint32_t ahb, uint32_t apb1, uint32_t apb2); void powerctrl_enter_stop_mode(void); -NORETURN void powerctrl_enter_standby_mode(void); +MP_NORETURN void powerctrl_enter_standby_mode(void); #endif // MICROPY_INCLUDED_STM32_POWERCTRL_H diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c index c542f0cd72e..770dfbe0ca5 100644 --- a/ports/webassembly/main.c +++ b/ports/webassembly/main.c @@ -233,7 +233,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index fabc9072d6c..6e32d5fe5eb 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -242,7 +242,7 @@ typedef long mp_off_t; // CL specific overrides from mpconfig -#define NORETURN __declspec(noreturn) +#define MP_NORETURN __declspec(noreturn) #define MP_WEAK #define MP_NOINLINE __declspec(noinline) #define MP_ALWAYSINLINE __forceinline diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index d4498c1079c..45af7b0c72c 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -219,7 +219,7 @@ mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); #endif -NORETURN void nlr_jump_fail(void *val) { +MP_NORETURN void nlr_jump_fail(void *val) { while (1) { ; } diff --git a/py/argcheck.c b/py/argcheck.c index 35b116ec0d4..298c19bcfdb 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -137,12 +137,12 @@ void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, mp_arg_parse_all(n_pos, args, &kw_args, n_allowed, allowed, out_vals); } -NORETURN void mp_arg_error_terse_mismatch(void) { +MP_NORETURN void mp_arg_error_terse_mismatch(void) { mp_raise_TypeError(MP_ERROR_TEXT("argument num/types mismatch")); } #if MICROPY_CPYTHON_COMPAT -NORETURN void mp_arg_error_unimpl_kw(void) { +MP_NORETURN void mp_arg_error_unimpl_kw(void) { mp_raise_NotImplementedError(MP_ERROR_TEXT("keyword argument(s) not implemented - use normal args instead")); } #endif diff --git a/py/bc.c b/py/bc.c index 899dbd6a072..cea31c93bd8 100644 --- a/py/bc.c +++ b/py/bc.c @@ -88,7 +88,7 @@ const byte *mp_decode_uint_skip(const byte *ptr) { return ptr; } -static NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { +static MP_NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues (void)f; diff --git a/py/dynruntime.h b/py/dynruntime.h index c93111bbd4d..0e438da4b70 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -70,7 +70,7 @@ #define m_realloc(ptr, new_num_bytes) (m_realloc_dyn((ptr), (new_num_bytes))) #define m_realloc_maybe(ptr, new_num_bytes, allow_move) (m_realloc_maybe_dyn((ptr), (new_num_bytes), (allow_move))) -static NORETURN inline void m_malloc_fail_dyn(size_t num_bytes) { +static MP_NORETURN inline void m_malloc_fail_dyn(size_t num_bytes) { mp_fun_table.raise_msg( mp_fun_table.load_global(MP_QSTR_MemoryError), "memory allocation failed"); @@ -295,7 +295,7 @@ static inline mp_obj_t mp_obj_new_exception_arg1_dyn(const mp_obj_type_t *exc_ty return mp_call_function_n_kw(MP_OBJ_FROM_PTR(exc_type), 1, 0, &args[0]); } -static NORETURN inline void mp_raise_dyn(mp_obj_t o) { +static MP_NORETURN inline void mp_raise_dyn(mp_obj_t o) { mp_fun_table.raise(o); for (;;) { } diff --git a/py/misc.h b/py/misc.h index e05fbe61a9d..49f2f871111 100644 --- a/py/misc.h +++ b/py/misc.h @@ -105,7 +105,7 @@ void *m_realloc(void *ptr, size_t new_num_bytes); void *m_realloc_maybe(void *ptr, size_t new_num_bytes, bool allow_move); void m_free(void *ptr); #endif -NORETURN void m_malloc_fail(size_t num_bytes); +MP_NORETURN void m_malloc_fail(size_t num_bytes); #if MICROPY_TRACKED_ALLOC // These alloc/free functions track the pointers in a linked list so the GC does not reclaim diff --git a/py/modmath.c b/py/modmath.c index 4d51a28d0bb..b792d8581d0 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -37,7 +37,7 @@ #define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) #define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) -static NORETURN void math_error(void) { +static MP_NORETURN void math_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); } diff --git a/py/mpconfig.h b/py/mpconfig.h index 9001b8983bf..49a9fa35ccf 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2115,8 +2115,12 @@ typedef double mp_float_t; #endif // INT_FMT // Modifier for function which doesn't return -#ifndef NORETURN -#define NORETURN __attribute__((noreturn)) +#ifndef MP_NORETURN +#define MP_NORETURN __attribute__((noreturn)) +#endif + +#if !MICROPY_PREVIEW_VERSION_2 +#define NORETURN MP_NORETURN #endif // Modifier for weak functions diff --git a/py/nativeglue.h b/py/nativeglue.h index e96fd7b66a0..2c7923c56df 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -143,7 +143,7 @@ typedef struct _mp_fun_table_t { int (*printf_)(const mp_print_t *print, const char *fmt, ...); int (*vprintf_)(const mp_print_t *print, const char *fmt, va_list args); #if defined(__GNUC__) - NORETURN // Only certain compilers support no-return attributes in function pointer declarations + MP_NORETURN // Only certain compilers support no-return attributes in function pointer declarations #endif void (*raise_msg)(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); const mp_obj_type_t *(*obj_get_type)(mp_const_obj_t o_in); diff --git a/py/nlr.c b/py/nlr.c index 7ab0c0955a2..de2a38ceff3 100644 --- a/py/nlr.c +++ b/py/nlr.c @@ -81,7 +81,7 @@ void nlr_call_jump_callbacks(nlr_buf_t *nlr) { } #if MICROPY_ENABLE_VM_ABORT -NORETURN void nlr_jump_abort(void) { +MP_NORETURN void nlr_jump_abort(void) { MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort); nlr_jump(NULL); } diff --git a/py/nlr.h b/py/nlr.h index ce30bc91d67..47447c5d174 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -177,18 +177,18 @@ unsigned int nlr_push(nlr_buf_t *); unsigned int nlr_push_tail(nlr_buf_t *top); void nlr_pop(void); -NORETURN void nlr_jump(void *val); +MP_NORETURN void nlr_jump(void *val); #if MICROPY_ENABLE_VM_ABORT #define nlr_set_abort(buf) MP_STATE_VM(nlr_abort) = buf #define nlr_get_abort() MP_STATE_VM(nlr_abort) -NORETURN void nlr_jump_abort(void); +MP_NORETURN void nlr_jump_abort(void); #endif // This must be implemented by a port. It's called by nlr_jump // if no nlr buf has been pushed. It must not return, but rather // should bail out with a fatal error. -NORETURN void nlr_jump_fail(void *val); +MP_NORETURN void nlr_jump_fail(void *val); // use nlr_raise instead of nlr_jump so that debugging is easier #ifndef MICROPY_DEBUG_NLR diff --git a/py/nlraarch64.c b/py/nlraarch64.c index d6d87ebc50d..3318004b5e0 100644 --- a/py/nlraarch64.c +++ b/py/nlraarch64.c @@ -56,7 +56,7 @@ __asm( #endif ); -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) MP_STATIC_ASSERT(offsetof(nlr_buf_t, regs) == 16); // asm assumes it diff --git a/py/nlrmips.c b/py/nlrmips.c index cba52b16a26..5c55db7e268 100644 --- a/py/nlrmips.c +++ b/py/nlrmips.c @@ -57,7 +57,7 @@ __asm( ".end nlr_push \n" ); -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm( "move $4, %0 \n" diff --git a/py/nlrpowerpc.c b/py/nlrpowerpc.c index 8a69fe1eeca..cf140400e68 100644 --- a/py/nlrpowerpc.c +++ b/py/nlrpowerpc.c @@ -78,7 +78,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( @@ -167,7 +167,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( diff --git a/py/nlrrv32.c b/py/nlrrv32.c index 9a12ede400d..565a8629db5 100644 --- a/py/nlrrv32.c +++ b/py/nlrrv32.c @@ -50,7 +50,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { ); } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "add x10, x0, %0 \n" // Load nlr_buf address. diff --git a/py/nlrrv64.c b/py/nlrrv64.c index e7ba79797b8..b7d1467b8f6 100644 --- a/py/nlrrv64.c +++ b/py/nlrrv64.c @@ -50,7 +50,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { ); } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "add x10, x0, %0 \n" // Load nlr_buf address. diff --git a/py/nlrthumb.c b/py/nlrthumb.c index e7b24f242bc..8546308a3de 100644 --- a/py/nlrthumb.c +++ b/py/nlrthumb.c @@ -100,7 +100,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrx64.c b/py/nlrx64.c index d1ad91ff7d7..51224729fc9 100644 --- a/py/nlrx64.c +++ b/py/nlrx64.c @@ -100,7 +100,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrx86.c b/py/nlrx86.c index 085e30d2034..26bf0dc6ccb 100644 --- a/py/nlrx86.c +++ b/py/nlrx86.c @@ -78,7 +78,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrxtensa.c b/py/nlrxtensa.c index ff7af6edeef..2d1bf35e381 100644 --- a/py/nlrxtensa.c +++ b/py/nlrxtensa.c @@ -55,7 +55,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; // needed to silence compiler warning } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/objstr.c b/py/objstr.c index a160ab415c6..fda31d53185 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -40,7 +40,7 @@ static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ #endif static mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); -static NORETURN void bad_implicit_conversion(mp_obj_t self_in); +static MP_NORETURN void bad_implicit_conversion(mp_obj_t self_in); static mp_obj_t mp_obj_new_str_type_from_vstr(const mp_obj_type_t *type, vstr_t *vstr); @@ -1001,7 +1001,7 @@ static mp_obj_t arg_as_int(mp_obj_t arg) { #endif #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE -static NORETURN void terse_str_format_value_error(void) { +static MP_NORETURN void terse_str_format_value_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); } #else @@ -2357,7 +2357,7 @@ bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { } } -static NORETURN void bad_implicit_conversion(mp_obj_t self_in) { +static MP_NORETURN void bad_implicit_conversion(mp_obj_t self_in) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); #else diff --git a/py/parsenum.c b/py/parsenum.c index 3281eb4b851..a38ce563f95 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -36,7 +36,7 @@ #include #endif -static NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +static MP_NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { // if lex!=NULL then the parser called us and we need to convert the // exception's type from ValueError to SyntaxError and add traceback info if (lex != NULL) { diff --git a/py/runtime.c b/py/runtime.c index 58819819ad0..d6fea172f21 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1649,7 +1649,7 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i #endif // MICROPY_ENABLE_COMPILER -NORETURN void m_malloc_fail(size_t num_bytes) { +MP_NORETURN void m_malloc_fail(size_t num_bytes) { DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); #if MICROPY_ENABLE_GC if (gc_is_locked()) { @@ -1662,25 +1662,25 @@ NORETURN void m_malloc_fail(size_t num_bytes) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE -NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { +MP_NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { nlr_raise(mp_obj_new_exception(exc_type)); } -NORETURN void mp_raise_ValueError_no_msg(void) { +MP_NORETURN void mp_raise_ValueError_no_msg(void) { mp_raise_type(&mp_type_ValueError); } -NORETURN void mp_raise_TypeError_no_msg(void) { +MP_NORETURN void mp_raise_TypeError_no_msg(void) { mp_raise_type(&mp_type_TypeError); } -NORETURN void mp_raise_NotImplementedError_no_msg(void) { +MP_NORETURN void mp_raise_NotImplementedError_no_msg(void) { mp_raise_type(&mp_type_NotImplementedError); } #else -NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { if (msg == NULL) { nlr_raise(mp_obj_new_exception(exc_type)); } else { @@ -1688,7 +1688,7 @@ NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t ms } } -NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { +MP_NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { va_list args; va_start(args, fmt); mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); @@ -1696,25 +1696,25 @@ NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text nlr_raise(exc); } -NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_ValueError, msg); } -NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_TypeError, msg); } -NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_NotImplementedError, msg); } #endif -NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg) { +MP_NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg) { nlr_raise(mp_obj_new_exception_arg1(exc_type, arg)); } -NORETURN void mp_raise_StopIteration(mp_obj_t arg) { +MP_NORETURN void mp_raise_StopIteration(mp_obj_t arg) { if (arg == MP_OBJ_NULL) { mp_raise_type(&mp_type_StopIteration); } else { @@ -1722,7 +1722,7 @@ NORETURN void mp_raise_StopIteration(mp_obj_t arg) { } } -NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { +MP_NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE (void)arg; mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); @@ -1732,11 +1732,11 @@ NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { #endif } -NORETURN void mp_raise_OSError(int errno_) { +MP_NORETURN void mp_raise_OSError(int errno_) { mp_raise_type_arg(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_)); } -NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { +MP_NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { vstr_t vstr; vstr_init(&vstr, 32); vstr_printf(&vstr, "can't open %s", filename); @@ -1746,7 +1746,7 @@ NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { } #if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK -NORETURN void mp_raise_recursion_depth(void) { +MP_NORETURN void mp_raise_recursion_depth(void) { mp_raise_type_arg(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded)); } #endif diff --git a/py/runtime.h b/py/runtime.h index ffbc3972a32..064bd021d78 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -136,8 +136,8 @@ static inline void mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_mi } void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); -NORETURN void mp_arg_error_terse_mismatch(void); -NORETURN void mp_arg_error_unimpl_kw(void); +MP_NORETURN void mp_arg_error_terse_mismatch(void); +MP_NORETURN void mp_arg_error_unimpl_kw(void); static inline mp_obj_dict_t *mp_locals_get(void) { return MP_STATE_THREAD(dict_locals); @@ -246,10 +246,10 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name); void mp_import_all(mp_obj_t module); #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE -NORETURN void mp_raise_type(const mp_obj_type_t *exc_type); -NORETURN void mp_raise_ValueError_no_msg(void); -NORETURN void mp_raise_TypeError_no_msg(void); -NORETURN void mp_raise_NotImplementedError_no_msg(void); +MP_NORETURN void mp_raise_type(const mp_obj_type_t *exc_type); +MP_NORETURN void mp_raise_ValueError_no_msg(void); +MP_NORETURN void mp_raise_TypeError_no_msg(void); +MP_NORETURN void mp_raise_NotImplementedError_no_msg(void); #define mp_raise_msg(exc_type, msg) mp_raise_type(exc_type) #define mp_raise_msg_varg(exc_type, ...) mp_raise_type(exc_type) #define mp_raise_ValueError(msg) mp_raise_ValueError_no_msg() @@ -257,19 +257,19 @@ NORETURN void mp_raise_NotImplementedError_no_msg(void); #define mp_raise_NotImplementedError(msg) mp_raise_NotImplementedError_no_msg() #else #define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) -NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); -NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...); -NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg); -NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg); -NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...); +MP_NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg); #endif -NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg); -NORETURN void mp_raise_StopIteration(mp_obj_t arg); -NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg); -NORETURN void mp_raise_OSError(int errno_); -NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename); -NORETURN void mp_raise_recursion_depth(void); +MP_NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg); +MP_NORETURN void mp_raise_StopIteration(mp_obj_t arg); +MP_NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg); +MP_NORETURN void mp_raise_OSError(int errno_); +MP_NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename); +MP_NORETURN void mp_raise_recursion_depth(void); #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG #undef mp_check_self diff --git a/shared/libc/abort_.c b/shared/libc/abort_.c index 3051eae81e0..54eab67d3fb 100644 --- a/shared/libc/abort_.c +++ b/shared/libc/abort_.c @@ -1,7 +1,7 @@ #include -NORETURN void abort_(void); +MP_NORETURN void abort_(void); -NORETURN void abort_(void) { +MP_NORETURN void abort_(void) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called")); } From 9032491efd8a63e0b13dbcfb8579cde4791c03af Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 25 Jan 2024 09:09:06 -0600 Subject: [PATCH 0639/2098] py/objstr: Add support for the :_b/o/x specifier in str.format. This groups non-decimal values by fours, such as bbb_bbbb_bbbb. It also supports `{:_d}` to use underscore for decimal numbers (grouped in threes). Use of incorrect ":,b" is not diagnosed. Thanks to @dpgeorge for the suggestion to reduce code size. Signed-off-by: Jeff Epler --- py/mpprint.c | 11 ++++------- py/mpprint.h | 14 +++++++------- py/mpz.c | 4 +++- py/objint.c | 5 +++-- py/objstr.c | 7 ++++--- py/runtime.h | 2 +- tests/basics/string_format.py | 10 ++++++++++ 7 files changed, 32 insertions(+), 21 deletions(-) diff --git a/py/mpprint.c b/py/mpprint.c index 291e4145ff0..00a5f944c69 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -58,7 +58,7 @@ int mp_print_str(const mp_print_t *print, const char *str) { return len; } -int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width) { +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, unsigned int flags, char fill, int width) { int left_pad = 0; int right_pad = 0; int pad = width - len; @@ -201,7 +201,7 @@ static int mp_print_int(const mp_print_t *print, mp_uint_t x, int sgn, int base, return len; } -int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec) { +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, unsigned int base, int base_char, int flags, char fill, int width, int prec) { // These are the only values for "base" that are required to be supported by this // function, since Python only allows the user to format integers in these bases. // If needed this function could be generalised to handle other values. @@ -248,10 +248,7 @@ int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char int prefix_len = prefix - prefix_buf; prefix = prefix_buf; - char comma = '\0'; - if (flags & PF_FLAG_SHOW_COMMA) { - comma = ','; - } + char comma = flags >> PF_FLAG_SEP_POS; // The size of this buffer is rather arbitrary. If it's not large // enough, a dynamic one will be allocated. @@ -340,7 +337,7 @@ int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char } #if MICROPY_PY_BUILTINS_FLOAT -int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec) { +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int flags, char fill, int width, int prec) { char buf[32]; char sign = '\0'; int chrs = 0; diff --git a/py/mpprint.h b/py/mpprint.h index 8383ea85794..511af329baf 100644 --- a/py/mpprint.h +++ b/py/mpprint.h @@ -33,11 +33,11 @@ #define PF_FLAG_SPACE_SIGN (0x004) #define PF_FLAG_NO_TRAILZ (0x008) #define PF_FLAG_SHOW_PREFIX (0x010) -#define PF_FLAG_SHOW_COMMA (0x020) -#define PF_FLAG_PAD_AFTER_SIGN (0x040) -#define PF_FLAG_CENTER_ADJUST (0x080) -#define PF_FLAG_ADD_PERCENT (0x100) -#define PF_FLAG_SHOW_OCTAL_LETTER (0x200) +#define PF_FLAG_PAD_AFTER_SIGN (0x020) +#define PF_FLAG_CENTER_ADJUST (0x040) +#define PF_FLAG_ADD_PERCENT (0x080) +#define PF_FLAG_SHOW_OCTAL_LETTER (0x100) +#define PF_FLAG_SEP_POS (9) // must be above all the above PF_FLAGs #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES #define MP_PYTHON_PRINTER &mp_sys_stdout_print @@ -69,9 +69,9 @@ extern const mp_print_t mp_sys_stdout_print; #endif int mp_print_str(const mp_print_t *print, const char *str); -int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width); +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, unsigned int flags, char fill, int width); #if MICROPY_PY_BUILTINS_FLOAT -int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec); +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int flags, char fill, int width, int prec); #endif int mp_printf(const mp_print_t *print, const char *fmt, ...); diff --git a/py/mpz.c b/py/mpz.c index 084aebda9ec..471bd159818 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -1672,6 +1672,8 @@ size_t mpz_as_str_inpl(const mpz_t *i, unsigned int base, const char *prefix, ch size_t ilen = i->len; + int n_comma = (base == 10) ? 3 : 4; + char *s = str; if (ilen == 0) { if (prefix) { @@ -1717,7 +1719,7 @@ size_t mpz_as_str_inpl(const mpz_t *i, unsigned int base, const char *prefix, ch break; } } - if (!done && comma && (s - last_comma) == 3) { + if (!done && comma && (s - last_comma) == n_comma) { *s++ = comma; last_comma = s; } diff --git a/py/objint.c b/py/objint.c index 4be6009a440..87d8a27852d 100644 --- a/py/objint.c +++ b/py/objint.c @@ -209,7 +209,7 @@ static const uint8_t log_base2_floor[] = { size_t mp_int_format_size(size_t num_bits, int base, const char *prefix, char comma) { assert(2 <= base && base <= 16); size_t num_digits = num_bits / log_base2_floor[base - 1] + 1; - size_t num_commas = comma ? num_digits / 3 : 0; + size_t num_commas = comma ? (base == 10 ? num_digits / 3 : num_digits / 4): 0; size_t prefix_len = prefix ? strlen(prefix) : 0; return num_digits + num_commas + prefix_len + 2; // +1 for sign, +1 for null byte } @@ -251,6 +251,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co sign = '-'; } + int n_comma = (base == 10) ? 3 : 4; size_t needed_size = mp_int_format_size(sizeof(fmt_int_t) * 8, base, prefix, comma); if (needed_size > *buf_size) { *buf = m_new(char, needed_size); @@ -275,7 +276,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co c += '0'; } *(--b) = c; - if (comma && num != 0 && b > str && (last_comma - b) == 3) { + if (comma && num != 0 && b > str && (last_comma - b) == n_comma) { *(--b) = comma; last_comma = b; } diff --git a/py/objstr.c b/py/objstr.c index fda31d53185..c81fc682fd4 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -1184,7 +1184,7 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar int width = -1; int precision = -1; char type = '\0'; - int flags = 0; + unsigned int flags = 0; if (format_spec) { // The format specifier (from http://docs.python.org/2/library/string.html#formatspec) @@ -1229,8 +1229,9 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar } } s = str_to_int(s, stop, &width); - if (*s == ',') { - flags |= PF_FLAG_SHOW_COMMA; + if (*s == ',' || *s == '_') { + MP_STATIC_ASSERT((unsigned)'_' << PF_FLAG_SEP_POS >> PF_FLAG_SEP_POS == '_'); + flags |= (unsigned)*s << PF_FLAG_SEP_POS; s++; } if (*s == '.') { diff --git a/py/runtime.h b/py/runtime.h index 064bd021d78..a93488e2cdc 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -128,7 +128,7 @@ void mp_event_wait_indefinite(void); void mp_event_wait_ms(mp_uint_t timeout_ms); // extra printing method specifically for mp_obj_t's which are integral type -int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, unsigned base, int base_char, int flags, char fill, int width, int prec); void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); static inline void mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { diff --git a/tests/basics/string_format.py b/tests/basics/string_format.py index e8600f58361..11e7836a73e 100644 --- a/tests/basics/string_format.py +++ b/tests/basics/string_format.py @@ -22,7 +22,17 @@ def test(fmt, *args): test("{:4x}", 123) test("{:4X}", 123) +test("{:4,d}", 1) +test("{:4_d}", 1) +test("{:4_o}", 1) +test("{:4_b}", 1) +test("{:4_x}", 1) + test("{:4,d}", 12345678) +test("{:4_d}", 12345678) +test("{:4_o}", 12345678) +test("{:4_b}", 12345678) +test("{:4_x}", 12345678) test("{:#4b}", 10) test("{:#4o}", 123) From f77fd6257cc14ff9a05d702406501a49b7bacbd0 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 12 May 2025 17:39:16 +0200 Subject: [PATCH 0640/2098] tests/cpydiff: Document format separator difference. Signed-off-by: Jeff Epler --- tests/cpydiff/types_str_formatsep.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cpydiff/types_str_formatsep.py diff --git a/tests/cpydiff/types_str_formatsep.py b/tests/cpydiff/types_str_formatsep.py new file mode 100644 index 00000000000..05d0b8d3d2c --- /dev/null +++ b/tests/cpydiff/types_str_formatsep.py @@ -0,0 +1,19 @@ +""" +categories: Types,str +description: MicroPython accepts the "," grouping option with any radix, unlike CPython +cause: To reduce code size, MicroPython does not issue an error for this combination +workaround: Do not use a format string like ``{:,b}`` if CPython compatibility is required. +""" + +try: + print("{:,b}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,x}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,o}".format(99)) +except ValueError: + print("ValueError") From d7371124d288cc85d2386b63ea39cfa4fa803194 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 21 Mar 2025 10:44:20 +0100 Subject: [PATCH 0641/2098] esp32/esp32_common.cmake: Use the tinyusb source files from ESP-IDF. This commit removes the explicit dependency on the vendored tinyusb version for the ESP32S2 and ESP32S3 boards. Tinyusb is still available to MicroPython through a dependency on the `espressif/esp_tinyusb` ESP-IDF component, which in turn depends on the `espressif/tinyusb` component itself. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index c54f5a54032..063e8d6b92f 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -80,9 +80,7 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) -list(APPEND GIT_SUBMODULES lib/tinyusb) if(MICROPY_PY_TINYUSB) - set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) list(APPEND MICROPY_DEF_TINYUSB @@ -90,12 +88,6 @@ if(MICROPY_PY_TINYUSB) ) list(APPEND MICROPY_SOURCE_TINYUSB - ${TINYUSB_SRC}/tusb.c - ${TINYUSB_SRC}/common/tusb_fifo.c - ${TINYUSB_SRC}/device/usbd.c - ${TINYUSB_SRC}/device/usbd_control.c - ${TINYUSB_SRC}/class/cdc/cdc_device.c - ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c @@ -103,7 +95,6 @@ if(MICROPY_PY_TINYUSB) ) list(APPEND MICROPY_INC_TINYUSB - ${TINYUSB_SRC} ${MICROPY_DIR}/shared/tinyusb/ ) From 1d4bf8ac40236e22d0a67b103b4330d1ee6659d4 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 29 Mar 2025 09:12:28 +0100 Subject: [PATCH 0642/2098] esp32/machine_timer: Fix timer.value() for an uninitialized timer. Raises a value error in that case, which happens after a timer was created but not initialized, or after calling `timer.deinit()`. Fixes issue #17033. Signed-off-by: robert-hh --- ports/esp32/machine_timer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 82b432bbacd..34d49c79d28 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -246,6 +246,9 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init) static mp_obj_t machine_timer_value(mp_obj_t self_in) { machine_timer_obj_t *self = self_in; + if (self->handle == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("timer not set")); + } uint64_t result = timer_ll_get_counter_value(self->hal_context.dev, self->index); return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result / (TIMER_SCALE / 1000))); // value in ms } From 116d0d494589e470b4003fcecd18162c9eed7e9b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 25 Mar 2025 21:53:45 +0100 Subject: [PATCH 0643/2098] esp32/esp32_common.cmake: Allow adding defines and compiler flags. This commit introduces two extra CMake variables, MICROPY_DEF_COMPONENT and MICROPY_COMPILE_COMPONENT, that make it easier to integrate MicroPython as a custom ESP-IDF component. Whilst there is no official MicroPython component available for ESP-IDF, integration can be achieved with some minor CMake scripting outside the MicroPython tree - except for customisation of compilation defines and build flags, which is what this commit tries to provide. Compilation defines customisation is especially important for MicroPython configuration, as it is not possible to inject a value for MP_CONFIGFILE otherwise. This means that unless MicroPython itself is forked first to edit ports/esp32/mpconfigport.h, it is not possible to perform any meaningful configuration of the interpreter/runtime when included as a component. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 063e8d6b92f..2e7b95b385b 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -242,6 +242,7 @@ endif() # Set compile options for this port. target_compile_definitions(${MICROPY_TARGET} PUBLIC + ${MICROPY_DEF_COMPONENT} ${MICROPY_DEF_CORE} ${MICROPY_DEF_BOARD} ${MICROPY_DEF_TINYUSB} @@ -254,6 +255,7 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC # Disable some warnings to keep the build output clean. target_compile_options(${MICROPY_TARGET} PUBLIC + ${MICROPY_COMPILE_COMPONENT} -Wno-clobbered -Wno-deprecated-declarations -Wno-missing-field-initializers From 883dc41d465a6a652b607b7982d8eb6be39fa7f4 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 25 Mar 2025 22:43:23 +0100 Subject: [PATCH 0644/2098] esp32/main: Make the entry point function name configurable. This commit introduces a new port configuration entry allowing the entry point function name to be changed, from "app_main" to a custom name. This is needed when MicroPython is embedded as an ESP-IDF component, since the "app_main" symbol is already provided elsewhere, making compilation not possible. Marking MicroPython's symbol as weak would make it compile and make it possible to create and start the MicroPython task anyway with the right FreeRTOS task creation incantation, but it is probably easier to just rename the initialisation function into something else that can be accessed from outside. When MicroPython is embedded as an ESP-IDF component, the MICROPY_ESP_IDF_ENTRY definition can be set to indicate the new entry point function name. The new function name prototype should still be defined in external code to let linking succeed. Also, the NLR failure callback is marked as weak to give the chance of handling such error in a more controlled fashion rather than trigger an unconditional board restart. Signed-off-by: Alessandro Gatti --- ports/esp32/main.c | 4 ++-- ports/esp32/mpconfigport.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 4f0c27ee078..b8f49a33baa 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -216,7 +216,7 @@ void boardctrl_startup(void) { } } -void app_main(void) { +void MICROPY_ESP_IDF_ENTRY(void) { // Hook for a board to run code at start up. // This defaults to initialising NVS. MICROPY_BOARD_STARTUP(); @@ -225,7 +225,7 @@ void app_main(void) { xTaskCreatePinnedToCore(mp_task, "mp_task", MICROPY_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } -void nlr_jump_fail(void *val) { +MP_WEAK void nlr_jump_fail(void *val) { printf("NLR jump failed, val=%p\n", val); esp_restart(); } diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b5b7d63a563..7c260095789 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -391,3 +391,8 @@ void boardctrl_startup(void); #ifndef MICROPY_PY_STRING_TX_GIL_THRESHOLD #define MICROPY_PY_STRING_TX_GIL_THRESHOLD (20) #endif + +// Code can override this to provide a custom ESP-IDF entry point. +#ifndef MICROPY_ESP_IDF_ENTRY +#define MICROPY_ESP_IDF_ENTRY app_main +#endif From 155fa94fbf6afda9dbb3b8508c093648b5bffa09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 6 Mar 2025 18:52:54 +0100 Subject: [PATCH 0645/2098] esp32/machine_uart: Correctly manage UART queue and event task. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the driver was reinitialised while there was already an event task running the queue that task is trying to receive from would be deleted, causing it to try to take a lock that no longer existed and deadlocking the CPU. This change ensures the task is always shut down before recreating the queue and recreates the task afterwards. It also allows setting an IRQ handler before the UART is initialized (like other ports allow), removes the task when the UART is deinitialized (which was previously missing), adds a check that no event task can be started when no queue exists, and adds a check to prevent reinitialising the UART driver unnecessarily. Signed-off-by: Daniël van de Giessen --- ports/esp32/machine_uart.c | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 73089ef4636..e5857e894b9 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -59,6 +59,7 @@ #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 +#define UART_QUEUE_SIZE (3) enum { RXIDLE_INACTIVE, @@ -174,6 +175,13 @@ static void uart_event_task(void *self_in) { } } +static inline void uart_event_task_create(machine_uart_obj_t *self) { + if (xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, + ESP_TASKD_EVENT_PRIO, (TaskHandle_t *)&self->uart_event_task, MP_TASK_COREID) != pdPASS) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create UART event task")); + } +} + 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; @@ -250,7 +258,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // wait for all data to be transmitted before changing settings uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - if (args[ARG_txbuf].u_int >= 0 || args[ARG_rxbuf].u_int >= 0) { + if ((args[ARG_txbuf].u_int >= 0 && args[ARG_txbuf].u_int != self->txbuf) || (args[ARG_rxbuf].u_int >= 0 && args[ARG_rxbuf].u_int != self->rxbuf)) { // must reinitialise driver to change the tx/rx buffer size #if MICROPY_HW_ENABLE_UART_REPL if (self->uart_num == MICROPY_HW_UART_REPL) { @@ -275,9 +283,12 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, check_esp_err(uart_get_word_length(self->uart_num, &uartcfg.data_bits)); check_esp_err(uart_get_parity(self->uart_num, &uartcfg.parity)); check_esp_err(uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits)); - check_esp_err(uart_driver_delete(self->uart_num)); + mp_machine_uart_deinit(self); check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, 0, NULL, 0)); + check_esp_err(uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); + if (self->mp_irq_obj != NULL && self->mp_irq_obj->handler != mp_const_none) { + uart_event_task_create(self); + } } // set baudrate @@ -437,7 +448,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->uart_event_task = NULL; + self->uart_queue = NULL; self->rxidle_state = RXIDLE_INACTIVE; switch (uart_num) { @@ -470,12 +482,13 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg { // Remove any existing configuration check_esp_err(uart_driver_delete(self->uart_num)); + self->uart_queue = NULL; // init the peripheral // Setup check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 3, &self->uart_queue, 0)); + check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); } mp_map_t kw_args; @@ -489,7 +502,12 @@ 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) { + if (self->uart_event_task != NULL) { + vTaskDelete(self->uart_event_task); + self->uart_event_task = NULL; + } check_esp_err(uart_driver_delete(self->uart_num)); + self->uart_queue = NULL; } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { @@ -568,6 +586,12 @@ static const mp_irq_methods_t uart_irq_methods = { }; static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + #if MICROPY_HW_ENABLE_UART_REPL + if (self->uart_num == MICROPY_HW_UART_REPL) { + mp_raise_ValueError(MP_ERROR_TEXT("UART does not support IRQs")); + } + #endif + 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)); @@ -597,9 +621,8 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args 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); + if (handler != mp_const_none && self->uart_event_task == NULL && self->uart_queue != NULL) { + uart_event_task_create(self); } else if (handler == mp_const_none && self->uart_event_task != NULL) { vTaskDelete(self->uart_event_task); self->uart_event_task = NULL; From 24065824796b54d42fdb93457bf53d21656e473e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 28 Mar 2025 15:30:29 +0100 Subject: [PATCH 0646/2098] esp32/network_common: Raise a memory error on ESP_ERR_NO_MEM. This commit changes the error handler for WiFi operations to recognise out of memory conditions reported by ESP-IDF functions, and report them as more descriptive exceptions rather than a generic "error 0x101". The error handler only provided a human-readable error description for WiFi-specific error codes (codes in the ESP_ERR_WIFI_BASE range), but WiFi functions are known to return other codes. Now ESP_ERR_NO_MEM is covered with a specific error message, making it easier to debug issues related to running out of ESP-IDF heap. Signed-off-by: Alessandro Gatti --- ports/esp32/network_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index e07d7a993f2..bd34f1b41fd 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -77,6 +77,8 @@ MP_NORETURN void esp_exceptions_helper(esp_err_t e) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Would Block")); case ESP_ERR_WIFI_NOT_CONNECT: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Connected")); + case ESP_ERR_NO_MEM: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("WiFi Out of Memory")); default: mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Wifi Unknown Error 0x%04x"), e); } From 3b1e22c66947271e8b60eddf4e8aa6dadc6d9a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Mon, 27 May 2024 14:25:23 +0200 Subject: [PATCH 0647/2098] esp32/network_ppp: Restructure to match extmod/network_ppp_lwip. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ESP32 PPP implementation predates the generic implementation in extmod. The new extmod implementation has a few advantages such as a better deinitialisation procedure (the ESP32 implemementation would not clean up properly and cause crashes if recreated) and using the UART IRQ functionality instead of running a task to read data from the UART. This change restructures the ESP implementation to be much closer to the new extmod version, while also bringing a few tiny improvements from the ESP32 version to the extmod version. The diff between extmod/network_ppp_lwip.c and ports/esp32/network_ppp.c is now a small set of easy to review ESP32 port-specific changes. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 9 +- ports/esp32/modnetwork.h | 2 +- ports/esp32/modnetwork_globals.h | 2 +- ports/esp32/network_ppp.c | 511 ++++++++++++++++++------------- 4 files changed, 310 insertions(+), 214 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 8eb90ea4a65..2c3dac92012 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -24,6 +24,10 @@ * THE SOFTWARE. */ +// This file is intended to closely match ports/esp32/network_ppp.c. Changes can +// and should probably be applied to both files. Compare them directly by using: +// git diff --no-index extmod/network_ppp_lwip.c ports/esp32/network_ppp.c + #include "py/runtime.h" #include "py/mphal.h" #include "py/stream.h" @@ -80,7 +84,6 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { break; case PPPERR_USER: if (self->state >= STATE_ERROR) { - network_ppp_stream_uart_irq_disable(self); // Indicate that we are no longer connected and thus // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; @@ -121,6 +124,7 @@ static mp_obj_t network_ppp___del__(mp_obj_t self_in) { self->state = STATE_INACTIVE; ppp_close(self->pcb, 1); } + network_ppp_stream_uart_irq_disable(self); // Free PPP PCB and reset state. self->state = STATE_INACTIVE; ppp_free(self->pcb); @@ -295,7 +299,8 @@ static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_ ppp_set_auth(self->pcb, parsed_args[ARG_security].u_int, user_str, key_str); } - netif_set_default(self->pcb->netif); + ppp_set_default(self->pcb); + ppp_set_usepeerdns(self->pcb, true); if (ppp_connect(self->pcb, 0) != ERR_OK) { diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 308a515c6e2..383e200b983 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -52,7 +52,7 @@ extern const mp_obj_type_t esp_network_wlan_type; MP_DECLARE_CONST_FUN_OBJ_0(esp_network_initialize_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_get_wlan_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_get_lan_obj); -MP_DECLARE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj); +extern const struct _mp_obj_type_t esp_network_ppp_lwip_type; MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_ipconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_nic_ipconfig_obj); diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 1aad785ee3d..9e355e958f1 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -8,7 +8,7 @@ { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&esp_network_get_lan_obj) }, #endif #if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) -{ MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&esp_network_ppp_make_new_obj) }, +{ MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&esp_network_ppp_lwip_type) }, #endif { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_network_phy_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&esp_network_ipconfig_obj) }, diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 4dd5a3718c2..8b700c98ef3 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 "Eric Poulsen" + * Copyright (c) 2024 Damien P. George * * Based on the ESP IDF example code which is Public Domain / CC0 * @@ -26,172 +27,265 @@ * THE SOFTWARE. */ +// This file is intended to closely match extmod/network_ppp_lwip.c. Changes can +// and should probably be applied to both files. Compare them directly by using: +// git diff --no-index extmod/network_ppp_lwip.c ports/esp32/network_ppp.c + #include "py/runtime.h" #include "py/mphal.h" -#include "py/objtype.h" #include "py/stream.h" -#include "shared/netutils/netutils.h" -#include "modmachine.h" -#include "ppp_set_auth.h" -#include "netif/ppp/ppp.h" -#include "netif/ppp/pppos.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" +#if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) + #include "lwip/dns.h" +#include "netif/ppp/ppp.h" #include "netif/ppp/pppapi.h" +#include "netif/ppp/pppos.h" -#if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) +// Includes for port-specific changes compared to network_ppp_lwip.c +#include "shared/netutils/netutils.h" +#include "ppp_set_auth.h" -#define PPP_CLOSE_TIMEOUT_MS (4000) +// Enable this to see the serial data going between the PPP layer. +#define PPP_TRACE_IN_OUT (0) -typedef struct _ppp_if_obj_t { +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; - bool active; - bool connected; - volatile bool clean_close; - ppp_pcb *pcb; + network_ppp_state_t state; + int error_code; mp_obj_t stream; - SemaphoreHandle_t inactiveWaitSem; - volatile TaskHandle_t client_task_handle; - struct netif pppif; -} ppp_if_obj_t; + ppp_pcb *pcb; + struct netif netif; +} network_ppp_obj_t; -const mp_obj_type_t ppp_if_type; +const mp_obj_type_t esp_network_ppp_lwip_type; + +static mp_obj_t network_ppp___del__(mp_obj_t self_in); + +static void network_ppp_stream_uart_irq_disable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } -static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { - ppp_if_obj_t *self = ctx; - struct netif *pppif = ppp_netif(self->pcb); + // 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); +} +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: - #if CONFIG_LWIP_IPV6 - self->connected = (pppif->ip_addr.u_addr.ip4.addr != 0); - #else - self->connected = (pppif->ip_addr.addr != 0); - #endif // CONFIG_LWIP_IPV6 + self->state = STATE_CONNECTED; break; case PPPERR_USER: - self->clean_close = true; - break; - case PPPERR_CONNECT: - self->connected = false; + if (self->state >= STATE_ERROR) { + // Indicate that we are no longer connected and thus + // only need to free the PPP PCB, not close it. + 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 ppp_make_new(mp_obj_t stream) { +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]; + if (stream != mp_const_none) { mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); } - ppp_if_obj_t *self = mp_obj_malloc_with_finaliser(ppp_if_obj_t, &ppp_if_type); + network_ppp_obj_t *self = mp_obj_malloc_with_finaliser(network_ppp_obj_t, type); + self->state = STATE_INACTIVE; self->stream = stream; - self->active = false; - self->connected = false; - self->clean_close = false; - self->client_task_handle = NULL; + self->pcb = NULL; return MP_OBJ_FROM_PTR(self); } -MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) -static u32_t ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) -#else -static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) -#endif -{ - ppp_if_obj_t *self = ctx; - - mp_obj_t stream = self->stream; - if (stream == mp_const_none) { - return 0; +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 stream. + // Force the connection to close, with nocarrier=1. + self->state = STATE_INACTIVE; + pppapi_close(self->pcb, 1); + } + network_ppp_stream_uart_irq_disable(self); + // Free PPP PCB and reset state. + self->state = STATE_INACTIVE; + pppapi_free(self->pcb); + self->pcb = NULL; } - - int err; - return mp_stream_rw(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); + return mp_const_none; } +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp___del___obj, network_ppp___del__); -static void pppos_client_task(void *self_in) { - ppp_if_obj_t *self = (ppp_if_obj_t *)self_in; - uint8_t buf[256]; +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]); - int len = 0; - while (ulTaskNotifyTake(pdTRUE, len <= 0) == 0) { - mp_obj_t stream = self->stream; - if (stream == mp_const_none) { - len = 0; - } else { - int err; - len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); - if (len > 0) { - pppos_input_tcpip(self->pcb, (u8_t *)buf, len); - } - } + if (self->state <= STATE_ERROR) { + return MP_OBJ_NEW_SMALL_INT(-MP_EPERM); } - self->client_task_handle = NULL; - vTaskDelete(NULL); - for (;;) { + mp_int_t total_len = 0; + mp_obj_t stream = self->stream; + while (stream != mp_const_none) { + uint8_t buf[256]; + int err; + mp_uint_t len = mp_stream_rw(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 ppp_active(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); +static void network_ppp_stream_uart_irq_enable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - if (self->active) { - return mp_const_true; - } + // 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); +} - self->pcb = pppapi_pppos_create(&self->pppif, ppp_output_callback, ppp_status_cb, self); +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 (self->pcb == NULL) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("init failed")); - } - self->active = true; - } else { - if (!self->active) { - return mp_const_false; + 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)) { + case MP_QSTR_stream: { + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_disable(self); + } + self->stream = kwargs->table[i].value; + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_enable(self); + } + break; + } + default: + break; + } } + } + return mp_const_none; + } - if (self->client_task_handle != NULL) { // is connecting or connected? - // Wait for PPPERR_USER, with timeout - pppapi_close(self->pcb, 0); - uint32_t t0 = mp_hal_ticks_ms(); - while (!self->clean_close && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); - } + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } - // Shutdown task - xTaskNotifyGive(self->client_task_handle); - t0 = mp_hal_ticks_ms(); - while (self->client_task_handle != NULL && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } + case MP_QSTR_ifname: { + if (self->pcb != NULL) { + struct netif *pppif = ppp_netif(self->pcb); + char ifname[NETIF_NAMESIZE + 1] = {0}; + netif_index_to_name(netif_get_index(pppif), ifname); + if (ifname[0] != 0) { + val = mp_obj_new_str_from_cstr((char *)ifname); } } - - // Release PPP - pppapi_free(self->pcb); - self->pcb = NULL; - self->active = false; - self->connected = false; - self->clean_close = false; + break; } + 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); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +static u32_t network_ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) +#else +static u32_t network_ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) +#endif +{ + 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 + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; } - return mp_obj_new_bool(self->active); + 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(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_active_obj, 1, 2, ppp_active); -static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - enum { ARG_authmode, ARG_username, ARG_password }; +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, ARG_authmode, ARG_username, ARG_password }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { 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} }, + // Deprecated arguments for backwards compatibility { MP_QSTR_authmode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PPPAUTHTYPE_NONE} }, { MP_QSTR_username, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -200,17 +294,34 @@ static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw 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); - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + // Use deprecated arguments as defaults + if (parsed_args[ARG_security].u_int == -1) { + parsed_args[ARG_security].u_int = parsed_args[ARG_authmode].u_int; + } + if (parsed_args[ARG_user].u_obj == mp_const_none) { + parsed_args[ARG_user].u_obj = parsed_args[ARG_username].u_obj; + } + if (parsed_args[ARG_key].u_obj == mp_const_none) { + parsed_args[ARG_key].u_obj = parsed_args[ARG_password].u_obj; + } + + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->state == STATE_INACTIVE) { + self->pcb = pppapi_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; - if (!self->active) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("must be active")); + network_ppp_stream_uart_irq_enable(self); } - if (self->client_task_handle != NULL) { + if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { mp_raise_OSError(MP_EALREADY); } - switch (parsed_args[ARG_authmode].u_int) { + switch (parsed_args[ARG_security].u_int) { case PPPAUTHTYPE_NONE: case PPPAUTHTYPE_PAP: case PPPAUTHTYPE_CHAP: @@ -219,39 +330,49 @@ static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw mp_raise_ValueError(MP_ERROR_TEXT("invalid auth")); } - if (parsed_args[ARG_authmode].u_int != PPPAUTHTYPE_NONE) { - const char *username_str = mp_obj_str_get_str(parsed_args[ARG_username].u_obj); - const char *password_str = mp_obj_str_get_str(parsed_args[ARG_password].u_obj); - pppapi_set_auth(self->pcb, parsed_args[ARG_authmode].u_int, username_str, password_str); + 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); + pppapi_set_auth(self->pcb, parsed_args[ARG_security].u_int, user_str, key_str); } - if (pppapi_set_default(self->pcb) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("set default failed")); + + if (pppapi_set_default(self->pcb) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_set_default failed")); } ppp_set_usepeerdns(self->pcb, true); - if (pppapi_connect(self->pcb, 0) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("connect failed")); + if (pppapi_connect(self->pcb, 0) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_connect failed")); } - if (xTaskCreatePinnedToCore(pppos_client_task, "ppp", 2048, self, 1, (TaskHandle_t *)&self->client_task_handle, MP_TASK_COREID) != pdPASS) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create worker task")); - } + 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; } -MP_DEFINE_CONST_FUN_OBJ_KW(ppp_connect_obj, 1, ppp_connect_py); +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_connect_obj, 1, network_ppp_connect); -static mp_obj_t ppp_delete(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t args[] = {self, mp_const_false}; - ppp_active(2, args); +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. + pppapi_close(self->pcb, 0); + } return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_1(ppp_delete_obj, ppp_delete); +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 ppp_ifconfig(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); +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]); if (n_args == 1) { // get const ip_addr_t *dns; @@ -282,11 +403,11 @@ static mp_obj_t ppp_ifconfig(size_t n_args, const mp_obj_t *args) { return mp_const_none; } } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_ifconfig_obj, 1, 2, ppp_ifconfig); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_ifconfig_obj, 1, 2, network_ppp_ifconfig); -static mp_obj_t ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { +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]); if (kwargs->used == 0) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (self->pcb == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("PPP not active")); } @@ -318,94 +439,64 @@ static mp_obj_t ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwar } return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_KW(ppp_ipconfig_obj, 1, ppp_ipconfig); +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_ipconfig_obj, 1, network_ppp_ipconfig); -static mp_obj_t ppp_status(mp_obj_t self_in) { - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_1(ppp_status_obj, ppp_status); - -static mp_obj_t ppp_isconnected(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(self->connected); -} -static MP_DEFINE_CONST_FUN_OBJ_1(ppp_isconnected_obj, ppp_isconnected); - -static mp_obj_t 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")); - } - ppp_if_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)) { - case MP_QSTR_stream: { - if (kwargs->table[i].value != mp_const_none) { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); - } - self->stream = kwargs->table[i].value; - break; - } - default: - break; - } +static mp_obj_t network_ppp_active(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + if (self->state >= STATE_ACTIVE) { + return mp_const_true; } - } - 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; + self->pcb = pppapi_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; - switch (mp_obj_str_get_qstr(args[1])) { - case MP_QSTR_stream: { - val = self->stream; - break; - } - case MP_QSTR_ifname: { - if (self->pcb != NULL) { - struct netif *pppif = ppp_netif(self->pcb); - char ifname[NETIF_NAMESIZE + 1] = {0}; - netif_index_to_name(netif_get_index(pppif), ifname); - if (ifname[0] != 0) { - val = mp_obj_new_str_from_cstr((char *)ifname); - } + network_ppp_stream_uart_irq_enable(self); + } else { + if (self->state < STATE_ACTIVE) { + return mp_const_false; } - break; + + network_ppp___del__(MP_OBJ_FROM_PTR(self)); } - default: - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } - - return val; + return mp_obj_new_bool(self->state >= STATE_ACTIVE); } -static MP_DEFINE_CONST_FUN_OBJ_KW(ppp_config_obj, 1, ppp_config); - -static const mp_rom_map_elem_t ppp_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&ppp_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&ppp_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&ppp_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&ppp_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&ppp_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&ppp_ifconfig_obj) }, - { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&ppp_ipconfig_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ppp_delete_obj) }, +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_active_obj, 1, 2, network_ppp_active); + +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) }, + + // Deprecated interface for backwards compatibility + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_ppp_active_obj) }, { MP_ROM_QSTR(MP_QSTR_AUTH_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, { MP_ROM_QSTR(MP_QSTR_AUTH_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, { MP_ROM_QSTR(MP_QSTR_AUTH_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, }; -static MP_DEFINE_CONST_DICT(ppp_if_locals_dict, ppp_if_locals_dict_table); +static MP_DEFINE_CONST_DICT(network_ppp_locals_dict, network_ppp_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE( - ppp_if_type, + esp_network_ppp_lwip_type, MP_QSTR_PPP, MP_TYPE_FLAG_NONE, - locals_dict, &ppp_if_locals_dict + make_new, network_ppp_make_new, + locals_dict, &network_ppp_locals_dict ); #endif From 51b821ce820008b5419bf6889d15d5a7c00a2c99 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:35:49 +1000 Subject: [PATCH 0648/2098] tools/verifygitlog.py: Disallow a leading slash in commit subject line. Signed-off-by: Damien George --- tools/verifygitlog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index 5234611983b..f2d3d722d08 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -116,8 +116,8 @@ def verify_message_body(raw_body, err): def verify_subject_line_prefix(prefix, err): ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") - if prefix.startswith("."): - err.error('Subject prefix cannot begin with ".".') + if prefix.startswith((".", "/")): + err.error('Subject prefix cannot begin with "." or "/".') if prefix.endswith("/"): err.error('Subject prefix cannot end with "/".') From 420897045171305fa36440c08b575ebe3f6d4ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 14 May 2025 18:05:06 +0200 Subject: [PATCH 0649/2098] tools/verifygitlog.py: Allow long co-author and sign-off names. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- tools/verifygitlog.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index f2d3d722d08..dba6ebd6de5 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -105,8 +105,12 @@ def verify_message_body(raw_body, err): # Message body lines. for line in raw_body[2:]: - # Long lines with URLs are exempt from the line length rule. - if len(line) >= 76 and "://" not in line: + # Long lines with URLs or human names are exempt from the line length rule. + if len(line) >= 76 and not ( + "://" in line + or line.startswith("Co-authored-by: ") + or line.startswith("Signed-off-by: ") + ): err.error("Message lines should be 75 or less characters: " + line) if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: From 62d26bfc15840b6bc172a081066931b2fce6a696 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:54:47 +1000 Subject: [PATCH 0650/2098] extmod/vfs_lfsx: Fix errno value raised from chdir. OSError errno values should be positive. Signed-off-by: Damien George --- extmod/vfs_lfsx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 4b10ca3aa59..404eab84f47 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -300,7 +300,7 @@ static mp_obj_t MP_VFS_LFSx(chdir)(mp_obj_t self_in, mp_obj_t path_in) { struct LFSx_API (info) info; int ret = LFSx_API(stat)(&self->lfs, path, &info); if (ret < 0 || info.type != LFSx_MACRO(_TYPE_DIR)) { - mp_raise_OSError(-MP_ENOENT); + mp_raise_OSError(MP_ENOENT); } } From 44bcfe53de6fdc1f3eb0f104798f5679d7b4473d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:55:01 +1000 Subject: [PATCH 0651/2098] tests/extmod/vfs_lfs_error.py: Test value of all OSError's errno. To make sure they have the correct value. Signed-off-by: Damien George --- tests/extmod/vfs_lfs_error.py | 46 +++++++++++++++---------------- tests/extmod/vfs_lfs_error.py.exp | 44 ++++++++++++++--------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/extmod/vfs_lfs_error.py b/tests/extmod/vfs_lfs_error.py index 2ac7629bfa8..73cdf343733 100644 --- a/tests/extmod/vfs_lfs_error.py +++ b/tests/extmod/vfs_lfs_error.py @@ -1,7 +1,7 @@ # Test for VfsLittle using a RAM device, testing error handling try: - import vfs + import errno, vfs vfs.VfsLfs1 vfs.VfsLfs2 @@ -41,14 +41,14 @@ def test(bdev, vfs_class): # mkfs with too-small block device try: vfs_class.mkfs(RAMBlockDevice(1)) - except OSError: - print("mkfs OSError") + except OSError as er: + print("mkfs OSError", er.errno > 0) # mount with invalid filesystem try: vfs_class(bdev) - except OSError: - print("mount OSError") + except OSError as er: + print("mount OSError", er.errno > 0) # set up for following tests vfs_class.mkfs(bdev) @@ -60,60 +60,60 @@ def test(bdev, vfs_class): # ilistdir try: fs.ilistdir("noexist") - except OSError: - print("ilistdir OSError") + except OSError as er: + print("ilistdir OSError", er) # remove try: fs.remove("noexist") - except OSError: - print("remove OSError") + except OSError as er: + print("remove OSError", er) # rmdir try: fs.rmdir("noexist") - except OSError: - print("rmdir OSError") + except OSError as er: + print("rmdir OSError", er) # rename try: fs.rename("noexist", "somethingelse") - except OSError: - print("rename OSError") + except OSError as er: + print("rename OSError", er) # mkdir try: fs.mkdir("testdir") - except OSError: - print("mkdir OSError") + except OSError as er: + print("mkdir OSError", er) # chdir to nonexistent try: fs.chdir("noexist") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # chdir to file try: fs.chdir("testfile") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # stat try: fs.stat("noexist") - except OSError: - print("stat OSError") + except OSError as er: + print("stat OSError", er) # error during seek with fs.open("testfile", "r") as f: f.seek(1 << 30) # SEEK_SET try: f.seek(1 << 30, 1) # SEEK_CUR - except OSError: - print("seek OSError") + except OSError as er: + print("seek OSError", er) bdev = RAMBlockDevice(30) diff --git a/tests/extmod/vfs_lfs_error.py.exp b/tests/extmod/vfs_lfs_error.py.exp index f4327f6962e..440607ed84b 100644 --- a/tests/extmod/vfs_lfs_error.py.exp +++ b/tests/extmod/vfs_lfs_error.py.exp @@ -1,28 +1,28 @@ test -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL test -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL From e22c666d0623e6273d748f787a84d3108a57f1c3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 19:17:50 +0200 Subject: [PATCH 0652/2098] tests/cpydiff: Explain the numeric literal parsing difference. Fixes issue #17224. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_spaces.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index 03d25d56199..86faa5981ce 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,8 +1,15 @@ """ categories: Syntax,Spaces -description: uPy requires spaces between literal numbers and keywords, CPy doesn't -cause: Unknown -workaround: Unknown +description: MicroPython requires spaces between literal numbers and keywords, CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer treats a sequence like ``1and`` as a single token, while CPython treats it as two tokens. + +Since CPython 3.11, this syntax causes a ``SyntaxWarning`` for an "invalid literal". + +workaround: Add a space between the integer literal and the intended next token. + +This also fixes the ``SyntaxWarning`` in CPython. """ try: From 2f97d1dd28a7e2d34df268676e533944410aab42 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 21:22:33 +0200 Subject: [PATCH 0653/2098] tests/cpydiff: Document that uPy requires space after number+period. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_spaces.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index 86faa5981ce..e7d00838cb6 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,11 +1,11 @@ """ categories: Syntax,Spaces -description: MicroPython requires spaces between literal numbers and keywords, CPython doesn't +description: MicroPython requires spaces between literal numbers and keywords or ".", CPython doesn't cause: Different parser implementation MicroPython's tokenizer treats a sequence like ``1and`` as a single token, while CPython treats it as two tokens. -Since CPython 3.11, this syntax causes a ``SyntaxWarning`` for an "invalid literal". +Since CPython 3.11, when the literal number is followed by a token, this syntax causes a ``SyntaxWarning`` for an "invalid literal". When a literal number is followed by a "." denoting attribute access, CPython does not warn. workaround: Add a space between the integer literal and the intended next token. @@ -24,3 +24,7 @@ print(eval("1if 1else 0")) except SyntaxError: print("Should have worked") +try: + print(eval("0x1.to_bytes(1)")) +except SyntaxError: + print("Should have worked") From 605eda158de4f40ba03e15fa183a178f7cf1a1da Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 21:26:26 +0200 Subject: [PATCH 0654/2098] tools/gen-cpydiff.py: Improve stdout vs stderr interleaving. In the syntax_space cpydiff, all the warnings were shown after the other output. This is because the output always showed all of stdout first and all of stdout second. By running Python in unbuffered mode and using `stderr=STDOUT`, the two streams are interleaved in exactly the order they're printed, so the SyntaxWarnings are interleaved with the other output. By using the `encoding=` argument of Popen, the need to explicitly convert to utf-8 is avoided. The encoding of the input also becomes utf-8 in this case, which all the test cases are (well, they're all ASCII, I think). As in `run-tests.py`, setting PYTHONIOENCODING ensures the Python interpreter's input and output are in utf-8, which is not always the case, especially on Windows systems. I spot-checked the generated doc pages and they all seemed to make sense still. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 2f9394deeae..744cde8d7b5 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -45,6 +45,12 @@ CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/build-standard/micropython") +# Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale +os.environ["PYTHONIOENCODING"] = "utf-8" + +# Set PYTHONUNBUFFERED so that CPython will interleave stdout & stderr without buffering +os.environ["PYTHONUNBUFFERED"] = "a non-empty string" + TESTPATH = "../tests/cpydiff" DOCPATH = "../docs/genrst" SRCDIR = "../docs/differences" @@ -111,7 +117,7 @@ def run_tests(tests): results = [] for test in tests: test_fullpath = os.path.join(TESTPATH, test.name) - with open(test_fullpath, "rb") as f: + with open(test_fullpath, "r") as f: input_py = f.read() process = subprocess.Popen( @@ -119,20 +125,22 @@ def run_tests(tests): shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", ) - output_cpy = [com.decode("utf8") for com in process.communicate(input_py)] + output_cpy = process.communicate(input_py)[0] process = subprocess.Popen( MICROPYTHON, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", ) - output_upy = [com.decode("utf8") for com in process.communicate(input_py)] + output_upy = process.communicate(input_py)[0] - if output_cpy[0] == output_upy[0] and output_cpy[1] == output_upy[1]: + if output_cpy == output_upy: print("Error: Test has same output in CPython vs MicroPython: " + test_fullpath) same_results = True else: @@ -246,9 +254,9 @@ def gen_rst(results): rst.write("**Workaround:** " + output.workaround + "\n\n") rst.write("Sample code::\n\n" + indent(output.code, TAB) + "\n") - output_cpy = indent("".join(output.output_cpy[0:2]), TAB).rstrip() + output_cpy = indent(output.output_cpy, TAB).rstrip() output_cpy = ("::\n\n" if output_cpy != "" else "") + output_cpy - output_upy = indent("".join(output.output_upy[0:2]), TAB).rstrip() + output_upy = indent(output.output_upy, TAB).rstrip() output_upy = ("::\n\n" if output_upy != "" else "") + output_upy table = gen_table([["CPy output:", output_cpy], ["uPy output:", output_upy]]) rst.write(table) From 8648e6d1cf19afec1485ed204b2135ca322c6a8b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:04:29 +0200 Subject: [PATCH 0655/2098] tests/cpydiff: Add test of underscore-in-literals. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_literal_underscore.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cpydiff/syntax_literal_underscore.py diff --git a/tests/cpydiff/syntax_literal_underscore.py b/tests/cpydiff/syntax_literal_underscore.py new file mode 100644 index 00000000000..4b1406e9f3f --- /dev/null +++ b/tests/cpydiff/syntax_literal_underscore.py @@ -0,0 +1,19 @@ +""" +categories: Syntax,Literals +description: MicroPython accepts underscores in numeric literals where CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer ignores underscores in numeric literals, while CPython +rejects multiple consecutive underscores and underscores after the last digit. + +workaround: Remove the underscores not accepted by CPython. +""" + +try: + print(eval("1__1")) +except SyntaxError: + print("Should not work") +try: + print(eval("1_")) +except SyntaxError: + print("Should not work") From a19d3f742e84eb72fd8fb2a0f4e38822392ea88e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:08:23 +0200 Subject: [PATCH 0656/2098] tools/gen-cpydiff.py: Fix RST heading generation. The heading character for the difference title was always "~", but items had been added which had just a single heading level. This made the generated table of contents confused about heading levels, because heading levels are not fixed in rst, but are inferred from the order they appear in the document. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 744cde8d7b5..278023a4bdd 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -228,8 +228,8 @@ def gen_rst(results): filename = section[i].replace(" ", "_").lower() rst = open(os.path.join(DOCPATH, filename + ".rst"), "w") rst.write(HEADER) - rst.write(section[i] + "\n") - rst.write(RSTCHARS[0] * len(section[i])) + rst.write(section[0] + "\n") + rst.write(RSTCHARS[0] * len(section[0]) + "\n\n") rst.write(time.strftime("\nGenerated %a %d %b %Y %X UTC\n\n", time.gmtime())) # If a file docs/differences/_preamble.txt exists # then its output is inserted after the top-level heading, @@ -247,7 +247,7 @@ def gen_rst(results): class_ = section rst.write(".. _cpydiff_%s:\n\n" % os.path.splitext(output.name)[0]) rst.write(output.desc + "\n") - rst.write("~" * len(output.desc) + "\n\n") + rst.write(RSTCHARS[min(i + 1, len(RSTCHARS) - 1)] * len(output.desc) + "\n\n") if output.cause != "Unknown": rst.write("**Cause:** " + output.cause + "\n\n") if output.workaround != "Unknown": From ea19f3b7358eba713d6bcea5d5fd141312cf2e9d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:08:35 +0200 Subject: [PATCH 0657/2098] tests/cpydiff: Ensure all have two levels of category. This improves the TOC display of the generated differences section. Signed-off-by: Jeff Epler --- tests/cpydiff/core_fstring_concat.py | 2 +- tests/cpydiff/core_fstring_parser.py | 2 +- tests/cpydiff/core_fstring_repr.py | 2 +- tests/cpydiff/syntax_arg_unpacking.py | 2 +- tests/cpydiff/syntax_spaces.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/cpydiff/core_fstring_concat.py b/tests/cpydiff/core_fstring_concat.py index 3daa13d7536..2fbe1b961a1 100644 --- a/tests/cpydiff/core_fstring_concat.py +++ b/tests/cpydiff/core_fstring_concat.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support concatenation with adjacent literals if the adjacent literals contain braces cause: MicroPython is optimised for code space. workaround: Use the + operator between literal strings when they are not both f-strings diff --git a/tests/cpydiff/core_fstring_parser.py b/tests/cpydiff/core_fstring_parser.py index 22bbc5866ec..570b92434a9 100644 --- a/tests/cpydiff/core_fstring_parser.py +++ b/tests/cpydiff/core_fstring_parser.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings cannot support expressions that require parsing to resolve unbalanced nested braces and brackets cause: MicroPython is optimised for code space. workaround: Always use balanced braces and brackets in expressions inside f-strings diff --git a/tests/cpydiff/core_fstring_repr.py b/tests/cpydiff/core_fstring_repr.py index d37fb48db75..2589a34b7e3 100644 --- a/tests/cpydiff/core_fstring_repr.py +++ b/tests/cpydiff/core_fstring_repr.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support !a conversions cause: MicropPython does not implement ascii() workaround: None diff --git a/tests/cpydiff/syntax_arg_unpacking.py b/tests/cpydiff/syntax_arg_unpacking.py index e54832ddb91..7133a8a2827 100644 --- a/tests/cpydiff/syntax_arg_unpacking.py +++ b/tests/cpydiff/syntax_arg_unpacking.py @@ -1,5 +1,5 @@ """ -categories: Syntax +categories: Syntax,Unpacking description: Argument unpacking does not work if the argument being unpacked is the nth or greater argument where n is the number of bits in an MP_SMALL_INT. cause: The implementation uses an MP_SMALL_INT to flag args that need to be unpacked. workaround: Use fewer arguments. diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index e7d00838cb6..670cefdeac2 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,5 +1,5 @@ """ -categories: Syntax,Spaces +categories: Syntax,Literals description: MicroPython requires spaces between literal numbers and keywords or ".", CPython doesn't cause: Different parser implementation From 6d74b4e3c15d0b677247ce02c459095d3cf03fc5 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 10 May 2025 08:31:27 +0200 Subject: [PATCH 0658/2098] tools/gen-cpydiff.py: Ensure every item has at least 2 TOC levels. Previously, the navigation ended up messy when the (long) description of the item became used as a 2nd level header, meaning that it was placed in the navigation. Check for this when generating cpydiff so that new cases don't sneak in unnoticed. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 278023a4bdd..3bb928090bb 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -219,6 +219,8 @@ def gen_rst(results): class_ = [] for output in results: section = output.class_.split(",") + if len(section) < 2: + raise SystemExit("Each item must have at least 2 categories") for i in range(len(section)): section[i] = section[i].rstrip() if section[i] in CLASSMAP: From c310301f27e351e0dc9f966556cab512b3c82615 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 16 Dec 2024 14:44:37 +0200 Subject: [PATCH 0659/2098] docs/esp32: Improve PWM documentation and examples. This reduces inconsitencies between esp32 and other ports. According to the discussion in #10817. Signed-off-by: Ihor Nehrutsa --- docs/esp32/quickref.rst | 35 ++++-- docs/esp32/tutorial/pwm.rst | 220 ++++++++++++++++++++++++++++------- docs/library/machine.PWM.rst | 14 +-- 3 files changed, 209 insertions(+), 60 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index ccc01099d17..707ae27c08c 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -393,7 +393,7 @@ Use the :ref:`machine.PWM ` class:: pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%) duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535 - pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%) + pwm0.duty_u16(65536*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%) duty_ns = pwm0.duty_ns() # get current pulse width in ns pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%) @@ -402,19 +402,32 @@ Use the :ref:`machine.PWM ` class:: pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go print(pwm2) # view PWM settings + pwm2.deinit() # turn off PWM on the pin + + pwm0 = PWM(Pin(0), duty_u16=16384) # The output is at a high level 25% of the time. + pwm2 = PWM(Pin(2), duty_u16=16384, invert=1) # The output is at a low level 25% of the time. + + pwm4 = PWM(Pin(4), lightsleep=True) # Allow PWM during light sleep mode ESP chips have different hardware peripherals: -===================================================== ======== ======== ======== -Hardware specification ESP32 ESP32-S2 ESP32-C3 ------------------------------------------------------ -------- -------- -------- -Number of groups (speed modes) 2 1 1 -Number of timers per group 4 4 4 -Number of channels per group 8 8 6 ------------------------------------------------------ -------- -------- -------- -Different PWM frequencies (groups * timers) 8 4 4 -Total PWM channels (Pins, duties) (groups * channels) 16 8 6 -===================================================== ======== ======== ======== +======================================================= ======== ========= ========== +Hardware specification ESP32 ESP32-S2, ESP32-C2, + ESP32-S3, ESP32-C3, + ESP32-P4 ESP32-C5, + ESP32-C6, + ESP32-H2 +------------------------------------------------------- -------- --------- ---------- +Number of groups (speed modes) 2 1 1 +Number of timers per group 4 4 4 +Number of channels per group 8 8 6 +------------------------------------------------------- -------- --------- ---------- +Different PWM frequencies = (groups * timers) 8 4 4 +Total PWM channels (Pins, duties) = (groups * channels) 16 8 6 +======================================================= ======== ========= ========== + +In light sleep, the ESP32 PWM can only operate in low speed mode, so only 4 timers and +8 channels are available. A maximum number of PWM channels (Pins) are available on the ESP32 - 16 channels, but only 8 different PWM frequencies are available, the remaining 8 channels must diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst index 2650284d35f..82d43b36f6c 100644 --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -11,16 +11,20 @@ compared with the length of a single period (low plus high time). Maximum duty cycle is when the pin is high all of the time, and minimum is when it is low all of the time. -* More comprehensive example with all 16 PWM channels and 8 timers:: +* More comprehensive example with all **16 PWM channels and 8 timers**:: + from time import sleep from machine import Pin, PWM try: - f = 100 # Hz - d = 1024 // 16 # 6.25% - pins = (15, 2, 4, 16, 18, 19, 22, 23, 25, 26, 27, 14 , 12, 13, 32, 33) + F = 10000 # Hz + D = 65536 // 16 # 6.25% + pins = (2, 4, 12, 13, 14, 15, 16, 18, 19, 22, 23, 25, 26, 27, 32, 33) pwms = [] for i, pin in enumerate(pins): - pwms.append(PWM(Pin(pin), freq=f * (i // 2 + 1), duty= 1023 if i==15 else d * (i + 1))) + f = F * (i // 2 + 1) + d = min(65535, D * (i + 1)) + pwms.append(PWM(pin, freq=f, duty_u16=d)) + sleep(2 / f) print(pwms[i]) finally: for pwm in pwms: @@ -31,65 +35,100 @@ low all of the time. Output is:: - PWM(Pin(15), freq=100, duty=64, resolution=10, mode=0, channel=0, timer=0) - PWM(Pin(2), freq=100, duty=128, resolution=10, mode=0, channel=1, timer=0) - PWM(Pin(4), freq=200, duty=192, resolution=10, mode=0, channel=2, timer=1) - PWM(Pin(16), freq=200, duty=256, resolution=10, mode=0, channel=3, timer=1) - PWM(Pin(18), freq=300, duty=320, resolution=10, mode=0, channel=4, timer=2) - PWM(Pin(19), freq=300, duty=384, resolution=10, mode=0, channel=5, timer=2) - PWM(Pin(22), freq=400, duty=448, resolution=10, mode=0, channel=6, timer=3) - PWM(Pin(23), freq=400, duty=512, resolution=10, mode=0, channel=7, timer=3) - PWM(Pin(25), freq=500, duty=576, resolution=10, mode=1, channel=0, timer=0) - PWM(Pin(26), freq=500, duty=640, resolution=10, mode=1, channel=1, timer=0) - PWM(Pin(27), freq=600, duty=704, resolution=10, mode=1, channel=2, timer=1) - PWM(Pin(14), freq=600, duty=768, resolution=10, mode=1, channel=3, timer=1) - PWM(Pin(12), freq=700, duty=832, resolution=10, mode=1, channel=4, timer=2) - PWM(Pin(13), freq=700, duty=896, resolution=10, mode=1, channel=5, timer=2) - PWM(Pin(32), freq=800, duty=960, resolution=10, mode=1, channel=6, timer=3) - PWM(Pin(33), freq=800, duty=1023, resolution=10, mode=1, channel=7, timer=3) - -* Example of a smooth frequency change:: + PWM(Pin(2), freq=10000, duty_u16=4096) + PWM(Pin(4), freq=10000, duty_u16=8192) + PWM(Pin(12), freq=20000, duty_u16=12288) + PWM(Pin(13), freq=20000, duty_u16=16384) + PWM(Pin(14), freq=30030, duty_u16=20480) + PWM(Pin(15), freq=30030, duty_u16=24576) + PWM(Pin(16), freq=40000, duty_u16=28672) + PWM(Pin(18), freq=40000, duty_u16=32768) + PWM(Pin(19), freq=50000, duty_u16=36864) + PWM(Pin(22), freq=50000, duty_u16=40960) + PWM(Pin(23), freq=60060, duty_u16=45056) + PWM(Pin(25), freq=60060, duty_u16=49152) + PWM(Pin(26), freq=69930, duty_u16=53248) + PWM(Pin(27), freq=69930, duty_u16=57344) + PWM(Pin(32), freq=80000, duty_u16=61440) + PWM(Pin(33), freq=80000, duty_u16=65535) + + +* Example of a **smooth frequency change**:: from time import sleep from machine import Pin, PWM - F_MIN = 500 - F_MAX = 1000 + F_MIN = 1000 + F_MAX = 10000 f = F_MIN - delta_f = 1 + delta_f = F_MAX // 50 - p = PWM(Pin(5), f) - print(p) + pwm = PWM(Pin(27), f) while True: - p.freq(f) - - sleep(10 / F_MIN) + pwm.freq(f) + sleep(1 / f) + sleep(0.1) + print(pwm) f += delta_f - if f >= F_MAX or f <= F_MIN: + if f > F_MAX or f < F_MIN: delta_f = -delta_f + print() + if f > F_MAX: + f = F_MAX + elif f < F_MIN: + f = F_MIN - See PWM wave at Pin(5) with an oscilloscope. + See PWM wave on Pin(27) with an oscilloscope. + + Output is:: -* Example of a smooth duty change:: + PWM(Pin(27), freq=998, duty_u16=32768) + PWM(Pin(27), freq=1202, duty_u16=32768) + PWM(Pin(27), freq=1401, duty_u16=32768) + PWM(Pin(27), freq=1598, duty_u16=32768) + ... + PWM(Pin(27), freq=9398, duty_u16=32768) + PWM(Pin(27), freq=9615, duty_u16=32768) + PWM(Pin(27), freq=9804, duty_u16=32768) + PWM(Pin(27), freq=10000, duty_u16=32768) + + PWM(Pin(27), freq=10000, duty_u16=32768) + PWM(Pin(27), freq=9804, duty_u16=32768) + PWM(Pin(27), freq=9615, duty_u16=32768) + PWM(Pin(27), freq=9398, duty_u16=32768) + ... + PWM(Pin(27), freq=1598, duty_u16=32768) + PWM(Pin(27), freq=1401, duty_u16=32768) + PWM(Pin(27), freq=1202, duty_u16=32768) + PWM(Pin(27), freq=998, duty_u16=32768) + + +* Example of a **smooth duty change**:: from time import sleep from machine import Pin, PWM - DUTY_MAX = 2**16 - 1 + DUTY_MAX = 65535 duty_u16 = 0 - delta_d = 16 + delta_d = 256 - p = PWM(Pin(5), 1000, duty_u16=duty_u16) - print(p) + pwm = PWM(Pin(27), freq=1000, duty_u16=duty_u16) while True: - p.duty_u16(duty_u16) + pwm.duty_u16(duty_u16) + sleep(2 / pwm.freq()) + print(pwm) - sleep(1 / 1000) + if duty_u16 >= DUTY_MAX: + print() + sleep(2) + elif duty_u16 <= 0: + print() + sleep(2) duty_u16 += delta_d if duty_u16 >= DUTY_MAX: @@ -99,9 +138,106 @@ low all of the time. duty_u16 = 0 delta_d = -delta_d - See PWM wave at Pin(5) with an oscilloscope. + PWM wave on Pin(27) with an oscilloscope. + + Output is:: + + PWM(Pin(27), freq=998, duty_u16=0) + PWM(Pin(27), freq=998, duty_u16=256) + PWM(Pin(27), freq=998, duty_u16=512) + PWM(Pin(27), freq=998, duty_u16=768) + PWM(Pin(27), freq=998, duty_u16=1024) + ... + PWM(Pin(27), freq=998, duty_u16=64512) + PWM(Pin(27), freq=998, duty_u16=64768) + PWM(Pin(27), freq=998, duty_u16=65024) + PWM(Pin(27), freq=998, duty_u16=65280) + PWM(Pin(27), freq=998, duty_u16=65535) + + PWM(Pin(27), freq=998, duty_u16=65279) + PWM(Pin(27), freq=998, duty_u16=65023) + PWM(Pin(27), freq=998, duty_u16=64767) + PWM(Pin(27), freq=998, duty_u16=64511) + ... + PWM(Pin(27), freq=998, duty_u16=1023) + PWM(Pin(27), freq=998, duty_u16=767) + PWM(Pin(27), freq=998, duty_u16=511) + PWM(Pin(27), freq=998, duty_u16=255) + PWM(Pin(27), freq=998, duty_u16=0) + + +* Example of a **smooth duty change and PWM output inversion**:: + + from utime import sleep + from machine import Pin, PWM + + try: + DUTY_MAX = 65535 + + duty_u16 = 0 + delta_d = 65536 // 32 + + pwm = PWM(Pin(27)) + pwmi = PWM(Pin(32), invert=1) + + while True: + pwm.duty_u16(duty_u16) + pwmi.duty_u16(duty_u16) + + duty_u16 += delta_d + if duty_u16 >= DUTY_MAX: + duty_u16 = DUTY_MAX + delta_d = -delta_d + elif duty_u16 <= 0: + duty_u16 = 0 + delta_d = -delta_d + + sleep(.01) + print(pwm) + print(pwmi) + + finally: + try: + pwm.deinit() + except: + pass + try: + pwmi.deinit() + except: + pass + + Output is:: + + PWM(Pin(27), freq=5000, duty_u16=0) + PWM(Pin(32), freq=5000, duty_u16=32768, invert=1) + PWM(Pin(27), freq=5000, duty_u16=2048) + PWM(Pin(32), freq=5000, duty_u16=2048, invert=1) + PWM(Pin(27), freq=5000, duty_u16=4096) + PWM(Pin(32), freq=5000, duty_u16=4096, invert=1) + PWM(Pin(27), freq=5000, duty_u16=6144) + PWM(Pin(32), freq=5000, duty_u16=6144, invert=1) + PWM(Pin(27), freq=5000, duty_u16=8192) + PWM(Pin(32), freq=5000, duty_u16=8192, invert=1) + ... + + + See PWM waves on Pin(27) and Pin(32) with an oscilloscope. + +Note: New PWM parameters take effect in the next PWM cycle. + + pwm = PWM(2, duty=512) + print(pwm) + >>> PWM(Pin(2), freq=5000, duty=1023) # the duty is not relevant + pwm.init(freq=2, duty=64) + print(pwm) + >>> PWM(Pin(2), freq=2, duty=16) # the duty is not relevant + time.sleep(1 / 2) # wait one PWM period + print(pwm) + >>> PWM(Pin(2), freq=2, duty=64) # the duty is actual + +Note: machine.freq(20_000_000) reduces the highest PWM frequency to 10 MHz. -Note: the Pin.OUT mode does not need to be specified. The channel is initialized +Note: the Pin.OUT mode does not need to be specified. The channel is initialized to PWM mode internally once for each Pin that is passed to the PWM constructor. The following code is wrong:: diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst index 5f592b8dff5..c2b606affd6 100644 --- a/docs/library/machine.PWM.rst +++ b/docs/library/machine.PWM.rst @@ -11,20 +11,20 @@ Example usage:: from machine import PWM pwm = PWM(pin, freq=50, duty_u16=8192) # create a PWM object on a pin - # and set freq and duty - pwm.duty_u16(32768) # set duty to 50% + # and set freq 50 Hz and duty 12.5% + pwm.duty_u16(32768) # set duty to 50% # reinitialise with a period of 200us, duty of 5us pwm.init(freq=5000, duty_ns=5000) - pwm.duty_ns(3000) # set pulse width to 3us + pwm.duty_ns(3000) # set pulse width to 3us pwm.deinit() Constructors ------------ -.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert) +.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert=False) Construct and return a new PWM object using the following parameters: @@ -40,7 +40,7 @@ Constructors Setting *freq* may affect other PWM objects if the objects share the same underlying PWM generator (this is hardware specific). Only one of *duty_u16* and *duty_ns* should be specified at a time. - *invert* is not available at all ports. + *invert* is available only on the esp32, mimxrt, nrf, rp2, samd and zephyr ports. Methods ------- @@ -116,10 +116,10 @@ Limitations of PWM resolution of 8 bit, not 16-bit as may be expected. In this case, the lowest 8 bits of *duty_u16* are insignificant. So:: - pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2) + pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2) and:: - pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2 + 255) + pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2 + 255) will generate PWM with the same 50% duty cycle. From 150a5aa3a102fb0f1f0388275e7e59d669bb79e5 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 19 Feb 2025 09:21:01 +0200 Subject: [PATCH 0660/2098] esp32/machine_pwm: Improve PWM and make its API match other ports. This reduce inconsistencies between esp32 PWM and other ports: 1. duty_u16() high value is 2**16-1 == 65535 2. Invert PWM wave with invert=1 parameter 3. Enable PWM in light sleep mode 4. Allow PWM output and read pulse input simultaneously on the same Pin() 5. Code refactoring Co-Authored-By: Angus Gratton Co-Authored-By: robert-hh Co-Authored-By: Andrew Leech Co-Authored-By: Yoann Darche Signed-off-by: Ihor Nehrutsa --- ports/esp32/machine_pwm.c | 974 +++++++++++++++++++++----------------- 1 file changed, 538 insertions(+), 436 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 7826769556c..7cb84640eec 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -6,7 +6,8 @@ * Copyright (c) 2016-2021 Damien P. George * Copyright (c) 2018 Alan Dragomirecky * Copyright (c) 2020 Antoine Aubert - * Copyright (c) 2021 Ihor Nehrutsa + * Copyright (c) 2021, 2023-2025 Ihor Nehrutsa + * Copyright (c) 2024 Yoann Darche * * 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,79 +33,41 @@ #include #include "py/mphal.h" -#include "driver/ledc.h" #include "esp_err.h" +#include "driver/ledc.h" +#include "soc/ledc_periph.h" #include "soc/gpio_sig_map.h" - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) #include "esp_clk_tree.h" -#endif - -#define PWM_DBG(...) -// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); - -// Total number of channels -#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) - -typedef struct _chan_t { - // Which channel has which GPIO pin assigned? - // (-1 if not assigned) - gpio_num_t pin; - // Which channel has which timer assigned? - // (-1 if not assigned) - int timer_idx; -} chan_t; +#include "py/mpprint.h" -// List of PWM channels -static chan_t chans[PWM_CHANNEL_MAX]; +#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__); -// channel_idx is an index (end-to-end sequential numbering) for all channels -// available on the chip and described in chans[] -#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) -#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) -#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) +#define FADE 0 -// Total number of timers -#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) +// 10-bit user interface resolution compatible with esp8266 PWM.duty() +#define UI_RES_10_BIT (10) +#define DUTY_10 UI_RES_10_BIT +// Maximum duty value on 10-bit resolution is 1024 but reduced to 1023 in UI +#define MAX_10_DUTY (1U << UI_RES_10_BIT) -// List of timer configs -static ledc_timer_config_t timers[PWM_TIMER_MAX]; +// 16-bit user interface resolution used in PWM.duty_u16() +#define UI_RES_16_BIT (16) +#define DUTY_16 UI_RES_16_BIT +// Maximum duty value on 16-bit resolution is 65536 but reduced to 65535 in UI +#define MAX_16_DUTY (1U << UI_RES_16_BIT) -// timer_idx is an index (end-to-end sequential numbering) for all timers -// available on the chip and configured in timers[] -#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) -#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) -#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) +// ns user interface used in PWM.duty_ns() +#define DUTY_NS (1) -// Params for PWM operation // 5khz is default frequency -#define PWM_FREQ (5000) - -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) - -// Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) -// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions -// duty() uses 10-bit resolution or less -// duty_u16() and duty_ns() use 16-bit resolution or less - -// Possible highest resolution in device -#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT -#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) -#else -#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used -#endif -// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer -#define UI_RES_16_BIT (16) -// Maximum duty value on highest user interface resolution -#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1) -// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT -#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 - -#if SOC_LEDC_SUPPORT_REF_TICK +#define PWM_FREQ (5000) +// default duty 50% +#define PWM_DUTY ((1U << UI_RES_16_BIT) / 2) + +// All chips except esp32 and esp32s2 do not have timer-specific clock sources, which means clock source for all timers must be the same one. +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 // If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used -#define EMPIRIC_FREQ (10) // Hz +#define EMPIRIC_FREQ (10) // Hz #endif // Config of timer upon which we run all PWM'ed GPIO pins @@ -113,490 +76,675 @@ static bool pwm_inited = false; // MicroPython PWM object struct typedef struct _machine_pwm_obj_t { mp_obj_base_t base; - gpio_num_t pin; - bool active; - int mode; - int channel; - int timer; - int duty_x; // PWM_RES_10_BIT if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() - int duty_u10; // stored values from previous duty setters - int duty_u16; // - / - - int duty_ns; // - / - + int8_t pin; + int8_t mode; + int8_t channel; + int8_t timer; + bool lightsleep; + int32_t freq; + int8_t duty_scale; // DUTY_10 if duty(), DUTY_16 if duty_u16(), DUTY_NS if duty_ns() + int duty_ui; // saved values of UI duty + int channel_duty; // saved values of UI duty, calculated to raw channel->duty + bool output_invert; + bool output_invert_prev; } machine_pwm_obj_t; -static bool is_timer_in_use(int current_channel_idx, int timer_idx); -static void set_duty_u16(machine_pwm_obj_t *self, int duty); -static void set_duty_u10(machine_pwm_obj_t *self, int duty); -static void set_duty_ns(machine_pwm_obj_t *self, int ns); +typedef struct _chans_t { + int8_t pin; // Which channel has which GPIO pin assigned? (-1 if not assigned) + int8_t timer; // Which channel has which timer assigned? (-1 if not assigned) + bool lightsleep; // Is light sleep enable has been set for this pin +} chans_t; + +// List of PWM channels +static chans_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; + +typedef struct _timers_t { + int32_t freq; + int8_t duty_resolution; + ledc_clk_cfg_t clk_cfg; +} timers_t; + +// List of PWM timers +static timers_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; + +// register-unregister channel +static void register_channel(int mode, int channel, int pin, int timer) { + chans[mode][channel].pin = pin; + chans[mode][channel].timer = timer; +} + +static void unregister_channel(int mode, int channel) { + register_channel(mode, channel, -1, -1); + chans[mode][channel].lightsleep = false; +} + +static void unregister_timer(int mode, int timer) { + timers[mode][timer].freq = -1; // unused timer freq is -1 + timers[mode][timer].duty_resolution = 0; + timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; +} static void pwm_init(void) { - // Initial condition: no channels assigned - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - chans[i].pin = -1; - chans[i].timer_idx = -1; + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + // Initial condition: no channels assigned + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + unregister_channel(mode, channel); + } + // Initial condition: no timers assigned + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + unregister_timer(mode, timer); + } } +} - // Prepare all timers config - // Initial condition: no timers assigned - for (int i = 0; i < PWM_TIMER_MAX; ++i) { - timers[i].duty_resolution = HIGHEST_PWM_RES; - // unset timer is -1 - timers[i].freq_hz = -1; - timers[i].speed_mode = TIMER_IDX_TO_MODE(i); - timers[i].timer_num = TIMER_IDX_TO_TIMER(i); - timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ +// Returns true if the timer is in use in addition to current channel +static bool is_timer_in_use(int mode, int current_channel, int timer) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((channel != current_channel) && (chans[mode][channel].timer == timer) && (chans[mode][channel].pin >= 0)) { + return true; + } } + return false; } // Deinit channel and timer if the timer is unused -static void pwm_deinit(int channel_idx) { - // Valid channel? - if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) { +static void pwm_deinit(int mode, int channel, int level) { + // Is valid channel? + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { // Clean up timer if necessary - int timer_idx = chans[channel_idx].timer_idx; - if (timer_idx != -1) { - if (!is_timer_in_use(channel_idx, timer_idx)) { - check_esp_err(ledc_timer_pause(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); - ledc_timer_config_t timer_config = { + int timer = chans[mode][channel].timer; + if (timer >= 0) { + if (!is_timer_in_use(mode, channel, timer)) { + check_esp_err(ledc_timer_pause(mode, timer)); + ledc_timer_config_t ledc_timer = { .deconfigure = true, - .speed_mode = TIMER_IDX_TO_MODE(timer_idx), - .timer_num = TIMER_IDX_TO_TIMER(timer_idx), + .speed_mode = mode, + .timer_num = timer, }; - check_esp_err(ledc_timer_config(&timer_config)); - // Flag it unused - timers[chans[channel_idx].timer_idx].freq_hz = -1; + ledc_timer_config(&ledc_timer); + unregister_timer(mode, timer); } } - int pin = chans[channel_idx].pin; - if (pin != -1) { - int mode = CHANNEL_IDX_TO_MODE(channel_idx); - int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - // Mark it unused, and tell the hardware to stop routing - check_esp_err(ledc_stop(mode, channel, 0)); - // Disable ledc signal for the pin - // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); - if (mode == LEDC_LOW_SPEED_MODE) { - esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); - } else { - #if LEDC_SPEED_MODE_MAX > 1 - #if CONFIG_IDF_TARGET_ESP32 - esp_rom_gpio_connect_out_signal(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); - #else - #error Add supported CONFIG_IDF_TARGET_ESP32_xxx - #endif - #endif + int pin = chans[mode][channel].pin; + if (pin >= 0) { + // Disable LEDC output, and set idle level + check_esp_err(ledc_stop(mode, channel, level)); + if (chans[mode][channel].lightsleep) { + // Enable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_en(pin)); + chans[mode][channel].lightsleep = false; } } - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; + unregister_channel(mode, channel); } } // This called from Ctrl-D soft reboot void machine_pwm_deinit_all(void) { if (pwm_inited) { - for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - pwm_deinit(channel_idx); + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + pwm_deinit(mode, channel, 0); + } } + #if FADE + ledc_fade_func_uninstall(); + #endif pwm_inited = false; } } -static void configure_channel(machine_pwm_obj_t *self) { - ledc_channel_config_t cfg = { - .channel = self->channel, - .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, - .gpio_num = self->pin, - .intr_type = LEDC_INTR_DISABLE, - .speed_mode = self->mode, - .timer_sel = self->timer, - }; - if (ledc_channel_config(&cfg) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); - } -} - -static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { - esp_err_t err; - if (freq != timer->freq_hz) { - // Configure the new frequency and resolution - timer->freq_hz = freq; - - #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK - timer->clk_cfg = LEDC_USE_PLL_DIV_CLK; - #elif SOC_LEDC_SUPPORT_APB_CLOCK - timer->clk_cfg = LEDC_USE_APB_CLK; - #elif SOC_LEDC_SUPPORT_XTAL_CLOCK - timer->clk_cfg = LEDC_USE_XTAL_CLK; - #else - #error No supported PWM / LEDC clocks. - #endif - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - timer->clk_cfg = LEDC_USE_REF_TICK; - } - #endif - uint32_t src_clk_freq = 0; - err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); - if (err != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); - } - - timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - - // Set frequency - err = ledc_timer_config(timer); - if (err != ESP_OK) { - if (err == ESP_FAIL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); - } else { - check_esp_err(err); - } - } - // Reset the timer if low speed - if (self->mode == LEDC_LOW_SPEED_MODE) { - check_esp_err(ledc_timer_rst(self->mode, self->timer)); - } - } - - // Save the same duty cycle when frequency is changed - if (self->duty_x == HIGHEST_PWM_RES) { - set_duty_u16(self, self->duty_u16); - } else if (self->duty_x == PWM_RES_10_BIT) { - set_duty_u10(self, self->duty_u10); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - set_duty_ns(self, self->duty_ns); +static void pwm_is_active(machine_pwm_obj_t *self) { + if (self->timer < 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM is inactive")); } } // Calculate the duty parameters based on an ns value static int ns_to_duty(machine_pwm_obj_t *self, int ns) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL; + pwm_is_active(self); + int64_t duty = ((int64_t)ns * (int64_t)MAX_16_DUTY * self->freq + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; + } else if (duty > MAX_16_DUTY) { + duty = MAX_16_DUTY; } return duty; } static int duty_to_ns(machine_pwm_obj_t *self, int duty) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY); - return ns; + pwm_is_active(self); + return ((int64_t)duty * 1000000000LL + (int64_t)self->freq * (int64_t)(MAX_16_DUTY / 2)) / ((int64_t)self->freq * (int64_t)MAX_16_DUTY); } -#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) +// Reconfigure PWM pin output as input/output. +static void reconfigure_pin(machine_pwm_obj_t *self) { + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 0) + // This allows to read the pin level. + gpio_set_direction(self->pin, GPIO_MODE_INPUT_OUTPUT); + #endif + esp_rom_gpio_connect_out_signal(self->pin, ledc_periph_signal[self->mode].sig_out0_idx + self->channel, self->output_invert, 0); +} -static void pwm_is_active(machine_pwm_obj_t *self) { - if (self->active == false) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM inactive")); +static void apply_duty(machine_pwm_obj_t *self) { + pwm_is_active(self); + + int duty = 0; + if (self->duty_scale == DUTY_16) { + duty = self->duty_ui; + } else if (self->duty_scale == DUTY_10) { + duty = self->duty_ui << (UI_RES_16_BIT - UI_RES_10_BIT); + } else if (self->duty_scale == DUTY_NS) { + duty = ns_to_duty(self, self->duty_ui); + } + self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); + if ((chans[self->mode][self->channel].pin == -1) || (self->output_invert_prev != self->output_invert)) { + self->output_invert_prev = self->output_invert; + // New PWM assignment + ledc_channel_config_t cfg = { + .channel = self->channel, + .duty = self->channel_duty, + .gpio_num = self->pin, + .intr_type = LEDC_INTR_DISABLE, + .speed_mode = self->mode, + .timer_sel = self->timer, + .hpoint = 0, + .flags.output_invert = self->output_invert, + }; + check_esp_err(ledc_channel_config(&cfg)); + reconfigure_pin(self); + } else { + #if FADE + check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, self->channel_duty, 0)); + #else + check_esp_err(ledc_set_duty(self->mode, self->channel, self->channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + #endif + } + if (self->lightsleep) { + // Disable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_dis(self->pin)); + chans[self->mode][self->channel].lightsleep = true; } + register_channel(self->mode, self->channel, self->pin, self->timer); +} + +static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + if (resolution > UI_RES_16_BIT) { + // limit resolution to user interface + resolution = UI_RES_16_BIT; + } + // Note: On ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C2, ESP32C6, ESP32H2, ESP32P4, due to a hardware bug, + // 100% duty cycle (i.e. 2**duty_res) is not reachable when the binded timer selects the maximum duty + // resolution. For example, the max duty resolution on ESP32C3 is 14-bit width, then set duty to (2**14) + // will mess up the duty calculation in hardware. + // Reduce the resolution from 14 to 13 bits to resolve the hardware bug. + if (resolution >= SOC_LEDC_TIMER_BIT_WIDTH) { + resolution -= 1; + } + return resolution; } static uint32_t get_duty_u16(machine_pwm_obj_t *self) { pwm_is_active(self); - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - int duty = ledc_get_duty(self->mode, self->channel); - if (resolution <= UI_RES_16_BIT) { - duty <<= (UI_RES_16_BIT - resolution); + int duty = ledc_get_duty(self->mode, self->channel) << (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); + if (duty != MAX_16_DUTY) { + return duty; } else { - duty >>= (resolution - UI_RES_16_BIT); + return MAX_16_DUTY - 1; } - return duty; } static uint32_t get_duty_u10(machine_pwm_obj_t *self) { - pwm_is_active(self); - return get_duty_u16(self) >> 6; // Scale down from 16 bit to 10 bit resolution + // Scale down from 16 bit to 10 bit resolution + return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); } static uint32_t get_duty_ns(machine_pwm_obj_t *self) { - pwm_is_active(self); return duty_to_ns(self, get_duty_u16(self)); } -static void set_duty_u16(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > UI_MAX_DUTY)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); +static void check_duty_u16(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_16_DUTY - 1)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_16_DUTY); } - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int channel_duty; - if (timer.duty_resolution <= UI_RES_16_BIT) { - channel_duty = duty >> (UI_RES_16_BIT - timer.duty_resolution); - } else { - channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); + if (duty == MAX_16_DUTY - 1) { + duty = MAX_16_DUTY; } - int max_duty = (1 << timer.duty_resolution) - 1; - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; - } - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); + self->duty_scale = DUTY_16; + self->duty_ui = duty; +} - /* - // Bug: Sometimes duty is not set right now. - // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. - // Bug: It has been experimentally established that the duty is set during 2 signal periods, but 1 period is expected. - // See https://github.com/espressif/esp-idf/issues/7288 - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - esp_rom_delay_us(2 * 1000000 / timer.freq_hz); - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - } - } - */ +static void set_duty_u16(machine_pwm_obj_t *self, int duty) { + check_duty_u16(self, duty); + apply_duty(self); +} - self->duty_x = HIGHEST_PWM_RES; - self->duty_u16 = duty; +static void check_duty_u10(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_10_DUTY - 1)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_10_DUTY - 1); + } + if (duty == MAX_10_DUTY - 1) { + duty = MAX_10_DUTY; + } + self->duty_scale = DUTY_10; + self->duty_ui = duty; } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > MAX_DUTY_U10)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); + check_duty_u10(self, duty); + apply_duty(self); +} + +static void check_duty_ns(machine_pwm_obj_t *self, int ns) { + if ((ns < 0) || (ns > duty_to_ns(self, MAX_16_DUTY))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_16_DUTY)); } - set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); - self->duty_x = PWM_RES_10_BIT; - self->duty_u10 = duty; + self->duty_scale = DUTY_NS; + self->duty_ui = ns; } static void set_duty_ns(machine_pwm_obj_t *self, int ns) { - pwm_is_active(self); - if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); + check_duty_ns(self, ns); + apply_duty(self); +} + +#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) +// This check if a clock is already set in the timer list, if yes, return the LEDC_XXX value +static ledc_clk_cfg_t find_clock_in_use() { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) { + return timers[mode][timer].clk_cfg; + } + } } - set_duty_u16(self, ns_to_duty(self, ns)); - self->duty_x = -HIGHEST_PWM_RES; - self->duty_ns = ns; + return LEDC_AUTO_CLK; } -/******************************************************************************/ +// Check if a timer is already set with a different clock source +static bool is_timer_with_different_clock(int mode, int current_timer, ledc_clk_cfg_t clk_cfg) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if ((timer != current_timer) && (clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != clk_cfg)) { + return true; + } + } + return false; +} +#endif -#define SAME_FREQ_ONLY (true) -#define SAME_FREQ_OR_FREE (false) -#define ANY_MODE (-1) +static void check_freq_ranges(machine_pwm_obj_t *self, int freq, int upper_freq) { + if ((freq <= 0) || (freq > upper_freq)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("frequency must be from 1Hz to %dMHz"), upper_freq / 1000000); + } +} -// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer -static int find_timer(unsigned int freq, bool same_freq_only, int mode) { - int free_timer_idx_found = -1; - // Find a free PWM Timer using the same freq - for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) { - if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) { - if (timers[timer_idx].freq_hz == freq) { - // A timer already uses the same freq. Use it now. - return timer_idx; - } - if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) { - free_timer_idx_found = timer_idx; - // Continue to check if a channel with the same freq is in use. +// Set timer frequency +static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { + self->freq = freq; + if ((timers[self->mode][self->timer].freq != freq) || (self->lightsleep)) { + ledc_timer_config_t timer = {}; + timer.speed_mode = self->mode; + timer.timer_num = self->timer; + timer.freq_hz = freq; + timer.deconfigure = false; + + timer.clk_cfg = LEDC_AUTO_CLK; + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + ledc_clk_cfg_t clk_cfg = find_clock_in_use(); + if (clk_cfg != LEDC_AUTO_CLK) { + timer.clk_cfg = clk_cfg; + } else + #endif + if (self->lightsleep) { + timer.clk_cfg = LEDC_USE_RC_FAST_CLK; // 8 or 20 MHz + } else { + #if SOC_LEDC_SUPPORT_APB_CLOCK + timer.clk_cfg = LEDC_USE_APB_CLK; // 80 MHz + #elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + timer.clk_cfg = LEDC_USE_PLL_DIV_CLK; // 60 or 80 or 96 MHz + #elif SOC_LEDC_SUPPORT_XTAL_CLOCK + timer.clk_cfg = LEDC_USE_XTAL_CLK; // 40 MHz + #else + #error No supported PWM / LEDC clocks. + #endif + + #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only + if (freq < EMPIRIC_FREQ) { + timer.clk_cfg = LEDC_USE_REF_TICK; // 1 MHz } + #endif } - } + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + // Check for clock source conflict + clk_cfg = find_clock_in_use(); + if ((clk_cfg != LEDC_AUTO_CLK) && (clk_cfg != timer.clk_cfg)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC.")); + } + #endif - return free_timer_idx_found; + uint32_t src_clk_freq = 0; + check_esp_err(esp_clk_tree_src_get_freq_hz(timer.clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &src_clk_freq)); + // machine.freq(20_000_000) reduces APB_CLK_FREQ to 20MHz and the highest PWM frequency to 10MHz + check_freq_ranges(self, freq, src_clk_freq / 2); + + // Configure the new resolution + timer.duty_resolution = find_suitable_duty_resolution(src_clk_freq, self->freq); + // Configure timer - Set frequency + if ((timers[self->mode][self->timer].duty_resolution == timer.duty_resolution) && (timers[self->mode][self->timer].clk_cfg == timer.clk_cfg)) { + check_esp_err(ledc_set_freq(self->mode, self->timer, freq)); + } else { + check_esp_err(ledc_timer_config(&timer)); + } + // Reset the timer if low speed + if (self->mode == LEDC_LOW_SPEED_MODE) { + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + } + timers[self->mode][self->timer].freq = freq; + timers[self->mode][self->timer].duty_resolution = timer.duty_resolution; + timers[self->mode][self->timer].clk_cfg = timer.clk_cfg; + } } -// Return true if the timer is in use in addition to current channel -static bool is_timer_in_use(int current_channel_idx, int timer_idx) { - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { +static bool is_free_channels(int mode, int pin) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((chans[mode][channel].pin < 0) || (chans[mode][channel].pin == pin)) { return true; } } + return false; +} +static bool is_free_timers(int mode, int32_t freq) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if ((timers[mode][timer].freq < 0) || (timers[mode][timer].freq == freq)) { + return true; + } + } return false; } -// Find a free PWM channel, also spot if our pin is already mentioned. -// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel -static int find_channel(int pin, int mode) { - int avail_idx = -1; - int channel_idx; - for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) { - if (chans[channel_idx].pin == pin) { - break; +// Find self channel or free channel +static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channel, int32_t freq) { + // Try to find self channel first + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin == self->pin) { + *ret_mode = mode; + *ret_channel = channel; + return; } - if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { - avail_idx = channel_idx; + } + } + // Find free channel + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((chans[mode][channel].pin < 0) && is_free_timers(mode, freq)) { + *ret_mode = mode; + *ret_channel = channel; + return; } } } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; +} + +// Returns timer with the same frequency, freq == -1 means free timer +static void find_timer(machine_pwm_obj_t *self, int freq, int *ret_mode, int *ret_timer) { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + if (is_free_channels(mode, self->pin)) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].freq == freq) { + *ret_mode = mode; + *ret_timer = timer; + return; + } + } + } } - return channel_idx; } -/******************************************************************************/ +// Try to find a timer with the same frequency in the current mode, otherwise in the next mode. +// If no existing timer and channel was found, then try to find free timer in any mode. +// If the mode or channel is changed, release the channel and register a new channel in the next mode. +static void select_timer(machine_pwm_obj_t *self, int freq) { + // mode, channel, timer may be -1(not defined) or actual values + int mode = -1; + int timer = -1; + // Check if an already running timer with the required frequency is running + find_timer(self, freq, &mode, &timer); + if (timer < 0) { + // Try to reuse self timer + if ((self->mode >= 0) && (self->channel >= 0)) { + if (!is_timer_in_use(self->mode, self->channel, self->timer)) { + mode = self->mode; + timer = self->timer; + } + } + // If no existing timer and channel was found, then try to find free timer in any mode + if (timer < 0) { + find_timer(self, -1, &mode, &timer); + } + } + if (timer < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM timers:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_TIMER_MAX : LEDC_SPEED_MODE_MAX *LEDC_TIMER_MAX); + } + // If the timer is found, then register + if (self->timer != timer) { + unregister_channel(self->mode, self->channel); + // Rregister the channel to the timer + self->mode = mode; + self->timer = timer; + register_channel(self->mode, self->channel, -1, self->timer); + } + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + if (is_timer_with_different_clock(self->mode, self->timer, timers[self->mode][self->timer].clk_cfg)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC.")); + } + #endif +} + +static void set_freq_duty(machine_pwm_obj_t *self, unsigned int freq) { + select_timer(self, freq); + set_freq(self, freq); + apply_duty(self); +} + +// ****************************************************************************** // MicroPython bindings for PWM static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "PWM(Pin(%u)", self->pin); - if (self->active) { + if (self->timer >= 0) { mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); - - if (self->duty_x == PWM_RES_10_BIT) { + if (self->duty_scale == DUTY_10) { mp_printf(print, ", duty=%d", get_duty_u10(self)); - } else if (self->duty_x == -HIGHEST_PWM_RES) { + } else if (self->duty_scale == DUTY_NS) { mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); } else { mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); } - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - mp_printf(print, ", resolution=%d", resolution); - - mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents - - mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); + if (self->output_invert) { + mp_printf(print, ", invert=True"); + } + if (self->lightsleep) { + mp_printf(print, ", lightsleep=True"); + } + mp_printf(print, ")"); + + #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + mp_printf(print, " # duty=%.2f%%", 100.0 * get_duty_u16(self) / MAX_16_DUTY); + mp_printf(print, ", raw_duty=%d, resolution=%d", ledc_get_duty(self->mode, self->channel), timers[self->mode][self->timer].duty_resolution); + mp_printf(print, ", mode=%d, timer=%d, channel=%d", self->mode, self->timer, self->channel); + int clk_cfg = timers[self->mode][self->timer].clk_cfg; + mp_printf(print, ", clk_cfg=%d=", clk_cfg); + if (clk_cfg == LEDC_USE_RC_FAST_CLK) { + mp_printf(print, "RC_FAST_CLK"); + } + #if SOC_LEDC_SUPPORT_APB_CLOCK + else if (clk_cfg == LEDC_USE_APB_CLK) { + mp_printf(print, "APB_CLK"); + } + #endif + #if SOC_LEDC_SUPPORT_XTAL_CLOCK + else if (clk_cfg == LEDC_USE_XTAL_CLK) { + mp_printf(print, "XTAL_CLK"); + } + #endif + #if SOC_LEDC_SUPPORT_REF_TICK + else if (clk_cfg == LEDC_USE_REF_TICK) { + mp_printf(print, "REF_TICK"); + } + #endif + #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + else if (clk_cfg == LEDC_USE_PLL_DIV_CLK) { + mp_printf(print, "PLL_CLK"); + } + #endif + else if (clk_cfg == LEDC_AUTO_CLK) { + mp_printf(print, "AUTO_CLK"); + } else { + mp_printf(print, "UNKNOWN"); + } + #endif + } else { + mp_printf(print, ")"); } - mp_printf(print, ")"); } // This called from pwm.init() method +// +// Check the current mode. +// If the frequency is changed, try to find a timer with the same frequency +// in the current mode, otherwise in the new mode. +// If the mode is changed, release the channel and select a new channel in the new mode. +// Then set the frequency with the same duty. static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; - static const mp_arg_t allowed_args[] = { + + enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_lightsleep }; + mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->output_invert} }, + { MP_QSTR_lightsleep, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->lightsleep} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - int channel_idx = find_channel(self->pin, ANY_MODE); - if (channel_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes + self->lightsleep = args[ARG_lightsleep].u_bool; + + int freq = args[ARG_freq].u_int; + if (freq != -1) { + check_freq_ranges(self, freq, 40000000); } int duty = args[ARG_duty].u_int; int duty_u16 = args[ARG_duty_u16].u_int; int duty_ns = args[ARG_duty_ns].u_int; - if (((duty != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) { - mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed")); + if (duty_u16 >= 0) { + check_duty_u16(self, duty_u16); + } else if (duty_ns >= 0) { + check_duty_ns(self, duty_ns); + } else if (duty >= 0) { + check_duty_u10(self, duty); + } else if (self->duty_scale == 0) { + self->duty_scale = DUTY_16; + self->duty_ui = PWM_DUTY; + } + + self->output_invert = args[ARG_invert].u_bool; + + // Check the current mode and channel + int mode = -1; + int channel = -1; + find_channel(self, &mode, &channel, freq); + if (channel < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM channels:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_CHANNEL_MAX : LEDC_SPEED_MODE_MAX *LEDC_CHANNEL_MAX); } + self->mode = mode; + self->channel = channel; - int freq = args[ARG_freq].u_int; // Check if freq wasn't passed as an argument - if (freq == -1) { + if ((freq == -1) && (mode >= 0) && (channel >= 0)) { // Check if already set, otherwise use the default freq. // It is possible in case: // pwm = PWM(pin, freq=1000, duty=256) // pwm = PWM(pin, duty=128) - if (chans[channel_idx].timer_idx != -1) { - freq = timers[chans[channel_idx].timer_idx].freq_hz; + if (chans[mode][channel].timer >= 0) { + freq = timers[mode][chans[mode][channel].timer].freq; } if (freq <= 0) { freq = PWM_FREQ; } } - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - - int timer_idx; - int current_timer_idx = chans[channel_idx].timer_idx; - bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx); - if (current_in_use) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx)); - } else { - timer_idx = chans[channel_idx].timer_idx; - } - - if (timer_idx == -1) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE); - } - if (timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes - } - - int mode = TIMER_IDX_TO_MODE(timer_idx); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - // unregister old channel - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; - // find new channel - channel_idx = find_channel(self->pin, mode); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode - } - } - self->mode = mode; - self->timer = TIMER_IDX_TO_TIMER(timer_idx); - self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - - // New PWM assignment - if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { - configure_channel(self); - chans[channel_idx].pin = self->pin; - } - chans[channel_idx].timer_idx = timer_idx; - self->active = true; - - // Set timer frequency - set_freq(self, freq, &timers[timer_idx]); + set_freq_duty(self, freq); +} - // Set duty cycle? - if (duty_u16 != -1) { - set_duty_u16(self, duty_u16); - } else if (duty_ns != -1) { - set_duty_ns(self, duty_ns); - } else if (duty != -1) { - set_duty_u10(self, duty); - } else if (self->duty_x == 0) { - set_duty_u10(self, (1 << PWM_RES_10_BIT) / 2); // 50% - } +static void self_reset(machine_pwm_obj_t *self) { + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->freq = -1; + self->duty_scale = 0; + self->duty_ui = 0; + self->channel_duty = -1; + self->output_invert = false; + self->output_invert_prev = false; + self->lightsleep = false; } // This called from PWM() constructor static mp_obj_t mp_machine_pwm_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, 2, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - - // create PWM object from the given pin - machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); - self->pin = pin_id; - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; // start the PWM subsystem if it's not already running if (!pwm_inited) { pwm_init(); + #if FADE + ledc_fade_func_install(0); + #endif pwm_inited = true; } - // start the PWM running for this channel + // create PWM object from the given pin + machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self_reset(self); + + self->pin = machine_pin_get_id(args[0]); + + // Process the remaining parameters. mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); - return MP_OBJ_FROM_PTR(self); } // This called from pwm.deinit() method static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { - int channel_idx = CHANNEL_IDX(self->mode, self->channel); - pwm_deinit(channel_idx); - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; + pwm_deinit(self->mode, self->channel, self->output_invert); + self_reset(self); } // Set and get methods of PWM class @@ -608,57 +756,11 @@ static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { pwm_is_active(self); - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { + check_freq_ranges(self, freq, 40000000); + if (freq == timers[self->mode][self->timer].freq) { return; } - - int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx; - bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx); - - // Check if an already running timer with the same freq is running - int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode); - - // If no existing timer was found, and the current one is in use, then find a new one - if ((new_timer_idx == -1) && current_in_use) { - // Have to find a new timer - new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode); - - if (new_timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode - } - } - - if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) { - // Bind the channel to the new timer - chans[self->channel].timer_idx = new_timer_idx; - - if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel")); - } - - if (!current_in_use) { - // Free the old timer - check_esp_err(ledc_timer_pause(self->mode, self->timer)); - ledc_timer_config_t timer_config = { - .deconfigure = true, - .speed_mode = self->mode, - .timer_num = self->timer, - }; - check_esp_err(ledc_timer_config(&timer_config)); - // Flag it unused - timers[current_timer_idx].freq_hz = -1; - } - - current_timer_idx = new_timer_idx; - } - self->mode = TIMER_IDX_TO_MODE(current_timer_idx); - self->timer = TIMER_IDX_TO_TIMER(current_timer_idx); - - // Set the frequency - set_freq(self, freq, &timers[current_timer_idx]); + set_freq_duty(self, freq); } static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { From a72454519362dd1de5eff9e2eb268e6ea1040a40 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 19 Feb 2025 09:23:04 +0200 Subject: [PATCH 0661/2098] esp32/mpconfigport: Document how to get more debug info. Signed-off-by: Ihor Nehrutsa --- ports/esp32/mpconfigport.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7c260095789..35c8ff1831d 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -62,7 +62,8 @@ #define MICROPY_STACK_CHECK_MARGIN (1024) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) -#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) // Debugging Note: Increase the error reporting level to view + // __FUNCTION__, __LINE__, __FILE__ in check_esp_err() exceptions #define MICROPY_WARNINGS (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_STREAMS_POSIX_API (1) From 9d565182d757e7a9bdf95bcb2b18fe2d6fa4111a Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 13 Mar 2025 13:46:46 +0200 Subject: [PATCH 0662/2098] docs/esp32/quickref: Add PWM lightsleep example. Signed-off-by: Ihor Nehrutsa --- docs/esp32/quickref.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 707ae27c08c..56d8b0e4f6b 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -383,7 +383,7 @@ for more details. Use the :ref:`machine.PWM ` class:: - from machine import Pin, PWM + from machine import Pin, PWM, lightsleep pwm0 = PWM(Pin(0), freq=5000, duty_u16=32768) # create PWM object from a pin freq = pwm0.freq() # get current frequency @@ -409,6 +409,9 @@ Use the :ref:`machine.PWM ` class:: pwm4 = PWM(Pin(4), lightsleep=True) # Allow PWM during light sleep mode + lightsleep(10*1000) # pwm0, pwm2 goes off, pwm4 stays on during 10s light sleep + # pwm0, pwm2, pwm4 on after 10s light sleep + ESP chips have different hardware peripherals: ======================================================= ======== ========= ========== From e1ab04e8205f78bb3ed9d0368bc1bb013b1bdcf9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 May 2025 13:18:33 +1000 Subject: [PATCH 0663/2098] esp32/mpthreadport: Fix double delete of tasks on soft reset. Python threads (created via the `_thread` module) are backed by a FreeRTOS task. Managing the deletion of the task can be tricky, and there are currently some bugs with this in the esp32 port. The actual crash seen was in FreeRTOS' `uxListRemove()`, and that's because of two calls to `vTaskDelete()` for the same task: one in `freertos_entry()` when the task ran to completion, and the other in `mp_thread_deinit()`. The latter tried to delete the task a second time because it was still in the linked list, because `vTaskPreDeletionHook()` had not yet been called. And the reason `vTaskPreDeletionHook()` was yet to be called is because the FreeRTOS idle task was starved. This commit fixes that. There are three things done by this commit: - remove the `vTaskPreDeletionHook`, it's not needed anymore because task stack memory is allocated by the IDF, not on the MicroPython heap - when a task finishes it now removes itself from the linked list, just before it deletes itself - on soft reset, all tasks are deleted and removed from the linked list in one swoop (while the mutex is held) Signed-off-by: Damien George --- ports/esp32/boards/sdkconfig.base | 1 - ports/esp32/mpthreadport.c | 96 ++++++++++++------------------- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 530db427119..5595444e86f 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -50,7 +50,6 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n -CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y # UDP CONFIG_LWIP_PPP_SUPPORT=y diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index 962b5780d08..67917e33e51 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -41,10 +41,16 @@ #define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + MICROPY_STACK_CHECK_MARGIN) #define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) +typedef enum { + MP_THREAD_RUN_STATE_NEW, + MP_THREAD_RUN_STATE_RUNNING, + MP_THREAD_RUN_STATE_FINISHED, +} mp_thread_run_state_t; + // this structure forms a linked list, one node per active thread typedef struct _mp_thread_t { TaskHandle_t id; // system id of thread - int ready; // whether the thread is ready and running + mp_thread_run_state_t run_state; // current run state of the thread void *arg; // thread Python args, a GC root pointer void *stack; // pointer to the stack size_t stack_len; // number of words in the stack @@ -60,18 +66,16 @@ void mp_thread_init(void *stack, uint32_t stack_len) { mp_thread_set_state(&mp_state_ctx.thread); // create the first entry in the linked list of all threads thread_entry0.id = xTaskGetCurrentTaskHandle(); - thread_entry0.ready = 1; + thread_entry0.run_state = MP_THREAD_RUN_STATE_RUNNING; thread_entry0.arg = NULL; thread_entry0.stack = stack; thread_entry0.stack_len = stack_len; thread_entry0.next = NULL; + thread = &thread_entry0; mp_thread_mutex_init(&thread_mutex); // memory barrier to ensure above data is committed __sync_synchronize(); - - // vTaskPreDeletionHook needs the thread ready after thread_mutex is ready - thread = &thread_entry0; } void mp_thread_gc_others(void) { @@ -82,7 +86,7 @@ void mp_thread_gc_others(void) { if (th->id == xTaskGetCurrentTaskHandle()) { continue; } - if (!th->ready) { + if (th->run_state != MP_THREAD_RUN_STATE_RUNNING) { continue; } gc_collect_root(th->stack, th->stack_len); @@ -106,7 +110,7 @@ void mp_thread_start(void) { mp_thread_mutex_lock(&thread_mutex, 1); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 1; + th->run_state = MP_THREAD_RUN_STATE_RUNNING; break; } } @@ -116,12 +120,22 @@ void mp_thread_start(void) { static void *(*ext_thread_entry)(void *) = NULL; static void freertos_entry(void *arg) { + // Run the Python code. if (ext_thread_entry) { ext_thread_entry(arg); } - vTaskDelete(NULL); - for (;;) {; + + // Remove the thread from the linked-list of active threads. + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t **th = &thread; *th != NULL; th = &(*th)->next) { + if ((*th)->id == xTaskGetCurrentTaskHandle()) { + *th = (*th)->next; + } } + mp_thread_mutex_unlock(&thread_mutex); + + // Delete this FreeRTOS task (this call to vTaskDelete will not return). + vTaskDelete(NULL); } mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, int priority, char *name) { @@ -147,7 +161,7 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s } // add thread to linked list of all threads - th->ready = 0; + th->run_state = MP_THREAD_RUN_STATE_NEW; th->arg = arg; th->stack = pxTaskGetStackStart(th->id); th->stack_len = *stack_size / sizeof(uintptr_t); @@ -167,33 +181,7 @@ void mp_thread_finish(void) { mp_thread_mutex_lock(&thread_mutex, 1); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 0; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); -} - -// This is called either from vTaskDelete() or from the FreeRTOS idle task, so -// may not be within Python context. Therefore MP_STATE_THREAD may not be valid -// and it does not have the GIL. -void vTaskPreDeletionHook(void *tcb) { - if (thread == NULL) { - // threading not yet initialised - return; - } - mp_thread_t *prev = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; prev = th, th = th->next) { - // unlink the node from the list - if ((void *)th->id == tcb) { - if (prev != NULL) { - prev->next = th->next; - } else { - // move the start pointer - thread = th->next; - } - // The "th" memory will eventually be reclaimed by the GC. + th->run_state = MP_THREAD_RUN_STATE_FINISHED; break; } } @@ -221,32 +209,22 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { } void mp_thread_deinit(void) { - for (;;) { - // Find a task to delete - TaskHandle_t id = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - // Don't delete the current task - if (th->id != xTaskGetCurrentTaskHandle()) { - id = th->id; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); + // The current task should be thread_entry0 and should be the last in the linked list. + assert(thread_entry0.id == xTaskGetCurrentTaskHandle()); + assert(thread_entry0.next == NULL); - if (id == NULL) { - // No tasks left to delete - break; - } else { - // Call FreeRTOS to delete the task (it will call vTaskPreDeletionHook) - vTaskDelete(id); + // Delete all tasks except the main one. + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + if (th != &thread_entry0) { + vTaskDelete(th->id); } } -} - -#else + thread = &thread_entry0; + mp_thread_mutex_unlock(&thread_mutex); -void vTaskPreDeletionHook(void *tcb) { + // Give the idle task a chance to run, to clean up any deleted tasks. + vTaskDelay(1); } #endif // MICROPY_PY_THREAD From f48b9815671eb81e99339dc443fb20dca1c72e69 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Sun, 16 Feb 2025 12:58:50 +0200 Subject: [PATCH 0664/2098] esp32: Update to use ESP-IDF v5.4.1. This version of the IDF uses about 1KB more IRAM and 1KB more DRAM on most boards, but 6.5KB more DRAM usage on the S3. It seems that's due to a lot of small increases in many components. Signed-off-by: Ihor Nehrutsa --- ports/esp32/README.md | 8 ++++---- ports/esp32/tools/metrics_esp32.py | 2 +- tools/ci.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index d9115072a45..e11c64ad707 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -31,7 +31,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.2, v5.2.2, v5.3 and v5.4. +Currently MicroPython supports v5.2, v5.2.2, v5.3, v5.4 and v5.4.1. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). @@ -49,10 +49,10 @@ The steps to take are summarised below. To check out a copy of the IDF use git clone: ```bash -$ git clone -b v5.2.2 --recursive https://github.com/espressif/esp-idf.git +$ git clone -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git ``` -You can replace `v5.2.2` with any other supported version. +You can replace `v5.4.1` with any other supported version. (You don't need a full recursive clone; see the `ci_esp32_setup` function in `tools/ci.sh` in this repository for more detailed set-up commands.) @@ -61,7 +61,7 @@ MicroPython and update the submodules using: ```bash $ cd esp-idf -$ git checkout v5.2.2 +$ git checkout v5.4.1 $ git submodule update --init --recursive ``` diff --git a/ports/esp32/tools/metrics_esp32.py b/ports/esp32/tools/metrics_esp32.py index 5e65a78c97a..9efaae63a9b 100755 --- a/ports/esp32/tools/metrics_esp32.py +++ b/ports/esp32/tools/metrics_esp32.py @@ -37,7 +37,7 @@ import subprocess from dataclasses import dataclass -IDF_VERS = ("v5.2.2",) +IDF_VERS = ("v5.4.1",) BUILDS = ( ("ESP32_GENERIC", ""), diff --git a/tools/ci.sh b/tools/ci.sh index a4bd43567c8..d12b4bcd316 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -159,7 +159,7 @@ function ci_cc3200_build { # ports/esp32 # GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) -IDF_VER=v5.2.2 +IDF_VER=v5.4.1 PYTHON=$(command -v python3 2> /dev/null) PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) From 10f6c0699e52857a4f7350a5d3a9561862c98a8d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 10:42:55 +1100 Subject: [PATCH 0665/2098] esp32/network_lan: Add support for LAN8670 PHY. This adds support for LAN8670 to the esp32 port. Enabled conditionally for the esp32 target, if ESP-IDF version is new enough (v5.3 or newer). Fixes issue #15731. Signed-off-by: Damien George Signed-off-by: Angus Gratton --- ports/esp32/main/idf_component.yml | 5 +++++ ports/esp32/modnetwork.h | 17 ++++++++++++++++- ports/esp32/modnetwork_globals.h | 3 +++ ports/esp32/network_lan.c | 12 ++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index ccbde1f27e4..f7773f4f4e4 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -5,5 +5,10 @@ dependencies: rules: - if: "target in [esp32s2, esp32s3]" version: "~1.0.0" + espressif/lan867x: + version: "~1.0.0" + rules: + - if: "target == esp32" + - if: "idf_version >=5.3" idf: version: ">=5.2.0" diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 383e200b983..4debea1114b 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -28,7 +28,22 @@ #include "esp_netif.h" -enum { PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; +// lan867x component requires newer IDF version +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && CONFIG_IDF_TARGET_ESP32 +#define PHY_LAN867X_ENABLED (1) +#else +#define PHY_LAN867X_ENABLED (0) +#endif + +enum { + // PHYs supported by the internal Ethernet MAC: + PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, + #if PHY_LAN867X_ENABLED + PHY_LAN8670, + #endif + // PHYs which are actually SPI Ethernet MAC+PHY chips: + PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 +}; #define IS_SPI_PHY(NUM) (NUM >= 100) enum { ETH_INITIALIZED, ETH_STARTED, ETH_STOPPED, ETH_CONNECTED, ETH_DISCONNECTED, ETH_GOT_IP }; diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 9e355e958f1..9909e7c056a 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -47,6 +47,9 @@ { MP_ROM_QSTR(MP_QSTR_PHY_DP83848), MP_ROM_INT(PHY_DP83848) }, { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8041), MP_ROM_INT(PHY_KSZ8041) }, { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8081), MP_ROM_INT(PHY_KSZ8081) }, +#if PHY_LAN867X_ENABLED +{ MP_ROM_QSTR(MP_QSTR_PHY_LAN8670), MP_ROM_INT(PHY_LAN8670) }, +#endif #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index e1a8c957857..bf6b565e8cd 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -45,6 +45,10 @@ #include "modnetwork.h" #include "extmod/modnetwork.h" +#if PHY_LAN867X_ENABLED +#include "esp_eth_phy_lan867x.h" +#endif + typedef struct _lan_if_obj_t { base_if_obj_t base; bool initialized; @@ -156,6 +160,9 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar args[ARG_phy_type].u_int != PHY_RTL8201 && args[ARG_phy_type].u_int != PHY_KSZ8041 && args[ARG_phy_type].u_int != PHY_KSZ8081 && + #if PHY_LAN867X_ENABLED + args[ARG_phy_type].u_int != PHY_LAN8670 && + #endif #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL args[ARG_phy_type].u_int != PHY_KSZ8851SNL && @@ -231,7 +238,12 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar case PHY_KSZ8081: self->phy = esp_eth_phy_new_ksz80xx(&phy_config); break; + #if PHY_LAN867X_ENABLED + case PHY_LAN8670: + self->phy = esp_eth_phy_new_lan867x(&phy_config); + break; #endif + #endif // CONFIG_IDF_TARGET_ESP32 #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL case PHY_KSZ8851SNL: { From 06d8c084b90cb6e56ab9be0de040dcae439ce261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 1 Mar 2022 22:29:58 +0100 Subject: [PATCH 0666/2098] esp32/modesp32: Implement esp32.idf_task_info(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new function, `esp32.idf_task_info()`, that can be used to retrieve task statistics which is useful for diagnosing issues where some tasks are using up a lot of CPU time. It's best used in conjunction with the `utop` module from micropython-lib. Signed-off-by: Daniël van de Giessen --- docs/library/esp32.rst | 23 +++++++++++++++++++++ ports/esp32/modesp32.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dc35e7905e1..24831c58d6d 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -80,6 +80,29 @@ Functions The result of :func:`gc.mem_free()` is the total of the current "free" and "max new split" values printed by :func:`micropython.mem_info()`. +.. function:: idf_task_info() + + Returns information about running ESP-IDF/FreeRTOS tasks, which include + MicroPython threads. This data is useful to gain insight into how much time + tasks spend running or if they are blocked for significant parts of time, + and to determine if allocated stacks are fully utilized or might be reduced. + + ``CONFIG_FREERTOS_USE_TRACE_FACILITY=y`` must be set in the board + configuration to make this method available. Additionally configuring + ``CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y`` and + ``CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y`` is recommended to be able to + retrieve the total and per-task runtime and the core ID respectively. + + The return value is a 2-tuple where the first value is the total runtime, + and the second a list of tasks. Each task is a 7-tuple containing: the task + ID, name, current state, priority, runtime, stack high water mark, and the + ID of the core it is running on. Runtime and core ID will be None when the + respective FreeRTOS configuration option is not enabled. + + .. note:: For an easier to use output based on this function you can use the + `utop library `_, + which implements a live overview similar to the Unix ``top`` command. + Flash partitions ---------------- diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 164b1918261..0296ddf10e7 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -212,6 +212,49 @@ static mp_obj_t esp32_idf_heap_info(const mp_obj_t cap_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_idf_heap_info_obj, esp32_idf_heap_info); +#if CONFIG_FREERTOS_USE_TRACE_FACILITY +static mp_obj_t esp32_idf_task_info(void) { + const size_t task_count_max = uxTaskGetNumberOfTasks(); + TaskStatus_t *task_array = m_new(TaskStatus_t, task_count_max); + uint32_t total_time; + const size_t task_count = uxTaskGetSystemState(task_array, task_count_max, &total_time); + + mp_obj_list_t *task_list = MP_OBJ_TO_PTR(mp_obj_new_list(task_count, NULL)); + for (size_t i = 0; i < task_count; i++) { + mp_obj_t task_data[] = { + mp_obj_new_int_from_uint((mp_uint_t)task_array[i].xHandle), + mp_obj_new_str(task_array[i].pcTaskName, strlen(task_array[i].pcTaskName)), + MP_OBJ_NEW_SMALL_INT(task_array[i].eCurrentState), + MP_OBJ_NEW_SMALL_INT(task_array[i].uxCurrentPriority), + #if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + mp_obj_new_int_from_uint(task_array[i].ulRunTimeCounter), + #else + mp_const_none, + #endif + mp_obj_new_int_from_uint(task_array[i].usStackHighWaterMark), + #if CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID + MP_OBJ_NEW_SMALL_INT(task_array[i].xCoreID), + #else + mp_const_none, + #endif + }; + task_list->items[i] = mp_obj_new_tuple(7, task_data); + } + + m_del(TaskStatus_t, task_array, task_count_max); + mp_obj_t task_stats[] = { + #if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + MP_OBJ_NEW_SMALL_INT(total_time), + #else + mp_const_none, + #endif + task_list + }; + return mp_obj_new_tuple(2, task_stats); +} +static MP_DEFINE_CONST_FUN_OBJ_0(esp32_idf_task_info_obj, esp32_idf_task_info); +#endif + static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp32) }, @@ -228,6 +271,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mcu_temperature), MP_ROM_PTR(&esp32_mcu_temperature_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, + #if CONFIG_FREERTOS_USE_TRACE_FACILITY + { MP_ROM_QSTR(MP_QSTR_idf_task_info), MP_ROM_PTR(&esp32_idf_task_info_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, From 90aeac800a0ac861c64dda4b99db1a3f0ed91cea Mon Sep 17 00:00:00 2001 From: Rick Sorensen Date: Wed, 16 Apr 2025 09:28:39 -0500 Subject: [PATCH 0667/2098] esp32/machine_i2c: Fix default I2C pins for C3, S3. The default I2C init does not require setting SCL or SDA but the default I2C0 pins for C3, S3 conflict with the espressif GPIO usage. For the C3, pins 18/19 are for USB/JTAG. If used for I2C() they will cause the REPL to hang on initialization of the I2C. For the S3 pin 19 is allocated for USB/JTAG also but the defaults do not seem to affect the REPL. See related #16956. Fixes issue #17103. Signed-off-by: Rick Sorensen Signed-off-by: Angus Gratton --- ports/esp32/machine_i2c.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 12b86e4aa02..259101ee7e1 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -35,9 +35,14 @@ #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C #ifndef MICROPY_HW_I2C0_SCL +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_8) +#else #define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) #define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) #endif +#endif #ifndef MICROPY_HW_I2C1_SCL #if CONFIG_IDF_TARGET_ESP32 From ecbbc512b2083c472f7bec45e8fae521e7af22aa Mon Sep 17 00:00:00 2001 From: Elvis Pfutzenreuter Date: Thu, 1 May 2025 14:30:04 -0300 Subject: [PATCH 0668/2098] esp32/network_lan: Add PHY_GENERIC device type. Support the new PHY_GENERIC device type, added in ESP-IDF v5.4.0 [1]. This PHY driver was added to ESP-IDF to support "generic"/oddball PHY LAN chips like the JL1101, which offer no features beyond the bare 802.3 PHY standard and don't actually need a chip-specific driver (see discussion at [2]). [1] https://github.com/espressif/esp-idf/commit/0738314308ad36a73601ddb8bb82f1dcbfe1f550 [2] https://github.com/espressif/esp-eth-drivers/pull/28 Signed-off-by: Elvis Pfutzenreuter --- docs/esp32/quickref.rst | 1 + ports/esp32/modnetwork.h | 10 ++++++++++ ports/esp32/modnetwork_globals.h | 3 +++ ports/esp32/network_lan.c | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 56d8b0e4f6b..4e70ff255ec 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -148,6 +148,7 @@ Required keyword arguments for the constructor: - ``mdc`` and ``mdio`` - :class:`machine.Pin` objects (or integers) specifying the MDC and MDIO pins. - ``phy_type`` - Select the PHY device type. Supported devices are + ``PHY_GENERIC``, ``PHY_LAN8710``, ``PHY_LAN8720``, ``PHY_IP101``, ``PHY_RTL8201``, ``PHY_DP83848``, ``PHY_KSZ8041`` and ``PHY_KSZ8081``. These values are all constants defined in the ``network`` module. diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 4debea1114b..ba69d5cc0f9 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -35,12 +35,22 @@ #define PHY_LAN867X_ENABLED (0) #endif +// PHY_GENERIC support requires newer IDF version +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) && CONFIG_IDF_TARGET_ESP32 +#define PHY_GENERIC_ENABLED (1) +#else +#define PHY_GENERIC_ENABLED (0) +#endif + enum { // PHYs supported by the internal Ethernet MAC: PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, #if PHY_LAN867X_ENABLED PHY_LAN8670, #endif + #if PHY_GENERIC_ENABLED + PHY_GENERIC, + #endif // PHYs which are actually SPI Ethernet MAC+PHY chips: PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 9909e7c056a..12252ddbc5d 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -50,6 +50,9 @@ #if PHY_LAN867X_ENABLED { MP_ROM_QSTR(MP_QSTR_PHY_LAN8670), MP_ROM_INT(PHY_LAN8670) }, #endif +#if PHY_GENERIC_ENABLED +{ MP_ROM_QSTR(MP_QSTR_PHY_GENERIC), MP_ROM_INT(PHY_GENERIC) }, +#endif #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index bf6b565e8cd..309ee0b14a2 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -163,6 +163,9 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar #if PHY_LAN867X_ENABLED args[ARG_phy_type].u_int != PHY_LAN8670 && #endif + #if PHY_GENERIC_ENABLED + args[ARG_phy_type].u_int != PHY_GENERIC && + #endif #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL args[ARG_phy_type].u_int != PHY_KSZ8851SNL && @@ -243,6 +246,11 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar self->phy = esp_eth_phy_new_lan867x(&phy_config); break; #endif + #if PHY_GENERIC_ENABLED + case PHY_GENERIC: + self->phy = esp_eth_phy_new_generic(&phy_config); + break; + #endif #endif // CONFIG_IDF_TARGET_ESP32 #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL From 1dfb5092fc66b70420af5a6b8d6239e70b8c6e13 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sat, 26 Apr 2025 23:41:32 +0200 Subject: [PATCH 0669/2098] tools/mpremote: Add new 'fs tree' command. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `mpremote fs tree` command to show a tree of the device's files. It: - Shows a treeview from current path or specified path. - Uses the graph chars ("├── ", "└── ") (not configurable). - Has the options: -v/--verbose adds the serial device name to the top of the tree -s/--size add a size to the files -h/--human add a human readable size to the files Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/commands.py | 49 +++++++++++++++++++++++++++-- tools/mpremote/mpremote/main.py | 34 +++++++++++++++++--- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 452384728ab..428600baf43 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -334,6 +334,49 @@ def do_filesystem_recursive_rm(state, path, args): print(f"removed: '{path}'") +def human_size(size, decimals=1): + for unit in ['B', 'K', 'M', 'G', 'T']: + if size < 1024.0 or unit == 'T': + break + size /= 1024.0 + return f"{size:.{decimals}f}{unit}" if unit != 'B' else f"{int(size)}" + + +def do_filesystem_tree(state, path, args): + """Print a tree of the device's filesystem starting at path.""" + connectors = ("├── ", "└── ") + + def _tree_recursive(path, prefix=""): + entries = state.transport.fs_listdir(path) + entries.sort(key=lambda e: e.name) + for i, entry in enumerate(entries): + connector = connectors[1] if i == len(entries) - 1 else connectors[0] + is_dir = entry.st_mode & 0x4000 # Directory + size_str = "" + # most MicroPython filesystems don't support st_size on directories, reduce clutter + if entry.st_size > 0 or not is_dir: + if args.size: + size_str = f"[{entry.st_size:>9}] " + elif args.human: + size_str = f"[{human_size(entry.st_size):>6}] " + print(f"{prefix}{connector}{size_str}{entry.name}") + if is_dir: + _tree_recursive( + _remote_path_join(path, entry.name), + prefix + (" " if i == len(entries) - 1 else "│ "), + ) + + if not path or path == ".": + path = state.transport.exec("import os;print(os.getcwd())").strip().decode("utf-8") + if not (path == "." or state.transport.fs_isdir(path)): + raise CommandError(f"tree: '{path}' is not a directory") + if args.verbose: + print(f":{path} on {state.transport.device_name}") + else: + print(f":{path}") + _tree_recursive(path) + + def do_filesystem(state, args): state.ensure_raw_repl() state.did_action() @@ -361,8 +404,8 @@ def do_filesystem(state, args): # leading ':' if the user included them. paths = [path[1:] if path.startswith(":") else path for path in paths] - # ls implicitly lists the cwd. - if command == "ls" and not paths: + # ls and tree implicitly lists the cwd. + if command in ("ls", "tree") and not paths: paths = [""] try: @@ -404,6 +447,8 @@ def do_filesystem(state, args): ) else: do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force) + elif command == "tree": + do_filesystem_tree(state, path, args) except OSError as er: raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno))) except TransportError as er: diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index b30a1a21354..f8c913d26d1 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -181,7 +181,11 @@ def argparse_rtc(): def argparse_filesystem(): - cmd_parser = argparse.ArgumentParser(description="execute filesystem commands on the device") + cmd_parser = argparse.ArgumentParser( + description="execute filesystem commands on the device", + add_help=False, + ) + cmd_parser.add_argument("--help", action="help", help="show this help message and exit") _bool_flag(cmd_parser, "recursive", "r", False, "recursive (for cp and rm commands)") _bool_flag( cmd_parser, @@ -197,10 +201,26 @@ def argparse_filesystem(): None, "enable verbose output (defaults to True for all commands except cat)", ) + size_group = cmd_parser.add_mutually_exclusive_group() + size_group.add_argument( + "--size", + "-s", + default=False, + action="store_true", + help="show file size in bytes(tree command only)", + ) + size_group.add_argument( + "--human", + "-h", + default=False, + action="store_true", + help="show file size in a more human readable way (tree command only)", + ) + cmd_parser.add_argument( "command", nargs=1, - help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch)", + help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch, tree)", ) cmd_parser.add_argument("path", nargs="+", help="local and remote paths") return cmd_parser @@ -355,6 +375,7 @@ def argparse_none(description): "rmdir": "fs rmdir", "sha256sum": "fs sha256sum", "touch": "fs touch", + "tree": "fs tree", # Disk used/free. "df": [ "exec", @@ -552,8 +573,13 @@ def main(): command_args = remaining_args extra_args = [] - # Special case: "fs ls" allowed have no path specified. - if cmd == "fs" and len(command_args) == 1 and command_args[0] == "ls": + # Special case: "fs ls" and "fs tree" can have only options and no path specified. + if ( + cmd == "fs" + and len(command_args) >= 1 + and command_args[0] in ("ls", "tree") + and sum(1 for a in command_args if not a.startswith('-')) == 1 + ): command_args.append("") # Use the command-specific argument parser. From d9453164320e8c30b665c5252bbf8a59479ae848 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sat, 26 Apr 2025 23:41:32 +0200 Subject: [PATCH 0670/2098] tools/mpremote/tests: Add tests for 'fs tree' command. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/test_filesystem.sh | 3 + tools/mpremote/tests/test_filesystem.sh.exp | 2 + tools/mpremote/tests/test_fs_tree.sh | 114 ++++++++++ tools/mpremote/tests/test_fs_tree.sh.exp | 225 ++++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100755 tools/mpremote/tests/test_fs_tree.sh create mode 100644 tools/mpremote/tests/test_fs_tree.sh.exp diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index a29015e9872..13c5394f71c 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -237,3 +237,6 @@ echo ----- # try to delete existing folder in mounted filesystem $MPREMOTE mount "${TMP}" + rm -rv :package || echo "expect error" echo ----- +# fs without command should raise error +$MPREMOTE fs 2>/dev/null || echo "expect error: $?" +echo ----- diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 3d9d0fe9ae8..63411580b75 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -272,3 +272,5 @@ rm :package mpremote: rm -r not permitted on /remote directory expect error ----- +expect error: 2 +----- diff --git a/tools/mpremote/tests/test_fs_tree.sh b/tools/mpremote/tests/test_fs_tree.sh new file mode 100755 index 00000000000..d7fc433ae6a --- /dev/null +++ b/tools/mpremote/tests/test_fs_tree.sh @@ -0,0 +1,114 @@ +#!/bin/bash +set -e + +# Creates a RAM disk big enough to hold two copies of the test directory +# structure. +cat << EOF > "${TMP}/ramdisk.py" +class RAMBlockDev: + def __init__(self, block_size, num_blocks): + self.block_size = block_size + self.data = bytearray(block_size * num_blocks) + + def readblocks(self, block_num, buf): + for i in range(len(buf)): + buf[i] = self.data[block_num * self.block_size + i] + + def writeblocks(self, block_num, buf): + for i in range(len(buf)): + self.data[block_num * self.block_size + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return len(self.data) // self.block_size + if op == 5: # get block size + return self.block_size + +import os + +bdev = RAMBlockDev(512, 50) +os.VfsFat.mkfs(bdev) +os.mount(bdev, '/ramdisk') +os.chdir('/ramdisk') +EOF + +# setup +echo ----- +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume ls + +echo ----- +echo "empty tree" +$MPREMOTE resume tree : + +echo ----- +$MPREMOTE resume touch :a.py + touch :b.py +$MPREMOTE resume mkdir :foo + touch :foo/aa.py + touch :foo/ba.py + +echo "small tree - :" +$MPREMOTE resume tree : + +echo ----- +echo "no path" +$MPREMOTE resume tree + +echo ----- +echo "path = '.'" +$MPREMOTE resume tree . + +echo ----- +echo "path = ':.'" +$MPREMOTE resume tree :. + + +echo ----- +echo "multiple trees" +$MPREMOTE resume mkdir :bar + touch :bar/aaa.py + touch :bar/bbbb.py +$MPREMOTE resume mkdir :bar/baz + touch :bar/baz/aaa.py + touch :bar/baz/bbbb.py +$MPREMOTE resume mkdir :bar/baz/quux + touch :bar/baz/quux/aaa.py + touch :bar/baz/quux/bbbb.py +$MPREMOTE resume mkdir :bar/baz/quux/xen + touch :bar/baz/quux/xen/aaa.py + +$MPREMOTE resume tree + +echo ----- +echo single path +$MPREMOTE resume tree :foo + +echo ----- +echo "multiple paths" +$MPREMOTE resume tree :foo :bar + +echo ----- +echo "subtree" +$MPREMOTE resume tree bar/baz + +echo ----- +echo mountpoint +$MPREMOTE resume tree :/ramdisk + +echo ----- +echo non-existent folder : error +$MPREMOTE resume tree :not_there || echo "expect error: $?" + +echo ----- +echo file : error +$MPREMOTE resume tree :a.py || echo "expect error: $?" + +echo ----- +echo "tree -s :" +mkdir -p "${TMP}/data" +dd if=/dev/zero of="${TMP}/data/file1.txt" bs=1 count=20 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file2.txt" bs=1 count=204 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file3.txt" bs=1 count=1096 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file4.txt" bs=1 count=2192 > /dev/null 2>&1 + +$MPREMOTE resume cp -r "${TMP}/data" : +$MPREMOTE resume tree -s : +echo ----- +echo "tree -s" +$MPREMOTE resume tree -s +echo ----- +$MPREMOTE resume tree --human : +echo ----- +$MPREMOTE resume tree -s --human : || echo "expect error: $?" +echo ----- + diff --git a/tools/mpremote/tests/test_fs_tree.sh.exp b/tools/mpremote/tests/test_fs_tree.sh.exp new file mode 100644 index 00000000000..9a67883b1c8 --- /dev/null +++ b/tools/mpremote/tests/test_fs_tree.sh.exp @@ -0,0 +1,225 @@ +----- +ls : +----- +empty tree +tree : +:/ramdisk +----- +touch :a.py +touch :b.py +mkdir :foo +touch :foo/aa.py +touch :foo/ba.py +small tree - : +tree : +:/ramdisk +├── a.py +├── b.py +└── foo + ├── aa.py + └── ba.py +----- +no path +tree : +:/ramdisk +├── a.py +├── b.py +└── foo + ├── aa.py + └── ba.py +----- +path = '.' +tree :. +:/ramdisk +├── a.py +├── b.py +└── foo + ├── aa.py + └── ba.py +----- +path = ':.' +tree :. +:/ramdisk +├── a.py +├── b.py +└── foo + ├── aa.py + └── ba.py +----- +multiple trees +mkdir :bar +touch :bar/aaa.py +touch :bar/bbbb.py +mkdir :bar/baz +touch :bar/baz/aaa.py +touch :bar/baz/bbbb.py +mkdir :bar/baz/quux +touch :bar/baz/quux/aaa.py +touch :bar/baz/quux/bbbb.py +mkdir :bar/baz/quux/xen +touch :bar/baz/quux/xen/aaa.py +tree : +:/ramdisk +├── a.py +├── b.py +├── bar +│ ├── aaa.py +│ ├── baz +│ │ ├── aaa.py +│ │ ├── bbbb.py +│ │ └── quux +│ │ ├── aaa.py +│ │ ├── bbbb.py +│ │ └── xen +│ │ └── aaa.py +│ └── bbbb.py +└── foo + ├── aa.py + └── ba.py +----- +single path +tree :foo +:foo +├── aa.py +└── ba.py +----- +multiple paths +tree :foo +:foo +├── aa.py +└── ba.py +tree :bar +:bar +├── aaa.py +├── baz +│ ├── aaa.py +│ ├── bbbb.py +│ └── quux +│ ├── aaa.py +│ ├── bbbb.py +│ └── xen +│ └── aaa.py +└── bbbb.py +----- +subtree +tree :bar/baz +:bar/baz +├── aaa.py +├── bbbb.py +└── quux + ├── aaa.py + ├── bbbb.py + └── xen + └── aaa.py +----- +mountpoint +tree :/ramdisk +:/ramdisk +├── a.py +├── b.py +├── bar +│ ├── aaa.py +│ ├── baz +│ │ ├── aaa.py +│ │ ├── bbbb.py +│ │ └── quux +│ │ ├── aaa.py +│ │ ├── bbbb.py +│ │ └── xen +│ │ └── aaa.py +│ └── bbbb.py +└── foo + ├── aa.py + └── ba.py +----- +non-existent folder : error +tree :not_there +mpremote: tree: 'not_there' is not a directory +expect error: 1 +----- +file : error +tree :a.py +mpremote: tree: 'a.py' is not a directory +expect error: 1 +----- +tree -s : +cp ${TMP}/data : +tree : +:/ramdisk +├── [ 0] a.py +├── [ 0] b.py +├── bar +│ ├── [ 0] aaa.py +│ ├── baz +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── quux +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +├── data +│ ├── [ 20] file1.txt +│ ├── [ 204] file2.txt +│ ├── [ 1096] file3.txt +│ └── [ 2192] file4.txt +└── foo + ├── [ 0] aa.py + └── [ 0] ba.py +----- +tree -s +tree : +:/ramdisk +├── [ 0] a.py +├── [ 0] b.py +├── bar +│ ├── [ 0] aaa.py +│ ├── baz +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── quux +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +├── data +│ ├── [ 20] file1.txt +│ ├── [ 204] file2.txt +│ ├── [ 1096] file3.txt +│ └── [ 2192] file4.txt +└── foo + ├── [ 0] aa.py + └── [ 0] ba.py +----- +tree : +:/ramdisk +├── [ 0] a.py +├── [ 0] b.py +├── bar +│ ├── [ 0] aaa.py +│ ├── baz +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── quux +│ │ ├── [ 0] aaa.py +│ │ ├── [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +├── data +│ ├── [ 20] file1.txt +│ ├── [ 204] file2.txt +│ ├── [ 1.1K] file3.txt +│ └── [ 2.1K] file4.txt +└── foo + ├── [ 0] aa.py + └── [ 0] ba.py +----- +usage: fs [--help] [--recursive | --no-recursive] [--force | --no-force] + [--verbose | --no-verbose] [--size | --human] + command path [path ...] ... +fs: error: argument --human/-h: not allowed with argument --size/-s +expect error: 2 +----- From e39243c3820c193b97f34ff033c9a598e40277a7 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Fri, 9 May 2025 20:30:10 +0200 Subject: [PATCH 0671/2098] docs/reference/mpremote: Document the 'fs tree' command. Signed-off-by: Jos Verlinde --- docs/reference/mpremote.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index 32ca5c246a7..bee008c6373 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -234,6 +234,7 @@ The full list of supported commands are: - ``rmdir `` to remove directories on the device - ``touch `` to create the files (if they don't already exist) - ``sha256sum `` to calculate the SHA256 sum of files + - ``tree [-vsh] `` to print a tree of the given directories The ``cp`` command uses a convention where a leading ``:`` represents a remote path. Without a leading ``:`` means a local path. This is based on the @@ -264,6 +265,13 @@ The full list of supported commands are: There is no supported way to undelete files removed by ``mpremote rm -r :``. Please use with caution. + The ``tree`` command will print a tree of the given directories. + Using the ``--size/-s`` option will print the size of each file, or use + ``--human/-h`` to use a more human readable format. + Note: Directory size is only printed when a non-zero size is reported by the device's filesystem. + The ``-v`` option can be used to include the name of the serial device in + the output. + All other commands implicitly assume the path is a remote path, but the ``:`` can be optionally used for clarity. From 7a55cb6b364fdbc2f3291456643bd640ba566ec9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 17:16:11 +1000 Subject: [PATCH 0672/2098] tests/run-tests.py: Add list of passed/skipped tests to _result.json. The output `_result.json` file generated by `run-tests.py` currently contains a list of failed tests. This commit adds to the output a list of passed and skipped tests, and so now provides full information about which tests were run and what their results were. Signed-off-by: Damien George --- tests/run-tests.py | 48 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index ac411a0be6a..9294c7e636f 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -605,7 +605,7 @@ def run_script_on_remote_target(self, args, test_file, is_special): def run_tests(pyb, tests, args, result_dir, num_threads=1): test_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter() - passed_count = ThreadSafeCounter() + passed_tests = ThreadSafeCounter([]) failed_tests = ThreadSafeCounter([]) skipped_tests = ThreadSafeCounter([]) @@ -896,7 +896,7 @@ def run_one_test(test_file): if skip_it: print("skip ", test_file) - skipped_tests.append(test_name) + skipped_tests.append((test_name, test_file)) return # Run the test on the MicroPython target. @@ -911,7 +911,7 @@ def run_one_test(test_file): # start-up code (eg boot.py) when preparing to run the next test. pyb.read_until(1, b"raw REPL; CTRL-B to exit\r\n") print("skip ", test_file) - skipped_tests.append(test_name) + skipped_tests.append((test_name, test_file)) return # Look at the output of the test to see if unittest was used. @@ -994,7 +994,7 @@ def run_one_test(test_file): # Print test summary, update counters, and save .exp/.out files if needed. if test_passed: print("pass ", test_file, extra_info) - passed_count.increment() + passed_tests.append((test_name, test_file)) rm_f(filename_expected) rm_f(filename_mupy) else: @@ -1035,17 +1035,30 @@ def run_one_test(test_file): print(line) sys.exit(1) + passed_tests = sorted(passed_tests.value) + skipped_tests = sorted(skipped_tests.value) + failed_tests = sorted(failed_tests.value) + print( "{} tests performed ({} individual testcases)".format( test_count.value, testcase_count.value ) ) - print("{} tests passed".format(passed_count.value)) + print("{} tests passed".format(len(passed_tests))) - skipped_tests = sorted(skipped_tests.value) if len(skipped_tests) > 0: - print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) - failed_tests = sorted(failed_tests.value) + print( + "{} tests skipped: {}".format( + len(skipped_tests), " ".join(test[0] for test in skipped_tests) + ) + ) + + if len(failed_tests) > 0: + print( + "{} tests failed: {}".format( + len(failed_tests), " ".join(test[0] for test in failed_tests) + ) + ) # Serialize regex added by append_filter. def to_json(obj): @@ -1055,21 +1068,18 @@ def to_json(obj): with open(os.path.join(result_dir, RESULTS_FILE), "w") as f: json.dump( - {"args": vars(args), "failed_tests": [test[1] for test in failed_tests]}, + { + "args": vars(args), + "passed_tests": [test[1] for test in passed_tests], + "skipped_tests": [test[1] for test in skipped_tests], + "failed_tests": [test[1] for test in failed_tests], + }, f, default=to_json, ) - if len(failed_tests) > 0: - print( - "{} tests failed: {}".format( - len(failed_tests), " ".join(test[0] for test in failed_tests) - ) - ) - return False - - # all tests succeeded - return True + # Return True only if all tests succeeded. + return len(failed_tests) == 0 class append_filter(argparse.Action): From 9ef16b466bf980a3b3e0980e7d010b0b9130569b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 17 Apr 2025 16:56:11 +0200 Subject: [PATCH 0673/2098] extmod/modjson: Detect unterminated composite entities. This commit makes the JSON parser raise an exception when handling objects or arrays whose declaration is incomplete, as in missing the closing marker (brace or bracket) and if the missing marker would have been the last non-whitespace character in the incoming string. Since CPython's JSON parser would raise an exception in such a case, unlike MicroPython's, this commit aligns MicroPython's behaviour with CPython. This commit fixes issue #17141. Signed-off-by: Alessandro Gatti --- extmod/modjson.c | 3 ++- tests/extmod/json_loads.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/extmod/modjson.c b/extmod/modjson.c index e655a02bc00..11aedd19833 100644 --- a/extmod/modjson.c +++ b/extmod/modjson.c @@ -160,7 +160,8 @@ static mp_obj_t mod_json_load(mp_obj_t stream_obj) { for (;;) { cont: if (S_END(s)) { - break; + // Input finished abruptly in the middle of a composite entity. + goto fail; } mp_obj_t next = MP_OBJ_NULL; bool enter = false; diff --git a/tests/extmod/json_loads.py b/tests/extmod/json_loads.py index f9073c121e2..095e67d740b 100644 --- a/tests/extmod/json_loads.py +++ b/tests/extmod/json_loads.py @@ -71,3 +71,27 @@ def my_print(o): my_print(json.loads("[null] a")) except ValueError: print("ValueError") + +# incomplete object declaration +try: + my_print(json.loads('{"a":0,')) +except ValueError: + print("ValueError") + +# incomplete nested array declaration +try: + my_print(json.loads('{"a":0, [')) +except ValueError: + print("ValueError") + +# incomplete array declaration +try: + my_print(json.loads('[0,')) +except ValueError: + print("ValueError") + +# incomplete nested object declaration +try: + my_print(json.loads('[0, {"a":0, ')) +except ValueError: + print("ValueError") From 186caf9f0326c9d61494a7d5c6d0408c0fef8485 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 15 May 2025 11:26:17 +1000 Subject: [PATCH 0674/2098] extmod/network_cyw43: Disconnect STA if making inactive. esp32 port will disconnect if active(0) is called on a STA interface, but rp2 port stays associated without this change. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 1b1b10b4074..9ebfa904db5 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -143,6 +143,9 @@ static mp_obj_t network_cyw43_active(size_t n_args, const mp_obj_t *args) { return mp_obj_new_bool(if_active[self->itf]); } else { bool value = mp_obj_is_true(args[1]); + if (!value && self->itf == CYW43_ITF_STA) { + cyw43_wifi_leave(self->cyw, self->itf); + } cyw43_wifi_set_up(self->cyw, self->itf, value, get_country_code()); if_active[self->itf] = value; return mp_const_none; From 1d37caa3679c86ef4fe43d2f9e6ac13074498433 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 17 Apr 2025 00:27:28 +0200 Subject: [PATCH 0675/2098] py/emitnative: Improve Viper register-indexed code for Arm. This commit lets the Viper code generator use optimised code sequences for register-indexed load and store operations when generating Arm code. The existing code defaulted to generic multi-operations code sequences for Arm code on most cases. Now optimised implementations are provided for register-indexed loads and stores of all data sizes, taking at most two machine opcodes for each operation. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 16 ++++++++++++++++ py/asmarm.h | 6 ++++++ py/emitnative.c | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index 6fa751b32eb..d304567882c 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -343,6 +343,12 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12)); } +void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldrh doesn't support scaled register index + emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1 + emit_al(as, 0x19000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // ldrh rd, [rm, r8]; +} + void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { if (byte_offset < 0x100) { // ldrh rd, [rn, #off] @@ -360,6 +366,16 @@ void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12)); } +void asm_arm_ldrb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldrb rd, [rm, rn] + emit_al(as, 0x7d00000 | (rm << 16) | (rd << 12) | rn); +} + +void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldr rd, [rm, rn, lsl #2] + emit_al(as, 0x7900100 | (rm << 16) | (rd << 12) | rn); +} + void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { // str rd, [rm, #off] emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); diff --git a/py/asmarm.h b/py/asmarm.h index 4a4253aef68..20b4757d21f 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -116,6 +116,12 @@ void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn); void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset); void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm); void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm); + +// load from array +void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); +void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); +void asm_arm_ldrb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); + // store to array void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); void asm_arm_strh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); diff --git a/py/emitnative.c b/py/emitnative.c index 1aab0a9eb78..2fb4bdb4285 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1638,6 +1638,10 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory + #if N_ARM + asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #endif // TODO optimise to use thumb ldrb r1, [r2, r3] ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD8_REG_REG(emit->as, REG_RET, REG_ARG_1); // store value to (base+index) @@ -1645,7 +1649,10 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_XTENSA || N_XTENSAWIN + #if N_ARM + asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); break; @@ -1657,7 +1664,10 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to word-size memory - #if N_RV32 + #if N_ARM + asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); asm_rv32_opcode_lw(emit->as, REG_RET, REG_ARG_1, 0); From 04c6b99cb941cdc00822f17fe530a63639359019 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:21:30 +0200 Subject: [PATCH 0676/2098] py/emitnative: Improve Viper register-indexed code for Thumb. This commit lets the Viper code generator use optimised code sequence for register-indexed load and store operations when generating Thumb code. Register-indexed load and store operations for Thumb now can take at most two machine opcodes for halfword and word values, and just a single machine opcode for byte values. The original implementation could generate up to four opcodes in the worst case (dealing with word values). Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 20 ++++++++++++++++++++ py/asmthumb.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ py/emitnative.c | 18 ++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/py/asmthumb.c b/py/asmthumb.c index 420815e8026..73684c0f5f0 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -491,6 +491,26 @@ void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint r } } +void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); + asm_thumb_ldrh_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); +} + +void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); + asm_thumb_ldr_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); +} + +void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); + asm_thumb_strh_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); +} + +void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); + asm_thumb_str_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); +} + // this could be wrong, because it should have a range of +/- 16MiB... #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) diff --git a/py/asmthumb.h b/py/asmthumb.h index a9e68d7adbb..f7cd52fb407 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -251,6 +251,50 @@ static inline void asm_thumb_bx_reg(asm_thumb_t *as, uint r_src) { asm_thumb_format_5(as, ASM_THUMB_FORMAT_5_BX, 0, r_src); } +// FORMAT 7: load/store with register offset +// FORMAT 8: load/store sign-extended byte/halfword + +#define ASM_THUMB_FORMAT_7_LDR (0x5800) +#define ASM_THUMB_FORMAT_7_STR (0x5000) +#define ASM_THUMB_FORMAT_7_WORD_TRANSFER (0x0000) +#define ASM_THUMB_FORMAT_7_BYTE_TRANSFER (0x0400) +#define ASM_THUMB_FORMAT_8_LDRH (0x5A00) +#define ASM_THUMB_FORMAT_8_STRH (0x5200) + +#define ASM_THUMB_FORMAT_7_8_ENCODE(op, rlo_dest, rlo_base, rlo_index) \ + ((op) | ((rlo_index) << 6) | ((rlo_base) << 3) | ((rlo_dest))) + +static inline void asm_thumb_format_7_8(asm_thumb_t *as, uint op, uint rlo_dest, uint rlo_base, uint rlo_index) { + assert(rlo_dest < ASM_THUMB_REG_R8); + assert(rlo_base < ASM_THUMB_REG_R8); + assert(rlo_index < ASM_THUMB_REG_R8); + asm_thumb_op16(as, ASM_THUMB_FORMAT_7_8_ENCODE(op, rlo_dest, rlo_base, rlo_index)); +} + +static inline void asm_thumb_ldrb_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_LDR | ASM_THUMB_FORMAT_7_BYTE_TRANSFER, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_ldrh_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_8_LDRH, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_ldr_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_LDR | ASM_THUMB_FORMAT_7_WORD_TRANSFER, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_strb_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_STR | ASM_THUMB_FORMAT_7_BYTE_TRANSFER, rlo_src, rlo_base, rlo_index); +} + +static inline void asm_thumb_strh_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_8_STRH, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_str_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_STR | ASM_THUMB_FORMAT_7_WORD_TRANSFER, rlo_src, rlo_base, rlo_index); +} + // FORMAT 9: load/store with immediate offset // For word transfers the offset must be aligned, and >>2 @@ -341,6 +385,11 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience +void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); +void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); +void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); +void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); + void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenience diff --git a/py/emitnative.c b/py/emitnative.c index 2fb4bdb4285..088e1e7ae0e 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1641,6 +1641,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldrb_rlo_rlo_rlo(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #endif // TODO optimise to use thumb ldrb r1, [r2, r3] ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base @@ -1652,6 +1655,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); @@ -1667,6 +1673,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); @@ -1944,6 +1953,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_strb_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_strb_rlo_rlo_rlo(emit->as, reg_value, REG_ARG_1, reg_index); + break; #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE8_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+index) @@ -1954,6 +1966,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); + break; #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_s16i(emit->as, reg_value, REG_ARG_1, 0); @@ -1969,6 +1984,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); + break; #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); From b6d269ee32026c7380fdb6ebcbc7e50e84239c12 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:41:33 +0200 Subject: [PATCH 0677/2098] py/emitnative: Refactor Viper register-indexed load/stores. This commit cleans up the Viper code generation blocks for register-indexed load and store operations. An attempt is made to simplify the code in the common code generator code block, by moving architecture-specific code to the appropriate native generation backends whenever possible. This should make that specific bit of code in the Viper generator clearer and easier to maintain in the long term. To achieve this, six generic assembler meta-opcodes have been introduced, named `ASM_{LOAD,STORE}{8,16,32}_REG_REG_REG`. A platform-independent implementation for those operations is provided, so backends that cannot emit a shorter sequence for the requested operation or are fine with the platform-independent implementation can just not provide said meta-opcodes. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 7 ++++ py/asmrv32.h | 24 +++++++++++++ py/asmthumb.c | 20 ----------- py/asmthumb.h | 28 ++++++++++++--- py/asmxtensa.h | 20 +++++++++++ py/emitnative.c | 94 +++++++++++++------------------------------------ 6 files changed, 98 insertions(+), 95 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 20b4757d21f..77c700ff0b8 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -221,6 +221,13 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) +#define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrb_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrh_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldr_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_STORE8_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_strb_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_strh_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_str_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMARM_H diff --git a/py/asmrv32.h b/py/asmrv32.h index b09f48eb12f..1cbdc887208 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -758,6 +758,30 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) +#define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 1); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_lhu(state, rd, rs1, 0); \ + } while (0) +#define ASM_LOAD32_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 2); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_lw(state, rd, rs1, 0); \ + } while (0) +#define ASM_STORE16_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 1); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_sh(state, rd, rs1, 0); \ + } while (0) +#define ASM_STORE32_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 2); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_sw(state, rd, rs1, 0); \ + } while (0) #endif diff --git a/py/asmthumb.c b/py/asmthumb.c index 73684c0f5f0..420815e8026 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -491,26 +491,6 @@ void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint r } } -void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); - asm_thumb_ldrh_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); -} - -void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); - asm_thumb_ldr_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); -} - -void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); - asm_thumb_strh_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); -} - -void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); - asm_thumb_str_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); -} - // this could be wrong, because it should have a range of +/- 16MiB... #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) diff --git a/py/asmthumb.h b/py/asmthumb.h index f7cd52fb407..f62f7dd8d9e 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -385,11 +385,6 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience -void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); -void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); -void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); -void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); - void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenience @@ -480,6 +475,29 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) +#define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_thumb_ldrb_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 1); \ + asm_thumb_ldrh_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)); \ + } while (0) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 2); \ + asm_thumb_ldr_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)); \ + } while (0) +#define ASM_STORE8_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_thumb_strb_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 1); \ + asm_thumb_strh_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)); \ + } while (0) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 2); \ + asm_thumb_str_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)); \ + } while (0) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMTHUMB_H diff --git a/py/asmxtensa.h b/py/asmxtensa.h index d2f37bf828e..6451c75aa59 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -411,12 +411,32 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), (uint16_offset)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0); \ + } while (0) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0); \ + } while (0) #define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s8i((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s16i((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_s16i((as), (reg_val), (reg_base), 0); \ + } while (0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s32i_n((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_s32i_n((as), (reg_val), (reg_base), 0); \ + } while (0) #endif // GENERIC_ASM_API diff --git a/py/emitnative.c b/py/emitnative.c index 088e1e7ae0e..c2eb46657de 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1638,59 +1638,36 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - #if N_ARM - asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldrb_rlo_rlo_rlo(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #endif - // TODO optimise to use thumb ldrb r1, [r2, r3] + #ifdef ASM_LOAD8_REG_REG_REG + ASM_LOAD8_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD8_REG_REG(emit->as, REG_RET, REG_ARG_1); // store value to (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_ARM - asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_LOAD16_REG_REG_REG + ASM_LOAD16_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD16_REG_REG(emit->as, REG_RET, REG_ARG_1); // load from (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to word-size memory - #if N_ARM - asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_RV32 - asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); - asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); - asm_rv32_opcode_lw(emit->as, REG_RET, REG_ARG_1, 0); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_l32i_n(emit->as, REG_RET, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_LOAD32_REG_REG_REG + ASM_LOAD32_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD32_REG_REG(emit->as, REG_RET, REG_ARG_1); // load from (base+4*index) + #endif break; } default: @@ -1949,59 +1926,36 @@ static void emit_native_store_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - // TODO optimise to use thumb strb r1, [r2, r3] - #if N_ARM - asm_arm_strb_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_strb_rlo_rlo_rlo(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #endif + #ifdef ASM_STORE8_REG_REG_REG + ASM_STORE8_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE8_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_ARM - asm_arm_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_s16i(emit->as, reg_value, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_STORE16_REG_REG_REG + ASM_STORE16_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE16_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to 32-bit memory - #if N_ARM - asm_arm_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_RV32 - asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); - asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); - asm_rv32_opcode_sw(emit->as, reg_value, REG_ARG_1, 0); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_s32i_n(emit->as, reg_value, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_STORE32_REG_REG_REG + ASM_STORE32_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE32_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+4*index) + #endif break; } default: From e66a6022e2a3263de6f2fa383d4c7d2c4c1cf359 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:45:56 +0200 Subject: [PATCH 0678/2098] py/asm: Remove unused generic ASM API opcode definitions. This commit removes the ASM_LOAD_REG_REG and ASM_STORE_REG_REG generic ASM API opcodes from all backends, as they are not used anymore in the native emitter framework. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 2 -- py/asmrv32.h | 2 -- py/asmthumb.h | 2 -- py/asmx64.h | 2 -- py/asmx86.h | 2 -- py/emitndebug.c | 4 ---- 6 files changed, 14 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 77c700ff0b8..42fa1ea212a 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -208,14 +208,12 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_arm_sub_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_arm_mul_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) -#define ASM_STORE_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_str_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_value, reg_base) asm_arm_strb_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) diff --git a/py/asmrv32.h b/py/asmrv32.h index 1cbdc887208..6453b0a3d43 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -737,7 +737,6 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_LOAD32_REG_REG(state, rd, rs) ASM_LOAD_REG_REG_OFFSET(state, rd, rs, 0) #define ASM_LOAD8_REG_REG(state, rd, rs) asm_rv32_opcode_lbu(state, rd, rs, 0) #define ASM_LOAD_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset) -#define ASM_LOAD_REG_REG(state, rd, rs) ASM_LOAD32_REG_REG(state, rd, rs) #define ASM_LSL_REG_REG(state, rd, rs) asm_rv32_opcode_sll(state, rd, rd, rs) #define ASM_LSR_REG_REG(state, rd, rs) asm_rv32_opcode_srl(state, rd, rd, rs) #define ASM_MOV_LOCAL_REG(state, local, rs) asm_rv32_emit_mov_local_reg(state, local, rs) @@ -754,7 +753,6 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_STORE32_REG_REG(state, rs1, rs2) ASM_STORE_REG_REG_OFFSET(state, rs1, rs2, 0) #define ASM_STORE8_REG_REG(state, rs1, rs2) asm_rv32_opcode_sb(state, rs1, rs2, 0) #define ASM_STORE_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset) -#define ASM_STORE_REG_REG(state, rs1, rs2) ASM_STORE32_REG_REG(state, rs1, rs2) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) diff --git a/py/asmthumb.h b/py/asmthumb.h index f62f7dd8d9e..cc4213503bf 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -462,14 +462,12 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_thumb_sub_rlo_rlo_rlo((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_MUL, (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrb_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrh_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_thumb_ldrh_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_thumb_strb_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) diff --git a/py/asmx64.h b/py/asmx64.h index c63e31797ef..30c6efd6d05 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -205,14 +205,12 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x64_sub_r64_r64((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x64_mul_r64_r64((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem64_to_r64((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x64_mov_mem64_to_r64((as), (reg_base), 8 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem8_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem32_to_r64zx((as), (reg_base), 0, (reg_dest)) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 8 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x64_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x64_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) diff --git a/py/asmx86.h b/py/asmx86.h index 027d44151e8..af73c163b4f 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -200,14 +200,12 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x86_sub_r32_r32((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x86_mul_r32_r32((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem8_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x86_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x86_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) diff --git a/py/emitndebug.c b/py/emitndebug.c index bd896a75c8d..c068a9a9a12 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -251,8 +251,6 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_MUL_REG_REG(as, reg_dest, reg_src) \ asm_debug_reg_reg(as, "mul", reg_dest, reg_src) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) \ - asm_debug_reg_reg(as, "load", reg_dest, reg_base) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) \ asm_debug_reg_reg_offset(as, "load", reg_dest, reg_base, word_offset) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) \ @@ -264,8 +262,6 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) \ asm_debug_reg_reg(as, "load32", reg_dest, reg_base) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) \ - asm_debug_reg_reg(as, "store", reg_src, reg_base) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) \ asm_debug_reg_reg_offset(as, "store", reg_src, reg_base, word_offset) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) \ From 2260fe0828a87b511ad69c0314ecdcb36b66a2cf Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 20:31:38 +0200 Subject: [PATCH 0679/2098] tests/micropython/viper_ptr: Add tests for arch edge cases. This commit adds a series of test cases to exercise the Viper code generator load/store emitting capabilities on certain boundary conditions. The new test cases check whether the emitted load/store code performs correctly when dealing with specific memory offsets, which trigger specific code generation sequences on different architectures. Right now the cases are for unsigned offsets whose bitmasks span up to 5, 8, and 12 bits (respectively Arm/Thumb, Xtensa, RV32). Signed-off-by: Alessandro Gatti --- .../micropython/viper_ptr16_load_boundary.py | 25 +++++++++++ .../viper_ptr16_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr16_store_boundary.py | 41 +++++++++++++++++++ .../viper_ptr16_store_boundary.py.exp | 18 ++++++++ .../micropython/viper_ptr32_load_boundary.py | 25 +++++++++++ .../viper_ptr32_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr32_store_boundary.py | 35 ++++++++++++++++ .../viper_ptr32_store_boundary.py.exp | 18 ++++++++ tests/micropython/viper_ptr8_load_boundary.py | 25 +++++++++++ .../viper_ptr8_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr8_store_boundary.py | 30 ++++++++++++++ .../viper_ptr8_store_boundary.py.exp | 12 ++++++ 12 files changed, 265 insertions(+) create mode 100644 tests/micropython/viper_ptr16_load_boundary.py create mode 100644 tests/micropython/viper_ptr16_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr16_store_boundary.py create mode 100644 tests/micropython/viper_ptr16_store_boundary.py.exp create mode 100644 tests/micropython/viper_ptr32_load_boundary.py create mode 100644 tests/micropython/viper_ptr32_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr32_store_boundary.py create mode 100644 tests/micropython/viper_ptr32_store_boundary.py.exp create mode 100644 tests/micropython/viper_ptr8_load_boundary.py create mode 100644 tests/micropython/viper_ptr8_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr8_store_boundary.py create mode 100644 tests/micropython/viper_ptr8_store_boundary.py.exp diff --git a/tests/micropython/viper_ptr16_load_boundary.py b/tests/micropython/viper_ptr16_load_boundary.py new file mode 100644 index 00000000000..ccaaa0909af --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr16) -> int: + return src[{off}] +print(b[{off} * 2:({off} + 1) * 2]) +""" + + +@micropython.viper +def get_index(src: ptr16, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[28:38] = b"0123456789" +b[252:262] = b"ABCDEFGHIJ" +b[4092:4102] = b"KLMNOPQRST" + +for pre, idx, post in (15, 16, 17), (127, 128, 129), (2047, 2048, 2049): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr16_load_boundary.py.exp b/tests/micropython/viper_ptr16_load_boundary.py.exp new file mode 100644 index 00000000000..4b8c184c134 --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py.exp @@ -0,0 +1,12 @@ +13106 13620 14134 +bytearray(b'23') +bytearray(b'45') +bytearray(b'67') +17475 17989 18503 +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +20045 20559 21073 +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary.py new file mode 100644 index 00000000000..e0a4f845573 --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -0,0 +1,41 @@ +# Test boundary conditions for various architectures + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr16): + dest[{off}] = {val} +set{off}(b) +print(b[{off} * 2:({off} + 1) * 2]) +""" + +TEST_DATA = ( + (15, (0x4241, 0x4443, 0x4645)), + (127, (0x4847, 0x4A49, 0x4C4B)), + (2047, (0x4E4D, 0x504F, 0x5251)), +) + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 2 : (start + i + 1) * 2]) + + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x0101)) diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp new file mode 100644 index 00000000000..b56fe6695f2 --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'AB') +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +bytearray(b'IJ') +bytearray(b'KL') +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') +bytearray(b'BC') +bytearray(b'DE') +bytearray(b'FG') +bytearray(b'HI') +bytearray(b'JK') +bytearray(b'LM') +bytearray(b'NO') +bytearray(b'PQ') +bytearray(b'RS') diff --git a/tests/micropython/viper_ptr32_load_boundary.py b/tests/micropython/viper_ptr32_load_boundary.py new file mode 100644 index 00000000000..6954bd46b23 --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr32) -> int: + return src[{off}] +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def get_index(src: ptr32, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[24:43] = b"0123456789ABCDEFGHIJ" +b[248:268] = b"KLMNOPQRSTUVWXYZabcd" +b[4088:4108] = b"efghijklmnopqrstuvwx" + +for pre, idx, post in (7, 8, 9), (63, 64, 65), (1023, 1024, 1025): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr32_load_boundary.py.exp b/tests/micropython/viper_ptr32_load_boundary.py.exp new file mode 100644 index 00000000000..a58e703f912 --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py.exp @@ -0,0 +1,12 @@ +926299444 1111570744 1178944579 +bytearray(b'4567') +bytearray(b'89AB') +bytearray(b'CDEF') +1381060687 1448432723 1515804759 +bytearray(b'OPQR') +bytearray(b'STUV') +bytearray(b'WXYZ') +1818978921 1886350957 1953722993 +bytearray(b'ijkl') +bytearray(b'mnop') +bytearray(b'qrst') diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary.py new file mode 100644 index 00000000000..243ff5cd9c2 --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -0,0 +1,35 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ( + (3, (0x04030201, 0x08070605, 0x0C0B0A09)), + (63, (0x100F0E0D, 0x14131211, 0x18171615)), + (1023, (0x1C1B1A19, 0x201F1E1D, 0x24232221)), +) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr32): + dest[{off}] = {val} & 0x3FFFFFFF +set{off}(b) +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def set_index(dest: ptr32, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 4 : (start + i + 1) * 4]) + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x01010101)) diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp new file mode 100644 index 00000000000..89f09fbc7af --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'\x01\x02\x03\x04') +bytearray(b'\x05\x06\x07\x08') +bytearray(b'\t\n\x0b\x0c') +bytearray(b'\r\x0e\x0f\x10') +bytearray(b'\x11\x12\x13\x14') +bytearray(b'\x15\x16\x17\x18') +bytearray(b'\x19\x1a\x1b\x1c') +bytearray(b'\x1d\x1e\x1f ') +bytearray(b'!"#$') +bytearray(b'\x02\x03\x04\x05') +bytearray(b'\x06\x07\x08\t') +bytearray(b'\n\x0b\x0c\r') +bytearray(b'\x0e\x0f\x10\x11') +bytearray(b'\x12\x13\x14\x15') +bytearray(b'\x16\x17\x18\x19') +bytearray(b'\x1a\x1b\x1c\x1d') +bytearray(b'\x1e\x1f !') +bytearray(b'"#$%') diff --git a/tests/micropython/viper_ptr8_load_boundary.py b/tests/micropython/viper_ptr8_load_boundary.py new file mode 100644 index 00000000000..bcb17a1e1f2 --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr8) -> int: + return src[{off}] +print(get{off}(b)) +""" + + +@micropython.viper +def get_index(src: ptr8, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[30:32] = b"123" +b[254:256] = b"456" +b[4094:4096] = b"789" + +for pre, idx, post in (30, 31, 32), (254, 255, 256), (4094, 4095, 4096): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr8_load_boundary.py.exp b/tests/micropython/viper_ptr8_load_boundary.py.exp new file mode 100644 index 00000000000..7cbd1ac78cf --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py.exp @@ -0,0 +1,12 @@ +49 50 51 +49 +50 +51 +52 53 54 +52 +53 +54 +55 56 57 +55 +56 +57 diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary.py new file mode 100644 index 00000000000..ad512684549 --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -0,0 +1,30 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ((49, 30, 3), (52, 254, 3), (55, 4094, 3)) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr8): + dest[{off}] = {val} +set{off}(b) +print(b[{off}]) +""" + + +@micropython.viper +def set_index(dest: ptr8, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for val, start, count in TEST_DATA: + for i in range(count): + set_index(b, start + i, val + i) + print(b[start : start + count]) + +for i in range(len(b)): + b[i] = 0 + +for val, start, count in TEST_DATA: + for i in range(count): + exec(SET_TEMPLATE.format(off=start + i, val=val + i + 16)) diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp new file mode 100644 index 00000000000..a35cb3ac9e0 --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py.exp @@ -0,0 +1,12 @@ +bytearray(b'123') +bytearray(b'456') +bytearray(b'789') +65 +66 +67 +68 +69 +70 +71 +72 +73 From 6b2792a097a841bf1c0a27e4fcffcaacc4968285 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 19 Apr 2025 22:36:13 +0200 Subject: [PATCH 0680/2098] py/asmthumb: Generate proper sequences for large register offsets. This commit lets the Thumb native emitter generate a proper opcode sequence when calculating an indexed register offset for load/store operations with said offset beight both greater than 65535 and not able to be represented as a shifted 8-bit bitmask. The original code would assume the scaled index would always fit in 16 bits and silently discard upper bits of the offset. Now an optimised constant loading sequence is emitted instead, and the final offset is also stored in the correct register in all cases. Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index 420815e8026..06021f2bc93 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -450,12 +450,12 @@ static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint re asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, offset_shift); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); } else if (reg_dest != reg_base) { - asm_thumb_mov_rlo_i16(as, reg_dest, offset << offset_shift); - asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); + asm_thumb_mov_reg_i32_optimised(as, reg_dest, offset << offset_shift); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); } else { uint reg_other = reg_dest ^ 7; asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); - asm_thumb_mov_rlo_i16(as, reg_other, offset << offset_shift); + asm_thumb_mov_reg_i32_optimised(as, reg_other, offset << offset_shift); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); } From 3d19a8bc2df1d153aedde587e8f8e9fded4d6c08 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 8 May 2025 00:02:26 +0200 Subject: [PATCH 0681/2098] py/emitnative: Clean up int-indexed Viper load/store code. This commit performs some minor clean up for the code involved in Viper load/store operations when said operations have an integer index. Most platform-specific code blocks were able to generate correct opcodes even when the index is 0, but they would still fall back to the general case. The general case would still emit a shortened opcode sequence so this commit does not alter the overall behaviour, but makes it easier to extend platform-specific code whenever the full index range is going to be handled rather than a subset of indices as it is now. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 198 ++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index c2eb46657de..4b470f3c935 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1537,25 +1537,24 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - // TODO optimise to use thumb ldrb r1, [r2, r3] + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 12)) { + asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is non-zero - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add index to base @@ -1566,24 +1565,24 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 11)) { + asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 2*index to base @@ -1594,24 +1593,24 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to 32-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 10)) { + asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 4*index to base @@ -1811,28 +1810,28 @@ static void emit_native_store_subscr(emit_t *emit) { case VTYPE_PTR8: { // pointer to 8-bit memory // TODO optimise to use thumb strb r1, [r2, r3] + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_strb_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 12)) { + asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_s8i(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is non-zero - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_strb_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_s8i(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value); #if N_ARM asm_arm_strb_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - return; + break; #endif ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add index to base reg_base = reg_index; @@ -1842,24 +1841,24 @@ static void emit_native_store_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 11)) { + asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_s16i(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_s16i(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 2*index to base reg_base = reg_index; @@ -1869,27 +1868,28 @@ static void emit_native_store_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to 32-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 10)) { + asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_s32i_optimised(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_s32i_optimised(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_ARM + #if N_ARM ASM_MOV_REG_IMM(emit->as, reg_index, index_value); asm_arm_str_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - return; + break; #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 4*index to base From 3d7edbd9ab80182b0a11a5df3860dd12f65597a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:48:30 +1000 Subject: [PATCH 0682/2098] py/persistentcode: Allow a port a custom commit function and track data. Allows both MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA and MP_PLAT_COMMIT_EXEC to be enabled at the same time. Signed-off-by: Damien George --- py/persistentcode.c | 14 ++++++++------ py/runtime.c | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index 2a42b904bc1..43207a0cc8f 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -72,6 +72,8 @@ typedef struct _bytecode_prelude_t { static int read_byte(mp_reader_t *reader); static size_t read_uint(mp_reader_t *reader); +#if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA // An mp_obj_list_t that tracks native text/BSS/rodata to prevent the GC from reclaiming them. @@ -86,8 +88,6 @@ static void track_root_pointer(void *ptr) { #endif -#if MICROPY_EMIT_MACHINE_CODE - typedef struct _reloc_info_t { mp_reader_t *reader; mp_module_context_t *context; @@ -415,15 +415,17 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co // Relocate and commit code to executable address space reloc_info_t ri = {reader, context, rodata, bss}; + #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA + if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { + // Track the function data memory so it's not reclaimed by the GC. + track_root_pointer(fun_data); + } + #endif #if defined(MP_PLAT_COMMIT_EXEC) void *opt_ri = (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { - #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA - // Track the function data memory so it's not reclaimed by the GC. - track_root_pointer(fun_data); - #endif // Do the relocations. mp_native_relocate(&ri, fun_data, (uintptr_t)fun_data); } diff --git a/py/runtime.c b/py/runtime.c index d6fea172f21..7979e520da4 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -123,7 +123,7 @@ void mp_init(void) { MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif - #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA + #if MICROPY_EMIT_MACHINE_CODE && (MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA) MP_STATE_VM(persistent_code_root_pointers) = MP_OBJ_NULL; #endif From d5db8f04613b6a7e6f3d55dd8d618e8ccdaecdfa Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:50:34 +1000 Subject: [PATCH 0683/2098] nrf: Use correct IRAM address for native code execution on nRF52. On nRF52, the physical SRAM is mapped to 0x20000000 for data access and 0x00800000 for instruction access. So, while native code is allocated and written using addresses in the 0x20000000 range, it must execute from the 0x00800000 range. This commit makes this work correctly on nRF52 MCUs by adjusting the address. Signed-off-by: Damien George --- ports/nrf/main.c | 14 ++++++++++++++ ports/nrf/mpconfigport.h | 12 +++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 99ed39895eb..784a031ce29 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -39,6 +39,7 @@ #include "py/stackctrl.h" #include "py/gc.h" #include "py/compile.h" +#include "py/persistentcode.h" #include "extmod/modmachine.h" #include "shared/runtime/pyexec.h" #include "readline.h" @@ -369,3 +370,16 @@ void MP_WEAK __assert_func(const char *file, int line, const char *func, const c printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); __fatal_error("Assertion failed"); } + +#if MICROPY_EMIT_MACHINE_CODE +void *nrf_native_code_commit(void *buf, unsigned int len, void *reloc) { + (void)len; + if (reloc) { + // Native code in RAM must execute from the IRAM region at 0x00800000, and so relocations + // to text must also point to this region. The MICROPY_MAKE_POINTER_CALLABLE macro will + // adjust the `buf` address from RAM to IRAM. + mp_native_relocate(reloc, buf, (uintptr_t)MICROPY_MAKE_POINTER_CALLABLE(buf) & ~1); + } + return buf; +} +#endif diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 7cc8a66d984..8c5c0300628 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -320,7 +320,17 @@ // type definitions for the specific machine -#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) +#if defined(NRF52832) || defined(NRF52840) +// On nRF52, the physical SRAM is mapped to 0x20000000 for data access and 0x00800000 +// for instruction access. So convert addresses to make them executable. +#define MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA (1) +#define MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA (0) +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)(((uintptr_t)(p) - 0x20000000 + 0x00800000) | 1)) +void *nrf_native_code_commit(void *, unsigned int, void *); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) nrf_native_code_commit(buf, len, reloc) +#else +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((uintptr_t)(p) | 1)) +#endif #define MP_SSIZE_MAX (0x7fffffff) From 32c65ad4552d7e7d88c06e94fd903dcea5080c5f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:51:35 +1000 Subject: [PATCH 0684/2098] nrf: Only process interrupt chars on UARTs used for REPL. This commit adds an `attached_to_repl` property to each UART, and makes sure that property is correctly set/unset when the UART is attached to or detached from the REPL. That property is then used to make sure incoming characters on the UART are only checked for the interrupt character if the UART is attached to the REPL. Otherwise a board without REPL on UART can have its code interrupted if ctrl-C is received on the UART. Also, put incoming UART characters on to `stdin_ringbuf` instead of the UARTs ring buffer (the former is much larger than the latter). Signed-off-by: Damien George --- ports/nrf/main.c | 4 +++- ports/nrf/modules/machine/uart.c | 26 ++++++++++++++++++-------- ports/nrf/modules/machine/uart.h | 3 +-- ports/nrf/modules/os/modos.c | 14 +++++++++++--- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 784a031ce29..21a71c7c9e4 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -40,6 +40,7 @@ #include "py/gc.h" #include "py/compile.h" #include "py/persistentcode.h" +#include "extmod/misc.h" #include "extmod/modmachine.h" #include "shared/runtime/pyexec.h" #include "readline.h" @@ -172,7 +173,8 @@ void MP_NORETURN _start(void) { MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL), MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL_BAUD), }; - MP_STATE_VM(dupterm_objs[0]) = MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, make_new)((mp_obj_t)&machine_uart_type, MP_ARRAY_SIZE(args), 0, args); + mp_obj_t uart = MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, make_new)((mp_obj_t)&machine_uart_type, MP_ARRAY_SIZE(args), 0, args); + mp_os_dupterm_obj.fun.var(1, &uart); } #endif diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 8d5a73e095f..45c79cda12a 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -104,6 +104,7 @@ typedef struct _machine_uart_obj_t { uint16_t timeout_char; // timeout waiting between chars (in ms) uint8_t uart_id; bool initialized; // static flag. Initialized to False + bool attached_to_repl; #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 @@ -118,6 +119,13 @@ static machine_uart_obj_t machine_uart_obj[] = { }; void uart_init0(void) { + for (int i = 0; i < MP_ARRAY_SIZE(machine_uart_obj); i++) { + machine_uart_obj[i].attached_to_repl = false; + } +} + +void uart_attach_to_repl(machine_uart_obj_t *self, bool attached) { + self->attached_to_repl = attached; } static int uart_find(mp_obj_t id) { @@ -137,14 +145,16 @@ static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context 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]; - #if !MICROPY_PY_BLE_NUS && MICROPY_KBD_EXCEPTION - if (chr == mp_interrupt_char) { - self->buf.rx_ringbuf.iget = 0; - self->buf.rx_ringbuf.iput = 0; - mp_sched_keyboard_interrupt(); - } else - #endif - { + if (self->attached_to_repl) { + #if MICROPY_KBD_EXCEPTION + if (chr == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else + #endif + { + ringbuf_put((ringbuf_t *)&stdin_ringbuf, chr); + } + } else { ringbuf_put((ringbuf_t *)&self->buf.rx_ringbuf, chr); } #if MICROPY_PY_MACHINE_UART_IRQ diff --git a/ports/nrf/modules/machine/uart.h b/ports/nrf/modules/machine/uart.h index 741473ab7a4..85c20924585 100644 --- a/ports/nrf/modules/machine/uart.h +++ b/ports/nrf/modules/machine/uart.h @@ -34,8 +34,7 @@ typedef struct _machine_uart_obj_t machine_uart_obj_t; void uart_init0(void); -void uart_deinit(void); -void uart_irq_handler(mp_uint_t uart_id); +void uart_attach_to_repl(machine_uart_obj_t *self, bool attached); bool uart_rx_any(machine_uart_obj_t *uart_obj); int uart_rx_char(machine_uart_obj_t *uart_obj); diff --git a/ports/nrf/modules/os/modos.c b/ports/nrf/modules/os/modos.c index f000e1eeb64..97ed1e1badb 100644 --- a/ports/nrf/modules/os/modos.c +++ b/ports/nrf/modules/os/modos.c @@ -30,6 +30,7 @@ #include "py/runtime.h" #include "extmod/modmachine.h" #include "drivers/rng.h" +#include "modules/machine/uart.h" #if MICROPY_PY_OS_URANDOM // Return a bytes object with n random bytes, generated by the hardware random number generator. @@ -46,10 +47,17 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_os_urandom_obj, mp_os_urandom); #endif #if MICROPY_PY_OS_DUPTERM -// TODO should accept any object with read/write methods. void mp_os_dupterm_stream_detached_attached(mp_obj_t stream_detached, mp_obj_t stream_attached) { - if (!(stream_attached == mp_const_none || mp_obj_get_type(stream_attached) == &machine_uart_type)) { - mp_raise_ValueError(MP_ERROR_TEXT("need a UART object")); + #if MICROPY_PY_MACHINE_UART + if (mp_obj_get_type(stream_detached) == &machine_uart_type) { + uart_attach_to_repl(MP_OBJ_TO_PTR(stream_detached), false); } + #endif + + #if MICROPY_PY_MACHINE_UART + if (mp_obj_get_type(stream_attached) == &machine_uart_type) { + uart_attach_to_repl(MP_OBJ_TO_PTR(stream_attached), true); + } + #endif } #endif // MICROPY_PY_OS_DUPTERM From e676b58d9fae4c4ebebfe0ea7fb787317dafba38 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:52:35 +1000 Subject: [PATCH 0685/2098] nrf: Fix UART write on parts that can't write more than 255 bytes. Some MCUs cannot write more than 255 bytes to the UART at once. Eg writing 256 bytes gets truncated to 0, writing 257 gets truncated to 1, etc. Signed-off-by: Damien George --- ports/nrf/modules/machine/uart.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 45c79cda12a..4c75bf82098 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -46,6 +46,11 @@ #include "nrfx_uarte.h" #endif +#if defined(NRF52832) +// The nRF52832 cannot write more than 255 bytes at a time. +#define UART_MAX_TX_CHUNK (255) +#endif + typedef struct _machine_uart_buf_t { uint8_t tx_buf[1]; uint8_t rx_buf[1]; @@ -456,17 +461,29 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf, mp_uin #endif machine_uart_obj_t *self = self_in; - nrfx_err_t err = nrfx_uart_tx(self->p_uart, buf, size); - if (err == NRFX_SUCCESS) { + + // Send data out, in chunks if needed. + mp_uint_t remaining = size; + while (remaining) { + #ifdef UART_MAX_TX_CHUNK + mp_uint_t chunk = MIN(UART_MAX_TX_CHUNK, remaining); + #else + mp_uint_t chunk = remaining; + #endif + nrfx_err_t err = nrfx_uart_tx(self->p_uart, buf, chunk); + if (err != NRFX_SUCCESS) { + *errcode = mp_hal_status_to_errno_table[err]; + return MP_STREAM_ERROR; + } while (nrfx_uart_tx_in_progress(self->p_uart)) { MICROPY_EVENT_POLL_HOOK; } - // return number of bytes written - return size; - } else { - *errcode = mp_hal_status_to_errno_table[err]; - return MP_STREAM_ERROR; + buf += chunk; + remaining -= chunk; } + + // return number of bytes written + return size; } static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { From cc7eb1a5351c0d7d60f084dc79ccd0fa047b259e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:54:13 +1000 Subject: [PATCH 0686/2098] nrf/boards: Use 64 byte raw-paste buffer on PCA10028 and PCA10040. To workaround issues with JLink CDC. Signed-off-by: Damien George --- ports/nrf/boards/PCA10028/mpconfigboard.h | 5 +++++ ports/nrf/boards/PCA10040/mpconfigboard.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ports/nrf/boards/PCA10028/mpconfigboard.h b/ports/nrf/boards/PCA10028/mpconfigboard.h index 3dc8ed3459b..df2e4e85d93 100644 --- a/ports/nrf/boards/PCA10028/mpconfigboard.h +++ b/ports/nrf/boards/PCA10028/mpconfigboard.h @@ -60,3 +60,8 @@ #define MICROPY_HW_SPI0_MISO (28) #define HELP_TEXT_BOARD_LED "1,2,3,4" + +// The JLink CDC on the PCA10028 cannot accept more than 64 incoming bytes at a time. +// That makes the UART REPL unreliable in general. But it can be improved to some +// extent by setting the raw-paste buffer size to that limit of 64. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) diff --git a/ports/nrf/boards/PCA10040/mpconfigboard.h b/ports/nrf/boards/PCA10040/mpconfigboard.h index b965bf319d0..2b1c4c7ef5c 100644 --- a/ports/nrf/boards/PCA10040/mpconfigboard.h +++ b/ports/nrf/boards/PCA10040/mpconfigboard.h @@ -64,3 +64,8 @@ #define MICROPY_HW_PWM2_NAME "PWM2" #define HELP_TEXT_BOARD_LED "1,2,3,4" + +// The JLink CDC on the PCA10040 cannot accept more than 64 incoming bytes at a time. +// That makes the UART REPL unreliable in general. But it can be improved to some +// extent by setting the raw-paste buffer size to that limit of 64. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) From a1ee42cd3e6172cd57756e382a4702ae2a1d4a9e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:53:18 +1000 Subject: [PATCH 0687/2098] nrf: Use common implementation of machine disable/enable IRQ. This is a breaking change due to the signature change of `enable_irq()`. Previously the signature was: machine.enable_irq() Now the signature matches other ports, and the docs, and is: machine.enable_irq(state) Where `state` is the return value from `machine.disable_irq()`. Signed-off-by: Damien George --- ports/nrf/modules/machine/modmachine.c | 23 ----------------------- ports/nrf/mpconfigport.h | 1 + ports/nrf/mphalport.h | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index aa1f5a8a4a0..f543265479c 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -85,8 +85,6 @@ #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&machine_info_obj) }, \ - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, \ - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, \ { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, \ \ @@ -214,24 +212,3 @@ 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_raise_NotImplementedError(NULL); } - -static mp_obj_t machine_enable_irq(void) { - #ifndef BLUETOOTH_SD - __enable_irq(); - #else - - #endif - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_enable_irq_obj, machine_enable_irq); - -// Resets the board in a manner similar to pushing the external RESET button. -static mp_obj_t machine_disable_irq(void) { - #ifndef BLUETOOTH_SD - __disable_irq(); - #else - - #endif - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 8c5c0300628..d52b5745d4e 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -181,6 +181,7 @@ #define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) +#define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_PULSE (0) #define MICROPY_PY_MACHINE_SOFTI2C (MICROPY_PY_MACHINE_I2C) diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 3a89636aec6..12e881d7b63 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -35,6 +35,22 @@ #include "nrfx_config.h" #include "shared/runtime/interrupt_char.h" +// Entering a critical section. +#ifndef BLUETOOTH_SD +#define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) +#endif + +static inline void enable_irq(mp_uint_t state) { + __set_PRIMASK(state); +} + +static inline mp_uint_t disable_irq(void) { + mp_uint_t state = __get_PRIMASK(); + __disable_irq(); + return state; +} + typedef enum { HAL_OK = 0x00, From 4545eb844df3acf342052bf80b35a8127cf7e194 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 26 Mar 2025 11:21:09 +1100 Subject: [PATCH 0688/2098] rp2: Disable the LWIP tick timer when not needed. Prevents lightsleep being woken up every 64ms to service LWIP timers, when: 1. No netif is up, and 2. No TCP sockets are active The TCP socket check may not be strictly necessary, but without ticking the tcp timer they won't ever time out by themselves. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/mpnetworkport.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index e1e1567828b..fed34be380a 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -31,6 +31,7 @@ #if MICROPY_PY_LWIP #include "shared/runtime/softtimer.h" +#include "lwip/netif.h" #include "lwip/timeouts.h" // Poll lwIP every 64ms by default @@ -39,6 +40,9 @@ // Soft timer for running lwIP in the background. static soft_timer_entry_t mp_network_soft_timer; +// Callback for change of netif state +NETIF_DECLARE_EXT_CALLBACK(netif_callback) + #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #include "lib/cyw43-driver/src/cyw43_stats.h" @@ -137,17 +141,48 @@ static void mp_network_soft_timer_callback(soft_timer_entry_t *self) { #if MICROPY_PY_NETWORK_WIZNET5K wiznet5k_poll(); #endif + + // Only keep the timer running if any TCP sockets are active, or any netif is up + struct netif *netif; + extern void *tcp_active_pcbs; + bool keep_running = (tcp_active_pcbs != NULL); + if (!keep_running) { + NETIF_FOREACH(netif) { + if (netif->flags & NETIF_FLAG_LINK_UP) { + keep_running = true; + break; + } + } + } + + // Periodic timer will re-queue as soon as this handler exits, + // one shot timer will not + mp_network_soft_timer.mode = keep_running ? SOFT_TIMER_MODE_PERIODIC : SOFT_TIMER_MODE_ONE_SHOT; } +static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args); + void mod_network_lwip_init(void) { soft_timer_static_init( &mp_network_soft_timer, - SOFT_TIMER_MODE_PERIODIC, + SOFT_TIMER_MODE_ONE_SHOT, LWIP_TICK_RATE_MS, mp_network_soft_timer_callback ); - soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); + if (netif_callback.callback_fn == NULL) { + netif_add_ext_callback(&netif_callback, mp_network_netif_status_cb); + } +} + +static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args) { + // Start the network soft timer any time an interface comes up, unless + // it's already running + if (reason == LWIP_NSC_LINK_CHANGED && args->link_changed.state + && mp_network_soft_timer.mode == SOFT_TIMER_MODE_ONE_SHOT) { + mp_network_soft_timer.mode = SOFT_TIMER_MODE_PERIODIC; + soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); + } } #endif // MICROPY_PY_LWIP From 28c8fff6d8f920eb6199cc5db59da1baf1b00843 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 12 May 2025 10:21:52 +0100 Subject: [PATCH 0689/2098] rp2/machine_pin: Replace macros with Pico SDK functions. Replace custom macros with Pico SDK functions, enabling support for RP2350B variant chips with > 32 GPIOs. Fixes issue #17241. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index d9ca3f8ce37..db3ca69d8b1 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -46,12 +46,6 @@ #define GPIO_IRQ_ALL (0xf) -// Macros to access the state of the hardware. -#define GPIO_GET_FUNCSEL(id) ((iobank0_hw->io[(id)].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB) -#define GPIO_IS_OUT(id) (sio_hw->gpio_oe & (1 << (id))) -#define GPIO_IS_PULL_UP(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PUE_BITS) -#define GPIO_IS_PULL_DOWN(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PDE_BITS) - // Open drain behaviour is simulated. #define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) @@ -198,13 +192,13 @@ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pin_obj_t *self = self_in; - uint funcsel = GPIO_GET_FUNCSEL(self->id); + uint funcsel = gpio_get_function(self->id); qstr mode_qst; if (!is_ext_pin(self)) { if (funcsel == GPIO_FUNC_SIO) { if (GPIO_IS_OPEN_DRAIN(self->id)) { mode_qst = MP_QSTR_OPEN_DRAIN; - } else if (GPIO_IS_OUT(self->id)) { + } else if (gpio_is_dir_out(self->id)) { mode_qst = MP_QSTR_OUT; } else { mode_qst = MP_QSTR_IN; @@ -214,11 +208,11 @@ static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_prin } mp_printf(print, "Pin(%q, mode=%q", self->name, mode_qst); bool pull_up = false; - if (GPIO_IS_PULL_UP(self->id)) { + if (gpio_is_pulled_up(self->id)) { mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); pull_up = true; } - if (GPIO_IS_PULL_DOWN(self->id)) { + if (gpio_is_pulled_down(self->id)) { if (pull_up) { mp_printf(print, "|%q", MP_QSTR_PULL_DOWN); } else { @@ -411,7 +405,7 @@ static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { machine_pin_ext_set(self, self->last_output_value ^ 1); #endif } else if (GPIO_IS_OPEN_DRAIN(self->id)) { - if (GPIO_IS_OUT(self->id)) { + if (gpio_is_dir_out(self->id)) { gpio_set_dir(self->id, GPIO_IN); } else { gpio_set_dir(self->id, GPIO_OUT); From 45cb9b4444a7316a137f9c32942bc6c76f904f01 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 12 May 2025 11:08:54 +0100 Subject: [PATCH 0690/2098] rp2/machine_pin: Fix simulated open drain with more than 32 GPIOs. Changes are: - Refactor the open-drain macros, add GPIO_ENABLE/DISABLE_OPEN_DRAIN, and move them to `mphalport.h`. - Only use `uint64_t` for the open-drain mask if there are more than 32 GPIOs (saves code size). - Ensure we're shifting a `uint64_t` by using 1ULL constants. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 9 +++++---- ports/rp2/mphalport.h | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index db3ca69d8b1..7a2de0c0bc6 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -46,9 +46,6 @@ #define GPIO_IRQ_ALL (0xf) -// Open drain behaviour is simulated. -#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) - #ifndef MICROPY_HW_PIN_RESERVED #define MICROPY_HW_PIN_RESERVED(i) (0) #endif @@ -83,7 +80,11 @@ static const mp_irq_methods_t machine_pin_irq_methods; 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. +#if NUM_BANK0_GPIOS > 32 uint64_t machine_pin_open_drain_mask; +#else +uint32_t machine_pin_open_drain_mask; +#endif #if MICROPY_HW_PIN_EXT_COUNT static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { @@ -292,7 +293,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 &= ~(1ULL << self->id); + GPIO_DISABLE_OPEN_DRAIN(self->id); } } diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 956db3ec702..5012b682b9d 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -123,7 +123,18 @@ 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) +// Open drain behaviour is simulated. +#if NUM_BANK0_GPIOS > 32 extern uint64_t machine_pin_open_drain_mask; +#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1ULL << id)) +#define GPIO_ENABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask |= (1ULL << id)) +#define GPIO_DISABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask &= ~(1ULL << id)) +#else +extern uint32_t machine_pin_open_drain_mask; +#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1U << id)) +#define GPIO_ENABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask |= (1U << id)) +#define GPIO_DISABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask &= ~(1U << id)) +#endif mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); @@ -133,13 +144,13 @@ static inline unsigned int mp_hal_pin_name(mp_hal_pin_obj_t pin) { static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { gpio_set_dir(pin, GPIO_IN); - machine_pin_open_drain_mask &= ~(1 << pin); + GPIO_DISABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { gpio_set_dir(pin, GPIO_OUT); - machine_pin_open_drain_mask &= ~(1 << pin); + GPIO_DISABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } @@ -151,7 +162,7 @@ static inline void mp_hal_pin_open_drain_with_value(mp_hal_pin_obj_t pin, int v) gpio_put(pin, 0); gpio_set_dir(pin, GPIO_OUT); } - machine_pin_open_drain_mask |= 1 << pin; + GPIO_ENABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } From 7f6fedef2aeb0234f3c7d35a735ade75b9fdeaf3 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 20 May 2025 13:23:25 -0600 Subject: [PATCH 0691/2098] rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA: Fix default I2C to use I2C1. Signed-off-by: Damien George --- ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h index 56071e18733..931391d972e 100644 --- a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h @@ -24,5 +24,9 @@ int mp_hal_is_pin_reserved(int n); #define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) +// Set the default I2C to I2C1 on pins 18 and 19 which route to the qwiic connector +#undef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C (1) + #define MICROPY_HW_I2C1_SDA (18) #define MICROPY_HW_I2C1_SCL (19) From dc1af386a81f0238d809b2443714dc8feba5f174 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Mon, 17 Mar 2025 09:19:44 -0600 Subject: [PATCH 0692/2098] esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32: Add SparkFun board. Add board definition files for SparkFun IoT RedBoard ESP32. Signed-off-by: Malcolm McKellips --- .../SPARKFUN_IOT_REDBOARD_ESP32/board.json | 23 +++++++++++++++++++ .../SPARKFUN_IOT_REDBOARD_ESP32/manifest.py | 2 ++ .../mpconfigboard.cmake | 7 ++++++ .../mpconfigboard.h | 14 +++++++++++ .../SPARKFUN_IOT_REDBOARD_ESP32/pins.csv | 22 ++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json new file mode 100644 index 00000000000..8bce1a00133 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x1000" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "WiFi", + "USB-C" + ], + "images": [ + "19177-Sparkfun_IoT_Redboard-ESP32.jpg" + ], + "mcu": "esp32", + "product": "ESP32 / WROOM", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-iot-redboard-esp32-development-board.html", + "vendor": "SparkFun" +} diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py new file mode 100644 index 00000000000..73446ecac98 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +require("sdcard") diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake new file mode 100644 index 00000000000..0ffcc38c877 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h new file mode 100644 index 00000000000..4fe888ac17c --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h @@ -0,0 +1,14 @@ +// Board and hardware specific configuration + +#define MICROPY_HW_BOARD_NAME "SparkFun IoT RedBoard ESP32" +#define MICROPY_HW_MCU_NAME "ESP32" + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (22) +#define MICROPY_HW_I2C0_SDA (21) + +#define MICROPY_HW_SPI1_SCK (18) +#define MICROPY_HW_SPI1_MOSI (23) +#define MICROPY_HW_SPI1_MISO (19) diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv new file mode 100644 index 00000000000..d37c51af8f7 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv @@ -0,0 +1,22 @@ +TX,GPIO1 +RX,GPIO3 +ALERT,GPIO4 +TCK,GPIO13 +TMS,GPIO14 +CS,GPIO5 +PICO,GPIO23 +POCI,GPIO19 +SCK,GPIO18 +SDA,GPIO21 +SCL,GPIO22 +A0,GPIO36 +A3,GPIO39 +A4,GPIO32 +A5,GPIO33 +A6,GPIO34 +A7,GPIO35 +LED,GPIO18 +LED_BLUE,GPIO18 +BLUE_LED,GPIO18 +RGB_LED,GPIO2 +NEOPIXEL,GPIO2 From 49f81d5046aaeb31f90626426363ae2518dbd810 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 May 2025 10:34:56 +1000 Subject: [PATCH 0693/2098] tests/float/math_constants.py: Test actual e and pi constant values. The existing test for `math.e` and `math.pi` constants can fail on certain targets if the functions `math.exp()` and/or `math.cos()` are not accurate enough (eg out by an LSB of float precision). For example this test currently fails on PYBD_SF6 which uses double precision floats (and that's due to the `lib/libm_dbl/exp.c` implementation not being exact). This commit changes this constant test so that it tests the actual constant value, not the evaluation of `exp()` and `cos()` functions. Signed-off-by: Damien George --- tests/float/math_constants.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/float/math_constants.py b/tests/float/math_constants.py index 2e4c3210526..21d822a01e3 100644 --- a/tests/float/math_constants.py +++ b/tests/float/math_constants.py @@ -1,11 +1,30 @@ # Tests various constants of the math module. + +import sys + try: - import math - from math import exp, cos + from array import array + from math import e, pi except ImportError: print("SKIP") raise SystemExit -print(math.e == exp(1.0)) +# Hexadecimal representations of e and pi constants. +e_truth_single = 0x402DF854 +pi_truth_single = 0x40490FDB +e_truth_double = 0x4005BF0A8B145769 +pi_truth_double = 0x400921FB54442D18 + +# Detect the floating-point precision of the system, to determine the exact values of +# the constants (parsing the float from a decimal string can lead to inaccuracies). +if float("1e300") == float("inf"): + # Single precision floats. + e_truth = array("f", e_truth_single.to_bytes(4, sys.byteorder))[0] + pi_truth = array("f", pi_truth_single.to_bytes(4, sys.byteorder))[0] +else: + # Double precision floats. + e_truth = array("d", e_truth_double.to_bytes(8, sys.byteorder))[0] + pi_truth = array("d", pi_truth_double.to_bytes(8, sys.byteorder))[0] -print(cos(math.pi)) +print("e:", e == e_truth or (e, e_truth, e - e_truth)) +print("pi:", pi == pi_truth or (pi, pi_truth, pi - pi_truth)) From 22f1d766334f96f48aaaa04a72faeb9a2a37b595 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 May 2025 17:38:22 +1000 Subject: [PATCH 0694/2098] shared/tinyusb: Use device event hook to schedule USB task. Previously MicroPython ports would linker-wrap dcd_event_handler in order to schedule the USB task callback to run when needed. TinyUSB 0.16 added proper support for an event hook to do the same thing without the hacky linker wrapping. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/alif/alif.mk | 4 ---- ports/esp32/esp32_common.cmake | 8 -------- ports/nrf/Makefile | 1 - ports/renesas-ra/Makefile | 3 --- ports/rp2/CMakeLists.txt | 1 - ports/samd/Makefile | 2 -- shared/tinyusb/mp_usbd.c | 13 ++----------- 7 files changed, 2 insertions(+), 30 deletions(-) diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index eee27b3b7d6..bb07a3aa202 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -103,10 +103,6 @@ CFLAGS += -Wl,-T$(BUILD)/ensemble.ld \ -Wl,--print-memory-usage \ -Wl,--no-warn-rwx-segment -ifeq ($(MCU_CORE),M55_HP) -CFLAGS += -Wl,--wrap=dcd_event_handler -endif - ################################################################################ # Source files and libraries diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 2e7b95b385b..09b12039130 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -97,10 +97,6 @@ if(MICROPY_PY_TINYUSB) list(APPEND MICROPY_INC_TINYUSB ${MICROPY_DIR}/shared/tinyusb/ ) - - list(APPEND MICROPY_LINK_TINYUSB - -Wl,--wrap=dcd_event_handler - ) endif() list(APPEND MICROPY_SOURCE_PORT @@ -261,10 +257,6 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-missing-field-initializers ) -target_link_options(${MICROPY_TARGET} PUBLIC - ${MICROPY_LINK_TINYUSB} -) - # Additional include directories needed for private NimBLE headers. target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 59e74dce421..d3d747186ff 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -268,7 +268,6 @@ SRC_C += $(addprefix lib/tinyusb/src/,\ portable/nordic/nrf5x/dcd_nrf5x.c \ ) -LDFLAGS += -Wl,--wrap=dcd_event_handler endif DRIVERS_SRC_C += $(addprefix modules/,\ diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index ec47510d981..fd74c60a856 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -157,9 +157,6 @@ LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" endif -# Hook tinyusb USB interrupt if used to service usb task. -LDFLAGS += --wrap=dcd_event_handler - # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 7cb0c60aa21..f0b278df2bb 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -525,7 +525,6 @@ 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=runtime_init_clocks ) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 005664f178d..bec530d803f 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -113,8 +113,6 @@ LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" endif -LDFLAGS += --wrap=dcd_event_handler - MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH) SRC_C += \ diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c index 7ccfa7018d4..f03f7f7db41 100644 --- a/shared/tinyusb/mp_usbd.c +++ b/shared/tinyusb/mp_usbd.c @@ -30,10 +30,6 @@ #include "mp_usbd.h" -#ifndef NO_QSTR -#include "device/dcd.h" -#endif - #if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE void mp_usbd_task(void) { @@ -47,13 +43,8 @@ void mp_usbd_task_callback(mp_sched_node_t *node) { #endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE -extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr); - -// If -Wl,--wrap=dcd_event_handler is passed to the linker, then this wrapper -// will be called and allows MicroPython to schedule the TinyUSB task when -// dcd_event_handler() is called from an ISR. -TU_ATTR_FAST_FUNC void __wrap_dcd_event_handler(dcd_event_t const *event, bool in_isr) { - __real_dcd_event_handler(event, in_isr); +// Schedule the TinyUSB task on demand, when there is a new USB device event +TU_ATTR_FAST_FUNC void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { mp_usbd_schedule_task(); mp_hal_wake_main_task_from_isr(); } From b36111b12c74889b1a9fdb2bdc63b8953389f30c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 May 2025 17:46:58 +1000 Subject: [PATCH 0695/2098] nrf: Revert "nrf/Makefile: Enable LTO by default only on newer gcc.". This reverts commit 62e0fa04a7a6f9044db1bb0f20ea7a2e00599921. Reverting as the only linker wrap needed for nrf port was removed in the parent commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/nrf/Makefile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index d3d747186ff..7b16974f970 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -129,14 +129,7 @@ CFLAGS_MCU_m4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 -mfpu=fpv4-s CFLAGS_MCU_m0 = $(CFLAGS_CORTEX_M) -fshort-enums -mtune=cortex-m0 -mcpu=cortex-m0 -mfloat-abi=soft -# linker wrap does not work with lto on older gcc/binutils: https://sourceware.org/bugzilla/show_bug.cgi?id=24406 -GCC_VERSION = $(shell arm-none-eabi-gcc -dumpversion) -GCC_MAJOR_VERS = $(word 1,$(subst ., ,$(GCC_VERSION))) -ifeq ($(shell test $(GCC_MAJOR_VERS) -ge 10; echo $$?),0) LTO ?= 1 -else -LTO ?= 0 -endif ifeq ($(LTO),1) CFLAGS += -flto From 670b7c93505a3995bcb7ba9af5cb721994414a1f Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Thu, 24 Oct 2024 15:52:55 +0530 Subject: [PATCH 0696/2098] zephyr/boards: Add support for BeaglePlay CC1352p7. - Enable support for FLASH and IEEE802154 subg radio - Requires Zephyr v3.8.0 Signed-off-by: Ayush Singh --- ports/zephyr/boards/beagleplay_cc1352p7.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 ports/zephyr/boards/beagleplay_cc1352p7.conf diff --git a/ports/zephyr/boards/beagleplay_cc1352p7.conf b/ports/zephyr/boards/beagleplay_cc1352p7.conf new file mode 100644 index 00000000000..f63d184e38e --- /dev/null +++ b/ports/zephyr/boards/beagleplay_cc1352p7.conf @@ -0,0 +1,13 @@ +# Flash drivers +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y + +# Networking +CONFIG_BT=n +CONFIG_NET_IPV4=n +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_L2_IEEE802154=y +CONFIG_NET_DHCPV4=n +CONFIG_NET_CONFIG_IEEE802154_CHANNEL=1 From 9dbae39348e2c95190d808bc14a0e8adb1c3b8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 13 May 2025 13:50:42 +0200 Subject: [PATCH 0697/2098] lib/littlefs: Update LittleFS to v2.11. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- lib/littlefs/lfs2.c | 80 +++++++++++++++++++++++++++------------- lib/littlefs/lfs2.h | 8 +++- lib/littlefs/lfs2_util.h | 16 ++++---- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index f9ce2cf2906..abdec19d7cb 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -3932,7 +3932,9 @@ static int lfs2_remove_(lfs2_t *lfs2, const char *path) { } lfs2->mlist = dir.next; - if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(lfs2_tag_type3(tag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -4076,8 +4078,10 @@ static int lfs2_rename_(lfs2_t *lfs2, const char *oldpath, const char *newpath) } lfs2->mlist = prevdir.next; - if (prevtag != LFS2_ERR_NOENT - && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(prevtag != LFS2_ERR_NOENT + && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -5233,40 +5237,64 @@ static int lfs2_fs_gc_(lfs2_t *lfs2) { #endif #ifndef LFS2_READONLY +#ifdef LFS2_SHRINKNONRELOCATING +static int lfs2_shrink_checkblock(void *data, lfs2_block_t block) { + lfs2_size_t threshold = *((lfs2_size_t*)data); + if (block >= threshold) { + return LFS2_ERR_NOTEMPTY; + } + return 0; +} +#endif + static int lfs2_fs_grow_(lfs2_t *lfs2, lfs2_size_t block_count) { - // shrinking is not supported - LFS2_ASSERT(block_count >= lfs2->block_count); + int err; - if (block_count > lfs2->block_count) { - lfs2->block_count = block_count; + if (block_count == lfs2->block_count) { + return 0; + } - // fetch the root - lfs2_mdir_t root; - int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + +#ifndef LFS2_SHRINKNONRELOCATING + // shrinking is not supported + LFS2_ASSERT(block_count >= lfs2->block_count); +#endif +#ifdef LFS2_SHRINKNONRELOCATING + if (block_count < lfs2->block_count) { + err = lfs2_fs_traverse_(lfs2, lfs2_shrink_checkblock, &block_count, true); if (err) { return err; } + } +#endif - // update the superblock - lfs2_superblock_t superblock; - lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), - LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - return tag; - } - lfs2_superblock_fromle32(&superblock); + lfs2->block_count = block_count; - superblock.block_count = lfs2->block_count; + // fetch the root + lfs2_mdir_t root; + err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } - lfs2_superblock_tole32(&superblock); - err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( - {tag, &superblock})); - if (err) { - return err; - } + // update the superblock + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; } + lfs2_superblock_fromle32(&superblock); + + superblock.block_count = lfs2->block_count; + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } return 0; } #endif diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index f503fd00bcf..77477aaff83 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x0002000a +#define LFS2_VERSION 0x0002000b #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -766,7 +766,11 @@ int lfs2_fs_gc(lfs2_t *lfs2); // Grows the filesystem to a new size, updating the superblock with the new // block count. // -// Note: This is irreversible. +// If LFS2_SHRINKNONRELOCATING is defined, this function will also accept +// block_counts smaller than the current configuration, after checking +// that none of the blocks that are being removed are in use. +// Note that littlefs's pseudorandom block allocation means that +// this is very unlikely to work in the general case. // // Returns a negative error code on failure. int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count); diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 3b191f6885f..12c82a630b0 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -195,10 +195,10 @@ static inline uint32_t lfs2_fromle32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) return __builtin_bswap32(a); #else - return (((uint8_t*)&a)[0] << 0) | - (((uint8_t*)&a)[1] << 8) | - (((uint8_t*)&a)[2] << 16) | - (((uint8_t*)&a)[3] << 24); + return ((uint32_t)((uint8_t*)&a)[0] << 0) | + ((uint32_t)((uint8_t*)&a)[1] << 8) | + ((uint32_t)((uint8_t*)&a)[2] << 16) | + ((uint32_t)((uint8_t*)&a)[3] << 24); #endif } @@ -218,10 +218,10 @@ static inline uint32_t lfs2_frombe32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return a; #else - return (((uint8_t*)&a)[0] << 24) | - (((uint8_t*)&a)[1] << 16) | - (((uint8_t*)&a)[2] << 8) | - (((uint8_t*)&a)[3] << 0); + return ((uint32_t)((uint8_t*)&a)[0] << 24) | + ((uint32_t)((uint8_t*)&a)[1] << 16) | + ((uint32_t)((uint8_t*)&a)[2] << 8) | + ((uint32_t)((uint8_t*)&a)[3] << 0); #endif } From 2dada065ac4333d8623fd2c7509c47d15d8c9a5e Mon Sep 17 00:00:00 2001 From: Herwin Grobben Date: Thu, 22 May 2025 12:26:59 +0200 Subject: [PATCH 0698/2098] stm32: Allow QSPI to work on STM32G4. Adding a QSPI memory chip on a STM32G4 does not work due to some small issues, which are fixed in this commit: - Rename QUADSPI1_xxx alt-func names to QUADSPI_xxx, to match the static names used in `qspi.c`. - Enable `mpu.h` macros on G4. - Don't include I- and D-cache invalidation on G4. Signed-off-by: Damien George --- ports/stm32/boards/stm32g474_af.csv | 58 ++++++++++++++--------------- ports/stm32/mpu.h | 2 +- ports/stm32/qspi.c | 2 + 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/ports/stm32/boards/stm32g474_af.csv b/ports/stm32/boards/stm32g474_af.csv index 5853f5f9008..59a0e91a14f 100644 --- a/ports/stm32/boards/stm32g474_af.csv +++ b/ports/stm32/boards/stm32g474_af.csv @@ -2,12 +2,12 @@ Port ,Pin ,AF0 ,AF1 ,AF2 , ,I2C4/SYS_AF ,LPTIM1/TIM2/5/15/16/17,I2C1/3/TIM1/2/3/4/5/8/20/15/COMP1,QUADSPI1/I2C3/4/SAI1/USB/HRTIM1/TIM8/20/15/COMP3,I2C1/2/3/4/TIM1/8/16/17,QUADSPI1/SPI1/2/3/4/I2S2/3/I2C4/UART4/5/TIM8/Infrared,QUADSPI1/SPI2/3/I2S2/3/TIM1/5/8/20/Infrared,USART1/2/3/CAN/COMP7/5/6,I2C3/4/UART4/5/LPUART1/COMP1/2/7/4/5/6/3,CAN/TIM1/8/15/CAN1/2,QUADSPI1/TIM2/3/4/8/17,LPTIM1/TIM1/8/CAN1/3,FMC/LPUART1/SAI1/HRTIM1/TIM1,SAI1SAI1/HRTIM1/OPAMP2,UART4/5/SAI1/TIM2/15/UCPD1,SYS ,ADC PortA,PA0 , ,TIM2_CH1 ,TIM5_CH1 , , , , ,USART2_CTS ,COMP1_OUT ,TIM8_BKIN ,TIM8_ETR , , , ,TIM2_ETR ,EVENTOUT,ADC12_IN1 PortA,PA1 ,RTC_REFIN ,TIM2_CH2 ,TIM5_CH2 , , , , ,USART2_RTS_DE , ,TIM15_CH1N , , , , , ,EVENTOUT,ADC12_IN2 -PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3 , , , , ,USART2_TX ,COMP2_OUT ,TIM15_CH1 ,QUADSPI1_BK1_NCS , ,LPUART1_TX , ,UCPD1_FRSTX ,EVENTOUT,ADC1_IN3 -PortA,PA3 , ,TIM2_CH4 ,TIM5_CH4 ,SAI1_CK1 , , , ,USART2_RX , ,TIM15_CH2 ,QUADSPI1_CLK , ,LPUART1_RX ,SAI1_MCLK_A , ,EVENTOUT,ADC1_IN4 +PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3 , , , , ,USART2_TX ,COMP2_OUT ,TIM15_CH1 ,QUADSPI_BK1_NCS , ,LPUART1_TX , ,UCPD1_FRSTX ,EVENTOUT,ADC1_IN3 +PortA,PA3 , ,TIM2_CH4 ,TIM5_CH4 ,SAI1_CK1 , , , ,USART2_RX , ,TIM15_CH2 ,QUADSPI_CLK , ,LPUART1_RX ,SAI1_MCLK_A , ,EVENTOUT,ADC1_IN4 PortA,PA4 , , ,TIM3_CH2 , , ,SPI1_NSS ,SPI3_NSS/I2S3_WS ,USART2_CK , , , , , ,SAI1_FS_B , ,EVENTOUT,ADC2_IN17 PortA,PA5 , ,TIM2_CH1 ,TIM2_ETR , , ,SPI1_SCK , , , , , , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN13 -PortA,PA6 , ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_BKIN ,SPI1_MISO ,TIM1_BKIN , ,COMP1_OUT , ,QUADSPI1_BK1_IO3 , ,LPUART1_CTS , , ,EVENTOUT,ADC2_IN3 -PortA,PA7 , ,TIM17_CH1 ,TIM3_CH2 , ,TIM8_CH1N ,SPI1_MOSI ,TIM1_CH1N , ,COMP2_OUT , ,QUADSPI1_BK1_IO2 , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN4 +PortA,PA6 , ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_BKIN ,SPI1_MISO ,TIM1_BKIN , ,COMP1_OUT , ,QUADSPI_BK1_IO3 , ,LPUART1_CTS , , ,EVENTOUT,ADC2_IN3 +PortA,PA7 , ,TIM17_CH1 ,TIM3_CH2 , ,TIM8_CH1N ,SPI1_MOSI ,TIM1_CH1N , ,COMP2_OUT , ,QUADSPI_BK1_IO2 , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN4 PortA,PA8 ,MCO , ,I2C3_SCL , ,I2C2_SDA ,I2S2_MCK ,TIM1_CH1 ,USART1_CK ,COMP7_OUT , ,TIM4_ETR ,CAN3_RX ,SAI1_CK2 ,HRTIM1_CHA1 ,SAI1_SCK_A ,EVENTOUT,ADC5_IN1 PortA,PA9 , , ,I2C3_SMBA , ,I2C2_SCL ,I2S3_MCK ,TIM1_CH2 ,USART1_TX ,COMP5_OUT ,TIM15_BKIN ,TIM2_CH3 , , ,HRTIM1_CHA2 ,SAI1_FS_A ,EVENTOUT,ADC5_IN2 PortA,PA10, ,TIM17_BKIN , ,USB_CRS_SYNC ,I2C2_SMBA ,SPI2_MISO ,TIM1_CH3 ,USART1_RX ,COMP6_OUT , ,TIM2_CH4 ,TIM8_BKIN ,SAI1_D1 ,HRTIM1_CHB1 ,SAI1_SD_A ,EVENTOUT, @@ -16,9 +16,9 @@ PortA,PA12, ,TIM16_CH1 , PortA,PA13,SWDIOJTMS ,TIM16_CH1N , ,I2C4_SCL ,I2C1_SCL ,IR_OUT , ,USART3_CTS , , ,TIM4_CH3 , , ,SAI1_SD_B , ,EVENTOUT, PortA,PA14,SWCLKJTCK ,LPTIM1_OUT , ,I2C4_SMBA ,I2C1_SDA ,TIM8_CH2 ,TIM1_BKIN ,USART2_TX , , , , , ,SAI1_FS_B , ,EVENTOUT, PortA,PA15,JTDI ,TIM2_CH1 ,TIM8_CH1 , ,I2C1_SCL ,SPI1_NSS ,SPI3_NSS/I2S3_WS ,USART2_RX ,UART4_RTS_DE ,TIM1_BKIN , ,CAN3_TX , ,HRTIM1_FLT2 ,TIM2_ETR ,EVENTOUT, -PortB,PB0 , , ,TIM3_CH3 , ,TIM8_CH2N , ,TIM1_CH2N , , , ,QUADSPI1_BK1_IO1 , , ,HRTIM1_FLT5 ,UCPD1_FRSTX ,EVENTOUT,ADC3_IN12/ADC1_IN15 -PortB,PB1 , , ,TIM3_CH4 , ,TIM8_CH3N , ,TIM1_CH3N , ,COMP4_OUT , ,QUADSPI1_BK1_IO0 , ,LPUART1_RTS_DE ,HRTIM1_SCOUT , ,EVENTOUT,ADC3_IN1/ADC1_IN12 -PortB,PB2 ,RTC_OUT2 ,LPTIM1_OUT ,TIM5_CH1 ,TIM20_CH1 ,I2C3_SMBA , , , , , ,QUADSPI1_BK2_IO1 , , ,HRTIM1_SCIN , ,EVENTOUT,ADC2_IN12 +PortB,PB0 , , ,TIM3_CH3 , ,TIM8_CH2N , ,TIM1_CH2N , , , ,QUADSPI_BK1_IO1 , , ,HRTIM1_FLT5 ,UCPD1_FRSTX ,EVENTOUT,ADC3_IN12/ADC1_IN15 +PortB,PB1 , , ,TIM3_CH4 , ,TIM8_CH3N , ,TIM1_CH3N , ,COMP4_OUT , ,QUADSPI_BK1_IO0 , ,LPUART1_RTS_DE ,HRTIM1_SCOUT , ,EVENTOUT,ADC3_IN1/ADC1_IN12 +PortB,PB2 ,RTC_OUT2 ,LPTIM1_OUT ,TIM5_CH1 ,TIM20_CH1 ,I2C3_SMBA , , , , , ,QUADSPI_BK2_IO1 , , ,HRTIM1_SCIN , ,EVENTOUT,ADC2_IN12 PortB,PB3 ,JTDOTRACESWO,TIM2_CH2 ,TIM4_ETR ,USB_CRS_SYNC ,TIM8_CH1N ,SPI1_SCK ,SPI3_SCK/I2S3_CK ,USART2_TX , , ,TIM3_ETR ,CAN3_RX ,HRTIM1_SCOUT ,HRTIM1_EEV9 ,SAI1_SCK_B ,EVENTOUT, PortB,PB4 ,JTRST ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_CH2N ,SPI1_MISO ,SPI3_MISO ,USART2_RX ,UART5_RTS_DE , ,TIM17_BKIN ,CAN3_TX , ,HRTIM1_EEV7 ,SAI1_MCLK_B ,EVENTOUT, PortB,PB5 , ,TIM16_BKIN ,TIM3_CH2 ,TIM8_CH3N ,I2C1_SMBA ,SPI1_MOSI ,SPI3_MOSI/I2S3_SD ,USART2_CK ,I2C3_SDA ,CAN2_RX ,TIM17_CH1 ,LPTIM1_IN1 ,SAI1_SD_B ,HRTIM1_EEV6 ,UART5_CTS ,EVENTOUT, @@ -26,17 +26,17 @@ PortB,PB6 , ,TIM16_CH1N ,TIM4_CH1 PortB,PB7 , ,TIM17_CH1N ,TIM4_CH2 ,I2C4_SDA ,I2C1_SDA ,TIM8_BKIN , ,USART1_RX ,COMP3_OUT , ,TIM3_CH4 ,LPTIM1_IN2 ,FMC_NL ,HRTIM1_EEV3 ,UART4_CTS ,EVENTOUT, PortB,PB8 , ,TIM16_CH1 ,TIM4_CH3 ,SAI1_CK1 ,I2C1_SCL , , ,USART3_RX ,COMP1_OUT ,CAN1_RX ,TIM8_CH2 , ,TIM1_BKIN ,HRTIM1_EEV8 ,SAI1_MCLK_A ,EVENTOUT, PortB,PB9 , ,TIM17_CH1 ,TIM4_CH4 ,SAI1_D2 ,I2C1_SDA , ,IR_OUT ,USART3_TX ,COMP2_OUT ,CAN1_TX ,TIM8_CH3 , ,TIM1_CH3N ,HRTIM1_EEV5 ,SAI1_FS_A ,EVENTOUT, -PortB,PB10, ,TIM2_CH3 , , , , , ,USART3_TX ,LPUART1_RX , ,QUADSPI1_CLK , ,TIM1_BKIN ,HRTIM1_FLT3 ,SAI1_SCK_A ,EVENTOUT, -PortB,PB11, ,TIM2_CH4 , , , , , ,USART3_RX ,LPUART1_TX , ,QUADSPI1_BK1_NCS , , ,HRTIM1_FLT4 , ,EVENTOUT,ADC12_IN14 +PortB,PB10, ,TIM2_CH3 , , , , , ,USART3_TX ,LPUART1_RX , ,QUADSPI_CLK , ,TIM1_BKIN ,HRTIM1_FLT3 ,SAI1_SCK_A ,EVENTOUT, +PortB,PB11, ,TIM2_CH4 , , , , , ,USART3_RX ,LPUART1_TX , ,QUADSPI_BK1_NCS , , ,HRTIM1_FLT4 , ,EVENTOUT,ADC12_IN14 PortB,PB12, , ,TIM5_ETR , ,I2C2_SMBA ,SPI2_NSS/I2S2_WS ,TIM1_BKIN ,USART3_CK ,LPUART1_RTS_DE ,CAN2_RX , , , ,HRTIM1_CHC1 , ,EVENTOUT,ADC4_IN3/ADC1_IN11 PortB,PB13, , , , , ,SPI2_SCK/I2S2_CK ,TIM1_CH1N ,USART3_CTS ,LPUART1_CTS ,CAN2_TX , , , ,HRTIM1_CHC2 , ,EVENTOUT,ADC3_IN5 PortB,PB14, ,TIM15_CH1 , , , ,SPI2_MISO ,TIM1_CH2N ,USART3_RTS_DE ,COMP4_OUT , , , , ,HRTIM1_CHD1 , ,EVENTOUT,ADC4_IN4/ADC1_IN5 PortB,PB15,RTC_REFIN ,TIM15_CH2 ,TIM15_CH1N ,COMP3_OUT ,TIM1_CH3N ,SPI2_MOSI/I2S2_SD , , , , , , , ,HRTIM1_CHD2 , ,EVENTOUT,ADC4_IN5/ADC2_IN15 PortC,PC0 , ,LPTIM1_IN1 ,TIM1_CH1 , , , , , ,LPUART1_RX , , , , , , ,EVENTOUT,ADC12_IN6 -PortC,PC1 , ,LPTIM1_OUT ,TIM1_CH2 , , , , , ,LPUART1_TX , ,QUADSPI1_BK2_IO0 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN7 -PortC,PC2 , ,LPTIM1_IN2 ,TIM1_CH3 ,COMP3_OUT , , ,TIM20_CH2 , , , ,QUADSPI1_BK2_IO1 , , , , ,EVENTOUT,ADC12_IN8 -PortC,PC3 , ,LPTIM1_ETR ,TIM1_CH4 ,SAI1_D1 , , ,TIM1_BKIN2 , , , ,QUADSPI1_BK2_IO2 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN9 -PortC,PC4 , , ,TIM1_ETR , ,I2C2_SCL , , ,USART1_TX , , ,QUADSPI1_BK2_IO3 , , , , ,EVENTOUT,ADC2_IN5 +PortC,PC1 , ,LPTIM1_OUT ,TIM1_CH2 , , , , , ,LPUART1_TX , ,QUADSPI_BK2_IO0 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN7 +PortC,PC2 , ,LPTIM1_IN2 ,TIM1_CH3 ,COMP3_OUT , , ,TIM20_CH2 , , , ,QUADSPI_BK2_IO1 , , , , ,EVENTOUT,ADC12_IN8 +PortC,PC3 , ,LPTIM1_ETR ,TIM1_CH4 ,SAI1_D1 , , ,TIM1_BKIN2 , , , ,QUADSPI_BK2_IO2 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN9 +PortC,PC4 , , ,TIM1_ETR , ,I2C2_SCL , , ,USART1_TX , , ,QUADSPI_BK2_IO3 , , , , ,EVENTOUT,ADC2_IN5 PortC,PC5 , , ,TIM15_BKIN ,SAI1_D3 , , ,TIM1_CH4N ,USART1_RX , , , , , ,HRTIM1_EEV10 , ,EVENTOUT,ADC2_IN11 PortC,PC6 , , ,TIM3_CH1 ,HRTIM1_EEV10 ,TIM8_CH1 , ,I2S2_MCK ,COMP6_OUT ,I2C4_SCL , , , , ,HRTIM1_CHF1 , ,EVENTOUT, PortC,PC7 , , ,TIM3_CH2 ,HRTIM1_FLT5 ,TIM8_CH2 , ,I2S3_MCK ,COMP5_OUT ,I2C4_SDA , , , , ,HRTIM1_CHF2 , ,EVENTOUT, @@ -51,11 +51,11 @@ PortC,PC15, , , PortD,PD0 , , , , , , ,TIM8_CH4N , , ,CAN1_RX , , ,FMC_D2 , , ,EVENTOUT, PortD,PD1 , , , , ,TIM8_CH4 , ,TIM8_BKIN2 , , ,CAN1_TX , , ,FMC_D3 , , ,EVENTOUT, PortD,PD2 , , ,TIM3_ETR , ,TIM8_BKIN ,UART5_RX , , , , , , , , , ,EVENTOUT, -PortD,PD3 , , ,TIM2_CH1/TIM2_ETR , , , , ,USART2_CTS , , ,QUADSPI1_BK2_NCS , ,FMC_CLK , , ,EVENTOUT, -PortD,PD4 , , ,TIM2_CH2 , , , , ,USART2_RTS_DE , , ,QUADSPI1_BK2_IO0 , ,FMC_NOE , , ,EVENTOUT, -PortD,PD5 , , , , , , , ,USART2_TX , , ,QUADSPI1_BK2_IO1 , ,FMC_NWE , , ,EVENTOUT, -PortD,PD6 , , ,TIM2_CH4 ,SAI1_D1 , , , ,USART2_RX , , ,QUADSPI1_BK2_IO2 , ,FMC_NWAIT ,SAI1_SD_A , ,EVENTOUT, -PortD,PD7 , , ,TIM2_CH3 , , , , ,USART2_CK , , ,QUADSPI1_BK2_IO3 , ,FMC_NCE/FMC_NE1 , , ,EVENTOUT, +PortD,PD3 , , ,TIM2_CH1/TIM2_ETR , , , , ,USART2_CTS , , ,QUADSPI_BK2_NCS , ,FMC_CLK , , ,EVENTOUT, +PortD,PD4 , , ,TIM2_CH2 , , , , ,USART2_RTS_DE , , ,QUADSPI_BK2_IO0 , ,FMC_NOE , , ,EVENTOUT, +PortD,PD5 , , , , , , , ,USART2_TX , , ,QUADSPI_BK2_IO1 , ,FMC_NWE , , ,EVENTOUT, +PortD,PD6 , , ,TIM2_CH4 ,SAI1_D1 , , , ,USART2_RX , , ,QUADSPI_BK2_IO2 , ,FMC_NWAIT ,SAI1_SD_A , ,EVENTOUT, +PortD,PD7 , , ,TIM2_CH3 , , , , ,USART2_CK , , ,QUADSPI_BK2_IO3 , ,FMC_NCE/FMC_NE1 , , ,EVENTOUT, PortD,PD8 , , , , , , , ,USART3_TX , , , , ,FMC_D13 , , ,EVENTOUT,ADC4_IN12/ADC5_IN12 PortD,PD9 , , , , , , , ,USART3_RX , , , , ,FMC_D14 , , ,EVENTOUT,ADC4_IN13/ADC5_IN13 PortD,PD10, , , , , , , ,USART3_CK , , , , ,FMC_D15 , , ,EVENTOUT,ADC345_IN7 @@ -74,23 +74,23 @@ PortE,PE6 ,TRACED3 , , PortE,PE7 , , ,TIM1_ETR , , , , , , , , , ,FMC_D4 ,SAI1_SD_B , ,EVENTOUT,ADC3_IN4 PortE,PE8 , ,TIM5_CH3 ,TIM1_CH1N , , , , , , , , , ,FMC_D5 ,SAI1_SCK_B , ,EVENTOUT,ADC345_IN6 PortE,PE9 , ,TIM5_CH4 ,TIM1_CH1 , , , , , , , , , ,FMC_D6 ,SAI1_FS_B , ,EVENTOUT,ADC3_IN2 -PortE,PE10, , ,TIM1_CH2N , , , , , , , ,QUADSPI1_CLK , ,FMC_D7 ,SAI1_MCLK_B , ,EVENTOUT,ADC345_IN14 -PortE,PE11, , ,TIM1_CH2 , , ,SPI4_NSS , , , , ,QUADSPI1_BK1_NCS , ,FMC_D8 , , ,EVENTOUT,ADC345_IN15 -PortE,PE12, , ,TIM1_CH3N , , ,SPI4_SCK , , , , ,QUADSPI1_BK1_IO0 , ,FMC_D9 , , ,EVENTOUT,ADC345_IN16 -PortE,PE13, , ,TIM1_CH3 , , ,SPI4_MISO , , , , ,QUADSPI1_BK1_IO1 , ,FMC_D10 , , ,EVENTOUT,ADC3_IN3 -PortE,PE14, , ,TIM1_CH4 , , ,SPI4_MOSI ,TIM1_BKIN2 , , , ,QUADSPI1_BK1_IO2 , ,FMC_D11 , , ,EVENTOUT,ADC4_IN1 -PortE,PE15, , ,TIM1_BKIN , , , ,TIM1_CH4N ,USART3_RX , , ,QUADSPI1_BK1_IO3 , ,FMC_D12 , , ,EVENTOUT,ADC4_IN2 +PortE,PE10, , ,TIM1_CH2N , , , , , , , ,QUADSPI_CLK , ,FMC_D7 ,SAI1_MCLK_B , ,EVENTOUT,ADC345_IN14 +PortE,PE11, , ,TIM1_CH2 , , ,SPI4_NSS , , , , ,QUADSPI_BK1_NCS , ,FMC_D8 , , ,EVENTOUT,ADC345_IN15 +PortE,PE12, , ,TIM1_CH3N , , ,SPI4_SCK , , , , ,QUADSPI_BK1_IO0 , ,FMC_D9 , , ,EVENTOUT,ADC345_IN16 +PortE,PE13, , ,TIM1_CH3 , , ,SPI4_MISO , , , , ,QUADSPI_BK1_IO1 , ,FMC_D10 , , ,EVENTOUT,ADC3_IN3 +PortE,PE14, , ,TIM1_CH4 , , ,SPI4_MOSI ,TIM1_BKIN2 , , , ,QUADSPI_BK1_IO2 , ,FMC_D11 , , ,EVENTOUT,ADC4_IN1 +PortE,PE15, , ,TIM1_BKIN , , , ,TIM1_CH4N ,USART3_RX , , ,QUADSPI_BK1_IO3 , ,FMC_D12 , , ,EVENTOUT,ADC4_IN2 PortF,PF0 , , , , ,I2C2_SDA ,SPI2_NSS/I2S2_WS ,TIM1_CH3N , , , , , , , , ,EVENTOUT,ADC1_IN10 PortF,PF1 , , , , , ,SPI2_SCK/I2S2_CK , , , , , , , , , ,EVENTOUT,ADC2_IN10 PortF,PF2 , , ,TIM20_CH3 , ,I2C2_SMBA , , , , , , , ,FMC_A2 , , ,EVENTOUT, PortF,PF3 , , ,TIM20_CH4 , ,I2C3_SCL , , , , , , , ,FMC_A3 , , ,EVENTOUT, PortF,PF4 , , ,COMP1_OUT ,TIM20_CH1N ,I2C3_SDA , , , , , , , ,FMC_A4 , , ,EVENTOUT, PortF,PF5 , , ,TIM20_CH2N , , , , , , , , , ,FMC_A5 , , ,EVENTOUT, -PortF,PF6 , ,TIM5_ETR ,TIM4_CH4 ,SAI1_SD_B ,I2C2_SCL , ,TIM5_CH1 ,USART3_RTS , , ,QUADSPI1_BK1_IO3 , , , , ,EVENTOUT, -PortF,PF7 , , ,TIM20_BKIN , , , ,TIM5_CH2 , , , ,QUADSPI1_BK1_IO2 , ,FMC_A1 ,SAI1_MCLK_B , ,EVENTOUT, -PortF,PF8 , , ,TIM20_BKIN2 , , , ,TIM5_CH3 , , , ,QUADSPI1_BK1_IO0 , ,FMC_A24 ,SAI1_SCK_B , ,EVENTOUT, -PortF,PF9 , , ,TIM20_BKIN ,TIM15_CH1 , ,SPI2_SCK ,TIM5_CH4 , , , ,QUADSPI1_BK1_IO1 , ,FMC_A25 ,SAI1_FS_B , ,EVENTOUT, -PortF,PF10, , ,TIM20_BKIN2 ,TIM15_CH2 , ,SPI2_SCK , , , , ,QUADSPI1_CLK , ,FMC_A0 ,SAI1_D3 , ,EVENTOUT, +PortF,PF6 , ,TIM5_ETR ,TIM4_CH4 ,SAI1_SD_B ,I2C2_SCL , ,TIM5_CH1 ,USART3_RTS , , ,QUADSPI_BK1_IO3 , , , , ,EVENTOUT, +PortF,PF7 , , ,TIM20_BKIN , , , ,TIM5_CH2 , , , ,QUADSPI_BK1_IO2 , ,FMC_A1 ,SAI1_MCLK_B , ,EVENTOUT, +PortF,PF8 , , ,TIM20_BKIN2 , , , ,TIM5_CH3 , , , ,QUADSPI_BK1_IO0 , ,FMC_A24 ,SAI1_SCK_B , ,EVENTOUT, +PortF,PF9 , , ,TIM20_BKIN ,TIM15_CH1 , ,SPI2_SCK ,TIM5_CH4 , , , ,QUADSPI_BK1_IO1 , ,FMC_A25 ,SAI1_FS_B , ,EVENTOUT, +PortF,PF10, , ,TIM20_BKIN2 ,TIM15_CH2 , ,SPI2_SCK , , , , ,QUADSPI_CLK , ,FMC_A0 ,SAI1_D3 , ,EVENTOUT, PortF,PF11, , ,TIM20_ETR , , , , , , , , , ,FMC_NE4 , , ,EVENTOUT, PortF,PF12, , ,TIM20_CH1 , , , , , , , , , ,FMC_A6 , , ,EVENTOUT, PortF,PF13, , ,TIM20_CH2 , ,I2C4_SMBA , , , , , , , ,FMC_A7 , , ,EVENTOUT, diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index a87c04a58df..5756cb0560d 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -28,7 +28,7 @@ #include "irq.h" -#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32WB) #define MPU_REGION_ETH (MPU_REGION_NUMBER0) #define MPU_REGION_QSPI1 (MPU_REGION_NUMBER1) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 781aae803ef..2ef9a4d0187 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -213,11 +213,13 @@ static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { qspi_memory_map(); break; case MP_QSPI_IOCTL_MEMORY_MODIFIED: { + #if defined(__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U) uintptr_t *addr_len = (uintptr_t *)arg; volatile void *addr = (volatile void *)(QSPI_MAP_ADDR + addr_len[0]); size_t len = addr_len[1]; SCB_InvalidateICache_by_Addr(addr, len); SCB_InvalidateDCache_by_Addr(addr, len); + #endif break; } } From 6bfb83e30aa28e7bbfb0f77f378da05b32574f3d Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 22 May 2025 10:40:48 -0600 Subject: [PATCH 0699/2098] rp2: Make FLASH LENGTH match PICO_FLASH_SIZE_BYTES in .ld files. With a fallback to default sizes if `PICO_FLASH_SIZE_BYTES` is not defined. Signed-off-by: Dryw Wade --- ports/rp2/CMakeLists.txt | 14 ++++++++++++++ ports/rp2/memmap_mp_rp2040.ld | 2 +- ports/rp2/memmap_mp_rp2350.ld | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index f0b278df2bb..cf9f1807928 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -528,6 +528,20 @@ target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--wrap=runtime_init_clocks ) +if(PICO_FLASH_SIZE_BYTES GREATER 0) + target_link_options(${MICROPY_TARGET} PRIVATE + -Wl,--defsym=__micropy_flash_size__=${PICO_FLASH_SIZE_BYTES} + ) +elseif(PICO_RP2040) + target_link_options(${MICROPY_TARGET} PRIVATE + -Wl,--defsym=__micropy_flash_size__=2048k # Default to 2MB + ) +elseif(PICO_RP2350) + target_link_options(${MICROPY_TARGET} PRIVATE + -Wl,--defsym=__micropy_flash_size__=4096k # Default to 4MB + ) +endif() + if(PICO_RP2350) target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--defsym=__micropy_extra_stack__=4096 diff --git a/ports/rp2/memmap_mp_rp2040.ld b/ports/rp2/memmap_mp_rp2040.ld index a5799cd88be..5c8d9f47188 100644 --- a/ports/rp2/memmap_mp_rp2040.ld +++ b/ports/rp2/memmap_mp_rp2040.ld @@ -23,7 +23,7 @@ MEMORY { - FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __micropy_flash_size__ RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k diff --git a/ports/rp2/memmap_mp_rp2350.ld b/ports/rp2/memmap_mp_rp2350.ld index 1e1cbbfd702..1c4770efe10 100644 --- a/ports/rp2/memmap_mp_rp2350.ld +++ b/ports/rp2/memmap_mp_rp2350.ld @@ -23,7 +23,7 @@ MEMORY { - FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 4096k + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __micropy_flash_size__ RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k From bbdc832ca9719947124ebcd70da8d08b38cae24b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 26 Mar 2025 16:22:04 +0100 Subject: [PATCH 0700/2098] samd/boards: Add two SparkFun SAMD21 boards. Add support for the boards: - SparkFun SAMD21 Dev Breakout - SparkFun RedBoard Turbo Both boards are SAMD21 based and actively sold by SparkFun. Signed-off-by: robert-hh --- docs/samd/pinout.rst | 118 ++++++++++++++++++ .../boards/SPARKFUN_REDBOARD_TURBO/board.json | 21 ++++ .../SPARKFUN_REDBOARD_TURBO/mpconfigboard.h | 11 ++ .../SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk | 8 ++ .../boards/SPARKFUN_REDBOARD_TURBO/pins.csv | 47 +++++++ .../SPARKFUN_SAMD21_DEV_BREAKOUT/board.json | 19 +++ .../mpconfigboard.h | 8 ++ .../mpconfigboard.mk | 4 + .../SPARKFUN_SAMD21_DEV_BREAKOUT/pins.csv | 45 +++++++ 9 files changed, 281 insertions(+) create mode 100644 ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json create mode 100644 ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.h create mode 100644 ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk create mode 100644 ports/samd/boards/SPARKFUN_REDBOARD_TURBO/pins.csv create mode 100644 ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json create mode 100644 ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.h create mode 100644 ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.mk create mode 100644 ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/pins.csv diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 1ad8f558840..5731a8fa769 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -650,6 +650,124 @@ Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. The board does not provide access to UART, I2C, SPI or DAC. +SparkFun Redboard Turbo assignment table +---------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 2 PA02 A0 2 0 - - - - + 40 PB08 A1 8 2 - 4/0 4/0 - + 41 PB09 A2 9 3 - 4/1 4/1 - + 4 PA04 A3 4 4 - 0/0 0/0 - + 5 PA05 A4 5 5 - 0/1 0/1 - + 34 PB02 A5 2 10 - 5/0 6/0 - + 11 PA11 D0 11 19 0/3 2/3 1/1 0/3 + 10 PA10 D1 10 18 0/2 2/2 1/0 0/2 + 14 PA14 D2 14 - 2/2 4/2 3/0 0/4 + 9 PA09 D3 9 17 0/1 2/1 0/1 1/3 + 8 PA08 D4 - 16 0/0 2/0 0/0 1/2 + 15 PA15 D5 15 - 2/3 4/3 3/1 0/5 + 20 PA20 D6 4 - 5/2 3/2 7/0 0/4 + 21 PA21 D7 5 - 5/3 3/3 7/1 0/7 + 6 PA06 D8 6 6 - 0/2 1/0 - + 7 PA07 D9 7 7 - 0/3 1/1 - + 11 PA11 RX 11 19 0/3 2/3 1/1 0/3 + 10 PA10 TX 10 18 0/2 2/2 1/0 0/2 + 3 PA03 AREF 3 1 - - - - + 18 PA18 D10 2 - 1/2 3/2 3/0 0/2 + 16 PA16 D11 0 - 1/0 3/0 2/0 0/6 + 19 PA19 D12 3 - 1/3 3/3 3/1 0/3 + 17 PA17 D13 1 - 1/1 3/1 2/1 0/7 + 13 PA13 FLASH_CS 13 - 2/1 4/1 2/0 0/7 + 35 PB03 FLASH_MISO 3 11 - 5/1 6/1 - + 54 PB22 FLASH_MOSI 6 - - 5/2 7/0 - + 55 PB23 FLASH_SCK 7 - - 5/3 7/1 - + 31 PA31 LED_RX 11 - - 1/3 1/1 - + 27 PA27 LED_TX 15 - - - - - + 12 PA12 MISO 12 - 2/0 4/0 2/0 0/6 + 42 PB10 MOSI 10 - - 4/2 5/0 0/4 + 30 PA30 NEOPIXEL 10 - - 1/2 1/0 - + 43 PB11 SCK 11 - - 4/3 5/1 0/5 + 23 PA23 SCL 7 - 3/1 5/1 4/1 0/5 + 22 PA22 SDA 6 - 3/0 5/0 4/0 0/4 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 0 PA00 0 - - 1/0 2/0 - + 1 PA01 1 - - 1/1 2/1 - + 28 PA28 8 - - - - - +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 0 at pins PA11/PA10, labelled RX/TX +- I2C 3 at pins PA22/PA23, labelled SDA/SCL +- SPI 4 at pins PB10/PA12/PB11, labelled MISO, MOSI and SCK +- DAC output on pin PA02, labelled A0 + + +SparkFun SAMD21 Dev Breakout assignment table +--------------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 2 PA02 A0 2 0 - - - - + 40 PB08 A1 8 2 - 4/0 4/0 - + 41 PB09 A2 9 3 - 4/1 4/1 - + 4 PA04 A3 4 4 - 0/0 0/0 - + 5 PA05 A4 5 5 - 0/1 0/1 - + 34 PB02 A5 2 10 - 5/0 6/0 - + 11 PA11 D0 11 19 0/3 2/3 1/1 0/3 + 10 PA10 D1 10 18 0/2 2/2 1/0 0/2 + 14 PA14 D2 14 - 2/2 4/2 3/0 0/4 + 9 PA09 D3 9 17 0/1 2/1 0/1 1/3 + 8 PA08 D4 - 16 0/0 2/0 0/0 1/2 + 15 PA15 D5 15 - 2/3 4/3 3/1 0/5 + 20 PA20 D6 4 - 5/2 3/2 7/0 0/4 + 21 PA21 D7 5 - 5/3 3/3 7/1 0/7 + 6 PA06 D8 6 6 - 0/2 1/0 - + 7 PA07 D9 7 7 - 0/3 1/1 - + 11 PA11 RX 11 19 0/3 2/3 1/1 0/3 + 10 PA10 TX 10 18 0/2 2/2 1/0 0/2 + 3 PA03 AREF 3 1 - - - - + 18 PA18 D10 2 - 1/2 3/2 3/0 0/2 + 16 PA16 D11 0 - 1/0 3/0 2/0 0/6 + 19 PA19 D12 3 - 1/3 3/3 3/1 0/3 + 17 PA17 D13 1 - 1/1 3/1 2/1 0/7 + 54 PB22 D30 6 - - 5/2 7/0 - + 55 PB23 D31 7 - - 5/3 7/1 - + 13 PA13 D38 13 - 2/1 4/1 2/0 0/7 + 35 PB03 LED_RX 3 11 - 5/1 6/1 - + 27 PA27 LED_TX 15 - - - - - + 12 PA12 MISO 12 - 2/0 4/0 2/0 0/6 + 42 PB10 MOSI 10 - - 4/2 5/0 0/4 + 43 PB11 SCK 11 - - 4/3 5/1 0/5 + 23 PA23 SCL 7 - 3/1 5/1 4/1 0/5 + 22 PA22 SDA 6 - 3/0 5/0 4/0 0/4 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 0 PA00 0 - - 1/0 2/0 - + 1 PA01 1 - - 1/1 2/1 - + 28 PA28 8 - - - - - +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 0 at pins PA11/PA10, labelled RX/TX +- I2C 3 at pins PA22/PA23, labelled SDA/SCL +- SPI 4 at pins PB10/PA12/PB11, labelled MISO, MOSI and SCK +- DAC output on pin PA02, labelled A0 SAMD21 Xplained PRO pin assignment table ---------------------------------------- diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json new file mode 100644 index 00000000000..b7ccac725d4 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "DAC", + "External Flash", + "USB", + "RGB LED" + ], + "images": [ + "sparkfun_readboard_turbo.jpg" + ], + "mcu": "samd21", + "product": "SparkFun RedBoard Turbo", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/14812", + "vendor": "Sparkfun" +} diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.h b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.h new file mode 100644 index 00000000000..cd0ec51acce --- /dev/null +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.h @@ -0,0 +1,11 @@ +#define MICROPY_HW_BOARD_NAME "SparkFun RedBoard Turbo" +#define MICROPY_HW_MCU_NAME "SAMD21G18A" + +#define MICROPY_HW_XOSC32K (1) + +#define MICROPY_HW_SPIFLASH (1) +#define MICROPY_HW_SPIFLASH_ID (5) + +#define MICROPY_HW_DEFAULT_UART_ID (0) +#define MICROPY_HW_DEFAULT_I2C_ID (3) +#define MICROPY_HW_DEFAULT_SPI_ID (4) diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk new file mode 100644 index 00000000000..6ea327a650d --- /dev/null +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk @@ -0,0 +1,8 @@ +MCU_SERIES = SAMD21 +CMSIS_MCU = SAMD21G18A +LD_FILES = boards/samd21x18a.ld sections.ld +TEXT0 = 0x2000 + +# The ?='s allow overriding in mpconfigboard.mk. +# MicroPython settings +MICROPY_HW_CODESIZE ?= 232K diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/pins.csv b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/pins.csv new file mode 100644 index 00000000000..f065fc89170 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/pins.csv @@ -0,0 +1,47 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +USB_DM,PA24 +USB_DP,PA25 + +SWCLK,PA30 +SWDIO,PA31 + +D0,PA11 +D1,PA10 +D2,PA14 +D3,PA09 +D4,PA08 +D5,PA15 +D6,PA20 +D7,PA21 +D8,PA06 +D9,PA07 +D10,PA18 +D11,PA16 +D12,PA19 +D13,PA17 +A0,PA02 +A1,PB08 +A2,PB09 +A3,PA04 +A4,PA05 +A5,PB02 +AREF,PA03 +RX,PA11 +TX,PA10 +SCL,PA23 +SDA,PA22 +MOSI,PB10 +MISO,PA12 +SCK,PB11 +FLASH_MOSI,PB22 +FLASH_MISO,PB03 +FLASH_SCK,PB23 +FLASH_CS,PA13 +NEOPIXEL,PA30 + +LED_TX,PA27 +LED_RX,PA31 diff --git a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json new file mode 100644 index 00000000000..b2e6553b2d2 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "DAC", + "USB" + ], + "images": [ + "sparkfun_sam21_dev_breakout.jpg" + ], + "mcu": "samd21", + "product": "SparkFun SAMD21 Dev Breakout", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/13672", + "vendor": "Sparkfun" +} diff --git a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.h new file mode 100644 index 00000000000..0679a9f4e4b --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "SparkFun SAMD21 Dev Breakout" +#define MICROPY_HW_MCU_NAME "SAMD21G18A" + +#define MICROPY_HW_XOSC32K (1) + +#define MICROPY_HW_DEFAULT_UART_ID (0) +#define MICROPY_HW_DEFAULT_I2C_ID (3) +#define MICROPY_HW_DEFAULT_SPI_ID (4) diff --git a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.mk new file mode 100644 index 00000000000..8696c966bc3 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/mpconfigboard.mk @@ -0,0 +1,4 @@ +MCU_SERIES = SAMD21 +CMSIS_MCU = SAMD21G18A +LD_FILES = boards/samd21x18a.ld sections.ld +TEXT0 = 0x2000 diff --git a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/pins.csv b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/pins.csv new file mode 100644 index 00000000000..794be318000 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/pins.csv @@ -0,0 +1,45 @@ +# The lines contain pairs of Pin name and Pin number. +# Pin names must be valid Python identifiers. +# Pin numbers have the form Pxnn, with x being A, B, C or D. +# Lines starting with # or empty lines are ignored. + +USB_DM,PA24 +USB_DP,PA25 + +SWCLK,PA30 +SWDIO,PA31 + +D0,PA11 +D1,PA10 +D2,PA14 +D3,PA09 +D4,PA08 +D5,PA15 +D6,PA20 +D7,PA21 +D8,PA06 +D9,PA07 +D10,PA18 +D11,PA16 +D12,PA19 +D13,PA17 +A0,PA02 +A1,PB08 +A2,PB09 +A3,PA04 +A4,PA05 +A5,PB02 +AREF,PA03 +RX,PA11 +TX,PA10 +SCL,PA23 +SDA,PA22 +MOSI,PB10 +MISO,PA12 +SCK,PB11 +D38,PA13 +D30,PB22 +D31,PB23 + +LED_TX,PA27 +LED_RX,PB03 From 2f864416c6535cdf33a85ea0092e2415dc29dde3 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 29 Dec 2024 21:32:44 +0100 Subject: [PATCH 0701/2098] docs/library/time: Amend the documentation of time.mktime(). By showing the argument and refer to epoch instead of a fixed date. The note about epoch lists the ports using the POSIX epoch. Signed-off-by: robert-hh --- docs/library/time.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/library/time.rst b/docs/library/time.rst index 8c1c1d4d6fb..b53bb133ec4 100644 --- a/docs/library/time.rst +++ b/docs/library/time.rst @@ -9,9 +9,10 @@ The ``time`` module provides functions for getting the current time and date, measuring time intervals, and for delays. -**Time Epoch**: Unix port uses standard for POSIX systems epoch of -1970-01-01 00:00:00 UTC. However, some embedded ports use epoch of -2000-01-01 00:00:00 UTC. Epoch year may be determined with ``gmtime(0)[0]``. +**Time Epoch**: The unix, windows, webassembly, alif, mimxrt and rp2 ports +use the standard for POSIX systems epoch of 1970-01-01 00:00:00 UTC. +The other embedded ports use an epoch of 2000-01-01 00:00:00 UTC. +Epoch year may be determined with ``gmtime(0)[0]``. **Maintaining actual calendar date/time**: This requires a Real Time Clock (RTC). On systems with underlying OS (including some @@ -57,11 +58,11 @@ Functions * weekday is 0-6 for Mon-Sun * yearday is 1-366 -.. function:: mktime() +.. function:: mktime(date_time_tuple) This is inverse function of localtime. It's argument is a full 8-tuple which expresses a time as per localtime. It returns an integer which is - the number of seconds since Jan 1, 2000. + the number of seconds since the time epoch. .. function:: sleep(seconds) From 92c219afd6377b10579d7511242cbc747b79a026 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 12 Jan 2025 12:16:26 +0100 Subject: [PATCH 0702/2098] docs/esp32/quickref: Mention the different timer counts. Since the are ESP32 variants with 1, 2 or 4 hardware timers. Signed-off-by: robert-hh --- docs/esp32/quickref.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 4e70ff255ec..c7d4a098e57 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -271,8 +271,10 @@ Use the :mod:`time
[\da-fA-F]{1,8})*' # symbol address + r'(?(weak)\));$', # optional weak marker end and line terminator + re.ASCII, + ) + + inside_comment = False + for line in (line.strip() for line in source.readlines()): + if line.startswith('/*') and not inside_comment: + if not line.endswith('*/'): + inside_comment = True + continue + if inside_comment: + if line.endswith('*/'): + inside_comment = False + continue + if line.startswith('//'): + continue + match = LINE_REGEX.match(''.join(line.split())) + if not match: + continue + tokens = match.groupdict() + symbol = tokens['symbol'] + address = int(tokens['address'], 16) + if symbol in symbols: + raise ValueError(f"Symbol {symbol} already defined") + symbols[symbol] = address + return symbols + + def main(): import argparse @@ -1500,6 +1567,13 @@ def main(): cmd_parser.add_argument( "--output", "-o", default=None, help="output .mpy file (default to input with .o->.mpy)" ) + cmd_parser.add_argument( + "--externs", + "-e", + type=argparse.FileType("rt"), + default=None, + help="linkerscript providing fixed-address symbols to augment symbol resolution", + ) cmd_parser.add_argument("files", nargs="+", help="input files") args = cmd_parser.parse_args() From 9ef7322a5d11937e061b85da12900eeee62e9891 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 9 Apr 2025 21:58:19 +0200 Subject: [PATCH 0728/2098] tests/run-natmodtests.py: Allow injected code customisation. This commit introduces a mechanism to customise the code that is injected to the board when performing a native module import. A new argument, "-b"/"--begin", is added so regular Python code can be inserted in the injected fragment between the module file creation and the effective module import. This is needed for running natmod tests on ESP8266 as that board does not have enough memory to fit certain modules unless additional configuration is performed. Signed-off-by: Alessandro Gatti --- tests/run-natmodtests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index b858989daa4..073e0b053e6 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -73,6 +73,7 @@ def open(self, path, mode): return __File() vfs.mount(__FS(), '/__remote') sys.path.insert(0, '/__remote') +{import_prelude} sys.modules['{}'] = __import__('__injected') """ @@ -133,6 +134,13 @@ def detect_architecture(target): def run_tests(target_truth, target, args, stats, resolved_arch): + global injected_import_hook_code + + prelude = "" + if args.begin: + prelude = args.begin.read() + injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prelude) + for test_file in args.files: # Find supported test test_file_basename = os.path.basename(test_file) @@ -212,6 +220,13 @@ def main(): cmd_parser.add_argument( "-a", "--arch", choices=AVAILABLE_ARCHS, help="override native architecture of the target" ) + cmd_parser.add_argument( + "-b", + "--begin", + type=argparse.FileType("rt"), + default=None, + help="prologue python file to execute before module import", + ) cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() From 0bf2fd7ad034404208d79339c1bd82136b1cd4e3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Apr 2025 22:36:26 +0200 Subject: [PATCH 0729/2098] examples/natmod/random: Fix build for Xtensa. This commit provides the appropriate external symbol addresses to let the "random" example natmod build for the Xtensa platform. On the ESP8266, signed integer division code isn't provided as part of libgcc.a, libm.a, or libc.a, but it is instead provided by the ROM. Regular builds inject the appropriate symbol addresses as part of the linking process (see eagle.rom.addr.v6.ld), but natmods need this information brought in from somewhere else. Signed-off-by: Alessandro Gatti --- examples/natmod/random/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/natmod/random/Makefile b/examples/natmod/random/Makefile index 8abdb66dc87..5d23eac1e9c 100644 --- a/examples/natmod/random/Makefile +++ b/examples/natmod/random/Makefile @@ -8,6 +8,10 @@ MOD = random_$(ARCH) SRC = random.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) -ARCH = x64 +ARCH ?= x64 + +ifeq ($(ARCH),xtensa) +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif include $(MPY_DIR)/py/dynruntime.mk From 462ee12d3c9b04d815eb347881b7b2f4951a17c1 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Apr 2025 22:45:45 +0200 Subject: [PATCH 0730/2098] examples/natmod/framebuf: Fix build for Xtensa. This commit provides the appropriate external symbol addresses to let the "framebuf" example natmod build for the Xtensa platform. On the ESP8266, integer division code isn't provided as part of libgcc.a, libm.a, or libc.a, but it is instead provided by the ROM. Regular builds inject the appropriate symbol addresses as part of the linking process (see eagle.rom.addr.v6.ld), but natmods need this information brought in from somewhere else. Signed-off-by: Alessandro Gatti --- examples/natmod/framebuf/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/natmod/framebuf/Makefile b/examples/natmod/framebuf/Makefile index 2e2b8159754..cb821736e70 100644 --- a/examples/natmod/framebuf/Makefile +++ b/examples/natmod/framebuf/Makefile @@ -8,6 +8,10 @@ MOD = framebuf_$(ARCH) SRC = framebuf.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) -ARCH = x64 +ARCH ?= x64 + +ifeq ($(ARCH),xtensa) +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif include $(MPY_DIR)/py/dynruntime.mk From 887125fc589de9ada0bc532d9bdc29a3806694c3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 1 Jun 2025 10:56:58 +0200 Subject: [PATCH 0731/2098] examples/natmod/deflate: Fix build for Xtensa. This commit provides the appropriate external symbol addresses to let the "deflate" example natmod build for the Xtensa platform. Unlike other natmods that require an external symbol list to build without bringing in the whole runtime libraries set, this natmod is referencing the `__modsi3` symbol which was removed from the ESP8266's SDK but not present in ROM. The latter only has a `__umodsi3` implementation that only operates on unsigned values, and thus unable to handle this natmod. Thus, the extended library resolution process is enabled for this natmod as a `__modsi3` implementation is made available that way (still using ROM symbols whenever possible). This also means that symbols that appear in both ROM and external libraries sort of co-exist in the final MPY file, with ROM symbols being used by natmod code but the implementation from the library still exists in the final MPY file, unused. Signed-off-by: Alessandro Gatti --- examples/natmod/deflate/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/natmod/deflate/Makefile b/examples/natmod/deflate/Makefile index 504130d5723..1f63de20d20 100644 --- a/examples/natmod/deflate/Makefile +++ b/examples/natmod/deflate/Makefile @@ -8,6 +8,12 @@ MOD = deflate_$(ARCH) SRC = deflate.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) -ARCH = x64 +ARCH ?= x64 + +ifeq ($(ARCH),xtensa) +# Link with libm.a and libgcc.a from the toolchain +LINK_RUNTIME = 1 +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif include $(MPY_DIR)/py/dynruntime.mk From 4227654d4294b778b2bbba8a3b735882786b1d2d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 1 Jun 2025 11:24:48 +0200 Subject: [PATCH 0732/2098] examples/natmod/btree: Fix build for Xtensa. This commit provides the appropriate external symbol addresses to let the "btree" example natmod build for the Xtensa platform. On the ESP8266, unsigned integer division code isn't provided as part of libgcc.a, libm.a, or libc.a, but it is instead provided by the ROM. Regular builds inject the appropriate symbol addresses as part of the linking process (see eagle.rom.addr.v6.ld), but natmods need this information brought in from somewhere else. Signed-off-by: Alessandro Gatti --- examples/natmod/btree/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index ff130d61b37..6273ccc6572 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -8,7 +8,7 @@ MOD = btree_$(ARCH) SRC = btree_c.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) -ARCH = x64 +ARCH ?= x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" @@ -32,6 +32,10 @@ SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ mpool/mpool.c \ ) +ifeq ($(ARCH),xtensa) +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif + include $(MPY_DIR)/py/dynruntime.mk # btree needs gnu99 defined From e4c0e2b73d0e8842160c86a39706d872ec226b6c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 1 Jun 2025 18:54:37 +0200 Subject: [PATCH 0733/2098] esp8266/main: Print error information on crash-induced reboots. This commit adds an optional configuration option for the ESP8266 port that, if the board rebooted due to a crash, will print to stdout some information about the error that triggered the issue. It is not possible using regular SDK functions to intercept errors and print information at that stage, and the only error response from the board is to reboot itself. This is the next best thing, print some error information just once at boot time after the crash - the least invasive option given the situation we're in. This is disabled by default, and can be enabled by enabling MICROPY_HW_HARD_FAULT_DEBUG in the port configuration - obviously with a small increase in the firmware code footprint. Signed-off-by: Alessandro Gatti --- ports/esp8266/main.c | 62 ++++++++++++++++++++++++++++++++++++ ports/esp8266/mpconfigport.h | 3 ++ 2 files changed, 65 insertions(+) diff --git a/ports/esp8266/main.c b/ports/esp8266/main.c index 2dd7c1dece3..da712fce9be 100644 --- a/ports/esp8266/main.c +++ b/ports/esp8266/main.c @@ -49,6 +49,64 @@ static char heap[38 * 1024]; +#if MICROPY_HW_HARD_FAULT_DEBUG + +static void format_hex(uint32_t hex, char *buffer) { + static const char table[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + int offset = 7; + uint32_t value = hex; + while (offset >= 0) { + buffer[offset--] = table[value & 0x0F]; + value >>= 4; + } +} + +static void print_reset_info(void) { + struct rst_info *rst_info = system_get_rst_info(); + if ((rst_info->reason == REASON_WDT_RST) || (rst_info->reason == REASON_EXCEPTION_RST) || (rst_info->reason == REASON_SOFT_WDT_RST)) { + char buffer[8]; + mp_hal_stdout_tx_str("\r\n\r\nThe system restarted due to an error.\r\n\r\nReason: "); + switch (rst_info->reason) { + case REASON_WDT_RST: + mp_hal_stdout_tx_str("WDT"); + break; + + case REASON_EXCEPTION_RST: + mp_hal_stdout_tx_str("EXCEPTION"); + break; + + case REASON_SOFT_WDT_RST: + mp_hal_stdout_tx_str("SOFT_WDT"); + break; + + default: + assert(!"Should not ever get here."); + break; + } + mp_hal_stdout_tx_str(" Cause: "); + format_hex(rst_info->exccause, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str(" EPC1: "); + format_hex(rst_info->epc1, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str(" EPC2: "); + format_hex(rst_info->epc2, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str(" EPC3: "); + format_hex(rst_info->epc3, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str(" Exception Vector address: "); + format_hex(rst_info->excvaddr, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str(" DEPC: "); + format_hex(rst_info->depc, buffer); + mp_hal_stdout_tx_strn(buffer, sizeof(buffer)); + mp_hal_stdout_tx_str("\r\n\r\n"); + } +} + +#endif + static void mp_reset(void) { mp_stack_set_top((void *)0x40000000); mp_stack_set_limit(8192); @@ -114,6 +172,10 @@ void init_done(void) { pyexec_event_repl_init(); #endif + #if MICROPY_HW_HARD_FAULT_DEBUG + print_reset_info(); + #endif + #if !MICROPY_REPL_EVENT_DRIVEN soft_reset: for (;;) { diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 83d80a7c963..03f3bb643d1 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -117,6 +117,9 @@ #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define MICROPY_ESP8266_APA102 (1) +// Print error information at reboot time if the board crashed. +#define MICROPY_HW_HARD_FAULT_DEBUG (0) + // No blocking wait-for-event on ESP8266, only non-blocking pump of the "OS" event // loop // From e8c92240e25f75a754f3b12f6a25edd6b8771bb7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 1 Jun 2025 19:19:58 +0200 Subject: [PATCH 0734/2098] tools/ci.sh: Remove natmod build restrictions for Xtensa. This commit lets the CI pipeline build all natmods for the Xtensa target, now that ROM symbols can be used in the linking process. The restriction was put in place due to build failures on certain natmods for Xtensa, as ROM symbols would not be used, causing undefined symbol errors at build time. Signed-off-by: Alessandro Gatti --- tools/ci.sh | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index d12b4bcd316..7de21e43b38 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -524,23 +524,12 @@ function ci_native_mpy_modules_build { else arch=$1 fi - for natmod in features1 features3 features4 heapq re + for natmod in deflate features1 features3 features4 framebuf heapq random re do make -C examples/natmod/$natmod clean make -C examples/natmod/$natmod ARCH=$arch done - # deflate, framebuf, and random currently cannot build on xtensa due to - # some symbols that have been removed from the compiler's runtime, in - # favour of being provided from ROM. - if [ $arch != "xtensa" ]; then - for natmod in deflate framebuf random - do - make -C examples/natmod/$natmod clean - make -C examples/natmod/$natmod ARCH=$arch - done - fi - # features2 requires soft-float on armv7m, rv32imc, and xtensa. On armv6m # the compiler generates absolute relocations in the object file # referencing soft-float functions, which is not supported at the moment. @@ -551,10 +540,8 @@ function ci_native_mpy_modules_build { make -C examples/natmod/features2 ARCH=$arch fi - # btree requires thread local storage support on rv32imc, whilst on xtensa - # it relies on symbols that are provided from ROM but not exposed to - # natmods at the moment. - if [ $arch != "rv32imc" ] && [ $arch != "xtensa" ]; then + # btree requires thread local storage support on rv32imc. + if [ $arch != "rv32imc" ]; then make -C examples/natmod/btree clean make -C examples/natmod/btree ARCH=$arch fi From 193603dbac9712df5a54f4cecf3cab17851e2ce8 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 4 Jun 2025 07:07:02 +0200 Subject: [PATCH 0735/2098] tools/ci.sh: Clean the correct MPY files when batch compiling. This commit fixes a small yet harmless issue that occurs when invoking `ci_native_mpy_modules_build` on a persistent environment, as only X64 MPY files would be removed by the cleaning process. Now the correct architecture is passed at all times when cleaning before building a natmod for a particular architecture, forcing a full build of all files to better simulate the CI environment (where there's no state persisted between runs for this step). Signed-off-by: Alessandro Gatti --- tools/ci.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 7de21e43b38..32eb99235c4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -526,14 +526,14 @@ function ci_native_mpy_modules_build { fi for natmod in deflate features1 features3 features4 framebuf heapq random re do - make -C examples/natmod/$natmod clean + make -C examples/natmod/$natmod ARCH=$arch clean make -C examples/natmod/$natmod ARCH=$arch done # features2 requires soft-float on armv7m, rv32imc, and xtensa. On armv6m # the compiler generates absolute relocations in the object file # referencing soft-float functions, which is not supported at the moment. - make -C examples/natmod/features2 clean + make -C examples/natmod/features2 ARCH=$arch clean if [ $arch = "rv32imc" ] || [ $arch = "armv7m" ] || [ $arch = "xtensa" ]; then make -C examples/natmod/features2 ARCH=$arch MICROPY_FLOAT_IMPL=float elif [ $arch != "armv6m" ]; then @@ -542,7 +542,7 @@ function ci_native_mpy_modules_build { # btree requires thread local storage support on rv32imc. if [ $arch != "rv32imc" ]; then - make -C examples/natmod/btree clean + make -C examples/natmod/btree ARCH=$arch clean make -C examples/natmod/btree ARCH=$arch fi } From 4c55b0879b38b373b44e84552d6754b7842b5b72 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 31 May 2025 13:44:03 +1000 Subject: [PATCH 0736/2098] tools/ci.sh: Allow errors in code-size build to fail the CI. It was possible for CI to pass even if the bare-arm port fails to build. This commit fixes that. Signed-off-by: Damien George --- tools/ci.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 32eb99235c4..ea67e2c1048 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -93,23 +93,30 @@ function ci_code_size_build { function code_size_build_step { COMMIT=$1 OUTFILE=$2 - IGNORE_ERRORS=$3 echo "Building ${COMMIT}..." git checkout --detach $COMMIT git submodule update --init $SUBMODULES git show -s tools/metrics.py clean $PORTS_TO_CHECK - tools/metrics.py build $PORTS_TO_CHECK | tee $OUTFILE || $IGNORE_ERRORS + tools/metrics.py build $PORTS_TO_CHECK | tee $OUTFILE + return $? } + # Allow errors from tools/metrics.py to propagate out of the pipe above. + set -o pipefail + # build reference, save to size0 # ignore any errors with this build, in case master is failing - code_size_build_step $REFERENCE ~/size0 true + code_size_build_step $REFERENCE ~/size0 # build PR/branch, save to size1 - code_size_build_step $COMPARISON ~/size1 false + code_size_build_step $COMPARISON ~/size1 + STATUS=$? + set +o pipefail unset -f code_size_build_step + + return $STATUS } ######################################################################################## From 229104558fb7f9d283b7302bc3720bc35c5c49cf Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 11:15:32 +1000 Subject: [PATCH 0737/2098] tests/run-tests.py: Automatically skip tests that are too large. Some tests are just too big for targets that don't have much heap memory, eg `tests/extmod/vfs_rom.py`. Other tests are too large because the target doesn't have enough IRAM for native code, eg esp8266 running `tests/micropython/viper_args.py`. Previously, such tests were explicitly skipped on targets known to have little memory, eg esp8266. But this doesn't scale to multiple targets, nor to more and more tests which are too large. This commit addresses that by adding logic to the test runner so it can automatically skip tests when they don't fit in the target's memory. It does this by prepending a `print('START TEST')` to every test, and if a `MemoryError` occurs before that line is printed then the test was too big. This works for standard tests, tests that go via .mpy files, and tests that run in native emitter mode via .mpy files. For tests that are too big, it prints `lrge ` on the output, and at the end prints them on a separate line of skipped tests so they can be distinguished. They are also distinguished in the `_result.json` file as a skipped test with reason "too large". Signed-off-by: Damien George --- tests/misc/print_exception.py | 2 ++ tests/run-tests.py | 53 ++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/tests/misc/print_exception.py b/tests/misc/print_exception.py index 1d196d6ab16..92754733b58 100644 --- a/tests/misc/print_exception.py +++ b/tests/misc/print_exception.py @@ -1,3 +1,5 @@ +# Test sys.print_exception (MicroPython) / traceback.print_exception (CPython). + try: import io import sys diff --git a/tests/run-tests.py b/tests/run-tests.py index ecd611aa5b5..c012dc96f45 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -105,14 +105,11 @@ def open(self, path, mode): # These are tests that are difficult to detect that they should not be run on the given target. platform_tests_to_skip = { "esp8266": ( - "micropython/viper_args.py", # too large - "micropython/viper_binop_arith.py", # too large - "misc/rge_sm.py", # too large + "misc/rge_sm.py", # incorrect values due to object representation C ), "minimal": ( "basics/class_inplace_op.py", # all special methods not supported "basics/subclass_native_init.py", # native subclassing corner cases not support - "misc/rge_sm.py", # too large "micropython/opt_level.py", # don't assume line numbers are stored ), "nrf": ( @@ -315,9 +312,21 @@ def prepare_script_for_target(args, *, script_filename=None, script_text=None, f def run_script_on_remote_target(pyb, args, test_file, is_special): - had_crash, script = prepare_script_for_target( - args, script_filename=test_file, force_plain=is_special - ) + with open(test_file, "rb") as f: + script = f.read() + + # If the test is not a special test, prepend it with a print to indicate that it started. + # If the print does not execute this means that the test did not even start, eg it was + # too large for the target. + prepend_start_test = not is_special + if prepend_start_test: + if script.startswith(b"#"): + script = b"print('START TEST')" + script + else: + script = b"print('START TEST')\n" + script + + had_crash, script = prepare_script_for_target(args, script_text=script, force_plain=is_special) + if had_crash: return True, script @@ -328,9 +337,19 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): except pyboard.PyboardError as e: had_crash = True if not is_special and e.args[0] == "exception": - output_mupy = e.args[1] + e.args[2] + b"CRASH" + if prepend_start_test and e.args[1] == b"" and b"MemoryError" in e.args[2]: + output_mupy = b"SKIP-TOO-LARGE\n" + else: + output_mupy = e.args[1] + e.args[2] + b"CRASH" else: output_mupy = bytes(e.args[0], "ascii") + b"\nCRASH" + + if prepend_start_test: + if output_mupy.startswith(b"START TEST\r\n"): + output_mupy = output_mupy.removeprefix(b"START TEST\r\n") + else: + had_crash = True + return had_crash, output_mupy @@ -474,7 +493,7 @@ def send_get(what): output_mupy = output_mupy.replace(b"\r\n", b"\n") # don't try to convert the output if we should skip this test - if had_crash or output_mupy in (b"SKIP\n", b"CRASH"): + if had_crash or output_mupy in (b"SKIP\n", b"SKIP-TOO-LARGE\n", b"CRASH"): return output_mupy # skipped special tests will output "SKIP" surrounded by other interpreter debug output @@ -911,6 +930,10 @@ def run_one_test(test_file): print("skip ", test_file) test_results.append((test_name, test_file, "skip", "")) return + elif output_mupy == b"SKIP-TOO-LARGE\n": + print("lrge ", test_file) + test_results.append((test_name, test_file, "skip", "too large")) + return # Look at the output of the test to see if unittest was used. uses_unittest = False @@ -1035,7 +1058,10 @@ def run_one_test(test_file): test_results = test_results.value passed_tests = list(r for r in test_results if r[2] == "pass") - skipped_tests = list(r for r in test_results if r[2] == "skip") + skipped_tests = list(r for r in test_results if r[2] == "skip" and r[3] != "too large") + skipped_tests_too_large = list( + r for r in test_results if r[2] == "skip" and r[3] == "too large" + ) failed_tests = list(r for r in test_results if r[2] == "fail") print( @@ -1052,6 +1078,13 @@ def run_one_test(test_file): ) ) + if len(skipped_tests_too_large) > 0: + print( + "{} tests skipped because they are too large: {}".format( + len(skipped_tests_too_large), " ".join(test[0] for test in skipped_tests_too_large) + ) + ) + if len(failed_tests) > 0: print( "{} tests failed: {}".format( From 5b340b12b8ac8498838e0df73ff491bc2d105b46 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 26 May 2025 10:41:33 +1000 Subject: [PATCH 0738/2098] tests/run-tests.py: Remove filename arg from prepare_script_for_target. It's no longer used. Signed-off-by: Damien George --- tests/run-tests.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index c012dc96f45..cb7a8b77056 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -269,22 +269,17 @@ def detect_test_platform(pyb, args): print() -def prepare_script_for_target(args, *, script_filename=None, script_text=None, force_plain=False): +def prepare_script_for_target(args, *, script_text=None, force_plain=False): if force_plain or (not args.via_mpy and args.emit == "bytecode"): - if script_filename is not None: - with open(script_filename, "rb") as f: - script_text = f.read() + # A plain test to run as-is, no processing needed. + pass elif args.via_mpy: tempname = tempfile.mktemp(dir="") mpy_filename = tempname + ".mpy" - if script_filename is None: - script_filename = tempname + ".py" - cleanup_script_filename = True - with open(script_filename, "wb") as f: - f.write(script_text) - else: - cleanup_script_filename = False + script_filename = tempname + ".py" + with open(script_filename, "wb") as f: + f.write(script_text) try: subprocess.check_output( @@ -300,8 +295,7 @@ def prepare_script_for_target(args, *, script_filename=None, script_text=None, f script_text = b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" rm_f(mpy_filename) - if cleanup_script_filename: - rm_f(script_filename) + rm_f(script_filename) script_text += bytes(injected_import_hook_code, "ascii") else: From bf909303ffaf64140f69f2c4696fad82dec01d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 22 Apr 2025 17:12:26 +0200 Subject: [PATCH 0739/2098] esp32/machine_timer: Do not free interrupt from ISR. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esp_intr_free is not safe to call from the timer ISR because it requires the current task (the one the ISR interrupted) to be pinned to the same core as the interrupt was allocated on. Merely disabling the ISR however is safe since that only requires that we're currently running on the same core (which the ISR always is), regardless of the current task. This was causing deadlocks in machine_uart when the ISR happened to interrupt a task that was not pinned to a specific core. Signed-off-by: Daniël van de Giessen --- ports/esp32/machine_timer.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 34d49c79d28..6bd4fa7390a 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -50,12 +50,13 @@ const mp_obj_type_t machine_timer_type; 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); +static mp_obj_t machine_timer_deinit(mp_obj_t self_in); void machine_timer_deinit_all(void) { // Disable, deallocate and remove all timers from list machine_timer_obj_t **t = &MP_STATE_PORT(machine_timer_obj_head); while (*t != NULL) { - machine_timer_disable(*t); + machine_timer_deinit(*t); machine_timer_obj_t *next = (*t)->next; m_del_obj(machine_timer_obj_t, *t); *t = next; @@ -96,6 +97,7 @@ machine_timer_obj_t *machine_timer_create(mp_uint_t timer) { self = mp_obj_malloc(machine_timer_obj_t, &machine_timer_type); self->group = group; self->index = index; + self->handle = NULL; // Add the timer to the linked-list of timers self->next = MP_STATE_PORT(machine_timer_obj_head); @@ -131,9 +133,8 @@ void machine_timer_disable(machine_timer_obj_t *self) { } if (self->handle) { - // Free the interrupt handler. - esp_intr_free(self->handle); - self->handle = NULL; + // Disable the interrupt + ESP_ERROR_CHECK(esp_intr_disable(self->handle)); } // We let the disabled timer stay in the list, as it might be @@ -167,6 +168,10 @@ void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { // Allocate and enable the alarm interrupt. timer_ll_enable_intr(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index), false); timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); + if (self->handle) { + ESP_ERROR_CHECK(esp_intr_free(self->handle)); + self->handle = NULL; + } ESP_ERROR_CHECK( esp_intr_alloc(timer_group_periph_signals.groups[self->group].timer_irq_id[self->index], TIMER_FLAGS, timer_isr, self, &self->handle) @@ -233,7 +238,13 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n } static mp_obj_t machine_timer_deinit(mp_obj_t self_in) { - machine_timer_disable(self_in); + machine_timer_obj_t *self = self_in; + + machine_timer_disable(self); + if (self->handle) { + ESP_ERROR_CHECK(esp_intr_free(self->handle)); + self->handle = NULL; + } return mp_const_none; } From 2c2f0b292afbeb41a23a742c3cd74a714e9c03c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 22 Apr 2025 17:42:29 +0200 Subject: [PATCH 0740/2098] esp32: Re-use allocated timer interrupts and simplify UART timer code. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the interrupt is not freed but merely disabled, instead of reallocating it every time the timer is enabled again we can instead just re-enable it. That means we're no longer setting the handler every time, and we need to ensure it does not change. Doing so by adding an additional wrapper function does not only solve that problem, it also allows us to remove some code duplication and simplify how machine_uart uses the timer. Signed-off-by: Daniël van de Giessen --- ports/esp32/machine_timer.c | 29 ++++++++++++++++++----------- ports/esp32/machine_timer.h | 3 ++- ports/esp32/machine_uart.c | 35 +++++++++++------------------------ 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 6bd4fa7390a..a104288f6ee 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -151,12 +151,16 @@ static void machine_timer_isr(void *self_in) { if (self->repeat) { timer_ll_enable_alarm(self->hal_context.dev, self->index, true); } - mp_sched_schedule(self->callback, self); - mp_hal_wake_main_task_from_isr(); + self->handler(self); } } -void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { +static void machine_timer_isr_handler(machine_timer_obj_t *self) { + mp_sched_schedule(self->callback, self); + mp_hal_wake_main_task_from_isr(); +} + +void machine_timer_enable(machine_timer_obj_t *self) { // Initialise the timer. timer_hal_init(&self->hal_context, self->group, self->index); timer_ll_enable_counter(self->hal_context.dev, self->index, false); @@ -169,13 +173,16 @@ void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { timer_ll_enable_intr(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index), false); timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); if (self->handle) { - ESP_ERROR_CHECK(esp_intr_free(self->handle)); - self->handle = NULL; + ESP_ERROR_CHECK(esp_intr_enable(self->handle)); + } else { + 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 + )); } - ESP_ERROR_CHECK( - esp_intr_alloc(timer_group_periph_signals.groups[self->group].timer_irq_id[self->index], - TIMER_FLAGS, timer_isr, self, &self->handle) - ); timer_ll_enable_intr(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index), true); // Enable the alarm to trigger at the given period. @@ -229,10 +236,10 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n } self->repeat = args[ARG_mode].u_int; + self->handler = machine_timer_isr_handler; self->callback = args[ARG_callback].u_obj; - self->handle = NULL; - machine_timer_enable(self, machine_timer_isr); + machine_timer_enable(self); return mp_const_none; } diff --git a/ports/esp32/machine_timer.h b/ports/esp32/machine_timer.h index 914bedd86ba..10fe2f39c90 100644 --- a/ports/esp32/machine_timer.h +++ b/ports/esp32/machine_timer.h @@ -55,12 +55,13 @@ typedef struct _machine_timer_obj_t { mp_obj_t callback; intr_handle_t handle; + void (*handler)(struct _machine_timer_obj_t *timer); 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_enable(machine_timer_obj_t *self); 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 e5857e894b9..982d9a7e27a 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -110,30 +110,19 @@ static const char *_parity_name[] = {"None", "1", "0"}; { 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); - } - } - +static void uart_timer_callback(machine_timer_obj_t *timer) { // 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) { + machine_uart_obj_t *self = (machine_uart_obj_t *)timer->callback; + if (self->rxidle_state == RXIDLE_ALERT) { // At the first call, just switch the state - uart->rxidle_state = RXIDLE_ARMED; - } else if (uart->rxidle_state == RXIDLE_ARMED) { + self->rxidle_state = RXIDLE_ARMED; + } else if (self->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); + self->rxidle_state = RXIDLE_STANDBY; + self->mp_irq_flags = UART_IRQ_RXIDLE; + mp_irq_handler(self->mp_irq_obj); mp_hal_wake_main_task_from_isr(); - machine_timer_disable(uart->rxidle_timer); + machine_timer_disable(self->rxidle_timer); } } @@ -150,9 +139,7 @@ static void uart_event_task(void *self_in) { 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); + machine_timer_enable(self->rxidle_timer); } } self->rxidle_state = RXIDLE_ALERT; @@ -553,11 +540,11 @@ static void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger } self->rxidle_period = period; self->rxidle_timer->period = period; + self->rxidle_timer->handler = uart_timer_callback; // 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; } } From 5f058e9863c0770aa8a4defe23f70919cea94160 Mon Sep 17 00:00:00 2001 From: purewack Date: Thu, 2 Jan 2025 11:44:00 +0000 Subject: [PATCH 0741/2098] esp32: Update ADC driver update to the new esp_adc API. This commit updates the ADC to use the new driver `esp_adc/adc_oneshot.h`. There are several errata notes about not being able to change the bit-width of the ADCs certain chips. The only chip that can switch resolution to a lower one is the normal ESP32. ESP32 C2 and S3 are stuck at 12 bits, while S2 is at 13 bits. On the S2, you can change the resolution, but it has no effect on the resolution, rather, it prevents attenuation from working at all! The resolution is set to the maximum possible for each SoC, with the ESP32 being the only one not throwing errors when trying to set the bit-width to 9, 10, 11 or 12 bits using `ADC.width(bits)`. Signed-off-by: Damian Nowacki (purewack) bobimaster15@gmail.com --- docs/esp32/quickref.rst | 15 ++++- ports/esp32/adc.c | 94 ++++++++++++++---------------- ports/esp32/adc.h | 47 +++++++++++++-- ports/esp32/boards/sdkconfig.base | 1 - ports/esp32/machine_adc.c | 95 +++++++++++++++++-------------- ports/esp32/machine_adc_block.c | 14 ++--- ports/esp32/modesp32.c | 1 - ports/esp32/mpconfigport.h | 1 + 8 files changed, 155 insertions(+), 113 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 63180b470ae..c394414a76f 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -544,14 +544,27 @@ Legacy methods: Equivalent to ``ADC.block().init(bits=bits)``. +The only chip that can switch resolution to a lower one is the normal esp32. +The C2 & S3 are stuck at 12 bits, while the S2 is at 13 bits. + For compatibility, the ``ADC`` object also provides constants matching the -supported ADC resolutions: +supported ADC resolutions, per chip: +ESP32: - ``ADC.WIDTH_9BIT`` = 9 - ``ADC.WIDTH_10BIT`` = 10 - ``ADC.WIDTH_11BIT`` = 11 - ``ADC.WIDTH_12BIT`` = 12 +ESP32 C3 & S3: + - ``ADC.WIDTH_12BIT`` = 12 + +ESP32 S2: + - ``ADC.WIDTH_13BIT`` = 13 + +.. method:: ADC.deinit() + + Provided to deinit the adc driver. Software SPI bus ---------------- diff --git a/ports/esp32/adc.c b/ports/esp32/adc.c index 83af6dce0b2..d0e5e81330d 100644 --- a/ports/esp32/adc.c +++ b/ports/esp32/adc.c @@ -26,83 +26,73 @@ */ #include "py/mphal.h" +#include "py/mperrno.h" #include "adc.h" -#include "driver/adc.h" +#include "esp_adc/adc_oneshot.h" #include "esp_adc/adc_cali_scheme.h" -#define DEFAULT_VREF 1100 +static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten); -void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits) { - if (bits < SOC_ADC_RTC_MIN_BITWIDTH && bits > SOC_ADC_RTC_MAX_BITWIDTH) { - // Invalid value for the current chip, raise exception in the switch below. - bits = -1; +void adc_is_init_guard(machine_adc_block_obj_t *self) { + if (!self->handle) { + mp_raise_OSError(MP_EPERM); } - switch (bits) { - case 9: - self->width = ADC_BITWIDTH_9; - break; - case 10: - self->width = ADC_BITWIDTH_10; - break; - case 11: - self->width = ADC_BITWIDTH_11; - break; - case 12: - self->width = ADC_BITWIDTH_12; - break; - case 13: - self->width = ADC_BITWIDTH_13; - break; - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); - } - self->bits = bits; +} - if (self->unit_id == ADC_UNIT_1) { - adc1_config_width(self->width); - } +esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten) { + adc_is_init_guard(self->block); + + adc_oneshot_chan_cfg_t config = { + .atten = atten, + .bitwidth = self->block->bitwidth, + }; + esp_err_t ret = adc_oneshot_config_channel(self->block->handle, self->channel_id, &config); + return ret; } mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id) { - int raw = 0; - if (self->unit_id == ADC_UNIT_1) { - raw = adc1_get_raw(channel_id); - } else { - #if (SOC_ADC_PERIPH_NUM >= 2) - check_esp_err(adc2_get_raw(channel_id, self->width, &raw)); - #endif - } - return raw; + adc_is_init_guard(self); + + int reading = 0; + adc_oneshot_read(self->handle, channel_id, &reading); + return reading; +} + +/* +During testing, it turned out that the function `adc_cali_raw_to_voltage` does not account for the lower resolution, +instead it expects the full resolution value as an argument, hence the scaling applied here +*/ +mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { + int raw = madcblock_read_helper(self, channel_id); + int uv = 0; + + check_esp_err(ensure_adc_calibration(self, atten)); + check_esp_err(adc_cali_raw_to_voltage(self->calib[atten], (raw << (ADC_WIDTH_MAX - self->bitwidth)), &uv)); + return (mp_int_t)uv * 1000; } static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten) { - if (self->handle[atten] != NULL) { + if (self->calib[atten] != NULL) { return ESP_OK; } + esp_err_t ret = ESP_OK; + #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED adc_cali_curve_fitting_config_t cali_config = { .unit_id = self->unit_id, .atten = atten, - .bitwidth = self->width, + .bitwidth = self->bitwidth, }; - return adc_cali_create_scheme_curve_fitting(&cali_config, &self->handle[atten]); + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &self->calib[atten]); #else adc_cali_line_fitting_config_t cali_config = { .unit_id = self->unit_id, .atten = atten, - .bitwidth = self->width, + .bitwidth = self->bitwidth, }; - return adc_cali_create_scheme_line_fitting(&cali_config, &self->handle[atten]); + ret = adc_cali_create_scheme_line_fitting(&cali_config, &self->calib[atten]); #endif -} -mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { - int raw = madcblock_read_helper(self, channel_id); - int uv; - - check_esp_err(ensure_adc_calibration(self, atten)); - check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); - - return (mp_int_t)uv * 1000; + return ret; } diff --git a/ports/esp32/adc.h b/ports/esp32/adc.h index 5688e0a29a7..ebf7fcc21aa 100644 --- a/ports/esp32/adc.h +++ b/ports/esp32/adc.h @@ -29,17 +29,45 @@ #define MICROPY_INCLUDED_ESP32_ADC_H #include "py/runtime.h" -#include "esp_adc_cal.h" +#include "esp_adc/adc_oneshot.h" #include "esp_adc/adc_cali_scheme.h" -#define ADC_ATTEN_MAX SOC_ADC_ATTEN_NUM +#define ADC_ATTEN_COUNT SOC_ADC_ATTEN_NUM +#define ADC_ATTEN_MIN ADC_ATTEN_DB_0 +#define ADC_ATTEN_MAX ADC_ATTEN_DB_11 + +/* +https://github.com/espressif/esp-idf/issues/13128 +https://github.com/espressif/esp-idf/blob/release/v5.2/components/soc/esp32s3/include/soc/soc_caps.h +https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32s2/03-errata-description/index.html +https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/adc.html + +Looks like only the original esp32 is capable of bitwidth adjustment, all others are stuck at 12 bits, +except the S2, which is locked at 13 bits, otherwise attenuation doesn't work. +*/ +#if CONFIG_IDF_TARGET_ESP32S2 + +#define ADC_WIDTH_MIN ADC_BITWIDTH_13 +#define ADC_WIDTH_MAX ADC_BITWIDTH_13 + +#elif CONFIG_IDF_TARGET_ESP32 + +#define ADC_WIDTH_MIN ADC_BITWIDTH_9 +#define ADC_WIDTH_MAX ADC_BITWIDTH_12 + +#else + +#define ADC_WIDTH_MIN ADC_BITWIDTH_12 +#define ADC_WIDTH_MAX ADC_BITWIDTH_12 + +#endif typedef struct _machine_adc_block_obj_t { mp_obj_base_t base; adc_unit_t unit_id; - mp_int_t bits; - adc_bits_width_t width; - adc_cali_handle_t handle[ADC_ATTEN_MAX]; + adc_oneshot_unit_handle_t handle; + adc_bitwidth_t bitwidth; + adc_cali_handle_t calib[ADC_ATTEN_COUNT]; } machine_adc_block_obj_t; typedef struct _machine_adc_obj_t { @@ -51,11 +79,18 @@ typedef struct _machine_adc_obj_t { extern machine_adc_block_obj_t madcblock_obj[]; -void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits); +esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten); + mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id); mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten); const machine_adc_obj_t *madc_search_helper(machine_adc_block_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id); void madc_init_helper(const machine_adc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +mp_int_t mp_machine_adc_atten_get_helper(const machine_adc_obj_t *self); +void mp_machine_adc_atten_set_helper(const machine_adc_obj_t *self, mp_int_t atten); + +mp_int_t mp_machine_adc_width_get_helper(const machine_adc_obj_t *self); +void mp_machine_adc_block_width_set_helper(machine_adc_block_obj_t *self, mp_int_t width); + #endif // MICROPY_INCLUDED_ESP32_ADC_H diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 5595444e86f..823b916d960 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -117,7 +117,6 @@ CONFIG_ADC_CAL_LUT_ENABLE=y CONFIG_UART_ISR_IN_IRAM=y # IDF 5 deprecated -CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=y CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y CONFIG_ETH_USE_SPI_ETHERNET=y diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index be1725c3709..02acaa22da0 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -30,7 +30,7 @@ #include "py/mphal.h" #include "adc.h" -#include "driver/adc.h" +#include "esp_adc/adc_oneshot.h" #define ADCBLOCK1 (&madcblock_obj[0]) #define ADCBLOCK2 (&madcblock_obj[1]) @@ -126,20 +126,8 @@ static const machine_adc_obj_t madc_obj[] = { #endif }; -// These values are initialised to 0, which means the corresponding ADC channel is not initialised. -// The madc_atten_get/madc_atten_set functions store (atten+1) here so that the uninitialised state -// can be distinguished from the initialised state. static uint8_t madc_obj_atten[MP_ARRAY_SIZE(madc_obj)]; -static inline adc_atten_t madc_atten_get(const machine_adc_obj_t *self) { - uint8_t value = madc_obj_atten[self - &madc_obj[0]]; - return value == 0 ? ADC_ATTEN_MAX : value - 1; -} - -static inline void madc_atten_set(const machine_adc_obj_t *self, adc_atten_t atten) { - madc_obj_atten[self - &madc_obj[0]] = atten + 1; -} - const machine_adc_obj_t *madc_search_helper(machine_adc_block_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id) { for (int i = 0; i < MP_ARRAY_SIZE(madc_obj); i++) { const machine_adc_obj_t *adc = &madc_obj[i]; @@ -152,22 +140,7 @@ const machine_adc_obj_t *madc_search_helper(machine_adc_block_obj_t *block, adc_ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { const machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, madc_atten_get(self)); -} - -static void madc_atten_helper(const machine_adc_obj_t *self, mp_int_t atten) { - esp_err_t err = ESP_FAIL; - if (self->block->unit_id == ADC_UNIT_1) { - err = adc1_config_channel_atten(self->channel_id, atten); - } else { - #if SOC_ADC_PERIPH_NUM >= 2 - err = adc2_config_channel_atten(self->channel_id, atten); - #endif - } - if (err != ESP_OK) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid atten")); - } - madc_atten_set(self, atten); + mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, mp_machine_adc_atten_get_helper(self)); } void madc_init_helper(const machine_adc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -182,18 +155,32 @@ void madc_init_helper(const machine_adc_obj_t *self, size_t n_pos_args, const mp mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_int_t atten = args[ARG_atten].u_int; - if (atten != -1) { - madc_atten_helper(self, atten); - } else if (madc_atten_get(self) == ADC_ATTEN_MAX) { - madc_atten_helper(self, ADC_ATTEN_DB_0); + + if (!self->block->handle) { + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = self->block->unit_id + }; + check_esp_err(adc_oneshot_new_unit(&init_config, &self->block->handle)); } + + mp_int_t atten = args[ARG_atten].u_int; + mp_machine_adc_atten_set_helper(self, atten != -1 ? atten : ADC_ATTEN_MAX); + mp_machine_adc_block_width_set_helper(self->block, ADC_WIDTH_MAX); + apply_self_adc_channel_atten(self, mp_machine_adc_atten_get_helper(self)); + } static void mp_machine_adc_init_helper(machine_adc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { madc_init_helper(self, n_pos_args, pos_args, kw_args); } +static void mp_machine_adc_deinit(machine_adc_obj_t *self) { + if (self->block->handle) { + check_esp_err(adc_oneshot_del_unit(self->block->handle)); + self->block->handle = NULL; + } +} + static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); gpio_num_t gpio_id = machine_pin_get_id(args[0]); @@ -202,10 +189,6 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_pos_ mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); } - if (self->block->width == -1) { - madcblock_bits_helper(self->block, self->block->bits); - } - mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); madc_init_helper(self, n_pos_args - 1, args + 1, &kw_args); @@ -225,20 +208,46 @@ static mp_int_t mp_machine_adc_read(machine_adc_obj_t *self) { static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { mp_uint_t raw = madcblock_read_helper(self->block, self->channel_id); // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) - mp_int_t bits = self->block->bits; + mp_int_t bits = mp_machine_adc_width_get_helper(self); mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16); return u16; } static mp_int_t mp_machine_adc_read_uv(machine_adc_obj_t *self) { - adc_atten_t atten = madc_atten_get(self); - return madcblock_read_uv_helper(self->block, self->channel_id, atten); + return madcblock_read_uv_helper(self->block, self->channel_id, mp_machine_adc_atten_get_helper(self)); +} + +mp_int_t mp_machine_adc_atten_get_helper(const machine_adc_obj_t *self) { + uint8_t value = madc_obj_atten[self - &madc_obj[0]]; + return value == 0 ? ADC_ATTEN_MAX : value - 1; +} + +void mp_machine_adc_atten_set_helper(const machine_adc_obj_t *self, mp_int_t atten) { + if (atten < ADC_ATTEN_MIN || atten > ADC_ATTEN_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid attenuation")); + } + + madc_obj_atten[self - &madc_obj[0]] = atten + 1; } static void mp_machine_adc_atten_set(machine_adc_obj_t *self, mp_int_t atten) { - madc_atten_helper(self, atten); + mp_machine_adc_atten_set_helper(self, atten); + apply_self_adc_channel_atten(self, mp_machine_adc_atten_get_helper(self)); +} + +mp_int_t mp_machine_adc_width_get_helper(const machine_adc_obj_t *self) { + return self->block->bitwidth; +} + +void mp_machine_adc_block_width_set_helper(machine_adc_block_obj_t *self, mp_int_t width) { + if (width < ADC_WIDTH_MIN || width > ADC_WIDTH_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid bit-width")); + } + + self->bitwidth = width; } static void mp_machine_adc_width_set(machine_adc_obj_t *self, mp_int_t width) { - madcblock_bits_helper(self->block, width); + mp_machine_adc_block_width_set_helper(self->block, width); + apply_self_adc_channel_atten(self, mp_machine_adc_atten_get_helper(self)); } diff --git a/ports/esp32/machine_adc_block.c b/ports/esp32/machine_adc_block.c index a373603b7e3..6b06b432c28 100644 --- a/ports/esp32/machine_adc_block.c +++ b/ports/esp32/machine_adc_block.c @@ -29,25 +29,21 @@ #include "py/mphal.h" #include "adc.h" -#include "driver/adc.h" +#include "esp_adc/adc_oneshot.h" machine_adc_block_obj_t madcblock_obj[] = { - {{&machine_adc_block_type}, ADC_UNIT_1, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, + {{&machine_adc_block_type}, ADC_UNIT_1, NULL, ADC_WIDTH_MAX, {0}}, #if SOC_ADC_PERIPH_NUM > 1 - {{&machine_adc_block_type}, ADC_UNIT_2, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, + {{&machine_adc_block_type}, ADC_UNIT_2, NULL, ADC_WIDTH_MAX, {0}}, #endif }; static void mp_machine_adc_block_print(const mp_print_t *print, machine_adc_block_obj_t *self) { - mp_printf(print, "ADCBlock(%u, bits=%u)", self->unit_id, self->bits); + mp_printf(print, "ADCBlock(%u, bits=%u)", self->unit_id, self->bitwidth); } static void mp_machine_adc_block_bits_set(machine_adc_block_obj_t *self, mp_int_t bits) { - if (bits != -1) { - madcblock_bits_helper(self, bits); - } else if (self->width == -1) { - madcblock_bits_helper(self, self->bits); - } + mp_machine_adc_block_width_set_helper(self, bits); } static machine_adc_block_obj_t *mp_machine_adc_block_get(mp_int_t unit) { diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 0296ddf10e7..4572e7b68b7 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -30,7 +30,6 @@ #include #include #include "driver/gpio.h" -#include "driver/adc.h" #include "esp_heap_caps.h" #include "multi_heap.h" diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 35c8ff1831d..a6f103cdef7 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -124,6 +124,7 @@ #define MICROPY_PY_MACHINE_ADC_INCLUDEFILE "ports/esp32/machine_adc.c" #define MICROPY_PY_MACHINE_ADC_ATTEN_WIDTH (1) #define MICROPY_PY_MACHINE_ADC_INIT (1) +#define MICROPY_PY_MACHINE_ADC_DEINIT (1) #define MICROPY_PY_MACHINE_ADC_READ (1) #define MICROPY_PY_MACHINE_ADC_READ_UV (1) #define MICROPY_PY_MACHINE_ADC_BLOCK (1) From b8e56a17b126fd990b7cdf3fb81944d504146f69 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 3 Jun 2025 22:54:09 +0200 Subject: [PATCH 0742/2098] github/workflows: Split QEMU/Arm builds into separate entries. This commit takes the QEMU/Arm CI build and test step and splits it into three separate steps (bigendian, sabrelite, thumb), to allow them to run in parallel. Currently the QEMU/Arm CI build step would take up to 16 minutes, often being the last step blocking a full test run. With this commit, when the steps run in parallel the time it takes to complete the QEMU/Arm build and test procedure is cut in half - taking between 8 to 9 minutes depending on the CI runner load. The existing `ci_build_and_test_arm` function has been removed, in favour of having three separate functions - one per configuration. They are called `ci_build_and_test_arm_bigendian`, `ci_build_and_test_arm_sabrelite`, and `ci_build_and_test_arm_thumb`. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_qemu.yml | 11 +++++++++-- tools/ci.sh | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index 57192c43936..ac09dde8640 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -20,13 +20,20 @@ concurrency: jobs: build_and_test_arm: + strategy: + fail-fast: false + matrix: + ci_func: # names are functions in ci.sh + - bigendian + - sabrelite + - thumb runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install packages run: source tools/ci.sh && ci_qemu_setup_arm - - name: Build and run test suite - run: source tools/ci.sh && ci_qemu_build_arm + - name: Build and run test suite ci_qemu_build_arm_${{ matrix.ci_func }} + run: source tools/ci.sh && ci_qemu_build_arm_${{ matrix.ci_func }} - name: Print failures if: failure() run: tests/run-tests.py --print-failures diff --git a/tools/ci.sh b/tools/ci.sh index ea67e2c1048..011aabea31c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -324,13 +324,24 @@ function ci_qemu_setup_rv32 { qemu-system-riscv32 --version } -function ci_qemu_build_arm { +function ci_qemu_build_arm_prepare { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu submodules +} + +function ci_qemu_build_arm_bigendian { + ci_qemu_build_arm_prepare make ${MAKEOPTS} -C ports/qemu CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 - make ${MAKEOPTS} -C ports/qemu clean - make ${MAKEOPTS} -C ports/qemu test_full +} + +function ci_qemu_build_arm_sabrelite { + ci_qemu_build_arm_prepare make ${MAKEOPTS} -C ports/qemu BOARD=SABRELITE test_full +} + +function ci_qemu_build_arm_thumb { + ci_qemu_build_arm_prepare + make ${MAKEOPTS} -C ports/qemu test_full # Test building and running native .mpy with armv7m architecture. ci_native_mpy_modules_build armv7m From 95203ab88b50f5083c28afe6a39aae605b4df2da Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 5 Jun 2025 10:43:48 +1000 Subject: [PATCH 0743/2098] tools/boardgen.py: Ensure board pin locals_dict has consistent order. `tools/boardgen.py` is used by the `make-pins.py` scripts in many ports to generate the pin definitions for the machine module. In #17391 it was found that this is currently generating the C structs for board pin definitions with inconsistent ordering (across different build runs), which makes it sometimes impossible to get a consistent binary file even for no change in source files. This commit fixes that by sorting the board pin names alphabetically. Signed-off-by: Andrew Leech --- tools/boardgen.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/boardgen.py b/tools/boardgen.py index 39bedf71cd0..3723e7ce31b 100644 --- a/tools/boardgen.py +++ b/tools/boardgen.py @@ -108,6 +108,10 @@ def add_board_pin_name(self, board_pin_name, hidden=False): ) ) + # Iterate over board pin names in consistent sorted order. + def board_pin_names(self): + return sorted(self._board_pin_names, key=lambda x: x[0]) + # Override this to handle an af specified in af.csv. def add_af(self, af_idx, af_name, af): raise NotImplementedError @@ -295,7 +299,7 @@ def print_board_locals_dict(self, out_source): file=out_source, ) for pin in self.available_pins(): - for board_pin_name, board_hidden in pin._board_pin_names: + for board_pin_name, board_hidden in pin.board_pin_names(): if board_hidden: # Don't include hidden pins in Pins.board. continue @@ -389,7 +393,7 @@ def print_defines(self, out_header, cpu=True, board=True): # #define pin_BOARDNAME (pin_CPUNAME) if board: - for board_pin_name, _board_hidden in pin._board_pin_names: + for board_pin_name, _board_hidden in pin.board_pin_names(): # Note: Hidden board pins are still available to C via the macro. # Note: The RHS isn't wrapped in (), which is necessary to make the # STATIC_AF_ macro work on STM32. From fffaf8a41f094868590aab0506bba03f126a3f5d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 4 Jun 2025 12:13:46 +1000 Subject: [PATCH 0744/2098] extmod/modnetwork: Consolidate definition of common drivers. Most extmod network drivers were being defined on a per-port basis, duplicating code and making enabling a driver on a new port harder. This consolidates extmod driver declarations and removes the existing per-port definitions of them. This commit has been verified to be a no-op in terms of firmware change. Signed-off-by: Andrew Leech --- extmod/modnetwork.c | 39 +++++++++++++++++++++++++++++++++ extmod/modnetwork.h | 5 +++++ extmod/network_esp_hosted.c | 2 ++ extmod/network_wiznet5k.c | 2 ++ ports/alif/mpconfigport.h | 8 ------- ports/mimxrt/mpconfigport.h | 8 ------- ports/renesas-ra/mpconfigport.h | 8 ------- ports/rp2/mpconfigport.h | 35 ----------------------------- ports/stm32/mpconfigport.h | 16 -------------- 9 files changed, 48 insertions(+), 75 deletions(-) diff --git a/extmod/modnetwork.c b/extmod/modnetwork.c index 336836b6b86..b6855fcaaab 100644 --- a/extmod/modnetwork.c +++ b/extmod/modnetwork.c @@ -40,6 +40,19 @@ #if MICROPY_PY_NETWORK_CYW43 // So that CYW43_LINK_xxx constants are available to MICROPY_PORT_NETWORK_INTERFACES. #include "lib/cyw43-driver/src/cyw43.h" +extern const struct _mp_obj_type_t mp_network_cyw43_type; +#endif + +#if MICROPY_PY_NETWORK_WIZNET5K +extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k; +#endif + +#if MICROPY_PY_NETWORK_NINAW10 +extern const struct _mp_obj_type_t mod_network_nic_type_nina; +#endif + +#if MICROPY_PY_NETWORK_ESP_HOSTED +extern const struct _mp_obj_type_t mod_network_esp_hosted_type; #endif #ifdef MICROPY_PY_NETWORK_INCLUDEFILE @@ -166,6 +179,32 @@ static const mp_rom_map_elem_t mp_module_network_globals_table[] = { MICROPY_PORT_NETWORK_INTERFACES #endif + #if MICROPY_PY_NETWORK_CYW43 + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, + // CYW43 status constants, currently for rp2 port only. + // TODO move these to WIFI module for all ports. + #if defined(PICO_PROGRAM_NAME) && defined(CYW43_LINK_DOWN) + { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(CYW43_LINK_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(CYW43_LINK_JOIN) }, + { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(CYW43_LINK_BADAUTH) }, + { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(CYW43_LINK_NONET) }, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECT_FAIL), MP_ROM_INT(CYW43_LINK_FAIL) }, + { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(CYW43_LINK_UP) }, + #endif + #endif + + #if MICROPY_PY_NETWORK_WIZNET5K + { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) }, + #endif + + #if MICROPY_PY_NETWORK_NINAW10 + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mod_network_nic_type_nina) }, + #endif + + #if MICROPY_PY_NETWORK_ESP_HOSTED + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mod_network_esp_hosted_type) }, + #endif + // Allow a port to take mostly full control of the network module. #ifdef MICROPY_PY_NETWORK_MODULE_GLOBALS_INCLUDEFILE #include MICROPY_PY_NETWORK_MODULE_GLOBALS_INCLUDEFILE diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index 7e5a2833537..d16329f07dd 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -60,6 +60,11 @@ extern char mod_network_country_code[2]; #define MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN (32) #endif +#if MICROPY_PY_NETWORK_NINAW10 +// This Network interface requires the extended socket state. +#define MICROPY_PY_SOCKET_EXTENDED_STATE (1) +#endif + // This is a null-terminated string. extern char mod_network_hostname_data[MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN + 1]; diff --git a/extmod/network_esp_hosted.c b/extmod/network_esp_hosted.c index 04cfc36b323..6ed348450ba 100644 --- a/extmod/network_esp_hosted.c +++ b/extmod/network_esp_hosted.c @@ -48,6 +48,8 @@ #include "esp_hosted_wifi.h" #include "esp_hosted_hal.h" +extern const mp_obj_type_t mod_network_esp_hosted_type; + typedef struct _esp_hosted_obj_t { mp_obj_base_t base; uint32_t itf; diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index 533d39a1871..e065c148667 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -78,6 +78,8 @@ #endif +extern const mp_obj_type_t mod_network_nic_type_wiznet5k; + #ifndef printf #define printf(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) #endif diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 6c9e6af635f..ddfc551bf89 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -158,19 +158,11 @@ #define MICROPY_FATFS_MAX_SS (MICROPY_HW_FLASH_BLOCK_SIZE_BYTES) #endif -#if MICROPY_PY_NETWORK_CYW43 -extern const struct _mp_obj_type_t mp_network_cyw43_type; -#define MICROPY_HW_NIC_CYW43 { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, -#else -#define MICROPY_HW_NIC_CYW43 -#endif - #ifndef MICROPY_BOARD_NETWORK_INTERFACES #define MICROPY_BOARD_NETWORK_INTERFACES #endif #define MICROPY_PORT_NETWORK_INTERFACES \ - MICROPY_HW_NIC_CYW43 \ MICROPY_BOARD_NETWORK_INTERFACES \ // Bluetooth code only runs in the scheduler, no locking/mutex required. diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 9a7dfc630f3..e1c605f452a 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -178,20 +178,12 @@ extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_NIC_ETH #endif -#if MICROPY_PY_NETWORK_CYW43 -extern const struct _mp_obj_type_t mp_network_cyw43_type; -#define MICROPY_HW_NIC_CYW43 { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, -#else -#define MICROPY_HW_NIC_CYW43 -#endif - #ifndef MICROPY_BOARD_NETWORK_INTERFACES #define MICROPY_BOARD_NETWORK_INTERFACES #endif #define MICROPY_PORT_NETWORK_INTERFACES \ MICROPY_HW_NIC_ETH \ - MICROPY_HW_NIC_CYW43 \ MICROPY_BOARD_NETWORK_INTERFACES \ #ifndef MICROPY_BOARD_ROOT_POINTERS diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index bd936061fa4..c2b89f415ba 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -210,19 +210,11 @@ #define MP_STATE_PORT MP_STATE_VM -#if MICROPY_PY_NETWORK_ESP_HOSTED -extern const struct _mp_obj_type_t mod_network_esp_hosted_type; -#define MICROPY_HW_NIC_ESP_HOSTED { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mod_network_esp_hosted_type) }, -#else -#define MICROPY_HW_NIC_ESP_HOSTED -#endif - #ifndef MICROPY_BOARD_NETWORK_INTERFACES #define MICROPY_BOARD_NETWORK_INTERFACES #endif #define MICROPY_PORT_NETWORK_INTERFACES \ - MICROPY_HW_NIC_ESP_HOSTED \ MICROPY_BOARD_NETWORK_INTERFACES \ // Miscellaneous settings diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 877cea08717..35afea4fac5 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -242,46 +242,11 @@ #endif #endif -#if MICROPY_PY_NETWORK_CYW43 -extern const struct _mp_obj_type_t mp_network_cyw43_type; -#define MICROPY_HW_NIC_CYW43 \ - { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(CYW43_LINK_DOWN) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(CYW43_LINK_JOIN) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(CYW43_LINK_BADAUTH) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(CYW43_LINK_NONET) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_CONNECT_FAIL), MP_ROM_INT(CYW43_LINK_FAIL) }, \ - { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(CYW43_LINK_UP) }, -#else -#define MICROPY_HW_NIC_CYW43 -#endif - -#if MICROPY_PY_NETWORK_NINAW10 -// This Network interface requires the extended socket state. -#ifndef MICROPY_PY_SOCKET_EXTENDED_STATE -#define MICROPY_PY_SOCKET_EXTENDED_STATE (1) -#endif -extern const struct _mp_obj_type_t mod_network_nic_type_nina; -#define MICROPY_HW_NIC_NINAW10 { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mod_network_nic_type_nina) }, -#else -#define MICROPY_HW_NIC_NINAW10 -#endif - -#if MICROPY_PY_NETWORK_WIZNET5K -extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k; -#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) }, -#else -#define MICROPY_HW_NIC_WIZNET5K -#endif - #ifndef MICROPY_BOARD_NETWORK_INTERFACES #define MICROPY_BOARD_NETWORK_INTERFACES #endif #define MICROPY_PORT_NETWORK_INTERFACES \ - MICROPY_HW_NIC_CYW43 \ - MICROPY_HW_NIC_NINAW10 \ - MICROPY_HW_NIC_WIZNET5K \ MICROPY_BOARD_NETWORK_INTERFACES \ // Additional entries for use with pendsv_schedule_dispatch. diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index d1d6fe249bd..bfaa3fb0ba0 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -205,20 +205,6 @@ extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_NIC_ETH #endif -#if MICROPY_PY_NETWORK_CYW43 -extern const struct _mp_obj_type_t mp_network_cyw43_type; -#define MICROPY_HW_NIC_CYW43 { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, -#else -#define MICROPY_HW_NIC_CYW43 -#endif - -#if MICROPY_PY_NETWORK_WIZNET5K -extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k; -#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) }, -#else -#define MICROPY_HW_NIC_WIZNET5K -#endif - // extra constants #define MICROPY_PORT_CONSTANTS \ MACHINE_BUILTIN_MODULE_CONSTANTS \ @@ -231,8 +217,6 @@ extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k; #define MICROPY_PORT_NETWORK_INTERFACES \ MICROPY_HW_NIC_ETH \ - MICROPY_HW_NIC_CYW43 \ - MICROPY_HW_NIC_WIZNET5K \ MICROPY_BOARD_NETWORK_INTERFACES \ #define MP_STATE_PORT MP_STATE_VM From e43a3849d9b725f7a59dd17623193738b7ea04a5 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 6 Jun 2025 07:57:54 +0200 Subject: [PATCH 0745/2098] lib/berkeley-db-1.xx: Update submodule to latest. Fixes a memory leak in the case lseek fails when creating the mpool. Signed-off-by: Jeff Epler --- lib/berkeley-db-1.xx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/berkeley-db-1.xx b/lib/berkeley-db-1.xx index 85373b548f1..0f3bb6947c2 160000 --- a/lib/berkeley-db-1.xx +++ b/lib/berkeley-db-1.xx @@ -1 +1 @@ -Subproject commit 85373b548f1fb0119a463582570b44189dfb09ae +Subproject commit 0f3bb6947c2f57233916dccd7bb425d7bf86e5a6 From 78ee1bac60a89f13d62395c1f7b6b122e26af98a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 May 2025 12:20:43 +0200 Subject: [PATCH 0746/2098] py/emitnative: Let Viper int-indexed code use appropriate operands. This commit extends the generic ASM API by adding the rest of the ASM_{LOAD,STORE}[size]_REG_REG_OFFSET macros whenever applicable. The Viper int-indexed load/store code generator was changed to use those API functions if they are available, falling back to backend-specific implementations if possible and ultimately to a generic implementation. Right now all backends except for x64 implement load16, load32, and store32 operations (x64 only implements load16). Signed-off-by: Alessandro Gatti --- py/asmarm.h | 6 ++++-- py/asmrv32.h | 10 ++++++---- py/asmthumb.h | 6 ++++-- py/asmx86.h | 6 ++++-- py/asmxtensa.h | 6 ++++-- py/emitnative.c | 24 ++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 42fa1ea212a..2c3af413125 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -208,16 +208,18 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_arm_sub_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_arm_mul_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) -#define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_str_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) +#define ASM_STORE_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_value), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_value, reg_base) asm_arm_strb_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrb_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrh_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) diff --git a/py/asmrv32.h b/py/asmrv32.h index 6453b0a3d43..4f986d7bbd5 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -732,11 +732,12 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_JUMP_IF_REG_NONZERO(state, rs, label, bool_test) asm_rv32_emit_jump_if_reg_nonzero(state, rs, label) #define ASM_JUMP_IF_REG_ZERO(state, rs, label, bool_test) asm_rv32_emit_jump_if_reg_eq(state, rs, ASM_RV32_REG_ZERO, label) #define ASM_JUMP_REG(state, rs) asm_rv32_opcode_cjr(state, rs) +#define ASM_LOAD_REG_REG_OFFSET(state, rd, rs, offset) ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, offset) #define ASM_LOAD16_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load16_reg_reg_offset(state, rd, rs, offset) #define ASM_LOAD16_REG_REG(state, rd, rs) asm_rv32_opcode_lhu(state, rd, rs, 0) -#define ASM_LOAD32_REG_REG(state, rd, rs) ASM_LOAD_REG_REG_OFFSET(state, rd, rs, 0) +#define ASM_LOAD32_REG_REG(state, rd, rs) ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, 0) +#define ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset) #define ASM_LOAD8_REG_REG(state, rd, rs) asm_rv32_opcode_lbu(state, rd, rs, 0) -#define ASM_LOAD_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset) #define ASM_LSL_REG_REG(state, rd, rs) asm_rv32_opcode_sll(state, rd, rd, rs) #define ASM_LSR_REG_REG(state, rd, rs) asm_rv32_opcode_srl(state, rd, rd, rs) #define ASM_MOV_LOCAL_REG(state, local, rs) asm_rv32_emit_mov_local_reg(state, local, rs) @@ -749,10 +750,11 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_NEG_REG(state, rd) asm_rv32_opcode_sub(state, rd, ASM_RV32_REG_ZERO, rd) #define ASM_NOT_REG(state, rd) asm_rv32_opcode_xori(state, rd, rd, -1) #define ASM_OR_REG_REG(state, rd, rs) asm_rv32_opcode_or(state, rd, rd, rs) +#define ASM_STORE_REG_REG_OFFSET(state, rd, rs, offset) ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) #define ASM_STORE16_REG_REG(state, rs1, rs2) asm_rv32_opcode_sh(state, rs1, rs2, 0) -#define ASM_STORE32_REG_REG(state, rs1, rs2) ASM_STORE_REG_REG_OFFSET(state, rs1, rs2, 0) +#define ASM_STORE32_REG_REG(state, rs1, rs2) ASM_STORE32_REG_REG_OFFSET(state, rs1, rs2, 0) +#define ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset) #define ASM_STORE8_REG_REG(state, rs1, rs2) asm_rv32_opcode_sb(state, rs1, rs2, 0) -#define ASM_STORE_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) diff --git a/py/asmthumb.h b/py/asmthumb.h index cc4213503bf..570b44b96bc 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -462,16 +462,18 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_thumb_sub_rlo_rlo_rlo((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_MUL, (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrb_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrh_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_thumb_ldrh_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), (word_offset)) +#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_thumb_strb_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_thumb_ldrb_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ diff --git a/py/asmx86.h b/py/asmx86.h index af73c163b4f..5011e56d0c1 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -200,16 +200,18 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x86_sub_r32_r32((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x86_mul_r32_r32((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem8_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) -#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (word_offset)) +#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x86_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x86_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (word_offset)) #endif // GENERIC_ASM_API diff --git a/py/asmxtensa.h b/py/asmxtensa.h index d0fd2dcc187..598111f909f 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -413,7 +413,7 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_xtensa_op_sub((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_xtensa_op_mull((as), (reg_dest), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_l32i_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), (uint16_offset)) @@ -423,13 +423,14 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0); \ } while (0) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_l32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ do { \ asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0); \ } while (0) -#define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s8i((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s16i((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ @@ -438,6 +439,7 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); asm_xtensa_op_s16i((as), (reg_val), (reg_base), 0); \ } while (0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s32i_n((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ do { \ asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ diff --git a/py/emitnative.c b/py/emitnative.c index 4b470f3c935..577bcddfbfc 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1537,6 +1537,9 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory + #ifdef ASM_LOAD8_REG_REG_OFFSET + ASM_LOAD8_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); + #else #if N_THUMB if (index_value >= 0 && index_value < 32) { asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); @@ -1561,10 +1564,14 @@ static void emit_native_load_subscr(emit_t *emit) { reg_base = reg_index; } ASM_LOAD8_REG_REG(emit->as, REG_RET, reg_base); // load from (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory + #ifdef ASM_LOAD16_REG_REG_OFFSET + ASM_LOAD16_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); + #else #if N_THUMB if (index_value >= 0 && index_value < 32) { asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); @@ -1589,10 +1596,14 @@ static void emit_native_load_subscr(emit_t *emit) { reg_base = reg_index; } ASM_LOAD16_REG_REG(emit->as, REG_RET, reg_base); // load from (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to 32-bit memory + #ifdef ASM_LOAD32_REG_REG_OFFSET + ASM_LOAD32_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); + #else #if N_THUMB if (index_value >= 0 && index_value < 32) { asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); @@ -1617,6 +1628,7 @@ static void emit_native_load_subscr(emit_t *emit) { reg_base = reg_index; } ASM_LOAD32_REG_REG(emit->as, REG_RET, reg_base); // load from (base+4*index) + #endif break; } default: @@ -1809,6 +1821,9 @@ static void emit_native_store_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory + #ifdef ASM_STORE8_REG_REG_OFFSET + ASM_STORE8_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); + #else // TODO optimise to use thumb strb r1, [r2, r3] #if N_THUMB if (index_value >= 0 && index_value < 32) { @@ -1837,10 +1852,14 @@ static void emit_native_store_subscr(emit_t *emit) { reg_base = reg_index; } ASM_STORE8_REG_REG(emit->as, reg_value, reg_base); // store value to (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory + #ifdef ASM_STORE16_REG_REG_OFFSET + ASM_STORE16_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); + #else #if N_THUMB if (index_value >= 0 && index_value < 32) { asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); @@ -1864,10 +1883,14 @@ static void emit_native_store_subscr(emit_t *emit) { reg_base = reg_index; } ASM_STORE16_REG_REG(emit->as, reg_value, reg_base); // store value to (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to 32-bit memory + #ifdef ASM_STORE32_REG_REG_OFFSET + ASM_STORE32_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); + #else #if N_THUMB if (index_value >= 0 && index_value < 32) { asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); @@ -1896,6 +1919,7 @@ static void emit_native_store_subscr(emit_t *emit) { reg_base = reg_index; } ASM_STORE32_REG_REG(emit->as, reg_value, reg_base); // store value to (base+4*index) + #endif break; } default: From 1f5ba6998bb1895354f5afcd7bb52d83a02733be Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 May 2025 12:34:12 +0200 Subject: [PATCH 0747/2098] py/asmthumb: Extend load/store generators with ARMv7-M opcodes. This commit lets the Thumb native code generator backend emit ARMv7-M specific opcodes for indexed load/store operations if possible. Now T3 opcode encodings are used if the generator backend is configured to allow emitting ARMv7-M opcodes and if the (unsigned) scaled index fits in 12 bits. Or, in other words, LDR{B,H}.W and STR{B,H}.W opcodes are now emitted if possible. Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 66 ++++++++++++++++++++++++++----------------------- py/asmthumb.h | 27 +++++++++++--------- py/emitnative.c | 43 +++++--------------------------- 3 files changed, 57 insertions(+), 79 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index 06021f2bc93..c0ba6222cfd 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -40,6 +40,7 @@ #define UNSIGNED_FIT5(x) ((uint32_t)(x) < 32) #define UNSIGNED_FIT7(x) ((uint32_t)(x) < 128) #define UNSIGNED_FIT8(x) (((x) & 0xffffff00) == 0) +#define UNSIGNED_FIT12(x) (((x) & 0xfffff000) == 0) #define UNSIGNED_FIT16(x) (((x) & 0xffff0000) == 0) #define SIGNED_FIT8(x) (((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80) #define SIGNED_FIT9(x) (((x) & 0xffffff00) == 0) || (((x) & 0xffffff00) == 0xffffff00) @@ -52,12 +53,6 @@ #define OP_SUB_W_RRI_HI(reg_src) (0xf2a0 | (reg_src)) #define OP_SUB_W_RRI_LO(reg_dest, imm11) ((imm11 << 4 & 0x7000) | reg_dest << 8 | (imm11 & 0xff)) -#define OP_LDR_W_HI(reg_base) (0xf8d0 | (reg_base)) -#define OP_LDR_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) - -#define OP_LDRH_W_HI(reg_base) (0xf8b0 | (reg_base)) -#define OP_LDRH_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) - static inline byte *asm_thumb_get_cur_to_write_bytes(asm_thumb_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); } @@ -432,11 +427,6 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label) { asm_thumb_add_reg_reg(as, rlo_dest, ASM_THUMB_REG_R15); // 2 bytes } -// ARMv7-M only -static inline void asm_thumb_ldr_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { - asm_thumb_op32(as, OP_LDR_W_HI(reg_base), OP_LDR_W_LO(reg_dest, word_offset * 4)); -} - // emits code for: reg_dest = reg_base + offset << offset_shift static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint offset_shift) { if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8) { @@ -464,30 +454,44 @@ static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint re } } -void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { - if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(word_offset)) { - asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_base, word_offset); - } else if (asm_thumb_allow_armv7m(as)) { - asm_thumb_ldr_reg_reg_i12(as, reg_dest, reg_base, word_offset); +#define OP_LDR_STR_W_HI(shift, reg) ((0xf880 | (shift) << 5) | (reg)) +#define OP_LDR_STR_W_LO(reg, imm12) (((reg) << 12) | (imm12)) + +#define OP_LDR 0x01 +#define OP_STR 0x00 + +#define OP_LDR_W 0x10 +#define OP_STR_W 0x00 + +static const uint8_t OP_LDR_STR_TABLE[3] = { + 0x0E, 0x10, 0x0C +}; + +void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint shift) { + if (UNSIGNED_FIT5(offset) && (reg_dest < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { + // Can use T1 encoding + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_LDR) << 11) | (offset << 6) | (reg_base << 3) | reg_dest); + } else if (asm_thumb_allow_armv7m(as) && UNSIGNED_FIT12(offset << shift)) { + // Can use T3 encoding + asm_thumb_op32(as, (OP_LDR_STR_W_HI(shift, reg_base) | OP_LDR_W), OP_LDR_STR_W_LO(reg_dest, (offset << shift))); } else { - asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, word_offset - 31, 2); - asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_dest, 31); + // Must use the generic sequence + asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, offset - 31, shift); + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_LDR) << 11) | (31 << 6) | (reg_dest << 3) | (reg_dest)); } } -// ARMv7-M only -static inline void asm_thumb_ldrh_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset) { - asm_thumb_op32(as, OP_LDRH_W_HI(reg_base), OP_LDRH_W_LO(reg_dest, uint16_offset * 2)); -} - -void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset) { - if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(uint16_offset)) { - asm_thumb_ldrh_rlo_rlo_i5(as, reg_dest, reg_base, uint16_offset); - } else if (asm_thumb_allow_armv7m(as)) { - asm_thumb_ldrh_reg_reg_i12(as, reg_dest, reg_base, uint16_offset); +void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint shift) { + if (UNSIGNED_FIT5(offset) && (reg_src < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { + // Can use T1 encoding + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_STR) << 11) | (offset << 6) | (reg_base << 3) | reg_src); + } else if (asm_thumb_allow_armv7m(as) && UNSIGNED_FIT12(offset << shift)) { + // Can use T3 encoding + asm_thumb_op32(as, (OP_LDR_STR_W_HI(shift, reg_base) | OP_STR_W), OP_LDR_STR_W_LO(reg_src, (offset << shift))); } else { - asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, uint16_offset - 31, 1); - asm_thumb_ldrh_rlo_rlo_i5(as, reg_dest, reg_dest, 31); + // Must use the generic sequence + asm_thumb_add_reg_reg_offset(as, reg_base, reg_base, offset - 31, shift); + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_STR) << 11) | (31 << 6) | (reg_base << 3) | reg_src); } } @@ -569,7 +573,7 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel) { void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp) { // Load ptr to function from table, indexed by fun_id, then call it - asm_thumb_ldr_reg_reg_i12_optimised(as, reg_temp, ASM_THUMB_REG_FUN_TABLE, fun_id); + asm_thumb_load_reg_reg_offset(as, reg_temp, ASM_THUMB_REG_FUN_TABLE, fun_id, 2); asm_thumb_op16(as, OP_BLX(reg_temp)); } diff --git a/py/asmthumb.h b/py/asmthumb.h index 570b44b96bc..6c83b583e2b 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -382,8 +382,10 @@ void asm_thumb_mov_reg_local(asm_thumb_t *as, uint rlo_dest, int local_num); // void asm_thumb_mov_reg_local_addr(asm_thumb_t *as, uint rlo_dest, int local_num); // convenience void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); -void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience -void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience +// Generate optimised load dest, [src, #offset] sequence +void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint shift); +// Generate optimised store src, [dest, #offset] sequence +void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint shift); void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch @@ -463,17 +465,20 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_MUL, (reg_dest), (reg_src)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrb_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrh_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_thumb_ldrh_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (uint16_offset)) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) ASM_LOAD8_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG_OFFSET(as, reg_dest, reg_base, byte_offset) asm_thumb_load_reg_reg_offset((as), (reg_dest), (reg_base), (byte_offset), 0) +#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) ASM_LOAD16_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, halfword_offset) asm_thumb_load_reg_reg_offset((as), (reg_dest), (reg_base), (halfword_offset), 1) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_load_reg_reg_offset((as), (reg_dest), (reg_base), (word_offset), 2) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), (word_offset)) -#define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_thumb_strb_rlo_rlo_i5((as), (reg_src), (reg_base), 0) -#define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), (word_offset)) +#define ASM_STORE8_REG_REG(as, reg_src, reg_base) ASM_STORE8_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE8_REG_REG_OFFSET(as, reg_src, reg_base, byte_offset) asm_thumb_store_reg_reg_offset((as), (reg_src), (reg_base), (byte_offset), 0) +#define ASM_STORE16_REG_REG(as, reg_src, reg_base) ASM_STORE16_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_OFFSET(as, reg_src, reg_base, halfword_offset) asm_thumb_store_reg_reg_offset((as), (reg_src), (reg_base), (halfword_offset), 1) +#define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_store_reg_reg_offset((as), (reg_src), (reg_base), (word_offset), 2) #define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_thumb_ldrb_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ diff --git a/py/emitnative.c b/py/emitnative.c index 577bcddfbfc..c1965fc913f 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1540,12 +1540,7 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD8_REG_REG_OFFSET ASM_LOAD8_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 12)) { asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); break; @@ -1572,12 +1567,7 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD16_REG_REG_OFFSET ASM_LOAD16_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 11)) { asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); break; @@ -1604,12 +1594,7 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD32_REG_REG_OFFSET ASM_LOAD32_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 10)) { asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); break; @@ -1824,13 +1809,7 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE8_REG_REG_OFFSET ASM_STORE8_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - // TODO optimise to use thumb strb r1, [r2, r3] - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_strb_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 12)) { asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); break; @@ -1860,12 +1839,7 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE16_REG_REG_OFFSET ASM_STORE16_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 11)) { asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); break; @@ -1891,12 +1865,7 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE32_REG_REG_OFFSET ASM_STORE32_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_THUMB - if (index_value >= 0 && index_value < 32) { - asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 + #if N_RV32 if (FIT_SIGNED(index_value, 10)) { asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); break; From 84ad2c6cd04a1c51d706f9b25b81cad52c6c5802 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 May 2025 13:38:36 +0200 Subject: [PATCH 0748/2098] py/asmxtensa: Extend existing specialised load/store operations range. This commit updates the existing specialised implementations for int-indexed 32-bit load and store operations, and adds a specialised implementation for int-indexed 16-bit load. The 32-bit operations relied on the fact that their applicability was limited to a specific range, falling back on a generic implementation otherwise. Introducing a single entry point for each int-indexed load/store operation size would break that assumption. Now those two operations contain fallback code to generate working code by themselves instead of raising an exception. The 16-bit operation instead simply did not have any range check, but it was not exposed directly to the Viper emitter. When a 16-bit int-indexed load entry point was introduced, the existing implementation would fail when accessing memory outside its 0..255 halfwords range. A specialised implementation is now present, performing fewer operations than the existing Viper emitter equivalent. Signed-off-by: Alessandro Gatti --- py/asmxtensa.c | 24 ++++++++++++++++++++++-- py/asmxtensa.h | 8 +++++++- py/emitnative.c | 15 --------------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index ab2e7aeaeb6..2b36a56ead2 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -34,6 +34,12 @@ #include "py/asmxtensa.h" +#if N_XTENSAWIN +#define REG_TEMP ASM_XTENSA_REG_TEMPORARY_WIN +#else +#define REG_TEMP ASM_XTENSA_REG_TEMPORARY +#endif + #define WORD_SIZE (4) #define SIGNED_FIT8(x) ((((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80)) #define SIGNED_FIT12(x) ((((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800)) @@ -248,7 +254,9 @@ void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, u } else if (word_offset < 256) { asm_xtensa_op_l32i(as, reg_dest, reg_base, word_offset); } else { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("asm overflow")); + asm_xtensa_mov_reg_i32_optimised(as, reg_dest, word_offset * 4); + asm_xtensa_op_add_n(as, reg_dest, reg_base, reg_dest); + asm_xtensa_op_l32i_n(as, reg_dest, reg_dest, 0); } } @@ -258,7 +266,19 @@ void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, ui } else if (word_offset < 256) { asm_xtensa_op_s32i(as, reg_src, reg_base, word_offset); } else { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("asm overflow")); + asm_xtensa_mov_reg_i32_optimised(as, REG_TEMP, word_offset * 4); + asm_xtensa_op_add_n(as, REG_TEMP, reg_base, REG_TEMP); + asm_xtensa_op_s32i_n(as, reg_src, REG_TEMP, 0); + } +} + +void asm_xtensa_l16ui_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint halfword_offset) { + if (halfword_offset < 256) { + asm_xtensa_op_l16ui(as, reg_dest, reg_base, halfword_offset); + } else { + asm_xtensa_mov_reg_i32_optimised(as, reg_dest, halfword_offset * 2); + asm_xtensa_op_add_n(as, reg_dest, reg_base, reg_dest); + asm_xtensa_op_l16ui(as, reg_dest, reg_dest, 0); } } diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 598111f909f..d90aef3c533 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -295,6 +295,7 @@ void asm_xtensa_mov_reg_local_addr(asm_xtensa_t *as, uint reg_dest, int local_nu void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label); void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset); void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, uint word_offset); +void asm_xtensa_l16ui_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint halfword_offset); void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx); void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); void asm_xtensa_bit_branch(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t bit, mp_uint_t label, mp_uint_t condition); @@ -306,6 +307,11 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_XTENSA_REG_FUN_TABLE ASM_XTENSA_REG_A15 #define ASM_XTENSA_REG_FUN_TABLE_WIN ASM_XTENSA_REG_A7 +// Internal temporary register (currently aliased to REG_ARG_5 for xtensa, +// and to REG_TEMP2 for xtensawin). +#define ASM_XTENSA_REG_TEMPORARY ASM_XTENSA_REG_A6 +#define ASM_XTENSA_REG_TEMPORARY_WIN ASM_XTENSA_REG_A12 + #if GENERIC_ASM_API // The following macros provide a (mostly) arch-independent API to @@ -416,7 +422,7 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), (uint16_offset)) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_l16ui_optimised((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ do { \ asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ diff --git a/py/emitnative.c b/py/emitnative.c index c1965fc913f..dba9c7169b6 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1572,11 +1572,6 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); break; } - #elif N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); - break; - } #endif if (index_value != 0) { // index is a non-zero immediate @@ -1599,11 +1594,6 @@ static void emit_native_load_subscr(emit_t *emit) { asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); break; } - #elif N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); - break; - } #endif if (index_value != 0) { // index is a non-zero immediate @@ -1870,11 +1860,6 @@ static void emit_native_store_subscr(emit_t *emit) { asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); break; } - #elif N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_s32i_optimised(emit->as, reg_value, reg_base, index_value); - break; - } #endif if (index_value != 0) { // index is a non-zero immediate From 901c96dc554100513523cc9136161126e8cda436 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 May 2025 14:03:53 +0200 Subject: [PATCH 0749/2098] py/emitnative: Remove redundant RV32 Viper int-indexed code. This commit removes redundant RV32 implementations of certain int-indexed code generation operations (32-bit load/store and 16-bit load). Those operations were already available as part of the native emitter API but were not exposed to the Viper code generator. As part of the introduction of more specialised load and store API calls to int-indexed Viper load/store generator bits, the existing native emitter implementations are reused, thus making the Viper implementations redundant. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index dba9c7169b6..8ac9810c8ff 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1567,12 +1567,6 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD16_REG_REG_OFFSET ASM_LOAD16_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); - break; - } - #endif if (index_value != 0) { // index is a non-zero immediate need_reg_single(emit, reg_index, 0); @@ -1589,12 +1583,6 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD32_REG_REG_OFFSET ASM_LOAD32_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); - break; - } - #endif if (index_value != 0) { // index is a non-zero immediate need_reg_single(emit, reg_index, 0); @@ -1855,12 +1843,6 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE32_REG_REG_OFFSET ASM_STORE32_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); - break; - } - #endif if (index_value != 0) { // index is a non-zero immediate #if N_ARM From bbab2e98f5d82b3111544d26e600af592977c0c3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 22 May 2025 14:37:21 +0200 Subject: [PATCH 0750/2098] py/asmarm: Extend int-indexed 32-bit load/store offset ranges. This commit extends the range for int-indexed load/store opcode generators, making them emit correct code sequences for offsets that span more than 12 bits. This is necessary due to those generator bits being also used in the Viper emitter, where it's more probable to reference offsets that can not be embedded in the LDR/STR opcodes. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 26 ++++++++++++++++++++------ py/asmarm.h | 12 ++++++------ py/emitnative.c | 5 ----- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index d304567882c..5322321553c 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -333,9 +333,16 @@ void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs) { emit_al(as, 0x1a00050 | (rd << 12) | (rs << 8) | rd); } -void asm_arm_ldr_reg_reg(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { - // ldr rd, [rn, #off] - emit_al(as, 0x5900000 | (rn << 16) | (rd << 12) | byte_offset); +void asm_arm_ldr_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { + if (byte_offset < 0x1000) { + // ldr rd, [rn, #off] + emit_al(as, 0x5900000 | (rn << 16) | (rd << 12) | byte_offset); + } else { + // mov r8, #off + // ldr rd, [rn, r8] + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); + emit_al(as, 0x7900000 | (rn << 16) | (rd << 12) | ASM_ARM_REG_R8); + } } void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { @@ -376,9 +383,16 @@ void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { emit_al(as, 0x7900100 | (rm << 16) | (rd << 12) | rn); } -void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { - // str rd, [rm, #off] - emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); +void asm_arm_str_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { + if (byte_offset < 0x1000) { + // str rd, [rm, #off] + emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); + } else { + // mov r8, #off + // str rd, [rm, r8] + asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); + emit_al(as, 0x7800000 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); + } } void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm) { diff --git a/py/asmarm.h b/py/asmarm.h index 2c3af413125..07ed425c980 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -109,11 +109,11 @@ void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs); void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs); // memory -void asm_arm_ldr_reg_reg(asm_arm_t *as, uint rd, uint rn, uint byte_offset); +void asm_arm_ldr_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn); void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn); -void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset); +void asm_arm_str_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset); void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm); void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm); @@ -212,14 +212,14 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg_offset((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg_offset((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_STORE_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_value), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_value, reg_base) asm_arm_strb_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) -#define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) -#define ASM_STORE32_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 4 * (word_offset)) +#define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg_offset((as), (reg_value), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) asm_arm_str_reg_reg_offset((as), (reg_value), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrb_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrh_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) diff --git a/py/emitnative.c b/py/emitnative.c index 8ac9810c8ff..7662de69e22 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1845,11 +1845,6 @@ static void emit_native_store_subscr(emit_t *emit) { #else if (index_value != 0) { // index is a non-zero immediate - #if N_ARM - ASM_MOV_REG_IMM(emit->as, reg_index, index_value); - asm_arm_str_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - break; - #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 4*index to base reg_base = reg_index; From 5b90d6d4183d0455ba5077cfa8601bfacaa0bf15 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 8 Jun 2025 23:27:31 +0200 Subject: [PATCH 0751/2098] py/asmarm: Give a proper name to the temporary register. This commit performs a small refactoring on the Arm native emitter, by renaming all but one instance of ASM_ARM_REG_R8 into REG_TEMP. ASM_ARM_REG_R8 is the temporary register used by the emitter when operations cannot overwrite the value of a particular register and some extra storage is needed. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index 5322321553c..be50a991b7d 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -36,6 +36,8 @@ #include "py/asmarm.h" +#define REG_TEMP ASM_ARM_REG_R8 + #define SIGNED_FIT24(x) (((x) & 0xff800000) == 0) || (((x) & 0xff000000) == 0xff000000) // Insert word into instruction flow @@ -171,8 +173,8 @@ void asm_arm_entry(asm_arm_t *as, int num_locals) { if (as->stack_adjust < 0x100) { emit_al(as, asm_arm_op_sub_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); } else { - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, as->stack_adjust); - emit_al(as, asm_arm_op_sub_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, as->stack_adjust); + emit_al(as, asm_arm_op_sub_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, REG_TEMP)); } } } @@ -182,8 +184,8 @@ void asm_arm_exit(asm_arm_t *as) { if (as->stack_adjust < 0x100) { emit_al(as, asm_arm_op_add_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust)); } else { - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, as->stack_adjust); - emit_al(as, asm_arm_op_add_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, as->stack_adjust); + emit_al(as, asm_arm_op_add_reg(ASM_ARM_REG_SP, ASM_ARM_REG_SP, REG_TEMP)); } } @@ -293,10 +295,10 @@ void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num) { if (local_num >= 0x40) { - // mov r8, #local_num*4 - // add rd, sp, r8 - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, local_num << 2); - emit_al(as, asm_arm_op_add_reg(rd, ASM_ARM_REG_SP, ASM_ARM_REG_R8)); + // mov temp, #local_num*4 + // add rd, sp, temp + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, local_num << 2); + emit_al(as, asm_arm_op_add_reg(rd, ASM_ARM_REG_SP, REG_TEMP)); } else { // add rd, sp, #local_num*4 emit_al(as, asm_arm_op_add_imm(rd, ASM_ARM_REG_SP, local_num << 2)); @@ -338,10 +340,10 @@ void asm_arm_ldr_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offse // ldr rd, [rn, #off] emit_al(as, 0x5900000 | (rn << 16) | (rd << 12) | byte_offset); } else { - // mov r8, #off - // ldr rd, [rn, r8] - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); - emit_al(as, 0x7900000 | (rn << 16) | (rd << 12) | ASM_ARM_REG_R8); + // mov temp, #off + // ldr rd, [rn, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x7900000 | (rn << 16) | (rd << 12) | REG_TEMP); } } @@ -352,8 +354,8 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // ldrh doesn't support scaled register index - emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1 - emit_al(as, 0x19000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // ldrh rd, [rm, r8]; + emit_al(as, 0x1a00080 | (REG_TEMP << 12) | rn); // mov temp, rn, lsl #1 + emit_al(as, 0x19000b0 | (rm << 16) | (rd << 12) | REG_TEMP); // ldrh rd, [rm, temp]; } void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { @@ -361,10 +363,10 @@ void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offs // ldrh rd, [rn, #off] emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); } else { - // mov r8, #off - // ldrh rd, [rn, r8] - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); - emit_al(as, 0x19000b0 | (rn << 16) | (rd << 12) | ASM_ARM_REG_R8); + // mov temp, #off + // ldrh rd, [rn, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x19000b0 | (rn << 16) | (rd << 12) | REG_TEMP); } } @@ -388,10 +390,10 @@ void asm_arm_str_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offse // str rd, [rm, #off] emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); } else { - // mov r8, #off - // str rd, [rm, r8] - asm_arm_mov_reg_i32_optimised(as, ASM_ARM_REG_R8, byte_offset); - emit_al(as, 0x7800000 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); + // mov temp, #off + // str rd, [rm, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x7800000 | (rm << 16) | (rd << 12) | REG_TEMP); } } @@ -412,8 +414,8 @@ void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { void asm_arm_strh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // strh doesn't support scaled register index - emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1 - emit_al(as, 0x18000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // strh rd, [rm, r8] + emit_al(as, 0x1a00080 | (REG_TEMP << 12) | rn); // mov temp, rn, lsl #1 + emit_al(as, 0x18000b0 | (rm << 16) | (rd << 12) | REG_TEMP); // strh rd, [rm, temp] } void asm_arm_strb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { From c1c73d966ea2d3ee20dbde8c5cd6ea5bd99bc94f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 4 Jun 2025 21:16:43 +0200 Subject: [PATCH 0752/2098] tests/run-tests.py: Unconditionally enable native tests if asked. This commit lets the test runner enumerate and run native tests if the feature check fails but native tests were explicitly requested from the command line. The old behaviour would disable native tests anyway if the feature check failed, however this hid a bug in the x86 native emitter that would be triggered even during the feature check. That meant the test suite would pass on x86 even with a broken emitter, as those tests would have been skipped anyway. Now, if the user asks for native code it will get native code out of the runner no matter what. Co-authored-by: Damien George Signed-off-by: Alessandro Gatti --- tests/run-tests.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index cb7a8b77056..da05e18e4f1 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -875,11 +875,7 @@ def run_one_test(test_file): test_basename = test_file.replace("..", "_").replace("./", "").replace("/", "_") test_name = os.path.splitext(os.path.basename(test_file))[0] - is_native = ( - test_name.startswith("native_") - or test_name.startswith("viper_") - or args.emit == "native" - ) + is_native = test_name.startswith("native_") or test_name.startswith("viper_") is_endian = test_name.endswith("_endian") is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig") is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") From 0da22b2c30aac8a843c504f907b0675190ee0c89 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 4 Jun 2025 23:02:07 +0200 Subject: [PATCH 0753/2098] tools/ci.sh: Fix nanbox CI test runs. This commit fixes CI test runs for the `nanbox` target, which were broken by the unconditional native emitter code output changes in the test runner. The `nanbox` configuration does not enable native emitters of any kind, and with a full test run that includes executing emitted native code things would break when doing CI runs. This is worked around by introducing a common subset of tests that do not involve the native emitter, and a more comprehensive set of tests that include both non-emitter and emitter tests. The `nanbox` CI test run will stop at the first subset, whilst other configurations will run that and execute further tests. Function names have been kept the same for steps that involve native code, with the `nanbox` subset having another one. This should not trigger any breakage in existing CI configurations or external scripts. Signed-off-by: Alessandro Gatti --- ports/unix/Makefile | 11 +++++++---- tools/ci.sh | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 88fa1af0454..3c54d156c31 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -256,21 +256,24 @@ endif include $(TOP)/py/mkrules.mk -.PHONY: test test_full +.PHONY: test test_full_no_native test_full test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -test_full: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py +test_full_no_native: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d thread - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --emit native cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython cat $(TOP)/tests/basics/0prelim.py | ./$(BUILD)/$(PROG) | grep -q 'abc' +test_full: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py test_full_no_native + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --emit native + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + test_gcov: test_full gcov -o $(BUILD)/py $(TOP)/py/*.c gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c diff --git a/tools/ci.sh b/tools/ci.sh index 011aabea31c..aafd6cc573b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -527,13 +527,26 @@ function ci_unix_run_tests_helper { make -C ports/unix "$@" test } +function ci_unix_run_tests_full_extra { + micropython=$1 + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-multitests.py multi_net/*.py) + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-perfbench.py 1000 1000) +} + +function ci_unix_run_tests_full_no_native_helper { + variant=$1 + shift + micropython=../ports/unix/build-$variant/micropython + make -C ports/unix VARIANT=$variant "$@" test_full_no_native + ci_unix_run_tests_full_extra $micropython +} + function ci_unix_run_tests_full_helper { variant=$1 shift micropython=../ports/unix/build-$variant/micropython make -C ports/unix VARIANT=$variant "$@" test_full - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-multitests.py multi_net/*.py) - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-perfbench.py 1000 1000) + ci_unix_run_tests_full_extra $micropython } function ci_native_mpy_modules_build { @@ -673,7 +686,7 @@ function ci_unix_nanbox_build { } function ci_unix_nanbox_run_tests { - ci_unix_run_tests_full_helper nanbox PYTHON=python2.7 + ci_unix_run_tests_full_no_native_helper nanbox PYTHON=python2.7 } function ci_unix_float_build { From 80b823bca15d846de940d08a6e8144290514f8a2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 8 Jun 2025 21:51:30 +0200 Subject: [PATCH 0754/2098] py/asmxtensa: Extend BCCZ range to 18 bits. This commit lets the native emitter backend extends the range of the BCCZ family of opcodes (BEQZ, BNEZ, BLTZ, BGEZ) from 12 bits to 18 bits. The test suite contains some test files that, when compiled into native code, would require BCCZ jumps outside the (signed) 12 bits range. In this case either the MicroPython interpreter or mpy-cross would raise an exception, not running the test when using the "--via-mpy --emit native" command line options with the test runner. This comes with a 3 bytes penalty on each forward jump, bringing the footprint of those jumps to 6 bytes each, as a longer opcode sequence has to be emitted to let jumps access a larger range. However, this is slightly offset by the fact that backward jumps can be emitted with a single opcode if the range is small enough (3 bytes for a 12-bits offset). Signed-off-by: Alessandro Gatti --- py/asmxtensa.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index 2b36a56ead2..fd71dedb16f 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -41,6 +41,7 @@ #endif #define WORD_SIZE (4) +#define SIGNED_FIT6(x) ((((x) & 0xffffffe0) == 0) || (((x) & 0xffffffe0) == 0xffffffe0)) #define SIGNED_FIT8(x) ((((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80)) #define SIGNED_FIT12(x) ((((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800)) #define SIGNED_FIT18(x) ((((x) & 0xfffe0000) == 0) || (((x) & 0xfffe0000) == 0xfffe0000)) @@ -158,13 +159,36 @@ void asm_xtensa_j_label(asm_xtensa_t *as, uint label) { asm_xtensa_op_j(as, rel); } +static bool calculate_branch_displacement(asm_xtensa_t *as, uint label, ptrdiff_t *displacement) { + assert(displacement != NULL && "Displacement pointer is NULL"); + + uint32_t label_offset = get_label_dest(as, label); + *displacement = (ptrdiff_t)(label_offset - as->base.code_offset - 4); + return (label_offset != (uint32_t)-1) && (*displacement < 0); +} + void asm_xtensa_bccz_reg_label(asm_xtensa_t *as, uint cond, uint reg, uint label) { - uint32_t dest = get_label_dest(as, label); - int32_t rel = dest - as->base.code_offset - 4; - if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT12(rel)) { + ptrdiff_t rel = 0; + bool can_emit_short_jump = calculate_branch_displacement(as, label, &rel); + + if (can_emit_short_jump && SIGNED_FIT12(rel)) { + // Backwards BCCZ opcodes with an offset that fits in 12 bits can + // be emitted without any change. + asm_xtensa_op_bccz(as, cond, reg, rel); + return; + } + + // Range is effectively extended to 18 bits, as a more complex jump code + // sequence is emitted. + if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT18(rel - 6)) { mp_raise_msg_varg(&mp_type_RuntimeError, ET_OUT_OF_RANGE, MP_QSTR_bccz); } - asm_xtensa_op_bccz(as, cond, reg, rel); + + // ~BCCZ skip ; +0 <- Condition is flipped here (EQ -> NE, etc.) + // J addr ; +3 + // skip: ; +6 + asm_xtensa_op_bccz(as, cond ^ 1, reg, 6 - 4); + asm_xtensa_op_j(as, rel - 3); } void asm_xtensa_bcc_reg_reg_label(asm_xtensa_t *as, uint cond, uint reg1, uint reg2, uint label) { From 43f6013294ff1c59fb956b47be5686bab5a389a6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 8 Jun 2025 22:58:33 +0200 Subject: [PATCH 0755/2098] py/asmxtensa: Extend BCC range to 18 bits. This commit lets the native emitter backend extends the range of the BCC family of opcodes (BALL, BANY, BBC, BBS, BEQ, BGE, BGEU, BLT, BLTU, BNALL, BNE, BNONE) from 8 bits to 18 bits. The test suite contains some test files that, when compiled into native code, would require BCC jumps outside the (signed) 8 bits range. In this case either the MicroPython interpreter or mpy-cross would raise an exception, not running the test when using the "--via-mpy --emit native" command line options with the test runner. This comes with a 3 bytes penalty on each forward jump, bringing the footprint of those jumps to 6 bytes each, as a longer opcode sequence has to be emitted to let jumps access a larger range. However, this is slightly offset by the fact that backward jumps can be emitted with a single opcode if the range is small enough (8-bits offset). Signed-off-by: Alessandro Gatti --- py/asmxtensa.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index fd71dedb16f..85a8cfef555 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -192,12 +192,27 @@ void asm_xtensa_bccz_reg_label(asm_xtensa_t *as, uint cond, uint reg, uint label } void asm_xtensa_bcc_reg_reg_label(asm_xtensa_t *as, uint cond, uint reg1, uint reg2, uint label) { - uint32_t dest = get_label_dest(as, label); - int32_t rel = dest - as->base.code_offset - 4; - if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT8(rel)) { + ptrdiff_t rel = 0; + bool can_emit_short_jump = calculate_branch_displacement(as, label, &rel); + + if (can_emit_short_jump && SIGNED_FIT8(rel)) { + // Backwards BCC opcodes with an offset that fits in 8 bits can + // be emitted without any change. + asm_xtensa_op_bcc(as, cond, reg1, reg2, rel); + return; + } + + // Range is effectively extended to 18 bits, as a more complex jump code + // sequence is emitted. + if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT18(rel - 6)) { mp_raise_msg_varg(&mp_type_RuntimeError, ET_OUT_OF_RANGE, MP_QSTR_bcc); } - asm_xtensa_op_bcc(as, cond, reg1, reg2, rel); + + // ~BCC skip ; +0 <- Condition is flipped here (EQ -> NE, etc.) + // J addr ; +3 + // skip: ; +6 + asm_xtensa_op_bcc(as, cond ^ 8, reg1, reg2, 6 - 4); + asm_xtensa_op_j(as, rel - 3); } // convenience function; reg_dest must be different from reg_src[12] From 5f4abeb3859c948cc4a65221af6013a95533cdc4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:26:12 +1000 Subject: [PATCH 0756/2098] py/asmthumb: Implement long jumps on Thumb/armv6m architecture. With this change, all tests (except thread tests) now pass on RPI_PICO when using the native emitter: (plug in RPI_PICO) $ cd tests $ ./run-tests.py -t a0 --via-mpy --emit native Signed-off-by: Damien George --- py/asmthumb.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index c0ba6222cfd..fda0f52705e 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -499,6 +499,7 @@ void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) +// In Thumb1 mode, this may clobber r1. void asm_thumb_b_label(asm_thumb_t *as, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; @@ -518,19 +519,40 @@ void asm_thumb_b_label(asm_thumb_t *as, uint label) { if (asm_thumb_allow_armv7m(as)) { asm_thumb_op32(as, OP_BW_HI(rel), OP_BW_LO(rel)); } else { + // this code path has to be the same instruction size irrespective of the value of rel + bool need_align = as->base.code_offset & 2u; if (SIGNED_FIT12(rel)) { - // this code path has to be the same number of instructions irrespective of rel asm_thumb_op16(as, OP_B_N(rel)); - } else { asm_thumb_op16(as, ASM_THUMB_OP_NOP); - if (dest != (mp_uint_t)-1) { - // we have an actual branch > 12 bits; this is not handled yet - mp_raise_NotImplementedError(MP_ERROR_TEXT("native method too big")); + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + if (need_align) { + asm_thumb_op16(as, ASM_THUMB_OP_NOP); } + } else { + // do a large jump using: + // (nop) + // ldr r1, [pc, _data] + // add pc, r1 + // _data: .word rel + // + // note: can't use r0 as a temporary because native code can have the return value + // in that register and use a large jump to get to the exit point of the function + + rel -= 2; // account for the "ldr r1, [pc, _data]" + if (need_align) { + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + rel -= 2; // account for this nop + } + asm_thumb_ldr_rlo_pcrel_i8(as, ASM_THUMB_REG_R1, 0); + asm_thumb_add_reg_reg(as, ASM_THUMB_REG_R15, ASM_THUMB_REG_R1); + asm_thumb_op16(as, rel & 0xffff); + asm_thumb_op16(as, rel >> 16); } } } +// In Thumb1 mode, this may clobber r1. void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; @@ -551,8 +573,15 @@ void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); } else { // reverse the sense of the branch to jump over a longer branch - asm_thumb_op16(as, OP_BCC_N(cond ^ 1, 0)); + size_t code_offset_start = as->base.code_offset; + byte *c = asm_thumb_get_cur_to_write_bytes(as, 2); asm_thumb_b_label(as, label); + size_t bytes_to_skip = as->base.code_offset - code_offset_start; + uint16_t op = OP_BCC_N(cond ^ 1, bytes_to_skip - 4); + if (c != NULL) { + c[0] = op; + c[1] = op >> 8; + } } } From 718ff4fdd58efd3e853599b489640877070d17be Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 May 2025 14:27:35 +1000 Subject: [PATCH 0757/2098] tools/mpy_ld.py: Support R_ARM_ABS32 relocation in text. Add support for R_ARM_ABS32 relocations in native .mpy files. These can be rewritten in the same way that data relocations are. Fixes issue #14430. Signed-off-by: Damien George --- tools/mpy_ld.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 6518037f2ef..a600ec12c3d 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -711,8 +711,9 @@ def do_relocation_text(env, text_addr, r): (addr, value) = process_riscv32_relocation(env, text_addr, r) elif env.arch.name == "EM_ARM" and r_info_type == R_ARM_ABS32: - # happens for soft-float on armv6m - raise ValueError("Absolute relocations not supported on ARM") + # Absolute relocation, handled as a data relocation. + do_relocation_data(env, text_addr, r) + return else: # Unknown/unsupported relocation @@ -781,9 +782,9 @@ def do_relocation_data(env, text_addr, r): ): # Relocation in data.rel.ro to internal/external symbol if env.arch.word_size == 4: - struct_type = " Date: Wed, 28 May 2025 14:28:02 +1000 Subject: [PATCH 0758/2098] py/dynruntime.mk: Enable single-precision float by default on armv6/7m. Soft float now works on these ARM targets thanks to the parent commit. Signed-off-by: Damien George --- examples/natmod/random/Makefile | 5 +++++ py/dynruntime.mk | 4 ++-- tools/ci.sh | 8 +++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/natmod/random/Makefile b/examples/natmod/random/Makefile index 5d23eac1e9c..bffbb32d60e 100644 --- a/examples/natmod/random/Makefile +++ b/examples/natmod/random/Makefile @@ -14,4 +14,9 @@ ifeq ($(ARCH),xtensa) MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld endif +ifeq ($(ARCH),$(filter $(ARCH),armv6m armv7m)) +# Link with libm.a for soft-float helper functions +LINK_RUNTIME = 1 +endif + include $(MPY_DIR)/py/dynruntime.mk diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 84c78d6225c..030728cfc9a 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -63,14 +63,14 @@ else ifeq ($(ARCH),armv6m) # thumb CROSS = arm-none-eabi- CFLAGS_ARCH += -mthumb -mcpu=cortex-m0 -MICROPY_FLOAT_IMPL ?= none +MICROPY_FLOAT_IMPL ?= float else ifeq ($(ARCH),armv7m) # thumb CROSS = arm-none-eabi- CFLAGS_ARCH += -mthumb -mcpu=cortex-m3 -MICROPY_FLOAT_IMPL ?= none +MICROPY_FLOAT_IMPL ?= float else ifeq ($(ARCH),armv7emsp) diff --git a/tools/ci.sh b/tools/ci.sh index aafd6cc573b..a5cd326c1c7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -561,13 +561,11 @@ function ci_native_mpy_modules_build { make -C examples/natmod/$natmod ARCH=$arch done - # features2 requires soft-float on armv7m, rv32imc, and xtensa. On armv6m - # the compiler generates absolute relocations in the object file - # referencing soft-float functions, which is not supported at the moment. + # features2 requires soft-float on rv32imc and xtensa. make -C examples/natmod/features2 ARCH=$arch clean - if [ $arch = "rv32imc" ] || [ $arch = "armv7m" ] || [ $arch = "xtensa" ]; then + if [ $arch = "rv32imc" ] || [ $arch = "xtensa" ]; then make -C examples/natmod/features2 ARCH=$arch MICROPY_FLOAT_IMPL=float - elif [ $arch != "armv6m" ]; then + else make -C examples/natmod/features2 ARCH=$arch fi From 745bec9ce3cc4c95d8e80fcc8f64ffecde1d91bc Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 1 Jun 2025 18:47:36 +0200 Subject: [PATCH 0759/2098] extmod/modre: Use specific error message if regex is too complex. If the error reporting mode is at least "normal", report a failure due to a complex regex with a different message. Fixes issue #17150. Signed-off-by: Jeff Epler --- extmod/modre.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extmod/modre.c b/extmod/modre.c index 1a118009cbd..d17ec68d50e 100644 --- a/extmod/modre.c +++ b/extmod/modre.c @@ -427,6 +427,9 @@ static mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { const char *re_str = mp_obj_str_get_str(args[0]); int size = re1_5_sizecode(re_str); if (size == -1) { + #if MICROPY_ERROR_REPORTING >= MICROPY_ERROR_REPORTING_NORMAL + mp_raise_ValueError(MP_ERROR_TEXT("regex too complex")); + #endif goto error; } mp_obj_re_t *o = mp_obj_malloc_var(mp_obj_re_t, re.insts, char, size, (mp_obj_type_t *)&re_type); From 5eb97552591c6d2681b3e452b4b7d8445354b138 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 30 May 2025 07:54:08 +0200 Subject: [PATCH 0760/2098] py/parsenum: Reduce code size in check for inf/nan. By avoiding two different checks of the string length, code size is reduced without changing behavior: Some invalid float/complex strings like "ix" will get handled just like "xx" in the main number literal parsing code instead. The optimizer alone couldn't remove the reundant comparisons because it couldn't make a transformation that let an invalid string like "ix" pass into the generic number parsing code. Signed-off-by: Jeff Epler --- py/parsenum.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index a38ce563f95..e5c08b02883 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -252,9 +252,9 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex const char *str_val_start = str; // determine what the string is - if (str < top && (str[0] | 0x20) == 'i') { + if (str + 2 < top && (str[0] | 0x20) == 'i') { // string starts with 'i', should be 'inf' or 'infinity' (case insensitive) - if (str + 2 < top && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'f') { + if ((str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'f') { // inf str += 3; dec_val = (mp_float_t)INFINITY; @@ -263,9 +263,9 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex str += 5; } } - } else if (str < top && (str[0] | 0x20) == 'n') { + } else if (str + 2 < top && (str[0] | 0x20) == 'n') { // string starts with 'n', should be 'nan' (case insensitive) - if (str + 2 < top && (str[1] | 0x20) == 'a' && (str[2] | 0x20) == 'n') { + if ((str[1] | 0x20) == 'a' && (str[2] | 0x20) == 'n') { // NaN str += 3; dec_val = MICROPY_FLOAT_C_FUN(nan)(""); From c1629dc2ff12a92d600b5988827566f8d693cad1 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 30 May 2025 07:56:52 +0200 Subject: [PATCH 0761/2098] py/parsenum: Further reduce code size in check for inf/nan. A few more bytes can be saved by not using nested `if`s (4 bytes for `build-MICROBIT/py/parsenum.o`, 8 bytes for RPI_PICO firmware). This commit is better viewed with whitespace changes hidden, because two blocks were reindented (e.g., `git show -b`). Signed-off-by: Jeff Epler --- py/parsenum.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index e5c08b02883..300b5f28de8 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -252,24 +252,18 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex const char *str_val_start = str; // determine what the string is - if (str + 2 < top && (str[0] | 0x20) == 'i') { - // string starts with 'i', should be 'inf' or 'infinity' (case insensitive) - if ((str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'f') { - // inf - str += 3; - dec_val = (mp_float_t)INFINITY; - if (str + 4 < top && (str[0] | 0x20) == 'i' && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'i' && (str[3] | 0x20) == 't' && (str[4] | 0x20) == 'y') { - // infinity - str += 5; - } - } - } else if (str + 2 < top && (str[0] | 0x20) == 'n') { - // string starts with 'n', should be 'nan' (case insensitive) - if ((str[1] | 0x20) == 'a' && (str[2] | 0x20) == 'n') { - // NaN - str += 3; - dec_val = MICROPY_FLOAT_C_FUN(nan)(""); + if (str + 2 < top && (str[0] | 0x20) == 'i' && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'f') { + // 'inf' or 'infinity' (case insensitive) + str += 3; + dec_val = (mp_float_t)INFINITY; + if (str + 4 < top && (str[0] | 0x20) == 'i' && (str[1] | 0x20) == 'n' && (str[2] | 0x20) == 'i' && (str[3] | 0x20) == 't' && (str[4] | 0x20) == 'y') { + // infinity + str += 5; } + } else if (str + 2 < top && (str[0] | 0x20) == 'n' && (str[1] | 0x20) == 'a' && (str[2] | 0x20) == 'n') { + // 'nan' (case insensitive) + str += 3; + dec_val = MICROPY_FLOAT_C_FUN(nan)(""); } else { // string should be a decimal number parse_dec_in_t in = PARSE_DEC_IN_INTG; From 0a98f3a91130d127c937d6865ead24b6693182eb Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 1 Jun 2025 19:19:51 +0200 Subject: [PATCH 0762/2098] py/objarray: Allow extending array with any iterable. As suggested by @dpgeorge, factor out part of array_construct to allow it to be used for construction & extension. Note that extending with a known-length list (or tuple) goes through the slow path of calling array_extend once per element. Fixes issue #7408. Signed-off-by: Jeff Epler --- py/objarray.c | 31 ++++++++++++++++++------------- tests/basics/array_add.py | 6 ++++++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/py/objarray.c b/py/objarray.c index 9d68aa70616..1fd02693904 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -113,6 +113,19 @@ static mp_obj_array_t *array_new(char typecode, size_t n) { #endif #if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +static void array_extend_impl(mp_obj_array_t *array, mp_obj_t arg, char typecode, size_t len) { + mp_obj_t iterable = mp_getiter(arg, NULL); + mp_obj_t item; + size_t i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (len == 0) { + array_append(MP_OBJ_FROM_PTR(array), item); + } else { + mp_binary_set_val_array(typecode, array->items, i++, item); + } + } +} + static mp_obj_t array_construct(char typecode, mp_obj_t initializer) { // bytearrays can be raw-initialised from anything with the buffer protocol // other arrays can only be raw-initialised from bytes and bytearray objects @@ -142,18 +155,7 @@ static mp_obj_t array_construct(char typecode, mp_obj_t initializer) { } mp_obj_array_t *array = array_new(typecode, len); - - mp_obj_t iterable = mp_getiter(initializer, NULL); - mp_obj_t item; - size_t i = 0; - while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - if (len == 0) { - array_append(MP_OBJ_FROM_PTR(array), item); - } else { - mp_binary_set_val_array(typecode, array->items, i++, item); - } - } - + array_extend_impl(array, initializer, typecode, len); return MP_OBJ_FROM_PTR(array); } #endif @@ -413,7 +415,10 @@ static mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) { // allow to extend by anything that has the buffer protocol (extension to CPython) mp_buffer_info_t arg_bufinfo; - mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ); + if (!mp_get_buffer(arg_in, &arg_bufinfo, MP_BUFFER_READ)) { + array_extend_impl(self, arg_in, 0, 0); + return mp_const_none; + } size_t sz = mp_binary_get_size('@', self->typecode, NULL); diff --git a/tests/basics/array_add.py b/tests/basics/array_add.py index 76ce59f761e..e78615541c3 100644 --- a/tests/basics/array_add.py +++ b/tests/basics/array_add.py @@ -14,3 +14,9 @@ a1.extend(array.array('I', [5])) print(a1) + +a1.extend([6, 7]) +print(a1) + +a1.extend(i for i in (8, 9)) +print(a1) From 2ce63b142068fc04aae701ae78af683736dc179c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 28 May 2025 20:49:29 +0200 Subject: [PATCH 0763/2098] py/parsenum: Fix parsing complex literals with negative real part. If a complex literal had a negative real part and a positive imaginary part, it was not parsed properly because the imaginary part also came out negative. Includes a test of complex parsing, which fails without this fix. Co-authored-by: ComplexSymbol <141301057+ComplexSymbol@users.noreply.github.com> Signed-off-by: Jeff Epler --- py/parsenum.c | 4 ++-- tests/float/complex1.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index 300b5f28de8..7e6695fbfcd 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -227,13 +227,13 @@ mp_obj_t mp_parse_num_float(const char *str, size_t len, bool allow_imag, mp_lex const char *top = str + len; mp_float_t dec_val = 0; - bool dec_neg = false; #if MICROPY_PY_BUILTINS_COMPLEX unsigned int real_imag_state = REAL_IMAG_STATE_START; mp_float_t dec_real = 0; -parse_start: +parse_start:; #endif + bool dec_neg = false; // skip leading space for (; str < top && unichar_isspace(*str); str++) { diff --git a/tests/float/complex1.py b/tests/float/complex1.py index f4107a1390f..0a1d98b9af3 100644 --- a/tests/float/complex1.py +++ b/tests/float/complex1.py @@ -12,9 +12,11 @@ print(complex("1+j")) print(complex("1+2j")) print(complex("-1-2j")) +print(complex("-1+2j")) print(complex("+1-2j")) print(complex(" -1-2j ")) print(complex(" +1-2j ")) +print(complex(" -1+2j ")) print(complex("nanj")) print(complex("nan-infj")) print(complex(1, 2)) From ca80aabf21eac1d97f108ad7ed86ff3426f88449 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Jun 2025 22:15:37 +1000 Subject: [PATCH 0764/2098] py/objfloat: Change MSVC workaround for NAN being a constant. It's actually a bug in the Windows SDK, not MSVC, as per https://stackoverflow.com/questions/79195142/recent-msvc-versions-dont-treat-nan-as-constant-workaround/79324199#79324199 Thanks to @stinos. Signed-off-by: Damien George --- py/objfloat.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/py/objfloat.c b/py/objfloat.c index 0728fce3151..81b0daa6209 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -34,6 +34,11 @@ #if MICROPY_PY_BUILTINS_FLOAT +// Workaround a bug in Windows SDK version 10.0.26100.0, where NAN is no longer constant. +#if defined(_MSC_VER) && !defined(_UCRT_NOISY_NAN) +#define _UCRT_NOISY_NAN +#endif + #include #include "py/formatfloat.h" @@ -47,13 +52,6 @@ #define M_PI (3.14159265358979323846) #endif -// Workaround a bug in recent MSVC where NAN is no longer constant. -// (By redefining back to the previous MSVC definition of NAN) -#if defined(_MSC_VER) && _MSC_VER >= 1942 -#undef NAN -#define NAN (-(float)(((float)(1e+300 * 1e+300)) * 0.0F)) -#endif - typedef struct _mp_obj_float_t { mp_obj_base_t base; mp_float_t value; From 9bde12597a6980ff87ff0137a2616e6e430a1a0e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Jun 2025 13:46:29 +1000 Subject: [PATCH 0765/2098] github/workflows: Use windows-latest runner for all Windows CI jobs. The windows-2019 runner has been deprecated by GitHub, so stop using that. Also take the chance to stop using windows-2022 and just use windows-latest everywhere. Signed-off-by: Damien George --- .github/workflows/ports_windows.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 84e018ba15d..f33277d471d 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -28,13 +28,10 @@ jobs: visualstudio: ['2017', '2019', '2022'] include: - visualstudio: '2017' - runner: windows-latest vs_version: '[15, 16)' - visualstudio: '2019' - runner: windows-2019 vs_version: '[16, 17)' - visualstudio: '2022' - runner: windows-2022 vs_version: '[17, 18)' # trim down the number of jobs in the matrix exclude: @@ -42,9 +39,9 @@ jobs: configuration: Debug - visualstudio: '2019' configuration: Debug + runs-on: windows-latest env: CI_BUILD_CONFIGURATION: ${{ matrix.configuration }} - runs-on: ${{ matrix.runner }} steps: - name: Install Visual Studio 2017 if: matrix.visualstudio == '2017' @@ -52,13 +49,15 @@ jobs: choco install visualstudio2017buildtools choco install visualstudio2017-workload-vctools choco install windows-sdk-8.1 + - name: Install Visual Studio 2019 + if: matrix.visualstudio == '2019' + run: | + choco install visualstudio2019buildtools + choco install visualstudio2019-workload-vctools + choco install windows-sdk-8.1 - uses: microsoft/setup-msbuild@v2 with: vs-version: ${{ matrix.vs_version }} - - uses: actions/setup-python@v5 - if: matrix.runner == 'windows-2019' - with: - python-version: '3.9' - uses: actions/checkout@v4 - name: Build mpy-cross.exe run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} @@ -103,7 +102,7 @@ jobs: env: i686 - sys: mingw64 env: x86_64 - runs-on: windows-2022 + runs-on: windows-latest env: CHERE_INVOKING: enabled_from_arguments defaults: From e4d556b14931d0c5245cda585341f377f9186298 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 May 2025 16:06:36 +1000 Subject: [PATCH 0766/2098] tests/extmod/random_extra_float.py: Skip when funcs not available. This test was factored out from `random_extra.py` back in commit 6572029dc0665e58c2ea7355c9e541bdf83105a4, and the skip logic copied from that file. But the skip logic needs to test that the `random` and `uniform` functions exist, not `randint`. This commit fixes that skip logic. Signed-off-by: Damien George --- tests/extmod/random_extra_float.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/extmod/random_extra_float.py b/tests/extmod/random_extra_float.py index 3b37ed8dcef..03973c58349 100644 --- a/tests/extmod/random_extra_float.py +++ b/tests/extmod/random_extra_float.py @@ -1,12 +1,8 @@ try: import random -except ImportError: - print("SKIP") - raise SystemExit -try: - random.randint -except AttributeError: + random.random +except (ImportError, AttributeError): print("SKIP") raise SystemExit From 2bc5af694593c3654d0e7b59ad18b6f4a442072a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2025 00:09:32 +1000 Subject: [PATCH 0767/2098] tests/run-tests.py: Factor out helper function to create test report. This commit factors existing code in `run-tests.py` into a new helper function `create_test_report()`. That function prints out a summary of the test run (eg number of tests passed, number failed, number skipped) and creates the corresponding `_results.json` file. This is done so `create_test_report()` can be reused by the other test runners. The `test_count` counter is now gone, and instead the number of passed plus number of failed tests is used as an equivalent count. For consistency this commit makes a minor change to the printed output of `run-tests.py`: instead of printing a shorthand name for tests that failed or skipped, it now prints the full name. Eg what was previously printed as `attrtuple2` is now printed as `basics/attrtuple2.py`. This makes the output a little longer (when there are failed/skipped tests) but helps to disambiguate the test name, eg which directory it's in. Signed-off-by: Damien George --- tests/run-tests.py | 50 ++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index da05e18e4f1..5eebc72460a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -616,7 +616,6 @@ def run_script_on_remote_target(self, args, test_file, is_special): def run_tests(pyb, tests, args, result_dir, num_threads=1): - test_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter() test_results = ThreadSafeCounter([]) @@ -903,7 +902,7 @@ def run_one_test(test_file): if skip_it: print("skip ", test_file) - test_results.append((test_name, test_file, "skip", "")) + test_results.append((test_file, "skip", "")) return # Run the test on the MicroPython target. @@ -918,11 +917,11 @@ def run_one_test(test_file): # start-up code (eg boot.py) when preparing to run the next test. pyb.read_until(1, b"raw REPL; CTRL-B to exit\r\n") print("skip ", test_file) - test_results.append((test_name, test_file, "skip", "")) + test_results.append((test_file, "skip", "")) return elif output_mupy == b"SKIP-TOO-LARGE\n": print("lrge ", test_file) - test_results.append((test_name, test_file, "skip", "too large")) + test_results.append((test_file, "skip", "too large")) return # Look at the output of the test to see if unittest was used. @@ -1005,7 +1004,7 @@ def run_one_test(test_file): # Print test summary, update counters, and save .exp/.out files if needed. if test_passed: print("pass ", test_file, extra_info) - test_results.append((test_name, test_file, "pass", "")) + test_results.append((test_file, "pass", "")) rm_f(filename_expected) rm_f(filename_mupy) else: @@ -1017,9 +1016,7 @@ def run_one_test(test_file): rm_f(filename_expected) # in case left over from previous failed run with open(filename_mupy, "wb") as f: f.write(output_mupy) - test_results.append((test_name, test_file, "fail", "")) - - test_count.increment() + test_results.append((test_file, "fail", "")) # Print a note if this looks like it might have been a misfired unittest if not uses_unittest and not test_passed: @@ -1046,19 +1043,27 @@ def run_one_test(test_file): print(line) sys.exit(1) - test_results = test_results.value - passed_tests = list(r for r in test_results if r[2] == "pass") - skipped_tests = list(r for r in test_results if r[2] == "skip" and r[3] != "too large") + # Return test results. + return test_results.value, testcase_count.value + + +# Print a summary of the results and save them to a JSON file. +# Returns True if everything succeeded, False otherwise. +def create_test_report(args, test_results, testcase_count=None): + passed_tests = list(r for r in test_results if r[1] == "pass") + skipped_tests = list(r for r in test_results if r[1] == "skip" and r[2] != "too large") skipped_tests_too_large = list( - r for r in test_results if r[2] == "skip" and r[3] == "too large" + r for r in test_results if r[1] == "skip" and r[2] == "too large" ) - failed_tests = list(r for r in test_results if r[2] == "fail") + failed_tests = list(r for r in test_results if r[1] == "fail") + + num_tests_performed = len(passed_tests) + len(failed_tests) + + testcase_count_info = "" + if testcase_count is not None: + testcase_count_info = " ({} individual testcases)".format(testcase_count) + print("{} tests performed{}".format(num_tests_performed, testcase_count_info)) - print( - "{} tests performed ({} individual testcases)".format( - test_count.value, testcase_count.value - ) - ) print("{} tests passed".format(len(passed_tests))) if len(skipped_tests) > 0: @@ -1088,15 +1093,15 @@ def to_json(obj): return obj.pattern return obj - with open(os.path.join(result_dir, RESULTS_FILE), "w") as f: + with open(os.path.join(args.result_dir, RESULTS_FILE), "w") as f: json.dump( { # The arguments passed on the command-line. "args": vars(args), # A list of all results of the form [(test, result, reason), ...]. - "results": list(test[1:] for test in test_results), + "results": list(test for test in test_results), # A list of failed tests. This is deprecated, use the "results" above instead. - "failed_tests": [test[1] for test in failed_tests], + "failed_tests": [test[0] for test in failed_tests], }, f, default=to_json, @@ -1350,7 +1355,8 @@ def main(): try: os.makedirs(args.result_dir, exist_ok=True) - res = run_tests(pyb, tests, args, args.result_dir, args.jobs) + test_results, testcase_count = run_tests(pyb, tests, args, args.result_dir, args.jobs) + res = create_test_report(args, test_results, testcase_count) finally: if pyb: pyb.close() From 6db9c808567dfb921bc9dfbc19100a8daba867a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Jun 2025 22:13:33 +1000 Subject: [PATCH 0768/2098] tests/run-multitests.py: Create a _result.json at end of run. Reuse the `create_test_report()` function from `run-tests.py` to generate a `_result.json` file summarising the test run. If there's more than one permutation of the test run, only the last result is saved. Signed-off-by: Damien George --- tests/run-multitests.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 387eec7018b..92bd64193d8 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -15,6 +15,8 @@ import subprocess import tempfile +run_tests_module = __import__("run-tests") + test_dir = os.path.abspath(os.path.dirname(__file__)) if os.path.abspath(sys.path[0]) == test_dir: @@ -488,9 +490,7 @@ def print_diff(a, b): def run_tests(test_files, instances_truth, instances_test): - skipped_tests = [] - passed_tests = [] - failed_tests = [] + test_results = [] for test_file, num_instances in test_files: instances_str = "|".join(str(instances_test[i]) for i in range(num_instances)) @@ -526,13 +526,13 @@ def run_tests(test_files, instances_truth, instances_test): # Print result of test if skip: print("skip") - skipped_tests.append(test_file) + test_results.append((test_file, "skip", "")) elif output_test == output_truth: print("pass") - passed_tests.append(test_file) + test_results.append((test_file, "pass", "")) else: print("FAIL") - failed_tests.append(test_file) + test_results.append((test_file, "fail", "")) if not cmd_args.show_output: print("### TEST ###") print(output_test, end="") @@ -549,15 +549,7 @@ def run_tests(test_files, instances_truth, instances_test): if cmd_args.show_output: print() - print("{} tests performed".format(len(skipped_tests) + len(passed_tests) + len(failed_tests))) - print("{} tests passed".format(len(passed_tests))) - - if skipped_tests: - print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) - if failed_tests: - print("{} tests failed: {}".format(len(failed_tests), " ".join(failed_tests))) - - return not failed_tests + return test_results def main(): @@ -583,6 +575,12 @@ def main(): default=1, help="repeat the test with this many permutations of the instance order", ) + cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) cmd_parser.epilog = ( "Supported instance types:\r\n" " -i pyb: physical device (eg. pyboard) on provided repl port.\n" @@ -623,13 +621,15 @@ def main(): for _ in range(max_instances - len(instances_test)): instances_test.append(PyInstanceSubProcess([MICROPYTHON])) + os.makedirs(cmd_args.result_dir, exist_ok=True) all_pass = True try: for i, instances_test_permutation in enumerate(itertools.permutations(instances_test)): if i >= cmd_args.permutations: break - all_pass &= run_tests(test_files, instances_truth, instances_test_permutation) + test_results = run_tests(test_files, instances_truth, instances_test_permutation) + all_pass &= run_tests_module.create_test_report(cmd_args, test_results) finally: for i in instances_truth: From 09b058559b09a76fa81feeaacc3e4b22c4021e8f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2025 00:10:54 +1000 Subject: [PATCH 0769/2098] tests/run-natmodtests.py: Create a _result.json at end of run. Reuse the `create_test_report()` function from `run-tests.py` to generate a `_result.json` file summarising the test run. Signed-off-by: Damien George --- tests/run-natmodtests.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index 073e0b053e6..b689a07a5b2 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -9,6 +9,8 @@ import sys import argparse +run_tests_module = __import__("run-tests") + sys.path.append("../tools") import pyboard @@ -133,7 +135,7 @@ def detect_architecture(target): return platform, arch, None -def run_tests(target_truth, target, args, stats, resolved_arch): +def run_tests(target_truth, target, args, resolved_arch): global injected_import_hook_code prelude = "" @@ -141,6 +143,7 @@ def run_tests(target_truth, target, args, stats, resolved_arch): prelude = args.begin.read() injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prelude) + test_results = [] for test_file in args.files: # Find supported test test_file_basename = os.path.basename(test_file) @@ -195,17 +198,18 @@ def run_tests(target_truth, target, args, stats, resolved_arch): result = "pass" # Accumulate statistics - stats["total"] += 1 if result == "pass": - stats["pass"] += 1 + test_results.append((test_file, "pass", "")) elif result == "SKIP": - stats["skip"] += 1 + test_results.append((test_file, "skip", "")) else: - stats["fail"] += 1 + test_results.append((test_file, "fail", "")) # Print result print("{:4} {}{}".format(result, test_file, extra)) + return test_results + def main(): cmd_parser = argparse.ArgumentParser( @@ -227,6 +231,12 @@ def main(): default=None, help="prologue python file to execute before module import", ) + cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() @@ -251,20 +261,14 @@ def main(): print("platform={} ".format(target_platform), end="") print("arch={}".format(target_arch)) - stats = {"total": 0, "pass": 0, "fail": 0, "skip": 0} - run_tests(target_truth, target, args, stats, target_arch) + os.makedirs(args.result_dir, exist_ok=True) + test_results = run_tests(target_truth, target, args, target_arch) + res = run_tests_module.create_test_report(args, test_results) target.close() target_truth.close() - print("{} tests performed".format(stats["total"])) - print("{} tests passed".format(stats["pass"])) - if stats["fail"]: - print("{} tests failed".format(stats["fail"])) - if stats["skip"]: - print("{} tests skipped".format(stats["skip"])) - - if stats["fail"]: + if not res: sys.exit(1) From 63701c2e949861e2b07913d72b0f1490ca079005 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2025 00:11:14 +1000 Subject: [PATCH 0770/2098] tests/run-perfbench.py: Create a _result.json at end of run. Reuse the `create_test_report()` function from `run-tests.py` to generate a `_result.json` file summarising the test run. Signed-off-by: Damien George --- tests/run-perfbench.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 81d873c4599..cac2fee58f0 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -10,10 +10,12 @@ import argparse from glob import glob +run_tests_module = __import__("run-tests") + sys.path.append("../tools") import pyboard -prepare_script_for_target = __import__("run-tests").prepare_script_for_target +prepare_script_for_target = run_tests_module.prepare_script_for_target # Paths for host executables if os.name == "nt": @@ -90,9 +92,9 @@ def run_benchmark_on_target(target, script): def run_benchmarks(args, target, param_n, param_m, n_average, test_list): + test_results = [] skip_complex = run_feature_test(target, "complex") != "complex" skip_native = run_feature_test(target, "native_check") != "native" - target_had_error = False for test_file in sorted(test_list): print(test_file + ": ", end="") @@ -105,6 +107,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): and test_file.find("viper_") != -1 ) if skip: + test_results.append((test_file, "skip", "")) print("SKIP") continue @@ -125,6 +128,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): if isinstance(target, pyboard.Pyboard) or args.via_mpy: crash, test_script_target = prepare_script_for_target(args, script_text=test_script) if crash: + test_results.append((test_file, "fail", "preparation")) print("CRASH:", test_script_target) continue else: @@ -162,10 +166,13 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): error = "FAIL truth" if error is not None: - if not error.startswith("SKIP"): - target_had_error = True + if error.startswith("SKIP"): + test_results.append((test_file, "skip", error)) + else: + test_results.append((test_file, "fail", error)) print(error) else: + test_results.append((test_file, "pass", "")) t_avg, t_sd = compute_stats(times) s_avg, s_sd = compute_stats(scores) print( @@ -179,7 +186,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): sys.stdout.flush() - return target_had_error + return test_results def parse_output(filename): @@ -264,6 +271,12 @@ def main(): cmd_parser.add_argument("--heapsize", help="heapsize to use (use default if not specified)") cmd_parser.add_argument("--via-mpy", action="store_true", help="compile code to .mpy first") cmd_parser.add_argument("--mpy-cross-flags", default="", help="flags to pass to mpy-cross") + cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) cmd_parser.add_argument( "N", nargs=1, help="N parameter (approximate target CPU frequency in MHz)" ) @@ -307,13 +320,15 @@ def main(): print("N={} M={} n_average={}".format(N, M, n_average)) - target_had_error = run_benchmarks(args, target, N, M, n_average, tests) + os.makedirs(args.result_dir, exist_ok=True) + test_results = run_benchmarks(args, target, N, M, n_average, tests) + res = run_tests_module.create_test_report(args, test_results) if isinstance(target, pyboard.Pyboard): target.exit_raw_repl() target.close() - if target_had_error: + if not res: sys.exit(1) From 5676b45cbb8644f7b04e2a0495c760d905007983 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2025 19:47:03 +1000 Subject: [PATCH 0771/2098] tests/run-natmodtests.py: Consider a test skipped if mpy doesn't exist. This is different to a test not being run because there is no corresponding natmod at all. Signed-off-by: Damien George --- tests/run-natmodtests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index b689a07a5b2..f9d2074f6f0 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -166,7 +166,8 @@ def run_tests(target_truth, target, args, resolved_arch): with open(NATMOD_EXAMPLE_DIR + test_mpy, "rb") as f: test_script += b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" except OSError: - print("---- {} - mpy file not compiled".format(test_file)) + test_results.append((test_file, "skip", "mpy file not compiled")) + print("skip {} - mpy file not compiled".format(test_file)) continue test_script += bytes(injected_import_hook_code.format(test_module), "ascii") test_script += test_file_data From ef21ade602116421db2734d23320401ac6669773 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 23 May 2025 12:56:12 +1000 Subject: [PATCH 0772/2098] extmod/machine_pulse: Optimise time_pulse_us for code size. This implementation is based on the esp8266 custom implementation, and further optimised for size and accuracy. Testing on PYBD_SF2 and RPI_PICO2_W shows that it is at least as good as the original implementation in performance. Signed-off-by: Damien George --- extmod/machine_pulse.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 85dba86d9b5..1ec4d5f0831 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -31,19 +31,34 @@ #if MICROPY_PY_MACHINE_PULSE MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { + mp_uint_t nchanges = 2; mp_uint_t start = mp_hal_ticks_us(); - while (mp_hal_pin_read(pin) != pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { - return (mp_uint_t)-2; - } - } - start = mp_hal_ticks_us(); - while (mp_hal_pin_read(pin) == pulse_level) { - if ((mp_uint_t)(mp_hal_ticks_us() - start) >= timeout_us) { - return (mp_uint_t)-1; + for (;;) { + // Sample ticks and pin as close together as possible, and always in the same + // order each time around the loop. This gives the most accurate measurement. + mp_uint_t t = mp_hal_ticks_us(); + int pin_value = mp_hal_pin_read(pin); + + if (pin_value == pulse_level) { + // Pin is at desired value. Flip desired value and see if we are done. + pulse_level = 1 - pulse_level; + if (--nchanges == 0) { + return t - start; + } + start = t; + } else { + // Pin hasn't changed yet, check for timeout. + mp_uint_t dt = t - start; + if (dt >= timeout_us) { + return -nchanges; + } + + // Allow a port to perform background task processing if needed. + #ifdef MICROPY_PY_MACHINE_TIME_PULSE_US_HOOK + MICROPY_PY_MACHINE_TIME_PULSE_US_HOOK(dt); + #endif } } - return mp_hal_ticks_us() - start; } static mp_obj_t machine_time_pulse_us_(size_t n_args, const mp_obj_t *args) { From 398da22492d9e593f87f0b9ae4fc043f8b2b8a11 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 23 May 2025 12:57:20 +1000 Subject: [PATCH 0773/2098] esp8266/modmachine: Use common machine_time_pulse_us implementation. Testing shows that for frequencies which the esp8266 can handle -- up to about 1kHz -- `machine.time_pulse_us()` now gives more accurate results. Prior to this commit it would measure on average about 1us lower, but now the average is much closer to the true value. For example a pulse that is 1000us long, it would measure between 998 and 1000us. Now it measures between 999us and 1001us. Signed-off-by: Damien George --- extmod/machine_pulse.c | 2 +- ports/esp8266/esp_mphal.h | 9 +++++++++ ports/esp8266/modmachine.c | 30 ------------------------------ 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/extmod/machine_pulse.c b/extmod/machine_pulse.c index 1ec4d5f0831..b78a63f1827 100644 --- a/extmod/machine_pulse.c +++ b/extmod/machine_pulse.c @@ -30,7 +30,7 @@ #if MICROPY_PY_MACHINE_PULSE -MP_WEAK mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { mp_uint_t nchanges = 2; mp_uint_t start = mp_hal_ticks_us(); for (;;) { diff --git a/ports/esp8266/esp_mphal.h b/ports/esp8266/esp_mphal.h index 0a4f92ac0bf..fb2e441c972 100644 --- a/ports/esp8266/esp_mphal.h +++ b/ports/esp8266/esp_mphal.h @@ -27,11 +27,20 @@ #include "user_interface.h" #include "py/ringbuf.h" #include "shared/runtime/interrupt_char.h" +#include "ets_alt_task.h" #include "xtirq.h" #define MICROPY_BEGIN_ATOMIC_SECTION() esp_disable_irq() #define MICROPY_END_ATOMIC_SECTION(state) esp_enable_irq(state) +// During machine.time_pulse_us, feed WDT every now and then. +#define MICROPY_PY_MACHINE_TIME_PULSE_US_HOOK(dt) \ + do { \ + if ((dt & 0xffff) == 0xffff && !ets_loop_dont_feed_sw_wdt) { \ + system_soft_wdt_feed(); \ + } \ + } while (0) + void mp_sched_keyboard_interrupt(void); struct _mp_print_t; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 6ac17da4576..d43fe382457 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -33,7 +33,6 @@ #include "os_type.h" #include "osapi.h" #include "etshal.h" -#include "ets_alt_task.h" #include "user_interface.h" // #define MACHINE_WAKE_IDLE (0x01) @@ -327,32 +326,3 @@ MP_DEFINE_CONST_OBJ_TYPE( print, esp_timer_print, locals_dict, &esp_timer_locals_dict ); - -// Custom version of this function that feeds system WDT if necessary -mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { - int nchanges = 2; - uint32_t start = system_get_time(); // in microseconds - for (;;) { - uint32_t dt = system_get_time() - start; - - // Check if pin changed to wanted value - if (mp_hal_pin_read(pin) == pulse_level) { - if (--nchanges == 0) { - return dt; - } - pulse_level = 1 - pulse_level; - start = system_get_time(); - continue; - } - - // Check for timeout - if (dt >= timeout_us) { - return (mp_uint_t)-nchanges; - } - - // Only feed WDT every now and then, to make sure edge timing is accurate - if ((dt & 0xffff) == 0xffff && !ets_loop_dont_feed_sw_wdt) { - system_soft_wdt_feed(); - } - } -} From 2a0e2b578265bac43e158bccb195ca88ffea64c4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 May 2025 10:33:21 +1000 Subject: [PATCH 0774/2098] esp32/main: Auto detect the size of flash and auto create vfs partition. Currently in the esp32 port the size of the SPI flash must be configured at build time, eg 4MiB, 8MiB, etc. Also, the esp32 partition table must be configured at build time, which depends on the size of the SPI flash. A bigger flash means more can be allocated to the user filesystem. This commit makes it so the SPI flash size is automatically determined at runtime, and the filesystem size is automatically set to take up as much room as possible (a "vfs" partition is created automatically if it doesn't exist). This works by: - Setting the SPI flash size to be 4MiB in the build (or some other value, as long as the firmware app fits). - Removing the vfs partition from the esp32 partition table (only nvs, phy_init and firmware, and maybe romfs, remain in the partition table). - At boot, query the physical size of the SPI flash and use that as the actual size in the code. - If it doesn't already exist, automatically create a "vfs" partition which takes up the flash from the end of all existing partitions to the end of flash. This allows simplifying a lot of board configurations, and removing some board variants that just change the flash size (to be done in a following commit). It's also fully backwards compatible, in the following sense: - Existing boards with MicroPython firmware will continue to work with the same filesystem, ie the filesystem won't be erased when the firmware is updated. - If a user has a custom esp32 partition table and installs MicroPython as a bare app into the app partition, the new MicroPython firmware will honour the esp32 partition table and use either "vfs" or "ffat" partitions as the filesystem. Signed-off-by: Damien George --- ports/esp32/main.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index b8f49a33baa..f048aa85f5f 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -38,6 +38,7 @@ #include "nvs_flash.h" #include "esp_task.h" #include "esp_event.h" +#include "esp_flash.h" #include "esp_log.h" #include "esp_memory_utils.h" #include "esp_psram.h" @@ -214,11 +215,38 @@ void boardctrl_startup(void) { nvs_flash_erase(); nvs_flash_init(); } + + // Query the physical size of the SPI flash and store it in the size + // variable of the global, default SPI flash handle. + esp_flash_get_physical_size(NULL, &esp_flash_default_chip->size); + + // If there is no filesystem partition (no "vfs" or "ffat"), add a "vfs" partition + // that extends from the end of the application partition up to the end of flash. + if (esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "vfs") == NULL + && esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "ffat") == NULL) { + // No "vfs" or "ffat" partition, so try to create one. + + // Find the end of the last partition that exists in the partition table. + size_t offset = 0; + esp_partition_iterator_t iter = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + while (iter != NULL) { + const esp_partition_t *part = esp_partition_get(iter); + offset = MAX(offset, part->address + part->size); + iter = esp_partition_next(iter); + } + + // If we found the application partition and there is some space between the end of + // that and the end of flash, create a "vfs" partition taking up all of that space. + if (offset > 0 && esp_flash_default_chip->size > offset) { + size_t size = esp_flash_default_chip->size - offset; + esp_partition_register_external(esp_flash_default_chip, offset, size, "vfs", ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + } + } } void MICROPY_ESP_IDF_ENTRY(void) { // Hook for a board to run code at start up. - // This defaults to initialising NVS. + // This defaults to initialising NVS and detecting the flash size. MICROPY_BOARD_STARTUP(); // Create and transfer control to the MicroPython task. From 6201e77999b3614518abc4b21773e735d9b0b0ee Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 May 2025 14:18:02 +1000 Subject: [PATCH 0775/2098] esp32/boards: Convert all boards to auto detect flash size. Remove the "vfs" entry from all partitions-*.csv files, and then remove duplicated files. And remove the ESP32_GENERIC_S3-FLASH_4M variant, because it's no longer needed. Signed-off-by: Damien George --- ports/esp32/boards/ESP32_GENERIC/board.md | 2 +- ports/esp32/boards/ESP32_GENERIC_C3/board.md | 4 ++-- ports/esp32/boards/ESP32_GENERIC_S2/board.md | 2 +- ports/esp32/boards/ESP32_GENERIC_S3/board.json | 3 +-- ports/esp32/boards/ESP32_GENERIC_S3/board.md | 4 +--- .../ESP32_GENERIC_S3/mpconfigvariant_FLASH_4M.cmake | 4 ---- ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.board | 6 ------ ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.flash_4m | 4 ---- ports/esp32/boards/SIL_WESP32/sdkconfig.board | 9 +++------ ports/esp32/boards/UM_FEATHERS2/sdkconfig.board | 5 ----- ports/esp32/boards/UM_FEATHERS3/sdkconfig.board | 5 ----- ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board | 5 ----- ports/esp32/boards/UM_NANOS3/sdkconfig.board | 5 ----- ports/esp32/boards/UM_OMGS3/sdkconfig.board | 5 ----- ports/esp32/boards/UM_PROS3/sdkconfig.board | 5 ----- ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board | 5 ----- ports/esp32/boards/UM_TINYC6/sdkconfig.board | 5 ----- ports/esp32/boards/UM_TINYS3/sdkconfig.board | 5 ----- ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board | 5 ----- ports/esp32/boards/sdkconfig.base | 2 +- ports/esp32/partitions-16MiB-ota.csv | 10 ---------- ports/esp32/partitions-16MiB.csv | 7 ------- ports/esp32/partitions-2MiB.csv | 1 - ports/esp32/partitions-32MiB.csv | 7 ------- ports/esp32/partitions-4MiB-ota.csv | 1 - ports/esp32/partitions-4MiB-romfs.csv | 1 - .../{partitions-4MiB.csv => partitions-4MiBplus.csv} | 5 ++++- ports/esp32/partitions-8MiB.csv | 7 ------- ...tions-32MiB-ota.csv => partitions-8MiBplus-ota.csv} | 6 ++++-- 29 files changed, 18 insertions(+), 117 deletions(-) delete mode 100644 ports/esp32/boards/ESP32_GENERIC_S3/mpconfigvariant_FLASH_4M.cmake delete mode 100644 ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.flash_4m delete mode 100644 ports/esp32/partitions-16MiB-ota.csv delete mode 100644 ports/esp32/partitions-16MiB.csv delete mode 100644 ports/esp32/partitions-32MiB.csv rename ports/esp32/{partitions-4MiB.csv => partitions-4MiBplus.csv} (60%) delete mode 100644 ports/esp32/partitions-8MiB.csv rename ports/esp32/{partitions-32MiB-ota.csv => partitions-8MiBplus-ota.csv} (65%) diff --git a/ports/esp32/boards/ESP32_GENERIC/board.md b/ports/esp32/boards/ESP32_GENERIC/board.md index 9346d18d84a..06972e436f2 100644 --- a/ports/esp32/boards/ESP32_GENERIC/board.md +++ b/ports/esp32/boards/ESP32_GENERIC/board.md @@ -1,5 +1,5 @@ The following files are firmware that should work on most ESP32-based boards -with 4MiB of flash, including WROOM WROVER, SOLO, PICO, and MINI modules. +with 4MiB or more of flash, including WROOM WROVER, SOLO, PICO, and MINI modules. This board has multiple variants available: diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/board.md b/ports/esp32/boards/ESP32_GENERIC_C3/board.md index 1604b879c59..6097eb85df7 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/board.md +++ b/ports/esp32/boards/ESP32_GENERIC_C3/board.md @@ -1,6 +1,6 @@ The following files are firmware images that should work on most -ESP32-C3-based boards with 4MiB of flash, including WROOM and MINI modules, -that use the revision 3 silicon (or newer). +ESP32-C3-based boards with 4MiB or more of flash, including WROOM and MINI +modules, that use the revision 3 silicon (or newer). USB serial/JTAG support is enabled on pin 18 and 19. Note that this is not a full USB stack, the C3 just provides a CDC/ACM class serial diff --git a/ports/esp32/boards/ESP32_GENERIC_S2/board.md b/ports/esp32/boards/ESP32_GENERIC_S2/board.md index 3e46c02467a..d065ecbbb99 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S2/board.md +++ b/ports/esp32/boards/ESP32_GENERIC_S2/board.md @@ -1,5 +1,5 @@ The following files are firmware that should work on most ESP32-S2-based -boards with 4MiB of flash, including WROOM, WROVER, and MINI modules. +boards with 4MiB or more of flash, including WROOM, WROVER, and MINI modules. This firmware supports configurations with and without SPIRAM (also known as PSRAM) and will auto-detect a connected SPIRAM chip at startup and allocate diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/board.json b/ports/esp32/boards/ESP32_GENERIC_S3/board.json index fd0c9edce09..7a546d35fcd 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_S3/board.json @@ -21,7 +21,6 @@ "url": "https://www.espressif.com/en/products/modules", "vendor": "Espressif", "variants": { - "SPIRAM_OCT": "Support for Octal-SPIRAM", - "FLASH_4M": "4MiB flash" + "SPIRAM_OCT": "Support for Octal-SPIRAM" } } diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/board.md b/ports/esp32/boards/ESP32_GENERIC_S3/board.md index 16930c9193c..68ce0917e04 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/board.md +++ b/ports/esp32/boards/ESP32_GENERIC_S3/board.md @@ -1,9 +1,7 @@ The following files are firmware that should work on most ESP32-S3-based -boards with 4/8MiB of flash, including WROOM and MINI modules. +boards with 4MiB or more of flash, including WROOM and MINI modules. This firmware supports configurations with and without SPIRAM (also known as PSRAM) and will auto-detect a connected SPIRAM chip at startup and allocate the MicroPython heap accordingly. However if your board has Octal SPIRAM, then use the "spiram-oct" variant. - -If your board has 4MiB flash (including ESP32-S3FH4R2 based ones with embedded flash), then use the "flash-4m" build. diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigvariant_FLASH_4M.cmake b/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigvariant_FLASH_4M.cmake deleted file mode 100644 index e832cdb60fd..00000000000 --- a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigvariant_FLASH_4M.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(SDKCONFIG_DEFAULTS - ${SDKCONFIG_DEFAULTS} - boards/ESP32_GENERIC_S3/sdkconfig.flash_4m -) diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.board b/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.board index 9839b0d3005..369330682f9 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.board +++ b/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.board @@ -1,8 +1,2 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y - -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.flash_4m b/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.flash_4m deleted file mode 100644 index c967c46ae6b..00000000000 --- a/ports/esp32/boards/ESP32_GENERIC_S3/sdkconfig.flash_4m +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= - -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4MiB.csv" diff --git a/ports/esp32/boards/SIL_WESP32/sdkconfig.board b/ports/esp32/boards/SIL_WESP32/sdkconfig.board index 741a4db4390..36155cd5e31 100644 --- a/ports/esp32/boards/SIL_WESP32/sdkconfig.board +++ b/ports/esp32/boards/SIL_WESP32/sdkconfig.board @@ -1,9 +1,6 @@ -# 16 MB flash +# 8 MB+ flash -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y -CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y # Fast flash @@ -14,7 +11,7 @@ CONFIG_ESP32_REV_MIN_1=y # OTA CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB-ota.csv" +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiBplus-ota.csv" # Network name diff --git a/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board index 9ab58f215f2..162de0da14a 100644 --- a/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board +++ b/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board @@ -3,11 +3,6 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB.csv" - # LWIP CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS2" # end of LWIP diff --git a/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board index 3ca0c4b243e..3092e355982 100644 --- a/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board +++ b/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board @@ -1,12 +1,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS3" diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board index d0355a94dcc..25b8d7689d8 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board +++ b/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board @@ -3,11 +3,6 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_AFTER_NORESET=y CONFIG_SPIRAM_MEMTEST= -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS3Neo" diff --git a/ports/esp32/boards/UM_NANOS3/sdkconfig.board b/ports/esp32/boards/UM_NANOS3/sdkconfig.board index 2a39c64337b..e06f7a4245f 100644 --- a/ports/esp32/boards/UM_NANOS3/sdkconfig.board +++ b/ports/esp32/boards/UM_NANOS3/sdkconfig.board @@ -1,12 +1,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMNanoS3" diff --git a/ports/esp32/boards/UM_OMGS3/sdkconfig.board b/ports/esp32/boards/UM_OMGS3/sdkconfig.board index 84a8ce449ef..8a0bf0b13af 100644 --- a/ports/esp32/boards/UM_OMGS3/sdkconfig.board +++ b/ports/esp32/boards/UM_OMGS3/sdkconfig.board @@ -2,12 +2,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_AFTER_NORESET=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMOMGS3" diff --git a/ports/esp32/boards/UM_PROS3/sdkconfig.board b/ports/esp32/boards/UM_PROS3/sdkconfig.board index 5752e03e600..75ca58d80b0 100644 --- a/ports/esp32/boards/UM_PROS3/sdkconfig.board +++ b/ports/esp32/boards/UM_PROS3/sdkconfig.board @@ -1,12 +1,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMProS3" diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board b/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board index ef3f38af47e..7d244fdc163 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board @@ -2,12 +2,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_AFTER_NORESET=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMRGBTouchMini" diff --git a/ports/esp32/boards/UM_TINYC6/sdkconfig.board b/ports/esp32/boards/UM_TINYC6/sdkconfig.board index 7917467b12e..213d28d8b4a 100644 --- a/ports/esp32/boards/UM_TINYC6/sdkconfig.board +++ b/ports/esp32/boards/UM_TINYC6/sdkconfig.board @@ -2,9 +2,4 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_AFTER_NORESET=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" diff --git a/ports/esp32/boards/UM_TINYS3/sdkconfig.board b/ports/esp32/boards/UM_TINYS3/sdkconfig.board index d1d19761a06..2474c5fb279 100644 --- a/ports/esp32/boards/UM_TINYS3/sdkconfig.board +++ b/ports/esp32/boards/UM_TINYS3/sdkconfig.board @@ -1,12 +1,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMTinyS3" diff --git a/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board b/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board index 1380e15ce84..10121d235d0 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board +++ b/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board @@ -1,12 +1,7 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y -CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_SPIRAM_MEMTEST= -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" CONFIG_LWIP_LOCAL_HOSTNAME="UMTinyWATCHS3" diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 823b916d960..30740af434d 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -97,7 +97,7 @@ CONFIG_ULP_COPROC_RESERVE_MEM=2040 # For cmake build CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4MiB.csv" +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4MiBplus.csv" # To reduce iRAM usage CONFIG_ESP32_WIFI_IRAM_OPT=n diff --git a/ports/esp32/partitions-16MiB-ota.csv b/ports/esp32/partitions-16MiB-ota.csv deleted file mode 100644 index a6f83bc46b8..00000000000 --- a/ports/esp32/partitions-16MiB-ota.csv +++ /dev/null @@ -1,10 +0,0 @@ -# Partition table for MicroPython with OTA support using 16MB flash -# Notes: the offset of the partition table itself is set in -# $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -ota_0, app, ota_0, 0x10000, 0x270000, -ota_1, app, ota_1, 0x280000, 0x270000, -vfs, data, fat, 0x4f0000, 0xb10000, diff --git a/ports/esp32/partitions-16MiB.csv b/ports/esp32/partitions-16MiB.csv deleted file mode 100644 index ae926c7b94e..00000000000 --- a/ports/esp32/partitions-16MiB.csv +++ /dev/null @@ -1,7 +0,0 @@ -# Notes: the offset of the partition table itself is set in -# $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x6000, -phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x1F0000, -vfs, data, fat, 0x200000, 0xE00000, diff --git a/ports/esp32/partitions-2MiB.csv b/ports/esp32/partitions-2MiB.csv index ea6626825c0..5449201a7a2 100644 --- a/ports/esp32/partitions-2MiB.csv +++ b/ports/esp32/partitions-2MiB.csv @@ -4,4 +4,3 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x160000, -vfs, data, fat, 0x170000, 0x50000, diff --git a/ports/esp32/partitions-32MiB.csv b/ports/esp32/partitions-32MiB.csv deleted file mode 100644 index 31591c99495..00000000000 --- a/ports/esp32/partitions-32MiB.csv +++ /dev/null @@ -1,7 +0,0 @@ -# Notes: the offset of the partition table itself is set in -# $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x6000, -phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x1F0000, -vfs, data, fat, 0x200000, 0x1E00000, diff --git a/ports/esp32/partitions-4MiB-ota.csv b/ports/esp32/partitions-4MiB-ota.csv index 094ad76660d..9cbb4227998 100644 --- a/ports/esp32/partitions-4MiB-ota.csv +++ b/ports/esp32/partitions-4MiB-ota.csv @@ -7,4 +7,3 @@ otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, ota_0, app, ota_0, 0x10000, 0x180000, ota_1, app, ota_1, 0x190000, 0x180000, -vfs, data, fat, 0x310000, 0x0f0000, diff --git a/ports/esp32/partitions-4MiB-romfs.csv b/ports/esp32/partitions-4MiB-romfs.csv index dd02506e546..29033ff3ab7 100644 --- a/ports/esp32/partitions-4MiB-romfs.csv +++ b/ports/esp32/partitions-4MiB-romfs.csv @@ -5,4 +5,3 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x1D0000, romfs, data, 0x8f, 0x1E0000, 0x20000, -vfs, data, fat, 0x200000, 0x200000, diff --git a/ports/esp32/partitions-4MiB.csv b/ports/esp32/partitions-4MiBplus.csv similarity index 60% rename from ports/esp32/partitions-4MiB.csv rename to ports/esp32/partitions-4MiBplus.csv index 53f0f167442..460e5cc0e97 100644 --- a/ports/esp32/partitions-4MiB.csv +++ b/ports/esp32/partitions-4MiBplus.csv @@ -1,7 +1,10 @@ +# This partition table is for devices with 4MiB or more of flash. +# The first 2MiB is used for bootloader, nvs, phy_init and firmware. +# The remaining flash is for the user filesystem(s). + # Notes: the offset of the partition table itself is set in # $IDF_PATH/components/partition_table/Kconfig.projbuild. # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x1F0000, -vfs, data, fat, 0x200000, 0x200000, diff --git a/ports/esp32/partitions-8MiB.csv b/ports/esp32/partitions-8MiB.csv deleted file mode 100644 index 582d3b50e54..00000000000 --- a/ports/esp32/partitions-8MiB.csv +++ /dev/null @@ -1,7 +0,0 @@ -# Notes: the offset of the partition table itself is set in -# $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x6000, -phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x1F0000, -vfs, data, fat, 0x200000, 0x600000, diff --git a/ports/esp32/partitions-32MiB-ota.csv b/ports/esp32/partitions-8MiBplus-ota.csv similarity index 65% rename from ports/esp32/partitions-32MiB-ota.csv rename to ports/esp32/partitions-8MiBplus-ota.csv index 7366a2ad8df..09a8e6d702d 100644 --- a/ports/esp32/partitions-32MiB-ota.csv +++ b/ports/esp32/partitions-8MiBplus-ota.csv @@ -1,4 +1,7 @@ -# Partition table for MicroPython with OTA support using 32MB flash +# This partition table is for devices with 8MiB or more of flash and OTA support. +# The first 5056kiB is used for bootloader, nvs, phy_init and firmware. +# The remaining flash is for the user filesystem(s). + # Notes: the offset of the partition table itself is set in # $IDF_PATH/components/partition_table/Kconfig.projbuild. # Name, Type, SubType, Offset, Size, Flags @@ -7,4 +10,3 @@ otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, ota_0, app, ota_0, 0x10000, 0x270000, ota_1, app, ota_1, 0x280000, 0x270000, -vfs, data, fat, 0x4f0000, 0x1B10000, From fa393feaed16eb040c3fa7f57537eca1404f6f70 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 12 Jun 2025 10:52:32 +1000 Subject: [PATCH 0776/2098] esp32/README: Update README to describe auto filesystem sizing. Signed-off-by: Damien George --- ports/esp32/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index e11c64ad707..d8b55e45f3b 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -221,6 +221,30 @@ import machine antenna = machine.Pin(16, machine.Pin.OUT, value=0) ``` +Partition table and filesystem size +----------------------------------- + +ESP32 firmware contains a bootloader, partition table and main application +firmware, which are all stored in (external) SPI flash. The user filesystem +is also stored in the same SPI flash. By default, MicroPython does not have +a fixed entry in the ESP32 partition table for the filesystem. Instead it +will automatically determine the size of the SPI flash upon boot, and then -- +so long as there is no existing partition called "vfs" or "ffat" -- it will +create a partition for the filesystem called "vfs" which takes all of the +remaining flash between the end of the last defined partition up until the +end of flash. + +This means that firmware built for, say, a 4MiB flash will work on any +device that has at least 4MiB flash. The user "vfs" filesystem will then +take up as much space as possible. + +This auto-detection behaviour can be overridden: if the ESP32 partition +table does contain an entry called "vfs" or "ffat" then these are used for +the user filesystem and no automatic "vfs" partition is added. This is +useful in cases where only the MicroPython ESP32 application is flashed to +the device (and not the bootloader or partition table), eg when deploying +.uf2 files. + Defining a custom ESP32 board ----------------------------- From 42404b5588eb12c22e07283f2e62ee9bb80f1714 Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Thu, 29 May 2025 07:43:54 +0300 Subject: [PATCH 0777/2098] esp32/modesp32: Make wake_on_ulp available only on SoCs that support it. The `esp32.wake_on_ulp()` method should only be available on boards that have SOC_ULP_SUPPORTED=y. Update docs to reflect this. Signed-off-by: Meir Armon --- docs/library/esp32.rst | 2 ++ ports/esp32/machine_rtc.h | 2 ++ ports/esp32/modesp32.c | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 24831c58d6d..aeba3d60335 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -23,6 +23,8 @@ Functions Configure whether or not the Ultra-Low-Power co-processor can wake the device from sleep. *wake* should be a boolean value. + .. note:: This is only available for boards that have ULP coprocessor support. + .. function:: wake_on_ext0(pin, level) Configure how EXT0 wakes the device from sleep. *pin* can be ``None`` diff --git a/ports/esp32/machine_rtc.h b/ports/esp32/machine_rtc.h index ce2a5482a22..f327b9a2ac2 100644 --- a/ports/esp32/machine_rtc.h +++ b/ports/esp32/machine_rtc.h @@ -34,7 +34,9 @@ typedef struct { uint64_t ext1_pins; // set bit == pin# int8_t ext0_pin; // just the pin#, -1 == None bool wake_on_touch : 1; + #if SOC_ULP_SUPPORTED bool wake_on_ulp : 1; + #endif bool ext0_level : 1; wake_type_t ext0_wake_types; bool ext1_level : 1; diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 4572e7b68b7..f51c15322c6 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -126,6 +126,7 @@ static mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_m } static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1); +#if SOC_ULP_SUPPORTED static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { if (machine_rtc_config.ext0_pin != -1) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); @@ -134,6 +135,7 @@ static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); +#endif #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP static mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { @@ -260,7 +262,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_wake_on_touch), MP_ROM_PTR(&esp32_wake_on_touch_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, + #if SOC_ULP_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, + #endif #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, #endif From 0ef5ede3823ef00e95d1cc1a5b475d8d30c9caa4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 11 Jun 2025 19:32:28 +0200 Subject: [PATCH 0778/2098] py/mpz: Avoid undefined behavior decrementing NULL. In the case where an mpz number is zero, its `len` is 0 and its `dig` is NULL. In that case, decrementing NULL via `d--` is undefined behavior according to the C specification. Restructuring the loops in this way avoids undefined behavior. Also, ensure that these cases are tested in the coverage test. This doesn't make much difference now, but would otherwise cause errors later when the undefined behavior sanitizer is employed in CI. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 12 ++++++++++++ py/mpz.c | 12 ++++++++---- tests/ports/unix/extra_coverage.py.exp | 4 ++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 29e1457cb71..9201dab12d2 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -475,6 +475,18 @@ static mp_obj_t extra_coverage(void) { mp_int_t value_signed; mpz_as_int_checked(&mpz, &value_signed); mp_printf(&mp_plat_print, "%d\n", (int)value_signed); + + // hash the zero mpz integer + mpz_set_from_int(&mpz, 0); + mp_printf(&mp_plat_print, "%d\n", mpz_hash(&mpz)); + + // convert the mpz zero integer to int + mp_printf(&mp_plat_print, "%d\n", mpz_as_int_checked(&mpz, &value_signed)); + mp_printf(&mp_plat_print, "%d\n", value_signed); + + // mpz_set_from_float with 0 as argument + mpz_set_from_float(&mpz, 0); + mp_printf(&mp_plat_print, "%f\n", mpz_as_float(&mpz)); } // runtime utils diff --git a/py/mpz.c b/py/mpz.c index 471bd159818..5a4d7d27d94 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -1537,7 +1537,8 @@ mp_int_t mpz_hash(const mpz_t *z) { mp_uint_t val = 0; mpz_dig_t *d = z->dig + z->len; - while (d-- > z->dig) { + while (d > z->dig) { + d--; val = (val << DIG_SIZE) | *d; } @@ -1552,11 +1553,12 @@ bool mpz_as_int_checked(const mpz_t *i, mp_int_t *value) { mp_uint_t val = 0; mpz_dig_t *d = i->dig + i->len; - while (d-- > i->dig) { + while (d > i->dig) { if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> DIG_SIZE)) { // will overflow return false; } + d--; val = (val << DIG_SIZE) | *d; } @@ -1577,11 +1579,12 @@ bool mpz_as_uint_checked(const mpz_t *i, mp_uint_t *value) { mp_uint_t val = 0; mpz_dig_t *d = i->dig + i->len; - while (d-- > i->dig) { + while (d > i->dig) { if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> (DIG_SIZE - 1))) { // will overflow return false; } + d--; val = (val << DIG_SIZE) | *d; } @@ -1642,7 +1645,8 @@ mp_float_t mpz_as_float(const mpz_t *i) { mp_float_t val = 0; mpz_dig_t *d = i->dig + i->len; - while (d-- > i->dig) { + while (d > i->dig) { + d--; val = val * DIG_BASE + *d; } diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 19b32f1c9d9..1549168228e 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -89,6 +89,10 @@ data 12345 6 -1 +0 +1 +0 +0.000000 # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' From 73b1cbc17abe6da6f913d30dad960e5401577c0f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 12 Jun 2025 18:47:12 +0200 Subject: [PATCH 0779/2098] py/objlist: Reduce code size in slice operations. By refactoring the code to separate out the slicing operation from the regular indexing operation, code can be shared between the various types of slice operations (read/assign/delete). Signed-off-by: Jeff Epler --- py/objlist.c | 101 ++++++++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/py/objlist.c b/py/objlist.c index 9a88de3892e..71414a849f4 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -159,76 +159,63 @@ static mp_obj_t list_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { } static mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { - if (value == MP_OBJ_NULL) { - // delete - #if MICROPY_PY_BUILTINS_SLICE - if (mp_obj_is_type(index, &mp_type_slice)) { - mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); - mp_bound_slice_t slice; - if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice)) { - mp_raise_NotImplementedError(NULL); + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + mp_bound_slice_t slice; + bool fast = mp_seq_get_fast_slice_indexes(self->len, index, &slice); + if (value == MP_OBJ_SENTINEL) { + // load + if (!fast) { + return mp_seq_extract_slice(self->items, &slice); } - - mp_int_t len_adj = slice.start - slice.stop; - assert(len_adj <= 0); - mp_seq_replace_slice_no_grow(self->items, self->len, slice.start, slice.stop, self->items /*NULL*/, 0, sizeof(*self->items)); + mp_obj_list_t *res = list_new(slice.stop - slice.start); + mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); + return MP_OBJ_FROM_PTR(res); + } + // assign/delete + if (value == MP_OBJ_NULL) { + // delete is equivalent to slice assignment of an empty sequence + value = mp_const_empty_tuple; + } + if (!fast) { + mp_raise_NotImplementedError(NULL); + } + size_t value_len; + mp_obj_t *value_items; + mp_obj_get_array(value, &value_len, &value_items); + mp_int_t len_adj = value_len - (slice.stop - slice.start); + if (len_adj > 0) { + if (self->len + len_adj > self->alloc) { + // TODO: Might optimize memory copies here by checking if block can + // be grown inplace or not + self->items = m_renew(mp_obj_t, self->items, self->alloc, self->len + len_adj); + self->alloc = self->len + len_adj; + } + mp_seq_replace_slice_grow_inplace(self->items, self->len, + slice.start, slice.stop, value_items, value_len, len_adj, sizeof(*self->items)); + } else { + mp_seq_replace_slice_no_grow(self->items, self->len, + slice.start, slice.stop, value_items, value_len, sizeof(*self->items)); // Clear "freed" elements at the end of list mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); - self->len += len_adj; - return mp_const_none; + // TODO: apply allocation policy re: alloc_size } - #endif + self->len += len_adj; + return mp_const_none; + } + #endif + if (value == MP_OBJ_NULL) { + // delete mp_obj_t args[2] = {self_in, index}; list_pop(2, args); return mp_const_none; } else if (value == MP_OBJ_SENTINEL) { // load mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); - #if MICROPY_PY_BUILTINS_SLICE - if (mp_obj_is_type(index, &mp_type_slice)) { - mp_bound_slice_t slice; - if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice)) { - return mp_seq_extract_slice(self->items, &slice); - } - mp_obj_list_t *res = list_new(slice.stop - slice.start); - mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); - return MP_OBJ_FROM_PTR(res); - } - #endif size_t index_val = mp_get_index(self->base.type, self->len, index, false); return self->items[index_val]; } else { - #if MICROPY_PY_BUILTINS_SLICE - if (mp_obj_is_type(index, &mp_type_slice)) { - mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); - size_t value_len; - mp_obj_t *value_items; - mp_obj_get_array(value, &value_len, &value_items); - mp_bound_slice_t slice_out; - if (!mp_seq_get_fast_slice_indexes(self->len, index, &slice_out)) { - mp_raise_NotImplementedError(NULL); - } - mp_int_t len_adj = value_len - (slice_out.stop - slice_out.start); - if (len_adj > 0) { - if (self->len + len_adj > self->alloc) { - // TODO: Might optimize memory copies here by checking if block can - // be grown inplace or not - self->items = m_renew(mp_obj_t, self->items, self->alloc, self->len + len_adj); - self->alloc = self->len + len_adj; - } - mp_seq_replace_slice_grow_inplace(self->items, self->len, - slice_out.start, slice_out.stop, value_items, value_len, len_adj, sizeof(*self->items)); - } else { - mp_seq_replace_slice_no_grow(self->items, self->len, - slice_out.start, slice_out.stop, value_items, value_len, sizeof(*self->items)); - // Clear "freed" elements at the end of list - mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); - // TODO: apply allocation policy re: alloc_size - } - self->len += len_adj; - return mp_const_none; - } - #endif mp_obj_list_store(self_in, index, value); return mp_const_none; } From 1a8f4b290bc19cc3981f6deb2cb10a40a7b3bbea Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 13 Jun 2025 17:52:29 +0200 Subject: [PATCH 0780/2098] py/mpprint: Remove unused "PF_FLAG_NO_TRAILZ" flag. Looking at the git history, there's no indication that the `PF_FLAG_NO_TRAILZ` flag was ever implemented or that "%!" was used as an `mp_printf` format string in practice. So remove the flag and re-number the other flags. Leave `PF_FLAG_SEP_POS` at 9 (the highest position that probably works with 16-bit integers like the pic16bit port). Signed-off-by: Jeff Epler --- py/mpprint.c | 2 -- py/mpprint.h | 11 +++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/py/mpprint.c b/py/mpprint.c index 00a5f944c69..86dbfad05e3 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -413,8 +413,6 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { flags |= PF_FLAG_SHOW_SIGN; } else if (*fmt == ' ') { flags |= PF_FLAG_SPACE_SIGN; - } else if (*fmt == '!') { - flags |= PF_FLAG_NO_TRAILZ; } else if (*fmt == '0') { flags |= PF_FLAG_PAD_AFTER_SIGN; fill = '0'; diff --git a/py/mpprint.h b/py/mpprint.h index 511af329baf..583f00bda80 100644 --- a/py/mpprint.h +++ b/py/mpprint.h @@ -31,12 +31,11 @@ #define PF_FLAG_LEFT_ADJUST (0x001) #define PF_FLAG_SHOW_SIGN (0x002) #define PF_FLAG_SPACE_SIGN (0x004) -#define PF_FLAG_NO_TRAILZ (0x008) -#define PF_FLAG_SHOW_PREFIX (0x010) -#define PF_FLAG_PAD_AFTER_SIGN (0x020) -#define PF_FLAG_CENTER_ADJUST (0x040) -#define PF_FLAG_ADD_PERCENT (0x080) -#define PF_FLAG_SHOW_OCTAL_LETTER (0x100) +#define PF_FLAG_SHOW_PREFIX (0x008) +#define PF_FLAG_PAD_AFTER_SIGN (0x010) +#define PF_FLAG_CENTER_ADJUST (0x020) +#define PF_FLAG_ADD_PERCENT (0x040) +#define PF_FLAG_SHOW_OCTAL_LETTER (0x080) #define PF_FLAG_SEP_POS (9) // must be above all the above PF_FLAGs #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES From fc6205c4f005dcc9d0560cfe1619f022db8ff12f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 13 Jun 2025 17:50:05 +0200 Subject: [PATCH 0781/2098] unix/coverage: Add coverage test for left adjusted print. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 1 + tests/ports/unix/extra_coverage.py.exp | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 9201dab12d2..b041141f0f1 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -220,6 +220,7 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%X\n", 0x80000000); // should print unsigned mp_printf(&mp_plat_print, "abc\n%"); // string ends in middle of format specifier mp_printf(&mp_plat_print, "%%\n"); // literal % character + mp_printf(&mp_plat_print, ".%-3s.\n", "a"); // left adjust } // GC diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 1549168228e..3775036bb37 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -14,6 +14,7 @@ false true 80000000 abc % +.a . # GC 0 0 From 5ff2ae5a6c955a5123082ba441ad57fe4908b690 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 15 Jun 2025 08:08:55 +0200 Subject: [PATCH 0782/2098] py/asmbase: Fix assertion error with viper code. In the case of viper code it's possible to reach MP_ASM_PASS_EMIT with a code size of 0 bytes. Update the assertion accordingly. After this change, `mpy-cross -march=debug' on viper tests no longer crashes. Fixes issue #17467. Signed-off-by: Jeff Epler --- py/asmbase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/asmbase.c b/py/asmbase.c index 3fce543a7f4..f1b823fa368 100644 --- a/py/asmbase.c +++ b/py/asmbase.c @@ -53,7 +53,7 @@ void mp_asm_base_start_pass(mp_asm_base_t *as, int pass) { } else { // allocating executable RAM is platform specific MP_PLAT_ALLOC_EXEC(as->code_offset, (void **)&as->code_base, &as->code_size); - assert(as->code_base != NULL); + assert(as->code_size == 0 || as->code_base != NULL); } as->pass = pass; as->suppress = false; From cb315bb8e4d267eddb550ab71d17015eb6b5586c Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Thu, 29 May 2025 20:12:35 +0300 Subject: [PATCH 0783/2098] esp32/modesp32: Make wake_on_touch available only on SoCs supporting it. The `esp32.wake_on_touch()` method should only be available on boards that have SOC_TOUCH_SENSOR_SUPPORTED=y. And update docs to reflect this. Signed-off-by: Meir Armon --- docs/library/esp32.rst | 2 ++ ports/esp32/machine_pin.c | 2 ++ ports/esp32/machine_rtc.h | 2 ++ ports/esp32/modesp32.c | 7 +++++++ 4 files changed, 13 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index aeba3d60335..8a849adfcc1 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -18,6 +18,8 @@ Functions Configure whether or not a touch will wake the device from sleep. *wake* should be a boolean value. + .. note:: This is only available for boards that have touch sensor support. + .. function:: wake_on_ulp(wake) Configure whether or not the Ultra-Low-Power co-processor can wake the diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index d0c4ee1a7ec..5cc55beb930 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -325,9 +325,11 @@ static mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_raise_ValueError(MP_ERROR_TEXT("bad wake value")); } + #if SOC_TOUCH_SENSOR_SUPPORTED if (machine_rtc_config.wake_on_touch) { // not compatible mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } + #endif if (!RTC_IS_VALID_EXT_PIN(index)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for wake")); diff --git a/ports/esp32/machine_rtc.h b/ports/esp32/machine_rtc.h index f327b9a2ac2..566920f8500 100644 --- a/ports/esp32/machine_rtc.h +++ b/ports/esp32/machine_rtc.h @@ -33,7 +33,9 @@ typedef struct { uint64_t ext1_pins; // set bit == pin# int8_t ext0_pin; // just the pin#, -1 == None + #if SOC_TOUCH_SENSOR_SUPPORTED bool wake_on_touch : 1; + #endif #if SOC_ULP_SUPPORTED bool wake_on_ulp : 1; #endif diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index f51c15322c6..478cea68d3a 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -47,6 +47,7 @@ #include "../multi_heap_platform.h" #include "../heap_private.h" +#if SOC_TOUCH_SENSOR_SUPPORTED static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { if (machine_rtc_config.ext0_pin != -1) { @@ -57,12 +58,16 @@ static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_touch_obj, esp32_wake_on_touch); +#endif static mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + #if SOC_TOUCH_SENSOR_SUPPORTED if (machine_rtc_config.wake_on_touch) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } + #endif + enum {ARG_pin, ARG_level}; const mp_arg_t allowed_args[] = { { MP_QSTR_pin, MP_ARG_OBJ, {.u_obj = mp_obj_new_int(machine_rtc_config.ext0_pin)} }, @@ -259,7 +264,9 @@ static MP_DEFINE_CONST_FUN_OBJ_0(esp32_idf_task_info_obj, esp32_idf_task_info); static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp32) }, + #if SOC_TOUCH_SENSOR_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_touch), MP_ROM_PTR(&esp32_wake_on_touch_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, #if SOC_ULP_SUPPORTED From 4697a06fdb9d2639975e049463c2a3d776238aec Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Fri, 30 May 2025 14:01:06 +0300 Subject: [PATCH 0784/2098] esp32/modesp32: Make wake_on_ext0 available only on SoCs supporting it. The `esp32.wake_on_ext0()` method should only be available on boards that have SOC_PM_SUPPORT_EXT0_WAKEUP=y. And update docs to reflect this. Signed-off-by: Meir Armon --- docs/library/esp32.rst | 2 ++ ports/esp32/machine_pin.c | 4 ++++ ports/esp32/machine_rtc.c | 2 ++ ports/esp32/machine_rtc.h | 4 ++++ ports/esp32/modesp32.c | 4 ++++ 5 files changed, 16 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 8a849adfcc1..b4f423b5f48 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -33,6 +33,8 @@ Functions or a valid Pin object. *level* should be ``esp32.WAKEUP_ALL_LOW`` or ``esp32.WAKEUP_ANY_HIGH``. + .. note:: This is only available for boards that have ext0 support. + .. function:: wake_on_ext1(pins, level) Configure how EXT1 wakes the device from sleep. *pins* can be ``None`` diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 5cc55beb930..4ab79f0a264 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -335,6 +335,7 @@ static mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for wake")); } + #if SOC_PM_SUPPORT_EXT0_WAKEUP if (machine_rtc_config.ext0_pin == -1) { machine_rtc_config.ext0_pin = index; } else if (machine_rtc_config.ext0_pin != index) { @@ -343,10 +344,13 @@ static mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ machine_rtc_config.ext0_level = trigger == GPIO_INTR_LOW_LEVEL ? 0 : 1; machine_rtc_config.ext0_wake_types = wake; + #endif } else { + #if SOC_PM_SUPPORT_EXT0_WAKEUP if (machine_rtc_config.ext0_pin == index) { machine_rtc_config.ext0_pin = -1; } + #endif if (handler == mp_const_none) { handler = MP_OBJ_NULL; diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index a2f4bb132d4..48a2e2926c9 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -83,7 +83,9 @@ static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; machine_rtc_config_t machine_rtc_config = { .ext1_pins = 0, + #if SOC_PM_SUPPORT_EXT0_WAKEUP .ext0_pin = -1 + #endif }; 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) { diff --git a/ports/esp32/machine_rtc.h b/ports/esp32/machine_rtc.h index 566920f8500..6376f795819 100644 --- a/ports/esp32/machine_rtc.h +++ b/ports/esp32/machine_rtc.h @@ -32,15 +32,19 @@ typedef struct { uint64_t ext1_pins; // set bit == pin# + #if SOC_PM_SUPPORT_EXT0_WAKEUP int8_t ext0_pin; // just the pin#, -1 == None + #endif #if SOC_TOUCH_SENSOR_SUPPORTED bool wake_on_touch : 1; #endif #if SOC_ULP_SUPPORTED bool wake_on_ulp : 1; #endif + #if SOC_PM_SUPPORT_EXT0_WAKEUP bool ext0_level : 1; wake_type_t ext0_wake_types; + #endif bool ext1_level : 1; } machine_rtc_config_t; diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 478cea68d3a..4a42ebef98f 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -60,6 +60,7 @@ static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_touch_obj, esp32_wake_on_touch); #endif +#if SOC_PM_SUPPORT_EXT0_WAKEUP static mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { #if SOC_TOUCH_SENSOR_SUPPORTED @@ -94,6 +95,7 @@ static mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_m return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext0_obj, 0, esp32_wake_on_ext0); +#endif static mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_pins, ARG_level}; @@ -267,7 +269,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { #if SOC_TOUCH_SENSOR_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_touch), MP_ROM_PTR(&esp32_wake_on_touch_obj) }, #endif + #if SOC_PM_SUPPORT_EXT0_WAKEUP { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, #if SOC_ULP_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, From 171d751242ba7c9fd32b86852a1383bc23c6fda1 Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Fri, 30 May 2025 17:22:53 +0300 Subject: [PATCH 0785/2098] esp32/modesp32: Make wake_on_ext1 available only on SoCs supporting it. The `esp32.wake_on_ext1()` method should only be available on boards that have SOC_PM_SUPPORT_EXT1_WAKEUP=y. And update docs to reflect this. Signed-off-by: Meir Armon --- docs/library/esp32.rst | 2 ++ ports/esp32/machine_rtc.c | 2 ++ ports/esp32/machine_rtc.h | 4 ++++ ports/esp32/modesp32.c | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index b4f423b5f48..4be6dc2671c 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -41,6 +41,8 @@ Functions or a tuple/list of valid Pin objects. *level* should be ``esp32.WAKEUP_ALL_LOW`` or ``esp32.WAKEUP_ANY_HIGH``. + .. note:: This is only available for boards that have ext1 support. + .. function:: gpio_deep_sleep_hold(enable) Configure whether non-RTC GPIO pin configuration is retained during diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index 48a2e2926c9..ee6b1ad5cb5 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -82,7 +82,9 @@ _USER_MEM_ATTR uint8_t rtc_user_mem_data[MICROPY_HW_RTC_USER_MEM_MAX]; static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; machine_rtc_config_t machine_rtc_config = { + #if SOC_PM_SUPPORT_EXT1_WAKEUP .ext1_pins = 0, + #endif #if SOC_PM_SUPPORT_EXT0_WAKEUP .ext0_pin = -1 #endif diff --git a/ports/esp32/machine_rtc.h b/ports/esp32/machine_rtc.h index 6376f795819..72717c5936d 100644 --- a/ports/esp32/machine_rtc.h +++ b/ports/esp32/machine_rtc.h @@ -31,7 +31,9 @@ #include "modmachine.h" typedef struct { + #if SOC_PM_SUPPORT_EXT1_WAKEUP uint64_t ext1_pins; // set bit == pin# + #endif #if SOC_PM_SUPPORT_EXT0_WAKEUP int8_t ext0_pin; // just the pin#, -1 == None #endif @@ -45,7 +47,9 @@ typedef struct { bool ext0_level : 1; wake_type_t ext0_wake_types; #endif + #if SOC_PM_SUPPORT_EXT1_WAKEUP bool ext1_level : 1; + #endif } machine_rtc_config_t; extern machine_rtc_config_t machine_rtc_config; diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 4a42ebef98f..26a39d44511 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -97,6 +97,7 @@ static mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_m static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext0_obj, 0, esp32_wake_on_ext0); #endif +#if SOC_PM_SUPPORT_EXT1_WAKEUP static mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_pins, ARG_level}; const mp_arg_t allowed_args[] = { @@ -132,6 +133,7 @@ static mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_m return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1); +#endif #if SOC_ULP_SUPPORTED static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { @@ -272,7 +274,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { #if SOC_PM_SUPPORT_EXT0_WAKEUP { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, #endif + #if SOC_PM_SUPPORT_EXT1_WAKEUP { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, + #endif #if SOC_ULP_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, #endif From d9467b07cb036dfc0d54f36fd74356001881d154 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 13:15:40 +1000 Subject: [PATCH 0786/2098] tools/mpremote: Improve df command to use new no-arg vfs.mount() query. The existing `mpremote df` command is not very good, because it needs to assume that all directories in the root directory are mount points, and also doesn't correctly stat filesystems when the current directory is not the root. This leads to wrong results With the introduction of `vfs.mount()` to return a list of mounted filesystems and their path, a much better df can be implemented, as done in this commit. The new df will also fall back to using the old approach of listing the root directory if the no-arg `vfs.mount()` query is not supported. Signed-off-by: Damien George --- tools/mpremote/mpremote/main.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index f8c913d26d1..d122e93c0f9 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -379,7 +379,27 @@ def argparse_none(description): # Disk used/free. "df": [ "exec", - "import os\nprint('mount \\tsize \\tused \\tavail \\tuse%')\nfor _m in [''] + os.listdir('/'):\n _s = os.stat('/' + _m)\n if not _s[0] & 1 << 14: continue\n _s = os.statvfs(_m)\n if _s[0]:\n _size = _s[0] * _s[2]; _free = _s[0] * _s[3]; print(_m, _size, _size - _free, _free, int(100 * (_size - _free) / _size), sep='\\t')", + """ +import os,vfs +_f = "{:<10}{:>9}{:>9}{:>9}{:>5} {}" +print(_f.format("filesystem", "size", "used", "avail", "use%", "mounted on")) +try: + _ms = vfs.mount() +except: + _ms = [] + for _m in [""] + os.listdir("/"): + _m = "/" + _m + _s = os.stat(_m) + if _s[0] & 1 << 14: + _ms.append(("",_m)) +for _v,_p in _ms: + _s = os.statvfs(_p) + _sz = _s[0]*_s[2] + if _sz: + _av = _s[0]*_s[3] + _us = 100*(_sz-_av)//_sz + print(_f.format(str(_v), _sz, _sz-_av, _av, _us, _p)) +""", ], # Other shortcuts. "reset": { From 573180f7884112e2bc5bf279549b4941f7fab06b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 29 Sep 2024 21:23:29 +1000 Subject: [PATCH 0787/2098] renesas-ra: Replace MICROPY_EVENT_POLL_HOOK with mp_event_wait. Basic update to the renesas-ra port to replace the traditional `MICROPY_EVENT_POLL_HOOK` with the newer mp_event_wait API as appropriate. Signed-off-by: Andrew Leech --- ports/renesas-ra/machine_uart.c | 2 +- ports/renesas-ra/mpconfigport.h | 13 +------------ ports/renesas-ra/mphalport.c | 2 +- ports/renesas-ra/mphalport.h | 8 ++++++++ ports/renesas-ra/systick.c | 10 ++++++---- ports/renesas-ra/uart.c | 4 ++-- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/ports/renesas-ra/machine_uart.c b/ports/renesas-ra/machine_uart.c index b70978ad7a5..196e1ccd6dd 100644 --- a/ports/renesas-ra/machine_uart.c +++ b/ports/renesas-ra/machine_uart.c @@ -501,7 +501,7 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint if (!uart_tx_busy(self)) { return 0; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_indefinite(); } while (mp_hal_ticks_ms() < timeout); *errcode = MP_ETIMEDOUT; ret = MP_STREAM_ERROR; diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index c2b89f415ba..8a116269bb4 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -243,28 +243,17 @@ typedef unsigned int mp_uint_t; // must be pointer size typedef long mp_off_t; #if MICROPY_PY_THREAD -#define MICROPY_EVENT_POLL_HOOK \ +#define MICROPY_INTERNAL_EVENT_HOOK \ do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ if (pyb_thread_enabled) { \ MP_THREAD_GIL_EXIT(); \ pyb_thread_yield(); \ MP_THREAD_GIL_ENTER(); \ - } else { \ - __WFI(); \ } \ } while (0); #define MICROPY_THREAD_YIELD() pyb_thread_yield() #else -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ - __WFI(); \ - } while (0); - #define MICROPY_THREAD_YIELD() #endif diff --git a/ports/renesas-ra/mphalport.c b/ports/renesas-ra/mphalport.c index b2587af06de..455b28af195 100644 --- a/ports/renesas-ra/mphalport.c +++ b/ports/renesas-ra/mphalport.c @@ -104,7 +104,7 @@ int mp_hal_stdin_rx_chr(void) { return dupterm_c; } #endif - MICROPY_EVENT_POLL_HOOK + mp_event_wait_indefinite(); } } diff --git a/ports/renesas-ra/mphalport.h b/ports/renesas-ra/mphalport.h index 1dab67f9a76..4f8be705d1c 100644 --- a/ports/renesas-ra/mphalport.h +++ b/ports/renesas-ra/mphalport.h @@ -35,6 +35,14 @@ #define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV) #define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state) +// Port level Wait-for-Event macro +// +// Do not use this macro directly, include py/runtime.h and +// call mp_event_wait_indefinite() or mp_event_wait_ms(timeout). +// Uses WFI which will wake up from regular systick interrupt if not +// before from any other source. +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) __WFI() + #define MICROPY_PY_LWIP_ENTER #define MICROPY_PY_LWIP_REENTER #define MICROPY_PY_LWIP_EXIT diff --git a/ports/renesas-ra/systick.c b/ports/renesas-ra/systick.c index 6fa02a2b6ec..cb528d91d23 100644 --- a/ports/renesas-ra/systick.c +++ b/ports/renesas-ra/systick.c @@ -97,22 +97,24 @@ void HAL_Delay(uint32_t Delay) { // Core delay function that does an efficient sleep and may switch thread context. // If IRQs are enabled then we must have the GIL. -void mp_hal_delay_ms(mp_uint_t Delay) { +void mp_hal_delay_ms(mp_uint_t ms) { if (query_irq() == IRQ_STATE_ENABLED) { // IRQs enabled, so can use systick counter to do the delay uint32_t start = uwTick; + mp_uint_t elapsed = 0; // Wraparound of tick is taken care of by 2's complement arithmetic. do { // This macro will execute the necessary idle behaviour. It may // raise an exception, switch threads or enter sleep mode (waiting for // (at least) the SysTick interrupt). - MICROPY_EVENT_POLL_HOOK - } while (uwTick - start < Delay); + mp_event_wait_ms(ms - elapsed); + elapsed = uwTick - start; + } while (elapsed < ms); } else { // IRQs disabled, so need to use a busy loop for the delay. // To prevent possible overflow of the counter we use a double loop. volatile uint32_t count_1ms; - while (Delay-- > 0) { + while (ms-- > 0) { count_1ms = (MICROPY_HW_MCU_PCLK / 1000 / 10); while (count_1ms-- > 0) { __asm__ __volatile__ ("nop"); diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index 4800e0ea03f..91714bf05b3 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -477,7 +477,7 @@ bool uart_rx_wait(machine_uart_obj_t *self, uint32_t timeout) { if (HAL_GetTick() - start >= timeout) { return false; // timeout } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } @@ -498,7 +498,7 @@ bool uart_tx_wait(machine_uart_obj_t *self, uint32_t timeout) { if (HAL_GetTick() - start >= timeout) { return false; // timeout } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } From cfcc53da72d330d2eb9728d5f75c3ef5b459f1cf Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 1 Oct 2024 20:41:23 +1000 Subject: [PATCH 0788/2098] drivers/esp-hosted: Replace EVENT_POLL_HOOK with mp_event_wait_ms. Signed-off-by: Andrew Leech --- drivers/esp-hosted/esp_hosted_bthci_uart.c | 4 ++-- drivers/esp-hosted/esp_hosted_wifi.c | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/esp-hosted/esp_hosted_bthci_uart.c b/drivers/esp-hosted/esp_hosted_bthci_uart.c index 003054460d9..069509edde7 100644 --- a/drivers/esp-hosted/esp_hosted_bthci_uart.c +++ b/drivers/esp-hosted/esp_hosted_bthci_uart.c @@ -72,7 +72,7 @@ int esp_hosted_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_ // Receive HCI event packet, initially reading 3 bytes (HCI Event, Event code, Plen). for (mp_uint_t start = mp_hal_ticks_ms(), size = 3, i = 0; i < size;) { while (!mp_bluetooth_hci_uart_any()) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); // Timeout. if ((mp_hal_ticks_ms() - start) > HCI_COMMAND_TIMEOUT) { error_printf("timeout waiting for HCI packet\n"); @@ -126,7 +126,7 @@ int mp_bluetooth_hci_controller_init(void) { if (mp_bluetooth_hci_uart_any()) { mp_bluetooth_hci_uart_readchar(); } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } #ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY diff --git a/drivers/esp-hosted/esp_hosted_wifi.c b/drivers/esp-hosted/esp_hosted_wifi.c index d1b6333aa01..763b04db376 100644 --- a/drivers/esp-hosted/esp_hosted_wifi.c +++ b/drivers/esp-hosted/esp_hosted_wifi.c @@ -243,7 +243,7 @@ static int esp_hosted_request(CtrlMsgId msg_id, void *ctrl_payload) { static CtrlMsg *esp_hosted_response(CtrlMsgId msg_id, uint32_t timeout) { CtrlMsg *ctrl_msg = NULL; - for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_wait_ms(10)) { if (!esp_hosted_stack_empty(&esp_state.stack)) { ctrl_msg = esp_hosted_stack_pop(&esp_state.stack, true); if (ctrl_msg->msg_id == msg_id) { @@ -264,8 +264,6 @@ static CtrlMsg *esp_hosted_response(CtrlMsgId msg_id, uint32_t timeout) { if ((mp_hal_ticks_ms() - start) >= timeout) { return NULL; } - - MICROPY_EVENT_POLL_HOOK } // If message type is a response, check the response struct's return value. From 7816b1f5134b24d5c352af679d398a491a427edd Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 16 Jun 2025 12:35:51 +0100 Subject: [PATCH 0789/2098] rp2/machine_timer: Support hard IRQ timer callbacks. Unlike some boards like stm32, timer callbacks on the rp2 port are unconditionally dispatched via mp_sched_schedule(), behaving like soft IRQs with consequent GC jitter and delays. Add a 'hard' keyword argument to the rp2 Timer constructor and init. This defaults to False but if it is set True, the timer callback will be dispatched in hard IRQ context rather than queued. Signed-off-by: Chris Webb --- ports/rp2/machine_timer.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_timer.c b/ports/rp2/machine_timer.c index 6f8b04f10ea..ffb4c702430 100644 --- a/ports/rp2/machine_timer.c +++ b/ports/rp2/machine_timer.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "py/mperrno.h" #include "py/mphal.h" +#include "py/gc.h" #include "pico/time.h" #define ALARM_ID_INVALID (-1) @@ -40,13 +41,36 @@ typedef struct _machine_timer_obj_t { uint32_t mode; uint64_t delta_us; // for periodic mode mp_obj_t callback; + bool ishard; } machine_timer_obj_t; const mp_obj_type_t machine_timer_type; static int64_t alarm_callback(alarm_id_t id, void *user_data) { machine_timer_obj_t *self = user_data; - mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + + if (self->ishard) { + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_1(self->callback, MP_OBJ_FROM_PTR(self)); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so it doesn't run again. + self->mode = TIMER_MODE_ONE_SHOT; + mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in timer callback\n"); + mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + gc_unlock(); + mp_sched_unlock(); + } else { + mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + } + if (self->mode == TIMER_MODE_ONE_SHOT) { return 0; } else { @@ -66,13 +90,14 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr } static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, }; + enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIMER_MODE_PERIODIC} }, { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; // Parse args @@ -96,6 +121,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_ar } self->callback = args[ARG_callback].u_obj; + self->ishard = args[ARG_hard].u_bool; self->alarm_id = alarm_pool_add_alarm_in_us(self->pool, self->delta_us, alarm_callback, self, true); if (self->alarm_id == -1) { mp_raise_OSError(MP_ENOMEM); From 8f85edad8601c87a107b51a1b417600fbb8d8087 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 16 Jun 2025 12:35:51 +0100 Subject: [PATCH 0790/2098] tests/ports/rp2: Add tests for rp2-specific timer options. Add tests for both one-shot and periodic timers using the rp2-specific tick_hz= and hard= parameters. Signed-off-by: Chris Webb --- tests/ports/rp2/rp2_machine_timer.py | 20 ++++++++++++++++++++ tests/ports/rp2/rp2_machine_timer.py.exp | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/ports/rp2/rp2_machine_timer.py create mode 100644 tests/ports/rp2/rp2_machine_timer.py.exp diff --git a/tests/ports/rp2/rp2_machine_timer.py b/tests/ports/rp2/rp2_machine_timer.py new file mode 100644 index 00000000000..ac4efcf7f38 --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py @@ -0,0 +1,20 @@ +from machine import Timer +from time import sleep_ms + +# Test the rp2-specific adjustable tick_hz and hard/soft IRQ handlers +# for both one-shot and periodic timers. + +modes = {Timer.ONE_SHOT: "one-shot", Timer.PERIODIC: "periodic"} +kinds = {False: "soft", True: "hard"} + +for mode in modes: + for hard in kinds: + for period in 2, 4: + timer = Timer( + mode=mode, + period=period, + hard=hard, + callback=lambda t: print("callback", modes[mode], kinds[hard], period), + ) + sleep_ms(9) + timer.deinit() diff --git a/tests/ports/rp2/rp2_machine_timer.py.exp b/tests/ports/rp2/rp2_machine_timer.py.exp new file mode 100644 index 00000000000..b3dd93dfabf --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py.exp @@ -0,0 +1,16 @@ +callback one-shot soft 2 +callback one-shot soft 4 +callback one-shot hard 2 +callback one-shot hard 4 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 4 +callback periodic soft 4 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 4 +callback periodic hard 4 From 816246836e00b6c2af0ba26e8d16c234b43da4a0 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 16 Jun 2025 12:35:51 +0100 Subject: [PATCH 0791/2098] docs/rp2: Document the new rp2 Timer hard= option. Signed-off-by: Chris Webb --- docs/rp2/quickref.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst index 23071d77215..ec31442990f 100644 --- a/docs/rp2/quickref.rst +++ b/docs/rp2/quickref.rst @@ -135,6 +135,12 @@ Use the :mod:`machine.Timer` class:: tim = Timer(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1)) tim.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(2)) +By default, timer callbacks run as soft IRQs so they can allocate but +are prone to GC jitter and delays. Pass ``hard=True`` to the ``Timer()`` +constructor or ``init()`` method to run the callback in hard-IRQ context +instead. This reduces delay and jitter, but see :ref:`isr_rules` for the +restrictions that apply to hard-IRQ handlers. + .. _rp2_Pins_and_GPIO: From 2c8ccd3ee8945d53857e383911bf822374174257 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 1 Jun 2025 20:06:56 +0200 Subject: [PATCH 0792/2098] py: Fix undefined left shift operations. By ensuring the value to be shifted is an unsigned of the appropriate type. This fixes several runtime diagnostics such as: ../../py/binary.c:199:28: runtime error: left shift of 32768 by 16 places cannot be represented in type 'int' Signed-off-by: Jeff Epler --- py/binary.c | 2 +- py/persistentcode.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py/binary.c b/py/binary.c index 4fc8f751ad3..48d3421bca9 100644 --- a/py/binary.c +++ b/py/binary.c @@ -196,7 +196,7 @@ static float mp_decode_half_float(uint16_t hf) { ++e; } - fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13); + fpu.i = ((hf & 0x8000u) << 16) | (e << 23) | (m << 13); return fpu.f; } diff --git a/py/persistentcode.c b/py/persistentcode.c index 43207a0cc8f..6ec0717f948 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -759,7 +759,7 @@ static void bit_vector_clear(bit_vector_t *self) { static bool bit_vector_is_set(bit_vector_t *self, size_t index) { const size_t bits_size = sizeof(*self->bits) * MP_BITS_PER_BYTE; return index / bits_size < self->alloc - && (self->bits[index / bits_size] & (1 << (index % bits_size))) != 0; + && (self->bits[index / bits_size] & ((uintptr_t)1 << (index % bits_size))) != 0; } static void bit_vector_set(bit_vector_t *self, size_t index) { @@ -770,7 +770,7 @@ static void bit_vector_set(bit_vector_t *self, size_t index) { self->bits = m_renew(uintptr_t, self->bits, self->alloc, new_alloc); self->alloc = new_alloc; } - self->bits[index / bits_size] |= 1 << (index % bits_size); + self->bits[index / bits_size] |= (uintptr_t)1 << (index % bits_size); } typedef struct _mp_opcode_t { From 268264fe64d04b82434c8efb2e9e94e8712719d4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 15 Jun 2025 16:35:13 +0200 Subject: [PATCH 0793/2098] tools/ci.sh: Add functions for sanitizer builds. Includes both undefined and address sanitizer configurations. Signed-off-by: Jeff Epler --- tools/ci.sh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index a5cd326c1c7..b0e59509add 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -512,6 +512,18 @@ CI_UNIX_OPTS_QEMU_RISCV64=( MICROPY_STANDALONE=1 ) +CI_UNIX_OPTS_SANITIZE_ADDRESS=( + VARIANT=coverage + CFLAGS_EXTRA="-fsanitize=address" + LDFLAGS_EXTRA="-fsanitize=address" +) + +CI_UNIX_OPTS_SANITIZE_UNDEFINED=( + VARIANT=coverage + CFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" + LDFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" +) + function ci_unix_build_helper { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix "$@" submodules @@ -742,6 +754,28 @@ function ci_unix_settrace_stackless_run_tests { ci_unix_run_tests_full_helper standard "${CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS[@]}" } +function ci_unix_sanitize_undefined_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix submodules + make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + ci_unix_build_ffi_lib_helper gcc +} + +function ci_unix_sanitize_undefined_run_tests { + ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" +} + +function ci_unix_sanitize_address_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix submodules + make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" + ci_unix_build_ffi_lib_helper gcc +} + +function ci_unix_sanitize_address_run_tests { + ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" +} + function ci_unix_macos_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix submodules From 841acb9df1eba53b1d6daf0684eddec17680235c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 15 Jun 2025 16:45:23 +0200 Subject: [PATCH 0794/2098] github/workflows: Add sanitize_undefined workflow to unix port CI. gcc's "undefined behavior" sanitizer can catch a range of misbehaviors at runtime that normally go unnoticed. These include integer and pointer operations that are "undefined" per the relevant C specification. This commit enables undefined behavior detection during a new unix coverage-like build. Signed-off-by: Jeff Epler --- .github/workflows/ports_unix.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 2547015038e..662121654e4 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -262,3 +262,23 @@ jobs: - name: Print failures if: failure() run: tests/run-tests.py --print-failures + + sanitize_undefined: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_unix_coverage_setup + - name: Build + run: source tools/ci.sh && ci_unix_sanitize_undefined_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_sanitize_undefined_run_tests + - name: Test merging .mpy files + run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests + - name: Build native mpy modules + run: source tools/ci.sh && ci_native_mpy_modules_build + - name: Test importing .mpy generated by mpy_ld.py + run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures From 5901b8d149d63d74354bfb3f7f44a0b942eda23e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 10 Jun 2025 08:21:14 +0200 Subject: [PATCH 0795/2098] tests/cpydiff: Document complex() parsing difference. In #17384 it was decided that fixing this difference was not worth the code size increase. So document it instead. Signed-off-by: Jeff Epler --- tests/cpydiff/types_complex_parser.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/cpydiff/types_complex_parser.py diff --git a/tests/cpydiff/types_complex_parser.py b/tests/cpydiff/types_complex_parser.py new file mode 100644 index 00000000000..4a012987d9e --- /dev/null +++ b/tests/cpydiff/types_complex_parser.py @@ -0,0 +1,14 @@ +""" +categories: Types,complex +description: MicroPython's complex() accepts certain incorrect values that CPython rejects +cause: MicroPython is highly optimized for memory usage. +workaround: Do not use non-standard complex literals as argument to complex() + +MicroPython's ``complex()`` function accepts literals that contain a space and +no sign between the real and imaginary parts, and interprets it as a plus. +""" + +try: + print(complex("1 1j")) +except ValueError: + print("ValueError") From 5ade8b7058f95e77a0d62d523c8443647f176532 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 16 Jun 2025 09:11:31 +0200 Subject: [PATCH 0796/2098] tests/extmod: Add platform_basic.py for basic coverage test of platform. Signed-off-by: Jeff Epler --- tests/extmod/platform_basic.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/extmod/platform_basic.py diff --git a/tests/extmod/platform_basic.py b/tests/extmod/platform_basic.py new file mode 100644 index 00000000000..eb6f2be13c1 --- /dev/null +++ b/tests/extmod/platform_basic.py @@ -0,0 +1,8 @@ +try: + import platform +except ImportError: + print("SKIP") + raise SystemExit + +print(type(platform.python_compiler())) +print(type(platform.libc_ver())) From b6b7d64bd9257cc1c138d37033108dcff5ceb89e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 13 Jun 2025 17:24:08 +0200 Subject: [PATCH 0797/2098] py/modio: Fix the case where write fails in BufferedWriter.flush. Previously, there was no test coverage of the "write failed" path. In fact, the assertion would fire instead of gracefully raising a Python exception. Slightly re-organize the code to place the assertion later. Add a test case which exercises all paths, and update the expected output. Signed-off-by: Jeff Epler --- py/modio.c | 9 +++++---- tests/basics/io_buffered_writer.py | 24 ++++++++++++++++++++++++ tests/basics/io_buffered_writer.py.exp | 5 +++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/py/modio.c b/py/modio.c index d3e563dbcf4..9aeb42d30aa 100644 --- a/py/modio.c +++ b/py/modio.c @@ -169,12 +169,13 @@ static mp_obj_t bufwriter_flush(mp_obj_t self_in) { int err; mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->len, &err); (void)out_sz; - // TODO: try to recover from a case of non-blocking stream, e.g. move - // remaining chunk to the beginning of buffer. - assert(out_sz == self->len); - self->len = 0; if (err != 0) { mp_raise_OSError(err); + } else { + // TODO: try to recover from a case of non-blocking stream, e.g. move + // remaining chunk to the beginning of buffer. + assert(out_sz == self->len); + self->len = 0; } } diff --git a/tests/basics/io_buffered_writer.py b/tests/basics/io_buffered_writer.py index 5c065f158a2..60cf2c837d1 100644 --- a/tests/basics/io_buffered_writer.py +++ b/tests/basics/io_buffered_writer.py @@ -28,3 +28,27 @@ # hashing a BufferedWriter print(type(hash(buf))) + +# Test failing flush() +class MyIO(io.IOBase): + def __init__(self): + self.count = 0 + + def write(self, buf): + self.count += 1 + if self.count < 3: + return None + print("writing", buf) + return len(buf) + + +buf = io.BufferedWriter(MyIO(), 8) + +buf.write(b"foobar") + +for _ in range(4): + try: + buf.flush() + print("flushed") + except OSError: + print("OSError") diff --git a/tests/basics/io_buffered_writer.py.exp b/tests/basics/io_buffered_writer.py.exp index 2209348f5af..d61eb148b45 100644 --- a/tests/basics/io_buffered_writer.py.exp +++ b/tests/basics/io_buffered_writer.py.exp @@ -4,3 +4,8 @@ b'foobarfoobar' b'foobarfoobar' b'foo' +OSError +OSError +writing bytearray(b'foobar') +flushed +flushed From c1196cadb185a6834146c358ecbf495bd556c303 Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Mon, 16 Jun 2025 18:49:24 +0300 Subject: [PATCH 0798/2098] esp32/modesp32: Fix access to ext0_pin only if defined. In different functions `machine_rtc_config.ext0_pin` is accessed where SOC_PM_SUPPORT_EXT0_WAKEUP is not defined, fix that. Signed-off-by: Meir Armon --- ports/esp32/modesp32.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 26a39d44511..fcd6ed9fa83 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -50,9 +50,11 @@ #if SOC_TOUCH_SENSOR_SUPPORTED static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { + #if SOC_PM_SUPPORT_EXT0_WAKEUP if (machine_rtc_config.ext0_pin != -1) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } + #endif machine_rtc_config.wake_on_touch = mp_obj_is_true(wake); return mp_const_none; @@ -137,9 +139,11 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1) #if SOC_ULP_SUPPORTED static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { + #if SOC_PM_SUPPORT_EXT0_WAKEUP if (machine_rtc_config.ext0_pin != -1) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } + #endif machine_rtc_config.wake_on_ulp = mp_obj_is_true(wake); return mp_const_none; } From b725c26b4e351b71b8f72ad8e55bbbd5ccce4602 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 29 May 2025 13:46:05 +0100 Subject: [PATCH 0799/2098] rp2/rp2_flash: Add MICROPY_HW_FLASH_MAX_FREQ to replace fixed max freq. Assuming a 133MHz capable flash in 91cff8e4f10ea665c5f3f4b16d62c98d6ca22037 caused `rp2_flash_set_timing_internal` to set out of range dividers for some boards (anything with value of 4 and flash that doesn't tolerate higher speeds). This affected (at least) the XIAO RP2350 board, making it non-bootable. Since Pico SDK's `PICO_FLASH_SPI_CLKDIV` is entirely unreliable on a system with a variable system clock (users can change it at runtime) then use it only to work out a default `MICROPY_HW_FLASH_MAX_FREQ`. This value can be overridden in board config. Note that RP2350's default clock is 150MHz, RP2040's is 125MHz and it has been certified at 200MHz so it's quite possible that `PICO_FLASH_SPI_CLKDIV` is unreliable even at standard RP2 clocks. (If flash timings are marginal then this can manifest as instability rather than outright failure.) Fixes issue #17375. Signed-off-by: Phil Howard --- ports/rp2/rp2_flash.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index a9beabf051c..8b467fc6c84 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -49,6 +49,14 @@ static_assert(MICROPY_HW_ROMFS_BYTES % 4096 == 0, "ROMFS size must be a multiple of 4K"); static_assert(MICROPY_HW_FLASH_STORAGE_BYTES % 4096 == 0, "Flash storage size must be a multiple of 4K"); +#ifndef MICROPY_HW_FLASH_MAX_FREQ +// Emulate Pico SDK's SYS_CLK_HZ / PICO_FLASH_SPI_CLKDIV behaviour by default. +// On RP2040 if PICO_USE_FASTEST_SUPPORTED_CLOCK is set then SYS_CLK_HZ can be +// 200MHz, potentially putting timings derived from PICO_FLASH_SPI_CLKDIV +// out of range. +#define MICROPY_HW_FLASH_MAX_FREQ (SYS_CLK_HZ / PICO_FLASH_SPI_CLKDIV) +#endif + #ifndef MICROPY_HW_FLASH_STORAGE_BASE #define MICROPY_HW_FLASH_STORAGE_BASE (PICO_FLASH_SIZE_BYTES - MICROPY_HW_FLASH_STORAGE_BYTES) #endif @@ -104,9 +112,8 @@ static bool use_multicore_lockout(void) { // 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; + // Use the minimum divisor based upon our target MICROPY_HW_FLASH_MAX_FREQ + int divisor = (clock_hz + MICROPY_HW_FLASH_MAX_FREQ - 1) / MICROPY_HW_FLASH_MAX_FREQ; #if PICO_RP2350 // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) From dc8daad3c90d48efb50f5f0469346fae8402540d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 17 Jun 2025 14:55:25 +0100 Subject: [PATCH 0800/2098] rp2/rp2_flash: Add default MICROPY_HW_FLASH_MAX_FREQ. Set a default MICROPY_HW_FLASH_MAX_FREQ if PICO_FLASH_SPI_CLKDIV is unset. Use a divider of 4, which is the default in boot2_generic_03h.S. Signed-off-by: Phil Howard --- ports/rp2/rp2_flash.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 8b467fc6c84..d6b9e136533 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -54,7 +54,12 @@ static_assert(MICROPY_HW_FLASH_STORAGE_BYTES % 4096 == 0, "Flash storage size mu // On RP2040 if PICO_USE_FASTEST_SUPPORTED_CLOCK is set then SYS_CLK_HZ can be // 200MHz, potentially putting timings derived from PICO_FLASH_SPI_CLKDIV // out of range. +#ifdef PICO_FLASH_SPI_CLKDIV #define MICROPY_HW_FLASH_MAX_FREQ (SYS_CLK_HZ / PICO_FLASH_SPI_CLKDIV) +#else +// A default PICO_FLASH_SPI_CLKDIV of 4 is set in boot2_generic_03h.S +#define MICROPY_HW_FLASH_MAX_FREQ (SYS_CLK_HZ / 4) +#endif #endif #ifndef MICROPY_HW_FLASH_STORAGE_BASE From 1a060e87cd413241b526bfd6ed4f94c72f748285 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Mar 2025 14:06:26 +0000 Subject: [PATCH 0801/2098] rp2/CMakeLists.txt: Make board's pins.csv configurable. Allow `mpconfigboard.cmake` to specify a custom `MICROPY_BOARD_PINS` to override `${MICROPY_BOARD_DIR}/pins.csv`. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index cf9f1807928..b9776012a0d 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -654,9 +654,13 @@ 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}") +if(NOT MICROPY_BOARD_PINS) + set(MICROPY_BOARD_PINS "${MICROPY_BOARD_DIR}/pins.csv") +endif() + +if(EXISTS "${MICROPY_BOARD_PINS}") + set(GEN_PINS_BOARD_CSV "${MICROPY_BOARD_PINS}") + set(GEN_PINS_CSV_ARG --board-csv "${MICROPY_BOARD_PINS}") endif() target_sources(${MICROPY_TARGET} PRIVATE From c16a4db151c0fac7db45c7c896741f707e9ad364 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 28 Mar 2025 14:27:26 +0000 Subject: [PATCH 0802/2098] rp2/CMakeLists.txt: Make linker script configurable. Add `MICROPY_BOARD_LINKER_SCRIPT` to specify a custom linker script for rp2 boards/variants. This may, for example, include a PSRAM region so that C buffers or otherwise can be allocated into PSRAM. Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index b9776012a0d..49ba8d77d63 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -610,14 +610,18 @@ endif() # todo this is a bit brittle, but we want to move a few source files into RAM (which requires # 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) +if (NOT MICROPY_BOARD_LINKER_SCRIPT) if(PICO_RP2040) - pico_set_linker_script(${MICROPY_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2040.ld) + set(MICROPY_BOARD_LINKER_SCRIPT ${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) + set(MICROPY_BOARD_LINKER_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/memmap_mp_rp2350.ld) endif() endif() +if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) + pico_set_linker_script(${MICROPY_TARGET} ${MICROPY_BOARD_LINKER_SCRIPT}) +endif() + pico_add_extra_outputs(${MICROPY_TARGET}) pico_find_compiler_with_triples(PICO_COMPILER_SIZE "${PICO_GCC_TRIPLE}" size) From 5e965618beb24927091a897e9cc81241d22d89ad Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 18 Jun 2025 07:21:36 +1000 Subject: [PATCH 0803/2098] tests/run-tests.py: Add support for ctrl keys in REPL tests. This allows having {\xDD} in tests, which will be expanded to the given hex character. Signed-off-by: Andrew Leech --- tests/run-tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 5eebc72460a..628fde9d30a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -405,6 +405,10 @@ def get(required=False): return rv def send_get(what): + # Detect {\x00} pattern and convert to ctrl-key codes. + ctrl_code = lambda m: bytes([int(m.group(1))]) + what = re.sub(rb'{\\x(\d\d)}', ctrl_code, what) + os.write(master, what) return get() From 8eb56369967382b6644fa56b98acbff2e77a66a3 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 19 Jun 2025 14:10:57 +1000 Subject: [PATCH 0804/2098] tests/cmdline: Add a test for REPL paste mode. Signed-off-by: Andrew Leech --- pyproject.toml | 1 + tests/cmdline/repl_paste.py | 90 +++++++++++++++++++++ tests/cmdline/repl_paste.py.exp | 133 ++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 tests/cmdline/repl_paste.py create mode 100644 tests/cmdline/repl_paste.py.exp diff --git a/pyproject.toml b/pyproject.toml index 263b120d3fe..556dfdaeeae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ exclude = [ # Ruff finds Python SyntaxError in these files "tests/cmdline/repl_basic.py", "tests/cmdline/repl_cont.py", "tests/cmdline/repl_emacs_keys.py", + "tests/cmdline/repl_paste.py", "tests/cmdline/repl_words_move.py", "tests/feature_check/repl_emacs_check.py", "tests/feature_check/repl_words_move_check.py", diff --git a/tests/cmdline/repl_paste.py b/tests/cmdline/repl_paste.py new file mode 100644 index 00000000000..7cec450fce7 --- /dev/null +++ b/tests/cmdline/repl_paste.py @@ -0,0 +1,90 @@ +# Test REPL paste mode functionality + +# Basic paste mode with a simple function +{\x05} +def hello(): + print('Hello from paste mode!') +hello() +{\x04} + +# Paste mode with multiple indentation levels +{\x05} +def calculate(n): + if n > 0: + for i in range(n): + if i % 2 == 0: + print(f'Even: {i}') + else: + print(f'Odd: {i}') + else: + print('n must be positive') + +calculate(5) +{\x04} + +# Paste mode with blank lines +{\x05} +def function_with_blanks(): + print('First line') + + print('After blank line') + + + print('After two blank lines') + +function_with_blanks() +{\x04} + +# Paste mode with class definition and multiple methods +{\x05} +class TestClass: + def __init__(self, value): + self.value = value + + def display(self): + print(f'Value is: {self.value}') + + def double(self): + self.value *= 2 + return self.value + +obj = TestClass(21) +obj.display() +print(f'Doubled: {obj.double()}') +obj.display() +{\x04} + +# Paste mode with exception handling +{\x05} +try: + x = 1 / 0 +except ZeroDivisionError: + print('Caught division by zero') +finally: + print('Finally block executed') +{\x04} + +# Cancel paste mode with Ctrl-C +{\x05} +print('This should not execute') +{\x03} + +# Normal REPL still works after cancelled paste +print('Back to normal REPL') + +# Paste mode with syntax error +{\x05} +def bad_syntax(: + print('Missing parameter') +{\x04} + +# Paste mode with runtime error +{\x05} +def will_error(): + undefined_variable + +will_error() +{\x04} + +# Final test to show REPL is still functioning +1 + 2 + 3 diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp new file mode 100644 index 00000000000..22d9bd57400 --- /dev/null +++ b/tests/cmdline/repl_paste.py.exp @@ -0,0 +1,133 @@ +MicroPython \.\+ version +Use Ctrl-D to exit, Ctrl-E for paste mode +>>> # Test REPL paste mode functionality +>>> +>>> # Basic paste mode with a simple function +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def hello(): +=== print('Hello from paste mode!') +=== hello() +=== +Hello from paste mode! +>>> +>>> # Paste mode with multiple indentation levels +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def calculate(n): +=== if n > 0: +=== for i in range(n): +=== if i % 2 == 0: +=== print(f'Even: {i}') +=== else: +=== print(f'Odd: {i}') +=== else: +=== print('n must be positive') +=== +=== calculate(5) +=== +Even: 0 +Odd: 1 +Even: 2 +Odd: 3 +Even: 4 +>>> +>>> # Paste mode with blank lines +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def function_with_blanks(): +=== print('First line') +=== +=== print('After blank line') +=== +=== +=== print('After two blank lines') +=== +=== function_with_blanks() +=== +First line +After blank line +After two blank lines +>>> +>>> # Paste mode with class definition and multiple methods +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== class TestClass: +=== def __init__(self, value): +=== self.value = value +=== +=== def display(self): +=== print(f'Value is: {self.value}') +=== +=== def double(self): +=== self.value *= 2 +=== return self.value +=== +=== obj = TestClass(21) +=== obj.display() +=== print(f'Doubled: {obj.double()}') +=== obj.display() +=== +Value is: 21 +Doubled: 42 +Value is: 42 +>>> +>>> # Paste mode with exception handling +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== try: +=== x = 1 / 0 +=== except ZeroDivisionError: +=== print('Caught division by zero') +=== finally: +=== print('Finally block executed') +=== +Caught division by zero +Finally block executed +>>> +>>> # Cancel paste mode with Ctrl-C +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== print('This should not execute') +=== +>>> +>>> +>>> # Normal REPL still works after cancelled paste +>>> print('Back to normal REPL') +Back to normal REPL +>>> +>>> # Paste mode with syntax error +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def bad_syntax(: +=== print('Missing parameter') +=== +Traceback (most recent call last): + File "", line 2 +SyntaxError: invalid syntax +>>> +>>> # Paste mode with runtime error +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def will_error(): +=== undefined_variable +=== +=== will_error() +=== +Traceback (most recent call last): + File "", line 5, in + File "", line 3, in will_error +NameError: name 'undefined_variable' isn't defined +>>> +>>> # Final test to show REPL is still functioning +>>> 1 + 2 + 3 +6 +>>> From 09541b78963d6aff334b143c3247271a939d54fa Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 11:29:54 +1100 Subject: [PATCH 0805/2098] py/repl: Skip private variables when printing tab completion options. Any '_' variables/functions in frozen modules are currently printed, when they shouldn't be. That's due to underscore names possibly existing between the start and end qstrs which are used to print the auto-complete matches. The underscore names should be skipped when iterating between the two boundary qstrs. The underscore attributes are removed from the extra coverage exp file because tab completing "import " no longer lists modules beginning with an underscore. Signed-off-by: Andrew Leech --- py/repl.c | 4 ++ pyproject.toml | 1 + tests/cmdline/repl_autocomplete_underscore.py | 33 +++++++++++++++ .../repl_autocomplete_underscore.py.exp | 41 +++++++++++++++++++ tests/ports/unix/extra_coverage.py.exp | 20 ++++----- 5 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 tests/cmdline/repl_autocomplete_underscore.py create mode 100644 tests/cmdline/repl_autocomplete_underscore.py.exp diff --git a/py/repl.c b/py/repl.c index 87c171cc872..b0ccfa383af 100644 --- a/py/repl.c +++ b/py/repl.c @@ -218,6 +218,10 @@ static void print_completions(const mp_print_t *print, for (qstr q = q_first; q <= q_last; ++q) { size_t d_len; const char *d_str = (const char *)qstr_data(q, &d_len); + // filter out words that begin with underscore unless there's already a partial match + if (s_len == 0 && d_str[0] == '_') { + continue; + } if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) { if (test_qstr(obj, q)) { int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len; diff --git a/pyproject.toml b/pyproject.toml index 556dfdaeeae..0dd15d06c7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ target-version = "py37" [tool.ruff.lint] exclude = [ # Ruff finds Python SyntaxError in these files "tests/cmdline/repl_autocomplete.py", + "tests/cmdline/repl_autocomplete_underscore.py", "tests/cmdline/repl_autoindent.py", "tests/cmdline/repl_basic.py", "tests/cmdline/repl_cont.py", diff --git a/tests/cmdline/repl_autocomplete_underscore.py b/tests/cmdline/repl_autocomplete_underscore.py new file mode 100644 index 00000000000..98bbb699200 --- /dev/null +++ b/tests/cmdline/repl_autocomplete_underscore.py @@ -0,0 +1,33 @@ +# Test REPL autocompletion filtering of underscore attributes + +# Start paste mode +{\x05} +class TestClass: + def __init__(self): + self.public_attr = 1 + self._private_attr = 2 + self.__very_private = 3 + + def public_method(self): + pass + + def _private_method(self): + pass + + @property + def public_property(self): + return 42 + + @property + def _private_property(self): + return 99 + +{\x04} +# Paste executed + +# Create an instance +obj = TestClass() + +# Test tab completion on the instance +# The tab character after `obj.` and 'a' below triggers the completions +obj.{\x09}{\x09}a{\x09} diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp new file mode 100644 index 00000000000..35617554f57 --- /dev/null +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -0,0 +1,41 @@ +MicroPython \.\+ version +Use Ctrl-D to exit, Ctrl-E for paste mode +>>> # Test REPL autocompletion filtering of underscore attributes +>>> +>>> # Start paste mode +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== class TestClass: +=== def __init__(self): +=== self.public_attr = 1 +=== self._private_attr = 2 +=== self.__very_private = 3 +=== +=== def public_method(self): +=== pass +=== +=== def _private_method(self): +=== pass +=== +=== @property +=== def public_property(self): +=== return 42 +=== +=== @property +=== def _private_property(self): +=== return 99 +=== +=== +>>> # Paste executed +>>> +>>> # Create an instance +>>> obj = TestClass() +>>> +>>> # Test tab completion on the instance +>>> # The tab character after `obj.` and 'a' below triggers the completions +>>> obj.public_ +public_attr public_method public_property +>>> obj.public_attr +1 +>>> diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 3775036bb37..ac64edde692 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -51,16 +51,16 @@ RuntimeError: ame__ port -builtins micropython _asyncio _thread -array binascii btree cexample -cmath collections cppexample cryptolib -deflate errno example_package -ffi framebuf gc hashlib -heapq io json machine -marshal math os platform -random re select socket -struct sys termios time -tls uctypes vfs websocket +builtins micropython array binascii +btree cexample cmath collections +cppexample cryptolib deflate errno +example_package ffi framebuf +gc hashlib heapq io +json machine marshal math +os platform random re +select socket struct sys +termios time tls uctypes +vfs websocket me micropython machine marshal math From 32e69f7fdb31781b2b57c260db9ee4cbb70c1bf3 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 19 Jun 2025 15:12:49 +0200 Subject: [PATCH 0806/2098] samd/boards: Change the SparkFun vendor name to SparkFun. Only the board.json files are affected. No other file uses the style "Sparkfun". The documentation is fine. Signed-off-by: robert-hh --- ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json | 2 +- ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json index b7ccac725d4..789bd977303 100644 --- a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/board.json @@ -17,5 +17,5 @@ "product": "SparkFun RedBoard Turbo", "thumbnail": "", "url": "https://www.sparkfun.com/products/14812", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json index b2e6553b2d2..e5c5411a0c3 100644 --- a/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json +++ b/ports/samd/boards/SPARKFUN_SAMD21_DEV_BREAKOUT/board.json @@ -15,5 +15,5 @@ "product": "SparkFun SAMD21 Dev Breakout", "thumbnail": "", "url": "https://www.sparkfun.com/products/13672", - "vendor": "Sparkfun" + "vendor": "SparkFun" } From 41fc3e1505fff31e79d60c17b29a790b46c3af6b Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Mon, 16 Jun 2025 22:04:56 +0530 Subject: [PATCH 0807/2098] zephyr/boards/beagleconnect_freedom: Enable networking. Having both PWM and networking enabled now works fine. So enable subg networking. Signed-off-by: Ayush Singh --- ports/zephyr/boards/beagleconnect_freedom.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf index 8fe2583205b..e31e09444fb 100644 --- a/ports/zephyr/boards/beagleconnect_freedom.conf +++ b/ports/zephyr/boards/beagleconnect_freedom.conf @@ -7,3 +7,13 @@ CONFIG_SPI=y # Flash drivers CONFIG_FLASH=y CONFIG_FLASH_MAP=y + +# Networking +CONFIG_BT=n +CONFIG_NET_IPV4=n +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_L2_IEEE802154=y +CONFIG_NET_DHCPV4=n +CONFIG_NET_CONFIG_IEEE802154_CHANNEL=1 From 35f15cfdf2a04a0ad290d70383fc3e4be4f79812 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Mon, 16 Jun 2025 22:05:35 +0530 Subject: [PATCH 0808/2098] zephyr/boards/beagleconnect_freedom: Remove board overlay. Since MicroPython supports Zephyr v4.0.0, no need for overlay to enable PWM. It is enabled by default for a while now. Signed-off-by: Ayush Singh --- .../boards/beagleconnect_freedom.overlay | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 ports/zephyr/boards/beagleconnect_freedom.overlay diff --git a/ports/zephyr/boards/beagleconnect_freedom.overlay b/ports/zephyr/boards/beagleconnect_freedom.overlay deleted file mode 100644 index 7dd4469c992..00000000000 --- a/ports/zephyr/boards/beagleconnect_freedom.overlay +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024 Ayush Singh - * - * SPDX-License-Identifier: Apache-2.0 - */ - -&pinctrl { - /* MB1 PWM */ - pwm0_default: pwm0_default { - pinmux = <17 IOC_PORT_MCU_PORT_EVENT1>; - bias-disable; - drive-strength = <2>; - }; - - /* MB2 PWM */ - pwm1_default: pwm1_default { - pinmux = <19 IOC_PORT_MCU_PORT_EVENT3>; - bias-disable; - drive-strength = <2>; - }; -}; - -&gpt0 { - status = "okay"; -}; - -&gpt1 { - status = "okay"; -}; - -&pwm0 { - status = "okay"; - pinctrl-0 = <&pwm0_default>; - pinctrl-names = "default"; -}; - -&pwm1 { - status = "okay"; - pinctrl-0 = <&pwm1_default>; - pinctrl-names = "default"; -}; From 66c01480226aaf880a9d6e88340772bd53bc0421 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Tue, 20 May 2025 15:57:11 +0200 Subject: [PATCH 0809/2098] py/runtime: Add support for using __all__ in star import. When the symbol `__all__` is defined in a module, `mp_import_all()` should import all listed symbols into the global environment, rather than relying on the underscore-is-private default. This is the standard in CPython. Each item is loaded in the same way as if it would be an explicit import statement, and will invoke the module's `__getattr__` function if needed. This provides a straightforward solution for fixing star import of modules using a dynamic loader, such as `extmod/asyncio` (see issue #7266). This improvement has been enabled at BASIC_FEATURES level, to avoid impacting devices with limited ressources, for which star import is of little use anyway. Additionally, detailled reporting of errors during `__all__` import has been implemented to match CPython, but this is only enabled when ERROR_REPORTING is set to MICROPY_ERROR_REPORTING_DETAILED. Signed-off-by: Yoctopuce dev --- py/mpconfig.h | 5 ++ py/runtime.c | 35 ++++++++++++- tests/cpydiff/core_import_all.py | 10 ---- tests/cpydiff/modules3/__init__.py | 1 - tests/cpydiff/modules3/foo.py | 2 - tests/import/import_star.py | 59 ++++++++++++++++++++++ tests/import/pkgstar_all_array/__init__.py | 49 ++++++++++++++++++ tests/import/pkgstar_all_array/funcs.py | 2 + tests/import/pkgstar_all_inval/__init__.py | 1 + tests/import/pkgstar_all_miss/__init__.py | 8 +++ tests/import/pkgstar_all_tuple/__init__.py | 22 ++++++++ tests/import/pkgstar_default/__init__.py | 20 ++++++++ 12 files changed, 200 insertions(+), 14 deletions(-) delete mode 100644 tests/cpydiff/core_import_all.py delete mode 100644 tests/cpydiff/modules3/__init__.py delete mode 100644 tests/cpydiff/modules3/foo.py create mode 100644 tests/import/import_star.py create mode 100644 tests/import/pkgstar_all_array/__init__.py create mode 100644 tests/import/pkgstar_all_array/funcs.py create mode 100644 tests/import/pkgstar_all_inval/__init__.py create mode 100644 tests/import/pkgstar_all_miss/__init__.py create mode 100644 tests/import/pkgstar_all_tuple/__init__.py create mode 100644 tests/import/pkgstar_default/__init__.py diff --git a/py/mpconfig.h b/py/mpconfig.h index 01712bd5b4d..94b84530037 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1323,6 +1323,11 @@ typedef double mp_float_t; #define MICROPY_PY___FILE__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif +// Whether to process __all__ when importing all public symbols from module +#ifndef MICROPY_MODULE___ALL__ +#define MICROPY_MODULE___ALL__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) +#endif + // Whether to provide mem-info related functions in micropython module #ifndef MICROPY_PY_MICROPYTHON_MEM_INFO #define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) diff --git a/py/runtime.c b/py/runtime.c index 7979e520da4..90587a010a4 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1247,6 +1247,19 @@ void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("type object '%q' has no attribute '%q'"), ((mp_obj_type_t *)MP_OBJ_TO_PTR(base))->name, attr); + #if MICROPY_MODULE___ALL__ && MICROPY_ERROR_REPORTING >= MICROPY_ERROR_REPORTING_DETAILED + } else if (mp_obj_is_type(base, &mp_type_module)) { + // report errors in __all__ as done by CPython + mp_obj_t dest_name[2]; + qstr module_name = MP_QSTR_; + mp_load_method_maybe(base, MP_QSTR___name__, dest_name); + if (mp_obj_is_qstr(dest_name[0])) { + module_name = mp_obj_str_get_qstr(dest_name[0]); + } + mp_raise_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("module '%q' has no attribute '%q'"), + module_name, attr); + #endif } else { mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("'%s' object has no attribute '%q'"), @@ -1593,8 +1606,28 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { void mp_import_all(mp_obj_t module) { DEBUG_printf("import all %p\n", module); - // TODO: Support __all__ mp_map_t *map = &mp_obj_module_get_globals(module)->map; + + #if MICROPY_MODULE___ALL__ + mp_map_elem_t *elem = mp_map_lookup(map, MP_OBJ_NEW_QSTR(MP_QSTR___all__), MP_MAP_LOOKUP); + if (elem != NULL) { + // When __all__ is defined, we must explicitly load all specified + // symbols, possibly invoking the module __getattr__ function + size_t len; + mp_obj_t *items; + mp_obj_get_array(elem->value, &len, &items); + for (size_t i = 0; i < len; i++) { + qstr qname = mp_obj_str_get_qstr(items[i]); + mp_obj_t dest[2]; + mp_load_method(module, qname, dest); + mp_store_name(qname, dest[0]); + } + return; + } + #endif + + // By default, the set of public names includes all names found in the module's + // namespace which do not begin with an underscore character ('_') for (size_t i = 0; i < map->alloc; i++) { if (mp_map_slot_is_filled(map, i)) { // Entry in module global scope may be generated programmatically diff --git a/tests/cpydiff/core_import_all.py b/tests/cpydiff/core_import_all.py deleted file mode 100644 index 0fbe9d4d4ec..00000000000 --- a/tests/cpydiff/core_import_all.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -categories: Core,import -description: __all__ is unsupported in __init__.py in MicroPython. -cause: Not implemented. -workaround: Manually import the sub-modules directly in __init__.py using ``from . import foo, bar``. -""" - -from modules3 import * - -foo.hello() diff --git a/tests/cpydiff/modules3/__init__.py b/tests/cpydiff/modules3/__init__.py deleted file mode 100644 index 27a2bf2ad90..00000000000 --- a/tests/cpydiff/modules3/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ["foo"] diff --git a/tests/cpydiff/modules3/foo.py b/tests/cpydiff/modules3/foo.py deleted file mode 100644 index dd9b9d4ddd4..00000000000 --- a/tests/cpydiff/modules3/foo.py +++ /dev/null @@ -1,2 +0,0 @@ -def hello(): - print("hello") diff --git a/tests/import/import_star.py b/tests/import/import_star.py new file mode 100644 index 00000000000..0947f6a835d --- /dev/null +++ b/tests/import/import_star.py @@ -0,0 +1,59 @@ +# test `from package import *` conventions, including __all__ support +# +# This test requires MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES + +try: + next(iter([]), 42) +except TypeError: + # 2-argument version of next() not supported + # we are probably not at MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES + print('SKIP') + raise SystemExit + +# 1. test default visibility +from pkgstar_default import * + +print('visibleFun' in globals()) +print('VisibleClass' in globals()) +print('_hiddenFun' in globals()) +print('_HiddenClass' in globals()) +print(visibleFun()) + +# 2. test explicit visibility as defined by __all__ (as an array) +from pkgstar_all_array import * + +print('publicFun' in globals()) +print('PublicClass' in globals()) +print('unlistedFun' in globals()) +print('UnlistedClass' in globals()) +print('_privateFun' in globals()) +print('_PrivateClass' in globals()) +print(publicFun()) +# test dynamic import as used in asyncio +print('dynamicFun' in globals()) +print(dynamicFun()) + +# 3. test explicit visibility as defined by __all__ (as an tuple) +from pkgstar_all_tuple import * + +print('publicFun2' in globals()) +print('PublicClass2' in globals()) +print('unlistedFun2' in globals()) +print('UnlistedClass2' in globals()) +print(publicFun2()) + +# 4. test reporting of missing entries in __all__ +try: + from pkgstar_all_miss import * + + print("missed detection of incorrect __all__ definition") +except AttributeError as er: + print("AttributeError triggered for bad __all__ definition") + +# 5. test reporting of invalid __all__ definition +try: + from pkgstar_all_inval import * + + print("missed detection of incorrect __all__ definition") +except TypeError as er: + print("TypeError triggered for bad __all__ definition") diff --git a/tests/import/pkgstar_all_array/__init__.py b/tests/import/pkgstar_all_array/__init__.py new file mode 100644 index 00000000000..4499a94d591 --- /dev/null +++ b/tests/import/pkgstar_all_array/__init__.py @@ -0,0 +1,49 @@ +__all__ = ['publicFun', 'PublicClass', 'dynamicFun'] + + +# Definitions below should always be imported by a star import +def publicFun(): + return 1 + + +class PublicClass: + def __init__(self): + self._val = 1 + + +# If __all__ support is enabled, definitions below +# should not be imported by a star import +def unlistedFun(): + return 0 + + +class UnlistedClass: + def __init__(self): + self._val = 0 + + +# Definitions below should be not be imported by a star import +# (they start with an underscore, and are not listed in __all__) +def _privateFun(): + return -1 + + +class _PrivateClass: + def __init__(self): + self._val = -1 + + +# Test lazy loaded function, as used by extmod/asyncio: +# Works with a star import only if __all__ support is enabled +_attrs = { + "dynamicFun": "funcs", +} + + +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, globals(), locals(), True, 1), attr) + globals()[attr] = value + return value diff --git a/tests/import/pkgstar_all_array/funcs.py b/tests/import/pkgstar_all_array/funcs.py new file mode 100644 index 00000000000..7540d70f66b --- /dev/null +++ b/tests/import/pkgstar_all_array/funcs.py @@ -0,0 +1,2 @@ +def dynamicFun(): + return 777 diff --git a/tests/import/pkgstar_all_inval/__init__.py b/tests/import/pkgstar_all_inval/__init__.py new file mode 100644 index 00000000000..7022476c19b --- /dev/null +++ b/tests/import/pkgstar_all_inval/__init__.py @@ -0,0 +1 @@ +__all__ = 42 diff --git a/tests/import/pkgstar_all_miss/__init__.py b/tests/import/pkgstar_all_miss/__init__.py new file mode 100644 index 00000000000..d960c7d0e29 --- /dev/null +++ b/tests/import/pkgstar_all_miss/__init__.py @@ -0,0 +1,8 @@ +__all__ = ('existingFun', 'missingFun') + + +def existingFun(): + return None + + +# missingFun is not defined, should raise an error on import diff --git a/tests/import/pkgstar_all_tuple/__init__.py b/tests/import/pkgstar_all_tuple/__init__.py new file mode 100644 index 00000000000..a97715ed391 --- /dev/null +++ b/tests/import/pkgstar_all_tuple/__init__.py @@ -0,0 +1,22 @@ +__all__ = ('publicFun2', 'PublicClass2') + + +# Definitions below should always be imported by a star import +def publicFun2(): + return 2 + + +class PublicClass2: + def __init__(self): + self._val = 2 + + +# If __all__ support is enabled, definitions below +# should not be imported by a star import +def unlistedFun2(): + return 0 + + +class UnlistedClass2: + def __init__(self): + self._val = 0 diff --git a/tests/import/pkgstar_default/__init__.py b/tests/import/pkgstar_default/__init__.py new file mode 100644 index 00000000000..4947e4ce7f1 --- /dev/null +++ b/tests/import/pkgstar_default/__init__.py @@ -0,0 +1,20 @@ +# When __all__ is undefined, star import should only +# show objects that do not start with an underscore + + +def visibleFun(): + return 42 + + +class VisibleClass: + def __init__(self): + self._val = 42 + + +def _hiddenFun(): + return -1 + + +class _HiddenClass: + def __init__(self): + self._val = -1 From e57aa7e70a326659529d02891abb9a57b055fc0c Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 19 Jun 2025 15:47:13 +0200 Subject: [PATCH 0810/2098] py/obj: Fix nan handling in object REPR_C and REPR_D. CPython math.nan is positive with regards to copysign. The signaling bit (aka sign flag) was incorrectly set. In addition, REPR_C and REPR_D should only use the _true_ nan to prevent system crash in case of hand-crafted floats. For instance, with REPR_C, any nan-like float following the pattern `01111111 1xxxxxxx xxxxxxxx xxxxx1xx` would be switched to an immediate object or a qstr string. When the qstr index is too large, this would cause a crash. This commit fixes the issue, and adds the relevant test cases. Signed-off-by: Yoctopuce dev --- ports/windows/mpconfigport.h | 5 +++++ py/modmath.c | 5 +++++ py/obj.h | 20 +++++++++++++++++--- tests/float/float_array.py | 8 +++++++- tests/float/math_constants_extra.py | 3 +++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 6e32d5fe5eb..4e140d5edb7 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -266,6 +266,11 @@ typedef long mp_off_t; #endif #endif +// VC++ 2017 fixes +#if (_MSC_VER < 1920) +#define MICROPY_PY_MATH_COPYSIGN_FIX_NAN (1) +#endif + // CL specific definitions #ifndef __cplusplus diff --git a/py/modmath.c b/py/modmath.c index b792d8581d0..919a8ccd9d3 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -161,6 +161,11 @@ MATH_FUN_2(atan2, atan2) MATH_FUN_1_TO_INT(ceil, ceil) // copysign(x, y) static mp_float_t MICROPY_FLOAT_C_FUN(copysign_func)(mp_float_t x, mp_float_t y) { + #if MICROPY_PY_MATH_COPYSIGN_FIX_NAN + if (isnan(y)) { + y = 0.0; + } + #endif return MICROPY_FLOAT_C_FUN(copysign)(x, y); } MATH_FUN_2(copysign, copysign_func) diff --git a/py/obj.h b/py/obj.h index 07362522474..0f87282a9f4 100644 --- a/py/obj.h +++ b/py/obj.h @@ -184,13 +184,15 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) << 1) | 1)) #if MICROPY_PY_BUILTINS_FLOAT -#define MP_OBJ_NEW_CONST_FLOAT(f) MP_ROM_PTR((mp_obj_t)((((((uint64_t)f) & ~3) | 2) + 0x80800000) & 0xffffffff)) +#include +// note: MP_OBJ_NEW_CONST_FLOAT should be a MP_ROM_PTR but that macro isn't available yet +#define MP_OBJ_NEW_CONST_FLOAT(f) ((mp_obj_t)((((((uint64_t)f) & ~3) | 2) + 0x80800000) & 0xffffffff)) #define mp_const_float_e MP_OBJ_NEW_CONST_FLOAT(0x402df854) #define mp_const_float_pi MP_OBJ_NEW_CONST_FLOAT(0x40490fdb) +#define mp_const_float_nan MP_OBJ_NEW_CONST_FLOAT(0x7fc00000) #if MICROPY_PY_MATH_CONSTANTS #define mp_const_float_tau MP_OBJ_NEW_CONST_FLOAT(0x40c90fdb) #define mp_const_float_inf MP_OBJ_NEW_CONST_FLOAT(0x7f800000) -#define mp_const_float_nan MP_OBJ_NEW_CONST_FLOAT(0xffc00000) #endif static inline bool mp_obj_is_float(mp_const_obj_t o) { @@ -207,6 +209,10 @@ static inline mp_float_t mp_obj_float_get(mp_const_obj_t o) { return num.f; } static inline mp_obj_t mp_obj_new_float(mp_float_t f) { + if (isnan(f)) { + // prevent creation of bad nanboxed pointers via array.array or struct + return mp_const_float_nan; + } union { mp_float_t f; mp_uint_t u; @@ -257,12 +263,13 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #error MICROPY_OBJ_REPR_D requires MICROPY_FLOAT_IMPL_DOUBLE #endif +#include #define mp_const_float_e {((mp_obj_t)((uint64_t)0x4005bf0a8b145769 + 0x8004000000000000))} #define mp_const_float_pi {((mp_obj_t)((uint64_t)0x400921fb54442d18 + 0x8004000000000000))} +#define mp_const_float_nan {((mp_obj_t)((uint64_t)0x7ff8000000000000 + 0x8004000000000000))} #if MICROPY_PY_MATH_CONSTANTS #define mp_const_float_tau {((mp_obj_t)((uint64_t)0x401921fb54442d18 + 0x8004000000000000))} #define mp_const_float_inf {((mp_obj_t)((uint64_t)0x7ff0000000000000 + 0x8004000000000000))} -#define mp_const_float_nan {((mp_obj_t)((uint64_t)0xfff8000000000000 + 0x8004000000000000))} #endif static inline bool mp_obj_is_float(mp_const_obj_t o) { @@ -276,6 +283,13 @@ static inline mp_float_t mp_obj_float_get(mp_const_obj_t o) { return num.f; } static inline mp_obj_t mp_obj_new_float(mp_float_t f) { + if (isnan(f)) { + // prevent creation of bad nanboxed pointers via array.array or struct + struct { + uint64_t r; + } num = mp_const_float_nan; + return num.r; + } union { mp_float_t f; uint64_t r; diff --git a/tests/float/float_array.py b/tests/float/float_array.py index 3d128da8381..cfff3b220c6 100644 --- a/tests/float/float_array.py +++ b/tests/float/float_array.py @@ -19,4 +19,10 @@ def test(a): test(array("f")) test(array("d")) -print("{:.4f}".format(array("f", bytes(array("I", [0x3DCCCCCC])))[0])) +# hand-crafted floats, including non-standard nan +for float_hex in (0x3DCCCCCC, 0x7F800024, 0x7FC00004): + f = array("f", bytes(array("I", [float_hex])))[0] + if type(f) is float: + print("{:.4e}".format(f)) + else: + print(f) diff --git a/tests/float/math_constants_extra.py b/tests/float/math_constants_extra.py index dea49aef5a7..756cb458036 100644 --- a/tests/float/math_constants_extra.py +++ b/tests/float/math_constants_extra.py @@ -9,9 +9,12 @@ raise SystemExit print(math.tau == 2.0 * math.pi) +print(math.copysign(1.0, math.tau)) print(math.inf == float("inf")) print(-math.inf == -float("inf")) +print(math.copysign(1.0, math.inf)) print(isnan(math.nan)) print(isnan(-math.nan)) +print(math.copysign(1.0, math.nan)) From 6fee099cae58644ea49ca0f470e9cb00a7da8f29 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 14 Jun 2025 22:22:30 +1000 Subject: [PATCH 0811/2098] py/misc: Fix fallback implementation of mp_popcount. Tested using gcc 7.3.1 which does not have the popcount built-in and uses this fallback version. Without the fix, mpy-cross produces mpy files with corrupt RISC-V machine code. With the fix, mpy-cross output is correct. Signed-off-by: Damien George --- py/misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/misc.h b/py/misc.h index 49f2f871111..1cf582456c0 100644 --- a/py/misc.h +++ b/py/misc.h @@ -390,7 +390,7 @@ static inline uint32_t mp_popcount(uint32_t x) { x = x - ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; - return x * 0x01010101; + return (x * 0x01010101) >> 24; } #endif #endif From b4b7c0a0033b128b488dae99251e7492fbb24008 Mon Sep 17 00:00:00 2001 From: Garatronic Date: Thu, 29 May 2025 09:51:51 +0200 Subject: [PATCH 0812/2098] esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3: Add pybstick26-esp32c3 defn. --- .../GARATRONIC_PYBSTICK26_ESP32C3/board.json | 23 +++++++++++++++++++ .../mpconfigboard.cmake | 7 ++++++ .../mpconfigboard.h | 10 ++++++++ .../GARATRONIC_PYBSTICK26_ESP32C3/pins.csv | 13 +++++++++++ .../sdkconfig.board | 4 ++++ 5 files changed, 57 insertions(+) create mode 100644 ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/board.json create mode 100644 ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake create mode 100644 ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.h create mode 100644 ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/pins.csv create mode 100644 ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/board.json b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/board.json new file mode 100644 index 00000000000..d2ada7cccd9 --- /dev/null +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy_nativeusb.md" + ], + "deploy_options": { + "flash_offset": "0" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "RGB LED", + "WiFi" + ], + "images": [ + "GAR-PYBSTICK26-C3-mUSB-00.JPG" + ], + "mcu": "esp32c3", + "product": "PYBSTICK26_ESP32C3", + "thumbnail": "", + "url": "https://shop.mchobby.be/fr/pybstick/2505-pybstick26-esp32-c3-micropython-et-arduino-3232100025059.html", + "vendor": "McHobby" +} diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake new file mode 100644 index 00000000000..81cfff1d77b --- /dev/null +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(IDF_TARGET esp32c3) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board +) diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.h b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.h new file mode 100644 index 00000000000..c9796d1f659 --- /dev/null +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.h @@ -0,0 +1,10 @@ +#define MICROPY_HW_BOARD_NAME "PYBSTICK26_ESP32C3" +#define MICROPY_HW_MCU_NAME "ESP32C3" +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "pybstick26_esp32c3" + +#define MICROPY_HW_I2C0_SCL (1) +#define MICROPY_HW_I2C0_SDA (0) + +#define MICROPY_HW_SPI1_MOSI (7) +#define MICROPY_HW_SPI1_MISO (2) +#define MICROPY_HW_SPI1_SCK (6) diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/pins.csv b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/pins.csv new file mode 100644 index 00000000000..d7866549037 --- /dev/null +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/pins.csv @@ -0,0 +1,13 @@ +GP0,GPIO0 +GP1,GPIO1 +GP2,GPIO2 +GP3,GPIO3 +GP4,GPIO4 +GP5,GPIO5 +GP6,GPIO6 +GP7,GPIO7 +GP8,GPIO8 +GP9,GPIO9 +GP10,GPIO10 +GP20,GPIO20 +GP21,GPIO21 diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board new file mode 100644 index 00000000000..70e5f1f0d19 --- /dev/null +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board @@ -0,0 +1,4 @@ +CONFIG_ESP32C3_REV_MIN_3=y + +# Workaround for https://github.com/espressif/esp-idf/issues/14456 +CONFIG_ESP_SYSTEM_HW_STACK_GUARD=n From 0815b454796c198eeed90c0bb5645b8d0e3b9b38 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 23 Jun 2025 09:31:18 +0200 Subject: [PATCH 0813/2098] stm32/boards/ARDUINO_PORTENTA_H7: Free reserved timer. This pin is used for the camera clock on Portenta carrier, and vision shield but it doesn't need to be reserved. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 90a14690566..a9ecf38fbfa 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -30,7 +30,6 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_ENABLE_MMCARD (0) #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) -#define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) // ROMFS config #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) From cbe50635e85e7b7966d51fe65626304bd04f4032 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 27 Mar 2025 13:47:31 +0100 Subject: [PATCH 0814/2098] stm32/main: Add support for additional GC blocks. Add support for defining additional GC blocks via linker scripts. A board would need to define `_gc_blocks_table_start` and `_gc_blocks_table_end` and within that region have pairs of (address, length) for each GC block to add. Signed-off-by: iabdalkader --- ports/stm32/main.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index e8395013b91..5e114f562f2 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -521,6 +521,23 @@ void stm32_main(uint32_t reset_mode) { // GC init gc_init(MICROPY_HEAP_START, MICROPY_HEAP_END); + // Add additional GC blocks (if enabled). + #if MICROPY_GC_SPLIT_HEAP + typedef struct { + uint8_t *addr; + uint32_t size; + } gc_blocks_table_t; + + extern const gc_blocks_table_t _gc_blocks_table_start; + extern const gc_blocks_table_t _gc_blocks_table_end; + + for (gc_blocks_table_t const *block = &_gc_blocks_table_start; block < &_gc_blocks_table_end; block++) { + if (block->size) { + gc_add(block->addr, block->addr + block->size); + } + } + #endif + #if MICROPY_ENABLE_PYSTACK static mp_obj_t pystack[384]; mp_pystack_init(pystack, &pystack[384]); From 5c6da117995ef7f9a7396c4048d3e34f56d73ba4 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 27 Mar 2025 13:48:42 +0100 Subject: [PATCH 0815/2098] stm32/boards/ARDUINO_GIGA: Define additional GC blocks in SDRAM. Signed-off-by: iabdalkader --- .../stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 1 + ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index cef45d730cc..44f6ce66bc4 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -31,6 +31,7 @@ typedef unsigned int mp_uint_t; // must be pointer size #define MICROPY_HW_ENABLE_MMCARD (0) #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) #define MICROPY_HW_TIM_IS_RESERVED(id) (id == 1) +#define MICROPY_GC_SPLIT_HEAP (1) // ROMFS config #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) diff --git a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld index e7bb950dbe6..dceb1a74899 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld @@ -12,6 +12,7 @@ MEMORY SRAM2 (xrw) : ORIGIN = 0x30020000, LENGTH = 128K /* SRAM2 D2 */ SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3 D2 */ SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ + SDRAM (xrw) : ORIGIN = 0x60000000, LENGTH = 8M /* SDRAM */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ @@ -53,3 +54,27 @@ _openamp_shm_region_start = ORIGIN(SRAM4); _openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); INCLUDE common_blifs.ld + +SECTIONS +{ + /* GC blocks addresses and sizes */ + .gc.blocks.table (READONLY) : { + . = ALIGN(4); + _gc_blocks_table_start = .; + + LONG (ORIGIN(SRAM1)); + LONG (128K); + + LONG (ORIGIN(SDRAM) + 0M); + LONG (2M); + + LONG (ORIGIN(SDRAM) + 2M); + LONG (2M); + + LONG (ORIGIN(SDRAM) + 4M); + LONG (4M); + + _gc_blocks_table_end = .; + . = ALIGN(4); + } > FLASH_TEXT +} From 2a46759fe83a5295f7c06e2dab37c3f85848800e Mon Sep 17 00:00:00 2001 From: ennyKey Date: Wed, 4 Dec 2024 22:33:17 +0100 Subject: [PATCH 0816/2098] stm32/uart: Enable UART FIFO for H7 MCUs. The H7 has a hardware UART FIFO, so it's worth enabling it, to reduce the chance of missed incoming characters. Note that `HAL_UART_Init(&huart)` does not activate the FIFO, it must be done explicitly by calling `HAL_UARTEx_EnableFifoMode(&huart)`. Signed-off-by: ennyKey --- ports/stm32/Makefile | 6 ++++++ ports/stm32/boards/stm32h7xx_hal_conf_base.h | 1 + ports/stm32/uart.c | 11 ++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 8ac9a8af03d..eabbd64a3b1 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -417,6 +417,12 @@ HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ ) endif +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),h7)) +HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ + hal_uart_ex.c \ + ) +endif + USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\ core/src/usbd_core.c \ core/src/usbd_ctlreq.c \ diff --git a/ports/stm32/boards/stm32h7xx_hal_conf_base.h b/ports/stm32/boards/stm32h7xx_hal_conf_base.h index 670dee383f8..1953ba020b8 100644 --- a/ports/stm32/boards/stm32h7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32h7xx_hal_conf_base.h @@ -90,6 +90,7 @@ #include "stm32h7xx_hal_spi.h" #include "stm32h7xx_hal_tim.h" #include "stm32h7xx_hal_uart.h" +#include "stm32h7xx_hal_uart_ex.h" #include "stm32h7xx_hal_usart.h" #include "stm32h7xx_hal_wwdg.h" #include "stm32h7xx_ll_adc.h" diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 91db91395ea..55fa6221422 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -653,7 +653,7 @@ bool uart_init(machine_uart_obj_t *uart_obj, huart.Init.HwFlowCtl = flow; huart.Init.OverSampling = UART_OVERSAMPLING_16; - #if defined(STM32G4) // H7 and WB also have fifo.. + #if defined(STM32G4) || defined(STM32H7) // WB also has a fifo.. huart.FifoMode = UART_FIFOMODE_ENABLE; #endif @@ -701,6 +701,12 @@ bool uart_init(machine_uart_obj_t *uart_obj, uart_obj->char_width = CHAR_WIDTH_8BIT; } + #if defined(STM32H7) + HAL_UARTEx_SetTxFifoThreshold(&huart, UART_TXFIFO_THRESHOLD_1_8); + HAL_UARTEx_SetRxFifoThreshold(&huart, UART_RXFIFO_THRESHOLD_1_8); + HAL_UARTEx_EnableFifoMode(&huart); + #endif + uart_obj->mp_irq_trigger = 0; uart_obj->mp_irq_obj = NULL; @@ -1141,6 +1147,9 @@ size_t uart_tx_data(machine_uart_obj_t *self, const void *src_in, size_t num_cha // timeout_char by FIFO size + 1. // STM32G4 has 8 words FIFO. timeout = (8 + 1) * self->timeout_char; + #elif defined(STM32H7) + // STM32H7 has 16 words FIFO. + timeout = (16 + 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 From 2f04381aeb18b11a9973b8bf461bdb5cfd46edc5 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 21 May 2025 11:16:54 +1000 Subject: [PATCH 0817/2098] extmod/modlwip: Fix crash when calling recv on listening socket. Add check to prevent calling recv on a socket in the listening state. This prevents a crash/hard fault when user code mistakenly tries to recv on the listening socket instead of on the accepted connection. Add corresponding test case to demonstrate the bug. Signed-off-by: Andrew Leech --- extmod/modlwip.c | 6 +++ tests/multi_net/tcp_accept_recv.py | 73 ++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 65a3412ecb8..b53559ed8cf 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -825,6 +825,12 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ // Check for any pending errors STREAM_ERROR_CHECK(socket); + if (socket->state == STATE_LISTENING) { + // original socket in listening state, not the accepted connection. + *_errno = MP_ENOTCONN; + return -1; + } + if (socket->incoming.tcp.pbuf == NULL) { // Non-blocking socket or flag diff --git a/tests/multi_net/tcp_accept_recv.py b/tests/multi_net/tcp_accept_recv.py index dee14e3b977..4108a6f8a3e 100644 --- a/tests/multi_net/tcp_accept_recv.py +++ b/tests/multi_net/tcp_accept_recv.py @@ -1,30 +1,73 @@ -# Test recv on socket that just accepted a connection +# Test recv on listening socket after accept(), with various listen() arguments import socket PORT = 8000 +# Test cases for listen() function +LISTEN_ARGS = [None, 0, 1, 2] # None means no argument + # Server def instance0(): multitest.globals(IP=multitest.get_network_ip()) - s = socket.socket() - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) - s.listen(1) multitest.next() - s.accept() - try: - print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN - except OSError as er: - print(er.errno in (107, 128)) - s.close() + + test_num = 0 + for blocking_mode in [True, False]: + for listen_arg in LISTEN_ARGS: + test_num += 1 + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + + # Call listen with or without argument based on test case + if listen_arg is None: + print(f"Test case {test_num}/8: listen() blocking={blocking_mode}") + s.listen() + else: + print(f"Test case {test_num}/8: listen({listen_arg}) blocking={blocking_mode}") + s.listen(listen_arg) + + # Signal client that server is ready + multitest.broadcast(f"server_ready_{test_num}") + + # Wait for client connection + c, _ = s.accept() + + # Set blocking mode after accept + s.setblocking(blocking_mode) + + try: + print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN + except OSError as er: + # Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows) + print(er.errno in (107, 128)) + + # Cleanup + c.close() + s.close() + + # Signal client we're done with this test case + multitest.broadcast(f"server_done_{test_num}") # Client def instance1(): multitest.next() - s = socket.socket() - s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) - s.send(b"GET / HTTP/1.0\r\n\r\n") - s.close() + + test_num = 0 + for blocking_mode in [True, False]: + for _ in LISTEN_ARGS: + test_num += 1 + # Wait for server to be ready + multitest.wait(f"server_ready_{test_num}") + + # Connect to server + s = socket.socket() + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"GET / HTTP/1.0\r\n\r\n") + s.close() + + # Wait for server to finish this test case + multitest.wait(f"server_done_{test_num}") From 74a4cce6d1697fce89e7dbd0e846b68b96e534c0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 14 Jun 2025 00:33:08 +1000 Subject: [PATCH 0818/2098] alif/Makefile: Create firmware.zip with files needed for deploying. The resulting `firmware.zip` file is self contained with everything needed to deploy the firmware, eg over SE UART. Signed-off-by: Damien George --- ports/alif/Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 68706674365..07f87f7f803 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -21,6 +21,7 @@ Reset\n\ Exit ALIF_TOC_CONFIG = alif_cfg.json +ALIF_TOC_BIN = $(BUILD)/firmware.toc.bin ALIF_TOC_APPS = $(BUILD)/$(ALIF_TOC_CONFIG) ALIF_TOC_CFLAGS += -DTOC_CFG_FILE=$(ALIF_TOOLKIT_CFG_FILE) @@ -69,7 +70,7 @@ include $(TOP)/extmod/extmod.mk # Main targets .PHONY: all -all: $(BUILD)/firmware.toc.bin +all: $(BUILD)/firmware.zip # Force make commands to run the targets every time # regardless of whether firmware.toc.bin already exists @@ -90,15 +91,19 @@ $(BUILD)/$(ALIF_TOC_CONFIG): mcu/$(ALIF_TOC_CONFIG).in | $(BUILD) $(ECHO) "Preprocess toc config $@" $(Q)$(CPP) -P -E $(ALIF_TOC_CFLAGS) - < mcu/$(ALIF_TOC_CONFIG).in > $@ -$(BUILD)/firmware.toc.bin: $(ALIF_TOC_APPS) +$(ALIF_TOC_BIN): $(ALIF_TOC_APPS) $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ --filename $(abspath $(BUILD)/$(ALIF_TOC_CONFIG)) \ --output-dir $(BUILD) \ --firmware-dir $(BUILD) \ --output $@ +$(BUILD)/firmware.zip: $(ALIF_TOC_BIN) $(ALIF_TOC_APPS) + $(ECHO) "Create $@" + $(Q)$(ZIP) -q - $(BUILD)/application_package.ds $^ > $@ + .PHONY: deploy -deploy: $(BUILD)/firmware.toc.bin +deploy: $(ALIF_TOC_BIN) $(ECHO) "Writing $< to the board" $(Q)python $(ALIF_TOOLS)/app-write-mram.py \ --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ From 77c454829f075f0234ff6495b00e04ac3589c647 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 14 Jun 2025 00:33:30 +1000 Subject: [PATCH 0819/2098] tools/autobuild: Build alif boards as part of auto-build. Signed-off-by: Damien George --- tools/autobuild/autobuild.sh | 4 +++- tools/autobuild/build-boards.sh | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/autobuild/autobuild.sh b/tools/autobuild/autobuild.sh index 7854945bbee..7073152df79 100755 --- a/tools/autobuild/autobuild.sh +++ b/tools/autobuild/autobuild.sh @@ -69,7 +69,9 @@ fi FW_TAG="-$FW_DATE-$FW_SEMVER" # build new firmware -cd ports/cc3200 +cd ports/alif +build_alif_boards ${FW_TAG} ${LOCAL_FIRMWARE} +cd ../cc3200 build_cc3200_boards ${FW_TAG} ${LOCAL_FIRMWARE} cd ../esp8266 build_esp8266_boards ${FW_TAG} ${LOCAL_FIRMWARE} diff --git a/tools/autobuild/build-boards.sh b/tools/autobuild/build-boards.sh index bd6828cb40f..165743cab56 100755 --- a/tools/autobuild/build-boards.sh +++ b/tools/autobuild/build-boards.sh @@ -86,6 +86,10 @@ function build_boards { done } +function build_alif_boards { + build_boards modalif.c $1 $2 zip +} + function build_cc3200_boards { build_boards hal/cc3200_hal.c $1 $2 zip } From 8cd8e146a4da6dc689b5db876e56692f547fff38 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 5 Jun 2025 09:53:03 +0200 Subject: [PATCH 0820/2098] alif/boards/ALIF_ENSEMBLE: Add board.json and deploy instructions. The firmware for this board will now be built and available automatically. Signed-off-by: iabdalkader --- ports/alif/boards/ALIF_ENSEMBLE/board.json | 17 ++++++++++ ports/alif/boards/ALIF_ENSEMBLE/deploy.md | 37 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/board.json create mode 100644 ports/alif/boards/ALIF_ENSEMBLE/deploy.md diff --git a/ports/alif/boards/ALIF_ENSEMBLE/board.json b/ports/alif/boards/ALIF_ENSEMBLE/board.json new file mode 100644 index 00000000000..0c63494f7ac --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/board.json @@ -0,0 +1,17 @@ +{ + "deploy": [ + "./deploy.md" + ], + "docs": "", + "features": [ + "Ethernet" + ], + "images": [ + "ensemble-devkit-gen-2.jpg" + ], + "mcu": "AE722F80F55D5XX", + "product": "Ensemble E7 DevKit", + "thumbnail": "", + "url": "https://alifsemi.com/support/kits/ensemble-devkit/", + "vendor": "Alif Semiconductor" +} diff --git a/ports/alif/boards/ALIF_ENSEMBLE/deploy.md b/ports/alif/boards/ALIF_ENSEMBLE/deploy.md new file mode 100644 index 00000000000..acbc85617e1 --- /dev/null +++ b/ports/alif/boards/ALIF_ENSEMBLE/deploy.md @@ -0,0 +1,37 @@ +### Alif Security Toolkit + +This board can be programmed via the SE UART interface using the Alif Security +Toolkit. MicroPython uses a custom version of the toolkit, which can be downloaded +from [here](https://github.com/micropython/alif-security-toolkit/) or cloned: + +```bash +git clone https://github.com/micropython/alif-security-toolkit/ +``` + +--- + +### Update the SE Firmware (Optional) + +If needed, update the SE firmware to match the version used by MicroPython. Ensure +you have the correct port, part number, and chip revision. + +```bash +python alif-security-toolkit/toolkit/updateSystemPackage.py --port /dev/ttyACM0 --cfg-part AE722F80F55D5LS --cfg-rev B4 +``` + +**Note:** The board must be power-cycled after this step. + +--- + +### Deploy MicroPython + +Download (or build) the firmware package, unzip it, then deploy it: + +```bash + +python alif-security-toolkit/toolkit/app-write-mram.py --port /dev/ttyACM0 --cfg-part AE722F80F55D5LS --cfg-rev B4 --pad --images file:build-ALIF_ENSEMBLE/application_package.ds +``` + +The MicroPython REPL is available on the second USB serial port, eg `/dev/ttyACM1`. + +--- From 9b38cf9f82257a9c58b76b151475f41b431f374e Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 10 Jun 2025 10:46:09 +0200 Subject: [PATCH 0821/2098] alif/README: Update README with build instructions. Signed-off-by: iabdalkader --- ports/alif/README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/ports/alif/README.md b/ports/alif/README.md index 824a63da186..dcf327060ff 100644 --- a/ports/alif/README.md +++ b/ports/alif/README.md @@ -19,3 +19,61 @@ The following more advanced features will follow later: - Ethernet support. - SDRAM support. - Other machine modules. + +Build instructions +------------------ + +Before building the firmware for a given board the MicroPython cross-compiler +must be built; it will be used to pre-compile some of the built-in scripts to +bytecode. The cross-compiler is built and run on the host machine, using: +```bash +$ make -C mpy-cross +``` + +This command should be executed from the root directory of this repository. +All other commands below should be executed from the ports/alif/ directory. + +An ARM compiler is required for the build, along with the associated binary +utilities. The recommended toolchain version to use with this port is +Arm GNU toolchain version 13.3.Rel1. The compiler can be changed using the +`CROSS_COMPILE` variable when invoking `make`. + +Next, the board to build must be selected. The default board is `ALIF_ENSEMBLE` +but any of the names of the subdirectories in the `boards/` directory is valid. +The board name must be passed as the argument to `BOARD=` when invoking `make`. + +All boards require certain submodules to be obtained before they can be built. +The correct set of submodules can be initialised using (with `ALIF_ENSEMBLE` +as an example of the selected board): +```bash +make BOARD=ALIF_ENSEMBLE submodules +``` + +Then to build the board's firmware run: +```bash +make BOARD=ALIF_ENSEMBLE +``` + +The above command should produce binary images in the `build-ALIF_ENSEMBLE/` +subdirectory (or the equivalent directory for the board specified). + +### Update the SE Firmware + +The SE firmware must be updated **before** flashing the main firmware to match +the version used by MicroPython. This step only needs to be performed once. +Connect the board to your PC via the **SE UART USB** port (on the Ensemble kit, +this is labeled **PRG USB**), then run: + +```bash +make update-system-package +``` + +**Note:** The board must be power-cycled after this step. + +### Deploy MicroPython + +To flash the firmware, run: + +```bash +make BOARD=ALIF_ENSEMBLE deploy +``` From 4bd99260dc31ab5dbc8dcfb64a6255cd50eb4de7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 Jun 2025 14:22:13 +1000 Subject: [PATCH 0822/2098] alif/Makefile: Allow specifying a custom build directory. Signed-off-by: Damien George --- ports/alif/Makefile | 4 ++-- ports/alif/alif.mk | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 07f87f7f803..d258b27b1d5 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -82,10 +82,10 @@ $(BUILD): $(MKDIR) -p $@ $(BUILD)/M55_HP/firmware.bin: - make -f alif.mk MCU_CORE=M55_HP MICROPY_PY_OPENAMP_MODE=0 + make -f alif.mk BUILD=$(BUILD)/M55_HP MCU_CORE=M55_HP MICROPY_PY_OPENAMP_MODE=0 $(BUILD)/M55_HE/firmware.bin: - make -f alif.mk MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 + make -f alif.mk BUILD=$(BUILD)/M55_HE MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 $(BUILD)/$(ALIF_TOC_CONFIG): mcu/$(ALIF_TOC_CONFIG).in | $(BUILD) $(ECHO) "Preprocess toc config $@" diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index bb07a3aa202..265418aa074 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -3,7 +3,6 @@ BOARD ?= ALIF_ENSEMBLE BOARD_DIR ?= boards/$(BOARD) -BUILD ?= build-$(BOARD)/$(MCU_CORE) ifeq ($(wildcard $(BOARD_DIR)/.),) $(error Invalid BOARD specified: $(BOARD_DIR)) From 9a5cf0e3dbb7dbc1b66ef67dd2fec0245d24e37e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 16 Jun 2025 09:35:31 +0200 Subject: [PATCH 0823/2098] github/workflows: Run the address sanitizer (ASan) build during CI. Signed-off-by: Jeff Epler --- .github/workflows/ports_unix.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 662121654e4..c5223a71e6c 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -263,6 +263,26 @@ jobs: if: failure() run: tests/run-tests.py --print-failures + sanitize_address: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_unix_coverage_setup + - name: Build + run: source tools/ci.sh && ci_unix_sanitize_address_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_sanitize_address_run_tests + - name: Test merging .mpy files + run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests + - name: Build native mpy modules + run: source tools/ci.sh && ci_native_mpy_modules_build + - name: Test importing .mpy generated by mpy_ld.py + run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures + sanitize_undefined: runs-on: ubuntu-latest steps: From 07c3bf21f22edf83fb3ced3d5e6c7095c4b4d7f4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 16 Jun 2025 17:04:16 +0200 Subject: [PATCH 0824/2098] tools/ci.sh: Disable "stack use after return" in ASan build. This check, runtime-enabled by default in gcc 13 (and existing at least since gcc 12, but runtime-disabled) changes the stack layout in ways that are not compatible with assumptions spread across the core code (nlr, gc, and stack checking). Signed-off-by: Jeff Epler --- tools/ci.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index b0e59509add..82543362584 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -514,8 +514,8 @@ CI_UNIX_OPTS_QEMU_RISCV64=( CI_UNIX_OPTS_SANITIZE_ADDRESS=( VARIANT=coverage - CFLAGS_EXTRA="-fsanitize=address" - LDFLAGS_EXTRA="-fsanitize=address" + CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" + LDFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" ) CI_UNIX_OPTS_SANITIZE_UNDEFINED=( From 1eb27e11f3bf8e5e0ee13191ef1e9c30b00f9ead Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 16 Jun 2025 17:04:58 +0200 Subject: [PATCH 0825/2098] unix/README: Add some small documentation about sanitizers. Signed-off-by: Jeff Epler --- ports/unix/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ports/unix/README.md b/ports/unix/README.md index b7aa6e3fef7..656d4303d38 100644 --- a/ports/unix/README.md +++ b/ports/unix/README.md @@ -155,3 +155,21 @@ The default compiler optimisation level is -Os, or -Og if `DEBUG=1` is set. Setting the variable `COPT` will explicitly set the optimisation level. For example `make [other arguments] COPT=-O0 DEBUG=1` will build a binary with no optimisations, assertions enabled, and debug symbols. + +### Sanitizers + +Sanitizers are extra runtime checks supported by gcc and clang. The CI process +supports building with the "undefined behavior" (UBSan) or "address" (ASan) +sanitizers. The script `tools/ci.sh` is the source of truth about how to build +and run in these modes. + +Several classes of checks are disabled via compiler flags: + +* In the undefined behavior sanitizer, checks based on the presence of the + `non_null` attribute are disabled because the code makes technically incorrect + calls like `memset(NULL, 0, 0)`. A future C standard is likely to permit such + calls. +* In the address sanitizer, `detect_stack_use_after_return` is disabled. This + check is intended to make sure locals in a "returned from" stack frame are not + used. However, this mode interferes with various assumptions that + MicroPython's stack checking, NLR, and GC rely on. From 703d5acbadd0234aa9c8cbb9aaba7a42aca99fc4 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 07:01:17 +0200 Subject: [PATCH 0826/2098] py/misc: Introduce macros to check values' bits size. This commit adds two macros that lets check whether a given value can fit an arbitrary number of bits, either as a signed or as an unsigned number. The native emitter code backends perform a lot of bit size checks to see if a particular code sequence can be emitted instead of a generic one, and each platform backend has its own ad-hoc macros (usually one per bit count and signedness). With these macros there's a single way to perform those checks, plus there's no more chance for off-by-one mask length errors when dealing with signed numbers. Signed-off-by: Alessandro Gatti --- py/misc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/py/misc.h b/py/misc.h index 1cf582456c0..5d0893bbdd3 100644 --- a/py/misc.h +++ b/py/misc.h @@ -395,6 +395,11 @@ static inline uint32_t mp_popcount(uint32_t x) { #endif #endif +#define MP_FIT_UNSIGNED(bits, value) (((value) & (~0U << (bits))) == 0) +#define MP_FIT_SIGNED(bits, value) \ + (MP_FIT_UNSIGNED(((bits) - 1), (value)) || \ + (((value) & (~0U << ((bits) - 1))) == (~0U << ((bits) - 1)))) + // mp_int_t can be larger than long, i.e. Windows 64-bit, nan-box variants static inline uint32_t mp_clz_mpi(mp_int_t x) { #ifdef __XC16__ From a8e036982677077e27ab3bdfb0792a8de14f8b77 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 17:58:34 +0200 Subject: [PATCH 0827/2098] py/asmrv32: Implement the full set of Viper load/store operations. This commit expands the implementation of Viper load/store operations that are optimised for the RV32 platform. Given the way opcodes are encoded, all value sizes are implemented with only two functions - one for loads and one for stores. This should reduce duplication with existing operations and should, in theory, save space as some code is removed. Both load and store emitters will generate the shortest possible sequence (as long as the stack pointer is not involved), using compressed opcodes when possible. Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 73 ++++++++++++++++++++++--------------------------- py/asmrv32.h | 32 +++++++++++++++------- py/emitnative.c | 27 ++---------------- 3 files changed, 58 insertions(+), 74 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index c24d05a1384..158b5521917 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -450,18 +450,24 @@ void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t asm_rv32_opcode_cadd(state, rd, ASM_RV32_REG_SP); } -void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { - mp_int_t scaled_offset = offset * sizeof(ASM_WORD_SIZE); +static const uint8_t RV32_LOAD_OPCODE_TABLE[3] = { + 0x04, 0x05, 0x02 +}; - if (scaled_offset >= 0 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs) && FIT_UNSIGNED(scaled_offset, 6)) { +void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, int32_t offset, mp_uint_t operation_size) { + assert(operation_size <= 2 && "Operation size value out of range."); + + int32_t scaled_offset = offset << operation_size; + + if (scaled_offset >= 0 && operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs) && MP_FIT_UNSIGNED(6, scaled_offset)) { // c.lw rd', offset(rs') asm_rv32_opcode_clw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs), scaled_offset); return; } - if (FIT_SIGNED(scaled_offset, 12)) { - // lw rd, offset(rs) - asm_rv32_opcode_lw(state, rd, rs, scaled_offset); + if (MP_FIT_SIGNED(12, scaled_offset)) { + // lbu|lhu|lw rd, offset(rs) + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rs, scaled_offset)); return; } @@ -469,12 +475,12 @@ void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_ mp_uint_t lower = 0; split_immediate(scaled_offset, &upper, &lower); - // lui rd, HI(offset) ; Or c.lui if possible - // c.add rd, rs - // lw rd, LO(offset)(rd) + // lui rd, HI(offset) ; Or c.lui if possible + // c.add rd, rs + // lbu|lhu|lw rd, LO(offset)(rd) load_upper_immediate(state, rd, upper); asm_rv32_opcode_cadd(state, rd, rs); - asm_rv32_opcode_lw(state, rd, rd, lower); + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rd, lower)); } void asm_rv32_emit_jump(asm_rv32_t *state, mp_uint_t label) { @@ -497,12 +503,20 @@ void asm_rv32_emit_jump(asm_rv32_t *state, mp_uint_t label) { asm_rv32_opcode_jalr(state, ASM_RV32_REG_ZERO, REG_TEMP2, lower); } -void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { - mp_int_t scaled_offset = offset * ASM_WORD_SIZE; +void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, int32_t offset, mp_uint_t operation_size) { + assert(operation_size <= 2 && "Operation size value out of range."); + + int32_t scaled_offset = offset << operation_size; - if (FIT_SIGNED(scaled_offset, 12)) { - // sw rd, offset(rs) - asm_rv32_opcode_sw(state, rd, rs, scaled_offset); + if (scaled_offset >= 0 && operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs) && MP_FIT_UNSIGNED(6, scaled_offset)) { + // c.sw rd', offset(rs') + asm_rv32_opcode_csw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs), scaled_offset); + return; + } + + if (MP_FIT_SIGNED(12, scaled_offset)) { + // sb|sh|sw rd, offset(rs) + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_S(0x23, operation_size, rs, rd, scaled_offset)); return; } @@ -510,12 +524,12 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint mp_uint_t lower = 0; split_immediate(scaled_offset, &upper, &lower); - // lui temporary, HI(offset) ; Or c.lui if possible - // c.add temporary, rs - // sw rd, LO(offset)(temporary) + // lui temporary, HI(offset) ; Or c.lui if possible + // c.add temporary, rs + // sb|sh|sw rd, LO(offset)(temporary) load_upper_immediate(state, REG_TEMP2, upper); asm_rv32_opcode_cadd(state, REG_TEMP2, rs); - asm_rv32_opcode_sw(state, rd, REG_TEMP2, lower); + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_S(0x23, operation_size, REG_TEMP2, rd, lower)); } void asm_rv32_emit_mov_reg_pcrel(asm_rv32_t *state, mp_uint_t rd, mp_uint_t label) { @@ -530,27 +544,6 @@ void asm_rv32_emit_mov_reg_pcrel(asm_rv32_t *state, mp_uint_t rd, mp_uint_t labe asm_rv32_opcode_addi(state, rd, rd, lower); } -void asm_rv32_emit_load16_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset) { - mp_int_t scaled_offset = offset * sizeof(uint16_t); - - if (FIT_SIGNED(scaled_offset, 12)) { - // lhu rd, offset(rs) - asm_rv32_opcode_lhu(state, rd, rs, scaled_offset); - return; - } - - mp_uint_t upper = 0; - mp_uint_t lower = 0; - split_immediate(scaled_offset, &upper, &lower); - - // lui rd, HI(offset) ; Or c.lui if possible - // c.add rd, rs - // lhu rd, LO(offset)(rd) - load_upper_immediate(state, rd, upper); - asm_rv32_opcode_cadd(state, rd, rs); - asm_rv32_opcode_lhu(state, rd, rd, lower); -} - void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) { if (rs == rd) { // c.li rd, 0 diff --git a/py/asmrv32.h b/py/asmrv32.h index 4f986d7bbd5..99c2226ef33 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -709,14 +709,13 @@ void asm_rv32_emit_call_ind(asm_rv32_t *state, mp_uint_t index); void asm_rv32_emit_jump(asm_rv32_t *state, mp_uint_t label); void asm_rv32_emit_jump_if_reg_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t label); void asm_rv32_emit_jump_if_reg_nonzero(asm_rv32_t *state, mp_uint_t rs, mp_uint_t label); -void asm_rv32_emit_load16_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset); -void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t offset); +void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, int32_t offset, mp_uint_t operation_size); void asm_rv32_emit_mov_local_reg(asm_rv32_t *state, mp_uint_t local, mp_uint_t rs); void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local); void asm_rv32_emit_mov_reg_local(asm_rv32_t *state, mp_uint_t rd, mp_uint_t local); void asm_rv32_emit_mov_reg_pcrel(asm_rv32_t *state, mp_uint_t rd, mp_uint_t label); void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs); -void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_uint_t base, mp_int_t offset); +void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_uint_t base, int32_t offset, mp_uint_t operation_size); #define ASM_T asm_rv32_t #define ASM_ENTRY(state, labels) asm_rv32_entry(state, labels) @@ -733,11 +732,12 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_JUMP_IF_REG_ZERO(state, rs, label, bool_test) asm_rv32_emit_jump_if_reg_eq(state, rs, ASM_RV32_REG_ZERO, label) #define ASM_JUMP_REG(state, rs) asm_rv32_opcode_cjr(state, rs) #define ASM_LOAD_REG_REG_OFFSET(state, rd, rs, offset) ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, offset) -#define ASM_LOAD16_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load16_reg_reg_offset(state, rd, rs, offset) -#define ASM_LOAD16_REG_REG(state, rd, rs) asm_rv32_opcode_lhu(state, rd, rs, 0) +#define ASM_LOAD8_REG_REG(state, rd, rs) ASM_LOAD8_REG_REG_OFFSET(state, rd, rs, 0) +#define ASM_LOAD16_REG_REG(state, rd, rs) ASM_LOAD16_REG_REG_OFFSET(state, rd, rs, 0) #define ASM_LOAD32_REG_REG(state, rd, rs) ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, 0) -#define ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset) -#define ASM_LOAD8_REG_REG(state, rd, rs) asm_rv32_opcode_lbu(state, rd, rs, 0) +#define ASM_LOAD8_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset, 0) +#define ASM_LOAD16_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset, 1) +#define ASM_LOAD32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset, 2) #define ASM_LSL_REG_REG(state, rd, rs) asm_rv32_opcode_sll(state, rd, rd, rs) #define ASM_LSR_REG_REG(state, rd, rs) asm_rv32_opcode_srl(state, rd, rd, rs) #define ASM_MOV_LOCAL_REG(state, local, rs) asm_rv32_emit_mov_local_reg(state, local, rs) @@ -751,13 +751,20 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_NOT_REG(state, rd) asm_rv32_opcode_xori(state, rd, rd, -1) #define ASM_OR_REG_REG(state, rd, rs) asm_rv32_opcode_or(state, rd, rd, rs) #define ASM_STORE_REG_REG_OFFSET(state, rd, rs, offset) ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) -#define ASM_STORE16_REG_REG(state, rs1, rs2) asm_rv32_opcode_sh(state, rs1, rs2, 0) +#define ASM_STORE8_REG_REG(state, rs1, rs2) ASM_STORE8_REG_REG_OFFSET(state, rs1, rs2, 0) +#define ASM_STORE16_REG_REG(state, rs1, rs2) ASM_STORE16_REG_REG_OFFSET(state, rs1, rs2, 0) #define ASM_STORE32_REG_REG(state, rs1, rs2) ASM_STORE32_REG_REG_OFFSET(state, rs1, rs2, 0) -#define ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset) -#define ASM_STORE8_REG_REG(state, rs1, rs2) asm_rv32_opcode_sb(state, rs1, rs2, 0) +#define ASM_STORE8_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset, 0) +#define ASM_STORE16_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset, 1) +#define ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset, 2) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) +#define ASM_LOAD8_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_lbu(state, rd, rs1, 0); \ + } while (0) #define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) \ do { \ asm_rv32_opcode_slli(state, rs2, rs2, 1); \ @@ -770,6 +777,11 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ asm_rv32_opcode_cadd(state, rs1, rs2); \ asm_rv32_opcode_lw(state, rd, rs1, 0); \ } while (0) +#define ASM_STORE8_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_sb(state, rd, rs1, 0); \ + } while (0) #define ASM_STORE16_REG_REG_REG(state, rd, rs1, rs2) \ do { \ asm_rv32_opcode_slli(state, rs2, rs2, 1); \ diff --git a/py/emitnative.c b/py/emitnative.c index 7662de69e22..2cce31dae98 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -180,12 +180,6 @@ static const uint8_t reg_local_table[MAX_REGS_FOR_LOCAL_VARS] = {REG_LOCAL_1, RE *emit->error_slot = mp_obj_new_exception_msg_varg(&mp_type_ViperTypeError, __VA_ARGS__); \ } while (0) -#if N_RV32 -#define FIT_SIGNED(value, bits) \ - ((((value) & ~((1U << ((bits) - 1)) - 1)) == 0) || \ - (((value) & ~((1U << ((bits) - 1)) - 1)) == ~((1U << ((bits) - 1)) - 1))) -#endif - typedef enum { STACK_VALUE, STACK_REG, @@ -1540,12 +1534,7 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD8_REG_REG_OFFSET ASM_LOAD8_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN + #if N_XTENSA || N_XTENSAWIN if (index_value >= 0 && index_value < 256) { asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); break; @@ -1787,12 +1776,7 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE8_REG_REG_OFFSET ASM_STORE8_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN + #if N_XTENSA || N_XTENSAWIN if (index_value >= 0 && index_value < 256) { asm_xtensa_op_s8i(emit->as, reg_value, reg_base, index_value); break; @@ -1817,12 +1801,7 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE16_REG_REG_OFFSET ASM_STORE16_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); - break; - } - #elif N_XTENSA || N_XTENSAWIN + #if N_XTENSA || N_XTENSAWIN if (index_value >= 0 && index_value < 256) { asm_xtensa_op_s16i(emit->as, reg_value, reg_base, index_value); break; From cd1b921bf296da72cee4f6135ad8bd74e6217d2f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 19:24:10 +0200 Subject: [PATCH 0828/2098] py/asmarm: Implement the full set of Viper load/store operations. This commit expands the implementation of Viper load/store operations that are optimised for the Arm platform. Now both load and store emitters should generate the shortest possible sequence in all cases. Redundant specialised operation emitters have been folded into the general case implementation - this was the case of integer-indexed load/store operations with a fixed offset of zero. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 52 +++++++++++++++++++++++++++++++------------------ py/asmarm.h | 24 ++++++++++++----------- py/emitnative.c | 4 ---- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index be50a991b7d..15bc73b61ec 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -38,8 +38,6 @@ #define REG_TEMP ASM_ARM_REG_R8 -#define SIGNED_FIT24(x) (((x) & 0xff800000) == 0) || (((x) & 0xff000000) == 0xff000000) - // Insert word into instruction flow static void emit(asm_arm_t *as, uint op) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 4); @@ -347,11 +345,6 @@ void asm_arm_ldr_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offse } } -void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { - // ldrh rd, [rn] - emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12)); -} - void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // ldrh doesn't support scaled register index emit_al(as, 0x1a00080 | (REG_TEMP << 12) | rn); // mov temp, rn, lsl #1 @@ -370,16 +363,23 @@ void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offs } } -void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { - // ldrb rd, [rn] - emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12)); -} - void asm_arm_ldrb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // ldrb rd, [rm, rn] emit_al(as, 0x7d00000 | (rm << 16) | (rd << 12) | rn); } +void asm_arm_ldrb_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { + if (byte_offset < 0x1000) { + // ldrb rd, [rn, #off] + emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12) | byte_offset); + } else { + // mov temp, #off + // ldrb rd, [rn, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x7d00000 | (rn << 16) | (rd << 12) | REG_TEMP); + } +} + void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { // ldr rd, [rm, rn, lsl #2] emit_al(as, 0x7900100 | (rm << 16) | (rd << 12) | rn); @@ -397,14 +397,28 @@ void asm_arm_str_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offse } } -void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm) { - // strh rd, [rm] - emit_al(as, 0x1c000b0 | (rm << 16) | (rd << 12)); +void asm_arm_strh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { + if (byte_offset < 0x100) { + // strh rd, [rn, #off] + emit_al(as, 0x1c000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); + } else { + // mov temp, #off + // strh rd, [rn, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x18000b0 | (rn << 16) | (rd << 12) | REG_TEMP); + } } -void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm) { - // strb rd, [rm] - emit_al(as, 0x5c00000 | (rm << 16) | (rd << 12)); +void asm_arm_strb_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { + if (byte_offset < 0x1000) { + // strb rd, [rm, #off] + emit_al(as, 0x5c00000 | (rm << 16) | (rd << 12) | byte_offset); + } else { + // mov temp, #off + // strb rd, [rm, temp] + asm_arm_mov_reg_i32_optimised(as, REG_TEMP, byte_offset); + emit_al(as, 0x7c00000 | (rm << 16) | (rd << 12) | REG_TEMP); + } } void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { @@ -430,7 +444,7 @@ void asm_arm_bcc_label(asm_arm_t *as, int cond, uint label) { rel -= 8; // account for instruction prefetch, PC is 8 bytes ahead of this instruction rel >>= 2; // in ARM mode the branch target is 32-bit aligned, so the 2 LSB are omitted - if (SIGNED_FIT24(rel)) { + if (MP_FIT_SIGNED(24, rel)) { emit(as, cond | 0xa000000 | (rel & 0xffffff)); } else { printf("asm_arm_bcc: branch does not fit in 24 bits\n"); diff --git a/py/asmarm.h b/py/asmarm.h index 07ed425c980..0d68812145f 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -110,12 +110,11 @@ void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs); // memory void asm_arm_ldr_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); -void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn); void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); -void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn); +void asm_arm_ldrb_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); void asm_arm_str_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset); -void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm); -void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm); +void asm_arm_strh_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset); +void asm_arm_strb_reg_reg_offset(asm_arm_t *as, uint rd, uint rm, uint byte_offset); // load from array void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); @@ -209,16 +208,19 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_arm_mul_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) -#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg_offset((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) ASM_LOAD8_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG_OFFSET(as, reg_dest, reg_base, byte_offset) asm_arm_ldrb_reg_reg_offset((as), (reg_dest), (reg_base), (byte_offset)) +#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) ASM_LOAD16_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, halfword_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (halfword_offset)) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) #define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg_offset((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_STORE_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_value), (reg_base), (word_offset)) -#define ASM_STORE8_REG_REG(as, reg_value, reg_base) asm_arm_strb_reg_reg((as), (reg_value), (reg_base)) -#define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) -#define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg_offset((as), (reg_value), (reg_base), 0) +#define ASM_STORE8_REG_REG(as, reg_value, reg_base) ASM_STORE8_REG_REG_OFFSET((as), (reg_value), (reg_base), 0) +#define ASM_STORE8_REG_REG_OFFSET(as, reg_value, reg_base, byte_offset) asm_arm_strb_reg_reg_offset((as), (reg_value), (reg_base), (byte_offset)) +#define ASM_STORE16_REG_REG(as, reg_value, reg_base) ASM_STORE16_REG_REG_OFFSET((as), (reg_value), (reg_base), 0) +#define ASM_STORE16_REG_REG_OFFSET(as, reg_value, reg_base, halfword_offset) asm_arm_strh_reg_reg_offset((as), (reg_value), (reg_base), 2 * (halfword_offset)) +#define ASM_STORE32_REG_REG(as, reg_value, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_value), (reg_base), 0) #define ASM_STORE32_REG_REG_OFFSET(as, reg_value, reg_base, word_offset) asm_arm_str_reg_reg_offset((as), (reg_value), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrb_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) diff --git a/py/emitnative.c b/py/emitnative.c index 2cce31dae98..36e9719db40 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1785,10 +1785,6 @@ static void emit_native_store_subscr(emit_t *emit) { if (index_value != 0) { // index is non-zero ASM_MOV_REG_IMM(emit->as, reg_index, index_value); - #if N_ARM - asm_arm_strb_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - break; - #endif ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add index to base reg_base = reg_index; } From 12f36cc13c126770d81bc95daf695f3aa05bc5cb Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 19:54:28 +0200 Subject: [PATCH 0829/2098] py/asmxtensa: Implement the full set of Viper load/store operations. This commit expands the implementation of Viper load/store operations that are optimised for the Xtensa platform. Now both load and store emitters should generate the shortest possible sequence in all cases. Redundant specialised operation emitters have been aliased to the general case implementation - this was the case of integer-indexed load/store operations with a fixed offset of zero. Signed-off-by: Alessandro Gatti --- py/asmxtensa.c | 50 +++++++++++++++++++++++++++++++++++-------------- py/asmxtensa.h | 36 +++++++++++++++++++++++------------ py/emitnative.c | 18 ------------------ 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index 85a8cfef555..bc3e717d9f3 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -299,25 +299,47 @@ void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, u } } -void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, uint word_offset) { - if (word_offset < 16) { - asm_xtensa_op_s32i_n(as, reg_src, reg_base, word_offset); - } else if (word_offset < 256) { - asm_xtensa_op_s32i(as, reg_src, reg_base, word_offset); +void asm_xtensa_load_reg_reg_offset(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint offset, uint operation_size) { + assert(operation_size <= 2 && "Operation size value out of range."); + + if (operation_size == 2 && MP_FIT_UNSIGNED(4, offset)) { + asm_xtensa_op_l32i_n(as, reg_dest, reg_base, offset); + return; + } + + if (MP_FIT_UNSIGNED(8, offset)) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, operation_size, reg_base, reg_dest, offset)); + return; + } + + asm_xtensa_mov_reg_i32_optimised(as, reg_dest, offset << operation_size); + asm_xtensa_op_add_n(as, reg_dest, reg_base, reg_dest); + if (operation_size == 2) { + asm_xtensa_op_l32i_n(as, reg_dest, reg_dest, 0); } else { - asm_xtensa_mov_reg_i32_optimised(as, REG_TEMP, word_offset * 4); - asm_xtensa_op_add_n(as, REG_TEMP, reg_base, REG_TEMP); - asm_xtensa_op_s32i_n(as, reg_src, REG_TEMP, 0); + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, operation_size, reg_dest, reg_dest, 0)); } } -void asm_xtensa_l16ui_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint halfword_offset) { - if (halfword_offset < 256) { - asm_xtensa_op_l16ui(as, reg_dest, reg_base, halfword_offset); +void asm_xtensa_store_reg_reg_offset(asm_xtensa_t *as, uint reg_src, uint reg_base, uint offset, uint operation_size) { + assert(operation_size <= 2 && "Operation size value out of range."); + + if (operation_size == 2 && MP_FIT_UNSIGNED(4, offset)) { + asm_xtensa_op_s32i_n(as, reg_src, reg_base, offset); + return; + } + + if (MP_FIT_UNSIGNED(8, offset)) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, 0x04 | operation_size, reg_base, reg_src, offset)); + return; + } + + asm_xtensa_mov_reg_i32_optimised(as, REG_TEMP, offset << operation_size); + asm_xtensa_op_add_n(as, REG_TEMP, reg_base, REG_TEMP); + if (operation_size == 2) { + asm_xtensa_op_s32i_n(as, reg_src, REG_TEMP, 0); } else { - asm_xtensa_mov_reg_i32_optimised(as, reg_dest, halfword_offset * 2); - asm_xtensa_op_add_n(as, reg_dest, reg_base, reg_dest); - asm_xtensa_op_l16ui(as, reg_dest, reg_dest, 0); + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, 0x04 | operation_size, REG_TEMP, reg_src, 0)); } } diff --git a/py/asmxtensa.h b/py/asmxtensa.h index d90aef3c533..7f113ca12e0 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -293,9 +293,8 @@ void asm_xtensa_mov_local_reg(asm_xtensa_t *as, int local_num, uint reg_src); void asm_xtensa_mov_reg_local(asm_xtensa_t *as, uint reg_dest, int local_num); void asm_xtensa_mov_reg_local_addr(asm_xtensa_t *as, uint reg_dest, int local_num); void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label); -void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset); -void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, uint word_offset); -void asm_xtensa_l16ui_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint halfword_offset); +void asm_xtensa_load_reg_reg_offset(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint offset, uint operation_size); +void asm_xtensa_store_reg_reg_offset(asm_xtensa_t *as, uint reg_src, uint reg_base, uint offset, uint operation_size); void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx); void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); void asm_xtensa_bit_branch(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t bit, mp_uint_t label, mp_uint_t condition); @@ -420,16 +419,22 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_xtensa_op_mull((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_l16ui_optimised((as), (reg_dest), (reg_base), (uint16_offset)) +#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) ASM_LOAD8_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG_OFFSET(as, reg_dest, reg_base, byte_offset) asm_xtensa_load_reg_reg_offset((as), (reg_dest), (reg_base), (byte_offset), 0) +#define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_xtensa_op_add_n((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0); \ + } while (0) +#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) ASM_LOAD16_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, halfword_offset) asm_xtensa_load_reg_reg_offset((as), (reg_dest), (reg_base), (halfword_offset), 1) #define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ do { \ asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0); \ } while (0) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0) -#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_l32i_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_load_reg_reg_offset((as), (reg_dest), (reg_base), (word_offset), 2) #define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ do { \ asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ @@ -437,15 +442,22 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); } while (0) #define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s8i((as), (reg_src), (reg_base), 0) -#define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s16i((as), (reg_src), (reg_base), 0) +#define ASM_STORE8_REG_REG(as, reg_src, reg_base) ASM_STORE8_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE8_REG_REG_OFFSET(as, reg_src, reg_base, byte_offset) asm_xtensa_store_reg_reg_offset((as), (reg_src), (reg_base), (byte_offset), 0) +#define ASM_STORE8_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_xtensa_op_add_n((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_s8i((as), (reg_val), (reg_base), 0); \ + } while (0) +#define ASM_STORE16_REG_REG(as, reg_src, reg_base) ASM_STORE16_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_OFFSET(as, reg_src, reg_base, halfword_offset) asm_xtensa_store_reg_reg_offset((as), (reg_src), (reg_base), (halfword_offset), 1) #define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ do { \ asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ asm_xtensa_op_s16i((as), (reg_val), (reg_base), 0); \ } while (0) -#define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s32i_n((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_store_reg_reg_offset((as), (reg_dest), (reg_base), (word_offset), 2) #define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ do { \ asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ diff --git a/py/emitnative.c b/py/emitnative.c index 36e9719db40..f3ab483e8a6 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1534,12 +1534,6 @@ static void emit_native_load_subscr(emit_t *emit) { #ifdef ASM_LOAD8_REG_REG_OFFSET ASM_LOAD8_REG_REG_OFFSET(emit->as, REG_RET, reg_base, index_value); #else - #if N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif if (index_value != 0) { // index is non-zero need_reg_single(emit, reg_index, 0); @@ -1776,12 +1770,6 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE8_REG_REG_OFFSET ASM_STORE8_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_op_s8i(emit->as, reg_value, reg_base, index_value); - break; - } - #endif if (index_value != 0) { // index is non-zero ASM_MOV_REG_IMM(emit->as, reg_index, index_value); @@ -1797,12 +1785,6 @@ static void emit_native_store_subscr(emit_t *emit) { #ifdef ASM_STORE16_REG_REG_OFFSET ASM_STORE16_REG_REG_OFFSET(emit->as, reg_value, reg_base, index_value); #else - #if N_XTENSA || N_XTENSAWIN - if (index_value >= 0 && index_value < 256) { - asm_xtensa_op_s16i(emit->as, reg_value, reg_base, index_value); - break; - } - #endif if (index_value != 0) { // index is a non-zero immediate ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); From 7bb5f2da9d36d8e017953969d5acbd561c5bb1d6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 20:05:59 +0200 Subject: [PATCH 0830/2098] py/asmthumb: Remove redundant load/store opcode implementations. This commit removes load/store opcode implementations that have been made redundant in 1f5ba6998bb1895354f5afcd7bb52d83a02733be. Signed-off-by: Alessandro Gatti --- py/asmthumb.h | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/py/asmthumb.h b/py/asmthumb.h index 6c83b583e2b..b423ce7a4d0 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -317,24 +317,6 @@ static inline void asm_thumb_format_9_10(asm_thumb_t *as, uint op, uint rlo_dest asm_thumb_op16(as, ASM_THUMB_FORMAT_9_10_ENCODE(op, rlo_dest, rlo_base, offset)); } -static inline void asm_thumb_str_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint word_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_STR | ASM_THUMB_FORMAT_9_WORD_TRANSFER, rlo_src, rlo_base, word_offset); -} -static inline void asm_thumb_strb_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint byte_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_STR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER, rlo_src, rlo_base, byte_offset); -} -static inline void asm_thumb_strh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint uint16_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_STRH, rlo_src, rlo_base, uint16_offset); -} -static inline void asm_thumb_ldr_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint word_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_WORD_TRANSFER, rlo_dest, rlo_base, word_offset); -} -static inline void asm_thumb_ldrb_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint byte_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER, rlo_dest, rlo_base, byte_offset); -} -static inline void asm_thumb_ldrh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint uint16_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_LDRH, rlo_dest, rlo_base, uint16_offset); -} static inline void asm_thumb_lsl_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_src, uint shift) { asm_thumb_format_1(as, ASM_THUMB_FORMAT_1_LSL, rlo_dest, rlo_src, shift); } From a8dd393eee7e3ebfc5c6e97b2fe2d4b3099f2959 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 20:23:30 +0200 Subject: [PATCH 0831/2098] py/asmx86: Implement the full set of Viper load/store operations. This commit expands the implementation of Viper load/store operations that are optimised for the x86 platform. Unlike other platforms, x86 already implemented all necessary functions and all it took to expose these to Viper after the infrastructure refactoring was to add a few defines. Signed-off-by: Alessandro Gatti --- py/asmx86.h | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/py/asmx86.h b/py/asmx86.h index 5011e56d0c1..2cec38ed451 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -200,18 +200,21 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x86_sub_r32_r32((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x86_mul_r32_r32((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (word_offset)) -#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem8_to_r32zx((as), (reg_base), 0, (reg_dest)) -#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 0, (reg_dest)) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) -#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) - -#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), (word_offset)) -#define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x86_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) -#define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x86_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (word_offset)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, dword_offset) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), (dword_offset)) +#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) ASM_LOAD8_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG_OFFSET(as, reg_dest, reg_base, byte_offset) asm_x86_mov_mem8_to_r32zx((as), (reg_base), (byte_offset), (reg_dest)) +#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) ASM_LOAD16_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (word_offset), (reg_dest)) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, dword_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (dword_offset), (reg_dest)) + +#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, dword_offset) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), (dword_offset)) +#define ASM_STORE8_REG_REG(as, reg_src, reg_base) ASM_STORE8_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE8_REG_REG_OFFSET(as, reg_src, reg_base, byte_offset) asm_x86_mov_r8_to_mem8((as), (reg_src), (reg_base), (byte_offset)) +#define ASM_STORE16_REG_REG(as, reg_src, reg_base) ASM_STORE16_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r16_to_mem16((as), (reg_src), (reg_base), 2 * (word_offset)) +#define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, dword_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (dword_offset)) #endif // GENERIC_ASM_API From c8c8b0456955cd78436657c885b83620393e81a6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 11 Jun 2025 07:51:55 +0200 Subject: [PATCH 0832/2098] py/asmx64: Implement the full set of Viper load/store operations. This commit expands the implementation of Viper load/store operations that are optimised for the x86 platform. Like x86, x64 already implemented all necessary functions and all it took to expose these to Viper after the infrastructure refactoring was to add a few defines. Signed-off-by: Alessandro Gatti --- py/asmx64.h | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/py/asmx64.h b/py/asmx64.h index 30c6efd6d05..f2fb5da1805 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -205,16 +205,21 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x64_sub_r64_r64((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x64_mul_r64_r64((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x64_mov_mem64_to_r64((as), (reg_base), 8 * (word_offset), (reg_dest)) -#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem8_to_r64zx((as), (reg_base), 0, (reg_dest)) -#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 0, (reg_dest)) -#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) -#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem32_to_r64zx((as), (reg_base), 0, (reg_dest)) - -#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 8 * (word_offset)) -#define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x64_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) -#define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x64_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) -#define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_x64_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, qword_offset) asm_x64_mov_mem64_to_r64((as), (reg_base), 8 * (qword_offset), (reg_dest)) +#define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) ASM_LOAD8_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD8_REG_REG_OFFSET(as, reg_dest, reg_base, byte_offset) asm_x64_mov_mem8_to_r64zx((as), (reg_base), (byte_offset), (reg_dest)) +#define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) ASM_LOAD16_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 2 * (word_offset), (reg_dest)) +#define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) ASM_LOAD32_REG_REG_OFFSET((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_OFFSET(as, reg_dest, reg_base, dword_offset) asm_x64_mov_mem32_to_r64zx((as), (reg_base), 4 * (dword_offset), (reg_dest)) + +#define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, qword_offset) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 8 * (qword_offset)) +#define ASM_STORE8_REG_REG(as, reg_src, reg_base) ASM_STORE8_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE8_REG_REG_OFFSET(as, reg_src, reg_base, byte_offset) asm_x64_mov_r8_to_mem8((as), (reg_src), (reg_base), (byte_offset)) +#define ASM_STORE16_REG_REG(as, reg_src, reg_base) ASM_STORE16_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x64_mov_r16_to_mem16((as), (reg_src), (reg_base), 2 * (word_offset)) +#define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, dword_offset) asm_x64_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (dword_offset)) #endif // GENERIC_ASM_API From 0d435959e0acf4059cff843e45936c21c6e7eb84 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 11 Jun 2025 09:31:34 +0200 Subject: [PATCH 0833/2098] tests/micropython: Improve viper ptr boundary tests. This commit reworks the Viper pointer boundary tests in order to make them more accurate and easier to extend. The tests are now easier to reason about in their output, using easier to read values, and bit thresholds are now more configurable. If a new conditional code sequence is introduced, adding a new bit threshold is just a matter of adding a value into a tuple at the beginning of the relevant test file. Load tests have also been made more accurate, with better function templates to test register-indexed operations. Signed-off-by: Alessandro Gatti --- .../micropython/viper_ptr16_load_boundary.py | 32 ++++++--- .../viper_ptr16_load_boundary.py.exp | 32 +++++---- .../micropython/viper_ptr16_store_boundary.py | 66 ++++++++++-------- .../viper_ptr16_store_boundary.py.exp | 38 +++++----- .../micropython/viper_ptr32_load_boundary.py | 32 ++++++--- .../viper_ptr32_load_boundary.py.exp | 32 +++++---- .../micropython/viper_ptr32_store_boundary.py | 69 ++++++++++++------- .../viper_ptr32_store_boundary.py.exp | 38 +++++----- tests/micropython/viper_ptr8_load_boundary.py | 31 ++++++--- .../viper_ptr8_load_boundary.py.exp | 32 +++++---- .../micropython/viper_ptr8_store_boundary.py | 53 +++++++++----- .../viper_ptr8_store_boundary.py.exp | 32 +++++---- 12 files changed, 309 insertions(+), 178 deletions(-) diff --git a/tests/micropython/viper_ptr16_load_boundary.py b/tests/micropython/viper_ptr16_load_boundary.py index ccaaa0909af..0d4c3105b68 100644 --- a/tests/micropython/viper_ptr16_load_boundary.py +++ b/tests/micropython/viper_ptr16_load_boundary.py @@ -2,24 +2,38 @@ GET_TEMPLATE = """ @micropython.viper -def get{off}(src: ptr16) -> int: - return src[{off}] -print(b[{off} * 2:({off} + 1) * 2]) +def get{off}(src: ptr16) -> uint: + return uint(src[{off}]) +print(hex(get{off}(buffer))) """ +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 2 + + @micropython.viper def get_index(src: ptr16, i: int) -> int: return src[i] -b = bytearray(5000) -b[28:38] = b"0123456789" -b[252:262] = b"ABCDEFGHIJ" -b[4092:4102] = b"KLMNOPQRST" +def data(start, len): + output = bytearray(len) + for idx in range(len): + output[idx] = (start + idx) & 0xFF + return output + + +buffer = bytearray((((1 << max(BIT_THRESHOLDS)) + 1) // 1024) * 1024) +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = ((1 << bit) - (2 * SIZE), (1 << bit) - (1 * SIZE), 1 << bit) + buffer[pre:post] = data(val, 3 * SIZE) + val = val + (3 * SIZE) -for pre, idx, post in (15, 16, 17), (127, 128, 129), (2047, 2048, 2049): - print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + pre, idx, post = pre // SIZE, idx // SIZE, post // SIZE + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) exec(GET_TEMPLATE.format(off=pre)) exec(GET_TEMPLATE.format(off=idx)) exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr16_load_boundary.py.exp b/tests/micropython/viper_ptr16_load_boundary.py.exp index 4b8c184c134..56f1d322904 100644 --- a/tests/micropython/viper_ptr16_load_boundary.py.exp +++ b/tests/micropython/viper_ptr16_load_boundary.py.exp @@ -1,12 +1,20 @@ -13106 13620 14134 -bytearray(b'23') -bytearray(b'45') -bytearray(b'67') -17475 17989 18503 -bytearray(b'CD') -bytearray(b'EF') -bytearray(b'GH') -20045 20559 21073 -bytearray(b'MN') -bytearray(b'OP') -bytearray(b'QR') +--- 5 +0x100 0x302 0x504 +0x100 +0x302 +0x504 +--- 8 +0x706 0x908 0xb0a +0x706 +0x908 +0xb0a +--- 11 +0xd0c 0xf0e 0x1110 +0xd0c +0xf0e +0x1110 +--- 12 +0x1312 0x1514 0x1716 +0x1312 +0x1514 +0x1716 diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary.py index e0a4f845573..1694c61ac0a 100644 --- a/tests/micropython/viper_ptr16_store_boundary.py +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -4,38 +4,50 @@ @micropython.viper def set{off}(dest: ptr16): dest[{off}] = {val} -set{off}(b) -print(b[{off} * 2:({off} + 1) * 2]) +set{off}(buffer) +print(hex(get_index(buffer, {off}))) """ -TEST_DATA = ( - (15, (0x4241, 0x4443, 0x4645)), - (127, (0x4847, 0x4A49, 0x4C4B)), - (2047, (0x4E4D, 0x504F, 0x5251)), -) +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 2 +MASK = (1 << (8 * SIZE)) - 1 @micropython.viper -def set_index(dest: ptr16, i: int, val: int): +def set_index(dest: ptr16, i: int, val: uint): dest[i] = val -@micropython.viper -def set_index(dest: ptr16, i: int, val: int): - dest[i] = val - - -b = bytearray(5000) -for start, vals in TEST_DATA: - for i, v in enumerate(vals): - set_index(b, start + i, v) - print(b[(start + i) * 2 : (start + i + 1) * 2]) - - -for i in range(len(b)): - b[i] = 0 - - -for start, vals in TEST_DATA: - for i, v in enumerate(vals): - exec(SET_TEMPLATE.format(off=start + i, val=v + 0x0101)) +def get_index(src, i): + return src[i * SIZE] + (src[(i * SIZE) + 1] << 8) + + +buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) +next = 1 +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = ( + (((1 << bit) - (2 * SIZE)) // SIZE), + (((1 << bit) - (1 * SIZE)) // SIZE), + ((1 << bit) // SIZE), + ) + val = (val << 8) + next + next += 1 + set_index(buffer, pre, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, idx, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, post, val & MASK) + val = (val << 8) + next + next += 1 + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) + exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=post, val=val & MASK)) diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp index b56fe6695f2..1c084da2d9c 100644 --- a/tests/micropython/viper_ptr16_store_boundary.py.exp +++ b/tests/micropython/viper_ptr16_store_boundary.py.exp @@ -1,18 +1,20 @@ -bytearray(b'AB') -bytearray(b'CD') -bytearray(b'EF') -bytearray(b'GH') -bytearray(b'IJ') -bytearray(b'KL') -bytearray(b'MN') -bytearray(b'OP') -bytearray(b'QR') -bytearray(b'BC') -bytearray(b'DE') -bytearray(b'FG') -bytearray(b'HI') -bytearray(b'JK') -bytearray(b'LM') -bytearray(b'NO') -bytearray(b'PQ') -bytearray(b'RS') +--- 5 +0x1 0x102 0x203 +0x304 +0x405 +0x506 +--- 8 +0x607 0x708 0x809 +0x90a +0xa0b +0xb0c +--- 11 +0xc0d 0xd0e 0xe0f +0xf10 +0x1011 +0x1112 +--- 12 +0x1213 0x1314 0x1415 +0x1516 +0x1617 +0x1718 diff --git a/tests/micropython/viper_ptr32_load_boundary.py b/tests/micropython/viper_ptr32_load_boundary.py index 6954bd46b23..971d1113c49 100644 --- a/tests/micropython/viper_ptr32_load_boundary.py +++ b/tests/micropython/viper_ptr32_load_boundary.py @@ -2,24 +2,38 @@ GET_TEMPLATE = """ @micropython.viper -def get{off}(src: ptr32) -> int: - return src[{off}] -print(b[{off} * 4:({off} + 1) * 4]) +def get{off}(src: ptr32) -> uint: + return uint(src[{off}]) +print(hex(get{off}(buffer))) """ +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 4 + + @micropython.viper def get_index(src: ptr32, i: int) -> int: return src[i] -b = bytearray(5000) -b[24:43] = b"0123456789ABCDEFGHIJ" -b[248:268] = b"KLMNOPQRSTUVWXYZabcd" -b[4088:4108] = b"efghijklmnopqrstuvwx" +def data(start, len): + output = bytearray(len) + for idx in range(len): + output[idx] = (start + idx) & 0xFF + return output + + +buffer = bytearray((((1 << max(BIT_THRESHOLDS)) + 1) // 1024) * 1024) +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = (((1 << bit) - (2 * SIZE)), ((1 << bit) - (1 * SIZE)), (1 << bit)) + buffer[pre:post] = data(val, 3 * SIZE) + val = val + (3 * SIZE) -for pre, idx, post in (7, 8, 9), (63, 64, 65), (1023, 1024, 1025): - print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + pre, idx, post = pre // SIZE, idx // SIZE, post // SIZE + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) exec(GET_TEMPLATE.format(off=pre)) exec(GET_TEMPLATE.format(off=idx)) exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr32_load_boundary.py.exp b/tests/micropython/viper_ptr32_load_boundary.py.exp index a58e703f912..1e22a8b3613 100644 --- a/tests/micropython/viper_ptr32_load_boundary.py.exp +++ b/tests/micropython/viper_ptr32_load_boundary.py.exp @@ -1,12 +1,20 @@ -926299444 1111570744 1178944579 -bytearray(b'4567') -bytearray(b'89AB') -bytearray(b'CDEF') -1381060687 1448432723 1515804759 -bytearray(b'OPQR') -bytearray(b'STUV') -bytearray(b'WXYZ') -1818978921 1886350957 1953722993 -bytearray(b'ijkl') -bytearray(b'mnop') -bytearray(b'qrst') +--- 5 +0x3020100 0x7060504 0xb0a0908 +0x3020100 +0x7060504 +0xb0a0908 +--- 8 +0xf0e0d0c 0x13121110 0x17161514 +0xf0e0d0c +0x13121110 +0x17161514 +--- 11 +0x1b1a1918 0x1f1e1d1c 0x23222120 +0x1b1a1918 +0x1f1e1d1c +0x23222120 +--- 12 +0x27262524 0x2b2a2928 0x2f2e2d2c +0x27262524 +0x2b2a2928 +0x2f2e2d2c diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary.py index 243ff5cd9c2..5109abb9dca 100644 --- a/tests/micropython/viper_ptr32_store_boundary.py +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -1,35 +1,58 @@ # Test boundary conditions for various architectures -TEST_DATA = ( - (3, (0x04030201, 0x08070605, 0x0C0B0A09)), - (63, (0x100F0E0D, 0x14131211, 0x18171615)), - (1023, (0x1C1B1A19, 0x201F1E1D, 0x24232221)), -) - SET_TEMPLATE = """ @micropython.viper def set{off}(dest: ptr32): - dest[{off}] = {val} & 0x3FFFFFFF -set{off}(b) -print(b[{off} * 4:({off} + 1) * 4]) + dest[{off}] = {val} +set{off}(buffer) +print(hex(get_index(buffer, {off}))) """ +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 4 +MASK = (1 << (8 * SIZE)) - 1 + @micropython.viper -def set_index(dest: ptr32, i: int, val: int): +def set_index(dest: ptr32, i: int, val: uint): dest[i] = val -b = bytearray(5000) -for start, vals in TEST_DATA: - for i, v in enumerate(vals): - set_index(b, start + i, v) - print(b[(start + i) * 4 : (start + i + 1) * 4]) - -for i in range(len(b)): - b[i] = 0 - - -for start, vals in TEST_DATA: - for i, v in enumerate(vals): - exec(SET_TEMPLATE.format(off=start + i, val=v + 0x01010101)) +def get_index(src, i): + return ( + src[i * SIZE] + + (src[(i * SIZE) + 1] << 8) + + (src[(i * SIZE) + 2] << 16) + + (src[(i * SIZE) + 3] << 24) + ) + + +buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) +next = 1 +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = ( + (((1 << bit) - (2 * SIZE)) // SIZE), + (((1 << bit) - (1 * SIZE)) // SIZE), + ((1 << bit) // SIZE), + ) + val = (val << 8) + next + next += 1 + set_index(buffer, pre, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, idx, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, post, val & MASK) + val = (val << 8) + next + next += 1 + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) + exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=post, val=val & MASK)) diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp index 89f09fbc7af..67b114d3358 100644 --- a/tests/micropython/viper_ptr32_store_boundary.py.exp +++ b/tests/micropython/viper_ptr32_store_boundary.py.exp @@ -1,18 +1,20 @@ -bytearray(b'\x01\x02\x03\x04') -bytearray(b'\x05\x06\x07\x08') -bytearray(b'\t\n\x0b\x0c') -bytearray(b'\r\x0e\x0f\x10') -bytearray(b'\x11\x12\x13\x14') -bytearray(b'\x15\x16\x17\x18') -bytearray(b'\x19\x1a\x1b\x1c') -bytearray(b'\x1d\x1e\x1f ') -bytearray(b'!"#$') -bytearray(b'\x02\x03\x04\x05') -bytearray(b'\x06\x07\x08\t') -bytearray(b'\n\x0b\x0c\r') -bytearray(b'\x0e\x0f\x10\x11') -bytearray(b'\x12\x13\x14\x15') -bytearray(b'\x16\x17\x18\x19') -bytearray(b'\x1a\x1b\x1c\x1d') -bytearray(b'\x1e\x1f !') -bytearray(b'"#$%') +--- 5 +0x1 0x102 0x10203 +0x1020304 +0x2030405 +0x3040506 +--- 8 +0x4050607 0x5060708 0x6070809 +0x708090a +0x8090a0b +0x90a0b0c +--- 11 +0xa0b0c0d 0xb0c0d0e 0xc0d0e0f +0xd0e0f10 +0xe0f1011 +0xf101112 +--- 12 +0x10111213 0x11121314 0x12131415 +0x13141516 +0x14151617 +0x15161718 diff --git a/tests/micropython/viper_ptr8_load_boundary.py b/tests/micropython/viper_ptr8_load_boundary.py index bcb17a1e1f2..57e06da5709 100644 --- a/tests/micropython/viper_ptr8_load_boundary.py +++ b/tests/micropython/viper_ptr8_load_boundary.py @@ -2,24 +2,37 @@ GET_TEMPLATE = """ @micropython.viper -def get{off}(src: ptr8) -> int: - return src[{off}] -print(get{off}(b)) +def get{off}(src: ptr8) -> uint: + return uint(src[{off}]) +print(hex(get{off}(buffer))) """ +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 1 + + @micropython.viper def get_index(src: ptr8, i: int) -> int: return src[i] -b = bytearray(5000) -b[30:32] = b"123" -b[254:256] = b"456" -b[4094:4096] = b"789" +def data(start, len): + output = bytearray(len) + for idx in range(len): + output[idx] = (start + idx) & 0xFF + return output + + +buffer = bytearray((((1 << max(BIT_THRESHOLDS)) + 1) // 1024) * 1024) +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = (((1 << bit) - (2 * SIZE)), ((1 << bit) - (1 * SIZE)), (1 << bit)) + buffer[pre:post] = data(val, 3 * SIZE) + val = val + (3 * SIZE) -for pre, idx, post in (30, 31, 32), (254, 255, 256), (4094, 4095, 4096): - print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) exec(GET_TEMPLATE.format(off=pre)) exec(GET_TEMPLATE.format(off=idx)) exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr8_load_boundary.py.exp b/tests/micropython/viper_ptr8_load_boundary.py.exp index 7cbd1ac78cf..a0e423686b8 100644 --- a/tests/micropython/viper_ptr8_load_boundary.py.exp +++ b/tests/micropython/viper_ptr8_load_boundary.py.exp @@ -1,12 +1,20 @@ -49 50 51 -49 -50 -51 -52 53 54 -52 -53 -54 -55 56 57 -55 -56 -57 +--- 5 +0x0 0x1 0x2 +0x0 +0x1 +0x2 +--- 8 +0x3 0x4 0x5 +0x3 +0x4 +0x5 +--- 11 +0x6 0x7 0x8 +0x6 +0x7 +0x8 +--- 12 +0x9 0xa 0xb +0x9 +0xa +0xb diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary.py index ad512684549..e1fe6dcae32 100644 --- a/tests/micropython/viper_ptr8_store_boundary.py +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -1,30 +1,49 @@ # Test boundary conditions for various architectures -TEST_DATA = ((49, 30, 3), (52, 254, 3), (55, 4094, 3)) - SET_TEMPLATE = """ @micropython.viper def set{off}(dest: ptr8): dest[{off}] = {val} -set{off}(b) -print(b[{off}]) +set{off}(buffer) +print(hex(get_index(buffer, {off}))) """ +BIT_THRESHOLDS = (5, 8, 11, 12) +SIZE = 1 +MASK = (1 << (8 * SIZE)) - 1 + @micropython.viper -def set_index(dest: ptr8, i: int, val: int): +def set_index(dest: ptr8, i: int, val: uint): dest[i] = val -b = bytearray(5000) -for val, start, count in TEST_DATA: - for i in range(count): - set_index(b, start + i, val + i) - print(b[start : start + count]) - -for i in range(len(b)): - b[i] = 0 - -for val, start, count in TEST_DATA: - for i in range(count): - exec(SET_TEMPLATE.format(off=start + i, val=val + i + 16)) +def get_index(src: ptr8, i: int): + return src[i] + + +buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) +next = 1 +val = 0 +for bit in BIT_THRESHOLDS: + print("---", bit) + pre, idx, post = (((1 << bit) - (2 * SIZE)), ((1 << bit) - (1 * SIZE)), (1 << bit)) + val = (val << 8) + next + next += 1 + set_index(buffer, pre, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, idx, val & MASK) + val = (val << 8) + next + next += 1 + set_index(buffer, post, val & MASK) + val = (val << 8) + next + next += 1 + print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) + exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) + val = (val << 8) + next + next += 1 + exec(SET_TEMPLATE.format(off=post, val=val & MASK)) diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp index a35cb3ac9e0..6b0f7ce13e8 100644 --- a/tests/micropython/viper_ptr8_store_boundary.py.exp +++ b/tests/micropython/viper_ptr8_store_boundary.py.exp @@ -1,12 +1,20 @@ -bytearray(b'123') -bytearray(b'456') -bytearray(b'789') -65 -66 -67 -68 -69 -70 -71 -72 -73 +--- 5 +0x1 0x2 0x3 +0x4 +0x5 +0x6 +--- 8 +0x7 0x8 0x9 +0xa +0xb +0xc +--- 11 +0xd 0xe 0xf +0x10 +0x11 +0x12 +--- 12 +0x13 0x14 0x15 +0x16 +0x17 +0x18 From 6fd7422a8e5b6c8952f7b67713f0551992d247cc Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 10 Jun 2025 22:54:38 +0200 Subject: [PATCH 0834/2098] tests/run-tests.py: Allow injected code customisation. This commit introduces a mechanism to customise the code that is injected to the board when performing a test file upload and execution. A new argument, --begin", is added so regular Python code can be inserted in the injected fragment between the module file creation and the effective file import. This is needed for running larger tests (usually ones that have been pre-compiled with "--via-mpy --emit native") on ESP8266, as that board does not have enough memory to fit certain blocks of code unless additional configuration is performed. Signed-off-by: Alessandro Gatti --- tests/run-tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index 628fde9d30a..e45122b10ed 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -95,6 +95,7 @@ def open(self, path, mode): return __File() vfs.mount(__FS(), '/__vfstest') os.chdir('/__vfstest') +{import_prologue} __import__('__injected_test') """ @@ -1130,6 +1131,8 @@ def __call__(self, parser, args, value, option): def main(): + global injected_import_hook_code + cmd_parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description="""Run and manage tests for MicroPython. @@ -1239,8 +1242,20 @@ def main(): action="store_true", help="re-run only the failed tests", ) + cmd_parser.add_argument( + "--begin", + metavar="PROLOGUE", + default=None, + help="prologue python file to execute before module import", + ) args = cmd_parser.parse_args() + prologue = "" + if args.begin: + with open(args.begin, "rt") as source: + prologue = source.read() + injected_import_hook_code = injected_import_hook_code.replace("{import_prologue}", prologue) + if args.print_failures: for out in glob(os.path.join(args.result_dir, "*.out")): testbase = out[:-4] From 1b0cdc0794825fe147fc3bc8a717e83246640d2d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 26 Jun 2025 20:25:09 +0200 Subject: [PATCH 0835/2098] py/asmthumb: Clean up integer-indexed load/store emitters. This commit cleans up the single entry point integer-indexed load/store emitters that have been built by merging the single operand type load/store functions in 1f5ba6998bb1895354f5afcd7bb52d83a02733be. To follow the same convention found in RV32 and Xtensa emitters, the function operand size is not named after the left shift amount to apply to the initial offset to get its true byte offset, but by a generic "operand size". Also, those functions were updated to use the new MP_FIT_UNSIGNED macros to perform bit width checks when figuring out which opcode encoding is the best one to use. Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 36 +++++++++++++++++++----------------- py/asmthumb.h | 4 ++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index fda0f52705e..18c3db9e4e2 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -37,10 +37,8 @@ #include "py/asmthumb.h" #include "py/misc.h" -#define UNSIGNED_FIT5(x) ((uint32_t)(x) < 32) #define UNSIGNED_FIT7(x) ((uint32_t)(x) < 128) #define UNSIGNED_FIT8(x) (((x) & 0xffffff00) == 0) -#define UNSIGNED_FIT12(x) (((x) & 0xfffff000) == 0) #define UNSIGNED_FIT16(x) (((x) & 0xffff0000) == 0) #define SIGNED_FIT8(x) (((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80) #define SIGNED_FIT9(x) (((x) & 0xffffff00) == 0) || (((x) & 0xffffff00) == 0xffffff00) @@ -454,7 +452,7 @@ static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint re } } -#define OP_LDR_STR_W_HI(shift, reg) ((0xf880 | (shift) << 5) | (reg)) +#define OP_LDR_STR_W_HI(operation_size, reg) ((0xf880 | (operation_size) << 5) | (reg)) #define OP_LDR_STR_W_LO(reg, imm12) (((reg) << 12) | (imm12)) #define OP_LDR 0x01 @@ -467,31 +465,35 @@ static const uint8_t OP_LDR_STR_TABLE[3] = { 0x0E, 0x10, 0x0C }; -void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint shift) { - if (UNSIGNED_FIT5(offset) && (reg_dest < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { +void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint operation_size) { + assert(operation_size <= 2 && "Operation size out of range."); + + if (MP_FIT_UNSIGNED(5, offset) && (reg_dest < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { // Can use T1 encoding - asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_LDR) << 11) | (offset << 6) | (reg_base << 3) | reg_dest); - } else if (asm_thumb_allow_armv7m(as) && UNSIGNED_FIT12(offset << shift)) { + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[operation_size] | OP_LDR) << 11) | (offset << 6) | (reg_base << 3) | reg_dest); + } else if (asm_thumb_allow_armv7m(as) && MP_FIT_UNSIGNED(12, offset << operation_size)) { // Can use T3 encoding - asm_thumb_op32(as, (OP_LDR_STR_W_HI(shift, reg_base) | OP_LDR_W), OP_LDR_STR_W_LO(reg_dest, (offset << shift))); + asm_thumb_op32(as, (OP_LDR_STR_W_HI(operation_size, reg_base) | OP_LDR_W), OP_LDR_STR_W_LO(reg_dest, (offset << operation_size))); } else { // Must use the generic sequence - asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, offset - 31, shift); - asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_LDR) << 11) | (31 << 6) | (reg_dest << 3) | (reg_dest)); + asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, offset - 31, operation_size); + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[operation_size] | OP_LDR) << 11) | (31 << 6) | (reg_dest << 3) | (reg_dest)); } } -void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint shift) { - if (UNSIGNED_FIT5(offset) && (reg_src < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { +void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint operation_size) { + assert(operation_size <= 2 && "Operation size out of range."); + + if (MP_FIT_UNSIGNED(5, offset) && (reg_src < ASM_THUMB_REG_R8) && (reg_base < ASM_THUMB_REG_R8)) { // Can use T1 encoding - asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_STR) << 11) | (offset << 6) | (reg_base << 3) | reg_src); - } else if (asm_thumb_allow_armv7m(as) && UNSIGNED_FIT12(offset << shift)) { + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[operation_size] | OP_STR) << 11) | (offset << 6) | (reg_base << 3) | reg_src); + } else if (asm_thumb_allow_armv7m(as) && MP_FIT_UNSIGNED(12, offset << operation_size)) { // Can use T3 encoding - asm_thumb_op32(as, (OP_LDR_STR_W_HI(shift, reg_base) | OP_STR_W), OP_LDR_STR_W_LO(reg_src, (offset << shift))); + asm_thumb_op32(as, (OP_LDR_STR_W_HI(operation_size, reg_base) | OP_STR_W), OP_LDR_STR_W_LO(reg_src, (offset << operation_size))); } else { // Must use the generic sequence - asm_thumb_add_reg_reg_offset(as, reg_base, reg_base, offset - 31, shift); - asm_thumb_op16(as, ((OP_LDR_STR_TABLE[shift] | OP_STR) << 11) | (31 << 6) | (reg_base << 3) | reg_src); + asm_thumb_add_reg_reg_offset(as, reg_base, reg_base, offset - 31, operation_size); + asm_thumb_op16(as, ((OP_LDR_STR_TABLE[operation_size] | OP_STR) << 11) | (31 << 6) | (reg_base << 3) | reg_src); } } diff --git a/py/asmthumb.h b/py/asmthumb.h index b423ce7a4d0..9cd9d32d83f 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -365,9 +365,9 @@ void asm_thumb_mov_reg_local_addr(asm_thumb_t *as, uint rlo_dest, int local_num) void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); // Generate optimised load dest, [src, #offset] sequence -void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint shift); +void asm_thumb_load_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint operation_size); // Generate optimised store src, [dest, #offset] sequence -void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint shift); +void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base, uint offset, uint operation_size); void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch From 4a1edc4866f253cb446889e1f1a719c74e7dcd04 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 17 Jun 2025 18:30:25 +0200 Subject: [PATCH 0836/2098] alif/machine_pin: Add support for machine.Pin IRQ. Matches existing `Pin.irq()` API. Both rising and falling edge work. Signed-off-by: iabdalkader --- ports/alif/irq.h | 1 + ports/alif/machine_pin.c | 197 +++++++++++++++++++++++++++++++++++++++ ports/alif/main.c | 2 + ports/alif/mphalport.h | 3 + 4 files changed, 203 insertions(+) diff --git a/ports/alif/irq.h b/ports/alif/irq.h index 08b8ef8086b..02df524a49c 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -48,6 +48,7 @@ #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 7, 0) #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) #define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) +#define IRQ_PRI_GPIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 50, 0) #define IRQ_PRI_RTC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 100, 0) #define IRQ_PRI_CYW43 NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 126, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) diff --git a/ports/alif/machine_pin.c b/ports/alif/machine_pin.c index 02c5e482adb..b8773f103f2 100644 --- a/ports/alif/machine_pin.c +++ b/ports/alif/machine_pin.c @@ -30,8 +30,76 @@ #include "extmod/virtpin.h" #include "shared/runtime/mpirq.h" +#ifndef MACHINE_PIN_NUM_VECTORS +#define MACHINE_PIN_NUM_PORT_IO (8) +#define MACHINE_PIN_NUM_VECTORS (16 * MACHINE_PIN_NUM_PORT_IO) +#endif + +typedef struct _machine_pin_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; + IRQn_Type irq_num; + bool reserved; // for use by other drivers +} machine_pin_irq_obj_t; + +#define MACHINE_PIN_IRQ_INDEX(port, pin) \ + ((port) * MACHINE_PIN_NUM_PORT_IO + (pin)) + +#define MACHINE_PIN_IRQ_OBJECT(port, pin) \ + (MP_STATE_PORT(machine_pin_irq_obj[MACHINE_PIN_IRQ_INDEX((port), (pin))])) + +// Defines a single GPIO IRQ handler +#define DEFINE_GPIO_IRQ_HANDLER(pname, port, pin) \ + void pname##_IRQ##pin##Handler(void) { \ + machine_pin_irq_obj_t *irq = MACHINE_PIN_IRQ_OBJECT(port, pin); \ + machine_pin_obj_t *self = MP_OBJ_TO_PTR(irq->base.parent); \ + gpio_interrupt_eoi(self->gpio, pin); \ + irq->flags = irq->trigger; \ + mp_irq_handler(&irq->base); \ + } + +// Defines all 8 pin IRQ handlers for a port +#define DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(gpio, port) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 0) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 1) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 2) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 3) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 4) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 5) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 6) \ + DEFINE_GPIO_IRQ_HANDLER(gpio, port, 7) + +// Generate handlers for GPIO ports 0 to 14 + LPGPIO +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO0, 0) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO1, 1) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO2, 2) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO3, 3) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO4, 4) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO5, 5) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO6, 6) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO7, 7) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO8, 8) + +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 0) +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 1) +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 2) +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 3) +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 4) +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 5) +// DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 6) // Reserved for WiFi +DEFINE_GPIO_IRQ_HANDLER(GPIO9, 9, 7) + +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO10, 10) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO11, 11) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO12, 12) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO13, 13) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(GPIO14, 14) +DEFINE_GPIO_IRQ_HANDLERS_FOR_PORT(LPGPIO, 15) + extern const mp_obj_dict_t machine_pin_cpu_pins_locals_dict; extern const mp_obj_dict_t machine_pin_board_pins_locals_dict; +static const mp_irq_methods_t machine_pin_irq_methods; static const machine_pin_obj_t *machine_pin_find_named(const mp_obj_dict_t *named_pins, mp_obj_t name) { const mp_map_t *named_map = &named_pins->map; @@ -171,6 +239,7 @@ mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); } + return MP_OBJ_FROM_PTR(self); } @@ -225,6 +294,129 @@ static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); +static mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t trigger) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MACHINE_PIN_IRQ_OBJECT(self->port, self->pin); + + irq->flags = 0; + irq->trigger = trigger; + + // Disable IRQs. + gpio_disable_interrupt(self->gpio, self->pin); + gpio_mask_interrupt(self->gpio, self->pin); + + NVIC_ClearPendingIRQ(irq->irq_num); + NVIC_DisableIRQ(irq->irq_num); + + // Return if the trigger is disabled. + if (trigger == 0) { + return 0; + } + + // Clear and enable GPIO IRQ. + gpio_enable_interrupt(self->gpio, self->pin); + gpio_unmask_interrupt(self->gpio, self->pin); + + // Clear GPIO config. + self->gpio->GPIO_INT_BOTHEDGE &= ~(1 << self->pin); + self->gpio->GPIO_INT_POLARITY &= ~(1 << self->pin); + self->gpio->GPIO_INTTYPE_LEVEL &= ~(1 << self->pin); + + // Configure GPIO IRQ trigger + if (trigger == MP_HAL_PIN_TRIGGER_FALL) { + gpio_interrupt_set_edge_trigger(self->gpio, self->pin); + gpio_interrupt_set_polarity_low(self->gpio, self->pin); + } else if (trigger == MP_HAL_PIN_TRIGGER_RISE) { + gpio_interrupt_set_edge_trigger(self->gpio, self->pin); + gpio_interrupt_set_polarity_high(self->gpio, self->pin); + } else if (trigger == (MP_HAL_PIN_TRIGGER_FALL | MP_HAL_PIN_TRIGGER_RISE)) { + gpio_interrupt_set_both_edge_trigger(self->gpio, self->pin); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid IRQ trigger")); + } + + // Clear GPIO IRQ (must be done after configuring trigger) + gpio_interrupt_eoi(self->gpio, self->pin); + + // Clear and enable NVIC GPIO IRQ. + NVIC_ClearPendingIRQ(irq->irq_num); + NVIC_SetPriority(irq->irq_num, IRQ_PRI_GPIO); + NVIC_EnableIRQ(irq->irq_num); + + return 0; +} + +static mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MACHINE_PIN_IRQ_OBJECT(self->port, self->pin); + + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +static const mp_irq_methods_t machine_pin_irq_methods = { + .trigger = machine_pin_irq_trigger, + .info = machine_pin_irq_info, +}; + +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) +static mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_HAL_PIN_TRIGGER_FALL | MP_HAL_PIN_TRIGGER_RISE} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + + machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + machine_pin_irq_obj_t *irq = MACHINE_PIN_IRQ_OBJECT(self->port, self->pin); + + // Allocate a new IRQ object if it doesn't exist. + if (irq == NULL) { + irq = m_new_obj(machine_pin_irq_obj_t); + uint32_t idx = MACHINE_PIN_IRQ_INDEX(self->port, self->pin); + + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + irq->reserved = false; + irq->irq_num = (self->port < 15) ? (GPIO0_IRQ0_IRQn + idx) : (LPGPIO_IRQ0_IRQn + self->pin); + MP_STATE_PORT(machine_pin_irq_obj[idx]) = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + if (irq->reserved) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Pin IRQ is reserved")); + } + irq->base.handler = args[ARG_handler].u_obj; + irq->base.ishard = args[ARG_hard].u_bool; + machine_pin_irq_trigger(self, args[ARG_trigger].u_int); + } + + return MP_OBJ_FROM_PTR(irq); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); + +void machine_pin_irq_deinit(void) { + for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(machine_pin_irq_obj)); i++) { + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[i]); + if (irq != NULL) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(irq->base.parent); + machine_pin_irq_trigger(self, 0); + MP_STATE_PORT(machine_pin_irq_obj[i]) = NULL; + } + } +} + static MP_DEFINE_CONST_OBJ_TYPE( pin_cpu_pins_obj_type, MP_QSTR_cpu, @@ -248,6 +440,7 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_low_obj) }, { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_high_obj) }, { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, // class attributes { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&pin_board_pins_obj_type) }, @@ -259,6 +452,8 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(MP_HAL_PIN_MODE_OPEN_DRAIN) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(MP_HAL_PIN_PULL_UP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(MP_HAL_PIN_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(MP_HAL_PIN_TRIGGER_FALL) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(MP_HAL_PIN_TRIGGER_RISE) }, }; static MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); @@ -296,3 +491,5 @@ MP_DEFINE_CONST_OBJ_TYPE( mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { return machine_pin_find(obj); } + +MP_REGISTER_ROOT_POINTER(void *machine_pin_irq_obj[MACHINE_PIN_NUM_VECTORS]); diff --git a/ports/alif/main.c b/ports/alif/main.c index 47836113577..ab5e85d5b9d 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -55,6 +55,7 @@ extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; +extern void machine_pin_irq_deinit(void); MP_NORETURN void panic(const char *msg) { mp_hal_stdout_tx_strn("\nFATAL ERROR:\n", 14); @@ -164,6 +165,7 @@ int main(void) { mp_bluetooth_deinit(); #endif soft_timer_deinit(); + machine_pin_irq_deinit(); gc_sweep_all(); mp_deinit(); } diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index 4f81439b549..f03dc7c1c1f 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -90,6 +90,9 @@ extern ringbuf_t stdin_ringbuf; #define MP_HAL_PIN_SPEED_LOW (0) #define MP_HAL_PIN_SPEED_HIGH (PADCTRL_SLEW_RATE_FAST) +#define MP_HAL_PIN_TRIGGER_FALL (1) +#define MP_HAL_PIN_TRIGGER_RISE (2) + #define mp_hal_pin_obj_t const machine_pin_obj_t * #define MP_HAL_PIN_ALT(function, unit) (MP_HAL_PIN_ALT_MAKE((MP_HAL_PIN_ALT_##function), (unit))) From 16f9d7fdc3b97aad9613b95e3258844ef722af5c Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 17 Jun 2025 18:59:31 +0200 Subject: [PATCH 0837/2098] alif/machine_spi: Improve transfer function to poll events. Poll events during SPI transfer (USB fails during long transfers otherwise). And add a timeout for the blocking transfer. Signed-off-by: iabdalkader --- ports/alif/machine_spi.c | 60 ++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/ports/alif/machine_spi.c b/ports/alif/machine_spi.c index abb60bc4e83..b2c14745cfc 100644 --- a/ports/alif/machine_spi.c +++ b/ports/alif/machine_spi.c @@ -39,6 +39,7 @@ typedef struct _machine_spi_obj_t { uint8_t id; SPI_Type *inst; bool is_lp; + uint32_t bits; } machine_spi_obj_t; static machine_spi_obj_t machine_spi_obj[] = { @@ -246,6 +247,9 @@ mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n // Get static peripheral object. machine_spi_obj_t *self = &machine_spi_obj[spi_id]; + // Set args + self->bits = args[ARG_bits].u_int; + // here we would check the sck/mosi/miso pins and configure them, but it's not implemented if (args[ARG_sck].u_obj != MP_OBJ_NULL || args[ARG_mosi].u_obj != MP_OBJ_NULL || @@ -294,22 +298,50 @@ static void machine_spi_deinit(mp_obj_base_t *self_in) { } } +static void machine_spi_poll_flag(SPI_Type *spi, uint32_t flag, uint32_t timeout) { + mp_uint_t tick_start = mp_hal_ticks_ms(); + while (!(spi->SPI_SR & flag)) { + if (mp_hal_ticks_ms() - tick_start >= timeout) { + mp_raise_OSError(MP_ETIMEDOUT); + } + mp_event_handle_nowait(); + } +} + static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { machine_spi_obj_t *self = (machine_spi_obj_t *)self_in; - spi_transfer_t spi_xfer = { - .tx_buff = src, - .tx_total_cnt = len, - .rx_buff = dest, - .rx_total_cnt = len, - .tx_default_val = 0xFF, - .tx_default_enable = true, - .mode = SPI_TMOD_TX_AND_RX, - }; - // TODO redo transfer_blocking to timeout and poll events. - if (!self->is_lp) { - spi_transfer_blocking(self->inst, &spi_xfer); - } else { - lpspi_transfer_blocking(self->inst, &spi_xfer); + volatile uint32_t *dr = self->inst->SPI_DR; + + spi_set_tmod(self->inst, SPI_TMOD_TX_AND_RX); + + for (size_t i = 0; i < len; i++) { + // Wait for space in the TX FIFO + machine_spi_poll_flag(self->inst, SPI_SR_TFNF, 100); + + // Send data + if (src == NULL) { + *dr = 0xFFFFFFFFU; + } else if (self->bits > 16) { + *dr = ((uint32_t *)src)[i]; + } else if (self->bits > 8) { + *dr = ((uint16_t *)src)[i]; + } else { + *dr = ((uint8_t *)src)[i]; + } + + // Wait for data in the RX FIFO + machine_spi_poll_flag(self->inst, SPI_SR_RFNE, 100); + + // Recv data + if (dest == NULL) { + (void)*dr; + } else if (self->bits > 16) { + ((uint32_t *)dest)[i] = *dr; + } else if (self->bits > 8) { + ((uint16_t *)dest)[i] = *dr; + } else { + ((uint8_t *)dest)[i] = *dr; + } } } From e33a0f4682374faadc0a6ef5bb02b16bcc3f2d88 Mon Sep 17 00:00:00 2001 From: Andrea Giammarchi Date: Wed, 2 Jul 2025 18:54:21 +0200 Subject: [PATCH 0838/2098] webassembly/objpyproxy: Avoid throwing on symbol or iterator has-check. JavaScript code uses "Symbol in object" to brand check its own proxies, and such checks should also work on the Python side. Signed-off-by: Andrea Giammarchi --- ports/webassembly/objpyproxy.js | 6 ++++++ tests/ports/webassembly/py_proxy_has.mjs | 2 ++ tests/ports/webassembly/py_proxy_has.mjs.exp | 2 ++ 3 files changed, 10 insertions(+) diff --git a/ports/webassembly/objpyproxy.js b/ports/webassembly/objpyproxy.js index 3b94f8aadc7..0eafd0dec53 100644 --- a/ports/webassembly/objpyproxy.js +++ b/ports/webassembly/objpyproxy.js @@ -148,6 +148,12 @@ const py_proxy_handler = { }; }, has(target, prop) { + // avoid throwing on `Symbol() in proxy` checks + if (typeof prop !== "string") { + // returns true only on iterator because other + // symbols are not considered in the `get` trap + return prop === Symbol.iterator; + } return Module.ccall( "proxy_c_to_js_has_attr", "number", diff --git a/tests/ports/webassembly/py_proxy_has.mjs b/tests/ports/webassembly/py_proxy_has.mjs index 8881776fdbe..37df0ae1794 100644 --- a/tests/ports/webassembly/py_proxy_has.mjs +++ b/tests/ports/webassembly/py_proxy_has.mjs @@ -9,3 +9,5 @@ x = [] const x = mp.globals.get("x"); console.log("no_exist" in x); console.log("sort" in x); +console.log(Symbol.toStringTag in x); +console.log(Symbol.iterator in x); diff --git a/tests/ports/webassembly/py_proxy_has.mjs.exp b/tests/ports/webassembly/py_proxy_has.mjs.exp index 1d474d52557..7565230c01f 100644 --- a/tests/ports/webassembly/py_proxy_has.mjs.exp +++ b/tests/ports/webassembly/py_proxy_has.mjs.exp @@ -1,2 +1,4 @@ false true +false +true From 3a97175f5f7023d9f8db37c21e0c04174df0f205 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 27 Sep 2021 17:42:50 +1000 Subject: [PATCH 0839/2098] tools/mpremote: Fix disconnect handling on Windows and Linux. Changes in this commit: - Handle SerialException on Windows when device disconnects. - Print clean 'device disconnected' message instead of stack trace. - Fix terminal formatting issues on Linux after disconnect. - Return disconnected state after console cleanup to avoid terminal issues. This ensures proper disconnect messages on both platforms without showing confusing error traces to users. Signed-off-by: Andrew Leech --- tools/mpremote/mpremote/main.py | 6 +- tools/mpremote/mpremote/repl.py | 107 +++++++++++++++++++------------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index d122e93c0f9..0441857fab7 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -620,7 +620,11 @@ def main(): # If no commands were "actions" then implicitly finish with the REPL # using default args. if state.run_repl_on_completion(): - do_repl(state, argparse_repl().parse_args([])) + disconnected = do_repl(state, argparse_repl().parse_args([])) + + # Handle disconnection message + if disconnected: + print("\ndevice disconnected") return 0 except CommandError as e: diff --git a/tools/mpremote/mpremote/repl.py b/tools/mpremote/mpremote/repl.py index d24a7774ac3..4fda04a2e2a 100644 --- a/tools/mpremote/mpremote/repl.py +++ b/tools/mpremote/mpremote/repl.py @@ -7,51 +7,53 @@ def do_repl_main_loop( state, console_in, console_out_write, *, escape_non_printable, code_to_inject, file_to_inject ): while True: - console_in.waitchar(state.transport.serial) - c = console_in.readchar() - if c: - if c in (b"\x1d", b"\x18"): # ctrl-] or ctrl-x, quit - break - elif c == b"\x04": # ctrl-D - # special handling needed for ctrl-D if filesystem is mounted - state.transport.write_ctrl_d(console_out_write) - elif c == b"\x0a" and code_to_inject is not None: # ctrl-j, inject code - state.transport.serial.write(code_to_inject) - elif c == b"\x0b" and file_to_inject is not None: # ctrl-k, inject script - console_out_write(bytes("Injecting %s\r\n" % file_to_inject, "utf8")) - state.transport.enter_raw_repl(soft_reset=False) - with open(file_to_inject, "rb") as f: - pyfile = f.read() - try: - state.transport.exec_raw_no_follow(pyfile) - except TransportError as er: - console_out_write(b"Error:\r\n") - console_out_write(er) - state.transport.exit_raw_repl() - else: - state.transport.serial.write(c) - try: + console_in.waitchar(state.transport.serial) + c = console_in.readchar() + if c: + if c in (b"\x1d", b"\x18"): # ctrl-] or ctrl-x, quit + break + elif c == b"\x04": # ctrl-D + # special handling needed for ctrl-D if filesystem is mounted + state.transport.write_ctrl_d(console_out_write) + elif c == b"\x0a" and code_to_inject is not None: # ctrl-j, inject code + state.transport.serial.write(code_to_inject) + elif c == b"\x0b" and file_to_inject is not None: # ctrl-k, inject script + console_out_write(bytes("Injecting %s\r\n" % file_to_inject, "utf8")) + state.transport.enter_raw_repl(soft_reset=False) + with open(file_to_inject, "rb") as f: + pyfile = f.read() + try: + state.transport.exec_raw_no_follow(pyfile) + except TransportError as er: + console_out_write(b"Error:\r\n") + console_out_write(er) + state.transport.exit_raw_repl() + else: + state.transport.serial.write(c) + n = state.transport.serial.inWaiting() - except OSError as er: - if er.args[0] == 5: # IO error, device disappeared - print("device disconnected") - break + if n > 0: + dev_data_in = state.transport.serial.read(n) + if dev_data_in is not None: + if escape_non_printable: + # Pass data through to the console, with escaping of non-printables. + console_data_out = bytearray() + for c in dev_data_in: + if c in (8, 9, 10, 13, 27) or 32 <= c <= 126: + console_data_out.append(c) + else: + console_data_out.extend(b"[%02x]" % c) + else: + console_data_out = dev_data_in + console_out_write(console_data_out) - if n > 0: - dev_data_in = state.transport.serial.read(n) - if dev_data_in is not None: - if escape_non_printable: - # Pass data through to the console, with escaping of non-printables. - console_data_out = bytearray() - for c in dev_data_in: - if c in (8, 9, 10, 13, 27) or 32 <= c <= 126: - console_data_out.append(c) - else: - console_data_out.extend(b"[%02x]" % c) - else: - console_data_out = dev_data_in - console_out_write(console_data_out) + except OSError as er: + if _is_disconnect_exception(er): + return True + else: + raise + return False def do_repl(state, args): @@ -86,7 +88,7 @@ def console_out_write(b): capture_file.flush() try: - do_repl_main_loop( + return do_repl_main_loop( state, console, console_out_write, @@ -98,3 +100,22 @@ def console_out_write(b): console.exit() if capture_file is not None: capture_file.close() + + +def _is_disconnect_exception(exception): + """ + Check if an exception indicates device disconnect. + + Returns True if the exception indicates the device has disconnected, + False otherwise. + """ + if isinstance(exception, OSError): + if hasattr(exception, 'args') and len(exception.args) > 0: + # IO error, device disappeared + if exception.args[0] == 5: + return True + # Check for common disconnect messages in the exception string + exception_str = str(exception) + disconnect_indicators = ["Write timeout", "Device disconnected", "ClearCommError failed"] + return any(indicator in exception_str for indicator in disconnect_indicators) + return False From 2ab06b61b3f60e42cda250203bda31667ecef043 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 26 Jun 2025 21:56:26 +0200 Subject: [PATCH 0840/2098] py/emitnative: Let emitters know the compiled entity's name. This commit introduces an optional feature to provide to native emitters the fully qualified name of the entity they are compiling. This is achieved by altering the generic ASM API to provide a third argument to the entry function, containing the name of the entity being compiled. Currently only the debug emitter uses this feature, as it is not really useful for other emitters for the time being; in fact the macros in question just strip the name away. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 10 +++++----- py/asmrv32.h | 2 +- py/asmthumb.h | 10 +++++----- py/asmx64.h | 10 +++++----- py/asmx86.h | 10 +++++----- py/asmxtensa.h | 12 ++++++------ py/emitnative.c | 34 +++++++++++++++++++++++++++++++--- py/emitndebug.c | 8 ++++---- py/mpconfig.h | 5 +++++ 9 files changed, 67 insertions(+), 34 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 0d68812145f..405457d4408 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -164,12 +164,12 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); // Holds a pointer to mp_fun_table #define REG_FUN_TABLE ASM_ARM_REG_FUN_TABLE -#define ASM_T asm_arm_t -#define ASM_END_PASS asm_arm_end_pass -#define ASM_ENTRY asm_arm_entry -#define ASM_EXIT asm_arm_exit +#define ASM_T asm_arm_t +#define ASM_END_PASS asm_arm_end_pass +#define ASM_ENTRY(as, num_locals, name) asm_arm_entry((as), (num_locals)) +#define ASM_EXIT asm_arm_exit -#define ASM_JUMP asm_arm_b_label +#define ASM_JUMP asm_arm_b_label #define ASM_JUMP_IF_REG_ZERO(as, reg, label, bool_test) \ do { \ asm_arm_cmp_reg_i8(as, reg, 0); \ diff --git a/py/asmrv32.h b/py/asmrv32.h index 99c2226ef33..dac9c028b07 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -718,7 +718,7 @@ void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs); void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_uint_t base, int32_t offset, mp_uint_t operation_size); #define ASM_T asm_rv32_t -#define ASM_ENTRY(state, labels) asm_rv32_entry(state, labels) +#define ASM_ENTRY(state, labels, name) asm_rv32_entry(state, labels) #define ASM_EXIT(state) asm_rv32_exit(state) #define ASM_END_PASS(state) asm_rv32_end_pass(state) diff --git a/py/asmthumb.h b/py/asmthumb.h index 9cd9d32d83f..5edf6573e1a 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -403,12 +403,12 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define REG_FUN_TABLE ASM_THUMB_REG_FUN_TABLE -#define ASM_T asm_thumb_t -#define ASM_END_PASS asm_thumb_end_pass -#define ASM_ENTRY asm_thumb_entry -#define ASM_EXIT asm_thumb_exit +#define ASM_T asm_thumb_t +#define ASM_END_PASS asm_thumb_end_pass +#define ASM_ENTRY(as, num_locals, name) asm_thumb_entry((as), (num_locals)) +#define ASM_EXIT asm_thumb_exit -#define ASM_JUMP asm_thumb_b_label +#define ASM_JUMP asm_thumb_b_label #define ASM_JUMP_IF_REG_ZERO(as, reg, label, bool_test) \ do { \ asm_thumb_cmp_rlo_i8(as, reg, 0); \ diff --git a/py/asmx64.h b/py/asmx64.h index f2fb5da1805..d80c5dcc13f 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -154,12 +154,12 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); // Holds a pointer to mp_fun_table #define REG_FUN_TABLE ASM_X64_REG_FUN_TABLE -#define ASM_T asm_x64_t -#define ASM_END_PASS asm_x64_end_pass -#define ASM_ENTRY asm_x64_entry -#define ASM_EXIT asm_x64_exit +#define ASM_T asm_x64_t +#define ASM_END_PASS asm_x64_end_pass +#define ASM_ENTRY(as, num_locals, name) asm_x64_entry((as), (num_locals)) +#define ASM_EXIT asm_x64_exit -#define ASM_JUMP asm_x64_jmp_label +#define ASM_JUMP asm_x64_jmp_label #define ASM_JUMP_IF_REG_ZERO(as, reg, label, bool_test) \ do { \ if (bool_test) { \ diff --git a/py/asmx86.h b/py/asmx86.h index 2cec38ed451..d2e078ad51d 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -149,12 +149,12 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r // Holds a pointer to mp_fun_table #define REG_FUN_TABLE ASM_X86_REG_FUN_TABLE -#define ASM_T asm_x86_t -#define ASM_END_PASS asm_x86_end_pass -#define ASM_ENTRY asm_x86_entry -#define ASM_EXIT asm_x86_exit +#define ASM_T asm_x86_t +#define ASM_END_PASS asm_x86_end_pass +#define ASM_ENTRY(as, num_locals, name) asm_x86_entry((as), (num_locals)) +#define ASM_EXIT asm_x86_exit -#define ASM_JUMP asm_x86_jmp_label +#define ASM_JUMP asm_x86_jmp_label #define ASM_JUMP_IF_REG_ZERO(as, reg, label, bool_test) \ do { \ if (bool_test) { \ diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 7f113ca12e0..559b3cacd5d 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -340,9 +340,9 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_NUM_REGS_SAVED ASM_XTENSA_NUM_REGS_SAVED #define REG_FUN_TABLE ASM_XTENSA_REG_FUN_TABLE -#define ASM_ENTRY(as, nlocal) asm_xtensa_entry((as), (nlocal)) -#define ASM_EXIT(as) asm_xtensa_exit((as)) -#define ASM_CALL_IND(as, idx) asm_xtensa_call_ind((as), (idx)) +#define ASM_ENTRY(as, nlocal, name) asm_xtensa_entry((as), (nlocal)) +#define ASM_EXIT(as) asm_xtensa_exit((as)) +#define ASM_CALL_IND(as, idx) asm_xtensa_call_ind((as), (idx)) #else // Configuration for windowed calls with window size 8 @@ -370,9 +370,9 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); #define ASM_NUM_REGS_SAVED ASM_XTENSA_NUM_REGS_SAVED_WIN #define REG_FUN_TABLE ASM_XTENSA_REG_FUN_TABLE_WIN -#define ASM_ENTRY(as, nlocal) asm_xtensa_entry_win((as), (nlocal)) -#define ASM_EXIT(as) asm_xtensa_exit_win((as)) -#define ASM_CALL_IND(as, idx) asm_xtensa_call_ind_win((as), (idx)) +#define ASM_ENTRY(as, nlocal, name) asm_xtensa_entry_win((as), (nlocal)) +#define ASM_EXIT(as) asm_xtensa_exit_win((as)) +#define ASM_CALL_IND(as, idx) asm_xtensa_call_ind_win((as), (idx)) #endif diff --git a/py/emitnative.c b/py/emitnative.c index f3ab483e8a6..3aacf69a817 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -419,6 +419,30 @@ static void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop emit->stack_info[i].vtype = VTYPE_UNBOUND; } + char *qualified_name = NULL; + + #if N_DEBUG + scope_t *current_scope = scope; + vstr_t *qualified_name_vstr = vstr_new(qstr_len(current_scope->simple_name)); + size_t fragment_length = 0; + const byte *fragment_pointer; + for (;;) { + fragment_pointer = qstr_data(current_scope->simple_name, &fragment_length); + vstr_hint_size(qualified_name_vstr, fragment_length); + memmove(qualified_name_vstr->buf + fragment_length, qualified_name_vstr->buf, qualified_name_vstr->len); + memcpy(qualified_name_vstr->buf, fragment_pointer, fragment_length); + qualified_name_vstr->len += fragment_length; + if (current_scope->parent == NULL || current_scope->parent->simple_name == MP_QSTR__lt_module_gt_) { + break; + } + vstr_ins_char(qualified_name_vstr, 0, '.'); + current_scope = current_scope->parent; + } + qualified_name = vstr_null_terminated_str(qualified_name_vstr); + #else + (void)qualified_name; + #endif + mp_asm_base_start_pass(&emit->as->base, pass == MP_PASS_EMIT ? MP_ASM_PASS_EMIT : MP_ASM_PASS_COMPUTE); // generate code for entry to function @@ -465,7 +489,7 @@ static void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop } // Entry to function - ASM_ENTRY(emit->as, emit->stack_start + emit->n_state - num_locals_in_regs); + ASM_ENTRY(emit->as, emit->stack_start + emit->n_state - num_locals_in_regs, qualified_name); #if N_X86 asm_x86_mov_arg_to_r32(emit->as, 0, REG_PARENT_ARG_1); @@ -536,7 +560,7 @@ static void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop if (emit->scope->scope_flags & MP_SCOPE_FLAG_GENERATOR) { mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)emit->start_offset); - ASM_ENTRY(emit->as, emit->code_state_start); + ASM_ENTRY(emit->as, emit->code_state_start, qualified_name); // Reset the state size for the state pointed to by REG_GENERATOR_STATE emit->code_state_start = 0; @@ -568,7 +592,7 @@ static void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop emit->stack_start = emit->code_state_start + SIZEOF_CODE_STATE; // Allocate space on C-stack for code_state structure, which includes state - ASM_ENTRY(emit->as, emit->stack_start + emit->n_state); + ASM_ENTRY(emit->as, emit->stack_start + emit->n_state, qualified_name); // Prepare incoming arguments for call to mp_setup_code_state @@ -634,6 +658,10 @@ static void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop } } } + + #if N_DEBUG + vstr_free(qualified_name_vstr); + #endif } static inline void emit_native_write_code_info_byte(emit_t *emit, byte val) { diff --git a/py/emitndebug.c b/py/emitndebug.c index c068a9a9a12..e49c5cdbffa 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -108,8 +108,8 @@ static void asm_debug_end_pass(asm_debug_t *as) { (void)as; } -static void asm_debug_entry(asm_debug_t *as, int num_locals) { - asm_debug_printf(as, "ENTRY(num_locals=%d)\n", num_locals); +static void asm_debug_entry(asm_debug_t *as, int num_locals, char *name) { + asm_debug_printf(as, "ENTRY(%s, num_locals=%d)\n", name != NULL ? name : "?", num_locals); } static void asm_debug_exit(asm_debug_t *as) { @@ -195,8 +195,8 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_T asm_debug_t #define ASM_END_PASS asm_debug_end_pass -#define ASM_ENTRY(as, num_locals) \ - asm_debug_entry(as, num_locals) +#define ASM_ENTRY(as, num_locals, name) \ + asm_debug_entry(as, num_locals, name) #define ASM_EXIT(as) \ asm_debug_exit(as) diff --git a/py/mpconfig.h b/py/mpconfig.h index 94b84530037..cf0538cae4d 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -426,6 +426,11 @@ #define MICROPY_EMIT_INLINE_RV32 (0) #endif +// Whether to enable the human-readable native instructions emitter +#ifndef MICROPY_EMIT_NATIVE_DEBUG +#define MICROPY_EMIT_NATIVE_DEBUG (0) +#endif + // Convenience definition for whether any native emitter is enabled #define MICROPY_EMIT_NATIVE (MICROPY_EMIT_X64 || MICROPY_EMIT_X86 || MICROPY_EMIT_THUMB || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN || MICROPY_EMIT_RV32 || MICROPY_EMIT_NATIVE_DEBUG) From fcfed6a0ea763ff14ea2bf819179fdf7eecda889 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 20 Jun 2025 19:44:07 +0200 Subject: [PATCH 0841/2098] unix/variants/coverage: Enable sys.settrace. The unix coverage variant should have all features enabled, so they can be tested for coverage. Therefore, enabled `MICROPY_PY_SYS_SETTRACE`. Signed-off-by: Jeff Epler --- ports/unix/variants/coverage/mpconfigvariant.h | 1 + tests/ports/unix/extra_coverage.py.exp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index cfefeb46720..2f5d9683b3f 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -39,6 +39,7 @@ // Enable additional features. #define MICROPY_DEBUG_PARSE_RULE_NAME (1) +#define MICROPY_PY_SYS_SETTRACE (1) #define MICROPY_TRACKED_ALLOC (1) #define MICROPY_WARNINGS_CATEGORY (1) #undef MICROPY_VFS_ROM_IOCTL diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index ac64edde692..ed21ced2425 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -69,8 +69,8 @@ argv atexit byteorder exc_info executable exit getsizeof implementation intern maxsize modules path platform print_exception ps1 -ps2 stderr stdin stdout -tracebacklimit version version_info +ps2 settrace stderr stdin +stdout tracebacklimit version version_info ementation # attrtuple (start=1, stop=2, step=3) From ff8c4e594375711297e1c6181dec483f1127f29e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 20 Jun 2025 19:59:14 +0200 Subject: [PATCH 0842/2098] tests/misc: Improve test coverage of py/profile.c. Signed-off-by: Jeff Epler --- tests/misc/sys_settrace_cov.py | 23 +++++++++++++++++++++++ tests/misc/sys_settrace_cov.py.exp | 2 ++ tests/run-tests.py | 1 + 3 files changed, 26 insertions(+) create mode 100644 tests/misc/sys_settrace_cov.py create mode 100644 tests/misc/sys_settrace_cov.py.exp diff --git a/tests/misc/sys_settrace_cov.py b/tests/misc/sys_settrace_cov.py new file mode 100644 index 00000000000..579c8a4a25e --- /dev/null +++ b/tests/misc/sys_settrace_cov.py @@ -0,0 +1,23 @@ +import sys + +try: + sys.settrace +except AttributeError: + print("SKIP") + raise SystemExit + + +def trace_tick_handler(frame, event, arg): + print("FRAME", frame) + print("LASTI", frame.f_lasti) + return None + + +def f(): + x = 3 + return x + + +sys.settrace(trace_tick_handler) +f() +sys.settrace(None) diff --git a/tests/misc/sys_settrace_cov.py.exp b/tests/misc/sys_settrace_cov.py.exp new file mode 100644 index 00000000000..423d78ec42b --- /dev/null +++ b/tests/misc/sys_settrace_cov.py.exp @@ -0,0 +1,2 @@ +FRAME +LASTI \\d\+ diff --git a/tests/run-tests.py b/tests/run-tests.py index e45122b10ed..d1d22a3c538 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -354,6 +354,7 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): "micropython/meminfo.py", "basics/bytes_compare3.py", "basics/builtin_help.py", + "misc/sys_settrace_cov.py", "thread/thread_exc2.py", "ports/esp32/partition_ota.py", ) From f04475afd82537e830a09bc58a3d14f9fee4a767 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 21 Jun 2025 08:24:21 +0200 Subject: [PATCH 0843/2098] py/runtime: Initialize profile fields in mp_thread_init_state. If the fields added for `MICROPY_PY_SYS_SETTRACE` are not initialized properly, their value in a thread is indeterminate. In particular, if the callback is not NULL, it will be invoked as a function. Signed-off-by: Jeff Epler --- py/runtime.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/runtime.h b/py/runtime.h index a93488e2cdc..f42039cab90 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -169,6 +169,12 @@ static inline void mp_thread_init_state(mp_state_thread_t *ts, size_t stack_size ts->nlr_jump_callback_top = NULL; ts->mp_pending_exception = MP_OBJ_NULL; + #if MICROPY_PY_SYS_SETTRACE + ts->prof_trace_callback = MP_OBJ_NULL; + ts->prof_callback_is_executing = false; + ts->current_code_state = NULL; + #endif + // If locals/globals are not given, inherit from main thread if (locals == NULL) { locals = mp_state_ctx.thread.dict_locals; From f33f1aa9d36c4dd2de4e1209ed7a94f14505d981 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 21 Jun 2025 08:26:20 +0200 Subject: [PATCH 0844/2098] unix/coverage: Initialize more code_state fields. When `MICROPY_PY_SYS_SETTRACE` was enabled, a crash was seen in the qemu_mips build. It seems likely that this was due to these added fields not being initialized. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index b041141f0f1..33e4208d924 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -582,12 +582,24 @@ static mp_obj_t extra_coverage(void) { fun_bc.context = &context; fun_bc.child_table = NULL; fun_bc.bytecode = (const byte *)"\x01"; // just needed for n_state + #if MICROPY_PY_SYS_SETTRACE + struct _mp_raw_code_t rc = {}; + fun_bc.rc = &rc; + #endif mp_code_state_t *code_state = m_new_obj_var(mp_code_state_t, state, mp_obj_t, 1); code_state->fun_bc = &fun_bc; code_state->ip = (const byte *)"\x00"; // just needed for an invalid opcode code_state->sp = &code_state->state[0]; code_state->exc_sp_idx = 0; code_state->old_globals = NULL; + #if MICROPY_STACKLESS + code_state->prev = NULL; + #endif + #if MICROPY_PY_SYS_SETTRACE + code_state->prev_state = NULL; + code_state->frame = NULL; + #endif + mp_vm_return_kind_t ret = mp_execute_bytecode(code_state, MP_OBJ_NULL); mp_printf(&mp_plat_print, "%d %d\n", ret, mp_obj_get_type(code_state->state[0]) == &mp_type_NotImplementedError); } From db0a836fc1c4cb20fc84edca19b57534d7365287 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 21 Jun 2025 09:35:39 +0200 Subject: [PATCH 0845/2098] py/profile: Fix printing lineno in frame objects. The argument corresponding to a `%q` specifier must be of type `qstr`, not a narrower type like `int16_t`. Not ensuring this caused an assertion error on one Windows x64 build. The argument corresponding to a `%d` specifier must be of type `int`, not a potentially-wider type like `mp_uint_t`. Not ensuring this prevented the function name from being printed on the unix nanbox build. Signed-off-by: Jeff Epler --- py/objcode.h | 2 +- py/profile.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py/objcode.h b/py/objcode.h index 8db9a34b6e1..8f26bd9dbd9 100644 --- a/py/objcode.h +++ b/py/objcode.h @@ -72,7 +72,7 @@ static inline const void *mp_code_get_proto_fun(mp_obj_code_t *self) { #include "py/emitglue.h" -#define MP_CODE_QSTR_MAP(context, idx) (context->constants.qstr_table[idx]) +#define MP_CODE_QSTR_MAP(context, idx) ((qstr)(context->constants.qstr_table[idx])) typedef struct _mp_obj_code_t { // TODO this was 4 words diff --git a/py/profile.c b/py/profile.c index 397d0291f9f..b5a0c54728c 100644 --- a/py/profile.c +++ b/py/profile.c @@ -80,7 +80,7 @@ static void frame_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t "", frame, MP_CODE_QSTR_MAP(code->context, 0), - frame->lineno, + (int)frame->lineno, MP_CODE_QSTR_MAP(code->context, prelude->qstr_block_name_idx) ); } From e415d03e7fd31af71c643a3d8f38a50e25fca0e7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 2 Jul 2025 21:33:34 +0100 Subject: [PATCH 0846/2098] github/workflows: Remove the unix "settrace" CI job. This becomes redundant when the main coverage build includes settrace. Signed-off-by: Jeff Epler --- .github/workflows/ports_unix.yml | 17 ----------------- tools/ci.sh | 17 ----------------- 2 files changed, 34 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index c5223a71e6c..e3ebcfda3d8 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -169,23 +169,6 @@ jobs: if: failure() run: tests/run-tests.py --print-failures - settrace: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. - # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. - with: - python-version: '3.11' - - name: Build - run: source tools/ci.sh && ci_unix_settrace_build - - name: Run main test suite - run: source tools/ci.sh && ci_unix_settrace_run_tests - - name: Print failures - if: failure() - run: tests/run-tests.py --print-failures - settrace_stackless: runs-on: ubuntu-latest steps: diff --git a/tools/ci.sh b/tools/ci.sh index 82543362584..2f62f3efe95 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -480,13 +480,6 @@ function ci_stm32_misc_build { ######################################################################################## # ports/unix -CI_UNIX_OPTS_SYS_SETTRACE=( - MICROPY_PY_BTREE=0 - MICROPY_PY_FFI=0 - MICROPY_PY_SSL=0 - CFLAGS_EXTRA="-DMICROPY_PY_SYS_SETTRACE=1" -) - CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS=( MICROPY_PY_BTREE=0 MICROPY_PY_FFI=0 @@ -734,16 +727,6 @@ function ci_unix_float_clang_run_tests { ci_unix_run_tests_helper CC=clang } -function ci_unix_settrace_build { - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix submodules - make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SYS_SETTRACE[@]}" -} - -function ci_unix_settrace_run_tests { - ci_unix_run_tests_full_helper standard "${CI_UNIX_OPTS_SYS_SETTRACE[@]}" -} - function ci_unix_settrace_stackless_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix submodules From a8c2b917e2837541597f24f7220c8c73c16c2fa7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 3 Jul 2025 20:11:03 +0100 Subject: [PATCH 0847/2098] tools/ci.sh: Increase test timeout to 60s in coverage jobs. The additional overhead of the settrace profiler means that the `aes_stress.py` test was running too slowly on GitHub CI. Double the timeout to 60 seconds. Signed-off-by: Jeff Epler --- tests/run-tests.py | 2 +- tools/ci.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index d1d22a3c538..faf1d2e3b48 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -16,7 +16,7 @@ import tempfile # Maximum time to run a PC-based test, in seconds. -TEST_TIMEOUT = 30 +TEST_TIMEOUT = float(os.environ.get('MICROPY_TEST_TIMEOUT', 30)) # See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] # are guaranteed to always work, this one should though. diff --git a/tools/ci.sh b/tools/ci.sh index 2f62f3efe95..2f0b742428b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -625,7 +625,7 @@ function ci_unix_coverage_build { } function ci_unix_coverage_run_tests { - ci_unix_run_tests_full_helper coverage + MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage } function ci_unix_coverage_run_mpy_merge_tests { @@ -745,7 +745,7 @@ function ci_unix_sanitize_undefined_build { } function ci_unix_sanitize_undefined_run_tests { - ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" } function ci_unix_sanitize_address_build { @@ -756,7 +756,7 @@ function ci_unix_sanitize_address_build { } function ci_unix_sanitize_address_run_tests { - ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" + MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" } function ci_unix_macos_build { From a9801f9960ea2b8e94d5626840c97bc45286e1a3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 4 Jul 2025 09:56:29 +0100 Subject: [PATCH 0848/2098] github/workflows: Use Python 3.11 for unix coverage testing. This removes the need for an explicit `sys_settrace_features.py.exp` file. This means that people testing locally will also need to install Python 3.11 in some way, such as with pyenv or uv, and use it during `make VARIANT=coverage test`, or they will get failures. When using Python from GitHub actions/setup-python, pip3 can't be wrapped by sudo, because this invokes the operating system python instead. Signed-off-by: Jeff Epler --- .github/workflows/ports_unix.yml | 15 +++++++++++++++ tools/ci.sh | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index e3ebcfda3d8..4b22926eaf8 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -71,6 +71,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: source tools/ci.sh && ci_unix_coverage_setup - name: Build @@ -250,6 +255,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: source tools/ci.sh && ci_unix_coverage_setup - name: Build @@ -270,6 +280,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: source tools/ci.sh && ci_unix_coverage_setup - name: Build diff --git a/tools/ci.sh b/tools/ci.sh index 2f0b742428b..4007dfebfc3 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -612,9 +612,9 @@ function ci_unix_standard_v2_run_tests { } function ci_unix_coverage_setup { - sudo pip3 install setuptools - sudo pip3 install pyelftools - sudo pip3 install ar + pip3 install setuptools + pip3 install pyelftools + pip3 install ar gcc --version python3 --version } From 431b79146e4c8611144795eebf4e7645aa0c6591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Mon, 30 Jun 2025 13:52:52 +0200 Subject: [PATCH 0849/2098] esp32/panichandler: Support building against IDFv5.4.2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IDF panic handler resets the watchdog timeout to prevent the printing of the error message from being cut off by a WDT reset. We use the exact same function call in our wrapper function for the same purpose. In IDFv5.4.2 the function used for this was changed from `esp_panic_handler_reconfigure_wdts` to `esp_panic_handler_feed_wdts`, specifically in this commit: https://github.com/espressif/esp-idf/commit/cd887ef59a7b966a7f431754aaec6ee653849d77 Signed-off-by: Daniël van de Giessen --- ports/esp32/panichandler.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ports/esp32/panichandler.c b/ports/esp32/panichandler.c index e6ff98ded27..5211286f840 100644 --- a/ports/esp32/panichandler.c +++ b/ports/esp32/panichandler.c @@ -42,11 +42,20 @@ #endif void __real_esp_panic_handler(void *); -void esp_panic_handler_reconfigure_wdts(uint32_t timeout_ms); void panic_print_str(const char *str); +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) +void esp_panic_handler_reconfigure_wdts(uint32_t timeout_ms); +#else +void esp_panic_handler_feed_wdts(void); +#endif + void MICROPY_WRAP_PANICHANDLER_FUN(__wrap_esp_panic_handler)(void *info) { + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) esp_panic_handler_reconfigure_wdts(1000); + #else + esp_panic_handler_feed_wdts(); + #endif const static char *msg = MICROPY_WRAP_PANICHANDLER_STR( "\r\n" From abcf023554df96f34e80e6eb73b9b705523ac5c5 Mon Sep 17 00:00:00 2001 From: Daniel Campora Date: Wed, 28 May 2025 23:19:14 +0800 Subject: [PATCH 0850/2098] zephyr/machine_uart: Complete UART driver and make it interrupt driven. Before this commit the UART would only work in very simple use cases. Receiving large amounts of data would result in lost bytes. Plus the print function would crash due to `uart_config_get()` returning incorrect values. Additionally, receiving data with `timeout==0` would fail even if data was already available in the internal UART Rx FIFO. This commit fixes those issues. The non-implemented functions have also been made usable. Signed-off-by: Daniel Campora --- ports/zephyr/machine_uart.c | 194 ++++++++++++++++++++++++++++++++---- ports/zephyr/prj.conf | 5 + 2 files changed, 177 insertions(+), 22 deletions(-) diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c index 1927335ddf1..60613befb12 100644 --- a/ports/zephyr/machine_uart.c +++ b/ports/zephyr/machine_uart.c @@ -5,6 +5,7 @@ * * Copyright (c) 2016 Damien P. George * Copyright (c) 2020 Yonatan Schachter + * Copyright (c) 2025 Daniel Campora on behalf of REMOTE TECH LTD * * 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,16 +33,32 @@ #include #include "py/mperrno.h" +#include "py/ringbuf.h" #include "zephyr_device.h" -// The UART class doesn't have any constants for this port. -#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS + +#define MACHINE_UART_RTS 1 +#define MACHINE_UART_CTS 2 + +// This class needs a finalizer, so we add it here +#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(MACHINE_UART_RTS) }, \ + { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(MACHINE_UART_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_uart_deinit_obj) }, + +#define UART_RX_RING_BUF_DEF_SIZE 128 +#define UART_TX_RING_BUF_DEF_SIZE 128 + +static void uart_interrupt_handler(const struct device *dev, void *user_data); typedef struct _machine_uart_obj_t { mp_obj_base_t base; const struct device *dev; uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms) + ringbuf_t rx_ringbuffer; + ringbuf_t tx_ringbuffer; + bool tx_complete; } machine_uart_obj_t; static const char *_parity_name[] = {"None", "Odd", "Even", "Mark", "Space"}; @@ -60,23 +77,96 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ } 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) { - enum { ARG_timeout, ARG_timeout_char }; + enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_flow }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 115200} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_RX_RING_BUF_DEF_SIZE} }, + { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_TX_RING_BUF_DEF_SIZE} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); self->timeout = args[ARG_timeout].u_int; self->timeout_char = args[ARG_timeout_char].u_int; + + uint8_t data_bits; + if (args[ARG_bits].u_int == 5) { + data_bits = UART_CFG_DATA_BITS_5; + } else if (args[ARG_bits].u_int == 6) { + data_bits = UART_CFG_DATA_BITS_6; + } else if (args[ARG_bits].u_int == 7) { + data_bits = UART_CFG_DATA_BITS_7; + } else if (args[ARG_bits].u_int == 8) { + data_bits = UART_CFG_DATA_BITS_8; + } else if (args[ARG_bits].u_int == 9) { + data_bits = UART_CFG_DATA_BITS_9; + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid data bits")); + } + + uint8_t parity; + if (args[ARG_parity].u_obj == mp_const_none) { + parity = UART_CFG_PARITY_NONE; + } else if (mp_obj_get_int(args[ARG_parity].u_obj) == 0) { + parity = UART_CFG_PARITY_EVEN; + } else if (mp_obj_get_int(args[ARG_parity].u_obj) == 1) { + parity = UART_CFG_PARITY_ODD; + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid parity")); + } + + uint8_t stop_bits; + if (args[ARG_stop].u_int == 1) { + stop_bits = UART_CFG_STOP_BITS_1; + } else if (args[ARG_stop].u_int == 2) { + data_bits = UART_CFG_STOP_BITS_2; + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid stop bits")); + } + + uint8_t flow_ctrl; + if (args[ARG_flow].u_int == 0) { + flow_ctrl = UART_CFG_FLOW_CTRL_NONE; + } else if (args[ARG_flow].u_int == (MACHINE_UART_RTS | MACHINE_UART_CTS)) { + flow_ctrl = UART_CFG_FLOW_CTRL_RTS_CTS; + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid flow control")); + } + + const struct uart_config cfg = { + .baudrate = args[ARG_baudrate].u_int, + .parity = parity, + .stop_bits = args[ARG_stop].u_int, + .data_bits = data_bits, + .flow_ctrl = flow_ctrl + }; + + int ret = uart_configure(self->dev, &cfg); + if (ret < 0) { + mp_raise_OSError(-ret); + } + + ringbuf_alloc(&self->tx_ringbuffer, args[ARG_txbuf].u_int); + ringbuf_alloc(&self->rx_ringbuffer, args[ARG_rxbuf].u_int); + + uart_irq_callback_user_data_set(self->dev, uart_interrupt_handler, (void *)self); + uart_irq_rx_enable(self->dev); } static mp_obj_t mp_machine_uart_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_uart_obj_t *self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); - self->dev = zephyr_device_find(args[0]); + const struct device *dev = zephyr_device_find(args[0]); + machine_uart_obj_t *self = mp_obj_malloc_with_finaliser(machine_uart_obj_t, &machine_uart_type); + self->dev = dev; + self->tx_complete = true; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); @@ -86,37 +176,38 @@ 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; + uart_irq_rx_disable(self->dev); + uart_irq_tx_disable(self->dev); } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { - (void)self; - mp_raise_NotImplementedError(NULL); // TODO + return ringbuf_avail(&self->rx_ringbuffer); } static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { - (void)self; - mp_raise_NotImplementedError(NULL); // TODO + return self->tx_complete && !ringbuf_avail(&self->tx_ringbuffer) ? true : false; } 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); uint8_t *buffer = (uint8_t *)buf_in; - uint8_t data; mp_uint_t bytes_read = 0; size_t elapsed_ms = 0; size_t time_to_wait = self->timeout; - while ((elapsed_ms < time_to_wait) && (bytes_read < size)) { - if (!uart_poll_in(self->dev, &data)) { - buffer[bytes_read++] = data; + do { + int _rx_len = MIN(ringbuf_avail(&self->rx_ringbuffer), size - bytes_read); + if (_rx_len > 0) { + ringbuf_get_bytes(&self->rx_ringbuffer, &buffer[bytes_read], _rx_len); + bytes_read += _rx_len; elapsed_ms = 0; time_to_wait = self->timeout_char; } else { k_msleep(1); elapsed_ms++; } - } + } while ((elapsed_ms < time_to_wait) && (bytes_read < size)); + return bytes_read; } @@ -124,27 +215,86 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uint8_t *buffer = (uint8_t *)buf_in; - for (mp_uint_t i = 0; i < size; i++) { + // wait for any pending transmission to complete + while (!mp_machine_uart_txdone(self)) { + MICROPY_EVENT_POLL_HOOK; + } + + int _ex_size = 0; + int _free_space = ringbuf_free(&self->tx_ringbuffer); + if (size > _free_space) { + _ex_size = size - _free_space; + } + + // do a blocking tx of what doesn't fit into the outgoing ring buffer + for (mp_uint_t i = 0; i < _ex_size; i++) { uart_poll_out(self->dev, buffer[i]); } + ringbuf_put_bytes(&self->tx_ringbuffer, &buffer[_ex_size], size - _ex_size); + self->tx_complete = false; + uart_irq_tx_enable(self->dev); + return size; } static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - mp_uint_t ret; + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t ret = 0; if (request == MP_STREAM_POLL) { - ret = 0; - // read is always blocking - - if (arg & MP_STREAM_POLL_WR) { + uintptr_t flags = arg; + if ((flags & MP_STREAM_POLL_RD) && (mp_machine_uart_any(self) > 0)) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && mp_machine_uart_txdone(self)) { ret |= MP_STREAM_POLL_WR; } - return ret; + } else if (request == MP_STREAM_FLUSH) { + while (!mp_machine_uart_txdone(self)) { + MICROPY_EVENT_POLL_HOOK; + } } else { *errcode = MP_EINVAL; ret = MP_STREAM_ERROR; } + return ret; } + +static void uart_interrupt_handler(const struct device *dev, void *user_data) { + machine_uart_obj_t *self = (machine_uart_obj_t *)user_data; + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + uint8_t _rx_buffer[32]; + size_t _free_space = MIN(ringbuf_free(&self->rx_ringbuffer), sizeof(_rx_buffer)); + + // empty the uart fifo even if we can't store bytes anymore + // otherwise we will never exit this interrupt handler + int rcv_len = uart_fifo_read(dev, _rx_buffer, (_free_space > 0) ? _free_space : 1); + if ((rcv_len <= 0) || (_free_space == 0)) { + continue; + } + + ringbuf_put_bytes(&self->rx_ringbuffer, _rx_buffer, rcv_len); + } + + int _max_uart_tx_len = uart_irq_tx_ready(dev); + if (_max_uart_tx_len > 0) { + uint8_t _tx_buffer[32]; + size_t _buffer_tx_len; + + _max_uart_tx_len = MIN(_max_uart_tx_len, sizeof(_tx_buffer)); + _buffer_tx_len = ringbuf_avail(&self->tx_ringbuffer); + if (_buffer_tx_len > 0) { + _buffer_tx_len = MIN(_max_uart_tx_len, _buffer_tx_len); + ringbuf_get_bytes(&self->tx_ringbuffer, _tx_buffer, _buffer_tx_len); + uart_fifo_fill(dev, _tx_buffer, _buffer_tx_len); + } else if (uart_irq_tx_complete(dev)) { + uart_irq_tx_disable(dev); + self->tx_complete = true; + } + } + } +} diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 0325cddd206..7b7513abd9d 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -81,3 +81,8 @@ CONFIG_MICROPY_VFS_LFS2=y CONFIG_WATCHDOG=y CONFIG_WDT_DISABLE_AT_BOOT=y + +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_UART_LINE_CTRL=y +CONFIG_UART_USE_RUNTIME_CONFIGURE=y From 49dbe1e272f486077bd16a6e68ece6d66a2bea7f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 2 Jul 2025 11:42:18 +1000 Subject: [PATCH 0851/2098] zephyr/machine_pin: Configure OUT pin also as input so it's readable. Zephyr allows setting both GPIO_OUTPUT and GPIO_INPUT on a pin, which means it's an output pin that can have its current value read. Fixes issue #17596. Signed-off-by: Damien George --- ports/zephyr/machine_pin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 22401966900..1baee656f5a 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -270,7 +270,7 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { // class constants { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_INPUT) }, - { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_OUTPUT) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_OUTPUT | GPIO_INPUT) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_INT_EDGE_RISING) }, From 91718657826139017f5cdd54efe0ab9c16f7e49c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Jul 2025 11:13:42 +1000 Subject: [PATCH 0852/2098] zephyr/boards: Disable WDT on qemu boards and networking for cortex_m3. This gets qemu_x86 and qemu_cortex_m3 building with `prj.conf` settings. Signed-off-by: Damien George --- ports/zephyr/boards/qemu_cortex_m3.conf | 9 ++++++--- ports/zephyr/boards/qemu_x86.conf | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/boards/qemu_cortex_m3.conf b/ports/zephyr/boards/qemu_cortex_m3.conf index dac0c358dc9..50d0c4d9281 100644 --- a/ports/zephyr/boards/qemu_cortex_m3.conf +++ b/ports/zephyr/boards/qemu_cortex_m3.conf @@ -2,6 +2,9 @@ # disable it CONFIG_CONSOLE_SUBSYS=n -# Networking drivers -# SLIP driver for QEMU -CONFIG_NET_SLIP_TAP=y +# This QEMU target only has 256k ROM and 64k RAM, so disable networking +CONFIG_NETWORKING=n + +# QEMU doesn't have a watchdog, so disable it +CONFIG_WATCHDOG=n +CONFIG_WDT_DISABLE_AT_BOOT=n diff --git a/ports/zephyr/boards/qemu_x86.conf b/ports/zephyr/boards/qemu_x86.conf index dac0c358dc9..35461025aa8 100644 --- a/ports/zephyr/boards/qemu_x86.conf +++ b/ports/zephyr/boards/qemu_x86.conf @@ -5,3 +5,7 @@ CONFIG_CONSOLE_SUBSYS=n # Networking drivers # SLIP driver for QEMU CONFIG_NET_SLIP_TAP=y + +# QEMU doesn't have a watchdog, so disable it +CONFIG_WATCHDOG=n +CONFIG_WDT_DISABLE_AT_BOOT=n From 5321b666ea3cb1108a64d64f0813f602bcefd424 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Jul 2025 11:14:59 +1000 Subject: [PATCH 0853/2098] zephyr/README: Update URL describing QEMU network settings. Signed-off-by: Damien George --- ports/zephyr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index fc18d25c0aa..17c1f613de4 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -95,7 +95,7 @@ qemu_cortex_m3): Networking is enabled with the default configuration, so you need to follow instructions in -https://docs.zephyrproject.org/latest/guides/networking/qemu_setup.html#networking-with-qemu +https://docs.zephyrproject.org/latest/connectivity/networking/qemu_setup.html#networking-with-qemu to setup the host side of TAP/SLIP networking. If you get an error like: could not connect serial device to character backend 'unix:/tmp/slip.sock' From 5eb94df09ec5964822b9de64d6430de16901562d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Jul 2025 11:15:36 +1000 Subject: [PATCH 0854/2098] zephyr/prj.conf: Use UART for console as default, not CONSOLE_SUBSYS. Most boards enable the UART console because it's needed for USB (where USB CDC creates a virtual UART), and for ctrl-C to work. The `prj_minimal.conf` settings still use CONSOLE_SUBSYS. Fixes issue #17608. Signed-off-by: Damien George --- ports/zephyr/boards/bbc_microbit_v2.conf | 1 - ports/zephyr/boards/nucleo_h743zi.conf | 3 --- ports/zephyr/boards/nucleo_wb55rg.conf | 1 - ports/zephyr/boards/qemu_cortex_m3.conf | 4 ---- ports/zephyr/boards/qemu_x86.conf | 4 ---- ports/zephyr/prj.conf | 5 ----- ports/zephyr/prj_minimal.conf | 3 --- 7 files changed, 21 deletions(-) diff --git a/ports/zephyr/boards/bbc_microbit_v2.conf b/ports/zephyr/boards/bbc_microbit_v2.conf index 31872244ca7..43742078f56 100644 --- a/ports/zephyr/boards/bbc_microbit_v2.conf +++ b/ports/zephyr/boards/bbc_microbit_v2.conf @@ -1,4 +1,3 @@ -CONFIG_CONSOLE_SUBSYS=n CONFIG_NETWORKING=n CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y diff --git a/ports/zephyr/boards/nucleo_h743zi.conf b/ports/zephyr/boards/nucleo_h743zi.conf index 942415b7967..ea6a60b3529 100644 --- a/ports/zephyr/boards/nucleo_h743zi.conf +++ b/ports/zephyr/boards/nucleo_h743zi.conf @@ -1,6 +1,3 @@ -# disable console subsys to get REPL working -CONFIG_CONSOLE_SUBSYS=n - # flash config CONFIG_FLASH=y CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf index baf0f280755..fab993676ac 100644 --- a/ports/zephyr/boards/nucleo_wb55rg.conf +++ b/ports/zephyr/boards/nucleo_wb55rg.conf @@ -1,4 +1,3 @@ -CONFIG_CONSOLE_SUBSYS=n CONFIG_NETWORKING=n CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y diff --git a/ports/zephyr/boards/qemu_cortex_m3.conf b/ports/zephyr/boards/qemu_cortex_m3.conf index 50d0c4d9281..11eebd96063 100644 --- a/ports/zephyr/boards/qemu_cortex_m3.conf +++ b/ports/zephyr/boards/qemu_cortex_m3.conf @@ -1,7 +1,3 @@ -# Interrupt-driven UART console has emulation artifacts under QEMU, -# disable it -CONFIG_CONSOLE_SUBSYS=n - # This QEMU target only has 256k ROM and 64k RAM, so disable networking CONFIG_NETWORKING=n diff --git a/ports/zephyr/boards/qemu_x86.conf b/ports/zephyr/boards/qemu_x86.conf index 35461025aa8..1d04675df41 100644 --- a/ports/zephyr/boards/qemu_x86.conf +++ b/ports/zephyr/boards/qemu_x86.conf @@ -1,7 +1,3 @@ -# Interrupt-driven UART console has emulation artifacts under QEMU, -# disable it -CONFIG_CONSOLE_SUBSYS=n - # Networking drivers # SLIP driver for QEMU CONFIG_NET_SLIP_TAP=y diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 7b7513abd9d..011cb72cfd0 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -6,11 +6,6 @@ CONFIG_STDOUT_CONSOLE=y CONFIG_CONSOLE_HANDLER=y CONFIG_UART_CONSOLE_DEBUG_SERVER_HOOKS=y -CONFIG_CONSOLE_SUBSYS=y -CONFIG_CONSOLE_GETCHAR=y -CONFIG_CONSOLE_GETCHAR_BUFSIZE=258 -CONFIG_CONSOLE_PUTCHAR_BUFSIZE=128 - CONFIG_NEWLIB_LIBC=y CONFIG_FPU=y CONFIG_MAIN_STACK_SIZE=4736 diff --git a/ports/zephyr/prj_minimal.conf b/ports/zephyr/prj_minimal.conf index 6c850751d72..3f9acc655e9 100644 --- a/ports/zephyr/prj_minimal.conf +++ b/ports/zephyr/prj_minimal.conf @@ -8,8 +8,5 @@ CONFIG_CONSOLE_SUBSYS=y CONFIG_CONSOLE_GETCHAR=y 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 -#CONFIG_UART_CONSOLE=n CONFIG_MICROPY_HEAP_SIZE=16384 From 16d9e704aeb48d9507c56bf90f565c0b19b3fb79 Mon Sep 17 00:00:00 2001 From: David Schneider Date: Mon, 23 Jun 2025 14:52:57 +0200 Subject: [PATCH 0855/2098] zephyr: Update generated header path. Support disabled LEGACY_GENERATED_INCLUDE_PATH compatibility option. Since Zephyr 3.7 generated include files are namespaced. See also: zephyrproject-rtos/zephyr@bbe5e1e6ebf4a1a66c0 Signed-off-by: David Schneider --- ports/zephyr/modsocket.c | 2 -- ports/zephyr/mpconfigport.h | 2 +- ports/zephyr/mpconfigport_minimal.h | 2 +- ports/zephyr/prj.conf | 2 ++ ports/zephyr/prj_minimal.conf | 2 ++ 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ports/zephyr/modsocket.c b/ports/zephyr/modsocket.c index 1ca84edcac8..d8955bffbe4 100644 --- a/ports/zephyr/modsocket.c +++ b/ports/zephyr/modsocket.c @@ -32,8 +32,6 @@ #include #include -// Zephyr's generated version header -#include #include #include #include diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 3a6e9348622..e7e67b02d61 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -26,7 +26,7 @@ #include // Include Zephyr's autoconf.h, which should be made first by Zephyr makefiles -#include "autoconf.h" +#include // Included here to get basic Zephyr environment (macros, etc.) #include #include diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index 9aae20c72c6..a0a7f973946 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -26,7 +26,7 @@ #include // Include Zephyr's autoconf.h, which should be made first by Zephyr makefiles -#include "autoconf.h" +#include // Included here to get basic Zephyr environment (macros, etc.) #include diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 011cb72cfd0..0939e226cfc 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -81,3 +81,5 @@ CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_UART_LINE_CTRL=y CONFIG_UART_USE_RUNTIME_CONFIGURE=y + +CONFIG_LEGACY_GENERATED_INCLUDE_PATH=n diff --git a/ports/zephyr/prj_minimal.conf b/ports/zephyr/prj_minimal.conf index 3f9acc655e9..f58c932ceab 100644 --- a/ports/zephyr/prj_minimal.conf +++ b/ports/zephyr/prj_minimal.conf @@ -10,3 +10,5 @@ CONFIG_CONSOLE_GETCHAR_BUFSIZE=258 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=32 CONFIG_MICROPY_HEAP_SIZE=16384 + +CONFIG_LEGACY_GENERATED_INCLUDE_PATH=n From d9b4327c66d455f4654ab4f223067f3e20440333 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:52:05 +1000 Subject: [PATCH 0856/2098] zephyr/main: Execute boot.py and main.py like other ports. Changes here make the zephyr port act the same as other ports for the start up and shut down sequence: - `boot.py` is executed if it exists, and can force a soft reset - `main.py` is only executed if in friendly REPL and if `boot.py` executed successfully; and it can also force a soft reset - print "MPY: " before "soft reboot" on soft reset Signed-off-by: Damien George --- ports/zephyr/main.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 45af7b0c72c..9addd7d6cdb 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -156,7 +156,17 @@ int real_main(void) { #endif #if MICROPY_MODULE_FROZEN || MICROPY_VFS - pyexec_file_if_exists("main.py"); + // Execute user scripts. + int ret = pyexec_file_if_exists("boot.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL && ret != 0) { + ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + } #endif for (;;) { @@ -171,7 +181,11 @@ int real_main(void) { } } - printf("soft reboot\n"); + #if MICROPY_MODULE_FROZEN || MICROPY_VFS +soft_reset_exit: + #endif + + mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); #if MICROPY_PY_BLUETOOTH mp_bluetooth_deinit(); From 6b82eb75bef70d44ef583385301b7c483ac9ae94 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:53:49 +1000 Subject: [PATCH 0857/2098] zephyr/main: Add /flash/lib or /sd/lib to sys.path on start up. If there is a filesystem available, this change makes sure there is a "lib" in `sys.path`, eg so that "mip install" works correctly. Signed-off-by: Damien George --- ports/zephyr/CMakeLists.txt | 4 ++++ ports/zephyr/main.c | 4 ++++ ports/zephyr/qstrdefsport.h | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 ports/zephyr/qstrdefsport.h diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index debf2bd2c15..0ae4b26ac69 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -70,6 +70,10 @@ set(MICROPY_SOURCE_SHARED ) list(TRANSFORM MICROPY_SOURCE_SHARED PREPEND ${MICROPY_DIR}/shared/) +set(MICROPY_QSTRDEFS_PORT + ${MICROPY_PORT_DIR}/qstrdefsport.h +) + set(MICROPY_SOURCE_LIB oofatfs/ff.c oofatfs/ffunicode.c diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 9addd7d6cdb..eaef34a7868 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -99,6 +99,7 @@ static void vfs_init(void) { mp_obj_t bdev = NULL; mp_obj_t mount_point; const char *mount_point_str = NULL; + qstr path_lib_qstr = MP_QSTRnull; int ret = 0; #ifdef CONFIG_DISK_DRIVER_SDMMC @@ -109,15 +110,18 @@ static void vfs_init(void) { #endif bdev = MP_OBJ_TYPE_GET_SLOT(&zephyr_disk_access_type, make_new)(&zephyr_disk_access_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/sd"; + path_lib_qstr = MP_QSTR__slash_sd_slash_lib; #elif defined(CONFIG_FLASH_MAP) && FIXED_PARTITION_EXISTS(storage_partition) mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(FIXED_PARTITION_ID(storage_partition)), MP_OBJ_NEW_SMALL_INT(4096) }; bdev = MP_OBJ_TYPE_GET_SLOT(&zephyr_flash_area_type, make_new)(&zephyr_flash_area_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/flash"; + path_lib_qstr = MP_QSTR__slash_flash_slash_lib; #endif if ((bdev != NULL)) { mount_point = mp_obj_new_str_from_cstr(mount_point_str); ret = mp_vfs_mount_and_chdir_protected(bdev, mount_point); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(path_lib_qstr)); // TODO: if this failed, make a new file system and try to mount again } } diff --git a/ports/zephyr/qstrdefsport.h b/ports/zephyr/qstrdefsport.h new file mode 100644 index 00000000000..66819e432c7 --- /dev/null +++ b/ports/zephyr/qstrdefsport.h @@ -0,0 +1,4 @@ +// qstrs specific to this port +// *FORMAT-OFF* +Q(/flash/lib) +Q(/sd/lib) From 4951a06bbb5b3987a8ac922c06b8764c083d168c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:56:04 +1000 Subject: [PATCH 0858/2098] zephyr: Enable sys.stdin/out/err. This change enables `sys.stdin`, `sys.stdout` and `sys.stderr` objects. They are useful for general IO, and also help with testing zephyr boards. Signed-off-by: Damien George --- ports/zephyr/CMakeLists.txt | 1 + ports/zephyr/mpconfigport.h | 1 + ports/zephyr/src/zephyr_getchar.c | 5 +++++ ports/zephyr/src/zephyr_getchar.h | 1 + ports/zephyr/uart_core.c | 19 +++++++++++++++++++ 5 files changed, 27 insertions(+) diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 0ae4b26ac69..c15d68babed 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -66,6 +66,7 @@ set(MICROPY_SOURCE_SHARED runtime/mpirq.c runtime/pyexec.c runtime/stdout_helpers.c + runtime/sys_stdio_mphal.c timeutils/timeutils.c ) list(TRANSFORM MICROPY_SOURCE_SHARED PREPEND ${MICROPY_DIR}/shared/) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index e7e67b02d61..b6f9176b6a7 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -96,6 +96,7 @@ #define MICROPY_PY_ZEPHYR (1) #define MICROPY_PY_ZSENSOR (1) #define MICROPY_PY_SYS_MODULES (0) +#define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_PY_BUILTINS_COMPLEX (0) diff --git a/ports/zephyr/src/zephyr_getchar.c b/ports/zephyr/src/zephyr_getchar.c index 94e35e2e84e..7660e3cc1f1 100644 --- a/ports/zephyr/src/zephyr_getchar.c +++ b/ports/zephyr/src/zephyr_getchar.c @@ -49,6 +49,11 @@ static int console_irq_input_hook(uint8_t ch) { return 1; } +// Returns true if a char is available for reading. +int zephyr_getchar_check(void) { + return i_get != i_put; +} + int zephyr_getchar(void) { mp_hal_wait_sem(&uart_sem, 0); if (k_sem_take(&uart_sem, K_MSEC(0)) == 0) { diff --git a/ports/zephyr/src/zephyr_getchar.h b/ports/zephyr/src/zephyr_getchar.h index fee899e1b43..3f31c4317fa 100644 --- a/ports/zephyr/src/zephyr_getchar.h +++ b/ports/zephyr/src/zephyr_getchar.h @@ -17,4 +17,5 @@ #include void zephyr_getchar_init(void); +int zephyr_getchar_check(void); int zephyr_getchar(void); diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index ee525c33f72..fe8a2a51db9 100644 --- a/ports/zephyr/uart_core.c +++ b/ports/zephyr/uart_core.c @@ -26,6 +26,7 @@ #include #include "py/mpconfig.h" #include "py/runtime.h" +#include "py/stream.h" #include "src/zephyr_getchar.h" // Zephyr headers #include @@ -52,6 +53,24 @@ static uint8_t mp_console_txbuf[CONFIG_CONSOLE_PUTCHAR_BUFSIZE]; * Core UART functions to implement for a port */ +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if (poll_flags & MP_STREAM_POLL_RD) { + #ifdef CONFIG_CONSOLE_SUBSYS + // It's not easy to test if tty is readable, so just unconditionally set it for now. + ret |= MP_STREAM_POLL_RD; + #else + if (zephyr_getchar_check()) { + ret |= MP_STREAM_POLL_RD; + } + #endif + } + if (poll_flags & MP_STREAM_POLL_WR) { + ret |= MP_STREAM_POLL_WR; + } + return ret; +} + // Receive single character int mp_hal_stdin_rx_chr(void) { for (;;) { From 6a53319336ce234757e04223cfe8560d6bea2de6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:56:35 +1000 Subject: [PATCH 0859/2098] zephyr/src: Increase UART input buffer to 512 bytes and reduce latency. There are two changes here: 1. Increase the UART input bufffer to 512 bytes. That's necessary to get basic REPL reliability tests working, and helps improve `mpremote` usage, eg copying large files. 2. Remove `uart_sem` semaphore. This is no longer needed because `zephyr_getchar()` should be fully non-blocking and have as low a latency as possible. `mp_hal_stdin_rx_chr()` (which calls `zephyr_getchar`) already uses `MICROPY_EVENT_POLL_HOOK` to get an efficient wait, and doing an extra wait and check for the semaphore in `zephyr_getchar()` just introduces unnecessary latency and can lead to slower input, and potentially overflowing the UART input buffer. Signed-off-by: Damien George --- ports/zephyr/src/zephyr_getchar.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ports/zephyr/src/zephyr_getchar.c b/ports/zephyr/src/zephyr_getchar.c index 7660e3cc1f1..bf504a97c9b 100644 --- a/ports/zephyr/src/zephyr_getchar.c +++ b/ports/zephyr/src/zephyr_getchar.c @@ -23,12 +23,10 @@ extern int mp_interrupt_char; void mp_sched_keyboard_interrupt(void); void mp_hal_signal_event(void); -void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms); -static struct k_sem uart_sem; -#define UART_BUFSIZE 256 +#define UART_BUFSIZE (512) static uint8_t uart_ringbuf[UART_BUFSIZE]; -static uint8_t i_get, i_put; +static uint16_t i_get, i_put; static int console_irq_input_hook(uint8_t ch) { int i_next = (i_put + 1) & (UART_BUFSIZE - 1); @@ -44,8 +42,6 @@ static int console_irq_input_hook(uint8_t ch) { uart_ringbuf[i_put] = ch; i_put = i_next; } - // printk("%x\n", ch); - k_sem_give(&uart_sem); return 1; } @@ -55,8 +51,7 @@ int zephyr_getchar_check(void) { } int zephyr_getchar(void) { - mp_hal_wait_sem(&uart_sem, 0); - if (k_sem_take(&uart_sem, K_MSEC(0)) == 0) { + if (i_get != i_put) { unsigned int key = irq_lock(); int c = (int)uart_ringbuf[i_get++]; i_get &= UART_BUFSIZE - 1; @@ -67,7 +62,6 @@ int zephyr_getchar(void) { } void zephyr_getchar_init(void) { - k_sem_init(&uart_sem, 0, UINT_MAX); uart_console_in_debug_hook_install(console_irq_input_hook); // All NULLs because we're interested only in the callback above uart_register_input(NULL, NULL, NULL); From 6fd069e8a5b8139c0a5aaec506d3d83c60285ac4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:57:41 +1000 Subject: [PATCH 0860/2098] zephyr/src: Fix USB device_next driver to work with zephyr 4.0.0. The blocklist argument is not available in zephyr 4.0.0. Signed-off-by: Damien George --- ports/zephyr/src/usbd.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c index 2444706cbea..36b07a8638f 100644 --- a/ports/zephyr/src/usbd.c +++ b/ports/zephyr/src/usbd.c @@ -34,12 +34,22 @@ #include LOG_MODULE_REGISTER(mp_usbd); +#if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(4, 1, 0) + +#define BLOCKLIST , blocklist + /* By default, do not register the USB DFU class DFU mode instance. */ static const char *const blocklist[] = { "dfu_dfu", NULL, }; +#else + +#define BLOCKLIST + +#endif + USBD_DEVICE_DEFINE(mp_usbd, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), CONFIG_MICROPY_USB_DEVICE_VID, CONFIG_MICROPY_USB_DEVICE_PID); @@ -121,7 +131,7 @@ struct usbd_context *mp_usbd_init_device(usbd_msg_cb_t msg_cb) { return NULL; } - err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_HS, 1, blocklist); + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_HS, 1 BLOCKLIST); if (err) { LOG_ERR("Failed to add register classes"); return NULL; @@ -137,7 +147,7 @@ struct usbd_context *mp_usbd_init_device(usbd_msg_cb_t msg_cb) { return NULL; } - err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_FS, 1, blocklist); + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_FS, 1 BLOCKLIST); if (err) { LOG_ERR("Failed to add register classes"); return NULL; From 9b97a30943ebf5fe381c29610d4b5cd1046240d7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:58:49 +1000 Subject: [PATCH 0861/2098] zephyr/machine_pin: Add Pin.OPEN_DRAIN constant. Adding this constant is all that's needed to support open-drain pins. Signed-off-by: Damien George --- ports/zephyr/machine_pin.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 1baee656f5a..818216ffef1 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -271,6 +271,7 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { // class constants { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_INPUT) }, { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_OUTPUT | GPIO_INPUT) }, + { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_OUTPUT | GPIO_INPUT | GPIO_OPEN_DRAIN) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_INT_EDGE_RISING) }, From 359887933cf179bf7c4d9c5dec69aecfbd9a14fb Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:59:51 +1000 Subject: [PATCH 0862/2098] zephyr/machine_pin: Allow constructing a Pin with an existing Pin. Following other ports. Signed-off-by: Damien George --- ports/zephyr/machine_pin.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 818216ffef1..517ef51167a 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -126,19 +126,26 @@ static mp_obj_t machine_pin_obj_init_helper(machine_pin_obj_t *self, size_t n_ar mp_obj_t mp_pin_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); - // get the wanted port - if (!mp_obj_is_type(args[0], &mp_type_tuple)) { + machine_pin_obj_t *pin; + if (mp_obj_is_type(args[0], &machine_pin_type)) { + // Already a Pin object, reuse it. + pin = MP_OBJ_TO_PTR(args[0]); + } else if (mp_obj_is_type(args[0], &mp_type_tuple)) { + // Get the wanted (port, pin) values. + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[0], 2, &items); + const struct device *wanted_port = zephyr_device_find(items[0]); + int wanted_pin = mp_obj_get_int(items[1]); + + pin = m_new_obj(machine_pin_obj_t); + pin->base = machine_pin_obj_template; + pin->port = wanted_port; + pin->pin = wanted_pin; + pin->irq = NULL; + } else { + // Unknown Pin. mp_raise_ValueError(MP_ERROR_TEXT("Pin id must be tuple of (\"GPIO_x\", pin#)")); } - mp_obj_t *items; - mp_obj_get_array_fixed_n(args[0], 2, &items); - const struct device *wanted_port = zephyr_device_find(items[0]); - int wanted_pin = mp_obj_get_int(items[1]); - - machine_pin_obj_t *pin = m_new_obj(machine_pin_obj_t); - pin->base = machine_pin_obj_template; - pin->port = wanted_port; - pin->pin = wanted_pin; if (n_args > 1 || n_kw > 0) { // pin mode given, so configure this GPIO From 19814bf50fe19f3f276646a51be4f392a0d6ff64 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:00:15 +1000 Subject: [PATCH 0863/2098] zephyr/mphalport: Implement C-level pin HAL. Signed-off-by: Damien George --- ports/zephyr/machine_pin.c | 1 + ports/zephyr/mphalport.c | 8 +++++++ ports/zephyr/mphalport.h | 46 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 517ef51167a..7834b5de6d2 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -36,6 +36,7 @@ #include "py/gc.h" #include "py/mphal.h" #include "extmod/modmachine.h" +#include "extmod/virtpin.h" #include "shared/runtime/mpirq.h" #include "modmachine.h" #include "zephyr_device.h" diff --git a/ports/zephyr/mphalport.c b/ports/zephyr/mphalport.c index 2c950328432..db536ec0851 100644 --- a/ports/zephyr/mphalport.c +++ b/ports/zephyr/mphalport.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "extmod/modmachine.h" static struct k_poll_signal wait_signal; static struct k_poll_event wait_events[2] = { @@ -75,3 +76,10 @@ void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms) { } } } + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in) { + if (mp_obj_is_type(pin_in, &machine_pin_type)) { + return MP_OBJ_TO_PTR(pin_in); + } + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); +} diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index e5414c27059..7410204621e 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -1,4 +1,5 @@ #include +#include #include "shared/runtime/interrupt_char.h" #define MICROPY_BEGIN_ATOMIC_SECTION irq_lock @@ -35,3 +36,48 @@ static inline uint64_t mp_hal_time_ns(void) { } #define mp_hal_delay_us_fast(us) (mp_hal_delay_us(us)) + +// C-level pin HAL + +#include "modmachine.h" + +#define MP_HAL_PIN_FMT "%u" +#define mp_hal_pin_obj_t const machine_pin_obj_t * + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); + +static inline unsigned int mp_hal_pin_name(mp_hal_pin_obj_t pin) { + // TODO make it include the port + return pin->pin; +} + +static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { + (void)gpio_pin_configure(pin->port, pin->pin, GPIO_INPUT); +} + +static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { + if (gpio_pin_configure(pin->port, pin->pin, GPIO_OUTPUT | GPIO_INPUT) == -ENOTSUP) { + // If GPIO_OUTPUT|GPIO_INPUT is not supported (eg frdm_k64f) then try just GPIO_OUTPUT. + (void)gpio_pin_configure(pin->port, pin->pin, GPIO_OUTPUT); + } +} + +static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { + (void)gpio_pin_configure(pin->port, pin->pin, GPIO_OUTPUT | GPIO_INPUT | GPIO_OPEN_DRAIN); +} + +static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { + return gpio_pin_get_raw(pin->port, pin->pin); +} + +static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { + (void)gpio_pin_set_raw(pin->port, pin->pin, v); +} + +static inline void mp_hal_pin_od_low(mp_hal_pin_obj_t pin) { + (void)gpio_pin_set_raw(pin->port, pin->pin, 0); +} + +static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { + (void)gpio_pin_set_raw(pin->port, pin->pin, 1); +} From 16b00cd6e78d9afc9f5c3930939fdae0d60dfb78 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:00:48 +1000 Subject: [PATCH 0864/2098] zephyr/mpconfigport: Enable machine.SoftI2C and machine.SoftSPI. These work now that the C-level pin HAL is implemented. Signed-off-by: Damien George --- ports/zephyr/CMakeLists.txt | 6 ++++++ ports/zephyr/mpconfigport.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index c15d68babed..a5cc477204e 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -71,6 +71,11 @@ set(MICROPY_SOURCE_SHARED ) list(TRANSFORM MICROPY_SOURCE_SHARED PREPEND ${MICROPY_DIR}/shared/) +set(MICROPY_SOURCE_DRIVERS + bus/softspi.c +) +list(TRANSFORM MICROPY_SOURCE_DRIVERS PREPEND ${MICROPY_DIR}/drivers/) + set(MICROPY_QSTRDEFS_PORT ${MICROPY_PORT_DIR}/qstrdefsport.h ) @@ -89,6 +94,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_PY} ${MICROPY_SOURCE_EXTMOD} ${MICROPY_SOURCE_SHARED} + ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} ) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index b6f9176b6a7..1ab838ad9c7 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -62,9 +62,11 @@ #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" #define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_TRANSFER_MSB) #define MICROPY_PY_MACHINE_SPI_LSB (SPI_TRANSFER_LSB) +#define MICROPY_PY_MACHINE_SOFTSPI (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/zephyr/machine_uart.c" From 07285323cf68435db86b3e736a714a487464dceb Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:01:33 +1000 Subject: [PATCH 0865/2098] zephyr/mpconfigport: Enable import of mpy and a few related features. Support for importing .mpy files is quite fundamental to MicroPython these days, eg it allows installing more efficient .mpy code via "mip install" (and installing `unittest` only works with the .mpy version because the .py version uses f-strings, which are not enabled on the zephyr port). So enable it generally for use by all boards. As part of this, also enable: - min/max: needed by `micropython/import_mpy_invalid.py`, and widely used - sys.modules: needed by `run-tests.py` to run .mpy tests with --via-mpy - io module: needed to run .mpy tests, and useful for `io.IOBase` - array slice assign: needed to run .mpy tests, and generally useful as a way to do a memory copy. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 1ab838ad9c7..a24caf74578 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -36,6 +36,7 @@ #define MICROPY_HEAP_SIZE (16 * 1024) #endif +#define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_ENABLE_SOURCE_LINE (1) #define MICROPY_STACK_CHECK (1) #define MICROPY_ENABLE_GC (1) @@ -46,7 +47,6 @@ #define MICROPY_PY_ASYNC_AWAIT (0) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) #define MICROPY_PY_BUILTINS_FILTER (0) -#define MICROPY_PY_BUILTINS_MIN_MAX (0) #define MICROPY_PY_BUILTINS_PROPERTY (0) #define MICROPY_PY_BUILTINS_RANGE_ATTRS (0) #define MICROPY_PY_BUILTINS_REVERSED (0) @@ -55,9 +55,9 @@ #define MICROPY_PY_BUILTINS_HELP (1) #define MICROPY_PY_BUILTINS_HELP_TEXT zephyr_help_text #define MICROPY_PY_ARRAY (0) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) #define MICROPY_PY_COLLECTIONS (0) #define MICROPY_PY_CMATH (0) -#define MICROPY_PY_IO (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" @@ -97,7 +97,6 @@ #define MICROPY_PY_TIME_INCLUDEFILE "ports/zephyr/modtime.c" #define MICROPY_PY_ZEPHYR (1) #define MICROPY_PY_ZSENSOR (1) -#define MICROPY_PY_SYS_MODULES (0) #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) From d0ccaff5b719e72456b92e2f40daa93a182801a1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:01:47 +1000 Subject: [PATCH 0866/2098] zephyr/mpconfigport: Enable MICROPY_NLR_THUMB_USE_LONG_JUMP. Needed for some ARMv6M boards, eg rpi_pico. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index a24caf74578..bf85ef6eaa6 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -36,6 +36,9 @@ #define MICROPY_HEAP_SIZE (16 * 1024) #endif +// We can't guarantee object layout of nlr code so use long jump by default. +#define MICROPY_NLR_THUMB_USE_LONG_JUMP (1) + #define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_ENABLE_SOURCE_LINE (1) #define MICROPY_STACK_CHECK (1) From 35880d432ae5f9763ebab7a1d8ce1798662898ed Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:02:16 +1000 Subject: [PATCH 0867/2098] zephyr/boards/frdm_k64f: Improve board configuration. Changes: - Enable CONFIG_PWM so that `machine.PWM()` works. - Increase MicroPython GC heap size. Signed-off-by: Damien George --- ports/zephyr/boards/frdm_k64f.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/zephyr/boards/frdm_k64f.conf b/ports/zephyr/boards/frdm_k64f.conf index c164c0c32c5..71f8cc178bc 100644 --- a/ports/zephyr/boards/frdm_k64f.conf +++ b/ports/zephyr/boards/frdm_k64f.conf @@ -3,6 +3,7 @@ CONFIG_NET_L2_ETHERNET=y # Hardware features CONFIG_I2C=y +CONFIG_PWM=y CONFIG_SPI=y # Sensor drivers @@ -14,3 +15,6 @@ CONFIG_FXOS8700_TEMP=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_MPU_ALLOW_FLASH_WRITE=y + +# MicroPython config +CONFIG_MICROPY_HEAP_SIZE=114688 From 0faddb3c8a0f44e04577c8f6e46bf0d1e976d2c9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:02:36 +1000 Subject: [PATCH 0868/2098] zephyr/boards/nucleo_wb55rg: Enable BLE, I2C, SPI and add filesystem. Bluetooth works well now on this board, so enable all supported features. Also increase the MicroPython GC heap size to make use of the available RAM. Unfortunately the filesystem does not match the stm32 port's NUCLEO_WB55 configuration. That's not possible to do because stm32 uses a 512 byte flash erase size, while zephyr uses 4096 bytes. But at least here in zephyr there's now a sizable and usable filesystem. Signed-off-by: Damien George --- ports/zephyr/boards/nucleo_wb55rg.conf | 16 ++++++++++++++++ ports/zephyr/boards/nucleo_wb55rg.overlay | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 ports/zephyr/boards/nucleo_wb55rg.overlay diff --git a/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf index fab993676ac..adfab367c89 100644 --- a/ports/zephyr/boards/nucleo_wb55rg.conf +++ b/ports/zephyr/boards/nucleo_wb55rg.conf @@ -1,5 +1,21 @@ CONFIG_NETWORKING=n + +# Hardware features +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_I2C=y +CONFIG_SPI=y + +# Bluetooth CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_CENTRAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_L2CAP_TX_MTU=252 +CONFIG_BT_BUF_ACL_RX_SIZE=256 +CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n + +# MicroPython config +CONFIG_MICROPY_HEAP_SIZE=131072 diff --git a/ports/zephyr/boards/nucleo_wb55rg.overlay b/ports/zephyr/boards/nucleo_wb55rg.overlay new file mode 100644 index 00000000000..d9a4d3f24c5 --- /dev/null +++ b/ports/zephyr/boards/nucleo_wb55rg.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Damien P. George + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Delete the defined partitions and create bigger one for storage. */ +/delete-node/ &slot1_partition; +/delete-node/ &scratch_partition; +/delete-node/ &storage_partition; +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Storage slot: 424 KiB placed after slot0_partition. */ + storage_partition: partition@70000 { + label = "storage"; + reg = <0x00070000 DT_SIZE_K(424)>; + }; + }; +}; From ec65cac971263975b32ec9fc73db512fdeb21ad7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:40:11 +1000 Subject: [PATCH 0869/2098] zephyr/boards/rpi_pico: Add board configuration for rpi_pico. Although the rpi_pico can already build and run with the zephyr port, this configuration improves it in a number of ways: - Use the USB CDC ACM as the REPL, rather than just a UART. - Enable I2C and SPI, and add I2C1. - Enable a filesystem, which matches exactly the rp2 port's RPI_PICO configuration. So switching between zephyr and rp2 is possible and will retain the filesystem. - Make the MicroPython GC heap make the most use of the available RAM. Signed-off-by: Damien George --- ports/zephyr/boards/rpi_pico.conf | 19 +++++++++++ ports/zephyr/boards/rpi_pico.overlay | 48 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 ports/zephyr/boards/rpi_pico.conf create mode 100644 ports/zephyr/boards/rpi_pico.overlay diff --git a/ports/zephyr/boards/rpi_pico.conf b/ports/zephyr/boards/rpi_pico.conf new file mode 100644 index 00000000000..6b31bc9f98b --- /dev/null +++ b/ports/zephyr/boards/rpi_pico.conf @@ -0,0 +1,19 @@ +# Disable floating point hardware. +CONFIG_FPU=n + +# Configure serial console over USB CDC ACM. +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USBD_CDC_ACM_CLASS=y +CONFIG_UART_LINE_CTRL=y + +# Disable networking. +CONFIG_NETWORKING=n + +# Hardware features. +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_I2C=y +CONFIG_SPI=y + +# MicroPython config. +CONFIG_MICROPY_HEAP_SIZE=196608 diff --git a/ports/zephyr/boards/rpi_pico.overlay b/ports/zephyr/boards/rpi_pico.overlay new file mode 100644 index 00000000000..d63ed73bdf6 --- /dev/null +++ b/ports/zephyr/boards/rpi_pico.overlay @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Damien P. George + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + /* Use USB CDC ACM as the console. */ + zephyr,console = &cdc_acm_uart0; + }; +}; + +/* Delete defined partitions and make a layout matching the rp2 port RPI_PICO configuration. */ +/delete-node/ &code_partition; +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Code slot: 640 KiB - 0x100 placed after second-stage bootloader. */ + code_partition: partition@100 { + label = "code-partition"; + reg = <0x00000100 (DT_SIZE_K(640) - 0x100)>; + read-only; + }; + + /* Storage slot: 1408 KiB placed after code_partition. */ + storage_partition: partition@a0000 { + label = "storage"; + reg = <0x000a0000 DT_SIZE_K(1408)>; + }; + }; +}; + +&zephyr_udc0 { + cdc_acm_uart0: cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +&i2c1 { + clock-frequency = ; + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-names = "default"; +}; From 688161830717ad34fe8b499455c2fcafee662c62 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 6 Jul 2025 20:31:16 +1000 Subject: [PATCH 0870/2098] zephyr/mpconfigport: Enable sys.maxsize. Costs +48 bytes. Useful to introspect the target. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index bf85ef6eaa6..576618e3687 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -100,6 +100,7 @@ #define MICROPY_PY_TIME_INCLUDEFILE "ports/zephyr/modtime.c" #define MICROPY_PY_ZEPHYR (1) #define MICROPY_PY_ZSENSOR (1) +#define MICROPY_PY_SYS_MAXSIZE (1) #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) @@ -150,6 +151,8 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM +#define MP_SSIZE_MAX (0x7fffffff) + // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, From 0fd65c44cf23b22a582a86409ca8408077dd0a94 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 6 Jul 2025 21:59:55 +1000 Subject: [PATCH 0871/2098] zephyr/mpconfigport: Enable emergency exception buffer. Needed to pass exception tests. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 576618e3687..62226a2ded7 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -46,6 +46,7 @@ #define MICROPY_ENABLE_FINALISER (MICROPY_VFS) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_AUTO_INDENT (1) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_KBD_EXCEPTION (1) #define MICROPY_PY_ASYNC_AWAIT (0) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) From bf432a3e0f542321b15fcc775f62f072ba56f29d Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 6 Jul 2025 22:00:27 +1000 Subject: [PATCH 0872/2098] zephyr/machine_timer: Make machine.Timer id argument optional. With a default of -1, for soft timer. This matches other ports, and the `extmod/machine_timer.c` implementation. This change allows the `tests/extmod/machine_soft_timer.py` test to pass. Signed-off-by: Damien George --- ports/zephyr/machine_timer.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ports/zephyr/machine_timer.c b/ports/zephyr/machine_timer.c index 8ab2f629131..1bb743c2eb9 100644 --- a/ports/zephyr/machine_timer.c +++ b/ports/zephyr/machine_timer.c @@ -77,9 +77,14 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr } 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); - - if (mp_obj_get_int(args[0]) != -1) { + // Get timer id (only soft timer (-1) supported at the moment) + mp_int_t id = -1; + if (n_args > 0) { + id = mp_obj_get_int(args[0]); + --n_args; + ++args; + } + if (id != -1) { mp_raise_ValueError(MP_ERROR_TEXT("only virtual timers are supported")); } @@ -90,10 +95,10 @@ 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; - if (n_args > 1 || n_kw > 0) { + if (n_args > 0 || n_kw > 0) { mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_timer_init_helper(self, n_args - 1, args + 1, &kw_args); + machine_timer_init_helper(self, n_args, args, &kw_args); } return self; } From 9a9e5529af9012534f2935474476d631c1e7cb81 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 11:12:45 +1000 Subject: [PATCH 0873/2098] zephyr/machine_pin: Retry configuring gpio with just GPIO_OUTPUT. Some targets like frdm_k64f don't support GPIO_OUTPUT|GPIO_INPUT, so just use GPIO_OUTPUT in those cases (it seems they still support reading the current output state even when configured only as GPIO_OUTPUT, unlike other targets which require both settings). Signed-off-by: Damien George --- ports/zephyr/machine_pin.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 7834b5de6d2..e0718588d65 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -116,6 +116,10 @@ static mp_obj_t machine_pin_obj_init_helper(machine_pin_obj_t *self, size_t n_ar } int ret = gpio_pin_configure(self->port, self->pin, mode | pull | init); + if (ret == -ENOTSUP && mode == (GPIO_OUTPUT | GPIO_INPUT)) { + // Some targets (eg frdm_k64f) don't support GPIO_OUTPUT|GPIO_INPUT, so try again with just GPIO_OUTPUT. + ret = gpio_pin_configure(self->port, self->pin, GPIO_OUTPUT | pull | init); + } if (ret) { mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); } From ea2000b81d2007e373a302d5361455147c693dbb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:23:36 +1000 Subject: [PATCH 0874/2098] stm32/adc: Apply re-read errata for WB55. Following 17898f8607dc4fb881e860719cc1906d304e60f4. Signed-off-by: Damien George --- ports/stm32/adc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 3549fc29a98..239175bac6d 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -464,10 +464,13 @@ static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) static uint32_t adc_read_channel(ADC_HandleTypeDef *adcHandle) { uint32_t value; - #if defined(STM32G4) - // For STM32G4 there is errata 2.7.7, "Wrong ADC result if conversion done late after - // calibration or previous conversion". According to the errata, this can be avoided - // by performing two consecutive ADC conversions and keeping the second result. + #if defined(STM32G4) || defined(STM32WB) + // For STM32G4 errata 2.7.7 / STM32WB errata 2.7.1: + // "Wrong ADC result if conversion done late after calibration or previous conversion" + // states an incorrect reading is returned if more than 1ms has elapsed since the last + // reading or calibration. According to the errata, this can be avoided by performing + // two consecutive ADC conversions and keeping the second result. + // Note: On STM32WB55 @ 64Mhz each ADC read takes ~ 3us. for (uint8_t i = 0; i < 2; i++) #endif { From 841439d5fb285a748acb0bdfa8c0cac7af7b10a7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:24:21 +1000 Subject: [PATCH 0875/2098] stm32/adc: Simplify ADC calibration settings. Combine the common settings for L1/L4/WB with existing G0/G4/H5 settings. Signed-off-by: Damien George --- ports/stm32/adc.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 239175bac6d..34c52f48955 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -107,12 +107,12 @@ #define ADC_CAL2 ((uint16_t *)(ADC_CAL_ADDRESS + 4)) #define ADC_CAL_BITS (12) -#elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) +#elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L1) || defined(STM32L4) || defined(STM32WB) #define ADC_SCALE_V (((float)VREFINT_CAL_VREF) / 1000.0f) -#define ADC_CAL_ADDRESS VREFINT_CAL_ADDR -#define ADC_CAL1 TEMPSENSOR_CAL1_ADDR -#define ADC_CAL2 TEMPSENSOR_CAL2_ADDR +#define ADC_CAL_ADDRESS (VREFINT_CAL_ADDR) +#define ADC_CAL1 (TEMPSENSOR_CAL1_ADDR) +#define ADC_CAL2 (TEMPSENSOR_CAL2_ADDR) #define ADC_CAL_BITS (12) // UM2319/UM2570, __HAL_ADC_CALC_TEMPERATURE: 'corresponds to a resolution of 12 bits' #elif defined(STM32H7) @@ -123,22 +123,6 @@ #define ADC_CAL2 ((uint16_t *)(0x1FF1E840)) #define ADC_CAL_BITS (16) -#elif defined(STM32L1) - -#define ADC_SCALE_V (VREFINT_CAL_VREF / 1000.0f) -#define ADC_CAL_ADDRESS (VREFINT_CAL_ADDR) -#define ADC_CAL1 (TEMPSENSOR_CAL1_ADDR) -#define ADC_CAL2 (TEMPSENSOR_CAL2_ADDR) -#define ADC_CAL_BITS (12) - -#elif defined(STM32L4) || defined(STM32WB) - -#define ADC_SCALE_V (VREFINT_CAL_VREF / 1000.0f) -#define ADC_CAL_ADDRESS (VREFINT_CAL_ADDR) -#define ADC_CAL1 (TEMPSENSOR_CAL1_ADDR) -#define ADC_CAL2 (TEMPSENSOR_CAL2_ADDR) -#define ADC_CAL_BITS (12) - #else #error Unsupported processor From c9adabc25a648a3d3d1e32bc60866a1bad30ffc5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:25:10 +1000 Subject: [PATCH 0876/2098] stm32/adc: Fix core temperature reading on WB55. It needs a divisor of 100 because the calibration temperatures are 30 and 130 degrees, similar to the H5. Signed-off-by: Damien George --- ports/stm32/adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 34c52f48955..10d78125927 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -923,7 +923,7 @@ float adc_read_core_temp_float(ADC_HandleTypeDef *adcHandle) { return 0; } float core_temp_avg_slope = (*ADC_CAL2 - *ADC_CAL1) / 100.0f; - #elif defined(STM32H5) + #elif defined(STM32H5) || defined(STM32WB) int32_t raw_value = adc_config_and_read_ref(adcHandle, ADC_CHANNEL_TEMPSENSOR); float core_temp_avg_slope = (*ADC_CAL2 - *ADC_CAL1) / 100.0f; #else From d5246cea616c9b783cfd88e5cd854b92953ed9d4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:25:46 +1000 Subject: [PATCH 0877/2098] stm32/machine_adc: Fix internal ADC channel reading on WB MCUs. Signed-off-by: Damien George --- ports/stm32/machine_adc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index c3211ea4f4a..aa72fc68d92 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -386,6 +386,7 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time #elif defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) ADC_Common_TypeDef *adc_common = ADC12_COMMON; #elif defined(STM32H7) @@ -423,8 +424,8 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp adc->OR |= ADC_OR_OP0; // Enable Vddcore channel on ADC2 #endif } - #if defined(STM32G4) || defined(STM32H5) - // G4 and H5 use encoded literals for internal channels -> extract ADC channel for following code + #if defined(STM32G4) || defined(STM32H5) || defined(STM32WB) + // MCU uses encoded literals for internal channels -> extract ADC channel for following code if (__LL_ADC_IS_CHANNEL_INTERNAL(channel)) { channel = __LL_ADC_CHANNEL_TO_DECIMAL_NB(channel); } From 0975255f863be2e2529d57fb67bc7ba841e81249 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 29 Jun 2025 22:14:52 +1000 Subject: [PATCH 0878/2098] stm32/uart: Suppress additional RX idle IRQs on F4/L1. These MCUs only clear the RX idle IRQ if the data register is read, which won't occur if the only IRQ is the RX idle IRQ (because then reading and discarding the DR may lead to lost data). To work around this, explicitly suppress the RX idle IRQ so that it's only passed through to the Python callback once. Signed-off-by: Damien George --- ports/stm32/uart.c | 15 +++++++++++++++ ports/stm32/uart.h | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 55fa6221422..e3f8dc1f906 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -689,6 +689,10 @@ bool uart_init(machine_uart_obj_t *uart_obj, uart_obj->is_enabled = true; uart_obj->attached_to_repl = false; + #if defined(STM32F4) || defined(STM32L1) + uart_obj->suppress_idle_irq = true; + #endif + if (bits == UART_WORDLENGTH_9B && parity == UART_PARITY_NONE) { uart_obj->char_mask = 0x1ff; uart_obj->char_width = CHAR_WIDTH_9BIT; @@ -1274,6 +1278,9 @@ void uart_irq_handler(mp_uint_t uart_id) { self->uartx->CR1 &= ~USART_CR1_RXNEIE; } } + if (self->suppress_idle_irq) { + self->mp_irq_flags &= ~USART_SR_IDLE; + } #else self->uartx->ICR = self->mp_irq_flags & (USART_ICR_IDLECF | USART_ICR_ORECF); #endif @@ -1282,6 +1289,14 @@ void uart_irq_handler(mp_uint_t uart_id) { if (self->mp_irq_trigger & self->mp_irq_flags) { mp_irq_handler(self->mp_irq_obj); } + + #if defined(STM32F4) || defined(STM32L1) + if (did_clear_sr) { + self->suppress_idle_irq = false; + } else { + self->suppress_idle_irq = true; + } + #endif } static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index de4b70cdea7..d92434c641c 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -26,6 +26,7 @@ #ifndef MICROPY_INCLUDED_STM32_UART_H #define MICROPY_INCLUDED_STM32_UART_H +#include "py/mphal.h" #include "shared/runtime/mpirq.h" typedef enum { @@ -63,6 +64,9 @@ typedef struct _machine_uart_obj_t { pyb_uart_t uart_id : 8; bool is_static : 1; bool is_enabled : 1; + #if defined(STM32F4) || defined(STM32L1) + bool suppress_idle_irq : 1; // whether the RX idle IRQ is suppressed (F4/L1 only) + #endif bool attached_to_repl; // whether the UART is attached to REPL byte char_width; // 0 for 7,8 bit chars, 1 for 9 bit chars uint16_t char_mask; // 0x7f for 7 bit, 0xff for 8 bit, 0x1ff for 9 bit From bb484b6d81516ecfd110973af371a6eb00918e85 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:28:17 +1000 Subject: [PATCH 0879/2098] tests/extmod/machine_uart_tx.py: Support STM32WB boards. Signed-off-by: Damien George --- tests/extmod/machine_uart_tx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index f0cc912da66..85bf7e9fb87 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -28,7 +28,10 @@ initial_delay_ms = 20 # UART sends idle frame after init, so wait for that bit_margin = 1 elif "pyboard" in sys.platform: - uart_id = 4 + if "STM32WB" in sys.implementation._machine: + uart_id = "LP1" + else: + uart_id = 4 pins = {} initial_delay_ms = 50 # UART sends idle frame after init, so wait for that bit_margin = 1 # first start-bit must wait to sync with the UART clock From a2e3055d2d4d84577d4c9c95b2dc740a705d1bfd Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 Jun 2025 00:03:52 +1000 Subject: [PATCH 0880/2098] tests/extmod_hardware: Add UART config for STM32WB boards. Signed-off-by: Damien George --- tests/extmod_hardware/machine_uart_irq_rx.py | 11 ++++++++--- tests/extmod_hardware/machine_uart_irq_rxidle.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py index ecc95e62ae5..3602c260e39 100644 --- a/tests/extmod_hardware/machine_uart_irq_rx.py +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -24,9 +24,14 @@ tx_pin = 4 rx_pin = 5 elif "pyboard" in sys.platform: - uart_id = 4 - tx_pin = None # PA0 - rx_pin = None # PA1 + if "STM32WB" in sys.implementation._machine: + # LPUART(1) is on PA2/PA3 + uart_id = "LP1" + else: + # UART(4) is on PA0/PA1 + uart_id = 4 + tx_pin = None + rx_pin = None elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: uart_id = 0 tx_pin = "D1" diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index af2412c75ee..634369150c0 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -26,9 +26,14 @@ uart_id = 1 tx_pin = None elif "pyboard" in sys.platform: - uart_id = 4 - tx_pin = None # PA0 - rx_pin = None # PA1 + if "STM32WB" in sys.implementation._machine: + # LPUART(1) is on PA2/PA3 + uart_id = "LP1" + else: + # UART(4) is on PA0/PA1 + uart_id = 4 + tx_pin = None + rx_pin = None elif "renesas-ra" in sys.platform: uart_id = 9 tx_pin = None # P602 @ RA6M2 From a4a098ff8210a086d947b53771ed747a8998cef0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 Jun 2025 00:29:22 +1000 Subject: [PATCH 0881/2098] tests/extmod_hardware/machine_uart_irq_rxidle.py: Ignore inital IRQ. On stm32, the hardware generates an RXIDLE IRQ after enabling the UART, because the RX line is technically idle. Signed-off-by: Damien George --- tests/extmod_hardware/machine_uart_irq_rxidle.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index 634369150c0..ced24dca642 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -13,6 +13,9 @@ import time, sys +# Target tuning options. +tune_wait_initial_rxidle = False + # Configure pins based on the target. if "alif" in sys.platform: uart_id = 1 @@ -26,6 +29,7 @@ uart_id = 1 tx_pin = None elif "pyboard" in sys.platform: + tune_wait_initial_rxidle = True if "STM32WB" in sys.implementation._machine: # LPUART(1) is on PA2/PA3 uart_id = "LP1" @@ -69,8 +73,15 @@ def irq(u): else: uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + # Ignore a possible initial RXIDLE condition after creating UART. + if tune_wait_initial_rxidle: + uart.irq(lambda _: None, uart.IRQ_RXIDLE) + time.sleep_ms(10) + + # Configure desired IRQ. uart.irq(irq, uart.IRQ_RXIDLE) + # Write data and wait for IRQ. print("write", bits_per_s) uart.write(text) uart.flush() From 29b5c2207cd3f267018c566ee20f2d95f3b38f5e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Jun 2025 11:27:50 +1000 Subject: [PATCH 0882/2098] tests/extmod_hardware/machine_uart_irq_rxidle.py: Test multiple writes. This tests that the RXIDLE callback is called correctly after a second lot of bytes are received. Signed-off-by: Damien George --- .../machine_uart_irq_rxidle.py | 20 +++++++++------- .../machine_uart_irq_rxidle.py.exp | 24 ++++++++++++++++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index ced24dca642..3c743c9e0c1 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -64,10 +64,13 @@ def irq(u): print("IRQ_RXIDLE:", bool(u.irq().flags() & u.IRQ_RXIDLE), "data:", u.read()) -text = "12345678" +text = ("12345678", "abcdefgh") # Test that the IRQ is called for each set of byte received. for bits_per_s in (2400, 9600, 115200): + print("========") + print("bits_per_s:", bits_per_s) + if tx_pin is None: uart = UART(uart_id, bits_per_s) else: @@ -81,10 +84,11 @@ def irq(u): # Configure desired IRQ. uart.irq(irq, uart.IRQ_RXIDLE) - # Write data and wait for IRQ. - print("write", bits_per_s) - uart.write(text) - uart.flush() - print("ready") - time.sleep_ms(100) - print("done") + for i in range(2): + # Write data and wait for IRQ. + print("write") + uart.write(text[i]) + 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 index ce1890a06a4..f3c7497e4ce 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp @@ -1,12 +1,30 @@ -write 2400 +======== +bits_per_s: 2400 +write ready IRQ_RXIDLE: True data: b'12345678' done -write 9600 +write +ready +IRQ_RXIDLE: True data: b'abcdefgh' +done +======== +bits_per_s: 9600 +write ready IRQ_RXIDLE: True data: b'12345678' done -write 115200 +write +ready +IRQ_RXIDLE: True data: b'abcdefgh' +done +======== +bits_per_s: 115200 +write ready IRQ_RXIDLE: True data: b'12345678' done +write +ready +IRQ_RXIDLE: True data: b'abcdefgh' +done From b680011d91229e6eff76e6e02bf3f3b3f537967b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Jun 2025 14:28:39 +1000 Subject: [PATCH 0883/2098] tests/ports/stm32: Tweak tests to run on a wider set of boards. There should be no change to these tests for existing PYBV1x and PYBD_SFx boards. Signed-off-by: Damien George --- tests/ports/stm32/adc.py | 5 +++++ tests/ports/stm32/adcall.py | 16 ++++++++++++++-- tests/ports/stm32/extint.py | 3 ++- tests/ports/stm32/i2c.py | 7 +++++-- tests/ports/stm32/i2c_accel.py | 9 ++++----- tests/ports/stm32/i2c_error.py | 3 ++- tests/ports/stm32/irq.py | 5 +++-- tests/ports/stm32/modstm.py | 4 ++-- tests/ports/stm32/pin.py | 14 ++++++++++---- tests/ports/stm32/pyb1.py | 4 ++++ tests/ports/stm32/rtc.py | 14 ++++++++++---- tests/ports/stm32/servo.py | 6 +++++- tests/ports/stm32/timer.py | 11 ++++++++--- tests/ports/stm32/timer_callback.py | 26 ++++++++++++++++---------- tests/ports/stm32/uart.py | 6 ++++++ 15 files changed, 96 insertions(+), 37 deletions(-) diff --git a/tests/ports/stm32/adc.py b/tests/ports/stm32/adc.py index 875d31d732c..299a5af9c63 100644 --- a/tests/ports/stm32/adc.py +++ b/tests/ports/stm32/adc.py @@ -1,5 +1,10 @@ +import sys from pyb import ADC, Timer +if "STM32WB" in sys.implementation._machine: + print("SKIP") + raise SystemExit + adct = ADC(16) # Temperature 930 -> 20C print(str(adct)[:19]) adcv = ADC(17) # Voltage 1500 -> 3.3V diff --git a/tests/ports/stm32/adcall.py b/tests/ports/stm32/adcall.py index cfe179a97b2..18896c40cb2 100644 --- a/tests/ports/stm32/adcall.py +++ b/tests/ports/stm32/adcall.py @@ -1,5 +1,13 @@ +import sys from pyb import Pin, ADCAll +if "STM32WB" in sys.implementation._machine: + pa0_adc_channel = 5 + skip_temp_test = True # temperature fails on WB55 +else: + pa0_adc_channel = 0 + skip_temp_test = False + pins = [Pin.cpu.A0, Pin.cpu.A1, Pin.cpu.A2, Pin.cpu.A3] # set pins to IN mode, init ADCAll, then check pins are ANALOG @@ -12,7 +20,7 @@ # set pins to IN mode, init ADCAll with mask, then check some pins are ANALOG for p in pins: p.init(p.IN) -adc = ADCAll(12, 0x70003) +adc = ADCAll(12, 0x70000 | 3 << pa0_adc_channel) for p in pins: print(p) @@ -25,7 +33,11 @@ print(type(adc.read_channel(c))) # call special reading functions -print(0 < adc.read_core_temp() < 100) +print(skip_temp_test or 0 < adc.read_core_temp() < 100) print(0 < adc.read_core_vbat() < 4) print(0 < adc.read_core_vref() < 2) print(0 < adc.read_vref() < 4) + +if sys.implementation._build == "NUCLEO_WB55": + # Restore button pin settings. + Pin("SW", Pin.IN, Pin.PULL_UP) diff --git a/tests/ports/stm32/extint.py b/tests/ports/stm32/extint.py index 5510600020e..d3161f7cc76 100644 --- a/tests/ports/stm32/extint.py +++ b/tests/ports/stm32/extint.py @@ -1,7 +1,8 @@ import pyb # test basic functionality -ext = pyb.ExtInt("X5", pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, lambda l: print("line:", l)) +pin = pyb.Pin.cpu.A4 +ext = pyb.ExtInt(pin, pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, lambda l: print("line:", l)) ext.disable() ext.enable() print(ext.line()) diff --git a/tests/ports/stm32/i2c.py b/tests/ports/stm32/i2c.py index c968843273a..7e7fd250408 100644 --- a/tests/ports/stm32/i2c.py +++ b/tests/ports/stm32/i2c.py @@ -1,5 +1,8 @@ -import pyb -from pyb import I2C +try: + from pyb import I2C +except ImportError: + print("SKIP") + raise SystemExit # test we can correctly create by id for bus in (-1, 0, 1): diff --git a/tests/ports/stm32/i2c_accel.py b/tests/ports/stm32/i2c_accel.py index 8b87d406d06..11ff1392ba4 100644 --- a/tests/ports/stm32/i2c_accel.py +++ b/tests/ports/stm32/i2c_accel.py @@ -1,15 +1,14 @@ # use accelerometer to test i2c bus -import pyb -from pyb import I2C - -if not hasattr(pyb, "Accel"): +try: + from pyb import Accel, I2C +except ImportError: print("SKIP") raise SystemExit accel_addr = 76 -pyb.Accel() # this will init the MMA for us +Accel() # this will init the MMA for us i2c = I2C(1, I2C.CONTROLLER, baudrate=400000) diff --git a/tests/ports/stm32/i2c_error.py b/tests/ports/stm32/i2c_error.py index 1228962f5f4..de6e1ca6fec 100644 --- a/tests/ports/stm32/i2c_error.py +++ b/tests/ports/stm32/i2c_error.py @@ -1,12 +1,13 @@ # test I2C errors, with polling (disabled irqs) and DMA import pyb -from pyb import I2C if not hasattr(pyb, "Accel"): print("SKIP") raise SystemExit +from pyb import I2C + # init accelerometer pyb.Accel() diff --git a/tests/ports/stm32/irq.py b/tests/ports/stm32/irq.py index 04e70a7b792..fd8742d3eaf 100644 --- a/tests/ports/stm32/irq.py +++ b/tests/ports/stm32/irq.py @@ -1,3 +1,4 @@ +import time import pyb @@ -8,7 +9,7 @@ def test_irq(): pyb.enable_irq() # by default should enable IRQ # check that interrupts are enabled by waiting for ticks - pyb.delay(10) + time.sleep_ms(10) # check nested disable/enable i1 = pyb.disable_irq() @@ -18,7 +19,7 @@ def test_irq(): pyb.enable_irq(i1) # check that interrupts are enabled by waiting for ticks - pyb.delay(10) + time.sleep_ms(10) test_irq() diff --git a/tests/ports/stm32/modstm.py b/tests/ports/stm32/modstm.py index f1e147c0529..1459ee2a9e3 100644 --- a/tests/ports/stm32/modstm.py +++ b/tests/ports/stm32/modstm.py @@ -1,13 +1,13 @@ # test stm module import stm -import pyb +import time # test storing a full 32-bit number # turn on then off the A15(=yellow) LED BSRR = 0x18 stm.mem32[stm.GPIOA + BSRR] = 0x00008000 -pyb.delay(100) +time.sleep_ms(100) print(hex(stm.mem32[stm.GPIOA + stm.GPIO_ODR] & 0x00008000)) stm.mem32[stm.GPIOA + BSRR] = 0x80000000 print(hex(stm.mem32[stm.GPIOA + stm.GPIO_ODR] & 0x00008000)) diff --git a/tests/ports/stm32/pin.py b/tests/ports/stm32/pin.py index 3d2bef97e34..cbc78e68abd 100644 --- a/tests/ports/stm32/pin.py +++ b/tests/ports/stm32/pin.py @@ -1,14 +1,20 @@ +import sys from pyb import Pin -p = Pin("X8", Pin.IN) +if "PYB" in sys.implementation._machine: + test_pin = "X8" +else: + test_pin = Pin.cpu.A7 + +p = Pin(test_pin, Pin.IN) print(p) print(p.name()) print(p.pin()) print(p.port()) -p = Pin("X8", Pin.IN, Pin.PULL_UP) -p = Pin("X8", Pin.IN, pull=Pin.PULL_UP) -p = Pin("X8", mode=Pin.IN, pull=Pin.PULL_UP) +p = Pin(test_pin, Pin.IN, Pin.PULL_UP) +p = Pin(test_pin, Pin.IN, pull=Pin.PULL_UP) +p = Pin(test_pin, mode=Pin.IN, pull=Pin.PULL_UP) print(p) print(p.value()) diff --git a/tests/ports/stm32/pyb1.py b/tests/ports/stm32/pyb1.py index e9626ecf4e7..5627946dbc3 100644 --- a/tests/ports/stm32/pyb1.py +++ b/tests/ports/stm32/pyb1.py @@ -2,6 +2,10 @@ import pyb +if not hasattr(pyb, "delay"): + print("SKIP") + raise SystemExit + # test delay pyb.delay(-1) diff --git a/tests/ports/stm32/rtc.py b/tests/ports/stm32/rtc.py index 013b2f33142..03ed93adc26 100644 --- a/tests/ports/stm32/rtc.py +++ b/tests/ports/stm32/rtc.py @@ -1,13 +1,15 @@ -import pyb, stm +import time, stm from pyb import RTC +prediv_a = stm.mem32[stm.RTC + stm.RTC_PRER] >> 16 + rtc = RTC() rtc.init() print(rtc) # make sure that 1 second passes correctly rtc.datetime((2014, 1, 1, 1, 0, 0, 0, 0)) -pyb.delay(1002) +time.sleep_ms(1002) print(rtc.datetime()[:7]) @@ -38,8 +40,12 @@ def set_and_print(datetime): def set_and_print_calib(cal): - rtc.calibration(cal) - print(rtc.calibration()) + if cal > 0 and prediv_a < 3: + # can't set positive calibration if prediv_a<3, so just make test pass + print(cal) + else: + rtc.calibration(cal) + print(rtc.calibration()) set_and_print_calib(512) diff --git a/tests/ports/stm32/servo.py b/tests/ports/stm32/servo.py index d15cafe483e..0784f64d336 100644 --- a/tests/ports/stm32/servo.py +++ b/tests/ports/stm32/servo.py @@ -1,4 +1,8 @@ -from pyb import Servo +try: + from pyb import Servo +except ImportError: + print("SKIP") + raise SystemExit servo = Servo(1) print(servo) diff --git a/tests/ports/stm32/timer.py b/tests/ports/stm32/timer.py index 251a06c081b..add8c299377 100644 --- a/tests/ports/stm32/timer.py +++ b/tests/ports/stm32/timer.py @@ -1,10 +1,15 @@ # check basic functionality of the timer class -import pyb +import sys from pyb import Timer -tim = Timer(4) -tim = Timer(4, prescaler=100, period=200) +if "STM32WB" in sys.implementation._machine: + tim_id = 16 +else: + tim_id = 4 + +tim = Timer(tim_id) +tim = Timer(tim_id, prescaler=100, period=200) print(tim.prescaler()) print(tim.period()) tim.prescaler(300) diff --git a/tests/ports/stm32/timer_callback.py b/tests/ports/stm32/timer_callback.py index 5f170ccde11..4add88ec6a7 100644 --- a/tests/ports/stm32/timer_callback.py +++ b/tests/ports/stm32/timer_callback.py @@ -1,8 +1,14 @@ # check callback feature of the timer class -import pyb +import sys +import time from pyb import Timer +if "STM32WB" in sys.implementation._machine: + tim_extra_id = 16 +else: + tim_extra_id = 4 + # callback function that disables the callback when called def cb1(t): @@ -29,27 +35,27 @@ def cb4(t): # create a timer with a callback, using callback(None) to stop tim = Timer(1, freq=100, callback=cb1) -pyb.delay(5) +time.sleep_ms(5) print("before cb1") -pyb.delay(15) +time.sleep_ms(15) # create a timer with a callback, using deinit to stop tim = Timer(2, freq=100, callback=cb2) -pyb.delay(5) +time.sleep_ms(5) print("before cb2") -pyb.delay(15) +time.sleep_ms(15) # create a timer, then set the freq, then set the callback -tim = Timer(4) +tim = Timer(tim_extra_id) tim.init(freq=100) tim.callback(cb1) -pyb.delay(5) +time.sleep_ms(5) print("before cb1") -pyb.delay(15) +time.sleep_ms(15) # test callback with a closure tim.init(freq=100) tim.callback(cb3(3)) -pyb.delay(5) +time.sleep_ms(5) print("before cb4") -pyb.delay(15) +time.sleep_ms(15) diff --git a/tests/ports/stm32/uart.py b/tests/ports/stm32/uart.py index 53b0ea6ade4..28eb2261b7f 100644 --- a/tests/ports/stm32/uart.py +++ b/tests/ports/stm32/uart.py @@ -1,5 +1,11 @@ +import sys from pyb import UART +if "STM32WB" in sys.implementation._machine: + # UART(1) is usually connected to the REPL on these MCUs. + print("SKIP") + raise SystemExit + # test we can correctly create by id for bus in (-1, 0, 1, 2, 5, 6): try: From 168e2c8f66d9cfb8c5212fc236aa645ad8275876 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Jun 2025 11:53:00 +1000 Subject: [PATCH 0884/2098] tests/extmod/select_poll_eintr.py: Skip test if target can't bind. Eg on PYBV10 with THREAD variant, the firmware has both the `_thread` and `socket` modules but no NIC. Signed-off-by: Damien George --- tests/extmod/select_poll_eintr.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/extmod/select_poll_eintr.py b/tests/extmod/select_poll_eintr.py index e1cbc2aaf57..d9e9b319090 100644 --- a/tests/extmod/select_poll_eintr.py +++ b/tests/extmod/select_poll_eintr.py @@ -10,6 +10,18 @@ print("SKIP") raise SystemExit +# Use a new UDP socket for tests, which should be writable but not readable. +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +localhost_addr_info = socket.getaddrinfo("127.0.0.1", 8000) +try: + s.bind(localhost_addr_info[0][-1]) +except OSError: + # Target can't bind to localhost. + # Most likely it doesn't have a NIC and the test cannot be run. + s.close() + print("SKIP") + raise SystemExit + def thread_main(): lock.acquire() @@ -26,10 +38,6 @@ def thread_main(): lock.acquire() _thread.start_new_thread(thread_main, ()) -# Use a new UDP socket for tests, which should be writable but not readable. -s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -s.bind(socket.getaddrinfo("127.0.0.1", 8000)[0][-1]) - # Create the poller object. poller = select.poll() poller.register(s, select.POLLIN) From f3c56c81eaeb197e7765514b8e3c3aed731f8c5f Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 29 Jun 2025 13:34:09 +0900 Subject: [PATCH 0885/2098] stm32/irq: Change SPI IRQ priority to be higher than DMA IRQ. On STM32H5/STM32H7, SPI flash cannot use as storage device with DMA. SPI interruption may not be genearated even if DMA transfer has been done. This is due to lower priority of SPI interruption than DMA. This commit changes SPI interrupt priority more higher than DMA's priority. Signed-off-by: Yuuki NAGAO --- ports/stm32/irq.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index 58e6d0a8048..dfe901ff74b 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -155,6 +155,9 @@ static inline void restore_irq_pri(uint32_t state) { // SDIO must be higher priority than DMA for SDIO DMA transfers to work. #define IRQ_PRI_SDIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 4, 0) +// SPI must be higher priority than DMA for SPI DMA transfers to work. +#define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 4, 0) + // DMA should be higher priority than USB, since USB Mass Storage calls // into the sdcard driver which waits for the DMA to complete. #define IRQ_PRI_DMA NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 5, 0) @@ -172,8 +175,6 @@ static inline void restore_irq_pri(uint32_t state) { #define IRQ_PRI_SUBGHZ_RADIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) -#define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) - #define IRQ_PRI_HSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 10, 0) // Interrupt priority for non-special timers. From 42cfa7cdaeef455e3744ee8a21a5a423926e211f Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Tue, 1 Jul 2025 08:17:09 +1000 Subject: [PATCH 0886/2098] stm32/dma: Extend STM32H5 DMA use to SPI3 and SPI4. Attempting to configure SPI3 and SPI4 for the STM32H5 would fail with a linker error. This patch resolves that, ensuring that appropriate DMA channels are assigned to those SPI resources. Signed-off-by: Matt Trentini --- ports/stm32/dma.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index df8a408cbf8..53c53868cd1 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -735,9 +735,13 @@ const dma_descr_t dma_SPI_1_RX = { GPDMA1_Channel0, GPDMA1_REQUEST_SPI1_RX, dma_ const dma_descr_t dma_SPI_1_TX = { GPDMA1_Channel1, GPDMA1_REQUEST_SPI1_TX, dma_id_1, &dma_init_struct_spi_i2c }; const dma_descr_t dma_SPI_2_RX = { GPDMA1_Channel2, GPDMA1_REQUEST_SPI2_RX, dma_id_2, &dma_init_struct_spi_i2c }; const dma_descr_t dma_SPI_2_TX = { GPDMA1_Channel3, GPDMA1_REQUEST_SPI2_TX, dma_id_3, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_3_RX = { GPDMA1_Channel4, GPDMA1_REQUEST_SPI3_RX, dma_id_4, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_3_TX = { GPDMA1_Channel5, GPDMA1_REQUEST_SPI3_TX, dma_id_5, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_4_RX = { GPDMA1_Channel6, GPDMA1_REQUEST_SPI4_RX, dma_id_6, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_4_TX = { GPDMA1_Channel7, GPDMA1_REQUEST_SPI4_TX, dma_id_7, &dma_init_struct_spi_i2c }; #if MICROPY_HW_ENABLE_DAC -const dma_descr_t dma_DAC_1_TX = { GPDMA1_Channel4, GPDMA1_REQUEST_DAC1_CH1, dma_id_4, &dma_init_struct_dac }; -const dma_descr_t dma_DAC_2_TX = { GPDMA1_Channel5, GPDMA1_REQUEST_DAC1_CH2, dma_id_5, &dma_init_struct_dac }; +const dma_descr_t dma_DAC_1_TX = { GPDMA2_Channel0, GPDMA1_REQUEST_DAC1_CH1, dma_id_8, &dma_init_struct_dac }; +const dma_descr_t dma_DAC_2_TX = { GPDMA2_Channel1, GPDMA1_REQUEST_DAC1_CH2, dma_id_9, &dma_init_struct_dac }; #endif static const uint8_t dma_irqn[NSTREAM] = { From eaffbacb10783c769dc46a61b9eea65fcb7413fe Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Thu, 3 Jul 2025 20:47:00 +0900 Subject: [PATCH 0887/2098] stm32/machine_adc: Add machine.ADC implementation for STM32L1. Signed-off-by: Yuuki NAGAO --- ports/stm32/adc.c | 2 +- ports/stm32/machine_adc.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 10d78125927..28e254ace23 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -416,7 +416,7 @@ static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { sConfig.SamplingTime = ADC_SAMPLETIME_384CYCLES; } else { - sConfig.SamplingTime = ADC_SAMPLETIME_384CYCLES; + sConfig.SamplingTime = ADC_SAMPLETIME_16CYCLES; } #elif defined(STM32G0) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index aa72fc68d92..77d6248d02f 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -80,7 +80,7 @@ #define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 #define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_160CYCLES_5 #elif defined(STM32L1) -#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_384CYCLES +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_16CYCLES #define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_384CYCLES #elif defined(STM32L4) || defined(STM32WB) #define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 @@ -225,6 +225,8 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { ADC3_COMMON->CCR = 3 << ADC_CCR_CKMODE_Pos; #elif defined(STM32L0) ADC1_COMMON->CCR = 0; // ADCPR=PCLK/2 + #elif defined(STM32L1) + ADC1_COMMON->CCR = 1 << ADC_CCR_ADCPRE_Pos; // ADCPRE=2 #elif defined(STM32WB) ADC1_COMMON->CCR = 0 << ADC_CCR_PRESC_Pos | 0 << ADC_CCR_CKMODE_Pos; // PRESC=1, MODE=ASYNC #elif defined(STM32WL) @@ -385,6 +387,31 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp } *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time + #elif defined(STM32L1) + + ADC_Common_TypeDef *adc_common = ADC1_COMMON; + if (channel == ADC_CHANNEL_VREFINT || channel == ADC_CHANNEL_TEMPSENSOR) { + adc_common->CCR |= ADC_CCR_TSVREFE; + if (channel == ADC_CHANNEL_TEMPSENSOR) { + adc_stabilisation_delay_us(ADC_TEMPSENSOR_DELAY_US); + } + } + + adc->SQR1 = (1 - 1) << ADC_SQR1_L_Pos; + adc->SQR5 = (channel & 0x1f) << ADC_SQR5_SQ1_Pos; + + __IO uint32_t *smpr; + if (channel >= 20) { + smpr = &adc->SMPR1; + channel -= 20; + } else if (channel >= 10) { + smpr = &adc->SMPR2; + channel -= 10; + } else { + smpr = &adc->SMPR3; + } + *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time + #elif defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) From 62a674c9c40b52903d7da4fda5331b0f8653d966 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 1 May 2025 01:19:52 +1000 Subject: [PATCH 0888/2098] drivers: Support special QSPI direct-read protocol. This is useful for interfaces that stay in memory-mapped mode by default. They can implement this method with a simple `memcpy()`. Signed-off-by: Damien George --- drivers/bus/qspi.h | 1 + drivers/memory/spiflash.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/bus/qspi.h b/drivers/bus/qspi.h index 32b2890e3fc..05d9dd473c3 100644 --- a/drivers/bus/qspi.h +++ b/drivers/bus/qspi.h @@ -46,6 +46,7 @@ typedef struct _mp_qspi_proto_t { int (*write_cmd_addr_data)(void *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src); int (*read_cmd)(void *self, uint8_t cmd, size_t len, uint32_t *dest); int (*read_cmd_qaddr_qdata)(void *self, uint8_t cmd, uint32_t addr, uint8_t num_dummy, size_t len, uint8_t *dest); + int (*direct_read)(void *self, uint32_t addr, size_t len, uint8_t *dest); // can be NULL if direct read not supported } mp_qspi_proto_t; typedef struct _mp_soft_qspi_obj_t { diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index 1ae0bbbc679..7cd1d18a3f9 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -197,6 +197,10 @@ void mp_spiflash_init(mp_spiflash_t *self) { } else { uint8_t num_dummy = MICROPY_HW_SPIFLASH_QREAD_NUM_DUMMY(self); self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, num_dummy); + if (self->config->bus.u_qspi.proto->direct_read != NULL) { + // A bus with a custom read function should not have any further initialisation done. + return; + } } mp_spiflash_acquire_bus(self); @@ -318,6 +322,10 @@ int mp_spiflash_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *de if (len == 0) { return 0; } + const mp_spiflash_config_t *c = self->config; + if (c->bus_kind == MP_SPIFLASH_BUS_QSPI && c->bus.u_qspi.proto->direct_read != NULL) { + return c->bus.u_qspi.proto->direct_read(c->bus.u_qspi.data, addr, len, dest); + } mp_spiflash_acquire_bus(self); int ret = mp_spiflash_read_data(self, addr, len, dest); mp_spiflash_release_bus(self); From d8f004b62fccae7e1b102d3ff5e055a1823d88d5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 May 2025 17:05:03 +1000 Subject: [PATCH 0889/2098] lib/libm_dbl: Support FLT_EVAL_METHOD == 16. That's almost the same as FLT_EVAL_METHOD == 0, but indicates the presence of _Float16_t support. Signed-off-by: Damien George --- lib/libm_dbl/rint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libm_dbl/rint.c b/lib/libm_dbl/rint.c index fbba390e7d7..b85dec8f246 100644 --- a/lib/libm_dbl/rint.c +++ b/lib/libm_dbl/rint.c @@ -2,7 +2,7 @@ #include #include -#if FLT_EVAL_METHOD==0 || FLT_EVAL_METHOD==1 +#if FLT_EVAL_METHOD==0 || FLT_EVAL_METHOD==1 || FLT_EVAL_METHOD==16 #define EPS DBL_EPSILON #elif FLT_EVAL_METHOD==2 #define EPS LDBL_EPSILON From 228abf8fc7404306db54e8be01bb3feed56c913e Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 16 May 2025 14:27:58 +1000 Subject: [PATCH 0890/2098] lib/stm32lib: Update library for N6 v1.1.0. Changes in this new library version are: - Add N6 HAL at v1.1.0. Signed-off-by: Damien George --- lib/stm32lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stm32lib b/lib/stm32lib index 928df866e4d..8b2bb4ef44f 160000 --- a/lib/stm32lib +++ b/lib/stm32lib @@ -1 +1 @@ -Subproject commit 928df866e4d287ebc3c60726151513ebee609128 +Subproject commit 8b2bb4ef44fbfab5075ed9d09e83ddc3754b9257 From 24fd5f72682922664c0bcf70d6e3631a6d5b8d2b Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 21 Jun 2024 16:24:32 +1000 Subject: [PATCH 0891/2098] stm32/boards/make-pins.py: Support up to GPIO-O. Signed-off-by: Damien George --- ports/stm32/boards/make-pins.py | 2 +- ports/stm32/pin_defs_stm32.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/make-pins.py b/ports/stm32/boards/make-pins.py index 1b89fd64154..6f8a0a659d2 100755 --- a/ports/stm32/boards/make-pins.py +++ b/ports/stm32/boards/make-pins.py @@ -215,7 +215,7 @@ def print_source(self, out_source): def validate_cpu_pin_name(cpu_pin_name): boardgen.Pin.validate_cpu_pin_name(cpu_pin_name) - if not re.match("P[A-K][0-9]+(_C)?$", cpu_pin_name): + if not re.match("P[A-O][0-9]+(_C)?$", cpu_pin_name): raise boardgen.PinGeneratorError("Invalid cpu pin name '{}'".format(cpu_pin_name)) diff --git a/ports/stm32/pin_defs_stm32.h b/ports/stm32/pin_defs_stm32.h index 6c67b649242..645ec5b2df5 100644 --- a/ports/stm32/pin_defs_stm32.h +++ b/ports/stm32/pin_defs_stm32.h @@ -39,6 +39,10 @@ enum { PORT_I, PORT_J, PORT_K, + PORT_L, + PORT_M, + PORT_N, + PORT_O, }; // Must have matching entries in SUPPORTED_FN in boards/make-pins.py From eb3ea9ee13093d81053f5d07b8c96e4fc0e1383d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Jun 2024 17:46:21 +1000 Subject: [PATCH 0892/2098] stm32: Add support for STM32N6xx MCUs. This commit adds preliminary support for ST's new STM32N6xx MCUs. Supported features of this MCU so far are: - basic clock tree initialisation, running at 800MHz - fully working USB - XSPI in memory-mapped mode - machine.Pin - machine.UART - RTC and deepsleep support - SD card - filesystem - ROMFS - WiFi and BLE via cyw43-driver (SDIO backend) Note that the N6 does not have internal flash, and has some tricky boot sequence, so using a custom bootloader (mboot) is almost a necessity. Signed-off-by: Damien George --- ports/stm32/Makefile | 54 +- ports/stm32/adc.c | 56 +- ports/stm32/adc.h | 2 +- ports/stm32/boardctrl.h | 1 + ports/stm32/boards/common_n6_flash.ld | 57 ++ ports/stm32/boards/common_text.ld | 8 + ports/stm32/boards/pllvalues.py | 2 +- ports/stm32/dma.c | 83 ++- ports/stm32/dma.h | 13 + ports/stm32/extint.c | 39 +- ports/stm32/extint.h | 4 +- ports/stm32/flash.c | 4 + ports/stm32/i2cslave.h | 10 + ports/stm32/machine_adc.c | 36 +- ports/stm32/machine_uart.c | 2 +- ports/stm32/main.c | 47 +- ports/stm32/modmachine.c | 20 +- ports/stm32/mpconfigboard_common.h | 31 +- ports/stm32/mpconfigport.h | 2 +- ports/stm32/mphalport.c | 2 +- ports/stm32/mpu.h | 20 +- ports/stm32/powerctrl.c | 80 ++- ports/stm32/powerctrl.h | 6 + ports/stm32/powerctrlboot.c | 126 +++- ports/stm32/resethandler_iram.s | 82 +++ ports/stm32/rtc.c | 55 +- ports/stm32/sdcard.c | 8 +- ports/stm32/sdio.c | 30 +- ports/stm32/spi.c | 32 +- ports/stm32/spibdev.c | 39 +- ports/stm32/stm32.mk | 8 +- ports/stm32/stm32_it.c | 27 +- ports/stm32/storage.c | 15 +- ports/stm32/storage.h | 4 + ports/stm32/timer.c | 32 +- ports/stm32/uart.c | 51 +- ports/stm32/usb.c | 2 +- ports/stm32/usbd_conf.c | 35 +- .../stm32/usbdev/class/inc/usbd_cdc_msc_hid.h | 6 +- ports/stm32/vfs_rom_ioctl.c | 21 + ports/stm32/xspi.c | 599 ++++++++++++++++++ ports/stm32/xspi.h | 43 ++ 42 files changed, 1659 insertions(+), 135 deletions(-) create mode 100644 ports/stm32/boards/common_n6_flash.ld create mode 100644 ports/stm32/resethandler_iram.s create mode 100644 ports/stm32/xspi.c create mode 100644 ports/stm32/xspi.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index eabbd64a3b1..affd9d2f2f6 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -123,7 +123,15 @@ CFLAGS += -DSTM32_HAL_H='' CFLAGS += -DMBOOT_VTOR=$(MBOOT_TEXT0_ADDR) CFLAGS += -DMICROPY_HW_VTOR=$(TEXT0_ADDR) +ifeq ($(MCU_SERIES),n6) +ifeq ($(USE_MBOOT),1) +CFLAGS += -DMICROPY_HW_RUNS_FROM_EXT_FLASH=1 +endif +# as doesn't recognise -mcpu=cortex-m55 +AFLAGS += -march=armv8.1-m.main +else AFLAGS += $(filter -mcpu=%,$(CFLAGS_MCU_$(MCU_SERIES))) +endif # Configure for nan-boxing object model if requested ifeq ($(NANBOX),1) @@ -300,6 +308,7 @@ SRC_C += \ adc.c \ sdio.c \ subghz.c \ + xspi.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O += \ @@ -316,6 +325,13 @@ CFLAGS += -DUSE_HAL_DRIVER SRC_O += \ resethandler_m3.o \ shared/runtime/gchelper_thumb2.o +else ifeq ($(MCU_SERIES),n6) +SRC_O += shared/runtime/gchelper_thumb2.o +ifeq ($(USE_MBOOT),1) +SRC_O += resethandler_iram.o +else +SRC_O += resethandler.o +endif else SRC_O += \ system_stm32.o \ @@ -329,8 +345,6 @@ HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_adc_ex.c \ hal_cortex.c \ hal_dma.c \ - hal_flash.c \ - hal_flash_ex.c \ hal_gpio.c \ hal_i2c.c \ hal_pwr.c \ @@ -347,7 +361,14 @@ HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ ll_utils.c \ ) -ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 g0 g4 h5 h7 l0 l1 l4 wb)) +ifneq ($(MCU_SERIES),n6) +HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ + hal_flash.c \ + hal_flash_ex.c \ + ) +endif + +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 g0 g4 h5 h7 l0 l1 l4 n6 wb)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_pcd.c \ hal_pcd_ex.c \ @@ -355,7 +376,15 @@ HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ ) endif -ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h5 h7 l4)) +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),n6)) +HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ + hal_bsec.c \ + hal_rif.c \ + hal_xspi.c \ + ) +endif + +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h5 h7 l4 n6)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_sd.c \ ll_sdmmc.c \ @@ -380,7 +409,7 @@ $(BUILD)/$(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_hal_mmc.o: CFLAGS += -Wno endif endif -ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 g0 g4 h5 h7)) +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 g0 g4 h5 h7 n6)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_dma_ex.c \ ) @@ -496,6 +525,12 @@ all: $(TOP)/lib/stm32lib/README.md all_main $(BUILD)/firmware.hex ifeq ($(MBOOT_ENABLE_PACKING),1) all_main: $(BUILD)/firmware.pack.dfu +else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),n6)) +ifeq ($(USE_MBOOT),1) +all_main: $(BUILD)/firmware.dfu +else +all_main: $(BUILD)/firmware-trusted.bin +endif else all_main: $(BUILD)/firmware.dfu endif @@ -556,7 +591,7 @@ define GENERATE_HEX $(Q)$(OBJCOPY) -O ihex $(2) $(1) endef -.PHONY: deploy deploy-stlink deploy-openocd +.PHONY: deploy deploy-stlink deploy-openocd deploy-trusted ifeq ($(MBOOT_ENABLE_PACKING),1) deploy: $(BUILD)/firmware.pack.dfu @@ -566,6 +601,9 @@ deploy: $(BUILD)/firmware.dfu $(call RUN_DFU,$^) endif +deploy-trusted: $(BUILD)/firmware-trusted.bin + $(STM32_CUBE_PROGRAMMER)/bin/STM32_Programmer.sh -c port=SWD mode=HOTPLUG ap=1 -el $(DKEL) -w $^ 0x70000000 -hardRst + # A board should specify TEXT0_ADDR if to use a different location than the # default for the firmware memory location. A board can also optionally define # TEXT1_ADDR to split the firmware into two sections; see below for details. @@ -620,6 +658,10 @@ $(BUILD)/firmware.hex: $(BUILD)/firmware.elf $(BUILD)/firmware.elf: $(OBJ) $(call GENERATE_ELF,$@,$^) +$(BUILD)/firmware-trusted.bin: $(BUILD)/firmware.bin + /bin/rm -f $@ + $(STM32_CUBE_PROGRAMMER)/bin/STM32_SigningTool_CLI -bin $^ -nk -of 0x80000000 -t fsbl -o $@ -hv $(STM32_N6_HEADER_VERSION) + # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SHARED_SRC_C) $(GEN_PINS_SRC) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 28e254ace23..f47e9eaad7b 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -51,7 +51,7 @@ /// val = adc.read_core_vref() # read MCU VREF /* ADC definitions */ -#if defined(STM32H5) +#if defined(STM32H5) || defined(STM32N6) // STM32H5 features two ADC instances, ADCx and pin_adc_table are set dynamically #define PIN_ADC_MASK (PIN_ADC1 | PIN_ADC2) #else @@ -107,7 +107,7 @@ #define ADC_CAL2 ((uint16_t *)(ADC_CAL_ADDRESS + 4)) #define ADC_CAL_BITS (12) -#elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L1) || defined(STM32L4) || defined(STM32WB) +#elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L1) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) #define ADC_SCALE_V (((float)VREFINT_CAL_VREF) / 1000.0f) #define ADC_CAL_ADDRESS (VREFINT_CAL_ADDR) @@ -166,6 +166,9 @@ #define VBAT_DIV (3) #elif defined(STM32L152xE) // STM32L152xE does not have vbat. +#elif defined(STM32N6) +// ADC2 VINP 16 +#define VBAT_DIV (4) #else #error Unsupported processor #endif @@ -247,7 +250,7 @@ static bool is_adcx_channel(int channel) { handle.Instance = ADCx; return __HAL_ADC_IS_CHANNEL_INTERNAL(channel) || IS_ADC_CHANNEL(&handle, __HAL_ADC_DECIMAL_NB_TO_CHANNEL(channel)); - #elif defined(STM32H5) + #elif defined(STM32H5) || defined(STM32N6) // The first argument to the IS_ADC_CHANNEL macro is unused. return __HAL_ADC_IS_CHANNEL_INTERNAL(channel) || IS_ADC_CHANNEL(NULL, __HAL_ADC_DECIMAL_NB_TO_CHANNEL(channel)); @@ -260,7 +263,7 @@ static void adc_wait_for_eoc_or_timeout(ADC_HandleTypeDef *adcHandle, int32_t ti uint32_t tickstart = HAL_GetTick(); #if defined(STM32F4) || defined(STM32F7) || defined(STM32L1) while ((adcHandle->Instance->SR & ADC_FLAG_EOC) != ADC_FLAG_EOC) { - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) while (READ_BIT(adcHandle->Instance->ISR, ADC_FLAG_EOC) != ADC_FLAG_EOC) { #else #error Unsupported processor @@ -279,7 +282,7 @@ static void adcx_clock_enable(ADC_HandleTypeDef *adch) { __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); #elif defined(STM32G0) __HAL_RCC_ADC_CLK_ENABLE(); - #elif defined(STM32G4) + #elif defined(STM32G4) || defined(STM32N6) __HAL_RCC_ADC12_CLK_ENABLE(); #elif defined(STM32H5) __HAL_RCC_ADC_CLK_ENABLE(); @@ -352,6 +355,15 @@ static void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { adch->Init.OversamplingMode = DISABLE; adch->Init.DataAlign = ADC_DATAALIGN_RIGHT; adch->Init.DMAContinuousRequests = DISABLE; + #elif defined(STM32N6) + adch->Init.GainCompensation = 0; + adch->Init.ScanConvMode = ADC_SCAN_DISABLE; + adch->Init.LowPowerAutoWait = DISABLE; + adch->Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL; + adch->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; + adch->Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; + adch->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; + adch->Init.OversamplingMode = DISABLE; #else #error Unsupported processor #endif @@ -384,7 +396,7 @@ static void adc_init_single(pyb_obj_adc_t *adc_obj, ADC_TypeDef *adc) { static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) { ADC_ChannelConfTypeDef sConfig; - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) sConfig.Rank = ADC_REGULAR_RANK_1; if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel) == 0) { channel = __HAL_ADC_DECIMAL_NB_TO_CHANNEL(channel); @@ -433,6 +445,18 @@ static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; + #elif defined(STM32N6) + if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { + sConfig.SamplingTime = ADC_SAMPLETIME_246CYCLES_5; + } else { + sConfig.SamplingTime = ADC_SAMPLETIME_11CYCLES_5; + } + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + sConfig.OffsetSignedSaturation = DISABLE; + sConfig.OffsetSaturation = DISABLE; + sConfig.OffsetSign = ADC_OFFSET_SIGN_POSITIVE; #else #error Unsupported processor #endif @@ -510,7 +534,7 @@ static mp_obj_t adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // 1st argument is the pin name mp_obj_t pin_obj = args[0]; - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) // STM32H5 has two ADC instances where some pins are only available on ADC1 or ADC2 (but not both). // Assume we're using a channel of ADC1. Can be overridden for ADC2 later in this function. ADC_TypeDef *adc = ADC1; @@ -527,7 +551,7 @@ static mp_obj_t adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // No ADC function on the given pin. mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Pin(%q) doesn't have ADC capabilities"), pin->name); } - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) if ((pin->adc_num & PIN_ADC2) == PIN_ADC2) { adc = ADC2; pin_adc_table = pin_adc2; @@ -542,7 +566,7 @@ static mp_obj_t adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ } // If this channel corresponds to a pin then configure the pin in ADC mode. - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) if (channel < num_adc_pins) { const machine_pin_obj_t *pin = pin_adc_table[channel]; if (pin != NULL) { @@ -563,7 +587,7 @@ static mp_obj_t adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ o->base.type = &pyb_adc_type; o->pin_name = pin_obj; o->channel = channel; - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) adc_init_single(o, adc); #else adc_init_single(o, ADCx); @@ -654,7 +678,7 @@ static mp_obj_t adc_read_timed(mp_obj_t self_in, mp_obj_t buf_in, mp_obj_t freq_ // for subsequent samples we can just set the "start sample" bit #if defined(STM32F4) || defined(STM32F7) || defined(STM32L1) self->handle.Instance->CR2 |= (uint32_t)ADC_CR2_SWSTART; - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) SET_BIT(self->handle.Instance->CR, ADC_CR_ADSTART); #else #error Unsupported processor @@ -764,7 +788,7 @@ static mp_obj_t adc_read_timed_multi(mp_obj_t adc_array_in, mp_obj_t buf_array_i // ADC is started: set the "start sample" bit #if defined(STM32F4) || defined(STM32F7) || defined(STM32L1) adc->handle.Instance->CR2 |= (uint32_t)ADC_CR2_SWSTART; - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) SET_BIT(adc->handle.Instance->CR, ADC_CR_ADSTART); #else #error Unsupported processor @@ -898,6 +922,8 @@ int adc_read_core_temp(ADC_HandleTypeDef *adcHandle) { } else { return 0; } + #elif defined(STM32N6) + int32_t raw_value = 0; // TODO #else int32_t raw_value = adc_config_and_read_ref(adcHandle, ADC_CHANNEL_TEMPSENSOR); #endif @@ -909,6 +935,10 @@ int adc_read_core_temp(ADC_HandleTypeDef *adcHandle) { static volatile float adc_refcor = 1.0f; float adc_read_core_temp_float(ADC_HandleTypeDef *adcHandle) { + #if defined(STM32N6) + return 0.0f; // TODO + #else + #if defined(STM32G4) || defined(STM32L1) || defined(STM32L4) // Update the reference correction factor before reading tempsensor // because TS_CAL1 and TS_CAL2 of STM32G4,L1/L4 are at VDDA=3.0V @@ -931,6 +961,8 @@ float adc_read_core_temp_float(ADC_HandleTypeDef *adcHandle) { float core_temp_avg_slope = (*ADC_CAL2 - *ADC_CAL1) / 80.0f; #endif return (((float)raw_value * adc_refcor - *ADC_CAL1) / core_temp_avg_slope) + 30.0f; + + #endif } float adc_read_core_vbat(ADC_HandleTypeDef *adcHandle) { diff --git a/ports/stm32/adc.h b/ports/stm32/adc.h index 0518cdcd9a5..0adc9ac2be6 100644 --- a/ports/stm32/adc.h +++ b/ports/stm32/adc.h @@ -48,7 +48,7 @@ static inline void adc_deselect_vbat(ADC_TypeDef *adc, uint32_t channel) { adc_common = ADC_COMMON_REGISTER(0); #elif defined(STM32F7) adc_common = ADC123_COMMON; - #elif defined(STM32G4) || defined(STM32H5) + #elif defined(STM32G4) || defined(STM32H5) || defined(STM32N6) adc_common = ADC12_COMMON; #elif defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) adc_common = ADC12_COMMON; diff --git a/ports/stm32/boardctrl.h b/ports/stm32/boardctrl.h index 1a03925ef46..cb5380c298e 100644 --- a/ports/stm32/boardctrl.h +++ b/ports/stm32/boardctrl.h @@ -122,5 +122,6 @@ int boardctrl_run_boot_py(boardctrl_state_t *state); int boardctrl_run_main_py(boardctrl_state_t *state); void boardctrl_start_soft_reset(boardctrl_state_t *state); void boardctrl_end_soft_reset(boardctrl_state_t *state); +void boardctrl_enter_standby(void); #endif // MICROPY_INCLUDED_STM32_BOARDCTRL_H diff --git a/ports/stm32/boards/common_n6_flash.ld b/ports/stm32/boards/common_n6_flash.ld new file mode 100644 index 00000000000..a1f1fa531f4 --- /dev/null +++ b/ports/stm32/boards/common_n6_flash.ld @@ -0,0 +1,57 @@ +/* Memory layout for N6 when the application runs from external flash in XIP mode. + + FLASH_APP .isr_vector + FLASH_APP .text + FLASH_APP .data + + RAM .data + RAM .bss + RAM .heap + RAM .stack +*/ + +ENTRY(Reset_Handler) + +REGION_ALIAS("FLASH_COMMON", FLASH_APP); + +/* define output sections */ +SECTIONS +{ + .isr_vector : + { + _siram = .; + + /* This ISR is used for normal application mode. */ + . = ALIGN(1024); + KEEP(*(.isr_vector)); + + /* This ISR is used when waking from STANDBY. */ + . = ALIGN(1024); + KEEP(*(.rodata.iram_bootloader_isr_vector)); + + /* Need to place in RAM all the code necessary to write to + * flash, and to resume from STANDBY. */ + *(.*.iram_bootloader_reset); + *(.*.memcpy); + *(.*.mp_hal_gpio_clock_enable); + *(.*.mp_hal_pin_config); + *(.*.mp_hal_pin_config_speed); + *drivers/memory/spiflash.o(.text.* .rodata.*) + *xspi.o(.text.* .rodata.*); + *boards*(.rodata.spiflash_config*) + *boards*(.*.board_leave_standby); + *(*.rodata.pin_N*_obj); + *(.text.LL_AHB4_GRP1_EnableClock); + *(.text.LL_APB4_GRP2_EnableClock); + + . = ALIGN(4); + _eiram = .; + } >IRAM AT> FLASH_COMMON + + INCLUDE common_text.ld + INCLUDE common_extratext_data_in_flash.ld + INCLUDE common_bss_heap_stack.ld +} + +/* Used by the start-up code to initialise data */ +_siiram = LOADADDR(.isr_vector); diff --git a/ports/stm32/boards/common_text.ld b/ports/stm32/boards/common_text.ld index 16eea43bae2..d95467babc7 100644 --- a/ports/stm32/boards/common_text.ld +++ b/ports/stm32/boards/common_text.ld @@ -12,3 +12,11 @@ . = ALIGN(4); _etext = .; /* define a global symbol at end of code */ } >FLASH_COMMON + +/* Secure Gateway stubs */ +.gnu.sgstubs : +{ + . = ALIGN(4); + *(.gnu.sgstubs*) + . = ALIGN(4); +} >FLASH_COMMON diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py index d8856bfecdb..ae042d999ce 100644 --- a/ports/stm32/boards/pllvalues.py +++ b/ports/stm32/boards/pllvalues.py @@ -293,7 +293,7 @@ def main(): break # Relax constraint on PLLQ being 48MHz on MCUs which have separate PLLs for 48MHz - relax_pll48 = mcu_series.startswith(("stm32f413", "stm32f7", "stm32h5", "stm32h7")) + relax_pll48 = mcu_series.startswith(("stm32f413", "stm32f7", "stm32h5", "stm32h7", "stm32n6")) hse_valid_plls = compute_pll_table(hse, relax_pll48) if hsi is not None: diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index 53c53868cd1..c252770740d 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -81,7 +81,7 @@ typedef union { struct _dma_descr_t { #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) DMA_Stream_TypeDef *instance; - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) DMA_Channel_TypeDef *instance; #else #error "Unsupported Processor" @@ -93,7 +93,7 @@ struct _dma_descr_t { // Default parameters to dma_init() shared by spi and i2c; Channel and Direction // vary depending on the peripheral instance so they get passed separately -#if defined(STM32H5) +#if defined(STM32H5) || defined(STM32N6) static const DMA_InitTypeDef dma_init_struct_spi_i2c = { .Request = 0, // set by dma_init_handle .BlkHWRequest = DMA_BREQ_SINGLE_BURST, @@ -157,7 +157,7 @@ static const DMA_InitTypeDef dma_init_struct_i2s = { }; #endif -#if ENABLE_SDIO && !defined(STM32H5) && !defined(STM32H7) +#if ENABLE_SDIO && !defined(STM32H5) && !defined(STM32H7) && !defined(STM32N6) // Parameters to dma_init() for SDIO tx and rx. static const DMA_InitTypeDef dma_init_struct_sdio = { #if defined(STM32F4) || defined(STM32F7) @@ -830,6 +830,46 @@ static const uint8_t dma_irqn[NSTREAM] = { DMA2_Stream7_IRQn, }; +#elif defined(STM32N6) + +#define NCONTROLLERS (1) +#define NSTREAMS_PER_CONTROLLER (16) +#define NSTREAM (NCONTROLLERS * NSTREAMS_PER_CONTROLLER) + +#define DMA_SUB_INSTANCE_AS_UINT8(dma_channel) (dma_channel) + +#define DMA1_ENABLE_MASK (0xffff) // Bits in dma_enable_mask corresponding to GPDMA1 + +const dma_descr_t dma_SPI_1_RX = { GPDMA1_Channel0, GPDMA1_REQUEST_SPI1_RX, dma_id_0, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_1_TX = { GPDMA1_Channel1, GPDMA1_REQUEST_SPI1_TX, dma_id_1, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_2_RX = { GPDMA1_Channel2, GPDMA1_REQUEST_SPI2_RX, dma_id_2, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_2_TX = { GPDMA1_Channel3, GPDMA1_REQUEST_SPI2_TX, dma_id_3, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_3_RX = { GPDMA1_Channel4, GPDMA1_REQUEST_SPI3_RX, dma_id_4, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_3_TX = { GPDMA1_Channel5, GPDMA1_REQUEST_SPI3_TX, dma_id_5, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_4_RX = { GPDMA1_Channel6, GPDMA1_REQUEST_SPI4_RX, dma_id_6, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_4_TX = { GPDMA1_Channel7, GPDMA1_REQUEST_SPI4_TX, dma_id_7, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_5_RX = { GPDMA1_Channel8, GPDMA1_REQUEST_SPI5_RX, dma_id_8, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_5_TX = { GPDMA1_Channel9, GPDMA1_REQUEST_SPI5_TX, dma_id_9, &dma_init_struct_spi_i2c }; + +static const uint8_t dma_irqn[NSTREAM] = { + GPDMA1_Channel0_IRQn, + GPDMA1_Channel1_IRQn, + GPDMA1_Channel2_IRQn, + GPDMA1_Channel3_IRQn, + GPDMA1_Channel4_IRQn, + GPDMA1_Channel5_IRQn, + GPDMA1_Channel6_IRQn, + GPDMA1_Channel7_IRQn, + GPDMA1_Channel8_IRQn, + GPDMA1_Channel9_IRQn, + GPDMA1_Channel10_IRQn, + GPDMA1_Channel11_IRQn, + GPDMA1_Channel12_IRQn, + GPDMA1_Channel13_IRQn, + GPDMA1_Channel14_IRQn, + GPDMA1_Channel15_IRQn, +}; + #endif static DMA_HandleTypeDef *dma_handle[NSTREAM] = {NULL}; @@ -853,6 +893,10 @@ volatile dma_idle_count_t dma_idle; #define __HAL_RCC_DMA2_CLK_ENABLE __HAL_RCC_GPDMA2_CLK_ENABLE #define __HAL_RCC_DMA1_CLK_DISABLE __HAL_RCC_GPDMA1_CLK_DISABLE #define __HAL_RCC_DMA2_CLK_DISABLE __HAL_RCC_GPDMA2_CLK_DISABLE +#elif defined(STM32N6) +#define DMA1_IS_CLK_ENABLED() (__HAL_RCC_GPDMA1_IS_CLK_ENABLED()) +#define __HAL_RCC_DMA1_CLK_ENABLE __HAL_RCC_GPDMA1_CLK_ENABLE +#define __HAL_RCC_DMA1_CLK_DISABLE __HAL_RCC_GPDMA1_CLK_DISABLE #else #define DMA1_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA1EN) != 0) #define DMA2_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA2EN) != 0) @@ -1185,10 +1229,11 @@ void DMA2_Channel8_IRQHandler(void) { } #endif -#elif defined(STM32H5) +#elif defined(STM32H5) || defined(STM32N6) #define DEFINE_IRQ_HANDLER(periph, channel, id) \ void GPDMA##periph##_Channel##channel##_IRQHandler(void) { \ + MP_STATIC_ASSERT(GPDMA##periph##_Channel##channel##_IRQn > 0); \ IRQ_ENTER(GPDMA##periph##_Channel##channel##_IRQn); \ if (dma_handle[id] != NULL) { \ HAL_DMA_IRQHandler(dma_handle[id]); \ @@ -1204,6 +1249,7 @@ DEFINE_IRQ_HANDLER(1, 4, dma_id_4) DEFINE_IRQ_HANDLER(1, 5, dma_id_5) DEFINE_IRQ_HANDLER(1, 6, dma_id_6) DEFINE_IRQ_HANDLER(1, 7, dma_id_7) +#if defined(STM32H5) DEFINE_IRQ_HANDLER(2, 0, dma_id_8) DEFINE_IRQ_HANDLER(2, 1, dma_id_9) DEFINE_IRQ_HANDLER(2, 2, dma_id_10) @@ -1212,6 +1258,16 @@ DEFINE_IRQ_HANDLER(2, 4, dma_id_12) DEFINE_IRQ_HANDLER(2, 5, dma_id_13) DEFINE_IRQ_HANDLER(2, 6, dma_id_14) DEFINE_IRQ_HANDLER(2, 7, dma_id_15) +#else +DEFINE_IRQ_HANDLER(1, 8, dma_id_8) +DEFINE_IRQ_HANDLER(1, 9, dma_id_9) +DEFINE_IRQ_HANDLER(1, 10, dma_id_10) +DEFINE_IRQ_HANDLER(1, 11, dma_id_11) +DEFINE_IRQ_HANDLER(1, 12, dma_id_12) +DEFINE_IRQ_HANDLER(1, 13, dma_id_13) +DEFINE_IRQ_HANDLER(1, 14, dma_id_14) +DEFINE_IRQ_HANDLER(1, 15, dma_id_15) +#endif #elif defined(STM32L0) @@ -1424,7 +1480,7 @@ void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint3 dma->Instance = dma_descr->instance; dma->Init = *dma_descr->init; dma->Init.Direction = dir; - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) dma->Init.Request = dma_descr->sub_instance; #else #if !defined(STM32F0) && !defined(STM32L1) @@ -1432,7 +1488,7 @@ void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint3 #endif #endif - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) // Configure src/dest settings based on the DMA direction. if (dir == DMA_MEMORY_TO_PERIPH) { dma->Init.SrcInc = DMA_SINC_INCREMENTED; @@ -1463,13 +1519,24 @@ void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir dma_enable_clock(dma_id); - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) // Always reset and configure the H7 and G0/G4/H7/L0/L4/WB/WL DMA peripheral // (dma->State is set to HAL_DMA_STATE_RESET by memset above) // TODO: understand how L0/L4 DMA works so this is not needed HAL_DMA_DeInit(dma); dma->Parent = data; // HAL_DMA_DeInit may clear Parent, so set it again HAL_DMA_Init(dma); + + #if defined(STM32N6) + // Configure security attributes. + HAL_DMA_ConfigChannelAttributes(dma, DMA_CHANNEL_PRIV | DMA_CHANNEL_SEC | DMA_CHANNEL_SRC_SEC | DMA_CHANNEL_DEST_SEC); + // Configure data handling. + DMA_DataHandlingConfTypeDef config; + config.DataExchange = DMA_EXCHANGE_NONE; + config.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED; + HAL_DMAEx_ConfigDataHandling(dma, &config); + #endif + NVIC_SetPriority(IRQn_NONNEG(dma_irqn[dma_id]), IRQ_PRI_DMA); #else // if this stream was previously configured for this channel/request and direction then we @@ -1740,7 +1807,7 @@ void dma_nohal_start(const dma_descr_t *descr, uint32_t src_addr, uint32_t dst_a dma->CCR |= DMA_CCR_EN; } -#elif defined(STM32G0) || defined(STM32WB) || defined(STM32WL) +#elif defined(STM32G0) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) // These functions are currently not implemented or needed for this MCU. diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h index 7ba04baf520..f05b22b5d05 100644 --- a/ports/stm32/dma.h +++ b/ports/stm32/dma.h @@ -147,6 +147,19 @@ extern const dma_descr_t dma_I2C_4_RX; extern const dma_descr_t dma_SPI_SUBGHZ_TX; extern const dma_descr_t dma_SPI_SUBGHZ_RX; +#elif defined(STM32N6) + +extern const dma_descr_t dma_SPI_1_RX; +extern const dma_descr_t dma_SPI_1_TX; +extern const dma_descr_t dma_SPI_2_RX; +extern const dma_descr_t dma_SPI_2_TX; +extern const dma_descr_t dma_SPI_3_RX; +extern const dma_descr_t dma_SPI_3_TX; +extern const dma_descr_t dma_SPI_4_RX; +extern const dma_descr_t dma_SPI_4_TX; +extern const dma_descr_t dma_SPI_5_RX; +extern const dma_descr_t dma_SPI_5_TX; + #endif // API that configures the DMA via the HAL. diff --git a/ports/stm32/extint.c b/ports/stm32/extint.c index 5b5658809bc..9c3c2432536 100644 --- a/ports/stm32/extint.c +++ b/ports/stm32/extint.c @@ -92,7 +92,7 @@ #define EXTI_SWIER_BB(line) (*(__IO uint32_t *)(PERIPH_BB_BASE + ((EXTI_OFFSET + offsetof(EXTI_TypeDef, SWIER)) * 32) + ((line) * 4))) #endif -#if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) +#if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) // The L4 MCU supports 40 Events/IRQs lines of the type configurable and direct. // Here we only support configurable line types. Details, see page 330 of RM0351, Rev 1. // The USB_FS_WAKUP event is a direct type and there is no support for it. @@ -170,7 +170,7 @@ static const uint8_t nvic_irq_channel[EXTI_NUM_VECTORS] = { ADC1_COMP_IRQn, #endif - #elif defined(STM32H5) + #elif defined(STM32N6) || defined(STM32H5) EXTI0_IRQn, EXTI1_IRQn, @@ -189,6 +189,13 @@ static const uint8_t nvic_irq_channel[EXTI_NUM_VECTORS] = { EXTI14_IRQn, EXTI15_IRQn, + #if defined(STM32N6) + 0, + RTC_S_IRQn, + RTC_IRQn, + TAMP_IRQn, + #endif + #else EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, EXTI3_IRQn, EXTI4_IRQn, @@ -307,7 +314,7 @@ void EXTI15_10_IRQHandler(void) { IRQ_EXIT(EXTI15_10_IRQn); } -#elif defined(STM32H5) +#elif defined(STM32H5) || defined(STM32N6) DEFINE_EXTI_IRQ_HANDLER(0) DEFINE_EXTI_IRQ_HANDLER(1) @@ -440,10 +447,10 @@ void extint_register_pin(const machine_pin_obj_t *pin, uint32_t mode, bool hard_ #if !defined(STM32H5) && !defined(STM32WB) && !defined(STM32WL) __HAL_RCC_SYSCFG_CLK_ENABLE(); #endif - #if defined(STM32G0) || defined(STM32H5) + #if defined(STM32G0) || defined(STM32H5) || defined(STM32N6) EXTI->EXTICR[line >> 2] = - (EXTI->EXTICR[line >> 2] & ~(0x0f << (4 * (line & 0x03)))) - | ((uint32_t)(GPIO_GET_INDEX(pin->gpio)) << (4 * (line & 0x03))); + (EXTI->EXTICR[line >> 2] & ~(0xff << (8 * (line & 0x03)))) + | ((uint32_t)(GPIO_GET_INDEX(pin->gpio)) << (8 * (line & 0x03))); #else SYSCFG->EXTICR[line >> 2] = (SYSCFG->EXTICR[line >> 2] & ~(0x0f << (4 * (line & 0x03)))) @@ -480,13 +487,13 @@ void extint_set(const machine_pin_obj_t *pin, uint32_t mode) { pyb_extint_callback_arg[line] = MP_OBJ_FROM_PTR(pin); // Route the GPIO to EXTI - #if !defined(STM32H5) && !defined(STM32WB) && !defined(STM32WL) + #if !defined(STM32H5) && !defined(STM32N6) && !defined(STM32WB) && !defined(STM32WL) __HAL_RCC_SYSCFG_CLK_ENABLE(); #endif - #if defined(STM32G0) || defined(STM32H5) + #if defined(STM32G0) || defined(STM32H5) || defined(STM32N6) EXTI->EXTICR[line >> 2] = - (EXTI->EXTICR[line >> 2] & ~(0x0f << (4 * (line & 0x03)))) - | ((uint32_t)(GPIO_GET_INDEX(pin->gpio)) << (4 * (line & 0x03))); + (EXTI->EXTICR[line >> 2] & ~(0xff << (8 * (line & 0x03)))) + | ((uint32_t)(GPIO_GET_INDEX(pin->gpio)) << (8 * (line & 0x03))); #else SYSCFG->EXTICR[line >> 2] = (SYSCFG->EXTICR[line >> 2] & ~(0x0f << (4 * (line & 0x03)))) @@ -526,7 +533,7 @@ void extint_enable(uint line) { if (pyb_extint_mode[line] == EXTI_Mode_Interrupt) { #if defined(STM32H7) EXTI_D1->IMR1 |= (1 << line); - #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32WB) || defined(STM32WL) + #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) EXTI->IMR1 |= (1 << line); #else EXTI->IMR |= (1 << line); @@ -534,7 +541,7 @@ void extint_enable(uint line) { } else { #if defined(STM32H7) EXTI_D1->EMR1 |= (1 << line); - #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32WB) || defined(STM32WL) + #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) EXTI->EMR1 |= (1 << line); #else EXTI->EMR |= (1 << line); @@ -560,7 +567,7 @@ void extint_disable(uint line) { #if defined(STM32H7) EXTI_D1->IMR1 &= ~(1 << line); EXTI_D1->EMR1 &= ~(1 << line); - #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32WB) || defined(STM32WL) + #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) EXTI->IMR1 &= ~(1 << line); EXTI->EMR1 &= ~(1 << line); #else @@ -582,7 +589,7 @@ void extint_swint(uint line) { return; } // we need 0 to 1 transition to trigger the interrupt - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) EXTI->SWIER1 &= ~(1 << line); EXTI->SWIER1 |= (1 << line); #else @@ -662,7 +669,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_swint_obj, extint_obj_swint); static mp_obj_t extint_regs(void) { const mp_print_t *print = &mp_plat_print; - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) mp_printf(print, "EXTI_IMR1 %08x\n", (unsigned int)EXTI->IMR1); mp_printf(print, "EXTI_IMR2 %08x\n", (unsigned int)EXTI->IMR2); mp_printf(print, "EXTI_EMR1 %08x\n", (unsigned int)EXTI->EMR1); @@ -673,7 +680,7 @@ static mp_obj_t extint_regs(void) { mp_printf(print, "EXTI_FTSR2 %08x\n", (unsigned int)EXTI->FTSR2); mp_printf(print, "EXTI_SWIER1 %08x\n", (unsigned int)EXTI->SWIER1); mp_printf(print, "EXTI_SWIER2 %08x\n", (unsigned int)EXTI->SWIER2); - #if defined(STM32G0) || defined(STM32H5) + #if defined(STM32G0) || defined(STM32N6) || defined(STM32H5) mp_printf(print, "EXTI_RPR1 %08x\n", (unsigned int)EXTI->RPR1); mp_printf(print, "EXTI_FPR1 %08x\n", (unsigned int)EXTI->FPR1); mp_printf(print, "EXTI_RPR2 %08x\n", (unsigned int)EXTI->RPR2); diff --git a/ports/stm32/extint.h b/ports/stm32/extint.h index d5abb04d654..801dcf62b54 100644 --- a/ports/stm32/extint.h +++ b/ports/stm32/extint.h @@ -46,7 +46,7 @@ #if defined(STM32F0) || defined(STM32G4) || defined(STM32L1) || defined(STM32L4) || defined(STM32WL) #define EXTI_RTC_TIMESTAMP (19) #define EXTI_RTC_WAKEUP (20) -#elif defined(STM32H5) +#elif defined(STM32H5) || defined(STM32N6) #define EXTI_RTC_WAKEUP (17) #define EXTI_RTC_TAMP (19) #elif defined(STM32H7) || defined(STM32WB) @@ -55,8 +55,6 @@ #elif defined(STM32G0) #define EXTI_RTC_WAKEUP (19) #define EXTI_RTC_TIMESTAMP (21) -#elif defined(STM32H5) -#define EXTI_RTC_WAKEUP (17) #else #define EXTI_RTC_TIMESTAMP (21) #define EXTI_RTC_WAKEUP (22) diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c index 7668b53a43b..85bcee5a97c 100644 --- a/ports/stm32/flash.c +++ b/ports/stm32/flash.c @@ -30,6 +30,8 @@ #include "py/mphal.h" #include "flash.h" +#if !defined(STM32N6) + #if defined(STM32F0) #define FLASH_FLAG_ALL_ERRORS (FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR) @@ -509,3 +511,5 @@ int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { return mp_hal_status_to_neg_errno(status); } + +#endif diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index a4b2957de33..cc4e7f9be92 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -51,6 +51,16 @@ static inline void i2c_slave_init(i2c_slave_t *i2c, int irqn, int irq_pri, int a RCC->APB1LENR |= 1 << (RCC_APB1LENR_I2C1EN_Pos + i2c_idx); volatile uint32_t tmp = RCC->APB1LENR; // Delay after enabling clock (void)tmp; + #elif defined(STM32N6) + if (i2c_idx == 3) { + RCC->APB4ENR1 |= RCC_APB4ENR1_I2C4EN; + volatile uint32_t tmp = RCC->APB4ENR1; // Delay after enabling clock + (void)tmp; + } else { + RCC->APB1ENR1 |= 1 << (RCC_APB1ENR1_I2C1EN_Pos + i2c_idx); + volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock + (void)tmp; + } #elif defined(STM32WB) RCC->APB1ENR1 |= 1 << (RCC_APB1ENR1_I2C1EN_Pos + i2c_idx); volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index 77d6248d02f..63cd4e089dd 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -30,7 +30,7 @@ #include "py/mphal.h" #include "adc.h" -#if defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) +#if defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) #define ADC_V2 (1) #else #define ADC_V2 (0) @@ -85,6 +85,9 @@ #elif defined(STM32L4) || defined(STM32WB) #define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 #define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_247CYCLES_5 +#elif defined(STM32N6) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_11CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_246CYCLES_5 #endif // Timeout for waiting for end-of-conversion @@ -127,6 +130,8 @@ static uint32_t adc_ll_channel(uint32_t channel_id) { case MACHINE_ADC_INT_CH_TEMPSENSOR: #if defined(STM32G4) adc_ll_ch = ADC_CHANNEL_TEMPSENSOR_ADC1; + #elif defined(STM32N6) + adc_ll_ch = ADC_CHANNEL_0; // TODO #else adc_ll_ch = ADC_CHANNEL_TEMPSENSOR; #endif @@ -183,7 +188,7 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { if (adc == ADC1) { #if defined(STM32H5) __HAL_RCC_ADC_CLK_ENABLE(); - #elif defined(STM32G4) || defined(STM32H7) + #elif defined(STM32G4) || defined(STM32H7) || defined(STM32N6) __HAL_RCC_ADC12_CLK_ENABLE(); #else __HAL_RCC_ADC1_CLK_ENABLE(); @@ -193,7 +198,7 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { if (adc == ADC2) { #if defined(STM32H5) __HAL_RCC_ADC_CLK_ENABLE(); - #elif defined(STM32G4) || defined(STM32H7) + #elif defined(STM32G4) || defined(STM32H7) || defined(STM32N6) __HAL_RCC_ADC12_CLK_ENABLE(); #else __HAL_RCC_ADC2_CLK_ENABLE(); @@ -233,7 +238,7 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { ADC_COMMON->CCR = 0 << ADC_CCR_PRESC_Pos; // PRESC=1 #endif - #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) if (adc->CR & ADC_CR_DEEPPWD) { adc->CR = 0; // disable deep powerdown } @@ -257,6 +262,11 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { LL_ADC_StartCalibration(adc); #elif defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) LL_ADC_StartCalibration(adc, LL_ADC_SINGLE_ENDED); + #elif defined(STM32N6) + ADC_HandleTypeDef hadc; + hadc.Instance = adc; + hadc.State = 0; + HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED); #else LL_ADC_StartCalibration(adc, LL_ADC_CALIB_OFFSET_LINEARITY, LL_ADC_SINGLE_ENDED); #endif @@ -312,11 +322,17 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { uint32_t cfgr = res << ADC_CFGR_RES_Pos; adc->CFGR = (adc->CFGR & ~cfgr_clr) | cfgr; + #elif defined(STM32N6) + + uint32_t cfgr1_clr = ADC_CFGR1_CONT | ADC_CFGR1_EXTEN; + uint32_t cfgr1 = res << ADC_CFGR1_RES_Pos; + adc->CFGR1 = (adc->CFGR1 & ~cfgr1_clr) | cfgr1; + #endif } static int adc_get_bits(ADC_TypeDef *adc) { - #if defined(STM32F0) || defined(STM32G0) || defined(STM32L0) || defined(STM32WL) + #if defined(STM32F0) || defined(STM32G0) || defined(STM32L0) || defined(STM32N6) || defined(STM32WL) uint32_t res = (adc->CFGR1 & ADC_CFGR1_RES) >> ADC_CFGR1_RES_Pos; #elif defined(STM32F4) || defined(STM32F7) || defined(STM32L1) uint32_t res = (adc->CR1 & ADC_CR1_RES) >> ADC_CR1_RES_Pos; @@ -412,9 +428,9 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp } *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time - #elif defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) - #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) + #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) || defined(STM32N6) ADC_Common_TypeDef *adc_common = ADC12_COMMON; #elif defined(STM32H7) #if defined(ADC_VER_V5_V90) @@ -432,6 +448,7 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp #endif if (channel == ADC_CHANNEL_VREFINT) { adc_common->CCR |= ADC_CCR_VREFEN; + #if !defined(STM32N6) #if defined(STM32G4) } else if (channel == ADC_CHANNEL_TEMPSENSOR_ADC1) { adc_common->CCR |= ADC_CCR_VSENSESEL; @@ -440,18 +457,19 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp adc_common->CCR |= ADC_CCR_TSEN; #endif adc_stabilisation_delay_us(ADC_TEMPSENSOR_DELAY_US); + #endif } else if (channel == ADC_CHANNEL_VBAT) { #if defined(STM32G4) adc_common->CCR |= ADC_CCR_VBATSEL; #else adc_common->CCR |= ADC_CCR_VBATEN; #endif - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) } else if (channel == ADC_CHANNEL_VDDCORE) { adc->OR |= ADC_OR_OP0; // Enable Vddcore channel on ADC2 #endif } - #if defined(STM32G4) || defined(STM32H5) || defined(STM32WB) + #if defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WB) // MCU uses encoded literals for internal channels -> extract ADC channel for following code if (__LL_ADC_IS_CHANNEL_INTERNAL(channel)) { channel = __LL_ADC_CHANNEL_TO_DECIMAL_NB(channel); diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 8444b299897..8f1faea4b69 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -399,7 +399,7 @@ static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { // Send a break condition. static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { - #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) self->uartx->RQR = USART_RQR_SBKRQ; // write-only register #else self->uartx->CR1 |= USART_CR1_SBK; diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 5e114f562f2..aea1953fd65 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -306,11 +306,30 @@ static bool init_sdcard_fs(void) { } #endif +#if defined(STM32N6) +static void risaf_init(void) { + RIMC_MasterConfig_t rimc_master = {0}; + + __HAL_RCC_RIFSC_CLK_ENABLE(); + LL_AHB3_GRP1_EnableClockLowPower(LL_AHB3_GRP1_PERIPH_RIFSC | LL_AHB3_GRP1_PERIPH_RISAF); + + rimc_master.MasterCID = RIF_CID_1; + rimc_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV; + + HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC1, &rimc_master); + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC1, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); + HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC2, &rimc_master); + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC2, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); +} +#endif + void stm32_main(uint32_t reset_mode) { // Low-level MCU initialisation. stm32_system_init(); - #if !defined(STM32F0) + // Set VTOR, the location of the interrupt vector table. + // On N6, SystemInit does this, setting VTOR to &g_pfnVectors. + #if !defined(STM32F0) && !defined(STM32N6) #if MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM // Copy IRQ vector table to RAM and point VTOR there extern uint32_t __isr_vector_flash_addr, __isr_vector_ram_start, __isr_vector_ram_end; @@ -325,8 +344,7 @@ void stm32_main(uint32_t reset_mode) { #endif #endif - - #if __CORTEX_M != 33 + #if __CORTEX_M != 33 && __CORTEX_M != 55 // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI SCB->CCR |= SCB_CCR_STKALIGN_Msk; #endif @@ -349,7 +367,7 @@ void stm32_main(uint32_t reset_mode) { __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); #endif - #elif defined(STM32F7) || defined(STM32H7) + #elif defined(STM32F7) || defined(STM32H7) || defined(STM32N6) #if ART_ACCLERATOR_ENABLE __HAL_FLASH_ART_ENABLE(); @@ -376,6 +394,23 @@ void stm32_main(uint32_t reset_mode) { #endif + #if defined(STM32N6) + // SRAM, XSPI needs to remain awake during sleep, eg so DMA from flash works. + LL_MEM_EnableClockLowPower(0xffffffff); + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_XSPI2 | LL_AHB5_GRP1_PERIPH_XSPIM); + LL_APB4_GRP1_EnableClock(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); + LL_APB4_GRP1_EnableClockLowPower(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); + + // Enable some AHB peripherals during sleep. + LL_AHB1_GRP1_EnableClockLowPower(0xffffffff); // GPDMA1, ADC12 + LL_AHB4_GRP1_EnableClockLowPower(0xffffffff); // GPIOA-Q, PWR, CRC + + // Enable some APB peripherals during sleep. + LL_APB1_GRP1_EnableClockLowPower(0xffffffff); // I2C, I3C, LPTIM, SPI, TIM, UART, WWDG + LL_APB2_GRP1_EnableClockLowPower(0xffffffff); // SAI, SPI, TIM, UART + LL_APB4_GRP1_EnableClockLowPower(0xffffffff); // I2C, LPTIM, LPUART, RTC, SPI + #endif + mpu_init(); #if __CORTEX_M >= 0x03 @@ -389,6 +424,10 @@ void stm32_main(uint32_t reset_mode) { // set the system clock to be HSE SystemClock_Config(); + #if defined(STM32N6) + risaf_init(); + #endif + #if defined(STM32F4) || defined(STM32F7) #if defined(__HAL_RCC_DTCMRAMEN_CLK_ENABLE) // The STM32F746 doesn't really have CCM memory, but it does have DTCM, diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index f3bdf1bc502..8123cd80115 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -54,7 +54,7 @@ #define RCC_CSR_PORRSTF RCC_CSR_PWRRSTF #endif -#if defined(STM32H5) +#if defined(STM32H5) || defined(STM32N6) #define RCC_SR RSR #define RCC_SR_IWDGRSTF RCC_RSR_IWDGRSTF #define RCC_SR_WWDGRSTF RCC_RSR_WWDGRSTF @@ -135,7 +135,7 @@ void machine_init(void) { reset_cause = PYB_RESET_DEEPSLEEP; PWR->PMCR |= PWR_PMCR_CSSF; } else - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32N6) if (PWR->CPUCR & PWR_CPUCR_SBF || PWR->CPUCR & PWR_CPUCR_STOPF) { // came out of standby or stop mode reset_cause = PYB_RESET_DEEPSLEEP; @@ -323,6 +323,19 @@ MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { // get or set the MCU frequencies static mp_obj_t mp_machine_get_freq(void) { + #if defined(STM32N6) + LL_RCC_ClocksTypeDef clocks; + LL_RCC_GetSystemClocksFreq(&clocks); + mp_obj_t tuple[] = { + mp_obj_new_int(clocks.CPUCLK_Frequency), + mp_obj_new_int(clocks.SYSCLK_Frequency), + mp_obj_new_int(clocks.HCLK_Frequency), + mp_obj_new_int(clocks.PCLK1_Frequency), + mp_obj_new_int(clocks.PCLK2_Frequency), + mp_obj_new_int(clocks.PCLK4_Frequency), + mp_obj_new_int(clocks.PCLK5_Frequency), + }; + #else mp_obj_t tuple[] = { mp_obj_new_int(HAL_RCC_GetSysClockFreq()), mp_obj_new_int(HAL_RCC_GetHCLKFreq()), @@ -331,11 +344,12 @@ static mp_obj_t mp_machine_get_freq(void) { mp_obj_new_int(HAL_RCC_GetPCLK2Freq()), #endif }; + #endif return mp_obj_new_tuple(MP_ARRAY_SIZE(tuple), tuple); } static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { - #if defined(STM32F0) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32G0) + #if defined(STM32F0) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32G0) || defined(STM32N6) mp_raise_NotImplementedError(MP_ERROR_TEXT("machine.freq set not supported yet")); #else mp_int_t sysclk = mp_obj_get_int(args[0]); diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index e01a4d4b87e..9fa9bf77148 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -64,8 +64,12 @@ // Whether machine.bootloader() will enter the bootloader via reset, or direct jump. #ifndef MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET +#if defined(STM32N6) +#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0) +#else #define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (1) #endif +#endif // Whether to enable ROMFS on the internal flash. #ifndef MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH @@ -77,6 +81,11 @@ #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (0) #endif +// Whether to enable ROMFS on external XSPI flash. +#ifndef MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI (0) +#endif + // Whether to enable ROMFS partition 0. #ifndef MICROPY_HW_ROMFS_ENABLE_PART0 #define MICROPY_HW_ROMFS_ENABLE_PART0 (0) @@ -465,6 +474,16 @@ #define MICROPY_HW_MAX_UART (5) #define MICROPY_HW_MAX_LPUART (1) +// Configuration for STM32N6 series +#elif defined(STM32N6) + +#define MP_HAL_UNIQUE_ID_ADDRESS (UID_BASE) +#define PYB_EXTI_NUM_VECTORS (20) // only EXTI[15:0], RTC and TAMP currently supported +#define MICROPY_HW_MAX_I2C (4) +#define MICROPY_HW_MAX_TIMER (18) +#define MICROPY_HW_MAX_UART (10) +#define MICROPY_HW_MAX_LPUART (1) + // Configuration for STM32WB series #elif defined(STM32WB) @@ -589,8 +608,16 @@ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(MICROPY_HW_BDEV_SPIFLASH, (op), (uint32_t)MICROPY_HW_BDEV_SPIFLASH_CONFIG) : \ spi_bdev_ioctl(MICROPY_HW_BDEV_SPIFLASH, (op), (arg)) \ ) -#define MICROPY_HW_BDEV_READBLOCKS(dest, bl, n) spi_bdev_readblocks(MICROPY_HW_BDEV_SPIFLASH, (dest), (bl), (n)) -#define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) spi_bdev_writeblocks(MICROPY_HW_BDEV_SPIFLASH, (src), (bl), (n)) +#ifndef MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES +#define MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES (0) +#endif +#define MICROPY_HW_BDEV_SPIFLASH_OFFSET_BLOCKS (MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES / FLASH_BLOCK_SIZE) +#define MICROPY_HW_BDEV_READBLOCKS(dest, bl, n) spi_bdev_readblocks(MICROPY_HW_BDEV_SPIFLASH, (dest), MICROPY_HW_BDEV_SPIFLASH_OFFSET_BLOCKS + (bl), (n)) +#define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) spi_bdev_writeblocks(MICROPY_HW_BDEV_SPIFLASH, (src), MICROPY_HW_BDEV_SPIFLASH_OFFSET_BLOCKS + (bl), (n)) +#endif + +#if defined(STM32N6) +#define MICROPY_FATFS_MAX_SS (4096) #endif // Whether to enable caching for external SPI flash, to allow block writes that are diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index bfaa3fb0ba0..41ed3d08b7b 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -81,7 +81,7 @@ #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_VFS (1) #ifndef MICROPY_VFS_ROM -#define MICROPY_VFS_ROM (MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH || MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI) +#define MICROPY_VFS_ROM (MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH || MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI || MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI) #endif // control over Python builtins diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c index b4b2267fa02..fcd08cbd845 100644 --- a/ports/stm32/mphalport.c +++ b/ports/stm32/mphalport.c @@ -105,7 +105,7 @@ void mp_hal_gpio_clock_enable(GPIO_TypeDef *gpio) { #elif defined(STM32F4) || defined(STM32F7) #define AHBxENR AHB1ENR #define AHBxENR_GPIOAEN_Pos RCC_AHB1ENR_GPIOAEN_Pos - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32N6) #define AHBxENR AHB4ENR #define AHBxENR_GPIOAEN_Pos RCC_AHB4ENR_GPIOAEN_Pos #elif defined(STM32L0) diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 5756cb0560d..8713fe8370c 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -137,18 +137,26 @@ static inline void mpu_config_end(uint32_t irq_state) { enable_irq(irq_state); } -#elif defined(STM32H5) +#elif defined(STM32H5) || defined(STM32N6) #define MPU_REGION_SIG (MPU_REGION_NUMBER0) #define MPU_REGION_ETH (MPU_REGION_NUMBER1) -#define MPU_REGION_LAST_USED (MPU_REGION_NUMBER1) +#define MPU_REGION_DMA_UNCACHED_1 (MPU_REGION_NUMBER2) +#define MPU_REGION_DMA_UNCACHED_2 (MPU_REGION_NUMBER3) +#define MPU_REGION_LAST_USED (MPU_REGION_NUMBER3) #define ST_DEVICE_SIGNATURE_BASE (0x08fff800) #define ST_DEVICE_SIGNATURE_LIMIT (0x08ffffff) // STM32H5 Cortex-M33 MPU works differently from older cores. // Macro only takes region size in bytes, Attributes are coded in mpu_config_region(). +#define MPU_CONFIG_DISABLE (0) #define MPU_CONFIG_ETH(size) (size) +#define MPU_CONFIG_UNCACHED(size) (size) + +#if defined(STM32N6) +#define MPU_REGION_SIZE_32B (32) +#endif static inline void mpu_init(void) { // Configure attribute 0, inner-outer non-cacheable (=0x44). @@ -180,8 +188,12 @@ static inline uint32_t mpu_config_start(void) { } static inline void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t size) { - if (region == MPU_REGION_ETH) { - // Configure region 1 to make DMA memory non-cacheable. + if (size == 0) { + // Disable MPU for this region. + MPU->RNR = region; + MPU->RLAR &= ~MPU_RLAR_EN_Msk; + } else if (region == MPU_REGION_ETH || region == MPU_REGION_DMA_UNCACHED_1 || region == MPU_REGION_DMA_UNCACHED_2) { + // Configure region to make DMA memory non-cacheable. __DMB(); // Configure attribute 1, inner-outer non-cacheable (=0x44). diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index e3e2fcdd44e..a750e8f5be7 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -26,10 +26,10 @@ #include "py/mperrno.h" #include "py/mphal.h" +#include "boardctrl.h" #include "powerctrl.h" #include "rtc.h" #include "extmod/modbluetooth.h" -#include "py/mpconfig.h" #ifndef NO_QSTR #include "genhdr/pllfreqtable.h" #endif @@ -46,7 +46,7 @@ static uint32_t __attribute__((unused)) micropy_hw_hse_value = HSE_VALUE; static uint32_t __attribute__((unused)) micropy_hw_clk_pllm = MICROPY_HW_CLK_PLLM; #endif -#if defined(STM32H5) || defined(STM32H7) +#if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) #define RCC_SR RSR #if defined(STM32H747xx) #define RCC_SR_SFTRSTF RCC_RSR_SFT2RSTF @@ -63,7 +63,7 @@ static uint32_t __attribute__((unused)) micropy_hw_clk_pllm = MICROPY_HW_CLK_PLL #define POWERCTRL_GET_VOLTAGE_SCALING() PWR_REGULATOR_VOLTAGE_SCALE0 #elif defined(STM32H723xx) #define POWERCTRL_GET_VOLTAGE_SCALING() LL_PWR_GetRegulVoltageScaling() -#elif defined(STM32H5) +#elif defined(STM32H5) || defined(STM32N6) #define POWERCTRL_GET_VOLTAGE_SCALING() LL_PWR_GetRegulVoltageScaling() #else #define POWERCTRL_GET_VOLTAGE_SCALING() \ @@ -136,6 +136,12 @@ MP_NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, } MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { + #if defined(STM32N6) + LL_PWR_EnableBkUpAccess(); + TAMP_S->BKP31R = r0; + NVIC_SystemReset(); + #endif + #if MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET // Enter the bootloader via a reset, so everything is reset (including WDT). @@ -169,6 +175,8 @@ void powerctrl_check_enter_bootloader(void) { #endif } +#if !defined(STM32N6) + #if !defined(STM32F0) && !defined(STM32L0) && !defined(STM32WB) && !defined(STM32WL) typedef struct _sysclk_scaling_table_entry_t { @@ -781,6 +789,8 @@ static void powerctrl_low_power_exit_wb55() { #endif // !defined(STM32F0) && !defined(STM32G0) && !defined(STM32L0) && !defined(STM32L1) && !defined(STM32L4) +#endif + void powerctrl_enter_stop_mode(void) { // Disable IRQs so that the IRQ that wakes the device from stop mode is not // executed until after the clocks are reconfigured @@ -809,7 +819,7 @@ void powerctrl_enter_stop_mode(void) { __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_MSI); #endif - #if !defined(STM32F0) && !defined(STM32G0) && !defined(STM32G4) && !defined(STM32L0) && !defined(STM32L1) && !defined(STM32L4) && !defined(STM32WB) && !defined(STM32WL) + #if !defined(STM32F0) && !defined(STM32G0) && !defined(STM32G4) && !defined(STM32L0) && !defined(STM32L1) && !defined(STM32L4) && !defined(STM32N6) && !defined(STM32WB) && !defined(STM32WL) // takes longer to wake but reduces stop current HAL_PWREx_EnableFlashPowerDown(); #endif @@ -848,6 +858,8 @@ void powerctrl_enter_stop_mode(void) { #if defined(STM32F7) HAL_PWR_EnterSTOPMode((PWR_CR1_LPDS | PWR_CR1_LPUDS | PWR_CR1_FPDS | PWR_CR1_UDEN), PWR_STOPENTRY_WFI); + #elif defined(STM32N6) + HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI); #else HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); #endif @@ -912,6 +924,19 @@ void powerctrl_enter_stop_mode(void) { while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL1) { } + #elif defined(STM32N6) + + // Enable PLL1, and switch the CPU and system clock source to use PLL1. + LL_RCC_PLL1_Enable(); + while (!LL_RCC_PLL1_IsReady()) { + } + LL_RCC_SetCpuClkSource(LL_RCC_CPU_CLKSOURCE_IC1); + while (LL_RCC_GetCpuClkSource() != LL_RCC_CPU_CLKSOURCE_STATUS_IC1) { + } + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_IC2_IC6_IC11); + while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_IC2_IC6_IC11) { + } + #else // defined(STM32H5) // enable PLL @@ -1016,9 +1041,47 @@ void powerctrl_enter_stop_mode(void) { enable_irq(irq_state); } +#if defined(STM32N6) + +// Upon wake from standby, STM32N6 can resume execution from retained SRAM1. +// Place a small bootloader there which initialises XSPI in memory-mapped mode +// and jumps to the main application entry point. + +#include "xspi.h" + +extern uint32_t _estack; + +void Reset_Handler(void); + +void iram_bootloader_reset(void) { + #if defined(MICROPY_BOARD_LEAVE_STANDBY) + MICROPY_BOARD_LEAVE_STANDBY; + #endif + xspi_init(); + Reset_Handler(); +} + +// Very simple ARM vector table. +const uint32_t iram_bootloader_isr_vector[] = { + (uint32_t)&_estack, + (uint32_t)&iram_bootloader_reset, +}; + +#endif + MP_NORETURN void powerctrl_enter_standby_mode(void) { rtc_init_finalise(); + #if defined(STM32N6) + // Upon wake from standby, jump to the code at SRAM1. + // A board can reconfigure this in MICROPY_BOARD_ENTER_STANDBY if needed. + LL_PWR_EnableTCMSBRetention(); + LL_PWR_EnableTCMFLXSBRetention(); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_SYSCFG); + SCB_CleanDCache(); + SYSCFG->INITSVTORCR = (uint32_t)&iram_bootloader_isr_vector[0]; + #endif + #if defined(MICROPY_BOARD_ENTER_STANDBY) MICROPY_BOARD_ENTER_STANDBY #endif @@ -1039,6 +1102,13 @@ MP_NORETURN void powerctrl_enter_standby_mode(void) { mp_bluetooth_deinit(); #endif + #if defined(STM32N6) + + // Clear all WKUPx flags. + LL_PWR_ClearFlag_WU(); + + #else + // We need to clear the PWR wake-up-flag before entering standby, since // the flag may have been set by a previous wake-up event. Furthermore, // we need to disable the wake-up sources while clearing this flag, so @@ -1135,6 +1205,8 @@ MP_NORETURN void powerctrl_enter_standby_mode(void) { powerctrl_low_power_prep_wb55(); #endif + #endif + // enter standby mode HAL_PWR_EnterSTANDBYMode(); diff --git a/ports/stm32/powerctrl.h b/ports/stm32/powerctrl.h index 05a70e52c6a..724ab58366f 100644 --- a/ports/stm32/powerctrl.h +++ b/ports/stm32/powerctrl.h @@ -34,6 +34,12 @@ void stm32_system_init(void); #else static inline void stm32_system_init(void) { SystemInit(); + + #if defined(STM32N6) + // The ROM bootloader uses PLL1 to set the CPU to 400MHz, so update + // the value of SystemCoreClock to reflect the hardware state. + SystemCoreClockUpdate(); + #endif } #endif diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 31dae527c1e..059d2a45da9 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -47,9 +47,15 @@ void stm32_system_init(void) { #endif void powerctrl_config_systick(void) { + #if defined(STM32N6) + uint32_t systick_source_freq = HAL_RCC_GetCpuClockFreq(); + #else + uint32_t systick_source_freq = HAL_RCC_GetHCLKFreq(); + #endif + // Configure SYSTICK to run at 1kHz (1ms interval) SysTick->CTRL |= SYSTICK_CLKSOURCE_HCLK; - SysTick_Config(HAL_RCC_GetHCLKFreq() / 1000); + SysTick_Config(systick_source_freq / 1000); NVIC_SetPriority(SysTick_IRQn, IRQ_PRI_SYSTICK); #if !BUILDING_MBOOT && (defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB)) @@ -410,6 +416,124 @@ void SystemClock_Config(void) { DBGMCU->CR &= ~(DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY); #endif } + +#elif defined(STM32N6) + +void SystemClock_Config(void) { + // Enable HSI. + LL_RCC_HSI_Enable(); + while (!LL_RCC_HSI_IsReady()) { + } + + // Switch the CPU clock source to HSI. + LL_RCC_SetCpuClkSource(LL_RCC_CPU_CLKSOURCE_HSI); + while (LL_RCC_GetCpuClkSource() != LL_RCC_CPU_CLKSOURCE_STATUS_HSI) { + } + + // Switch the system clock source to HSI. + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); + while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) { + } + + // Disable all ICx clocks. + RCC->DIVENCR = 0x000fffff; + + // This doesn't work, VOSRDY never becomes active. + #if 0 + LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE0); + while (!LL_PWR_IsActiveFlag_VOSRDY()) { + } + #endif + + // Enable HSE. + LL_RCC_HSE_Enable(); + while (!LL_RCC_HSE_IsReady()) { + } + + // Disable PLL1. + LL_RCC_PLL1_Disable(); + while (LL_RCC_PLL1_IsReady()) { + } + + // Configure PLL1 for use as system clock. + LL_RCC_PLL1_SetSource(LL_RCC_PLLSOURCE_HSE); + LL_RCC_PLL1_DisableBypass(); + LL_RCC_PLL1_DisableFractionalModulationSpreadSpectrum(); + LL_RCC_PLL1_SetM(MICROPY_HW_CLK_PLLM); + LL_RCC_PLL1_SetN(MICROPY_HW_CLK_PLLN); + LL_RCC_PLL1_SetP1(MICROPY_HW_CLK_PLLP1); + LL_RCC_PLL1_SetP2(MICROPY_HW_CLK_PLLP2); + LL_RCC_PLL1_SetFRACN(MICROPY_HW_CLK_PLLFRAC); + LL_RCC_PLL1P_Enable(); + + // Enable PLL1. + LL_RCC_PLL1_Enable(); + while (!LL_RCC_PLL1_IsReady()) { + } + + // Configure IC1, IC2, IC6, IC11. + LL_RCC_IC1_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC1_SetDivider(1); + LL_RCC_IC1_Enable(); + LL_RCC_IC2_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC2_SetDivider(2); + LL_RCC_IC2_Enable(); + LL_RCC_IC6_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC6_SetDivider(1); + LL_RCC_IC6_Enable(); + LL_RCC_IC11_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC11_SetDivider(1); + LL_RCC_IC11_Enable(); + + // Configure IC14 at 100MHz for slower peripherals. + LL_RCC_IC14_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC14_SetDivider(8); + LL_RCC_IC14_Enable(); + + // Enable buses. + LL_BUS_EnableClock(LL_APB5 | LL_APB4 | LL_APB3 | LL_APB2 | LL_APB1 | LL_AHB5 | LL_AHB4 | LL_AHB3 | LL_AHB2 | LL_AHB1); + LL_MISC_EnableClock(LL_PER); + + // Configure bus dividers. + LL_RCC_SetAHBPrescaler(LL_RCC_AHB_DIV_2); + LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1); + LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1); + LL_RCC_SetAPB4Prescaler(LL_RCC_APB4_DIV_1); + LL_RCC_SetAPB5Prescaler(LL_RCC_APB5_DIV_1); + + // Switch the CPU clock source to IC1 (connected to PLL1). + LL_RCC_SetCpuClkSource(LL_RCC_CPU_CLKSOURCE_IC1); + while (LL_RCC_GetCpuClkSource() != LL_RCC_CPU_CLKSOURCE_STATUS_IC1) { + } + + // Switch the system clock source to IC2/IC6/IC11 (connected to PLL1). + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_IC2_IC6_IC11); + while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_IC2_IC6_IC11) { + } + + // ADC clock configuration, HCLK/2. + LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_HCLK); + LL_RCC_SetADCPrescaler(2 - 1); + + // USB clock configuration. + #if MICROPY_HW_ENABLE_USB + + // Select HSE/2 as output of direct HSE signal. + LL_RCC_HSE_SelectHSEDiv2AsDiv2Clock(); + + // Select HSE/2 for OTG1 clock source. + LL_RCC_SetClockSource(LL_RCC_OTGPHY1_CLKSOURCE_HSE_DIV_2_OSC); + LL_RCC_SetClockSource(LL_RCC_OTGPHY1CKREF_CLKSOURCE_HSE_DIV_2_OSC); + LL_RCC_SetOTGPHYClockSource(LL_RCC_OTGPHY1_CLKSOURCE_HSE_DIV_2_OSC); + LL_RCC_SetOTGPHYCKREFClockSource(LL_RCC_OTGPHY1CKREF_CLKSOURCE_HSE_DIV_2_OSC); + + #endif + + // Reconfigure clock state and SysTick. + SystemCoreClockUpdate(); + powerctrl_config_systick(); +} + #elif defined(STM32WB) void SystemClock_Config(void) { diff --git a/ports/stm32/resethandler_iram.s b/ports/stm32/resethandler_iram.s new file mode 100644 index 00000000000..49a8b40068f --- /dev/null +++ b/ports/stm32/resethandler_iram.s @@ -0,0 +1,82 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2025 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. + */ + + .syntax unified + .cpu cortex-m4 + .thumb + + .section .text.Reset_Handler + .global Reset_Handler + .type Reset_Handler, %function + +Reset_Handler: + /* Save the first argument to pass through to stm32_main */ + mov r4, r0 + + /* Load the stack pointer */ + ldr sp, =_estack + + /* Initialise the iram section */ + ldr r1, =_siiram + ldr r2, =_siram + ldr r3, =_eiram + b .iram_copy_entry + nop +.iram_copy_loop: + ldr r0, [r1], #4 /* Should be 4-aligned to be as fast as possible */ + str r0, [r2], #4 +.iram_copy_entry: + cmp r2, r3 + bcc .iram_copy_loop + + /* Initialise the data section */ + ldr r1, =_sidata + ldr r2, =_sdata + ldr r3, =_edata + b .data_copy_entry +.data_copy_loop: + ldr r0, [r1], #4 /* Should be 4-aligned to be as fast as possible */ + str r0, [r2], #4 +.data_copy_entry: + cmp r2, r3 + bcc .data_copy_loop + + /* Zero out the BSS section */ + movs r0, #0 + ldr r1, =_sbss + ldr r2, =_ebss + b .bss_zero_entry +.bss_zero_loop: + str r0, [r1], #4 /* Should be 4-aligned to be as fast as possible */ +.bss_zero_entry: + cmp r1, r2 + bcc .bss_zero_loop + + /* Jump to the main code */ + mov r0, r4 + b stm32_main + + .size Reset_Handler, .-Reset_Handler diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index 8dadc4a88d7..b90d17149bd 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -100,6 +100,10 @@ static bool rtc_need_init_finalise = false; #define RCC_BDCR_LSEBYP RCC_CSR_LSEBYP #endif +#if defined(STM32N6) +#define RCC_DBP_TIMEOUT_VALUE (5) +#endif + void rtc_init_start(bool force_init) { // Enable the RTC APB bus clock, to communicate with the RTC. #if defined(STM32H5) @@ -129,6 +133,32 @@ void rtc_init_start(bool force_init) { if (!force_init) { bool rtc_running = false; + #if defined(STM32N6) + if (LL_RCC_IsEnabledRTC() + && LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSE + && LL_RCC_LSE_IsReady()) { + // LSE is enabled & ready --> no need to (re-)init RTC + rtc_running = true; + // remove Backup Domain write protection + HAL_PWR_EnableBkUpAccess(); + // Clear source Reset Flag + __HAL_RCC_CLEAR_RESET_FLAGS(); + // provide some status information + rtc_info |= 0x40000; + } else if (LL_RCC_IsEnabledRTC() + && LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSI) { + // LSI configured as the RTC clock source --> no need to (re-)init RTC + rtc_running = true; + // remove Backup Domain write protection + HAL_PWR_EnableBkUpAccess(); + // Clear source Reset Flag + __HAL_RCC_CLEAR_RESET_FLAGS(); + // Turn the LSI on (it may need this even if the RTC is running) + LL_RCC_LSI_Enable(); + // provide some status information + rtc_info |= 0x80000; + } + #else uint32_t bdcr = RCC->BDCR; if ((bdcr & (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL | RCC_BDCR_LSEON | RCC_BDCR_LSERDY)) == (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON | RCC_BDCR_LSERDY)) { @@ -157,6 +187,7 @@ void rtc_init_start(bool force_init) { // provide some status information rtc_info |= 0x80000; } + #endif if (rtc_running) { // Provide information about the registers that indicated the RTC is running. @@ -296,7 +327,7 @@ static HAL_StatusTypeDef PYB_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct return HAL_TIMEOUT; } } - #elif defined(STM32H5) + #elif defined(STM32H5) || defined(STM32N6) // Wait for Backup domain Write protection disable while (!LL_PWR_IsEnabledBkUpAccess()) { if (HAL_GetTick() - tickstart > RCC_DBP_TIMEOUT_VALUE) { @@ -381,7 +412,7 @@ static HAL_StatusTypeDef PYB_RTC_Init(RTC_HandleTypeDef *hrtc) { #elif defined(STM32F7) hrtc->Instance->OR &= (uint32_t) ~RTC_OR_ALARMTYPE; hrtc->Instance->OR |= (uint32_t)(hrtc->Init.OutPutType); - #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32WL) + #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WL) hrtc->Instance->CR &= (uint32_t) ~RTC_CR_TAMPALRM_TYPE_Msk; hrtc->Instance->CR |= (uint32_t)(hrtc->Init.OutPutType); #else @@ -413,7 +444,14 @@ static void PYB_RTC_MspInit_Kick(RTC_HandleTypeDef *hrtc, bool rtc_use_lse, bool RCC_OscInitTypeDef RCC_OscInitStruct; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE; + #if defined(STM32N6) + RCC_OscInitStruct.PLL1.PLLState = RCC_PLL_NONE; + RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE; + RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_NONE; + RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE; + #else RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; + #endif #if MICROPY_HW_RTC_USE_BYPASS if (rtc_use_byp) { RCC_OscInitStruct.LSEState = RCC_LSE_BYPASS; @@ -651,6 +689,8 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_datetime_obj, 1, 2, pyb_rtc_datetime #define RTC_WKUP_IRQn RTC_IRQn #elif defined(STM32G0) #define RTC_WKUP_IRQn RTC_TAMP_IRQn +#elif defined(STM32N6) +#define RTC_WKUP_IRQn RTC_S_IRQn #endif // wakeup(None) @@ -759,8 +799,9 @@ mp_obj_t pyb_rtc_wakeup(size_t n_args, const mp_obj_t *args) { #if defined(STM32G0) || defined(STM32G4) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) EXTI->IMR1 |= 1 << EXTI_RTC_WAKEUP; EXTI->RTSR1 |= 1 << EXTI_RTC_WAKEUP; - #elif defined(STM32H5) + #elif defined(STM32H5) || defined(STM32N6) EXTI->IMR1 |= 1 << EXTI_RTC_WAKEUP; + EXTI->RTSR1 |= 1 << EXTI_RTC_WAKEUP; #elif defined(STM32H7) EXTI_D1->IMR1 |= 1 << EXTI_RTC_WAKEUP; EXTI->RTSR1 |= 1 << EXTI_RTC_WAKEUP; @@ -772,8 +813,8 @@ mp_obj_t pyb_rtc_wakeup(size_t n_args, const mp_obj_t *args) { // clear interrupt flags #if defined(STM32G0) || defined(STM32G4) || defined(STM32WL) RTC->ICSR &= ~RTC_ICSR_WUTWF; - #elif defined(STM32H5) - RTC->SCR = RTC_SCR_CWUTF; + #elif defined(STM32H5) || defined(STM32N6) + LL_RTC_ClearFlag_WUT(RTC); #elif defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) RTC->SR &= ~RTC_SR_WUTF; #else @@ -783,7 +824,7 @@ mp_obj_t pyb_rtc_wakeup(size_t n_args, const mp_obj_t *args) { EXTI->PR1 = 1 << EXTI_RTC_WAKEUP; #elif defined(STM32H7) EXTI_D1->PR1 = 1 << EXTI_RTC_WAKEUP; - #elif defined(STM32G0) || defined(STM32H5) + #elif defined(STM32G0) || defined(STM32H5) || defined(STM32N6) // Do nothing #else EXTI->PR = 1 << EXTI_RTC_WAKEUP; @@ -799,7 +840,7 @@ mp_obj_t pyb_rtc_wakeup(size_t n_args, const mp_obj_t *args) { RTC->WPR = 0xff; // disable external interrupts on line EXTI_RTC_WAKEUP - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) EXTI->IMR1 &= ~(1 << EXTI_RTC_WAKEUP); #elif defined(STM32H7) EXTI_D1->IMR1 |= 1 << EXTI_RTC_WAKEUP; diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 706d6315c44..b91fa3a9c28 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -40,7 +40,7 @@ #if MICROPY_HW_ENABLE_SDCARD || MICROPY_HW_ENABLE_MMCARD -#if defined(STM32F7) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) +#if defined(STM32F7) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) // The H7/F7/L4 have 2 SDMMC peripherals, but at the moment this driver only supports // using one of them in a given build, selected by MICROPY_HW_SDCARD_SDMMC. @@ -104,7 +104,7 @@ #define SDIO_HARDWARE_FLOW_CONTROL_DISABLE SDMMC_HARDWARE_FLOW_CONTROL_DISABLE #define SDIO_HARDWARE_FLOW_CONTROL_ENABLE SDMMC_HARDWARE_FLOW_CONTROL_ENABLE -#if defined(STM32H5) || defined(STM32H7) +#if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) #define SDIO_TRANSFER_CLK_DIV SDMMC_NSpeed_CLK_DIV #define SDIO_USE_GPDMA 0 #else @@ -214,7 +214,7 @@ static void sdmmc_msp_init(void) { // enable SDIO clock SDMMC_CLK_ENABLE(); - #if defined(STM32H7) + #if defined(STM32H7) || defined(STM32N6) // Reset SDMMC SDMMC_FORCE_RESET(); SDMMC_RELEASE_RESET(); @@ -270,7 +270,7 @@ static HAL_StatusTypeDef sdmmc_init_sd(void) { // SD device interface configuration sdmmc_handle.sd.Instance = SDIO; sdmmc_handle.sd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; - #if !defined(STM32H5) && !defined(STM32H7) + #if !defined(STM32H5) && !defined(STM32H7) && !defined(STM32N6) sdmmc_handle.sd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; #endif sdmmc_handle.sd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_ENABLE; diff --git a/ports/stm32/sdio.c b/ports/stm32/sdio.c index 99d05a51555..de82ceadc5f 100644 --- a/ports/stm32/sdio.c +++ b/ports/stm32/sdio.c @@ -77,7 +77,11 @@ static volatile uint8_t *sdmmc_buf_top; #define SDMMC_IRQHandler SDMMC2_IRQHandler #define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC2_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC2_CLK_DISABLE() +#if defined(STM32N6) +#define SDMMC_IS_CLK_DISABLED() (!__HAL_RCC_SDMMC2_IS_CLK_ENABLED()) +#else #define SDMMC_IS_CLK_DISABLED() __HAL_RCC_SDMMC2_IS_CLK_DISABLED() +#endif #define STATIC_AF_SDMMC_CK STATIC_AF_SDMMC2_CK #define STATIC_AF_SDMMC_CMD STATIC_AF_SDMMC2_CMD #define STATIC_AF_SDMMC_D0 STATIC_AF_SDMMC2_D0 @@ -96,9 +100,17 @@ static volatile uint8_t *sdmmc_buf_top; #define MICROPY_HW_SDIO_CMD (pin_D2) #endif -#if defined(STM32H7) +#if defined(STM32H7) || defined(STM32N6) static uint32_t safe_divide(uint32_t denom) { + #if defined(STM32N6) + #if MICROPY_HW_SDIO_SDMMC == 1 + uint32_t num = LL_RCC_GetSDMMCClockFreq(LL_RCC_SDMMC1_CLKSOURCE); + #else + uint32_t num = LL_RCC_GetSDMMCClockFreq(LL_RCC_SDMMC2_CLKSOURCE); + #endif + #else uint32_t num = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SDMMC); + #endif uint32_t divres; divres = num / (2U * denom); @@ -119,11 +131,15 @@ void sdio_init(uint32_t irq_pri) { mp_hal_pin_config_alt_static(MICROPY_HW_SDIO_CMD, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, STATIC_AF_SDMMC_CMD); SDMMC_CLK_ENABLE(); // enable SDIO peripheral + #if defined(STM32N6) + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_SDMMC1); + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_SDMMC2); + #endif SDMMC_TypeDef *SDIO = SDMMC; #if defined(STM32F7) SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_PWRSAV | (120 - 2); // 1-bit, 400kHz - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32N6) SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_PWRSAV | safe_divide(400000U); // 1-bit, 400kHz #else SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_PWRSAV | (120 / 2); // 1-bit, 400kHz @@ -172,7 +188,7 @@ void sdio_enable_high_speed_4bit(void) { mp_hal_delay_us(10); #if defined(STM32F7) SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_WIDBUS_0 | SDMMC_CLKCR_BYPASS /*| SDMMC_CLKCR_PWRSAV*/; // 4-bit, 48MHz - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32N6) SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_WIDBUS_0 | safe_divide(48000000U); // 4-bit, 48MHz #else SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_WIDBUS_0; // 4-bit, 48MHz @@ -199,7 +215,7 @@ void SDMMC_IRQHandler(void) { sdmmc_irq_state = SDMMC_IRQ_STATE_DONE; return; } - #if defined(STM32H7) + #if defined(STM32H7) || defined(STM32N6) if (!sdmmc_dma) { while (sdmmc_buf_cur < sdmmc_buf_top && (SDMMC->STA & SDMMC_STA_DPSMACT) && !(SDMMC->STA & SDMMC_STA_RXFIFOE)) { *(uint32_t *)sdmmc_buf_cur = SDMMC->FIFO; @@ -413,11 +429,15 @@ int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t le dma_nohal_init(&dma_SDIO_0, dma_config); dma_nohal_start(&dma_SDIO_0, dma_src, dma_dest, dma_len); #else + #if defined(STM32N6) + SDMMC->IDMABASER = (uint32_t)buf; + #else SDMMC->IDMABASE0 = (uint32_t)buf; + #endif SDMMC->IDMACTRL = SDMMC_IDMA_IDMAEN; #endif } else { - #if defined(STM32H7) + #if defined(STM32H7) || defined(STM32N6) SDMMC->IDMACTRL = 0; #endif } diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 96dd170652e..19f2b65ed28 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -106,7 +106,7 @@ const spi_t spi_obj[6] = { #error "spi_obj needs updating for new value of MICROPY_HW_SUBGHZSPI_ID" #endif -#if defined(STM32H5) || defined(STM32H7) +#if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) // STM32H5/H7 HAL requires SPI IRQs to be enabled and handled. #if defined(MICROPY_HW_SPI1_SCK) void SPI1_IRQHandler(void) { @@ -176,6 +176,18 @@ void spi_init0(void) { #if defined(MICROPY_HW_SUBGHZSPI_ID) SPIHandleSubGhz.Instance = SUBGHZSPI; #endif + + #if defined(STM32N6) + // SPI1/2/3/6 clock configuration, PCLKx (max 200MHz). + LL_RCC_SetSPIClockSource(LL_RCC_SPI1_CLKSOURCE_PCLK2); + LL_RCC_SetSPIClockSource(LL_RCC_SPI2_CLKSOURCE_PCLK1); + LL_RCC_SetSPIClockSource(LL_RCC_SPI3_CLKSOURCE_PCLK1); + LL_RCC_SetSPIClockSource(LL_RCC_SPI6_CLKSOURCE_PCLK4); + + // SPI4/5 clock configuration, IC14 (max 100MHz). + LL_RCC_SetSPIClockSource(LL_RCC_SPI4_CLKSOURCE_IC14); + LL_RCC_SetSPIClockSource(LL_RCC_SPI5_CLKSOURCE_IC14); + #endif } int spi_find_index(mp_obj_t id) { @@ -256,6 +268,20 @@ static uint32_t spi_get_source_freq(SPI_HandleTypeDef *spi) { } else { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI6); } + #elif defined(STM32N6) + if (spi->Instance == SPI1) { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI1_CLKSOURCE); + } else if (spi->Instance == SPI2) { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI2_CLKSOURCE); + } else if (spi->Instance == SPI3) { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI3_CLKSOURCE); + } else if (spi->Instance == SPI4) { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI4_CLKSOURCE); + } else if (spi->Instance == SPI5) { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI5_CLKSOURCE); + } else { + return LL_RCC_GetSPIClockFreq(LL_RCC_SPI6_CLKSOURCE); + } #else // !STM32F0, !STM32G0, !STM32H #if defined(SPI2) if (spi->Instance == SPI2) { @@ -470,7 +496,7 @@ int spi_init(const spi_t *self, bool enable_nss_pin) { dma_invalidate_channel(self->tx_dma_descr); dma_invalidate_channel(self->rx_dma_descr); - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) NVIC_SetPriority(irqn, IRQ_PRI_SPI); HAL_NVIC_EnableIRQ(irqn); #else @@ -724,7 +750,7 @@ void spi_print(const mp_print_t *print, const spi_t *spi_obj, bool legacy) { if (spi->State != HAL_SPI_STATE_RESET) { if (spi->Init.Mode == SPI_MODE_MASTER) { // compute baudrate - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) uint log_prescaler = (spi->Init.BaudRatePrescaler >> 28) + 1; #else uint log_prescaler = (spi->Init.BaudRatePrescaler >> 3) + 1; diff --git a/ports/stm32/spibdev.c b/ports/stm32/spibdev.c index fecd4a99153..d7a75ed2402 100644 --- a/ports/stm32/spibdev.c +++ b/ports/stm32/spibdev.c @@ -32,6 +32,16 @@ #if MICROPY_HW_ENABLE_STORAGE +#if MICROPY_HW_RUNS_FROM_EXT_FLASH +// Disable all interrupts. +#define FLASH_WRITE_ENTER uint32_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION() +#define FLASH_WRITE_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state) +#else +// Prevent cache flushing and USB access. +#define FLASH_WRITE_ENTER uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH) +#define FLASH_WRITE_EXIT restore_irq_pri(basepri) +#endif + int32_t spi_bdev_ioctl(spi_bdev_t *bdev, uint32_t op, uint32_t arg) { switch (op) { case BDEV_IOCTL_INIT: @@ -68,6 +78,7 @@ int32_t spi_bdev_ioctl(spi_bdev_t *bdev, uint32_t op, uint32_t arg) { } #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + int spi_bdev_readblocks(spi_bdev_t *bdev, uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access int ret = mp_spiflash_cached_read(&bdev->spiflash, block_num * FLASH_BLOCK_SIZE, num_blocks * FLASH_BLOCK_SIZE, dest); @@ -87,20 +98,36 @@ int spi_bdev_writeblocks(spi_bdev_t *bdev, const uint8_t *src, uint32_t block_nu return ret; } + +#elif FLASH_BLOCK_SIZE == MP_SPIFLASH_ERASE_BLOCK_SIZE + +int spi_bdev_readblocks(spi_bdev_t *bdev, uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + int ret = spi_bdev_readblocks_raw(bdev, dest, block_num, 0, num_blocks * FLASH_BLOCK_SIZE); + return ret; +} + +int spi_bdev_writeblocks(spi_bdev_t *bdev, const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + int ret = spi_bdev_eraseblocks_raw(bdev, block_num, num_blocks * FLASH_BLOCK_SIZE); + if (ret == 0) { + ret = spi_bdev_writeblocks_raw(bdev, src, block_num, 0, num_blocks * FLASH_BLOCK_SIZE); + } + return ret; +} + #endif // MICROPY_HW_SPIFLASH_ENABLE_CACHE int spi_bdev_readblocks_raw(spi_bdev_t *bdev, uint8_t *dest, uint32_t block_num, uint32_t block_offset, uint32_t num_bytes) { - uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access + FLASH_WRITE_ENTER; int ret = mp_spiflash_read(&bdev->spiflash, block_num * MP_SPIFLASH_ERASE_BLOCK_SIZE + block_offset, num_bytes, dest); - restore_irq_pri(basepri); + FLASH_WRITE_EXIT; return ret; } int spi_bdev_writeblocks_raw(spi_bdev_t *bdev, const uint8_t *src, uint32_t block_num, uint32_t block_offset, uint32_t num_bytes) { - uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access + FLASH_WRITE_ENTER; int ret = mp_spiflash_write(&bdev->spiflash, block_num * MP_SPIFLASH_ERASE_BLOCK_SIZE + block_offset, num_bytes, src); - restore_irq_pri(basepri); + FLASH_WRITE_EXIT; return ret; } @@ -108,9 +135,9 @@ int spi_bdev_writeblocks_raw(spi_bdev_t *bdev, const uint8_t *src, uint32_t bloc int spi_bdev_eraseblocks_raw(spi_bdev_t *bdev, uint32_t block_num, uint32_t num_bytes) { int ret = 0; while (num_bytes >= MP_SPIFLASH_ERASE_BLOCK_SIZE) { - uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access + FLASH_WRITE_ENTER; ret = mp_spiflash_erase_block(&bdev->spiflash, block_num * MP_SPIFLASH_ERASE_BLOCK_SIZE); - restore_irq_pri(basepri); + FLASH_WRITE_EXIT; if (ret) { break; } diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index 718fa8cf067..e2e7d955c6c 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -43,19 +43,17 @@ ifneq ($(BUILDING_MBOOT),1) # Select hardware floating-point support. SUPPORTS_HARDWARE_FP_SINGLE = 0 SUPPORTS_HARDWARE_FP_DOUBLE = 0 -ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ)) +ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ STM32N657xx)) CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 SUPPORTS_HARDWARE_FP_DOUBLE = 1 -else -ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 g0 l0 l1 wl)) +else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 g0 l0 l1 wl)) CFLAGS_CORTEX_M += -msoft-float else CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 endif endif -endif # Options for particular MCU series. CFLAGS_MCU_f0 = $(CFLAGS_CORTEX_M) -mtune=cortex-m0 -mcpu=cortex-m0 @@ -68,6 +66,7 @@ CFLAGS_MCU_l1 = $(CFLAGS_CORTEX_M) -mtune=cortex-m3 -mcpu=cortex-m3 CFLAGS_MCU_l4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 CFLAGS_MCU_h5 = $(CFLAGS_CORTEX_M) -mtune=cortex-m33 -mcpu=cortex-m33 CFLAGS_MCU_h7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7 +CFLAGS_MCU_n6 = $(CFLAGS_CORTEX_M) -mtune=cortex-m55 -mcpu=cortex-m55 -mcmse CFLAGS_MCU_wb = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 CFLAGS_MCU_wl = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 @@ -81,5 +80,6 @@ MPY_CROSS_MCU_ARCH_l1 = armv7m MPY_CROSS_MCU_ARCH_l4 = armv7m MPY_CROSS_MCU_ARCH_h5 = armv7m MPY_CROSS_MCU_ARCH_h7 = armv7m +MPY_CROSS_MCU_ARCH_n6 = armv7m # really armv8m MPY_CROSS_MCU_ARCH_wb = armv7m MPY_CROSS_MCU_ARCH_wl = armv7m diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 4bf509bb946..3639e2f0499 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -343,14 +343,22 @@ void OTG_FS_IRQHandler(void) { } #endif #if MICROPY_HW_USB_HS +#if defined(STM32N6) +void USB1_OTG_HS_IRQHandler(void) { + IRQ_ENTER(USB1_OTG_HS_IRQn); + HAL_PCD_IRQHandler(&pcd_hs_handle); + IRQ_EXIT(USB1_OTG_HS_IRQn); +} +#else void OTG_HS_IRQHandler(void) { IRQ_ENTER(OTG_HS_IRQn); HAL_PCD_IRQHandler(&pcd_hs_handle); IRQ_EXIT(OTG_HS_IRQn); } #endif +#endif -#if MICROPY_HW_USB_FS || MICROPY_HW_USB_HS +#if (MICROPY_HW_USB_FS || MICROPY_HW_USB_HS) && !defined(STM32N6) /** * @brief This function handles USB OTG Common FS/HS Wakeup functions. * @param *pcd_handle for FS or HS @@ -421,7 +429,7 @@ void OTG_FS_WKUP_IRQHandler(void) { } #endif -#if MICROPY_HW_USB_HS +#if MICROPY_HW_USB_HS && !defined(STM32N6) /** * @brief This function handles USB OTG HS Wakeup IRQ Handler. * @param None @@ -480,7 +488,7 @@ void ETH_WKUP_IRQHandler(void) { } #endif -#if defined(STM32H5) +#if defined(STM32H5) || defined(STM32N6) void TAMP_IRQHandler(void) { IRQ_ENTER(TAMP_IRQn); Handle_EXTI_Irq(EXTI_RTC_TAMP); @@ -502,6 +510,9 @@ void TAMP_STAMP_IRQHandler(void) { #if defined(STM32H5) void RTC_IRQHandler(void) +#elif defined(STM32N6) +#define RTC_WKUP_IRQn RTC_S_IRQn +void RTC_S_IRQHandler(void) #else void RTC_WKUP_IRQHandler(void) #endif @@ -509,8 +520,8 @@ void RTC_WKUP_IRQHandler(void) IRQ_ENTER(RTC_WKUP_IRQn); #if defined(STM32G0) || defined(STM32G4) || defined(STM32WL) RTC->MISR &= ~RTC_MISR_WUTMF; // clear wakeup interrupt flag - #elif defined(STM32H5) - RTC->SCR = RTC_SCR_CWUTF; // clear wakeup interrupt flag + #elif defined(STM32H5) || defined(STM32N6) + LL_RTC_ClearFlag_WUT(RTC); #elif defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) RTC->SR &= ~RTC_SR_WUTF; // clear wakeup interrupt flag #else @@ -520,6 +531,12 @@ void RTC_WKUP_IRQHandler(void) IRQ_EXIT(RTC_WKUP_IRQn); } +#if defined(STM32N6) +void RTC_IRQHandler(void) { + RTC_S_IRQHandler(); +} +#endif + #if defined(STM32F0) || defined(STM32G0) || defined(STM32L0) #if defined(STM32G0) diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index d810261fbce..d26ac821e59 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -32,6 +32,7 @@ #include "led.h" #include "storage.h" #include "irq.h" +#include "xspi.h" #if MICROPY_HW_ENABLE_STORAGE @@ -44,13 +45,17 @@ static bool storage_is_initialised = false; +#if !defined(STM32N6) static void storage_systick_callback(uint32_t ticks_ms); +#endif void storage_init(void) { if (!storage_is_initialised) { storage_is_initialised = true; + #if !defined(STM32N6) systick_enable_dispatch(SYSTICK_DISPATCH_STORAGE, storage_systick_callback); + #endif MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_INIT, 0); @@ -58,10 +63,12 @@ void storage_init(void) { MICROPY_HW_BDEV2_IOCTL(BDEV_IOCTL_INIT, 0); #endif + #if !defined(STM32N6) // Enable the flash IRQ, which is used to also call our storage IRQ handler // It must go at the same priority as USB (see comment in irq.h). NVIC_SetPriority(FLASH_IRQn, IRQ_PRI_FLASH); HAL_NVIC_EnableIRQ(FLASH_IRQn); + #endif } } @@ -77,6 +84,7 @@ uint32_t storage_get_block_count(void) { #endif } +#if !defined(STM32N6) static void storage_systick_callback(uint32_t ticks_ms) { if (STORAGE_IDLE_TICK(ticks_ms)) { // Trigger a FLASH IRQ to execute at a lower priority @@ -96,6 +104,7 @@ void FLASH_IRQHandler(void) { #endif IRQ_EXIT(FLASH_IRQn); } +#endif void storage_flush(void) { MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_SYNC, 0); @@ -235,11 +244,11 @@ int storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_bl // Board defined an external SPI flash for use with extended block protocol #define MICROPY_HW_BDEV_BLOCKSIZE_EXT (MP_SPIFLASH_ERASE_BLOCK_SIZE) #define MICROPY_HW_BDEV_READBLOCKS_EXT(dest, bl, off, len) \ - (spi_bdev_readblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, (dest), (bl), (off), (len))) + (spi_bdev_readblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, (dest), MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE + (bl), (off), (len))) #define MICROPY_HW_BDEV_WRITEBLOCKS_EXT(src, bl, off, len) \ - (spi_bdev_writeblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, (src), (bl), (off), (len))) + (spi_bdev_writeblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, (src), MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE + (bl), (off), (len))) #define MICROPY_HW_BDEV_ERASEBLOCKS_EXT(bl, len) \ - (spi_bdev_eraseblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, (bl), (len))) + (spi_bdev_eraseblocks_raw(MICROPY_HW_BDEV_SPIFLASH_EXTENDED, MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE + (bl), (len))) #elif (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) && MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE // Board uses littlefs and internal flash, so enable extended block protocol on internal flash diff --git a/ports/stm32/storage.h b/ports/stm32/storage.h index accf6c39043..75cb0e9c1e5 100644 --- a/ports/stm32/storage.h +++ b/ports/stm32/storage.h @@ -28,7 +28,11 @@ #include "drivers/memory/spiflash.h" +#if defined(STM32N6) +#define FLASH_BLOCK_SIZE (4096) +#else #define FLASH_BLOCK_SIZE (512) +#endif #define FLASH_PART1_START_BLOCK (0x100) // Try to match Python-level VFS block protocol where possible for these constants diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 9d65b484cd1..4ec467d9db5 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -261,6 +261,12 @@ uint32_t timer_get_source_freq(uint32_t tim_id) { } } + #elif defined(STM32N6) + + // Timers are clocked either by ck_timg1 or ck_timg2. + // Both of those have the same frequency: sys_bus_ck / prescaler(TIMPRE) + return LL_RCC_GetSystemClockFreq() / (1 << LL_RCC_GetTIMPrescaler()); + #else uint32_t source, clk_div; @@ -846,7 +852,9 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(1, TIM1_UP_TIM16_IRQn), #endif #endif + TIM_ENTRY(2, TIM2_IRQn), + #if defined(TIM3) #if defined(STM32G0B1xx) || defined(STM32G0C1xx) TIM_ENTRY(3, TIM3_TIM4_IRQn), @@ -854,6 +862,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(3, TIM3_IRQn), #endif #endif + #if defined(TIM4) #if defined(STM32G0B1xx) || defined(STM32G0C1xx) TIM_ENTRY(3, TIM3_TIM4_IRQn), @@ -861,20 +870,23 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(4, TIM4_IRQn), #endif #endif + #if defined(TIM5) TIM_ENTRY(5, TIM5_IRQn), #endif + #if defined(TIM6) #if defined(STM32F412Zx) || defined(STM32L1) TIM_ENTRY(6, TIM6_IRQn), #elif defined(STM32G0) TIM_ENTRY(6, TIM6_DAC_LPTIM1_IRQn), - #elif defined(STM32H5) + #elif defined(STM32H5) || defined(STM32N6) TIM_ENTRY(6, TIM6_IRQn), #else TIM_ENTRY(6, TIM6_DAC_IRQn), #endif #endif + #if defined(TIM7) #if defined(STM32G0) TIM_ENTRY(7, TIM7_LPTIM2_IRQn), @@ -894,7 +906,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM9) - #if defined(STM32L1) + #if defined(STM32L1) || defined(STM32N6) TIM_ENTRY(9, TIM9_IRQn), #else TIM_ENTRY(9, TIM1_BRK_TIM9_IRQn), @@ -902,7 +914,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM10) - #if defined(STM32L1) + #if defined(STM32L1) || defined(STM32N6) TIM_ENTRY(10, TIM10_IRQn), #else TIM_ENTRY(10, TIM1_UP_TIM10_IRQn), @@ -910,7 +922,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM11) - #if defined(STM32L1) + #if defined(STM32L1) || defined(STM32N6) TIM_ENTRY(11, TIM11_IRQn), #else TIM_ENTRY(11, TIM1_TRG_COM_TIM11_IRQn), @@ -918,7 +930,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM12) - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) TIM_ENTRY(12, TIM12_IRQn), #else TIM_ENTRY(12, TIM8_BRK_TIM12_IRQn), @@ -926,21 +938,21 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM13) - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) TIM_ENTRY(13, TIM13_IRQn), #else TIM_ENTRY(13, TIM8_UP_TIM13_IRQn), #endif #endif - #if defined(STM32F0) || defined(STM32G0) || defined(STM32H5) + #if defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32N6) TIM_ENTRY(14, TIM14_IRQn), #elif defined(TIM14) TIM_ENTRY(14, TIM8_TRG_COM_TIM14_IRQn), #endif #if defined(TIM15) - #if defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) + #if defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) TIM_ENTRY(15, TIM15_IRQn), #else TIM_ENTRY(15, TIM1_BRK_TIM15_IRQn), @@ -950,7 +962,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #if defined(TIM16) #if defined(STM32G0B1xx) || defined(STM32G0C1xx) TIM_ENTRY(16, TIM16_FDCAN_IT0_IRQn), - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) || defined(STM32WL) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WL) TIM_ENTRY(16, TIM16_IRQn), #else TIM_ENTRY(16, TIM1_UP_TIM16_IRQn), @@ -960,7 +972,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #if defined(TIM17) #if defined(STM32G0B1xx) || defined(STM32G0C1xx) TIM_ENTRY(17, TIM17_FDCAN_IT1_IRQn), - #elif defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) || defined(STM32WL) + #elif defined(STM32F0) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WL) TIM_ENTRY(17, TIM17_IRQn), #else TIM_ENTRY(17, TIM1_TRG_COM_TIM17_IRQn), diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index e3f8dc1f906..9354af4a291 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -91,7 +91,7 @@ #define USART_CR3_IE_ALL (USART_CR3_IE_BASE | USART_CR3_WUFIE) #endif -#elif defined(STM32H7) +#elif defined(STM32H7) || defined(STM32N6) #define USART_CR1_IE_ALL (USART_CR1_IE_BASE | USART_CR1_RXFFIE | USART_CR1_TXFEIE | USART_CR1_EOBIE | USART_CR1_RTOIE | USART_CR1_CMIE) #define USART_CR2_IE_ALL (USART_CR2_IE_BASE) #define USART_CR3_IE_ALL (USART_CR3_IE_BASE | USART_CR3_RXFTIE | USART_CR3_TCBGTIE | USART_CR3_TXFTIE | USART_CR3_WUFIE) @@ -157,6 +157,18 @@ void uart_init0(void) { if (HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit) != HAL_OK) { MICROPY_BOARD_FATAL_ERROR("HAL_RCCEx_PeriphCLKConfig"); } + #elif defined(STM32N6) + // UART clock configuration, IC14 (max 100MHz). + LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_IC14); + LL_RCC_SetUSARTClockSource(LL_RCC_USART2_CLKSOURCE_IC14); + LL_RCC_SetUSARTClockSource(LL_RCC_USART3_CLKSOURCE_IC14); + LL_RCC_SetUARTClockSource(LL_RCC_UART4_CLKSOURCE_IC14); + LL_RCC_SetUARTClockSource(LL_RCC_UART5_CLKSOURCE_IC14); + LL_RCC_SetUSARTClockSource(LL_RCC_USART6_CLKSOURCE_IC14); + LL_RCC_SetUARTClockSource(LL_RCC_UART7_CLKSOURCE_IC14); + LL_RCC_SetUARTClockSource(LL_RCC_UART8_CLKSOURCE_IC14); + LL_RCC_SetUARTClockSource(LL_RCC_UART9_CLKSOURCE_IC14); + LL_RCC_SetUSARTClockSource(LL_RCC_USART10_CLKSOURCE_IC14); #endif } @@ -661,7 +673,7 @@ bool uart_init(machine_uart_obj_t *uart_obj, huart.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; #endif - #if defined(STM32H7) || defined(STM32WB) + #if defined(STM32H7) || defined(STM32N6) || defined(STM32WB) // Compute the smallest prescaler that will allow the given baudrate. uint32_t presc = UART_PRESCALER_DIV1; if (uart_obj->uart_id == PYB_LPUART_1) { @@ -976,6 +988,29 @@ uint32_t uart_get_source_freq(machine_uart_obj_t *self) { default: break; } + + #elif defined(STM32N6) + + static const uint16_t is_usart = 1 << 10 | 1 << 6 | 1 << 3 | 1 << 2 | 1 << 1; + static const uint32_t clksource[] = { + LL_RCC_USART1_CLKSOURCE, + LL_RCC_USART2_CLKSOURCE, + LL_RCC_USART3_CLKSOURCE, + LL_RCC_UART4_CLKSOURCE, + LL_RCC_UART5_CLKSOURCE, + LL_RCC_USART6_CLKSOURCE, + LL_RCC_UART7_CLKSOURCE, + LL_RCC_UART8_CLKSOURCE, + LL_RCC_UART9_CLKSOURCE, + LL_RCC_USART10_CLKSOURCE, + }; + + if (is_usart & (1 << self->uart_id)) { + uart_clk = LL_RCC_GetUSARTClockFreq(clksource[self->uart_id - 1]); + } else { + uart_clk = LL_RCC_GetUARTClockFreq(clksource[self->uart_id - 1]); + } + #else if (self->uart_id == 1 #if defined(USART6) @@ -1001,14 +1036,14 @@ uint32_t uart_get_baudrate(machine_uart_obj_t *self) { #if defined(LPUART1) if (self->uart_id == PYB_LPUART_1) { return LL_LPUART_GetBaudRate(self->uartx, uart_get_source_freq(self) - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) , self->uartx->PRESC #endif ); } #endif return LL_USART_GetBaudRate(self->uartx, uart_get_source_freq(self), - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) self->uartx->PRESC, #endif LL_USART_OVERSAMPLING_16); @@ -1018,7 +1053,7 @@ void uart_set_baudrate(machine_uart_obj_t *self, uint32_t baudrate) { #if defined(LPUART1) if (self->uart_id == PYB_LPUART_1) { LL_LPUART_SetBaudRate(self->uartx, uart_get_source_freq(self), - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) LL_LPUART_PRESCALER_DIV1, #endif baudrate); @@ -1026,7 +1061,7 @@ void uart_set_baudrate(machine_uart_obj_t *self, uint32_t baudrate) { } #endif LL_USART_SetBaudRate(self->uartx, uart_get_source_freq(self), - #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) LL_USART_PRESCALER_DIV1, #endif LL_USART_OVERSAMPLING_16, baudrate); @@ -1077,7 +1112,7 @@ int uart_rx_char(machine_uart_obj_t *self) { return data; } else { // no buffering - #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) int data = self->uartx->RDR & self->char_mask; self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set return data; @@ -1232,7 +1267,7 @@ void uart_irq_handler(mp_uint_t uart_id) { uint16_t next_head = (self->read_buf_head + 1) % self->read_buf_len; if (next_head != self->read_buf_tail) { // only read data if room in buf - #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32F0) || defined(STM32F7) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) int data = self->uartx->RDR; // clears UART_FLAG_RXNE #else self->mp_irq_flags = self->uartx->SR; // resample to get any new flags since next read of DR will clear SR diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index af9dd1d70ec..2d70dcb2619 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -67,7 +67,7 @@ #define MAX_ENDPOINT(dev_id) ((dev_id) == USB_PHY_FS_ID ? 3 : 5) #elif defined(STM32F7) #define MAX_ENDPOINT(dev_id) ((dev_id) == USB_PHY_FS_ID ? 5 : 8) -#elif defined(STM32H7) +#elif defined(STM32H7) || defined(STM32N6) #define MAX_ENDPOINT(dev_id) (8) #endif diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 829037ba935..7a9e63c9f32 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -51,6 +51,11 @@ PCD_HandleTypeDef pcd_hs_handle; #define USB_OTG_FS USB #endif +#if defined(STM32N6) +#define USB_OTG_HS USB1_OTG_HS +#define OTG_HS_IRQn USB1_OTG_HS_IRQn +#endif + /******************************************************************************* PCD BSP Routines *******************************************************************************/ @@ -191,6 +196,10 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { mp_hal_pin_config(pin_A12, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0); mp_hal_pin_config_speed(pin_A12, GPIO_SPEED_FREQ_VERY_HIGH); + #elif defined(STM32N6) + + // These MCUs have dedicated USB pins. + #else // Other MCUs have an alternate function for GPIO's to be in USB mode. @@ -220,6 +229,23 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { mp_hal_pin_config(MICROPY_HW_USB_OTG_ID_PIN, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_UP, otg_alt); #endif + #if defined(STM32N6) + + __HAL_RCC_USB1_OTG_HS_FORCE_RESET(); + __HAL_RCC_USB1_OTG_HS_PHY_FORCE_RESET(); + __HAL_RCC_USB1_OTG_HS_PHY_RELEASE_RESET(); + __HAL_RCC_USB1_OTG_HS_RELEASE_RESET(); + + LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_OTG1); + LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_OTGPHY1); + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_OTG1); + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_OTGPHY1); + + // Select 24MHz clock. + MODIFY_REG(USB1_HS_PHYC->USBPHYC_CR, USB_USBPHYC_CR_FSEL, 2 << USB_USBPHYC_CR_FSEL_Pos); + + #else + // Enable calling WFI and correct function of the embedded USB_FS_IN_HS phy __HAL_RCC_USB_OTG_HS_ULPI_CLK_SLEEP_DISABLE(); __HAL_RCC_USB_OTG_HS_CLK_SLEEP_ENABLE(); @@ -235,6 +261,8 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { __HAL_RCC_USB_OTG_HS_CLK_ENABLE(); + #endif + #else // !MICROPY_HW_USB_HS_IN_FS // Configure USB HS GPIOs @@ -283,7 +311,12 @@ void HAL_PCD_MspDeInit(PCD_HandleTypeDef *hpcd) { #if MICROPY_HW_USB_HS if (hpcd->Instance == USB_OTG_HS) { /* Disable USB FS Clocks */ + #if defined(STM32N6) + LL_AHB5_GRP1_DisableClock(LL_AHB5_GRP1_PERIPH_OTG1); + LL_AHB5_GRP1_DisableClock(LL_AHB5_GRP1_PERIPH_OTGPHY1); + #else __USB_OTG_HS_CLK_DISABLE(); + #endif } #endif @@ -517,7 +550,7 @@ USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev, int high_speed, const #if MICROPY_HW_USB_HS_IN_FS - #if defined(STM32F723xx) || defined(STM32F733xx) + #if defined(STM32F723xx) || defined(STM32F733xx) || defined(STM32N6) pcd_hs_handle.Init.phy_itface = USB_OTG_HS_EMBEDDED_PHY; #else pcd_hs_handle.Init.phy_itface = PCD_PHY_EMBEDDED; diff --git a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h index 2c90ce165e2..34f04125341 100644 --- a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h +++ b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h @@ -11,7 +11,7 @@ // Work out if we should support USB high-speed device mode #if MICROPY_HW_USB_HS \ - && (!MICROPY_HW_USB_HS_IN_FS || defined(STM32F723xx) || defined(STM32F733xx)) + && (!MICROPY_HW_USB_HS_IN_FS || defined(STM32F723xx) || defined(STM32F733xx) || defined(STM32N6)) #define USBD_SUPPORT_HS_MODE (1) #else #define USBD_SUPPORT_HS_MODE (0) @@ -31,7 +31,11 @@ #else #define CDC_DATA_MAX_PACKET_SIZE CDC_DATA_FS_MAX_PACKET_SIZE #endif +#if defined(STM32N6) +#define MSC_MEDIA_PACKET (4096) // must be at least the SPI flash erase size +#else #define MSC_MEDIA_PACKET (2048) // was 8192; how low can it go whilst still working? +#endif #define HID_DATA_FS_MAX_PACKET_SIZE (64) // endpoint IN & OUT packet size // Maximum number of LUN that can be exposed on the MSC interface diff --git a/ports/stm32/vfs_rom_ioctl.c b/ports/stm32/vfs_rom_ioctl.c index 7592aa22d62..5dbc855861d 100644 --- a/ports/stm32/vfs_rom_ioctl.c +++ b/ports/stm32/vfs_rom_ioctl.c @@ -33,6 +33,7 @@ #include "flash.h" #include "qspi.h" #include "storage.h" +#include "xspi.h" #if MICROPY_VFS_ROM_IOCTL @@ -142,6 +143,18 @@ mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(4); } #endif + + #if MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI + if (xspi_is_valid_addr(&xspi_flash2, dest)) { + dest -= xspi_get_xip_base(&xspi_flash2); + dest_max -= xspi_get_xip_base(&xspi_flash2); + int ret = spi_bdev_eraseblocks_raw(MICROPY_HW_ROMFS_XSPI_SPIBDEV_OBJ, dest / MP_SPIFLASH_ERASE_BLOCK_SIZE, dest_max - dest + MP_SPIFLASH_ERASE_BLOCK_SIZE - 1); + if (ret < 0) { + return MP_OBJ_NEW_SMALL_INT(ret); + } + return MP_OBJ_NEW_SMALL_INT(4); + } + #endif } if (cmd == MP_VFS_ROM_IOCTL_WRITE) { @@ -170,6 +183,14 @@ mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(ret); } #endif + + #if MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI + if (xspi_is_valid_addr(&xspi_flash2, dest)) { + dest -= xspi_get_xip_base(&xspi_flash2); + int ret = spi_bdev_writeblocks_raw(MICROPY_HW_ROMFS_XSPI_SPIBDEV_OBJ, bufinfo.buf, 0, dest, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(ret); + } + #endif } return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); diff --git a/ports/stm32/xspi.c b/ports/stm32/xspi.c new file mode 100644 index 00000000000..b113110c05e --- /dev/null +++ b/ports/stm32/xspi.c @@ -0,0 +1,599 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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. + */ + +// This XSPI driver is currently configured to run in 1-line (SPI) mode. +// It uses the mp_qspi_proto_t QSPI protocol and translates quad-commands +// into 1-line commands. + +#include +#include "py/mperrno.h" +#include "py/mphal.h" +#include "xspi.h" + +#if defined(MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2) + +#ifndef MICROPY_HW_XSPI_PRESCALER +#define MICROPY_HW_XSPI_PRESCALER (4) // F_CLK = F_AHB/4 +#endif + +#ifndef MICROPY_HW_XSPI_CS_HIGH_CYCLES +#define MICROPY_HW_XSPI_CS_HIGH_CYCLES (2) // nCS stays high for 4 cycles +#endif + +// Currently hard-coded to use XSPI2 instance. +#define XSPIx (XSPI2) + +// For XSPI2, PN0 through PN12. +#define XSPI2_AF (9) + +typedef struct _xspi_flash_t { + XSPI_TypeDef *xspi; + uintptr_t xip_base; +} xspi_flash_t; + +const xspi_flash_t xspi_flash1 = { + .xspi = XSPI1, + .xip_base = 0x90000000, +}; + +const xspi_flash_t xspi_flash2 = { + .xspi = XSPI2, + .xip_base = 0x70000000, +}; + +static bool xspi_dtr_enabled = false; + +#ifdef pyb_pin_FLASH_RESET +// Can't rely on SysTick being available, so use a busy loop for delays. +// The timing here is approximate and assumes a CPU frequency of 800MHz. +static void xspi_delay_us(unsigned int us) { + while (us--) { + for (unsigned int i = 0; i < 800; ++i) { + __NOP(); + } + } +} +#endif + +static inline void mp_hal_pin_config_alt_speed(mp_hal_pin_obj_t pin, uint32_t pull, uint32_t alt, uint32_t speed) { + mp_hal_pin_config(pin, MP_HAL_PIN_MODE_ALT, pull, alt); + mp_hal_pin_config_speed(pin, speed); +} + +static int xspi_read_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, uint8_t *dest); +static int xspi_write_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src); +static int xspi_read_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, uint32_t num_dummy, size_t len, uint8_t *dest); +static int xspi_write_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src); +static void xspi_memory_map_111(void); +static void xspi_memory_map_888(void); +static void xspi_memory_map_exit(void); + +void xspi_init(void) { + // Configure XSPI pins. + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_CS, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_SCK, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_DQS, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO0, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO1, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO2, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO3, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO4, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO5, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO6, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO7, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH); + + LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPIM); + LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI1); + LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI2); + LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI3); + + LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPIM); + LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI1); + LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI2); + LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI3); + + LL_RCC_SetXSPIClockSource(LL_RCC_XSPI1_CLKSOURCE_HCLK); + LL_RCC_SetXSPIClockSource(LL_RCC_XSPI2_CLKSOURCE_HCLK); + + LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPIM); + LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPI1); + LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPI2); + + // Configure XSPIM in direct mode. + XSPI1->CR &= ~XSPI_CR_EN; + XSPI2->CR &= ~XSPI_CR_EN; + XSPIM->CR = 0; + + // Configure the XSPIx peripheral. + + XSPIx->CR = + 3 << XSPI_CR_FTHRES_Pos // 4 byte must be available to read/write + | 0 << XSPI_CR_MSEL_Pos // FLASH 0 selected + | 0 << XSPI_CR_CSSEL_Pos // use NCS1 as chip select + | 0 << XSPI_CR_DMM_Pos // dual-memory mode disabled + | 1 << XSPI_CR_TCEN_Pos // time-out counter enabled + | 0 << XSPI_CR_DMAEN_Pos // DMA disabled + | 0 << XSPI_CR_ABORT_Pos // no abort request + | 0 << XSPI_CR_EN_Pos // disabled + ; + + XSPIx->DCR1 = + 1 << XSPI_DCR1_MTYP_Pos // Macronix mode + | (MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) << XSPI_DCR1_DEVSIZE_Pos + | (MICROPY_HW_XSPI_CS_HIGH_CYCLES - 1) << XSPI_DCR1_CSHT_Pos + | 0 << XSPI_DCR1_FRCK_Pos // CLK is not free running + | 0 << XSPI_DCR1_CKMODE_Pos // CLK idles at low state + ; + + XSPIx->DCR2 = + 0 << XSPI_DCR2_WRAPSIZE_Pos // separate wrap reads are not supported by the memory + | (MICROPY_HW_XSPI_PRESCALER - 1) << XSPI_DCR2_PRESCALER_Pos + ; + + XSPIx->DCR3 = + 0 + // 10 << XSPI_DCR3_CSBOUND_Pos // transaction boundary at 1024 + ; + + XSPIx->DCR4 = + 0 << XSPI_DCR4_REFRESH_Pos // refresh disabled (it's non-volatile memory) + ; + + XSPIx->TCR = 0; + + // Enable the XSPI peripheral. + XSPIx->CR |= XSPI_CR_EN; + + // XSPIM init + XSPI1->CR &= ~(1 << XSPI_CR_EN_Pos); + XSPI2->CR &= ~(1 << XSPI_CR_EN_Pos); + XSPIM->CR = 0; // can also be (1 << 4) to pass through CS signal + XSPIx->CR |= 1 << XSPI_CR_EN_Pos; + + #ifdef pyb_pin_FLASH_RESET + // Reset SPI flash to make sure it's in a known state (SPI mode). + mp_hal_pin_output(pyb_pin_FLASH_RESET); + mp_hal_pin_low(pyb_pin_FLASH_RESET); + xspi_delay_us(1000); + mp_hal_pin_high(pyb_pin_FLASH_RESET); + xspi_delay_us(10000); + #endif + + // Enable memory-mapped mode. + // Can select either SPI or DTR mode. + if (1) { + xspi_switch_to_dtr(); + xspi_memory_map_888(); + } else { + xspi_memory_map_111(); + } +} + +uint32_t xspi_get_xip_base(const xspi_flash_t *self) { + return self->xip_base; +} + +bool xspi_is_valid_addr(const xspi_flash_t *self, uint32_t addr) { + return self->xip_base <= addr && addr < self->xip_base + 256 * 1024 * 1024; +} + +static int xspi_read_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, uint8_t *dest) { + uint32_t admode = addr_enabled ? 1 : 0; + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 1 << XSPI_CR_FMODE_Pos; // indirect read mode + XSPIx->CCR = + 1 << XSPI_CCR_DMODE_Pos // data on 1 line + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size + | admode << XSPI_CCR_ADMODE_Pos // address on 1 line, or disabled + | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line + ; + XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles + XSPIx->DLR = len - 1; // number of bytes to read + XSPIx->IR = cmd; // read opcode (triggers the start of the transaction if address disabled) + if (addr_enabled) { + XSPIx->AR = addr; // triggers the start of the transaction + } + + #if 0 // untested code + // Read in the data 4 bytes at a time if dest is aligned. + if (((uintptr_t)dest & 3) == 0) { + while (len >= 4) { + while (!(XSPIx->SR & XSPI_SR_FTF)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + *(uint32_t *)dest = XSPIx->DR; + dest += 4; + len -= 4; + } + } + #endif + + // Read in data 1 byte at a time. + while (len--) { + while (!((XSPIx->SR >> XSPI_SR_FLEVEL_Pos) & 0x3f)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + *dest++ = *(volatile uint8_t *)&XSPIx->DR; + } + + XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag + + return 0; +} + +static int xspi_write_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src) { + uint32_t dmode = len == 0 ? 0 : 1; + uint32_t admode = addr_enabled ? 1 : 0; + + // Configure and start the transfer. + // Transfer starts with IR write if no address or data, with AR write if no data, + // otherwise with DR write. + + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode + XSPIx->CCR = + dmode << XSPI_CCR_DMODE_Pos // data on 1 line, or disabled + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size + | admode << XSPI_CCR_ADMODE_Pos // address on 1 line, or disabled + | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line + ; + XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles + if (len != 0) { + XSPIx->DLR = len - 1; + } + XSPIx->IR = cmd; // write opcode + if (addr_enabled) { + XSPIx->AR = addr; // address + } + + // Write out the data one byte at a time + while (len--) { + while (!(XSPIx->SR & XSPI_SR_FTF)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + *(volatile uint8_t *)&XSPIx->DR = *src++; + } + + // Wait for write to finish + while (!(XSPIx->SR & XSPI_SR_TCF)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + + XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag + + // Wait for peripheral to return to idle. + while (XSPIx->SR & XSPI_SR_BUSY) { + } + + return 0; +} + +static int xspi_read_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, uint32_t num_dummy, size_t len, uint8_t *dest) { + uint32_t admode = addr_enabled ? 4 : 0; + + // Configure and start the transfer. + // Transfer starts with IR write if no address, otherwise with AR write. + + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 1 << XSPI_CR_FMODE_Pos; // indirect read mode + XSPIx->CCR = + 1 << XSPI_CCR_DQSE_Pos // DQS enabled + | 1 << XSPI_CCR_DDTR_Pos // data DTR enabled + | 4 << XSPI_CCR_DMODE_Pos // data on 8 lines + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size + | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled + | admode << XSPI_CCR_ADMODE_Pos // address on 8 lines, or disabled + | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction + | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled + | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines + ; + XSPIx->TCR = num_dummy << XSPI_TCR_DCYC_Pos; // N dummy cycles + XSPIx->DLR = len - 1; + XSPIx->IR = cmd; // read opcode + if (addr_enabled) { + XSPIx->AR = addr; // address + } + + // Read in data 1 byte at a time. + while (len--) { + while (!((XSPIx->SR >> XSPI_SR_FLEVEL_Pos) & 0x3f)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + *dest++ = *(volatile uint8_t *)&XSPIx->DR; + } + + XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag + + return 0; +} + +static int xspi_write_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src) { + uint32_t dmode = len == 0 ? 0 : 4; + uint32_t admode = addr_enabled ? 4 : 0; + + // Configure and start the transfer. + // Transfer starts with IR write if no address or data, with AR write if no data, + // otherwise with DR write. + + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode + XSPIx->CCR = + 1 << XSPI_CCR_DDTR_Pos // data DTR enabled + | dmode << XSPI_CCR_DMODE_Pos // data on 8 lines, or disabled + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size + | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled + | admode << XSPI_CCR_ADMODE_Pos // address on 8 lines, or disabled + | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction + | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled + | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines + ; + XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles + if (len != 0) { + XSPIx->DLR = len - 1; + } + XSPIx->IR = cmd; // write opcode + if (addr_enabled) { + XSPIx->AR = addr; // address + } + + // Write out the data one byte at a time + while (len--) { + while (!(XSPIx->SR & XSPI_SR_FTF)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + *(volatile uint8_t *)&XSPIx->DR = *src++; + } + + // Wait for write to finish + while (!(XSPIx->SR & XSPI_SR_TCF)) { + if (XSPIx->SR & XSPI_SR_TEF) { + return -MP_EIO; + } + } + + XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag + + // Wait for peripheral to return to idle. + while (XSPIx->SR & XSPI_SR_BUSY) { + } + + return 0; +} + +static void xspi_memory_map_111(void) { + XSPIx->CCR = + 1 << XSPI_CCR_DMODE_Pos // data on 1 line + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address + | 1 << XSPI_CCR_ADMODE_Pos // address on 1 line + | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line + ; + + XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // no dummy cycles + XSPIx->IR = 0x13; // READ4B + XSPIx->LPTR = 1024; // timeout period in number of CLK cycles + + // Enable the XSPI peripheral in memory-mapped mode. + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 3 << XSPI_CR_FMODE_Pos; +} + +static void xspi_memory_map_888(void) { + XSPIx->CCR = + 1 << XSPI_CCR_DQSE_Pos // DQS enabled + | 1 << XSPI_CCR_DDTR_Pos // data DTR enabled + | 4 << XSPI_CCR_DMODE_Pos // data on 8 lines + | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address + | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled + | 4 << XSPI_CCR_ADMODE_Pos // address on 8 lines + | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction + | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled + | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines + ; + + XSPIx->TCR = 20 << XSPI_TCR_DCYC_Pos; // 20 dummy cycles for reading (minimum, flash may insert more by holding DQS low) + XSPIx->IR = 0xee11; // octal DTR read mode (8DTRD) + XSPIx->LPTR = 1024; // timeout period in number of CLK cycles + + // Enable the XSPI peripheral in memory-mapped mode. + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 3 << XSPI_CR_FMODE_Pos; +} + +static void xspi_memory_map_exit(void) { + // Abort any ongoing transfer if peripheral is busy. + if (XSPIx->SR & XSPI_SR_BUSY) { + XSPIx->CR |= XSPI_CR_ABORT; + while (!(XSPIx->SR & XSPI_SR_TCF)) { + } + XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag + while (XSPIx->SR & XSPI_SR_BUSY) { + } + } + XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode +} + +void xspi_switch_to_dtr(void) { + uint8_t buf[4]; + + // WREN. + xspi_write_111_ext(0x06, false, 0, 0, NULL); + + // Wait WEL=1, with small timeout. + for (unsigned int i = 0; i < 100; ++i) { + xspi_read_111_ext(0x05, false, 0, 1, buf); + if (buf[0] & 2) { + break; + } + } + + // Switch to DOPI DTR mode. + buf[0] = 2; + xspi_write_111_ext(0x72, true, 0x00000000, 1, buf); + + xspi_dtr_enabled = true; +} + +void xspi_switch_to_spi(void) { + uint8_t buf[4]; + + // WREN. + xspi_write_888_dtr_ext(0x06f9, false, 0, 0, NULL); + + // Wait WEL=1, with small timeout. + for (unsigned int i = 0; i < 100; ++i) { + xspi_read_111_ext(0x05, false, 0, 1, buf); + if (buf[0] & 2) { + break; + } + } + + // Switch to SPI mode. + buf[0] = 0; + buf[1] = 0; + xspi_write_888_dtr_ext(0x728d, true, 0x00000000, 2, buf); + + xspi_dtr_enabled = false; +} + +static int xspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { + xspi_flash_t *self = self_in; + switch (cmd) { + case MP_QSPI_IOCTL_INIT: + // XSPI must be manually initialise by calling `xspi_init()` at boot. + // Here, just determine if it's in SPI or DTR mode. + xspi_dtr_enabled = XSPIx->IR == 0xee11; + break; + case MP_QSPI_IOCTL_BUS_ACQUIRE: + xspi_memory_map_exit(); + break; + case MP_QSPI_IOCTL_BUS_RELEASE: + if (xspi_dtr_enabled) { + xspi_memory_map_888(); + } else { + xspi_memory_map_111(); + } + break; + case MP_QSPI_IOCTL_MEMORY_MODIFIED: { + uintptr_t *addr_len = (uintptr_t *)arg; + volatile void *addr = (volatile void *)(self->xip_base + addr_len[0]); + size_t len = addr_len[1]; + SCB_InvalidateICache_by_Addr(addr, len); + SCB_InvalidateDCache_by_Addr(addr, len); + break; + } + } + return 0; // success +} + +// These commands may be passed to this function. +#define CMD_WREN (0x06) +#define CMD_RSTEN (0x66) +#define CMD_RESET (0x99) +#define CMD_SLEEP (0xb9) +#define CMD_AWAKE (0xab) +static int xspi_write_cmd_data(void *self_in, uint8_t cmd, size_t len, uint32_t data) { + if (xspi_dtr_enabled) { + uint16_t cmd16 = 0; + if (cmd == CMD_WREN) { + cmd16 = 0x06f9; + } else if (cmd == CMD_SLEEP) { + cmd16 = 0xb946; + } else if (cmd == CMD_AWAKE) { + cmd16 = 0xab54; + } + return xspi_write_888_dtr_ext(cmd16, false, 0, len, (const uint8_t *)&data); + } + return xspi_write_111_ext(cmd, false, 0, len, (const uint8_t *)&data); +} + +// These commands may be passed to this function. +#define CMD_WRITE (0x02) +#define CMD_WRITE_32 (0x12) +#define CMD_SEC_ERASE (0x20) +#define CMD_SEC_ERASE_32 (0x21) +static int xspi_write_cmd_addr_data(void *self_in, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src) { + // Convert 24-bit address commands to 32-bit address commands. + if (cmd == CMD_WRITE) { + cmd = CMD_WRITE_32; + } else if (cmd == CMD_SEC_ERASE) { + cmd = CMD_SEC_ERASE_32; + } + if (xspi_dtr_enabled) { + uint16_t cmd16 = 0; + if (cmd == CMD_WRITE_32) { + cmd16 = 0x12ed; + } else if (cmd == CMD_SEC_ERASE_32) { + cmd16 = 0x21de; + } + return xspi_write_888_dtr_ext(cmd16, true, addr, len, src); + } + return xspi_write_111_ext(cmd, true, addr, len, src); +} + +// These commands may be passed to this function. +#define CMD_RDSR (0x05) +#define CMD_RD_DEVID (0x9f) +static int xspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *dest) { + (void)self_in; + if (xspi_dtr_enabled) { + uint16_t cmd16 = 0; + uint32_t num_dummy = 0; + if (cmd == CMD_RDSR) { + cmd16 = 0x05fa; + num_dummy = 4; + len = 2; + } else if (cmd == CMD_RD_DEVID) { + // TODO this doesn't really work, because result is in STR format. + cmd16 = 0x9f60; + num_dummy = 4; + } + return xspi_read_888_dtr_ext(cmd16, true, 0, num_dummy, len, (uint8_t *)dest); + } + return xspi_read_111_ext(cmd, false, 0, len, (uint8_t *)dest); +} + +static int xspi_direct_read(void *self_in, uint32_t addr, size_t len, uint8_t *dest) { + xspi_flash_t *self = self_in; + memcpy(dest, (const void *)(self->xip_base + addr), len); + return 0; +} + +const mp_qspi_proto_t xspi_proto = { + .ioctl = xspi_ioctl, + .write_cmd_data = xspi_write_cmd_data, + .write_cmd_addr_data = xspi_write_cmd_addr_data, + .read_cmd = xspi_read_cmd, + .read_cmd_qaddr_qdata = NULL, // unused because .direct_read is set below, and caching is disabled + .direct_read = xspi_direct_read, +}; + +#endif // defined(MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2) diff --git a/ports/stm32/xspi.h b/ports/stm32/xspi.h new file mode 100644 index 00000000000..cd216629667 --- /dev/null +++ b/ports/stm32/xspi.h @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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. + */ +#ifndef MICROPY_INCLUDED_STM32_XSPI_H +#define MICROPY_INCLUDED_STM32_XSPI_H + +#include "drivers/bus/qspi.h" + +typedef struct _xspi_flash_t xspi_flash_t; + +extern const mp_qspi_proto_t xspi_proto; +extern const xspi_flash_t xspi_flash1; +extern const xspi_flash_t xspi_flash2; + +void xspi_init(void); +uint32_t xspi_get_xip_base(const xspi_flash_t *self); +bool xspi_is_valid_addr(const xspi_flash_t *self, uint32_t addr); +void xspi_switch_to_spi(void); +void xspi_switch_to_dtr(void); + +#endif // MICROPY_INCLUDED_STM32_XSPI_H From eaed2518fab968362d15745c4f1f3d39bd60ad38 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 12 Sep 2024 09:34:01 +0200 Subject: [PATCH 0893/2098] stm32/main: Disable D-cache when debugging N6. See ST Errata ES0620 - Rev 0.2 section 2.1.2. Signed-off-by: iabdalkader --- ports/stm32/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index aea1953fd65..137e1328174 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -374,7 +374,11 @@ void stm32_main(uint32_t reset_mode) { #endif SCB_EnableICache(); + #if defined(STM32N6) && !defined(NDEBUG) + // Don't enable D-cache on N6 when debugging; see ST Errata ES0620 - Rev 0.2 section 2.1.2. + #else SCB_EnableDCache(); + #endif #elif defined(STM32H5) From 959e910366d0109f959a1ecbb91d5fbd92b9be2f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2025 13:31:40 +1000 Subject: [PATCH 0894/2098] stm32/lwip_inc: Increase lwIP memory on N6. Signed-off-by: Damien George --- ports/stm32/lwip_inc/lwipopts.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index 9e2402c8dc5..ad1143845f7 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -10,6 +10,15 @@ #define LWIP_RAND() rng_get() +// Increase memory for lwIP to get better performance. +#if defined(STM32N6) +#define MEM_SIZE (16 * 1024) +#define TCP_MSS (1460) +#define TCP_WND (8 * TCP_MSS) +#define TCP_SND_BUF (8 * TCP_MSS) +#define MEMP_NUM_TCP_SEG (32) +#endif + // Include common lwIP configuration. #include "extmod/lwip-include/lwipopts_common.h" From 96b8f3aebcc7abc67f15c912286d0cba64589de1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Jun 2024 17:46:41 +1000 Subject: [PATCH 0895/2098] stm32/boards: Add board support files for N6. Signed-off-by: Damien George --- ports/stm32/boards/stm32n657_af.csv | 42 ++++ ports/stm32/boards/stm32n657x0.ld | 34 +++ ports/stm32/boards/stm32n6xx_hal_conf_base.h | 215 +++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 ports/stm32/boards/stm32n657_af.csv create mode 100644 ports/stm32/boards/stm32n657x0.ld create mode 100644 ports/stm32/boards/stm32n6xx_hal_conf_base.h diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv new file mode 100644 index 00000000000..35e305a3767 --- /dev/null +++ b/ports/stm32/boards/stm32n657_af.csv @@ -0,0 +1,42 @@ +Port ,Pin ,AF0 ,AF1 ,AF2 ,AF3 ,AF4 ,AF5 ,AF6 ,AF7 ,AF8 ,AF9 ,AF10 ,AF11 ,AF12 ,AF13 ,AF14 ,AF15 ,ADC + , ,SYS ,LPTIM1/TIM1/2/16/17,LPTIM3/PDM_SAI1/TIM3/4/5/12/15,I3C1/LPTIM2/3/LPUART1/OCTOSPI/TIM1/8,CEC/DCMI/I2C1/2/3/4/LPTIM1/2/SPI1/I2S1/TIM15/USART1,CEC/I3C1/LPTIM1/SPI1/I2S1/SPI2/I2S2/SPI3/I2S3/SPI4/5/6,I2C4/OCTOSPI/SAI1/SPI3/I2S3/SPI4/UART4/12/USART10/USB_PD,SDMMC1/SPI2/I2S2/SPI3/I2S3/SPI6/UART7/8/12/USART1/2/3/6/10/11,LPUART1/SAI2/SDMMC1/SPI6/UART4/5/8,FDCAN1/2/FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/OCTOSPI/SDMMC2/TIM13/14,CRS/FMC[NAND16]/OCTOSPI/SAI2/SDMMC2/TIM8/USB_,ETH[MII/RMII]/FMC[NAND16]/OCTOSPI/SDMMC2/UART7/9/USB_PD,FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/FMC[SDRAM_16bit]/SDMMC1,DCMI/FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/LPTIM5,LPTIM3/4/5/6/TIM2/UART5,SYS ,ADC +PortA,PA0 , , , , , , , , , , , ,SDMMC2_CMD , , , , ,ADC12_INP0/ADC12_INN1 +PortA,PA3 , , , , , ,SPI5_NSS , , , , , , , , , , , +PortA,PA5 , , , , , , , , , , , , , , , , ,ADC2_INP18 +PortA,PA8 , , , , , , , , , , , , , , , , ,ADC12_INP5 +PortA,PA9 , , , , , , , , , , , , , , , , ,ADC12_INP10 +PortA,PA10, , , , , , , , , , , , , , , , ,ADC12_INP11/ADC12_INN10 +PortA,PA11, , , , , , , , ,UART4_RX , , , , , , , ,ADC12_INP12/ADC12_INN11 +PortA,PA12, , , , , , , , ,UART4_TX , , , , , , , ,ADC12_INP13/ADC12_INN12 +PortB,PB4 , , , , , , , , , , , ,SDMMC2_D3 , , , , , +PortB,PB8 , , , , , , , , , , , ,SDMMC2_D0 , , , , , +PortB,PB9 , , , , , , , , , , , ,SDMMC2_D2 , , , , , +PortB,PB10, ,TIM2_CH3 , , , , , ,USART3_TX , , , , , , , , , +PortB,PB11, ,TIM2_CH4 , , , , , ,USART3_RX , , , , , , , , , +PortC,PC8 , , , , , , , , , , ,SDMMC1_D0 , , , , , , +PortC,PC9 , , , , , , , , , , ,SDMMC1_D1 , , , , , , +PortC,PC10, , , , , , , , , , ,SDMMC1_D2 , , , , , , +PortC,PC11, , , , , , , , , , ,SDMMC1_D3 , , , , , , +PortC,PC12, , , , , , , , , , ,SDMMC1_CK , , , , , , +PortD,PD2 , , , , , , , , , , , ,SDMMC2_CK , , , , , +PortD,PD5 , , , , , , , ,USART2_TX , , , , , , , , , +PortD,PD6 , , , , ,TIM15_CH2 , , , , , , , , , , , , +PortD,PD8 , , , , , , , ,USART3_TX , , , , , , , , , +PortD,PD9 , , , , , , , ,USART3_RX , , , , , , , , , +PortD,PD13, , ,TIM4_CH2 , , , , , , , , , , , , , , +PortE,PE5 , , , , , , , ,USART1_TX , , , , , , , , , +PortE,PE6 , , , , , , , ,USART1_RX , , , , , , , , , +PortE,PE7 , , , , , , , , ,UART7_RX , , , , , , , , +PortE,PE8 , , , , , , , , ,UART7_TX , , , , , , , , +PortE,PE15, , , , , ,SPI5_SCK , , , , , , , , , , , +PortF,PF3 , , , , , , , ,USART2_RTS , , , , , , , , ,ADC1_INP16 +PortF,PF6 , , , , , , , ,USART2_RX , , , , , , , , , +PortG,PG0 , , ,TIM12_CH1 , , , , , , , , , , , , , , +PortG,PG1 , , , , , ,SPI5_MISO , , , , , , , , , , , +PortG,PG2 , , , , , ,SPI5_MOSI , , , , , , , , , , , +PortG,PG5 , , , , , , , ,USART2_CTS , , , , , , , , , +PortG,PG8 , , , , , , , , , , , ,SDMMC2_D1 , , , , , +PortG,PG12, ,TIM17_CH1 , , , , , , , , , , , , , , , +PortG,PG13, , ,TIM4_CH1 , , , , , , , , , , , , , , +PortG,PG15, , , , , , , , , , , , , , , , ,ADC12_INP7/ADC12_INN3 +PortC,PH2 , , , , , , , , , , ,SDMMC1_CMD , , , , , , diff --git a/ports/stm32/boards/stm32n657x0.ld b/ports/stm32/boards/stm32n657x0.ld new file mode 100644 index 00000000000..242d113b309 --- /dev/null +++ b/ports/stm32/boards/stm32n657x0.ld @@ -0,0 +1,34 @@ +/* + GNU linker script for STM32N657x0 + + Note: upper 512k of SRAM2 is copied from external flash upon reset. +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLEXRAM_S (xrw) : ORIGIN = 0x34000000, LENGTH = 80K + SRAM2_S_RAM (xrw) : ORIGIN = 0x34100000, LENGTH = 512K /* only use first half, second half may contain firmware */ + SRAM2_S_FSBL (xrw) : ORIGIN = 0x34180400, LENGTH = 511K /* firmware loaded from SPI flash upon reset */ + EXT_FLASH (rx) : ORIGIN = 0x70080000, LENGTH = 1536K +} + +REGION_ALIAS("IRAM", FLEXRAM_S); +REGION_ALIAS("RAM", SRAM2_S_RAM); +REGION_ALIAS("FLASH", SRAM2_S_FSBL); +REGION_ALIAS("FLASH_APP", EXT_FLASH); + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM extents for the garbage collector */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; diff --git a/ports/stm32/boards/stm32n6xx_hal_conf_base.h b/ports/stm32/boards/stm32n6xx_hal_conf_base.h new file mode 100644 index 00000000000..641a003d8bf --- /dev/null +++ b/ports/stm32/boards/stm32n6xx_hal_conf_base.h @@ -0,0 +1,215 @@ +/* + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32N6XX_HAL_CONF_BASE_H +#define MICROPY_INCLUDED_STM32N6XX_HAL_CONF_BASE_H + +// Enable various HAL modules +#define HAL_MODULE_ENABLED +#define HAL_ADC_MODULE_ENABLED +#define HAL_BSEC_MODULE_ENABLED +#define HAL_CACHEAXI_MODULE_ENABLED +#define HAL_CORTEX_MODULE_ENABLED +#define HAL_CRC_MODULE_ENABLED +#define HAL_CRYP_MODULE_ENABLED +#define HAL_CSI_MODULE_ENABLED +#define HAL_DCMI_MODULE_ENABLED +#define HAL_DCMIPP_MODULE_ENABLED +#define HAL_DMA_MODULE_ENABLED +#define HAL_DMA2D_MODULE_ENABLED +#define HAL_DTS_MODULE_ENABLED +#define HAL_ETH_MODULE_ENABLED +#define HAL_EXTI_MODULE_ENABLED +#define HAL_FDCAN_MODULE_ENABLED +#define HAL_GFXMMU_MODULE_ENABLED +#define HAL_GFXTIM_MODULE_ENABLED +#define HAL_GPIO_MODULE_ENABLED +#define HAL_GPU2D_MODULE_ENABLED +#define HAL_HASH_MODULE_ENABLED +#define HAL_HCD_MODULE_ENABLED +#define HAL_I2C_MODULE_ENABLED +#define HAL_I3C_MODULE_ENABLED +#define HAL_ICACHE_MODULE_ENABLED +#define HAL_IRDA_MODULE_ENABLED +#define HAL_IWDG_MODULE_ENABLED +#define HAL_JPEG_MODULE_ENABLED +#define HAL_LPTIM_MODULE_ENABLED +#define HAL_LTDC_MODULE_ENABLED +#define HAL_MCE_MODULE_ENABLED +#define HAL_MDF_MODULE_ENABLED +#define HAL_MDIOS_MODULE_ENABLED +#define HAL_MMC_MODULE_ENABLED +#define HAL_NAND_MODULE_ENABLED +#define HAL_NOR_MODULE_ENABLED +#define HAL_PCD_MODULE_ENABLED +#define HAL_PKA_MODULE_ENABLED +#define HAL_PSSI_MODULE_ENABLED +#define HAL_PWR_MODULE_ENABLED +#define HAL_RAMCFG_MODULE_ENABLED +#define HAL_RCC_MODULE_ENABLED +#define HAL_RIF_MODULE_ENABLED +#define HAL_RNG_MODULE_ENABLED +#define HAL_RTC_MODULE_ENABLED +#define HAL_SAI_MODULE_ENABLED +#define HAL_SD_MODULE_ENABLED +#define HAL_SDRAM_MODULE_ENABLED +#define HAL_SMARTCARD_MODULE_ENABLED +#define HAL_SMBUS_MODULE_ENABLED +#define HAL_SPDIFRX_MODULE_ENABLED +#define HAL_SPI_MODULE_ENABLED +#define HAL_SRAM_MODULE_ENABLED +#define HAL_TIM_MODULE_ENABLED +#define HAL_UART_MODULE_ENABLED +#define HAL_USART_MODULE_ENABLED +#define HAL_WWDG_MODULE_ENABLED +#define HAL_XSPI_MODULE_ENABLED + +// Oscillator values in Hz +#define HSI_VALUE (64000000UL) +#define LSI_VALUE (32000UL) +#define MSI_VALUE (4000000UL) + +// SysTick has the highest priority +#define TICK_INT_PRIORITY (0x00) + +// Miscellaneous HAL settings +#define VDD_VALUE 3300UL +#define USE_RTOS 0 +#define USE_SD_TRANSCEIVER 0 +#define USE_SPI_CRC 1 + +// Disable dynamic callback registration +#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ +#define USE_HAL_CACHEAXI_REGISTER_CALLBACKS 0U /* CACHEAXI register callback disabled */ +#define USE_HAL_CRYP_REGISTER_CALLBACKS 0U /* CRYP register callback disabled */ +#define USE_HAL_DCMI_REGISTER_CALLBACKS 0U /* DCMI register callback disabled */ +#define USE_HAL_DCMIPP_REGISTER_CALLBACKS 0U /* DCMIPP register callback disabled */ +#define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U /* DMA2D register callback disabled */ +#define USE_HAL_DTS_REGISTER_CALLBACKS 0U /* DTS register callback disabled */ +#define USE_HAL_ETH_REGISTER_CALLBACKS 0U /* ETH register callback disabled */ +#define USE_HAL_FDCAN_REGISTER_CALLBACKS 0U /* FDCAN register callback disabled */ +#define USE_HAL_GFXMMU_REGISTER_CALLBACKS 0U /* GFXMMU register callback disabled */ +#define USE_HAL_GFXTIM_REGISTER_CALLBACKS 0U /* GFXTIM register callback disabled */ +#define USE_HAL_HASH_REGISTER_CALLBACKS 0U /* HASH register callback disabled */ +#define USE_HAL_HCD_REGISTER_CALLBACKS 0U /* HCD register callback disabled */ +#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ +#define USE_HAL_I3C_REGISTER_CALLBACKS 0U /* I3C register callback disabled */ +#define USE_HAL_IWDG_REGISTER_CALLBACKS 0U /* IWDG register callback disabled */ +#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ +#define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U /* LPTIM register callback disabled */ +#define USE_HAL_LTDC_REGISTER_CALLBACKS 0U /* LTDC register callback disabled */ +#define USE_HAL_MCE_REGISTER_CALLBACKS 0U /* MCE register callback disabled */ +#define USE_HAL_MDF_REGISTER_CALLBACKS 0U /* MDF register callback disabled */ +#define USE_HAL_MMC_REGISTER_CALLBACKS 0U /* MMC register callback disabled */ +#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ +#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ +#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ +#define USE_HAL_PKA_REGISTER_CALLBACKS 0U /* PKA register callback disabled */ +#define USE_HAL_PSSI_REGISTER_CALLBACKS 0U /* PSSI register callback disabled */ +#define USE_HAL_RAMCFG_REGISTER_CALLBACKS 0U /* RAMCFG register callback disabled */ +#define USE_HAL_RNG_REGISTER_CALLBACKS 0U /* RNG register callback disabled */ +#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ +#define USE_HAL_SAI_REGISTER_CALLBACKS 0U /* SAI register callback disabled */ +#define USE_HAL_SD_REGISTER_CALLBACKS 0U /* SD register callback disabled */ +#define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U /* SDRAM register callback disabled */ +#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ +#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ +#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U /* SPDIFRX register callback disabled */ +#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ +#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ +#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ +#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ +#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ +#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ +#define USE_HAL_XSPI_REGISTER_CALLBACKS 0U /* XSPI register callback disabled */ + +// Include various HAL modules for convenience +#include "stm32n6xx_hal_rcc.h" +#include "stm32n6xx_hal_gpio.h" +#include "stm32n6xx_hal_rif.h" +#include "stm32n6xx_hal_dma.h" +#include "stm32n6xx_hal_cacheaxi.h" +#include "stm32n6xx_hal_cortex.h" +#include "stm32n6xx_hal_adc.h" +#include "stm32n6xx_hal_bsec.h" +#include "stm32n6xx_hal_crc.h" +#include "stm32n6xx_hal_cryp.h" +#include "stm32n6xx_hal_dcmi.h" +#include "stm32n6xx_hal_dcmipp.h" +#include "stm32n6xx_hal_dma2d.h" +#include "stm32n6xx_hal_dts.h" +#include "stm32n6xx_hal_eth.h" +#include "stm32n6xx_hal_exti.h" +#include "stm32n6xx_hal_fdcan.h" +#include "stm32n6xx_hal_gfxmmu.h" +#include "stm32n6xx_hal_gfxtim.h" +#include "stm32n6xx_hal_gpio.h" +#include "stm32n6xx_hal_gpu2d.h" +#include "stm32n6xx_hal_hash.h" +#include "stm32n6xx_hal_hcd.h" +#include "stm32n6xx_hal_i2c.h" +#include "stm32n6xx_hal_i3c.h" +#include "stm32n6xx_hal_icache.h" +#include "stm32n6xx_hal_irda.h" +#include "stm32n6xx_hal_iwdg.h" +#include "stm32n6xx_hal_jpeg.h" +#include "stm32n6xx_hal_lptim.h" +#include "stm32n6xx_hal_ltdc.h" +#include "stm32n6xx_hal_mce.h" +#include "stm32n6xx_hal_mdf.h" +#include "stm32n6xx_hal_mdios.h" +#include "stm32n6xx_hal_mmc.h" +#include "stm32n6xx_hal_nand.h" +#include "stm32n6xx_hal_nor.h" +#include "stm32n6xx_hal_nand.h" +#include "stm32n6xx_hal_pcd.h" +#include "stm32n6xx_hal_pka.h" +#include "stm32n6xx_hal_pssi.h" +#include "stm32n6xx_hal_pwr.h" +#include "stm32n6xx_hal_ramcfg.h" +#include "stm32n6xx_hal_rng.h" +#include "stm32n6xx_hal_rtc.h" +#include "stm32n6xx_hal_sai.h" +#include "stm32n6xx_hal_sd.h" +#include "stm32n6xx_hal_sdram.h" +#include "stm32n6xx_hal_smartcard.h" +#include "stm32n6xx_hal_smbus.h" +#include "stm32n6xx_hal_spdifrx.h" +#include "stm32n6xx_hal_spi.h" +#include "stm32n6xx_hal_sram.h" +#include "stm32n6xx_hal_tim.h" +#include "stm32n6xx_hal_uart.h" +#include "stm32n6xx_hal_usart.h" +#include "stm32n6xx_hal_wwdg.h" +#include "stm32n6xx_hal_xspi.h" +#include "stm32n6xx_ll_lpuart.h" +#include "stm32n6xx_ll_pwr.h" +#include "stm32n6xx_ll_rtc.h" +#include "stm32n6xx_ll_usart.h" + +// HAL parameter assertions are disabled +#define assert_param(expr) ((void)0) + +#endif // MICROPY_INCLUDED_STM32N6XX_HAL_CONF_BASE_H From acb294f61af3bd214cc95a4881722754624b912d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Sep 2024 00:02:18 +1000 Subject: [PATCH 0896/2098] stm32/mboot: Add support for STM32N6xx MCUs. Works in the usual USB DFU mode, and can program external SPI flash. It will enable XSPI memory-mapped mode before jumping to the application firmware in the external SPI flash. Signed-off-by: Damien George --- ports/stm32/mboot/Makefile | 40 +++++++++++++++++++++++----- ports/stm32/mboot/adc.c | 2 ++ ports/stm32/mboot/main.c | 37 ++++++++++++++++++++++--- ports/stm32/mboot/mphalport.h | 17 ++++++++++++ ports/stm32/mboot/stm32_memory_n6.ld | 18 +++++++++++++ ports/stm32/mboot/stm32_sections.ld | 8 ++++++ 6 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 ports/stm32/mboot/stm32_memory_n6.ld diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 87bced1aee7..7c0bde81fdd 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -34,7 +34,13 @@ include ../../../py/mkenv.mk include $(BOARD_DIR)/mpconfigboard.mk # A board can set MBOOT_TEXT0_ADDR to a custom location where mboot should reside. +ifeq ($(MCU_SERIES),n6) +MBOOT_TEXT0_ADDR ?= 0x34180400 +MBOOT_LD_FILES ?= stm32_memory_n6.ld stm32_sections.ld +else MBOOT_TEXT0_ADDR ?= 0x08000000 +MBOOT_LD_FILES ?= stm32_memory.ld stm32_sections.ld +endif # The string in MBOOT_VERSION (default defined in version.c if not defined by a # board) will be stored in the final MBOOT_VERSION_ALLOCATED_BYTES bytes of mboot flash. @@ -89,7 +95,6 @@ CFLAGS += -DMBOOT_VERSION=\"$(MBOOT_VERSION)\" endif CFLAGS += -DMBOOT_VERSION_ALLOCATED_BYTES=$(MBOOT_VERSION_ALLOCATED_BYTES) -DMBOOT_VERSION_INCLUDE_OPTIONS=$(MBOOT_VERSION_INCLUDE_OPTIONS) -MBOOT_LD_FILES ?= stm32_memory.ld stm32_sections.ld LDFLAGS += -nostdlib -L . $(addprefix -T,$(MBOOT_LD_FILES)) -Map=$(@:.elf=.map) --cref LDFLAGS += --defsym mboot_version_len=$(MBOOT_VERSION_ALLOCATED_BYTES) LIBS += $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) @@ -137,12 +142,11 @@ SRC_C += \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ ports/stm32/flash.c \ - ports/stm32/flashbdev.c \ ports/stm32/i2cslave.c \ ports/stm32/powerctrlboot.c \ ports/stm32/qspi.c \ - ports/stm32/spibdev.c \ ports/stm32/usbd_conf.c \ + ports/stm32/xspi.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O += \ @@ -169,16 +173,22 @@ SRC_HAL += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal.c \ hal_cortex.c \ hal_dma.c \ - hal_flash.c \ - hal_flash_ex.c \ hal_pcd.c \ hal_pcd_ex.c \ hal_pwr_ex.c \ hal_rcc.c \ hal_rcc_ex.c \ + ll_rcc.c \ ll_usb.c \ ) +ifneq ($(MCU_SERIES),n6) +SRC_HAL += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ + hal_flash.c \ + hal_flash_ex.c \ + ) +endif + ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h7)) SRC_HAL += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_mmc.c \ @@ -187,6 +197,12 @@ SRC_HAL += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ ) endif +ifeq ($(MCU_SERIES),n6) +SRC_HAL += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ + hal_bsec.c \ + ) +endif + SRC_USBDEV += $(addprefix ports/stm32/$(USBDEV_DIR)/,\ core/src/usbd_core.c \ core/src/usbd_ctlreq.c \ @@ -206,7 +222,7 @@ $(TOP)/lib/stm32lib/README.md: $(ECHO) "stm32lib submodule not found, fetching it now..." (cd $(TOP) && git submodule update --init lib/stm32lib) -.PHONY: deploy deploy-stlink +.PHONY: deploy deploy-stlink deploy-trusted deploy: $(BUILD)/firmware.dfu $(ECHO) "Writing $< to the board" @@ -216,9 +232,15 @@ deploy-stlink: $(BUILD)/firmware.dfu $(ECHO) "Writing $< to the board via ST-LINK" $(Q)$(STFLASH) write $(BUILD)/firmware.bin $(MBOOT_TEXT0_ADDR) -$(BUILD)/firmware.dfu: $(BUILD)/firmware.elf +deploy-trusted: $(BUILD)/firmware-trusted.bin + $(STM32_CUBE_PROGRAMMER)/bin/STM32_Programmer.sh -c port=SWD mode=HOTPLUG ap=1 -el $(DKEL) -w $^ 0x70000000 -hardRst + +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(ECHO) "Create $@" $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .mboot_version_text $^ $(BUILD)/firmware.bin + +$(BUILD)/firmware.dfu: $(BUILD)/firmware.bin + $(ECHO) "Create $@" $(Q)$(PYTHON) $(DFU) -b $(MBOOT_TEXT0_ADDR):$(BUILD)/firmware.bin $@ $(BUILD)/firmware.hex: $(BUILD)/firmware.elf @@ -230,6 +252,10 @@ $(BUILD)/firmware.elf: $(OBJ) $(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS) $(Q)$(SIZE) $@ +$(BUILD)/firmware-trusted.bin: $(BUILD)/firmware.bin + /bin/rm -f $@ + $(STM32_CUBE_PROGRAMMER)/bin/STM32_SigningTool_CLI -bin $^ -nk -of 0x80000000 -t fsbl -o $@ -hv $(STM32_N6_HEADER_VERSION) + ######################################### # Rules to generate header files diff --git a/ports/stm32/mboot/adc.c b/ports/stm32/mboot/adc.c index c7b9749244d..06db0b59b73 100644 --- a/ports/stm32/mboot/adc.c +++ b/ports/stm32/mboot/adc.c @@ -1,3 +1,5 @@ // Include the main ADC driver, so mboot can use adc_config() and adc_config_and_read_u16(). #include "py/obj.h" +#if MICROPY_PY_MACHINE_ADC #include "../machine_adc.c" +#endif diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index ff44dac630a..2be8793351e 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -41,6 +41,7 @@ #include "sdcard.h" #include "dfu.h" #include "pack.h" +#include "xspi.h" // Whether the bootloader will leave via reset, or direct jump to the application. #ifndef MBOOT_LEAVE_BOOTLOADER_VIA_RESET @@ -373,7 +374,7 @@ void SystemClock_Config(void) { #elif defined(STM32G0) #define AHBxENR IOPENR #define AHBxENR_GPIOAEN_Pos RCC_IOPENR_GPIOAEN_Pos -#elif defined(STM32H7) +#elif defined(STM32H7) || defined(STM32N6) #define AHBxENR AHB4ENR #define AHBxENR_GPIOAEN_Pos RCC_AHB4ENR_GPIOAEN_Pos #elif defined(STM32H5) || defined(STM32WB) @@ -424,6 +425,10 @@ void mp_hal_pin_config_speed(uint32_t port_pin, uint32_t speed) { #define MBOOT_SPIFLASH2_LAYOUT "" #endif +#if defined(STM32N6) +#define FLASH_LAYOUT_STR "@Internal Flash " MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT +#else + #if defined(STM32F4) \ || defined(STM32F722xx) \ || defined(STM32F723xx) \ @@ -584,12 +589,18 @@ static int mboot_flash_write(uint32_t addr, const uint8_t *src8, size_t len) { return 0; } +#endif + /******************************************************************************/ // Writable address space interface static int do_mass_erase(void) { + #if defined(STM32N6) + return -1; + #else // TODO spiflash erase ? return mboot_flash_mass_erase(); + #endif } #if defined(MBOOT_SPIFLASH_ADDR) || defined(MBOOT_SPIFLASH2_ADDR) @@ -625,7 +636,12 @@ int hw_page_erase(uint32_t addr, uint32_t *next_addr) { } else #endif { + #if defined(STM32N6) + dfu_context.status = DFU_STATUS_ERROR_ADDRESS; + dfu_context.error = MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; + #else ret = mboot_flash_page_erase(addr, next_addr); + #endif } mboot_state_change(MBOOT_STATE_ERASE_END, ret); @@ -678,9 +694,12 @@ int hw_write(uint32_t addr, const uint8_t *src8, size_t len) { ret = mp_spiflash_write(MBOOT_SPIFLASH2_SPIFLASH, addr - MBOOT_SPIFLASH2_ADDR, len, src8); } else #endif + #if !defined(STM32N6) if (flash_is_valid_addr(addr)) { ret = mboot_flash_write(addr, src8, len); - } else { + } else + #endif + { dfu_context.status = DFU_STATUS_ERROR_ADDRESS; dfu_context.error = MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; } @@ -1509,7 +1528,7 @@ void stm32_main(uint32_t initial_r0) { // Make sure IRQ vector table points to flash where this bootloader lives. SCB->VTOR = MBOOT_VTOR; - #if __CORTEX_M != 33 + #if __CORTEX_M != 33 && __CORTEX_M != 55 // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI SCB->CCR |= SCB_CCR_STKALIGN_Msk; #endif @@ -1539,6 +1558,12 @@ void stm32_main(uint32_t initial_r0) { SCB_EnableDCache(); #endif + #if defined(STM32N6) + LL_PWR_EnableBkUpAccess(); + initial_r0 = TAMP_S->BKP31R; + TAMP_S->BKP31R = 0; + #endif + MBOOT_BOARD_EARLY_INIT(&initial_r0); #ifdef MBOOT_BOOTPIN_PIN @@ -1748,6 +1773,12 @@ void USB_DRD_FS_IRQHandler(void) { HAL_PCD_IRQHandler(&pcd_fs_handle); } +#elif defined(STM32N6) + +void USB1_OTG_HS_IRQHandler(void) { + HAL_PCD_IRQHandler(&pcd_hs_handle); +} + #elif defined(STM32WB) void USB_LP_IRQHandler(void) { diff --git a/ports/stm32/mboot/mphalport.h b/ports/stm32/mboot/mphalport.h index 45bf11d42b7..9cac0f70c49 100644 --- a/ports/stm32/mboot/mphalport.h +++ b/ports/stm32/mboot/mphalport.h @@ -239,3 +239,20 @@ void mp_hal_pin_config_speed(uint32_t port_pin, uint32_t speed); #define pin_J13 (GPIOJ_BASE | 13) #define pin_J14 (GPIOJ_BASE | 14) #define pin_J15 (GPIOJ_BASE | 15) + +#define pin_N0 (GPION_BASE | 0) +#define pin_N1 (GPION_BASE | 1) +#define pin_N2 (GPION_BASE | 2) +#define pin_N3 (GPION_BASE | 3) +#define pin_N4 (GPION_BASE | 4) +#define pin_N5 (GPION_BASE | 5) +#define pin_N6 (GPION_BASE | 6) +#define pin_N7 (GPION_BASE | 7) +#define pin_N8 (GPION_BASE | 8) +#define pin_N9 (GPION_BASE | 9) +#define pin_N10 (GPION_BASE | 10) +#define pin_N11 (GPION_BASE | 11) +#define pin_N12 (GPION_BASE | 12) +#define pin_N13 (GPION_BASE | 13) +#define pin_N14 (GPION_BASE | 14) +#define pin_N15 (GPION_BASE | 15) diff --git a/ports/stm32/mboot/stm32_memory_n6.ld b/ports/stm32/mboot/stm32_memory_n6.ld new file mode 100644 index 00000000000..bd2471dbfad --- /dev/null +++ b/ports/stm32/mboot/stm32_memory_n6.ld @@ -0,0 +1,18 @@ +/* + Linker script fragment for mboot on an STM32N6xx MCU. + This defines the memory sections for the bootloader to use. + + On N6, the hardware bootloader loads the first 512k of external flash into + the upper part of SRAM2 AXI S, starting at 0x34180000. The first 1024 bytes + is a header. Then comes the actual code, starting with the vector table. +*/ + +MEMORY +{ + FLASH_BL (rx) : ORIGIN = 0x34180400, LENGTH = 31744 /* AXISRAM2_S */ + RAM (xrw) : ORIGIN = 0x341e0000, LENGTH = 128K /* AXISRAM2_S */ +} + +/* Location of protected flash area which must not be modified, because mboot lives there. */ +_mboot_protected_flash_start = ORIGIN(FLASH_BL); +_mboot_protected_flash_end_exclusive = ORIGIN(FLASH_BL) + LENGTH(FLASH_BL); diff --git a/ports/stm32/mboot/stm32_sections.ld b/ports/stm32/mboot/stm32_sections.ld index 43511f08397..4a6fd44b2b7 100644 --- a/ports/stm32/mboot/stm32_sections.ld +++ b/ports/stm32/mboot/stm32_sections.ld @@ -33,6 +33,14 @@ SECTIONS _etext = .; } >FLASH_BL + /* Secure Gateway stubs */ + .gnu.sgstubs : + { + . = ALIGN(4); + *(.gnu.sgstubs*) + . = ALIGN(4); + } >FLASH_BL + /* used by the startup to initialize data */ _sidata = LOADADDR(.data); From 7016900fbe6d691129dd03f87ede3a9a7bde5236 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Jul 2025 15:03:04 +1000 Subject: [PATCH 0897/2098] stm32/spi: Fail spi_init if pins can't be configured. Follows the UART and I2C drivers. Signed-off-by: Damien George --- ports/stm32/spi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 19f2b65ed28..248075579a8 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -481,7 +481,10 @@ int spi_init(const spi_t *self, bool enable_nss_pin) { if (pins[i] == NULL) { continue; } - mp_hal_pin_config_alt(pins[i], mode, pull, AF_FN_SPI, (self - &spi_obj[0]) + 1); + if (!mp_hal_pin_config_alt(pins[i], mode, pull, AF_FN_SPI, (self - &spi_obj[0]) + 1)) { + // Pin does not have SPI alternate function. + return -MP_EINVAL; + } } // init the SPI device From 3189e49d2824260ca786a93301a237d78eed092e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Sep 2024 00:00:29 +1000 Subject: [PATCH 0898/2098] stm32/boards/OPENMV_N6: Add new board definition files. Signed-off-by: Damien George --- ports/stm32/boards/OPENMV_N6/bdev.c | 41 +++++ ports/stm32/boards/OPENMV_N6/board.c | 131 ++++++++++++++ ports/stm32/boards/OPENMV_N6/board.ld | 39 ++++ ports/stm32/boards/OPENMV_N6/manifest.py | 3 + ports/stm32/boards/OPENMV_N6/mpconfigboard.h | 167 ++++++++++++++++++ ports/stm32/boards/OPENMV_N6/mpconfigboard.mk | 30 ++++ .../boards/OPENMV_N6/partition_stm32n657xx.h | 5 + ports/stm32/boards/OPENMV_N6/pins.csv | 142 +++++++++++++++ .../boards/OPENMV_N6/stm32n6xx_hal_conf.h | 18 ++ 9 files changed, 576 insertions(+) create mode 100644 ports/stm32/boards/OPENMV_N6/bdev.c create mode 100644 ports/stm32/boards/OPENMV_N6/board.c create mode 100644 ports/stm32/boards/OPENMV_N6/board.ld create mode 100644 ports/stm32/boards/OPENMV_N6/manifest.py create mode 100644 ports/stm32/boards/OPENMV_N6/mpconfigboard.h create mode 100644 ports/stm32/boards/OPENMV_N6/mpconfigboard.mk create mode 100644 ports/stm32/boards/OPENMV_N6/partition_stm32n657xx.h create mode 100644 ports/stm32/boards/OPENMV_N6/pins.csv create mode 100644 ports/stm32/boards/OPENMV_N6/stm32n6xx_hal_conf.h diff --git a/ports/stm32/boards/OPENMV_N6/bdev.c b/ports/stm32/boards/OPENMV_N6/bdev.c new file mode 100644 index 00000000000..c6c918bd67c --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/bdev.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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 "storage.h" +#include "xspi.h" + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +#error "Cannot enable MICROPY_HW_SPIFLASH_ENABLE_CACHE" +#endif + +// External SPI flash uses hardware XSPI interface. +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_QSPI, + .bus.u_qspi.data = (void *)&xspi_flash2, + .bus.u_qspi.proto = &xspi_proto, +}; + +spi_bdev_t spi_bdev; diff --git a/ports/stm32/boards/OPENMV_N6/board.c b/ports/stm32/boards/OPENMV_N6/board.c new file mode 100644 index 00000000000..1f82d10bac2 --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/board.c @@ -0,0 +1,131 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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 "boardctrl.h" +#include "xspi.h" + +// Values for OTP fuses for VDDIO2/3, to select low voltage mode (<2.5V). +// See RM0486, Section 5, Table 18. +#define BSEC_HW_CONFIG_ID (124U) +#define BSEC_HWS_HSLV_VDDIO3 (1U << 15) +#define BSEC_HWS_HSLV_VDDIO2 (1U << 16) + +#define OMV_BOOT_MAGIC_ADDR (0x3401FFFCU) +#define OMV_BOOT_MAGIC_VALUE (0xB00710ADU) + +void mboot_board_early_init(void) { + // TODO: move some of the below code to a common location for all N6 boards? + + // Enable PWR, BSEC and SYSCFG clocks. + LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_PWR); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_BSEC); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_SYSCFG); + + // Program high speed IO optimization fuses if they aren't already set. + uint32_t fuse; + BSEC_HandleTypeDef hbsec = { .Instance = BSEC }; + const uint32_t mask = BSEC_HWS_HSLV_VDDIO2 | BSEC_HWS_HSLV_VDDIO3; + if (HAL_BSEC_OTP_Read(&hbsec, BSEC_HW_CONFIG_ID, &fuse) != HAL_OK) { + fuse = 0; + } else if ((fuse & mask) != mask) { + // Program the fuse, and read back the set value. + if (HAL_BSEC_OTP_Program(&hbsec, BSEC_HW_CONFIG_ID, fuse | mask, HAL_BSEC_NORMAL_PROG) != HAL_OK) { + fuse = 0; + } else if (HAL_BSEC_OTP_Read(&hbsec, BSEC_HW_CONFIG_ID, &fuse) != HAL_OK) { + fuse = 0; + } + } + + // Enable Vdd ADC, needed for the ADC to work. + LL_PWR_EnableVddADC(); + + // Configure VDDIO2. Only enable 1.8V mode if the fuse is set. + LL_PWR_EnableVddIO2(); + if (fuse & BSEC_HWS_HSLV_VDDIO2) { + LL_PWR_SetVddIO2VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_1V8); + } + SYSCFG->VDDIO2CCCR |= SYSCFG_VDDIO2CCCR_EN; // enable IO compensation + + // Configure VDDIO3. Only enable 1.8V mode if the fuse is set. + LL_PWR_EnableVddIO3(); + if (fuse & BSEC_HWS_HSLV_VDDIO3) { + LL_PWR_SetVddIO3VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_1V8); + } + SYSCFG->VDDIO3CCCR |= SYSCFG_VDDIO3CCCR_EN; // enable IO compensation + + // Configure VDDIO4. + LL_PWR_EnableVddIO4(); + LL_PWR_SetVddIO4VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO4CCCR |= SYSCFG_VDDIO4CCCR_EN; // enable IO compensation + + // Enable VDD for ADC and USB. + LL_PWR_EnableVddADC(); + LL_PWR_EnableVddUSB(); + + // Enable XSPI in memory-mapped mode. + xspi_init(); +} + +void board_enter_bootloader(unsigned int n_args, const void *args) { + // Support both OpenMV bootloader and mboot. + *((uint32_t *)OMV_BOOT_MAGIC_ADDR) = OMV_BOOT_MAGIC_VALUE; + SCB_CleanDCache(); + boardctrl_maybe_enter_mboot(n_args, args); +} + +void board_early_init(void) { + // TODO: if (HAL_PWREx_ConfigSupply(PWR_EXTERNAL_SOURCE_SUPPLY ) != HAL_OK) + + LL_PWR_EnableWakeUpPin(LL_PWR_WAKEUP_PIN3 | LL_PWR_WAKEUP_PIN2); + LL_PWR_SetWakeUpPinPolarityLow(LL_PWR_WAKEUP_PIN3 | LL_PWR_WAKEUP_PIN2); +} + +void board_leave_standby(void) { + // TODO: move some of the below code to a common location for all N6 boards? + + // Enable PWR, BSEC and SYSCFG clocks. + LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_PWR); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_BSEC); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_SYSCFG); + + // Configure VDDIO2 (1.8V mode selection is retained). + LL_PWR_EnableVddIO2(); + SYSCFG->VDDIO2CCCR |= SYSCFG_VDDIO2CCCR_EN; // enable IO compensation + + // Configure VDDIO3 (1.8V mode selection is retained). + LL_PWR_EnableVddIO3(); + SYSCFG->VDDIO3CCCR |= SYSCFG_VDDIO3CCCR_EN; // enable IO compensation + + // Configure VDDIO4. + LL_PWR_EnableVddIO4(); + LL_PWR_SetVddIO4VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO4CCCR |= SYSCFG_VDDIO4CCCR_EN; // enable IO compensation + + // Enable VDD for ADC and USB. + LL_PWR_EnableVddADC(); + LL_PWR_EnableVddUSB(); +} diff --git a/ports/stm32/boards/OPENMV_N6/board.ld b/ports/stm32/boards/OPENMV_N6/board.ld new file mode 100644 index 00000000000..e9ded785f2d --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/board.ld @@ -0,0 +1,39 @@ +/* + Linker script for OPENMV_N6. + + Note: upper 512k of SRAM2 is copied from external flash upon reset. +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLEXRAM_S (xrw) : ORIGIN = 0x34000000, LENGTH = 80K + SRAM2_S_RAM (xrw) : ORIGIN = 0x34100000, LENGTH = 1024K + SRAM2_S_FSBL (xrw) : ORIGIN = 0x34180400, LENGTH = 511K /* mboot firmware, not needed after mboot exits */ + EXT_FLASH (rx) : ORIGIN = 0x70080000, LENGTH = 3584K + EXT_FLASH_FS (rx) : ORIGIN = 0x70400000, LENGTH = 4M + EXT_FLASH_ROMFS (rx) : ORIGIN = 0x70800000, LENGTH = 24M +} + +REGION_ALIAS("IRAM", FLEXRAM_S); +REGION_ALIAS("RAM", SRAM2_S_RAM); +REGION_ALIAS("FLASH_APP", EXT_FLASH); + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM extents for the garbage collector */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; + +/* ROMFS location */ +_micropy_hw_romfs_part0_start = ORIGIN(EXT_FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(EXT_FLASH_ROMFS); diff --git a/ports/stm32/boards/OPENMV_N6/manifest.py b/ports/stm32/boards/OPENMV_N6/manifest.py new file mode 100644 index 00000000000..62990220f31 --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/boards/manifest.py") +require("bundle-networking") +require("aioble") diff --git a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h new file mode 100644 index 00000000000..ed7bb548a1d --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h @@ -0,0 +1,167 @@ +#define MICROPY_HW_BOARD_NAME "OpenMV N6" +#define MICROPY_HW_MCU_NAME "STM32N657X0" + +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#define MICROPY_ALLOC_GC_STACK_SIZE (128) +#define MICROPY_FATFS_EXFAT (1) + +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) +#define MICROPY_HW_HAS_SWITCH (0) +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_SDCARD_MOUNT_AT_BOOT (0) +#define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_DAC (0) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_ENABLE_SDCARD (1) +#define MICROPY_PY_PYB_LEGACY (0) + +#define MICROPY_BOARD_ENTER_BOOTLOADER board_enter_bootloader +#define MICROPY_BOARD_EARLY_INIT board_early_init +#define MICROPY_BOARD_LEAVE_STANDBY board_leave_standby() + +// HSE is 48MHz, this gives a CPU frequency of 800MHz. +#define MICROPY_HW_CLK_PLLM (6) +#define MICROPY_HW_CLK_PLLN (100) +#define MICROPY_HW_CLK_PLLP1 (1) +#define MICROPY_HW_CLK_PLLP2 (1) +#define MICROPY_HW_CLK_PLLFRAC (0) + +// The LSE is a 32kHz crystal. +#define MICROPY_HW_RTC_USE_LSE (1) +#define MICROPY_HW_RTC_USE_US (1) + +// External SPI flash. +#define MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2 (28) // 256Mbit + +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_XSPI (1) +#define MICROPY_HW_ROMFS_XSPI_SPIBDEV_OBJ (&spi_bdev) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) + +// SPI flash, block device config. +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES (4 * 1024 * 1024) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (4 * 1024 * 1024) + +// UART buses +#define MICROPY_HW_UART2_TX (pyb_pin_BT_TXD) +#define MICROPY_HW_UART2_RX (pyb_pin_BT_RXD) +#define MICROPY_HW_UART2_RTS (pyb_pin_BT_RTS) +#define MICROPY_HW_UART2_CTS (pyb_pin_BT_CTS) +#define MICROPY_HW_UART3_TX (pyb_pin_UART3_TX) +#define MICROPY_HW_UART3_RX (pyb_pin_UART3_RX) +#define MICROPY_HW_UART4_TX (pyb_pin_UART4_TX) +#define MICROPY_HW_UART4_RX (pyb_pin_UART4_RX) +#define MICROPY_HW_UART7_TX (pyb_pin_UART7_TX) +#define MICROPY_HW_UART7_RX (pyb_pin_UART7_RX) + +// I2C buses +#define MICROPY_HW_I2C2_SCL (pyb_pin_I2C2_SCL) +#define MICROPY_HW_I2C2_SDA (pyb_pin_I2C2_SDA) +#define MICROPY_HW_I2C4_SCL (pyb_pin_I2C4_SCL) +#define MICROPY_HW_I2C4_SDA (pyb_pin_I2C4_SDA) + +// SPI buses +#define MICROPY_HW_SPI2_NSS (pyb_pin_SPI2_CS) +#define MICROPY_HW_SPI2_SCK (pyb_pin_SPI2_SCK) +#define MICROPY_HW_SPI2_MISO (pyb_pin_SPI2_MISO) +#define MICROPY_HW_SPI2_MOSI (pyb_pin_SPI2_MOSI) +#define MICROPY_HW_SPI4_NSS (pyb_pin_SPI4_CS) +#define MICROPY_HW_SPI4_SCK (pyb_pin_SPI4_SCK) +#define MICROPY_HW_SPI4_MISO (pyb_pin_SPI4_MISO) +#define MICROPY_HW_SPI4_MOSI (pyb_pin_SPI4_MOSI) + +// USER is pulled high, and pressing the button makes the input go low. +#define MICROPY_HW_USRSW_PIN (pyb_pin_BUTTON) +#define MICROPY_HW_USRSW_PULL (GPIO_NOPULL) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_FALLING) +#define MICROPY_HW_USRSW_PRESSED (0) + +// LEDs +#define MICROPY_HW_LED1 (pyb_pin_LED_RED) +#define MICROPY_HW_LED2 (pyb_pin_LED_GREEN) +#define MICROPY_HW_LED3 (pyb_pin_LED_BLUE) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) + +// SD Card SDMMC +// SD_VSELECT: low(default)=3.3V IO, high=1.8V IO +// SD_RESET: drive low to turn off SD VCC (pulled high by default) +// SD_DETECT: pulled high in hardware, goes low when SD inserted +#define MICROPY_HW_SDCARD_SDMMC (1) +#define MICROPY_HW_SDCARD_CK (pyb_pin_SD_SDIO_CK) +#define MICROPY_HW_SDCARD_CMD (pyb_pin_SD_SDIO_CMD) +#define MICROPY_HW_SDCARD_D0 (pyb_pin_SD_SDIO_D0) +#define MICROPY_HW_SDCARD_D1 (pyb_pin_SD_SDIO_D1) +#define MICROPY_HW_SDCARD_D2 (pyb_pin_SD_SDIO_D2) +#define MICROPY_HW_SDCARD_D3 (pyb_pin_SD_SDIO_D3) +#define MICROPY_HW_SDCARD_DETECT_PIN (pyb_pin_SD_DETECT) +#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_NOPULL) +#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) + +// WiFi SDMMC +#define MICROPY_HW_SDIO_SDMMC (2) +#define MICROPY_HW_SDIO_CK (pyb_pin_WL_SDIO_CK) +#define MICROPY_HW_SDIO_CMD (pyb_pin_WL_SDIO_CMD) +#define MICROPY_HW_SDIO_D0 (pyb_pin_WL_SDIO_D0) +#define MICROPY_HW_SDIO_D1 (pyb_pin_WL_SDIO_D1) +#define MICROPY_HW_SDIO_D2 (pyb_pin_WL_SDIO_D2) +#define MICROPY_HW_SDIO_D3 (pyb_pin_WL_SDIO_D3) + +// USB config +#define MICROPY_HW_USB_HS (1) +#define MICROPY_HW_USB_HS_IN_FS (1) +#define MICROPY_HW_USB_MAIN_DEV (USB_PHY_HS_ID) +#define MICROPY_HW_USB_VID 0x37C5 +#define MICROPY_HW_USB_PID 0x1206 +#define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) +#define MICROPY_HW_USB_PID_MSC (MICROPY_HW_USB_PID) +#define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) +#define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) +#define MICROPY_HW_USB_PID_CDC_MSC_HID (MICROPY_HW_USB_PID) + +// Murata 1YN configuration +#define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/w43439_sdio_1yn_7_95_59_combined.h" +#define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1yn.h" +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_1yn.h" + +// Bluetooth config +#define MICROPY_HW_BLE_UART_ID (PYB_UART_2) +#define MICROPY_HW_BLE_UART_BAUDRATE (115200) +#define MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY (3000000) +#define MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE (3000000) + +/******************************************************************************/ +// Bootloader configuration + +#define MBOOT_BOARD_EARLY_INIT(initial_r0) mboot_board_early_init() + +#define MBOOT_FSLOAD (1) +#define MBOOT_VFS_FAT (1) + +#define MBOOT_SPIFLASH_CS (pyb_pin_XSPIM_P2_CS) +#define MBOOT_SPIFLASH_SCK (pyb_pin_XSPIM_P2_SCK) +#define MBOOT_SPIFLASH_MOSI (pyb_pin_XSPIM_P2_IO0) +#define MBOOT_SPIFLASH_MISO (pyb_pin_XSPIM_P2_IO1) +#define MBOOT_SPIFLASH_ADDR (0x70000000) +#define MBOOT_SPIFLASH_BYTE_SIZE (32 * 1024 * 1024) +#define MBOOT_SPIFLASH_LAYOUT "/0x70000000/8192*4Kg" +#define MBOOT_SPIFLASH_ERASE_BLOCKS_PER_PAGE (1) +#define MBOOT_SPIFLASH_SPIFLASH (&spi_bdev.spiflash) +#define MBOOT_SPIFLASH_CONFIG (&spiflash_config) + +/******************************************************************************/ +// Function and variable declarations + +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; + +void mboot_board_early_init(void); +void mboot_board_entry_init(void); + +void board_enter_bootloader(unsigned int n_args, const void *args); +void board_early_init(void); +void board_leave_standby(void); diff --git a/ports/stm32/boards/OPENMV_N6/mpconfigboard.mk b/ports/stm32/boards/OPENMV_N6/mpconfigboard.mk new file mode 100644 index 00000000000..0283a486c1a --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/mpconfigboard.mk @@ -0,0 +1,30 @@ +# This board requires a bootloader, either mboot or OpenMV's bootloader. +USE_MBOOT = 1 + +MCU_SERIES = n6 +CMSIS_MCU = STM32N657xx +AF_FILE = boards/stm32n657_af.csv +ifeq ($(BUILDING_MBOOT),1) +SYSTEM_FILE = $(STM32LIB_CMSIS_BASE)/Source/Templates/system_stm32$(MCU_SERIES)xx_fsbl.o +else +SYSTEM_FILE = $(STM32LIB_CMSIS_BASE)/Source/Templates/system_stm32$(MCU_SERIES)xx_s.o +endif +STM32_N6_HEADER_VERSION = 2.3 +DKEL = $(STM32_CUBE_PROGRAMMER)/bin/ExternalLoader/MX25UM51245G_STM32N6570-NUCLEO.stldr + +LD_FILES = boards/OPENMV_N6/board.ld boards/common_n6_flash.ld +TEXT0_ADDR = 0x70080000 + +# MicroPython settings +MICROPY_FLOAT_IMPL = double +MICROPY_PY_BLUETOOTH ?= 1 +MICROPY_BLUETOOTH_NIMBLE ?= 1 +MICROPY_BLUETOOTH_BTSTACK ?= 0 +MICROPY_PY_LWIP ?= 1 +MICROPY_PY_NETWORK_CYW43 ?= 1 +MICROPY_PY_SSL ?= 1 +MICROPY_SSL_MBEDTLS ?= 1 +MICROPY_VFS_LFS2 ?= 1 + +# Board specific frozen modules +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py diff --git a/ports/stm32/boards/OPENMV_N6/partition_stm32n657xx.h b/ports/stm32/boards/OPENMV_N6/partition_stm32n657xx.h new file mode 100644 index 00000000000..ac38dac7486 --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/partition_stm32n657xx.h @@ -0,0 +1,5 @@ +// This board does not use any security settings, so can just stay in secure +// mode without configuring the SAU. + +static inline void TZ_SAU_Setup(void) { +} diff --git a/ports/stm32/boards/OPENMV_N6/pins.csv b/ports/stm32/boards/OPENMV_N6/pins.csv new file mode 100644 index 00000000000..b05b8b57f9e --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/pins.csv @@ -0,0 +1,142 @@ +,PA0 +,PA1 +,PA2 +,PA3 +,PA4 +,PA5 +,PA6 +,PA7 +,PA8 +,PA9 +,PA10 +SPI2_CS,PA11 +SPI2_SCK,PA12 +UART4_RX,PA11 +UART4_TX,PA12 +P3,PA11 +P2,PA12 +,PA13 +,PA14 +,PA15 +,PB0 +,PB1 +,PB2 +,PB3 +,PB4 +,PB5 +SPI4_MISO,PB6 +SPI4_MOSI,PB7 +,PB8 +,PB9 +I2C2_SCL,PB10 +I2C2_SDA,PB11 +UART3_TX,PB10 +UART3_RX,PB11 +P4,PB10 +P5,PB11 +,PB12 +,PB13 +,PB14 +,PB15 +,PC0 +,PC1 +,PC2 +,PC3 +,PC4 +,PC5 +,PC6 +,PC7 +,PC8 +,PC9 +,PC10 +,PC11 +,PC12 +P11,PC13 +,PC14 +,PC15 +,PD0 +,PD1 +,PD2 +,PD3 +,PD4 +,PD5 +P10,PD6 +SPI2_MOSI,PD7 +P0,PD7 +,PD8 +,PD9 +,PD10 +SPI2_MISO,PD11 +P1,PD11 +,PD12 +P8,PD13 +,PD14 +,PD15 +,PE0 +,PE1 +,PE2 +,PE3 +,PE4 +,PE5 +,PE6 +UART7_RX,PE7 +UART7_TX,PE8 +,PE9 +,PE10 +SPI4_CS,PE11 +SPI4_SCK,PE12 +I2C4_SCL,PE13 +I2C4_SDA,PE14 +,PE15 +P6,PG0 +P9,PG12 +P7,PG13 +,PG15 + +BUTTON,PF4 +LED_RED,PG10 +LED_GREEN,PA7 +LED_BLUE,PB1 + +-XSPIM_P2_DQS,PN0 +-XSPIM_P2_CS,PN1 +-XSPIM_P2_IO0,PN2 +-XSPIM_P2_IO1,PN3 +-XSPIM_P2_IO2,PN4 +-XSPIM_P2_IO3,PN5 +-XSPIM_P2_SCK,PN6 +-XSPIM_P2_NCLK,PN7 +-XSPIM_P2_IO4,PN8 +-XSPIM_P2_IO5,PN9 +-XSPIM_P2_IO6,PN10 +-XSPIM_P2_IO7,PN11 +-FLASH_RESET,PN12 + +-WL_REG_ON,PB12 +-WL_HOST_WAKE,PB14 +-WL_SDIO_D0,PB8 +-WL_SDIO_D1,PG8 +-WL_SDIO_D2,PB9 +-WL_SDIO_D3,PB4 +-WL_SDIO_CMD,PA0 +-WL_SDIO_CK,PD2 +-WL_I2S_SDO,PG14 +-WL_I2S_WS,PB15 +-WL_I2S_SCLK,PB13 +-BT_RXD,PF6 +-BT_TXD,PD5 +-BT_CTS,PG5 +-BT_RTS,PF3 +-BT_REG_ON,PD10 +-BT_HOST_WAKE,PD14 +-BT_DEV_WAKE,PD15 + +-SD_SDIO_D0,PC8 +-SD_SDIO_D1,PC9 +-SD_SDIO_D2,PC10 +-SD_SDIO_D3,PC11 +-SD_SDIO_CK,PC12 +-SD_SDIO_CMD,PH2 +-SD_RESET,PC7 +-SD_DETECT,PC6 +-SD_VSELECT,PG6 diff --git a/ports/stm32/boards/OPENMV_N6/stm32n6xx_hal_conf.h b/ports/stm32/boards/OPENMV_N6/stm32n6xx_hal_conf.h new file mode 100644 index 00000000000..4012d56e5a3 --- /dev/null +++ b/ports/stm32/boards/OPENMV_N6/stm32n6xx_hal_conf.h @@ -0,0 +1,18 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ +#ifndef MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H + +// Oscillator values in Hz +#define HSE_VALUE (48000000) +#define LSE_VALUE (32768) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (100) +#define LSE_STARTUP_TIMEOUT (5000) + +#include "boards/stm32n6xx_hal_conf_base.h" + +#endif // MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H From 50ea398b002d5e8ae4da2caa9c09d08c20df7007 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 18 Jun 2024 17:46:54 +1000 Subject: [PATCH 0899/2098] stm32/boards/NUCLEO_N657X0: Add new board definition files. Signed-off-by: Damien George --- ports/stm32/boards/NUCLEO_N657X0/bdev.c | 42 ++++++ ports/stm32/boards/NUCLEO_N657X0/board.c | 122 ++++++++++++++++++ ports/stm32/boards/NUCLEO_N657X0/board.md | 17 +++ .../boards/NUCLEO_N657X0/mpconfigboard.h | 103 +++++++++++++++ .../boards/NUCLEO_N657X0/mpconfigboard.mk | 26 ++++ .../NUCLEO_N657X0/partition_stm32n657xx.h | 5 + ports/stm32/boards/NUCLEO_N657X0/pins.csv | 62 +++++++++ .../boards/NUCLEO_N657X0/stm32n6xx_hal_conf.h | 18 +++ 8 files changed, 395 insertions(+) create mode 100644 ports/stm32/boards/NUCLEO_N657X0/bdev.c create mode 100644 ports/stm32/boards/NUCLEO_N657X0/board.c create mode 100644 ports/stm32/boards/NUCLEO_N657X0/board.md create mode 100644 ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h create mode 100644 ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk create mode 100644 ports/stm32/boards/NUCLEO_N657X0/partition_stm32n657xx.h create mode 100644 ports/stm32/boards/NUCLEO_N657X0/pins.csv create mode 100644 ports/stm32/boards/NUCLEO_N657X0/stm32n6xx_hal_conf.h diff --git a/ports/stm32/boards/NUCLEO_N657X0/bdev.c b/ports/stm32/boards/NUCLEO_N657X0/bdev.c new file mode 100644 index 00000000000..2180d46174a --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/bdev.c @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/obj.h" +#include "storage.h" +#include "xspi.h" + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +#error "Cannot enable MICROPY_HW_SPIFLASH_ENABLE_CACHE" +#endif + +// External SPI flash uses hardware XSPI interface. +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_QSPI, + .bus.u_qspi.data = (void *)&xspi_flash2, + .bus.u_qspi.proto = &xspi_proto, +}; + +spi_bdev_t spi_bdev; diff --git a/ports/stm32/boards/NUCLEO_N657X0/board.c b/ports/stm32/boards/NUCLEO_N657X0/board.c new file mode 100644 index 00000000000..fe5f2f1cc83 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/board.c @@ -0,0 +1,122 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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 "boardctrl.h" +#include "xspi.h" + +// Values for OTP fuses for VDDIO3, to select low voltage mode (<2.5V). +// See RM0486, Section 5, Table 18. +#define BSEC_HW_CONFIG_ID (124U) +#define BSEC_HWS_HSLV_VDDIO3 (1U << 15) + +static void board_config_vdd(void) { + // TODO: move some of the below code to a common location for all N6 boards? + + // Enable PWR, BSEC and SYSCFG clocks. + LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_PWR); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_BSEC); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_SYSCFG); + + // Program high speed IO optimization fuses if they aren't already set. + uint32_t fuse; + BSEC_HandleTypeDef hbsec = { .Instance = BSEC }; + const uint32_t mask = BSEC_HWS_HSLV_VDDIO3; + if (HAL_BSEC_OTP_Read(&hbsec, BSEC_HW_CONFIG_ID, &fuse) != HAL_OK) { + fuse = 0; + } else if ((fuse & mask) != mask) { + // Program the fuse, and read back the set value. + if (HAL_BSEC_OTP_Program(&hbsec, BSEC_HW_CONFIG_ID, fuse | mask, HAL_BSEC_NORMAL_PROG) != HAL_OK) { + fuse = 0; + } else if (HAL_BSEC_OTP_Read(&hbsec, BSEC_HW_CONFIG_ID, &fuse) != HAL_OK) { + fuse = 0; + } + } + + // Enable Vdd ADC, needed for the ADC to work. + LL_PWR_EnableVddADC(); + + // Configure VDDIO2. + LL_PWR_EnableVddIO2(); + LL_PWR_SetVddIO2VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO2CCCR |= SYSCFG_VDDIO2CCCR_EN; // enable IO compensation + + // Configure VDDIO3. Only enable 1.8V mode if the fuse is set. + LL_PWR_EnableVddIO3(); + if (fuse & BSEC_HWS_HSLV_VDDIO3) { + LL_PWR_SetVddIO3VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_1V8); + } + SYSCFG->VDDIO3CCCR |= SYSCFG_VDDIO3CCCR_EN; // enable IO compensation + + // Configure VDDIO4. + LL_PWR_EnableVddIO4(); + LL_PWR_SetVddIO4VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO4CCCR |= SYSCFG_VDDIO4CCCR_EN; // enable IO compensation + + // Enable VDD for ADC and USB. + LL_PWR_EnableVddADC(); + LL_PWR_EnableVddUSB(); +} + +void mboot_board_early_init(void) { + board_config_vdd(); + xspi_init(); +} + +void board_early_init(void) { + #if !MICROPY_HW_RUNS_FROM_EXT_FLASH + // Firmware runs directly from SRAM, so configure VDD and enable XSPI flash. + board_config_vdd(); + xspi_init(); + #endif +} + +void board_leave_standby(void) { + // TODO: move some of the below code to a common location for all N6 boards? + + // Enable PWR, BSEC and SYSCFG clocks. + LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_PWR); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_BSEC); + LL_APB4_GRP2_EnableClock(LL_APB4_GRP2_PERIPH_SYSCFG); + + // Configure VDDIO2. + LL_PWR_EnableVddIO2(); + LL_PWR_SetVddIO2VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO2CCCR |= SYSCFG_VDDIO2CCCR_EN; // enable IO compensation + + // Configure VDDIO3 (1.8V mode selection is retained). + LL_PWR_EnableVddIO3(); + SYSCFG->VDDIO3CCCR |= SYSCFG_VDDIO3CCCR_EN; // enable IO compensation + + // Configure VDDIO4. + LL_PWR_EnableVddIO4(); + LL_PWR_SetVddIO4VoltageRange(LL_PWR_VDDIO_VOLTAGE_RANGE_3V3); + SYSCFG->VDDIO4CCCR |= SYSCFG_VDDIO4CCCR_EN; // enable IO compensation + + // Enable VDD for ADC and USB. + LL_PWR_EnableVddADC(); + LL_PWR_EnableVddUSB(); +} diff --git a/ports/stm32/boards/NUCLEO_N657X0/board.md b/ports/stm32/boards/NUCLEO_N657X0/board.md new file mode 100644 index 00000000000..3360c5db6c7 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/board.md @@ -0,0 +1,17 @@ +The mboot bootloader must first be built and deployed to this board. Make sure that +CN9 is in position 1-2 to select STLK as the 5V power source, that JP1 is in position +1-2 (lower position) and JP2 is in position 2-3 (upper position). Then plug in a USB +cable into the ST-LINK port CN10. This will allow mboot firmware to be programmed to +the external SPI flash via ST's tools, eg: + + make -C ports/stm32/mboot BOARD=NUCLEO_N657X0 deploy-trusted + +Once mboot is installed, change CN9 to position 3-4 to select USB as the 5V power +source, change JP2 back to position 1-2 (lower position) and change the USB cable to +CN8. mboot will present a USB DFU device on this USB port, and the red LED2 should be +blinking at 1Hz to indicate that mboot is active. If it's not active then hold the +USER button and press NRST, and wait until all three LEDs are on, then release USER. +Now mboot will be active. + +Once the USB DFU port can be seen, the firmware below can be programmed as usual with +any DFU loader. diff --git a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h new file mode 100644 index 00000000000..ccc3fa051ff --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h @@ -0,0 +1,103 @@ +#define MICROPY_HW_BOARD_NAME "NUCLEO-N657X0" +#define MICROPY_HW_MCU_NAME "STM32N657X0" + +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#define MICROPY_ALLOC_GC_STACK_SIZE (128) +#define MICROPY_FATFS_EXFAT (1) + +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_DAC (0) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_PY_PYB_LEGACY (0) + +#define MICROPY_BOARD_EARLY_INIT board_early_init +#define MICROPY_BOARD_LEAVE_STANDBY board_leave_standby() + +// HSE is 48MHz, this gives a CPU frequency of 800MHz. +#define MICROPY_HW_CLK_PLLM (6) +#define MICROPY_HW_CLK_PLLN (100) +#define MICROPY_HW_CLK_PLLP1 (1) +#define MICROPY_HW_CLK_PLLP2 (1) +#define MICROPY_HW_CLK_PLLFRAC (0) + +// The LSE is a 32kHz crystal. +#define MICROPY_HW_RTC_USE_LSE (1) +#define MICROPY_HW_RTC_USE_US (1) + +// External SPI flash, MX25UM51245GXDI00. +#define MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2 (29) + +// SPI flash, block device config. +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_OFFSET_BYTES (4 * 1024 * 1024) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (60 * 1024 * 1024) + +// UART buses +#define MICROPY_HW_UART1_TX (pyb_pin_UART1_TX) +#define MICROPY_HW_UART1_RX (pyb_pin_UART1_RX) +#define MICROPY_HW_UART3_TX (pyb_pin_UART3_TX) +#define MICROPY_HW_UART3_RX (pyb_pin_UART3_RX) +#define MICROPY_HW_UART_REPL (PYB_UART_1) +#define MICROPY_HW_UART_REPL_BAUD (115200) + +// I2C buses +#define MICROPY_HW_I2C1_SCL (pyb_pin_I2C1_SCL) +#define MICROPY_HW_I2C1_SDA (pyb_pin_I2C1_SDA) + +// SPI buses +#define MICROPY_HW_SPI5_NSS (pyb_pin_SPI5_CS) +#define MICROPY_HW_SPI5_SCK (pyb_pin_SPI5_SCK) +#define MICROPY_HW_SPI5_MISO (pyb_pin_SPI5_MISO) +#define MICROPY_HW_SPI5_MOSI (pyb_pin_SPI5_MOSI) + +// USER2 is floating, and pressing the button makes the input go high. +#define MICROPY_HW_USRSW_PIN (pyb_pin_BUTTON) +#define MICROPY_HW_USRSW_PULL (GPIO_PULLDOWN) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_RISING) +#define MICROPY_HW_USRSW_PRESSED (1) + +// LEDs +#define MICROPY_HW_LED1 (pyb_pin_LED_RED) +#define MICROPY_HW_LED2 (pyb_pin_LED_GREEN) +#define MICROPY_HW_LED3 (pyb_pin_LED_BLUE) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) + +// USB config +#define MICROPY_HW_USB_HS (1) +#define MICROPY_HW_USB_HS_IN_FS (1) +#define MICROPY_HW_USB_MAIN_DEV (USB_PHY_HS_ID) + +/******************************************************************************/ +// Bootloader configuration + +#define MBOOT_BOARD_EARLY_INIT(initial_r0) mboot_board_early_init() + +#define MBOOT_SPIFLASH_CS (pyb_pin_XSPIM_P2_CS) +#define MBOOT_SPIFLASH_SCK (pyb_pin_XSPIM_P2_SCK) +#define MBOOT_SPIFLASH_MOSI (pyb_pin_XSPIM_P2_IO0) +#define MBOOT_SPIFLASH_MISO (pyb_pin_XSPIM_P2_IO1) +#define MBOOT_SPIFLASH_ADDR (0x70000000) +#define MBOOT_SPIFLASH_BYTE_SIZE (64 * 1024 * 1024) +#define MBOOT_SPIFLASH_LAYOUT "/0x70000000/16384*4Kg" +#define MBOOT_SPIFLASH_ERASE_BLOCKS_PER_PAGE (1) +#define MBOOT_SPIFLASH_SPIFLASH (&spi_bdev.spiflash) +#define MBOOT_SPIFLASH_CONFIG (&spiflash_config) + +/******************************************************************************/ +// Function and variable declarations + +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; + +void mboot_board_early_init(void); +void mboot_board_entry_init(void); + +void board_early_init(void); +void board_leave_standby(void); diff --git a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk new file mode 100644 index 00000000000..fa64cb17065 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk @@ -0,0 +1,26 @@ +# Without mboot, the main firmware must fit in 512k flash, will be copied to SRAM by +# the hardware bootloader, and will run from SRAM. With mboot, the main firmware can +# be much larger and will run from flash via XSPI in memory-mapped mode. +USE_MBOOT ?= 1 + +MCU_SERIES = n6 +CMSIS_MCU = STM32N657xx +AF_FILE = boards/stm32n657_af.csv +ifeq ($(BUILDING_MBOOT),1) +SYSTEM_FILE = $(STM32LIB_CMSIS_BASE)/Source/Templates/system_stm32$(MCU_SERIES)xx_fsbl.o +else +SYSTEM_FILE = $(STM32LIB_CMSIS_BASE)/Source/Templates/system_stm32$(MCU_SERIES)xx_s.o +endif +STM32_N6_HEADER_VERSION = 2.1 +DKEL = $(STM32_CUBE_PROGRAMMER)/bin/ExternalLoader/MX25UM51245G_STM32N6570-NUCLEO.stldr + +ifeq ($(USE_MBOOT),1) +LD_FILES = boards/stm32n657x0.ld boards/common_n6_flash.ld +TEXT0_ADDR = 0x70080000 +else +LD_FILES = boards/stm32n657x0.ld boards/common_basic.ld +TEXT0_ADDR = 0x34180400 +endif + +# MicroPython settings +MICROPY_FLOAT_IMPL = double diff --git a/ports/stm32/boards/NUCLEO_N657X0/partition_stm32n657xx.h b/ports/stm32/boards/NUCLEO_N657X0/partition_stm32n657xx.h new file mode 100644 index 00000000000..ac38dac7486 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/partition_stm32n657xx.h @@ -0,0 +1,5 @@ +// This board does not use any security settings, so can just stay in secure +// mode without configuring the SAU. + +static inline void TZ_SAU_Setup(void) { +} diff --git a/ports/stm32/boards/NUCLEO_N657X0/pins.csv b/ports/stm32/boards/NUCLEO_N657X0/pins.csv new file mode 100644 index 00000000000..033f0a552e0 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/pins.csv @@ -0,0 +1,62 @@ +D0,PD9 +D1,PD8 +D2,PD0 +D3,PE9 +D4,PE0 +D5,PE10 +D6,PD5 +D7,PE11 +D8,PD12 +D9,PD7 +D10,PA3 +D11,PG2 +D12,PG1 +D13,PE15 +D14,PC1 +D15,PH9 + +# Ax header pins are connected directly to the following digital IO +A0D,PF5 +A1D,PC10 +A2D,PF6 +A3D,PA2 +A4D,PC12 +A5D,PH2 + +# Ax header pins are connected to the following analog IO via an op-amp in voltage-follower mode running at 1.8V +A0,PA8 +A1,PA9 +A2,PA10 +A3,PA12 +A4,PF3 +A5,PG15 + +-UART1_TX,PE5 +-UART1_RX,PE6 +-UART3_TX,PD8 +-UART3_RX,PD9 + +-I2C1_SCL,PH9 +-I2C1_SDA,PC1 + +-SPI5_CS,PA3 +-SPI5_SCK,PE15 +-SPI5_MISO,PG1 +-SPI5_MOSI,PG2 + +-BUTTON,PC13 +LED_BLUE,PG8 +LED_RED,PG10 +LED_GREEN,PG0 + +-XSPIM_P2_DQS,PN0 +-XSPIM_P2_CS,PN1 +-XSPIM_P2_IO0,PN2 +-XSPIM_P2_IO1,PN3 +-XSPIM_P2_IO2,PN4 +-XSPIM_P2_IO3,PN5 +-XSPIM_P2_SCK,PN6 +-XSPIM_P2_IO4,PN8 +-XSPIM_P2_IO5,PN9 +-XSPIM_P2_IO6,PN10 +-XSPIM_P2_IO7,PN11 diff --git a/ports/stm32/boards/NUCLEO_N657X0/stm32n6xx_hal_conf.h b/ports/stm32/boards/NUCLEO_N657X0/stm32n6xx_hal_conf.h new file mode 100644 index 00000000000..4012d56e5a3 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_N657X0/stm32n6xx_hal_conf.h @@ -0,0 +1,18 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ +#ifndef MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H + +// Oscillator values in Hz +#define HSE_VALUE (48000000) +#define LSE_VALUE (32768) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (100) +#define LSE_STARTUP_TIMEOUT (5000) + +#include "boards/stm32n6xx_hal_conf_base.h" + +#endif // MICROPY_INCLUDED_STM32N6XX_HAL_CONF_H From 99740dbace962eb8e38da174cc1857406e626456 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 15:25:28 +1000 Subject: [PATCH 0900/2098] stm32/stm32.mk: Error out if compiling for cortex-m55 on old gcc. Signed-off-by: Damien George --- ports/stm32/Makefile | 4 +--- ports/stm32/mboot/Makefile | 3 +-- ports/stm32/stm32.mk | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index affd9d2f2f6..37d70dcdbf0 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -60,6 +60,7 @@ include $(TOP)/extmod/extmod.mk GIT_SUBMODULES += lib/libhydrogen lib/stm32lib +CROSS_COMPILE ?= arm-none-eabi- LD_DIR=boards USBDEV_DIR=usbdev #USBHOST_DIR=usbhost @@ -101,9 +102,6 @@ GEN_STMCONST_HDR = $(HEADER_BUILD)/modstm_const.h GEN_STMCONST_MPZ = $(HEADER_BUILD)/modstm_mpz.h CMSIS_MCU_HDR = $(STM32LIB_CMSIS_ABS)/Include/$(CMSIS_MCU_LOWER).h -# Select the cross compile prefix -CROSS_COMPILE ?= arm-none-eabi- - INC += -I. INC += -I$(TOP) INC += -I$(BUILD) diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 7c0bde81fdd..7226dd353f3 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -48,6 +48,7 @@ endif MBOOT_VERSION_ALLOCATED_BYTES ?= 64 MBOOT_VERSION_INCLUDE_OPTIONS ?= 1 # if set to 1, this will append build options to version string (see version.c) +CROSS_COMPILE ?= arm-none-eabi- USBDEV_DIR=usbdev DFU=$(TOP)/tools/dfu.py PYDFU ?= $(TOP)/tools/pydfu.py @@ -59,8 +60,6 @@ OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg include ../stm32.mk -CROSS_COMPILE ?= arm-none-eabi- - INC += -I. INC += -I.. INC += -I$(TOP) diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index e2e7d955c6c..e6526fc6bd5 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -83,3 +83,17 @@ MPY_CROSS_MCU_ARCH_h7 = armv7m MPY_CROSS_MCU_ARCH_n6 = armv7m # really armv8m MPY_CROSS_MCU_ARCH_wb = armv7m MPY_CROSS_MCU_ARCH_wl = armv7m + +# gcc up to 14.2.0 have a known loop-optimisation bug: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116799 +# This bug manifests for Cortex M55 targets, so require a newer compiler on such targets. +ifeq ($(MCU_SERIES),n6) +# Check if GCC version is less than 14.3 +GCC_VERSION := $(shell $(CROSS_COMPILE)gcc -dumpversion | cut -d. -f1-2) +GCC_VERSION_MAJOR := $(shell echo $(GCC_VERSION) | cut -d. -f1) +GCC_VERSION_MINOR := $(shell echo $(GCC_VERSION) | cut -d. -f2) +GCC_VERSION_NUM := $(shell echo $$(($(GCC_VERSION_MAJOR) * 100 + $(GCC_VERSION_MINOR)))) +ifeq ($(shell test $(GCC_VERSION_NUM) -lt 1403 && echo yes),yes) +$(error Error: GCC $(GCC_VERSION) has known issues with Cortex-M55; upgrade to GCC 14.3+ for proper CM55 support) +endif +endif From da3709a73810d054bb93baf8509dcc5d246962b1 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 5 Jul 2025 18:50:13 +0100 Subject: [PATCH 0901/2098] unix/coverage: Add missing MP_OBJ_FROM_PTR casts. An attempt to build the coverage module into the nanbox binary failed, but pointed out that these sites needed explicit conversion from pointer to object. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 33e4208d924..cdab17cacfa 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -505,7 +505,7 @@ static mp_obj_t extra_coverage(void) { mp_call_function_2_protected(MP_OBJ_FROM_PTR(&mp_builtin_divmod_obj), mp_obj_new_str_from_cstr("abc"), mp_obj_new_str_from_cstr("abc")); // mp_obj_int_get_checked with mp_obj_int_t that has a value that is a small integer - mp_printf(&mp_plat_print, "%d\n", mp_obj_int_get_checked(mp_obj_int_new_mpz())); + mp_printf(&mp_plat_print, "%d\n", mp_obj_int_get_checked(MP_OBJ_FROM_PTR(mp_obj_int_new_mpz()))); // mp_obj_int_get_uint_checked with non-negative small-int mp_printf(&mp_plat_print, "%d\n", (int)mp_obj_int_get_uint_checked(MP_OBJ_NEW_SMALL_INT(1))); @@ -844,7 +844,7 @@ static mp_obj_t extra_coverage(void) { mp_obj_streamtest_t *s2 = mp_obj_malloc(mp_obj_streamtest_t, &mp_type_stest_textio2); // return a tuple of data for testing on the Python side - mp_obj_t items[] = {(mp_obj_t)&str_no_hash_obj, (mp_obj_t)&bytes_no_hash_obj, MP_OBJ_FROM_PTR(s), MP_OBJ_FROM_PTR(s2)}; + mp_obj_t items[] = {MP_OBJ_FROM_PTR(&str_no_hash_obj), MP_OBJ_FROM_PTR(&bytes_no_hash_obj), MP_OBJ_FROM_PTR(s), MP_OBJ_FROM_PTR(s2)}; return mp_obj_new_tuple(MP_ARRAY_SIZE(items), items); } MP_DEFINE_CONST_FUN_OBJ_0(extra_coverage_obj, extra_coverage); From 05342b013d251b3e2ae84432aa541a2588e39925 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:42:01 +1000 Subject: [PATCH 0902/2098] tools/mpremote: Support OSError's on targets without errno. Targets without the `errno` module enabled will not render `OSError`s with the name of the error. Instead they just print the numeric error code. Add support for such targets by explicitly recognising certain error codes. Signed-off-by: Damien George --- tools/mpremote/mpremote/mp_errno.py | 53 ++++++++++++++++++++++++++++ tools/mpremote/mpremote/transport.py | 13 ++++++- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tools/mpremote/mpremote/mp_errno.py diff --git a/tools/mpremote/mpremote/mp_errno.py b/tools/mpremote/mpremote/mp_errno.py new file mode 100644 index 00000000000..37cb1e0cb9b --- /dev/null +++ b/tools/mpremote/mpremote/mp_errno.py @@ -0,0 +1,53 @@ +import errno + +# This table maps numeric values defined by `py/mperrno.h` to host errno code. +MP_ERRNO_TABLE = { + 1: errno.EPERM, + 2: errno.ENOENT, + 3: errno.ESRCH, + 4: errno.EINTR, + 5: errno.EIO, + 6: errno.ENXIO, + 7: errno.E2BIG, + 8: errno.ENOEXEC, + 9: errno.EBADF, + 10: errno.ECHILD, + 11: errno.EAGAIN, + 12: errno.ENOMEM, + 13: errno.EACCES, + 14: errno.EFAULT, + 15: errno.ENOTBLK, + 16: errno.EBUSY, + 17: errno.EEXIST, + 18: errno.EXDEV, + 19: errno.ENODEV, + 20: errno.ENOTDIR, + 21: errno.EISDIR, + 22: errno.EINVAL, + 23: errno.ENFILE, + 24: errno.EMFILE, + 25: errno.ENOTTY, + 26: errno.ETXTBSY, + 27: errno.EFBIG, + 28: errno.ENOSPC, + 29: errno.ESPIPE, + 30: errno.EROFS, + 31: errno.EMLINK, + 32: errno.EPIPE, + 33: errno.EDOM, + 34: errno.ERANGE, + 95: errno.EOPNOTSUPP, + 97: errno.EAFNOSUPPORT, + 98: errno.EADDRINUSE, + 103: errno.ECONNABORTED, + 104: errno.ECONNRESET, + 105: errno.ENOBUFS, + 106: errno.EISCONN, + 107: errno.ENOTCONN, + 110: errno.ETIMEDOUT, + 111: errno.ECONNREFUSED, + 113: errno.EHOSTUNREACH, + 114: errno.EALREADY, + 115: errno.EINPROGRESS, + 125: errno.ECANCELED, +} diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index 1b70f9b2edc..d7568b281b1 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -24,8 +24,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import ast, errno, hashlib, os, sys +import ast, errno, hashlib, os, re, sys from collections import namedtuple +from .mp_errno import MP_ERRNO_TABLE def stdout_write_bytes(b): @@ -62,6 +63,16 @@ def _convert_filesystem_error(e, info): ]: if estr in e.error_output: return OSError(code, info) + + # Some targets don't render OSError with the name of the errno, so in these + # cases support an explicit mapping of errnos to known numeric codes. + error_lines = e.error_output.splitlines() + match = re.match(r"OSError: (\d+)$", error_lines[-1]) + if match: + value = int(match.group(1), 10) + if value in MP_ERRNO_TABLE: + return OSError(MP_ERRNO_TABLE[value], info) + return e From 00fe312f83a9fd5816f8034c7f7ee591325d15d9 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Thu, 26 Jun 2025 12:41:01 -0400 Subject: [PATCH 0903/2098] py/bc: Factor out helper for line-number decoding. Signed-off-by: Anson Mansfield --- py/bc.h | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/py/bc.h b/py/bc.h index 718ba4a6846..f24510ea7ec 100644 --- a/py/bc.h +++ b/py/bc.h @@ -308,25 +308,35 @@ static inline void mp_module_context_alloc_tables(mp_module_context_t *context, #endif } +typedef struct _mp_code_lineinfo_t { + size_t bc_increment; + size_t line_increment; +} mp_code_lineinfo_t; + +static inline mp_code_lineinfo_t mp_bytecode_decode_lineinfo(const byte **line_info) { + mp_code_lineinfo_t result; + size_t c = (*line_info)[0]; + if ((c & 0x80) == 0) { + // 0b0LLBBBBB encoding + result.bc_increment = c & 0x1f; + result.line_increment = c >> 5; + *line_info += 1; + } else { + // 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) + result.bc_increment = c & 0xf; + result.line_increment = ((c << 4) & 0x700) | (*line_info)[1]; + *line_info += 2; + } + return result; +} + static inline size_t mp_bytecode_get_source_line(const byte *line_info, const byte *line_info_top, size_t bc_offset) { size_t source_line = 1; while (line_info < line_info_top) { - size_t c = *line_info; - size_t b, l; - if ((c & 0x80) == 0) { - // 0b0LLBBBBB encoding - b = c & 0x1f; - l = c >> 5; - line_info += 1; - } else { - // 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) - b = c & 0xf; - l = ((c << 4) & 0x700) | line_info[1]; - line_info += 2; - } - if (bc_offset >= b) { - bc_offset -= b; - source_line += l; + mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&line_info); + if (bc_offset >= decoded.bc_increment) { + bc_offset -= decoded.bc_increment; + source_line += decoded.line_increment; } else { // found source line corresponding to bytecode offset break; From 0732c45683fe7fabe61e4ef4eb95011817966372 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Thu, 26 Jun 2025 12:41:44 -0400 Subject: [PATCH 0904/2098] py/showbc: Use line-number decoding helper. Signed-off-by: Anson Mansfield --- py/showbc.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/py/showbc.c b/py/showbc.c index 6913d18c1ca..792fccd0133 100644 --- a/py/showbc.c +++ b/py/showbc.c @@ -144,17 +144,9 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t mp_uint_t source_line = 1; mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); for (const byte *ci = code_info; ci < line_info_top;) { - if ((ci[0] & 0x80) == 0) { - // 0b0LLBBBBB encoding - bc += ci[0] & 0x1f; - source_line += ci[0] >> 5; - ci += 1; - } else { - // 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) - bc += ci[0] & 0xf; - source_line += ((ci[0] << 4) & 0x700) | ci[1]; - ci += 2; - } + mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&ci); + bc += decoded.bc_increment; + source_line += decoded.line_increment; mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); } } From d6b62a28fe77962e1c0d6fcf4ad0d5d47dda0e6b Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 21 Mar 2025 13:05:16 -0400 Subject: [PATCH 0905/2098] tests/basics/fun_code_full: Test code objects with full feature set. Signed-off-by: Anson Mansfield --- tests/basics/fun_code_full.py | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/basics/fun_code_full.py diff --git a/tests/basics/fun_code_full.py b/tests/basics/fun_code_full.py new file mode 100644 index 00000000000..5eb23150df0 --- /dev/null +++ b/tests/basics/fun_code_full.py @@ -0,0 +1,47 @@ +# Test function.__code__ attributes not available with MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC + +try: + (lambda: 0).__code__.co_code +except AttributeError: + print("SKIP") + raise SystemExit + +try: + import warnings + warnings.simplefilter("ignore") # ignore deprecation warning about co_lnotab +except ImportError: + pass + +def f(x, y): + a = x + y + b = x - y + return a * b + +code = f.__code__ + +print(type(code.co_code)) # both bytes (but mpy and cpy have different instruction sets) +print(code.co_consts) # (not necessarily the same set, but in this function they are) +print(code.co_filename.rsplit('/')[-1]) # same terminal filename but might be different paths on other ports +print(type(code.co_firstlineno)) # both ints (but mpy points to first line inside, cpy points to declaration) +print(code.co_name) +print(iter(code.co_names) is not None) # both iterable (but mpy returns dict with names as keys, cpy only the names; and not necessarily the same set) +print(type(code.co_lnotab)) # both bytes + +co_lines = code.co_lines() + +l = list(co_lines) +first_start = l[0][0] +last_end = l[-1][1] +print(first_start) # co_lines should start at the start of the bytecode +print(len(code.co_code) - last_end) # and end at the end of the bytecode + +prev_end = 0 +for start, end, line_no in l: + if end != prev_end: + print("non-contiguous") + break # the offset ranges should be contiguous + prev_end = end +else: + print("contiguous") + + From 4b6d1085d12698f55bc47a9b58e7004def57991a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 1 Jul 2025 12:47:23 -0400 Subject: [PATCH 0906/2098] tests/basics/fun_code_colines: Test decoded co_lines values. Signed-off-by: Anson Mansfield --- tests/basics/fun_code_colines.py | 81 ++++++++++++++++++++++++++++ tests/basics/fun_code_colines.py.exp | 20 +++++++ 2 files changed, 101 insertions(+) create mode 100644 tests/basics/fun_code_colines.py create mode 100644 tests/basics/fun_code_colines.py.exp diff --git a/tests/basics/fun_code_colines.py b/tests/basics/fun_code_colines.py new file mode 100644 index 00000000000..a8867770edd --- /dev/null +++ b/tests/basics/fun_code_colines.py @@ -0,0 +1,81 @@ +# Check that we have sensical bytecode offsets in function.__code__.co_lines + +def f1(x, y, obj, obj2, obj3): + a = x + y # line 4: bc+4 line+4 + b = x - y # line 5: bc+4 line+1 + # line 6 + # line 7 + # line 8 + # line 9 + # line 10 + # line 11 + # line 12 + # line 13 + # line 14 + # line 15 + # line 16 + # line 17 + # line 18 + # line 19 + c = a * b # line 20: bc+4 line+15 + obj.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 21: bc+31 line+1; bc+27 line+0 + # line 22 + # line 23 + # line 24: bc+0 line+3 + # line 25 + # line 26 + # line 27: bc+0 line+3 + # line 28 + # line 29 + obj2.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 30: bc+31 line+3; bc+27 line+0 + # line 31 + # line 32 + # line 33: bc+0 line+3 + # line 34 + # line 35 + # line 36 + # line 37 + # line 38 + # line 39 + # line 40 + # line 41 + # line 42 + # line 43 + # line 44 + # line 45 + # line 46 + # line 47 + # line 48 + # line 49 + # line 50 + # line 51 + # line 52 + # line 53 + # line 54 + # line 55 + # line 56 + # line 57 + # line 58 + # line 59 + return obj3.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 60: bc+31 line+27; bc+27 line+0 + +def f2(x, y): + a = x + y # line 63 + b = x - y # line 64 + return a * b # line 65 + +try: + f1.__code__.co_lines +except AttributeError: + print("SKIP") + raise SystemExit + +print("f1") +for start, end, line_no in f1.__code__.co_lines(): + print("line {} start: {}".format(line_no, start)) + print("line {} end: {}".format(line_no, end)) + +print("f2") +for start, end, line_no in f2.__code__.co_lines(): + print("line {} start: {}".format(line_no, start)) + print("line {} end: {}".format(line_no, end)) diff --git a/tests/basics/fun_code_colines.py.exp b/tests/basics/fun_code_colines.py.exp new file mode 100644 index 00000000000..19bd4ef6e2a --- /dev/null +++ b/tests/basics/fun_code_colines.py.exp @@ -0,0 +1,20 @@ +f1 +line 4 start: 0 +line 4 end: 4 +line 5 start: 4 +line 5 end: 8 +line 20 start: 8 +line 20 end: 12 +line 21 start: 12 +line 21 end: 70 +line 30 start: 70 +line 30 end: 128 +line 60 start: 128 +line 60 end: 186 +f2 +line 63 start: 0 +line 63 end: 4 +line 64 start: 4 +line 64 end: 8 +line 65 start: 8 +line 65 end: 12 From 49159ef6b7e283681cb1c2dfe44cdf14bd397467 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 21 Mar 2025 12:44:34 -0400 Subject: [PATCH 0907/2098] py/objcode: Implement co_lines method. Signed-off-by: Anson Mansfield --- py/objcode.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/py/objcode.c b/py/objcode.c index 9b98a696798..52df84d012b 100644 --- a/py/objcode.c +++ b/py/objcode.c @@ -107,6 +107,67 @@ static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { return o; } +static mp_obj_t code_colines_iter(mp_obj_t); +static mp_obj_t code_colines_next(mp_obj_t); +typedef struct _mp_obj_colines_iter_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + const mp_raw_code_t *rc; + mp_uint_t bc; + mp_uint_t source_line; + const byte *ci; +} mp_obj_colines_iter_t; + +static mp_obj_t code_colines_iter(mp_obj_t self_in) { + mp_obj_code_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_colines_iter_t *iter = mp_obj_malloc(mp_obj_colines_iter_t, &mp_type_polymorph_iter); + iter->iternext = code_colines_next; + iter->rc = self->rc; + iter->bc = 0; + iter->source_line = 1; + iter->ci = self->rc->prelude.line_info; + return MP_OBJ_FROM_PTR(iter); +} +static MP_DEFINE_CONST_FUN_OBJ_1(code_colines_obj, code_colines_iter); + +static mp_obj_t code_colines_next(mp_obj_t iter_in) { + mp_obj_colines_iter_t *iter = MP_OBJ_TO_PTR(iter_in); + const byte *ci_end = iter->rc->prelude.line_info_top; + + mp_uint_t start = iter->bc; + mp_uint_t line_no = iter->source_line; + bool another = true; + + while (another && iter->ci < ci_end) { + another = false; + mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&iter->ci); + iter->bc += decoded.bc_increment; + iter->source_line += decoded.line_increment; + + if (decoded.bc_increment == 0) { + line_no = iter->source_line; + another = true; + } else if (decoded.line_increment == 0) { + another = true; + } + } + + if (another) { + mp_uint_t prelude_size = (iter->rc->prelude.opcodes - (const byte *)iter->rc->fun_data); + mp_uint_t bc_end = iter->rc->fun_data_len - prelude_size; + if (iter->bc >= bc_end) { + return MP_OBJ_STOP_ITERATION; + } else { + iter->bc = bc_end; + } + } + + mp_uint_t end = iter->bc; + mp_obj_t next[3] = {MP_OBJ_NEW_SMALL_INT(start), MP_OBJ_NEW_SMALL_INT(end), MP_OBJ_NEW_SMALL_INT(line_no)}; + + return mp_obj_new_tuple(MP_ARRAY_SIZE(next), next); +} + static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // not load attribute @@ -143,6 +204,10 @@ static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } dest[0] = o->lnotab; break; + case MP_QSTR_co_lines: + dest[0] = MP_OBJ_FROM_PTR(&code_colines_obj); + dest[1] = self_in; + break; } } From c4a88f2ce7da87d5f635ec25edba481917020fd8 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Mon, 30 Jun 2025 23:28:20 +0200 Subject: [PATCH 0908/2098] py/obj: Add functions to retrieve large integers from mp_obj_t. This commit provides helpers to retrieve integer values from mp_obj_t when the content does not fit in a 32 bits integer, without risking an implicit wrap due to an int overflow. Signed-off-by: Yoctopuce dev --- ports/unix/coverage.c | 36 ++++++++++++++++++++++++++ py/obj.c | 30 +++++++++++++++++++++ py/obj.h | 2 ++ py/objint_longlong.c | 16 ++++++++++++ tests/ports/unix/extra_coverage.py | 10 +++++++ tests/ports/unix/extra_coverage.py.exp | 8 ++++++ 6 files changed, 102 insertions(+) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index cdab17cacfa..0df6bf279ae 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -488,6 +488,26 @@ static mp_obj_t extra_coverage(void) { // mpz_set_from_float with 0 as argument mpz_set_from_float(&mpz, 0); mp_printf(&mp_plat_print, "%f\n", mpz_as_float(&mpz)); + + // convert a large integer value (stored in a mpz) to mp_uint_t and to ll; + mp_obj_t obj_bigint = mp_obj_new_int_from_uint((mp_uint_t)0xdeadbeef); + mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + obj_bigint = mp_obj_new_int_from_ll(0xc0ffee777c0ffeell); + long long value_ll = mp_obj_get_ll(obj_bigint); + mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); + + // convert a large integer value (stored via a struct object) to uint and to ll + // `deadbeef` global is an uctypes.struct defined by extra_coverage.py + obj_bigint = mp_load_global(MP_QSTR_deadbeef); + mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + value_ll = mp_obj_get_ll(obj_bigint); + mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); + + // convert a smaller integer value to mp_uint_t and to ll + obj_bigint = mp_obj_new_int_from_uint(0xc0ffee); + mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + value_ll = mp_obj_get_ll(obj_bigint); + mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); } // runtime utils @@ -530,6 +550,22 @@ static mp_obj_t extra_coverage(void) { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } + // mp_obj_get_uint from a non-int object (should raise exception) + if (nlr_push(&nlr) == 0) { + mp_obj_get_uint(mp_const_none); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + // mp_obj_int_get_ll from a non-int object (should raise exception) + if (nlr_push(&nlr) == 0) { + mp_obj_get_ll(mp_const_none); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + // call mp_obj_new_exception_args (it's a part of the public C API and not used in the core) mp_obj_print_exception(&mp_plat_print, mp_obj_new_exception_args(&mp_type_ValueError, 0, NULL)); } diff --git a/py/obj.c b/py/obj.c index 1606ad5209e..58675946076 100644 --- a/py/obj.c +++ b/py/obj.c @@ -314,6 +314,36 @@ mp_int_t mp_obj_get_int(mp_const_obj_t arg) { return val; } +#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE +mp_uint_t mp_obj_get_uint(mp_const_obj_t arg) { + if (!mp_obj_is_exact_type(arg, &mp_type_int)) { + mp_obj_t as_int = mp_unary_op(MP_UNARY_OP_INT_MAYBE, (mp_obj_t)arg); + if (as_int == MP_OBJ_NULL) { + mp_raise_TypeError_int_conversion(arg); + } + arg = as_int; + } + return mp_obj_int_get_uint_checked(arg); +} + +long long mp_obj_get_ll(mp_const_obj_t arg) { + if (!mp_obj_is_exact_type(arg, &mp_type_int)) { + mp_obj_t as_int = mp_unary_op(MP_UNARY_OP_INT_MAYBE, (mp_obj_t)arg); + if (as_int == MP_OBJ_NULL) { + mp_raise_TypeError_int_conversion(arg); + } + arg = as_int; + } + if (mp_obj_is_small_int(arg)) { + return MP_OBJ_SMALL_INT_VALUE(arg); + } else { + long long res; + mp_obj_int_to_bytes_impl((mp_obj_t)arg, MP_ENDIANNESS_BIG, sizeof(res), (byte *)&res); + return res; + } +} +#endif + mp_int_t mp_obj_get_int_truncated(mp_const_obj_t arg) { if (mp_obj_is_int(arg)) { return mp_obj_int_get_truncated(arg); diff --git a/py/obj.h b/py/obj.h index 0f87282a9f4..a1df661ff09 100644 --- a/py/obj.h +++ b/py/obj.h @@ -1051,6 +1051,8 @@ static inline bool mp_obj_is_integer(mp_const_obj_t o) { } mp_int_t mp_obj_get_int(mp_const_obj_t arg); +mp_uint_t mp_obj_get_uint(mp_const_obj_t arg); +long long mp_obj_get_ll(mp_const_obj_t arg); mp_int_t mp_obj_get_int_truncated(mp_const_obj_t arg); bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 1940b815386..5b60eb65ad8 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -295,6 +295,22 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return mp_obj_int_get_truncated(self_in); } +mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in) { + if (mp_obj_is_small_int(self_in)) { + if (MP_OBJ_SMALL_INT_VALUE(self_in) >= 0) { + return MP_OBJ_SMALL_INT_VALUE(self_in); + } + } else { + const mp_obj_int_t *self = self_in; + long long value = self->val; + mp_uint_t truncated = (mp_uint_t)value; + if (value >= 0 && (long long)truncated == value) { + return truncated; + } + } + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); +} + #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) { assert(mp_obj_is_exact_type(self_in, &mp_type_int)); diff --git a/tests/ports/unix/extra_coverage.py b/tests/ports/unix/extra_coverage.py index ec68a555081..72f5fe56b3a 100644 --- a/tests/ports/unix/extra_coverage.py +++ b/tests/ports/unix/extra_coverage.py @@ -6,6 +6,16 @@ import errno import io +import uctypes + +# create an int-like variable used for coverage of `mp_obj_get_ll` +buf = bytearray(b"\xde\xad\xbe\xef") +struct = uctypes.struct( + uctypes.addressof(buf), + {"f32": uctypes.UINT32 | 0}, + uctypes.BIG_ENDIAN, +) +deadbeef = struct.f32 data = extra_coverage() diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index ed21ced2425..00658ab3adc 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -94,6 +94,12 @@ data 1 0 0.000000 +deadbeef +c0ffee777c0ffee +deadbeef +0deadbeef +c0ffee +000c0ffee # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' @@ -102,6 +108,8 @@ TypeError: unsupported types for __divmod__: 'str', 'str' 2 OverflowError: overflow converting long int to machine word OverflowError: overflow converting long int to machine word +TypeError: can't convert NoneType to int +TypeError: can't convert NoneType to int ValueError: Warning: test # format float From df05caea6c6437a8b4756ec502a5e6210f4b6256 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Tue, 1 Jul 2025 13:16:20 +0200 Subject: [PATCH 0909/2098] shared/timeutils: Standardize supported date range on all platforms. This is code makes sure that time functions work properly on a reasonable date range, on all platforms, regardless of the epoch. The suggested minimum range is 1970 to 2099. In order to reduce code footprint, code to support far away dates is only enabled specified by the port. New types are defined to identify timestamps. The implementation with the smallest code footprint is when support timerange is limited to 1970-2099 and Epoch is 1970. This makes it possible to use 32 bit unsigned integers for all timestamps. On ARM4F, adding support for dates up to year 3000 adds 460 bytes of code. Supporting dates back to 1600 adds another 44 bytes of code. Signed-off-by: Yoctopuce dev --- extmod/modtime.c | 4 +- extmod/vfs_fat.c | 8 +-- extmod/vfs_lfsx.c | 8 +-- ports/cc3200/mods/modtime.c | 2 +- ports/esp32/modtime.c | 2 +- ports/esp8266/modtime.c | 4 +- ports/mimxrt/modtime.c | 9 +-- ports/renesas-ra/modtime.c | 2 +- ports/stm32/modtime.c | 2 +- ports/stm32/mpconfigport.h | 1 + ports/unix/Makefile | 4 ++ ports/unix/modtime.c | 10 +-- ports/unix/mpconfigport.h | 3 + ports/windows/Makefile | 4 ++ py/mpconfig.h | 58 +++++++++++++++ shared/timeutils/timeutils.c | 134 ++++++++++++++++++++++------------- shared/timeutils/timeutils.h | 118 +++++++++++++++++++++++------- tests/extmod/time_mktime.py | 120 +++++++++++++++++++++++++++++++ 18 files changed, 387 insertions(+), 106 deletions(-) create mode 100644 tests/extmod/time_mktime.py diff --git a/extmod/modtime.c b/extmod/modtime.c index deb4bb4c9ac..999b81230bc 100644 --- a/extmod/modtime.c +++ b/extmod/modtime.c @@ -58,7 +58,7 @@ static mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { return mp_time_localtime_get(); } else { // Convert given seconds to tuple. - mp_int_t seconds = mp_obj_get_int(args[0]); + mp_timestamp_t seconds = timeutils_obj_get_timestamp(args[0]); timeutils_struct_time_t tm; timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); mp_obj_t tuple[8] = { @@ -90,7 +90,7 @@ static mp_obj_t time_mktime(mp_obj_t tuple) { mp_raise_TypeError(MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9")); } - return mp_obj_new_int_from_uint(timeutils_mktime(mp_obj_get_int(elem[0]), + return timeutils_obj_from_timestamp(timeutils_mktime(mp_obj_get_int(elem[0]), mp_obj_get_int(elem[1]), mp_obj_get_int(elem[2]), mp_obj_get_int(elem[3]), mp_obj_get_int(elem[4]), mp_obj_get_int(elem[5]))); } diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index ee1169b8c31..e832992f46f 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -326,7 +326,7 @@ static mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { } else { mode |= MP_S_IFREG; } - mp_int_t seconds = timeutils_seconds_since_epoch( + mp_timestamp_t seconds = timeutils_seconds_since_epoch( 1980 + ((fno.fdate >> 9) & 0x7f), (fno.fdate >> 5) & 0x0f, fno.fdate & 0x1f, @@ -341,9 +341,9 @@ static mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(fno.fsize); // st_size - t->items[7] = mp_obj_new_int_from_uint(seconds); // st_atime - t->items[8] = mp_obj_new_int_from_uint(seconds); // st_mtime - t->items[9] = mp_obj_new_int_from_uint(seconds); // st_ctime + t->items[7] = timeutils_obj_from_timestamp(seconds); // st_atime + t->items[8] = timeutils_obj_from_timestamp(seconds); // st_mtime + t->items[9] = timeutils_obj_from_timestamp(seconds); // st_ctime return MP_OBJ_FROM_PTR(t); } diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 404eab84f47..372037784b4 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -378,7 +378,7 @@ static mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { mp_raise_OSError(-ret); } - mp_uint_t mtime = 0; + mp_timestamp_t mtime = 0; #if LFS_BUILD_VERSION == 2 uint8_t mtime_buf[8]; lfs2_ssize_t sz = lfs2_getattr(&self->lfs, path, LFS_ATTR_MTIME, &mtime_buf, sizeof(mtime_buf)); @@ -400,9 +400,9 @@ static mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size - t->items[7] = mp_obj_new_int_from_uint(mtime); // st_atime - t->items[8] = mp_obj_new_int_from_uint(mtime); // st_mtime - t->items[9] = mp_obj_new_int_from_uint(mtime); // st_ctime + t->items[7] = timeutils_obj_from_timestamp(mtime); // st_atime + t->items[8] = timeutils_obj_from_timestamp(mtime); // st_mtime + t->items[9] = timeutils_obj_from_timestamp(mtime); // st_ctime return MP_OBJ_FROM_PTR(t); } diff --git a/ports/cc3200/mods/modtime.c b/ports/cc3200/mods/modtime.c index 21388568ab8..254678fb2dd 100644 --- a/ports/cc3200/mods/modtime.c +++ b/ports/cc3200/mods/modtime.c @@ -50,5 +50,5 @@ static mp_obj_t mp_time_localtime_get(void) { // Returns the number of seconds, as an integer, since the Epoch. static mp_obj_t mp_time_time_get(void) { - return mp_obj_new_int(pyb_rtc_get_seconds()); + return timeutils_obj_from_timestamp(pyb_rtc_get_seconds()); } diff --git a/ports/esp32/modtime.c b/ports/esp32/modtime.c index 4695dd23e7f..991f2cf5787 100644 --- a/ports/esp32/modtime.c +++ b/ports/esp32/modtime.c @@ -54,5 +54,5 @@ static mp_obj_t mp_time_localtime_get(void) { static mp_obj_t mp_time_time_get(void) { struct timeval tv; gettimeofday(&tv, NULL); - return mp_obj_new_int(tv.tv_sec); + return timeutils_obj_from_timestamp(tv.tv_sec); } diff --git a/ports/esp8266/modtime.c b/ports/esp8266/modtime.c index 09030055975..e99d920fde8 100644 --- a/ports/esp8266/modtime.c +++ b/ports/esp8266/modtime.c @@ -31,7 +31,7 @@ // Return the localtime as an 8-tuple. static mp_obj_t mp_time_localtime_get(void) { - mp_int_t seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000; + mp_uint_t seconds = pyb_rtc_get_us_since_epoch() / 1000u / 1000u; timeutils_struct_time_t tm; timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); mp_obj_t tuple[8] = { @@ -50,5 +50,5 @@ static mp_obj_t mp_time_localtime_get(void) { // Returns the number of seconds, as an integer, since the Epoch. static mp_obj_t mp_time_time_get(void) { // get date and time - return mp_obj_new_int(pyb_rtc_get_us_since_epoch() / 1000 / 1000); + return timeutils_obj_from_timestamp(pyb_rtc_get_us_since_epoch() / 1000 / 1000); } diff --git a/ports/mimxrt/modtime.c b/ports/mimxrt/modtime.c index a2ccf1b273f..3bcfac411c0 100644 --- a/ports/mimxrt/modtime.c +++ b/ports/mimxrt/modtime.c @@ -51,14 +51,7 @@ static mp_obj_t mp_time_localtime_get(void) { static mp_obj_t mp_time_time_get(void) { snvs_lp_srtc_datetime_t t; SNVS_LP_SRTC_GetDatetime(SNVS, &t); - // EPOCH is 1970 for this port, which leads to the following trouble: - // timeutils_seconds_since_epoch() calls timeutils_seconds_since_2000(), and - // timeutils_seconds_since_2000() subtracts 2000 from year, but uses - // an unsigned number for seconds, That causes an underrun, which is not - // fixed by adding the TIMEUTILS_SECONDS_1970_TO_2000. - // Masking it to 32 bit for year < 2000 fixes it. - return mp_obj_new_int_from_ull( + return timeutils_obj_from_timestamp( timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.minute, t.second) - & (t.year < 2000 ? 0xffffffff : 0xffffffffffff) ); } diff --git a/ports/renesas-ra/modtime.c b/ports/renesas-ra/modtime.c index cbd639721fc..e1358f82bc5 100644 --- a/ports/renesas-ra/modtime.c +++ b/ports/renesas-ra/modtime.c @@ -53,5 +53,5 @@ static mp_obj_t mp_time_time_get(void) { rtc_init_finalise(); ra_rtc_t time; ra_rtc_get_time(&time); - return mp_obj_new_int(timeutils_seconds_since_epoch(time.year, time.month, time.date, time.hour, time.minute, time.second)); + return timeutils_obj_from_timestamp(timeutils_seconds_since_epoch(time.year, time.month, time.date, time.hour, time.minute, time.second)); } diff --git a/ports/stm32/modtime.c b/ports/stm32/modtime.c index ff1495a5d96..87a4536b043 100644 --- a/ports/stm32/modtime.c +++ b/ports/stm32/modtime.c @@ -59,5 +59,5 @@ static mp_obj_t mp_time_time_get(void) { RTC_TimeTypeDef time; HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); - return mp_obj_new_int(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); + return timeutils_obj_from_timestamp(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); } diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 41ed3d08b7b..b910188c5af 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -76,6 +76,7 @@ #ifndef MICROPY_FLOAT_IMPL // can be configured by each board via mpconfigboard.mk #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #endif +#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (1) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_SCHEDULER_DEPTH (8) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 3c54d156c31..8bd58a25426 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -52,6 +52,10 @@ CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EX # This option has no effect on 64-bit builds. CFLAGS += -D_FILE_OFFSET_BITS=64 +# Force the use of 64-bits for time_t in C library functions on 32-bit platforms. +# This option has no effect on 64-bit builds. +CFLAGS += -D_TIME_BITS=64 + # Debugging/Optimization ifdef DEBUG COPT ?= -Og diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index fbd94b5ecd1..41b7c89df42 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -34,6 +34,7 @@ #include "py/mphal.h" #include "py/runtime.h" +#include "shared/timeutils/timeutils.h" #ifdef _WIN32 static inline int msec_sleep_tv(struct timeval *tv) { @@ -130,12 +131,7 @@ static mp_obj_t mod_time_gm_local_time(size_t n_args, const mp_obj_t *args, stru if (n_args == 0) { t = time(NULL); } else { - #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE - mp_float_t val = mp_obj_get_float(args[0]); - t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val); - #else - t = mp_obj_get_int(args[0]); - #endif + t = (time_t)timeutils_obj_get_timestamp(args[0]); } struct tm *tm = time_func(&t); @@ -196,7 +192,7 @@ static mp_obj_t mod_time_mktime(mp_obj_t tuple) { if (ret == -1) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("invalid mktime usage")); } - return mp_obj_new_int(ret); + return timeutils_obj_from_timestamp(ret); } MP_DEFINE_CONST_FUN_OBJ_1(mod_time_mktime_obj, mod_time_mktime); diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 21ce75a351e..973b5e74ce1 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -124,6 +124,9 @@ typedef long mp_off_t; // VFS stat functions should return time values relative to 1970/1/1 #define MICROPY_EPOCH_IS_1970 (1) +// port modtime functions use time_t +#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_TIME_T) + // Assume that select() call, interrupted with a signal, and erroring // with EINTR, updates remaining timeout value. #define MICROPY_SELECT_REMAINING_TIME (1) diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 115d1a61ef5..9eee98cdd45 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -46,6 +46,10 @@ LDFLAGS += -lm -lbcrypt $(LDFLAGS_EXTRA) # This option has no effect on 64-bit builds. CFLAGS += -D_FILE_OFFSET_BITS=64 +# Force the use of 64-bits for time_t in C library functions on 32-bit platforms. +# This option has no effect on 64-bit builds. +CFLAGS += -D_TIME_BITS=64 + # Debugging/Optimization ifdef DEBUG CFLAGS += -g diff --git a/py/mpconfig.h b/py/mpconfig.h index cf0538cae4d..4c127627596 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -893,6 +893,64 @@ typedef double mp_float_t; #define MICROPY_FULL_CHECKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif +// Ports can choose to use timestamps based on 2000-01-01 or 1970-01-01 +// Default is timestamps based on 2000-01-01 +#if !defined(MICROPY_EPOCH_IS_2000) && !defined(MICROPY_EPOCH_IS_1970) +#define MICROPY_EPOCH_IS_2000 (1) +#define MICROPY_EPOCH_IS_1970 (0) +#elif !defined(MICROPY_EPOCH_IS_1970) +#define MICROPY_EPOCH_IS_1970 (1 - (MICROPY_EPOCH_IS_2000)) +#elif !defined(MICROPY_EPOCH_IS_2000) +#define MICROPY_EPOCH_IS_2000 (1 - (MICROPY_EPOCH_IS_1970)) +#endif + +// To maintain reasonable compatibility with CPython on embedded systems, +// and avoid breaking anytime soon, time functions are defined to work +// at least between 1970 and 2099 (included) on any machine. +// +// Specific ports can enable extended date support +// - after 2099 using MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND +// - before 1970 using MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE +// The largest possible range is year 1600 to year 3000 +// +// By default, extended date support is only enabled for machines using 64 bit pointers, +// but it can be enabled by specific ports +#ifndef MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE +#if MP_SSIZE_MAX > 2147483647 +#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (1) +#else +#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (0) +#endif +#endif + +// When support for dates <1970 is enabled, supporting >=2100 does not cost anything +#ifndef MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND +#define MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND (MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE) +#endif + +// The type to be used to represent platform-specific timestamps depends on the choices above +#define MICROPY_TIMESTAMP_IMPL_LONG_LONG (0) +#define MICROPY_TIMESTAMP_IMPL_UINT (1) +#define MICROPY_TIMESTAMP_IMPL_TIME_T (2) + +#ifndef MICROPY_TIMESTAMP_IMPL +#if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE || MICROPY_EPOCH_IS_2000 +#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_LONG_LONG) +#else +#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_UINT) +#endif +#endif + +// `mp_timestamp_t` is the type that should be used by the port +// to represent timestamps, and is referenced to the platform epoch +#if MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_LONG_LONG +typedef long long mp_timestamp_t; +#elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT +typedef mp_uint_t mp_timestamp_t; +#elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_TIME_T +typedef time_t mp_timestamp_t; +#endif + // Whether POSIX-semantics non-blocking streams are supported #ifndef MICROPY_STREAMS_NON_BLOCK #define MICROPY_STREAMS_NON_BLOCK (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) diff --git a/shared/timeutils/timeutils.c b/shared/timeutils/timeutils.c index 4282a0178dd..0c6916e06dd 100644 --- a/shared/timeutils/timeutils.c +++ b/shared/timeutils/timeutils.c @@ -29,12 +29,27 @@ #include "shared/timeutils/timeutils.h" -// LEAPOCH corresponds to 2000-03-01, which is a mod-400 year, immediately -// after Feb 29. We calculate seconds as a signed integer relative to that. +// To maintain reasonable compatibility with CPython on embedded systems, +// and avoid breaking anytime soon, timeutils functions are required to +// work properly between 1970 and 2099 on all ports. // -// Our timebase is relative to 2000-01-01. - -#define LEAPOCH ((31 + 29) * 86400) +// During that period of time, leap years occur every 4 years without +// exception, so we can keep the code short for 32 bit machines. + +// The last leap day before the required period is Feb 29, 1968. +// This is the number of days to add to get to that date. +#define PREV_LEAP_DAY ((mp_uint_t)(365 + 366 - (31 + 29))) +#define PREV_LEAP_YEAR 1968 + +// On ports where either MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND or +// MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE is enabled, we include extra +// code to support leap years outside of the 'easy' period. +// Computation is then made based on 1600 (a mod-400 year). +// This is the number of days between 1600 and 1968. +#define QC_BASE_DAY 134409 +#define QC_LEAP_YEAR 1600 +// This is the number of leap days between 1600 and 1970 +#define QC_LEAP_DAYS 89 #define DAYS_PER_400Y (365 * 400 + 97) #define DAYS_PER_100Y (365 * 100 + 24) @@ -42,8 +57,20 @@ static const uint16_t days_since_jan1[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; +// type used internally to count small integers relative to epoch +// (using uint when possible produces smaller code on some platforms) +#if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE +typedef mp_int_t relint_t; +#else +typedef mp_uint_t relint_t; +#endif + bool timeutils_is_leap_year(mp_uint_t year) { + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + #else + return year % 4 == 0; + #endif } // month is one based @@ -65,67 +92,67 @@ mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date) { return yday; } -void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, timeutils_struct_time_t *tm) { - // The following algorithm was adapted from musl's __secs_to_tm and adapted - // for differences in MicroPython's timebase. - - mp_int_t seconds = t - LEAPOCH; +void timeutils_seconds_since_1970_to_struct_time(timeutils_timestamp_t seconds, timeutils_struct_time_t *tm) { + // The following algorithm was inspired from musl's __secs_to_tm + // and simplified to reduce code footprint in the simple case - mp_int_t days = seconds / 86400; + relint_t days = seconds / 86400; seconds %= 86400; + #if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE if (seconds < 0) { seconds += 86400; days -= 1; } + #endif tm->tm_hour = seconds / 3600; tm->tm_min = seconds / 60 % 60; tm->tm_sec = seconds % 60; - mp_int_t wday = (days + 2) % 7; // Mar 1, 2000 was a Wednesday (2) + relint_t wday = (days + 3) % 7; // Jan 1, 1970 was a Thursday (3) + #if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE if (wday < 0) { wday += 7; } + #endif tm->tm_wday = wday; - mp_int_t qc_cycles = days / DAYS_PER_400Y; + days += PREV_LEAP_DAY; + + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE + // rebase day to the oldest supported date (=> always positive) + mp_uint_t base_year = QC_LEAP_YEAR; + days += QC_BASE_DAY; + mp_uint_t qc_cycles = days / DAYS_PER_400Y; days %= DAYS_PER_400Y; - if (days < 0) { - days += DAYS_PER_400Y; - qc_cycles--; - } - mp_int_t c_cycles = days / DAYS_PER_100Y; + mp_uint_t c_cycles = days / DAYS_PER_100Y; if (c_cycles == 4) { c_cycles--; } days -= (c_cycles * DAYS_PER_100Y); - - mp_int_t q_cycles = days / DAYS_PER_4Y; + #else + mp_uint_t base_year = PREV_LEAP_YEAR; + mp_uint_t qc_cycles = 0; + mp_uint_t c_cycles = 0; + #endif + + mp_uint_t q_cycles = days / DAYS_PER_4Y; + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE if (q_cycles == 25) { q_cycles--; } + #endif days -= q_cycles * DAYS_PER_4Y; - mp_int_t years = days / 365; + relint_t years = days / 365; if (years == 4) { years--; } days -= (years * 365); - /* We will compute tm_yday at the very end - mp_int_t leap = !years && (q_cycles || !c_cycles); - - tm->tm_yday = days + 31 + 28 + leap; - if (tm->tm_yday >= 365 + leap) { - tm->tm_yday -= 365 + leap; - } - - tm->tm_yday++; // Make one based - */ - - tm->tm_year = 2000 + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; + tm->tm_year = base_year + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; // Note: days_in_month[0] corresponds to March - static const int8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29}; + static const uint8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29}; mp_int_t month; for (month = 0; days_in_month[month] <= days; month++) { @@ -144,21 +171,28 @@ void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, timeutils_struct_t } // returns the number of seconds, as an integer, since 2000-01-01 -mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, +timeutils_timestamp_t timeutils_seconds_since_1970(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { - return - second - + minute * 60 - + hour * 3600 - + (timeutils_year_day(year, month, date) - 1 - + ((year - 2000 + 3) / 4) // add a day each 4 years starting with 2001 - - ((year - 2000 + 99) / 100) // subtract a day each 100 years starting with 2001 - + ((year - 2000 + 399) / 400) // add a day each 400 years starting with 2001 - ) * 86400 - + (year - 2000) * 31536000; + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE + mp_uint_t ref_year = QC_LEAP_YEAR; + #else + mp_uint_t ref_year = PREV_LEAP_YEAR; + #endif + timeutils_timestamp_t res; + res = ((relint_t)year - 1970) * 365; + res += (year - (ref_year + 1)) / 4; // add a day each 4 years + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE + res -= (year - (ref_year + 1)) / 100; // subtract a day each 100 years + res += (year - (ref_year + 1)) / 400; // add a day each 400 years + res -= QC_LEAP_DAYS; + #endif + res += timeutils_year_day(year, month, date) - 1; + res *= 86400; + res += hour * 3600 + minute * 60 + second; + return res; } -mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, +timeutils_timestamp_t timeutils_mktime_1970(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { // Normalize the tuple. This allows things like: @@ -211,12 +245,16 @@ mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, year++; } } - return timeutils_seconds_since_2000(year, month, mday, hours, minutes, seconds); + return timeutils_seconds_since_1970(year, month, mday, hours, minutes, seconds); } // Calculate the weekday from the date. // The result is zero based with 0 = Monday. // by Michael Keith and Tom Craver, 1990. int timeutils_calc_weekday(int y, int m, int d) { - return ((d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4 - y / 100 + y / 400) + 6) % 7; + return ((d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4 + #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE + - y / 100 + y / 400 + #endif + ) + 6) % 7; } diff --git a/shared/timeutils/timeutils.h b/shared/timeutils/timeutils.h index 874d16e9764..35356b462aa 100644 --- a/shared/timeutils/timeutils.h +++ b/shared/timeutils/timeutils.h @@ -27,9 +27,23 @@ #ifndef MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H #define MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H +#include "py/obj.h" +#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#include // required for trunc() +#endif + +// `timeutils_timestamp_t` is the type used internally by timeutils to +// represent timestamps, and is always referenced to 1970. +// It may not match the platform-specific `mp_timestamp_t`. +#if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE +typedef long long timeutils_timestamp_t; +#else +typedef mp_uint_t timeutils_timestamp_t; +#endif + // The number of seconds between 1970/1/1 and 2000/1/1 is calculated using: // time.mktime((2000,1,1,0,0,0,0,0,0)) - time.mktime((1970,1,1,0,0,0,0,0,0)) -#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800ULL) +#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800LL) typedef struct _timeutils_struct_time_t { uint16_t tm_year; // i.e. 2014 @@ -45,66 +59,116 @@ typedef struct _timeutils_struct_time_t { bool timeutils_is_leap_year(mp_uint_t year); mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month); mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date); +int timeutils_calc_weekday(int y, int m, int d); -void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, +void timeutils_seconds_since_1970_to_struct_time(timeutils_timestamp_t t, timeutils_struct_time_t *tm); // Year is absolute, month/date are 1-based, hour/minute/second are 0-based. -mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, +timeutils_timestamp_t timeutils_seconds_since_1970(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second); // Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based. -mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, +timeutils_timestamp_t timeutils_mktime_1970(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds); +static inline mp_timestamp_t timeutils_obj_get_timestamp(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE + mp_float_t val = mp_obj_get_float(o_in); + return (mp_timestamp_t)MICROPY_FLOAT_C_FUN(trunc)(val); + #elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT + return mp_obj_get_uint(o_in); + #else + return mp_obj_get_ll(o_in); + #endif +} + +static inline mp_obj_t timeutils_obj_from_timestamp(mp_timestamp_t t) { + #if MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT + return mp_obj_new_int_from_uint(t); + #else + return mp_obj_new_int_from_ll(t); + #endif +} + +static inline void timeutils_seconds_since_2000_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) { + timeutils_seconds_since_1970_to_struct_time((timeutils_timestamp_t)(t + TIMEUTILS_SECONDS_1970_TO_2000), tm); +} + +// Year is absolute, month/date are 1-based, hour/minute/second are 0-based. +static inline mp_timestamp_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t date, + mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { + return (mp_timestamp_t)timeutils_seconds_since_1970(year, month, date, hour, minute, second) - TIMEUTILS_SECONDS_1970_TO_2000; +} + +// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based. +static inline mp_timestamp_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, + mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { + return (mp_timestamp_t)timeutils_mktime_1970(year, month, mday, hours, minutes, seconds) - TIMEUTILS_SECONDS_1970_TO_2000; +} + + // Select the Epoch used by the port. #if MICROPY_EPOCH_IS_1970 -static inline void timeutils_seconds_since_epoch_to_struct_time(uint64_t t, timeutils_struct_time_t *tm) { - // TODO this will give incorrect results for dates before 2000/1/1 - timeutils_seconds_since_2000_to_struct_time((mp_uint_t)(t - TIMEUTILS_SECONDS_1970_TO_2000), tm); +static inline void timeutils_seconds_since_epoch_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) { + timeutils_seconds_since_1970_to_struct_time(t, tm); +} + +// Year is absolute, month/date are 1-based, hour/minute/second are 0-based. +static inline mp_timestamp_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date, + mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { + return timeutils_seconds_since_1970(year, month, date, hour, minute, second); } // Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based. -static inline uint64_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { - return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds) + TIMEUTILS_SECONDS_1970_TO_2000; +static inline mp_timestamp_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, + mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { + return timeutils_mktime_1970(year, month, mday, hours, minutes, seconds); } -// Year is absolute, month/date are 1-based, hour/minute/second are 0-based. -static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, - mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { - // TODO this will give incorrect results for dates before 2000/1/1 - return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000; +static inline mp_timestamp_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(int64_t ns) { + return (mp_timestamp_t)(ns / 1000000000LL); } -static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { - return (mp_uint_t)(ns / 1000000000ULL); +static inline int64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_timestamp_t s) { + return (int64_t)s * 1000000000LL; } -static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) { +static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) { return ns; } #else // Epoch is 2000 -#define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time -#define timeutils_seconds_since_epoch timeutils_seconds_since_2000 -#define timeutils_mktime timeutils_mktime_2000 +static inline void timeutils_seconds_since_epoch_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) { + timeutils_seconds_since_2000_to_struct_time(t, tm); +} + +// Year is absolute, month/date are 1-based, hour/minute/second are 0-based. +static inline mp_timestamp_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date, + mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { + return timeutils_seconds_since_2000(year, month, date, hour, minute, second); +} + +// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based. +static inline mp_timestamp_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, + mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { + return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds); +} -static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) { - return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; +static inline mp_timestamp_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(int64_t ns) { + return (mp_timestamp_t)(ns / 1000000000LL - TIMEUTILS_SECONDS_1970_TO_2000); } -static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { - return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000; +static inline int64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_timestamp_t s) { + return ((int64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000LL; } static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) { - return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000ULL; + return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000LL; } #endif -int timeutils_calc_weekday(int y, int m, int d); - #endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H diff --git a/tests/extmod/time_mktime.py b/tests/extmod/time_mktime.py new file mode 100644 index 00000000000..7fc643dc3cb --- /dev/null +++ b/tests/extmod/time_mktime.py @@ -0,0 +1,120 @@ +# test conversion from date tuple to timestamp and back + +try: + import time + + time.localtime +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# Range of date expected to work on all MicroPython platforms +MIN_YEAR = 1970 +MAX_YEAR = 2099 +# CPython properly supported date range: +# - on Windows: year 1970 to 3000+ +# - on Unix: year 1583 to 3000+ + +# Start test from Jan 1, 2001 13:00 (Feb 2000 might already be broken) +SAFE_DATE = (2001, 1, 1, 13, 0, 0, 0, 0, -1) + + +# mktime function that checks that the result is reversible +def safe_mktime(date_tuple): + try: + res = time.mktime(date_tuple) + chk = time.localtime(res) + except OverflowError: + print("safe_mktime:", date_tuple, "overflow error") + return None + if chk[0:5] != date_tuple[0:5]: + print("safe_mktime:", date_tuple[0:5], " -> ", res, " -> ", chk[0:5]) + return None + return res + + +# localtime function that checks that the result is reversible +def safe_localtime(timestamp): + try: + res = time.localtime(timestamp) + chk = time.mktime(res) + except OverflowError: + print("safe_localtime:", timestamp, "overflow error") + return None + if chk != timestamp: + print("safe_localtime:", timestamp, " -> ", res, " -> ", chk) + return None + return res + + +# look for smallest valid timestamps by iterating backwards on tuple +def test_bwd(date_tuple): + curr_stamp = safe_mktime(date_tuple) + year = date_tuple[0] + month = date_tuple[1] - 1 + if month < 1: + year -= 1 + month = 12 + while year >= MIN_YEAR: + while month >= 1: + next_tuple = (year, month) + date_tuple[2:] + next_stamp = safe_mktime(next_tuple) + # at this stage, only test consistency and monotonicity + if next_stamp is None or next_stamp >= curr_stamp: + return date_tuple + date_tuple = next_tuple + curr_stamp = next_stamp + month -= 1 + year -= 1 + month = 12 + return date_tuple + + +# test day-by-day to ensure that every date is properly converted +def test_fwd(start_date): + DAYS_PER_MONTH = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + curr_stamp = safe_mktime(start_date) + curr_date = safe_localtime(curr_stamp) + while curr_date[0] <= MAX_YEAR: + if curr_date[2] < 15: + skip_days = 13 + else: + skip_days = 1 + next_stamp = curr_stamp + skip_days * 86400 + next_date = safe_localtime(next_stamp) + if next_date is None: + return curr_date + if next_date[2] != curr_date[2] + skip_days: + # next month + if next_date[2] != 1: + print("wrong day of month:", next_date) + return curr_date + # check the number of days in previous month + month_days = DAYS_PER_MONTH[curr_date[1]] + if month_days == 28 and curr_date[0] % 4 == 0: + if curr_date[0] % 100 != 0 or curr_date[0] % 400 == 0: + month_days += 1 + if curr_date[2] != month_days: + print("wrong day count in prev month:", curr_date[2], "vs", month_days) + return curr_date + if next_date[1] != curr_date[1] + 1: + # next year + if curr_date[1] != 12: + print("wrong month count in prev year:", curr_date[1]) + return curr_date + if next_date[1] != 1: + print("wrong month:", next_date) + return curr_date + if next_date[0] != curr_date[0] + 1: + print("wrong year:", next_date) + return curr_date + curr_stamp = next_stamp + curr_date = next_date + return curr_date + + +small_date = test_bwd(SAFE_DATE) +large_date = test_fwd(small_date) +print("tested from", small_date[0:3], "to", large_date[0:3]) +print(small_date[0:3], "wday is", small_date[6]) +print(large_date[0:3], "wday is", large_date[6]) From 499bedf7aa07bb26dc19fb8b3ef5db5c8e578f52 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 10 Jul 2025 19:46:26 +0100 Subject: [PATCH 0910/2098] tools/ci.sh: Always call `apt-get update` before `apt-get install`. There have been recent build failures in build_renesas_ra_board. It appears to be the case that a security update for this package was recently issued by Ubuntu for CVE-2025-4565 and the buggy version is no longer on package servers. However, it is still referred to by the cached apt metadata in the GitHub runners. Add `apt-get update` to fix this, and audit for other sites in `ci.sh` where it might also be necessary. Signed-off-by: Jeff Epler --- tools/ci.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index 4007dfebfc3..518eb744971 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -13,11 +13,13 @@ ulimit -n 1024 # general helper functions function ci_gcc_arm_setup { + sudo apt-get update sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi arm-none-eabi-gcc --version } function ci_gcc_riscv_setup { + sudo apt-get update sudo apt-get install gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf riscv64-unknown-elf-gcc --version } @@ -35,6 +37,7 @@ function ci_picotool_setup { # c code formatting function ci_c_code_formatting_setup { + sudo apt-get update sudo apt-get install uncrustify uncrustify --version } @@ -703,6 +706,7 @@ function ci_unix_float_run_tests { } function ci_unix_clang_setup { + sudo apt-get update sudo apt-get install clang clang --version } @@ -839,6 +843,7 @@ function ci_unix_qemu_riscv64_run_tests { # ports/windows function ci_windows_setup { + sudo apt-get update sudo apt-get install gcc-mingw-w64 } From 908f938c44cc45bd725cf14d09895ab7472dd67a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 13:08:36 +1000 Subject: [PATCH 0911/2098] tests/extmod/asyncio_iterator_event.py: Use format instead of f-string. Some targets don't have f-strings enabled, so try not to use them in tests. Rather, use `str.format`, which is more portable. Signed-off-by: Damien George --- tests/extmod/asyncio_iterator_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extmod/asyncio_iterator_event.py b/tests/extmod/asyncio_iterator_event.py index 6efa6b86456..f61fefcf051 100644 --- a/tests/extmod/asyncio_iterator_event.py +++ b/tests/extmod/asyncio_iterator_event.py @@ -50,7 +50,7 @@ def schedule_watchdog(end_ticks): async def test(ai): for x in range(3): await asyncio.sleep(0.1) - ai.fetch_data(f"bar {x}") + ai.fetch_data("bar {}".format(x)) class AsyncIterable: From 125d19ce7be05c91be332ba088cc38ada8020b2f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 Jun 2025 12:25:01 +1000 Subject: [PATCH 0912/2098] tests/micropython: Add missing SystemExit after printing SKIP. The test runner expects `print("SKIP")` to be followed by `raise SystemExit`. Otherwise it waits for 10 seconds for the target to do a soft reset before timing out and continuing. Signed-off-by: Damien George --- tests/micropython/meminfo.py | 11 ++++++----- tests/micropython/memstats.py | 17 +++++++++-------- tests/micropython/stack_use.py | 5 +++-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/micropython/meminfo.py b/tests/micropython/meminfo.py index 9df341fbb83..957f061f153 100644 --- a/tests/micropython/meminfo.py +++ b/tests/micropython/meminfo.py @@ -5,8 +5,9 @@ # these functions are not always available if not hasattr(micropython, "mem_info"): print("SKIP") -else: - micropython.mem_info() - micropython.mem_info(1) - micropython.qstr_info() - micropython.qstr_info(1) + raise SystemExit + +micropython.mem_info() +micropython.mem_info(1) +micropython.qstr_info() +micropython.qstr_info(1) diff --git a/tests/micropython/memstats.py b/tests/micropython/memstats.py index dee3a4ce2f2..33204d908c6 100644 --- a/tests/micropython/memstats.py +++ b/tests/micropython/memstats.py @@ -5,13 +5,14 @@ # these functions are not always available if not hasattr(micropython, "mem_total"): print("SKIP") -else: - t = micropython.mem_total() - c = micropython.mem_current() - p = micropython.mem_peak() + raise SystemExit - l = list(range(10000)) +t = micropython.mem_total() +c = micropython.mem_current() +p = micropython.mem_peak() - print(micropython.mem_total() > t) - print(micropython.mem_current() > c) - print(micropython.mem_peak() > p) +l = list(range(10000)) + +print(micropython.mem_total() > t) +print(micropython.mem_current() > c) +print(micropython.mem_peak() > p) diff --git a/tests/micropython/stack_use.py b/tests/micropython/stack_use.py index 266885d9d18..640bb8b2f38 100644 --- a/tests/micropython/stack_use.py +++ b/tests/micropython/stack_use.py @@ -3,5 +3,6 @@ if not hasattr(micropython, "stack_use"): print("SKIP") -else: - print(type(micropython.stack_use())) # output varies + raise SystemExit + +print(type(micropython.stack_use())) # output varies From 8f8f8539827a4d38dd51e4960fe54a0ed8ab08ea Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Jul 2025 00:45:13 +1000 Subject: [PATCH 0913/2098] tests/run-tests.py: Consider tests ending in _async.py as async tests. The test `micropython/ringio_async.py` is a test that requires async keyword support, and will fail with SyntaxError on targets that don't support async/await. Really it should be skipped on such targets, and this commit makes sure that's the case. Signed-off-by: Damien George --- tests/run-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index faf1d2e3b48..0eaee5278e0 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -886,7 +886,7 @@ def run_one_test(test_file): is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") is_set_type = test_name.startswith(("set_", "frozenset")) or test_name.endswith("_set") is_slice = test_name.find("slice") != -1 or test_name in misc_slice_tests - is_async = test_name.startswith(("async_", "asyncio_")) + is_async = test_name.startswith(("async_", "asyncio_")) or test_name.endswith("_async") is_const = test_name.startswith("const") is_io_module = test_name.startswith("io_") is_fstring = test_name.startswith("string_fstring") From 274306860b2d64b67ef6e8a14e8af3fd56c8d0ff Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Fri, 11 Jul 2025 08:24:20 +0800 Subject: [PATCH 0914/2098] tests/extmod: Close UDP sockets at end of test. This adds call to release UDP port in a timely manner, so they can be reused in subsequent tests. Otherwise, one could face issue like #17623. Signed-off-by: Yanfeng Liu --- tests/extmod/select_poll_udp.py | 2 ++ tests/extmod/socket_udp_nonblock.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/extmod/select_poll_udp.py b/tests/extmod/select_poll_udp.py index 133871b1a42..887176a4f65 100644 --- a/tests/extmod/select_poll_udp.py +++ b/tests/extmod/select_poll_udp.py @@ -29,3 +29,5 @@ if hasattr(select, "select"): r, w, e = select.select([s], [], [], 0) assert not r and not w and not e + +s.close() diff --git a/tests/extmod/socket_udp_nonblock.py b/tests/extmod/socket_udp_nonblock.py index 1e74e2917dc..394115e4b88 100644 --- a/tests/extmod/socket_udp_nonblock.py +++ b/tests/extmod/socket_udp_nonblock.py @@ -19,3 +19,5 @@ s.recv(1) except OSError as er: print("EAGAIN:", er.errno == errno.EAGAIN) + +s.close() From aa2362d4de3bed960b65c5e6e66544f68f2e7ed1 Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Tue, 8 Jul 2025 12:22:25 +0800 Subject: [PATCH 0915/2098] unix/Makefile: Drop include path of "i686-linux-gnu". This drops use of non-existing path `/usr/include/i686-linux-gnu` as default include paths shall suffice. Signed-off-by: Yanfeng Liu --- ports/unix/Makefile | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 8bd58a25426..4e4e81a965c 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -120,16 +120,6 @@ LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) # Flags to link with pthread library LIBPTHREAD = -lpthread -ifeq ($(MICROPY_FORCE_32BIT),1) -# Note: you may need to install i386 versions of dependency packages, -# starting with linux-libc-dev:i386 -ifeq ($(MICROPY_PY_FFI),1) -ifeq ($(UNAME_S),Linux) -CFLAGS += -I/usr/include/i686-linux-gnu -endif -endif -endif - ifeq ($(MICROPY_USE_READLINE),1) INC += -I$(TOP)/shared/readline CFLAGS += -DMICROPY_USE_READLINE=1 From 5e9189d6d1c00c92694888bf9c74276779c40716 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 5 Dec 2022 17:01:30 +1100 Subject: [PATCH 0916/2098] py/vm: Avoid heap-allocating slices when subscripting built-ins. This commit adds a fast-path optimisation for when a BUILD_SLICE is immediately followed by a LOAD/STORE_SUBSCR for a native type, to avoid needing to allocate the slice on the heap. In some cases (e.g. `a[1:3] = x`) this can result in no allocations at all. We can't do this for instance types because the get/set/delattr implementation may keep a reference to the slice. Adds more tests to the basic slice tests to ensure that a stack-allocated slice never makes it to Python, and also a heapalloc test that verifies (when using bytecode) that assigning to a slice is no-alloc. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared Signed-off-by: Damien George --- py/vm.c | 32 +++++++++++++++++++++--- tests/basics/builtin_slice.py | 37 ++++++++++++++++++++++++++-- tests/micropython/heapalloc_slice.py | 18 ++++++++++++++ tests/run-tests.py | 3 +++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 tests/micropython/heapalloc_slice.py diff --git a/py/vm.c b/py/vm.c index f87e52c9298..6f1179721a9 100644 --- a/py/vm.c +++ b/py/vm.c @@ -195,6 +195,22 @@ #define TRACE_TICK(current_ip, current_sp, is_exception) #endif // MICROPY_PY_SYS_SETTRACE +#if MICROPY_PY_BUILTINS_SLICE +// This function is marked "no inline" so it doesn't increase the C stack usage of the main VM function. +MP_NOINLINE static mp_obj_t *build_slice_stack_allocated(byte op, mp_obj_t *sp, mp_obj_t step) { + mp_obj_t stop = sp[2]; + mp_obj_t start = sp[1]; + mp_obj_slice_t slice = { .base = { .type = &mp_type_slice }, .start = start, .stop = stop, .step = step }; + if (op == MP_BC_LOAD_SUBSCR) { + SET_TOP(mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), MP_OBJ_SENTINEL)); + } else { // MP_BC_STORE_SUBSCR + mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), sp[-1]); + sp -= 2; + } + return sp; +} +#endif + // fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc) // sp points to bottom of stack which grows up // returns: @@ -849,9 +865,19 @@ unwind_jump:; // 3-argument slice includes step step = POP(); } - mp_obj_t stop = POP(); - mp_obj_t start = TOP(); - SET_TOP(mp_obj_new_slice(start, stop, step)); + if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) && mp_obj_is_native_type(mp_obj_get_type(sp[-2]))) { + // Fast path optimisation for when the BUILD_SLICE is immediately followed + // by a LOAD/STORE_SUBSCR for a native type to avoid needing to allocate + // the slice on the heap. In some cases (e.g. a[1:3] = x) this can result + // in no allocations at all. We can't do this for instance types because + // the get/set/delattr implementation may keep a reference to the slice. + byte op = *ip++; + sp = build_slice_stack_allocated(op, sp - 2, step); + } else { + mp_obj_t stop = POP(); + mp_obj_t start = TOP(); + SET_TOP(mp_obj_new_slice(start, stop, step)); + } DISPATCH(); } #endif diff --git a/tests/basics/builtin_slice.py b/tests/basics/builtin_slice.py index df84d5c57bb..5197a7cada9 100644 --- a/tests/basics/builtin_slice.py +++ b/tests/basics/builtin_slice.py @@ -1,11 +1,44 @@ # test builtin slice +# ensures that slices passed to user types are heap-allocated and can be +# safely stored as well as not overriden by subsequent slices. + # print slice class A: def __getitem__(self, idx): - print(idx) + print("get", idx) + print("abc"[1:]) + print("get", idx) + return idx + + def __setitem__(self, idx, value): + print("set", idx) + print("abc"[1:]) + print("set", idx) + self.saved_idx = idx + return idx + + def __delitem__(self, idx): + print("del", idx) + print("abc"[1:]) + print("del", idx) return idx -s = A()[1:2:3] + + +a = A() +s = a[1:2:3] +a[4:5:6] = s +del a[7:8:9] + +print(a.saved_idx) + +# nested slicing +print(A()[1 : A()[A()[2:3:4] : 5]]) + +# tuple slicing +a[1:2, 4:5, 7:8] +a[1, 4:5, 7:8, 2] +a[1:2, a[3:4], 5:6] # check type print(type(s) is slice) diff --git a/tests/micropython/heapalloc_slice.py b/tests/micropython/heapalloc_slice.py new file mode 100644 index 00000000000..62d96595c71 --- /dev/null +++ b/tests/micropython/heapalloc_slice.py @@ -0,0 +1,18 @@ +# slice operations that don't require allocation +try: + from micropython import heap_lock, heap_unlock +except (ImportError, AttributeError): + heap_lock = heap_unlock = lambda: 0 + +b = bytearray(range(10)) + +m = memoryview(b) + +heap_lock() + +b[3:5] = b"aa" +m[5:7] = b"bb" + +heap_unlock() + +print(b) diff --git a/tests/run-tests.py b/tests/run-tests.py index 0eaee5278e0..fe338d7ffba 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -854,6 +854,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add( "micropython/emg_exc.py" ) # because native doesn't have proper traceback info + skip_tests.add( + "micropython/heapalloc_slice.py" + ) # because native doesn't do the stack-allocated slice optimisation skip_tests.add( "micropython/heapalloc_traceback.py" ) # because native doesn't have proper traceback info From 0a4f9ec46bc0c962b5d0eff00e4ea49287fd9112 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 5 Jul 2025 15:09:06 +0100 Subject: [PATCH 0917/2098] py/mpprint: Rework integer vararg handling. This adds support for %llx (needed by XINT_FMT for printing cell objects) and incidentally support for capitalized output of %P. It also reduces code size due to the common handling of all integers. Signed-off-by: Jeff Epler --- py/mpprint.c | 97 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/py/mpprint.c b/py/mpprint.c index 86dbfad05e3..e56b949ddda 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -446,16 +446,36 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { } } - // parse long specifiers (only for LP64 model where they make a difference) - #ifndef __LP64__ - const + // parse long and long long specifiers (only where they make a difference) + #if defined(MICROPY_UNIX_COVERAGE) || (LONG_MAX > INT_MAX) + #define SUPPORT_L_FORMAT (1) + #else + #define SUPPORT_L_FORMAT (0) #endif + #if SUPPORT_L_FORMAT bool long_arg = false; + #endif + + #if (MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D) || defined(_WIN64) || defined(MICROPY_UNIX_COVERAGE) + #define SUPPORT_LL_FORMAT (1) + #else + #define SUPPORT_LL_FORMAT (0) + #endif + #if SUPPORT_LL_FORMAT + bool long_long_arg = false; + #endif + if (*fmt == 'l') { ++fmt; - #ifdef __LP64__ + #if SUPPORT_L_FORMAT long_arg = true; #endif + #if SUPPORT_LL_FORMAT + if (*fmt == 'l') { + ++fmt; + long_long_arg = true; + } + #endif } if (*fmt == '\0') { @@ -501,35 +521,50 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { chrs += mp_print_strn(print, str, len, flags, fill, width); break; } - case 'd': { - mp_int_t val; - if (long_arg) { - val = va_arg(args, long int); - } else { - val = va_arg(args, int); - } - chrs += mp_print_int(print, val, 1, 10, 'a', flags, fill, width); - break; - } + case 'd': + case 'p': + case 'P': case 'u': case 'x': case 'X': { - int base = 16 - ((*fmt + 1) & 6); // maps char u/x/X to base 10/16/16 - char fmt_c = (*fmt & 0xf0) - 'P' + 'A'; // maps char u/x/X to char a/a/A + char fmt_chr = *fmt; mp_uint_t val; - if (long_arg) { - val = va_arg(args, unsigned long int); + if (fmt_chr == 'p' || fmt_chr == 'P') { + val = va_arg(args, intptr_t); + } + #if SUPPORT_LL_FORMAT + else if (long_long_arg) { + val = va_arg(args, unsigned long long); + } + #endif + #if SUPPORT_L_FORMAT + else if (long_arg) { + if (sizeof(long) != sizeof(mp_uint_t) && fmt_chr == 'd') { + val = va_arg(args, long); + } else { + val = va_arg(args, unsigned long); + } + } + #endif + else { + if (sizeof(int) != sizeof(mp_uint_t) && fmt_chr == 'd') { + val = va_arg(args, int); + } else { + val = va_arg(args, unsigned); + } + } + int base; + // Map format char x/p/X/P to a/a/A/A for hex letters. + // It doesn't matter what d/u map to. + char fmt_c = (fmt_chr & 0xf0) - 'P' + 'A'; + if (fmt_chr == 'd' || fmt_chr == 'u') { + base = 10; } else { - val = va_arg(args, unsigned int); + base = 16; } - chrs += mp_print_int(print, val, 0, base, fmt_c, flags, fill, width); + chrs += mp_print_int(print, val, fmt_chr == 'd', base, fmt_c, flags, fill, width); break; } - case 'p': - case 'P': // don't bother to handle upcase for 'P' - // Use unsigned long int to work on both ILP32 and LP64 systems - chrs += mp_print_int(print, va_arg(args, unsigned long int), 0, 16, 'a', flags, fill, width); - break; #if MICROPY_PY_BUILTINS_FLOAT case 'e': case 'E': @@ -545,18 +580,6 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { #endif break; } - #endif - // Because 'l' is eaten above, another 'l' means %ll. We need to support - // this length specifier for OBJ_REPR_D (64-bit NaN boxing). - // TODO Either enable this unconditionally, or provide a specific config var. - #if (MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D) || defined(_WIN64) - case 'l': { - unsigned long long int arg_value = va_arg(args, unsigned long long int); - ++fmt; - assert(*fmt == 'u' || *fmt == 'd' || !"unsupported fmt char"); - chrs += mp_print_int(print, arg_value, *fmt == 'd', 10, 'a', flags, fill, width); - break; - } #endif default: // if it's not %% then it's an unsupported format character From 628d53d23cf42986bc3ae55fda2062e2016a7c2d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 5 Jul 2025 17:37:28 +0100 Subject: [PATCH 0918/2098] unix/coverage: Expand mp_printf coverage tests. Test 'l' and 'll' sized objects. When the platform's `mp_int_t` is not 64 bits, dummy values are printed instead so the test result can match across all platforms. Ensure hex test values have a letter so 'x' vs 'X' is tested. And test 'p' and 'P' pointer printing. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 40 +++++++++++++++++++++++--- tests/ports/unix/extra_coverage.py.exp | 23 ++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 0df6bf279ae..68340d7f239 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -204,8 +204,20 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%d %+d % d\n", -123, 123, 123); // sign mp_printf(&mp_plat_print, "%05d\n", -123); // negative number with zero padding mp_printf(&mp_plat_print, "%ld\n", 123); // long - mp_printf(&mp_plat_print, "%lx\n", 0x123); // long hex - mp_printf(&mp_plat_print, "%X\n", 0x1abcdef); // capital hex + mp_printf(&mp_plat_print, "%lx\n", 0x123fl); // long hex + mp_printf(&mp_plat_print, "%lX\n", 0x123fl); // capital long hex + if (sizeof(mp_int_t) == 8) { + mp_printf(&mp_plat_print, "%llx\n", LLONG_MAX); // long long hex + mp_printf(&mp_plat_print, "%llX\n", LLONG_MAX); // capital long long hex + mp_printf(&mp_plat_print, "%llu\n", ULLONG_MAX); // unsigned long long + } else { + // fake for platforms without narrower mp_int_t + mp_printf(&mp_plat_print, "7fffffffffffffff\n", LLONG_MAX); + mp_printf(&mp_plat_print, "7FFFFFFFFFFFFFFF\n", LLONG_MAX); + mp_printf(&mp_plat_print, "18446744073709551615\n", ULLONG_MAX); + } + mp_printf(&mp_plat_print, "%p\n", (void *)0x789f); // pointer + mp_printf(&mp_plat_print, "%P\n", (void *)0x789f); // pointer uppercase mp_printf(&mp_plat_print, "%.2s %.3s '%4.4s' '%5.5q' '%.3q'\n", "abc", "abc", "abc", MP_QSTR_True, MP_QSTR_True); // fixed string precision mp_printf(&mp_plat_print, "%.*s\n", -1, "abc"); // negative string precision mp_printf(&mp_plat_print, "%b %b\n", 0, 1); // bools @@ -216,11 +228,31 @@ static mp_obj_t extra_coverage(void) { #endif mp_printf(&mp_plat_print, "%d\n", 0x80000000); // should print signed mp_printf(&mp_plat_print, "%u\n", 0x80000000); // should print unsigned - mp_printf(&mp_plat_print, "%x\n", 0x80000000); // should print unsigned - mp_printf(&mp_plat_print, "%X\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "%x\n", 0x8000000f); // should print unsigned + mp_printf(&mp_plat_print, "%X\n", 0x8000000f); // should print unsigned mp_printf(&mp_plat_print, "abc\n%"); // string ends in middle of format specifier mp_printf(&mp_plat_print, "%%\n"); // literal % character mp_printf(&mp_plat_print, ".%-3s.\n", "a"); // left adjust + + // Check that all kinds of mp_printf arguments are parsed out + // correctly, by having a char argument before and after each main type + // of value that can be formatted. + mp_printf(&mp_plat_print, "%c%%%c\n", '<', '>'); + mp_printf(&mp_plat_print, "%c%p%c\n", '<', (void *)0xaaaa, '>'); + mp_printf(&mp_plat_print, "%c%b%c\n", '<', true, '>'); + mp_printf(&mp_plat_print, "%c%d%c\n", '<', 0xaaaa, '>'); + mp_printf(&mp_plat_print, "%c%ld%c\n", '<', 0xaaaal, '>'); + mp_printf(&mp_plat_print, "%c" INT_FMT "%c\n", '<', (mp_int_t)0xaaaa, '>'); + mp_printf(&mp_plat_print, "%c%s%c\n", '<', "test", '>'); + mp_printf(&mp_plat_print, "%c%f%c\n", '<', MICROPY_FLOAT_CONST(1000.), '>'); + mp_printf(&mp_plat_print, "%c%q%c\n", '<', (qstr)MP_QSTR_True, '>'); + if (sizeof(mp_int_t) == 8) { + mp_printf(&mp_plat_print, "%c%lld%c\n", '<', LLONG_MAX, '>'); + } else { + mp_printf(&mp_plat_print, "<9223372036854775807>\n"); + } + + } // GC diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 00658ab3adc..e20871273d7 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -2,19 +2,34 @@ -123 +123 123 -0123 123 -123 -1ABCDEF +123f +123F +7fffffffffffffff +7FFFFFFFFFFFFFFF +18446744073709551615 +789f +789F ab abc ' abc' ' True' 'Tru' false true (null) -2147483648 2147483648 -80000000 -80000000 +8000000f +8000000F abc % .a . +<%> + + +<43690> +<43690> +<43690> + +<1000.000000> + +<9223372036854775807> # GC 0 0 From 8504391766cf9cd181ea178669b75e362043694d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Jul 2025 12:56:49 +1000 Subject: [PATCH 0919/2098] alif/lwip_inc: Refactor lwipopts.h to use extmod's common options. This change is a no-op for the firmware. Signed-off-by: Damien George --- ports/alif/lwip_inc/lwipopts.h | 43 ++++------------------------------ 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/ports/alif/lwip_inc/lwipopts.h b/ports/alif/lwip_inc/lwipopts.h index c0622225e10..62b6a84b609 100644 --- a/ports/alif/lwip_inc/lwipopts.h +++ b/ports/alif/lwip_inc/lwipopts.h @@ -1,49 +1,13 @@ #ifndef MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H -#include - -// This protection is not needed, instead we execute all lwIP code at PendSV priority -#define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) -#define SYS_ARCH_PROTECT(lev) do { } while (0) -#define SYS_ARCH_UNPROTECT(lev) do { } while (0) - -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 1 -#define MEM_ALIGNMENT 4 - -#define LWIP_CHKSUM_ALGORITHM 3 -#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 - -#define LWIP_ARP 1 -#define LWIP_ETHERNET 1 -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 -#define LWIP_STATS 0 -#define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_EXT_STATUS_CALLBACK 1 #define LWIP_LOOPIF_MULTICAST 1 #define LWIP_LOOPBACK_MAX_PBUFS 8 #define LWIP_IPV6 0 -#define LWIP_DHCP 1 -#define LWIP_DHCP_CHECK_LINK_UP 1 -#define LWIP_DHCP_DOES_ACD_CHECK 0 // to speed DHCP up -#define LWIP_DNS 1 -#define LWIP_DNS_SUPPORT_MDNS_QUERIES 1 -#define LWIP_MDNS_RESPONDER 1 -#define LWIP_IGMP 1 - -#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) - -#define SO_REUSE 1 -#define TCP_LISTEN_BACKLOG 1 - -extern uint64_t se_services_rand64(void); + #define LWIP_RAND() se_services_rand64() #define MEM_SIZE (16 * 1024) @@ -55,6 +19,9 @@ extern uint64_t se_services_rand64(void); #define TCP_QUEUE_OOSEQ (1) #define MEMP_NUM_TCP_SEG (2 * TCP_SND_QUEUELEN) -typedef uint32_t sys_prot_t; +// Include common lwIP configuration. +#include "extmod/lwip-include/lwipopts_common.h" + +uint64_t se_services_rand64(void); #endif // MICROPY_INCLUDED_ALIF_LWIP_LWIPOPTS_H From cf490ed34618782cef591a41438a62e1437d7d5c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Jul 2025 12:32:29 +1000 Subject: [PATCH 0920/2098] extmod/network_lwip: Add sys_untimeout_all_with_arg helper function. Really lwIP should provide this, to deregister all callbacks on the given netif. Signed-off-by: Damien George --- extmod/lwip-include/lwipopts_common.h | 3 +++ extmod/modnetwork.h | 3 +++ extmod/network_lwip.c | 13 +++++++++++++ 3 files changed, 19 insertions(+) diff --git a/extmod/lwip-include/lwipopts_common.h b/extmod/lwip-include/lwipopts_common.h index 3e423090949..8cb1acfe2ca 100644 --- a/extmod/lwip-include/lwipopts_common.h +++ b/extmod/lwip-include/lwipopts_common.h @@ -28,6 +28,9 @@ #include "py/mpconfig.h" +// This is needed to access `next_timeout` via `sys_timeouts_get_next_timeout()`. +#define LWIP_TESTMODE 1 + // This sys-arch protection is not needed. // Ports either protect lwIP code with flags, or run it at PendSV priority. #define SYS_ARCH_DECL_PROTECT(lev) do { } while (0) diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index d16329f07dd..754f6e12434 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -82,6 +82,9 @@ extern const struct _mp_obj_type_t mp_network_ppp_lwip_type; #endif struct netif; + +void sys_untimeout_all_with_arg(void *arg); + void mod_network_lwip_init(void); void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args); diff --git a/extmod/network_lwip.c b/extmod/network_lwip.c index 71dc295e184..9cfab6ef4cb 100644 --- a/extmod/network_lwip.c +++ b/extmod/network_lwip.c @@ -52,6 +52,19 @@ int mp_mod_network_prefer_dns_use_ip_version = 4; // Implementations of network methods that can be used by any interface. +// This follows sys_untimeout but removes all timeouts with the given argument. +void sys_untimeout_all_with_arg(void *arg) { + for (struct sys_timeo **t = sys_timeouts_get_next_timeout(); *t != NULL;) { + if ((*t)->arg == arg) { + struct sys_timeo *next = (*t)->next; + memp_free(MEMP_SYS_TIMEOUT, *t); + *t = next; + } else { + t = &(*t)->next; + } + } +} + // This function provides the implementation of nic.ifconfig, is deprecated and will be removed. // Use network.ipconfig and nic.ipconfig instead. mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args) { From 0b698b82417b17b1098af9e64433741154345252 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Jul 2025 12:32:53 +1000 Subject: [PATCH 0921/2098] rp2/mpnetworkport: Deregister all sys timeouts when netif is removed. When mDNS is active on a netif it registers a lot of timeouts, namely: mdns_probe_and_announce mdns_handle_tc_question mdns_multicast_probe_timeout_reset_ipv4 mdns_multicast_timeout_25ttl_reset_ipv4 mdns_multicast_timeout_reset_ipv4 mdns_send_multicast_msg_delayed_ipv4 mdns_send_unicast_msg_delayed_ipv4 mdns_multicast_probe_timeout_reset_ipv6 mdns_multicast_timeout_25ttl_reset_ipv6 mdns_multicast_timeout_reset_ipv6 mdns_send_multicast_msg_delayed_ipv6 mdns_send_unicast_msg_delayed_ipv6 These may still be active after a netif is removed, and if they are called they will find that the mDNS state pointer in the netif is NULL and they will crash. These functions could be explicitly removed using `sys_untimeout()`, but `mdns_handle_tc_question()` is static so it's not possible to access it. Instead use the new `sys_untimeout_all_with_arg()` helper to deregister all timeout callbacks when a netif is removed. Fixes issue #17621. Signed-off-by: Damien George --- ports/rp2/mpnetworkport.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index fed34be380a..675552d1e5c 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -30,6 +30,7 @@ #if MICROPY_PY_LWIP +#include "extmod/modnetwork.h" #include "shared/runtime/softtimer.h" #include "lwip/netif.h" #include "lwip/timeouts.h" @@ -183,6 +184,10 @@ static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t r mp_network_soft_timer.mode = SOFT_TIMER_MODE_PERIODIC; soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); } + + if (reason == LWIP_NSC_NETIF_REMOVED) { + sys_untimeout_all_with_arg(netif); + } } #endif // MICROPY_PY_LWIP From 554f114f181ee942ee3c74e44cef653604abbaef Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 14 Jul 2025 12:45:42 -0400 Subject: [PATCH 0922/2098] examples/rp2/pio_uart_rx.py: Fix use of PIO constants. Running the unmodified `pio_uart_rx.py` example by uploading the file and importing it doesn't succeed, and instead emits a NameError at line 26. Signed-off-by: Anson Mansfield --- examples/rp2/pio_uart_rx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rp2/pio_uart_rx.py b/examples/rp2/pio_uart_rx.py index f46aaa6a5e3..a5b2210876f 100644 --- a/examples/rp2/pio_uart_rx.py +++ b/examples/rp2/pio_uart_rx.py @@ -23,7 +23,7 @@ @asm_pio( autopush=True, push_thresh=8, - in_shiftdir=rp2.PIO.SHIFT_RIGHT, + in_shiftdir=PIO.SHIFT_RIGHT, fifo_join=PIO.JOIN_RX, ) def uart_rx_mini(): @@ -42,7 +42,7 @@ def uart_rx_mini(): @asm_pio( - in_shiftdir=rp2.PIO.SHIFT_RIGHT, + in_shiftdir=PIO.SHIFT_RIGHT, ) def uart_rx(): # fmt: off From c72a3e528d7909c212596b52de5f9a5fe0161f17 Mon Sep 17 00:00:00 2001 From: webreflection Date: Tue, 15 Jul 2025 10:40:15 +0200 Subject: [PATCH 0923/2098] webassembly/objpyproxy: Avoid throwing on implicit symbols access. This commit improves get handling by guarding against implicit unknown symbols accessed directly by specific JS native APIs. Fixes issue #17657. Signed-off-by: Andrea Giammarchi --- ports/webassembly/objpyproxy.js | 53 ++++++++++---------- tests/ports/webassembly/py_proxy_get.mjs | 14 ++++++ tests/ports/webassembly/py_proxy_get.mjs.exp | 5 ++ 3 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 tests/ports/webassembly/py_proxy_get.mjs create mode 100644 tests/ports/webassembly/py_proxy_get.mjs.exp diff --git a/ports/webassembly/objpyproxy.js b/ports/webassembly/objpyproxy.js index 0eafd0dec53..64703d78a55 100644 --- a/ports/webassembly/objpyproxy.js +++ b/ports/webassembly/objpyproxy.js @@ -165,34 +165,35 @@ const py_proxy_handler = { if (prop === "_ref") { return target._ref; } - if (prop === "then") { - return null; - } - if (prop === Symbol.iterator) { - // Get the Python object iterator, and return a JavaScript generator. - const iter_ref = Module.ccall( - "proxy_c_to_js_get_iter", - "number", - ["number"], - [target._ref], - ); - return function* () { - const value = Module._malloc(3 * 4); - while (true) { - const valid = Module.ccall( - "proxy_c_to_js_iternext", - "number", - ["number", "pointer"], - [iter_ref, value], - ); - if (!valid) { - break; + // ignore both then and all symbols but Symbol.iterator + if (prop === "then" || typeof prop !== "string") { + if (prop === Symbol.iterator) { + // Get the Python object iterator, and return a JavaScript generator. + const iter_ref = Module.ccall( + "proxy_c_to_js_get_iter", + "number", + ["number"], + [target._ref], + ); + return function* () { + const value = Module._malloc(3 * 4); + while (true) { + const valid = Module.ccall( + "proxy_c_to_js_iternext", + "number", + ["number", "pointer"], + [iter_ref, value], + ); + if (!valid) { + break; + } + yield proxy_convert_mp_to_js_obj_jsside(value); } - yield proxy_convert_mp_to_js_obj_jsside(value); - } - Module._free(value); - }; + Module._free(value); + }; + } + return undefined; } const value = Module._malloc(3 * 4); diff --git a/tests/ports/webassembly/py_proxy_get.mjs b/tests/ports/webassembly/py_proxy_get.mjs new file mode 100644 index 00000000000..825de7cabeb --- /dev/null +++ b/tests/ports/webassembly/py_proxy_get.mjs @@ -0,0 +1,14 @@ +// Test ` get ` on the JavaScript side, which tests PyProxy.get. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +mp.runPython(` +x = {"a": 1} +`); + +const x = mp.globals.get("x"); +console.log(x.a === 1); +console.log(x.b === undefined); +console.log(typeof x[Symbol.iterator] === "function"); +console.log(x[Symbol.toStringTag] === undefined); +console.log(x.then === undefined); diff --git a/tests/ports/webassembly/py_proxy_get.mjs.exp b/tests/ports/webassembly/py_proxy_get.mjs.exp new file mode 100644 index 00000000000..36c7afad66a --- /dev/null +++ b/tests/ports/webassembly/py_proxy_get.mjs.exp @@ -0,0 +1,5 @@ +true +true +true +true +true From 2d8d64059fbc7cd72e40503e5af19eded73c3d47 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Mar 2025 12:07:00 +1100 Subject: [PATCH 0924/2098] tests: Add specific tests for "long long" 64-bit bigints. These will run on all ports which support them, but importantly they'll also run on ports that don't support arbitrary precision but do support 64-bit long ints. Includes some test workarounds to account for things which will overflow once "long long" big integers overflow (added in follow-up commit): - uctypes_array_load_store test was failing already, now won't parse. - all the ffi_int tests contain 64-bit unsigned values, that won't parse as long long. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/basics/int_64_basics.py | 126 +++++++++++++++++++++++ tests/extmod/uctypes_array_load_store.py | 7 ++ tests/feature_check/int_64.py | 2 + tests/feature_check/int_64.py.exp | 1 + tests/run-tests.py | 14 ++- 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/basics/int_64_basics.py create mode 100644 tests/feature_check/int_64.py create mode 100644 tests/feature_check/int_64.py.exp diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py new file mode 100644 index 00000000000..73a06b64b13 --- /dev/null +++ b/tests/basics/int_64_basics.py @@ -0,0 +1,126 @@ +# test support for 64-bit long integers +# (some ports don't support arbitrary precision but do support these) + +# this test is adapted from int_big1.py with numbers kept within 64-bit signed range + +# to test arbitrary precision integers + +x = 1000000000000000000 +xn = -1000000000000000000 +y = 2000000000000000000 + +# printing +print(x) +print(y) +print('%#X' % (x - x)) # print prefix +print('{:#,}'.format(x)) # print with commas + +# construction +print(int(x)) + +# addition +print(x + 1) +print(x + y) +print(x + xn == 0) +print(bool(x + xn)) + +# subtraction +print(x - 1) +print(x - y) +print(y - x) +print(x - x == 0) +print(bool(x - x)) + +# multiplication +print(x * 2) +print(1090511627776 * 1048500) + +# integer division +print(x // 2) +print(y // x) + +# bit inversion +print(~x) +print(~(-x)) + +# left shift +print("left shift positive") +x = 0x40000000 +for i in range(32): + x = x << 1 + print(x) + +# right shift +print("right shift positive") +x = 0x2000000000000000 # TODO: why can't second-tip bit be set? +for i in range(64): + x = x >> 1 + print(x) + +# left shift of a negative number +print("left shift negative") +for i in range(8): + print(-10000000000000000 << i) + print(-10000000000000001 << i) + print(-10000000000000002 << i) + print(-10000000000000003 << i) + print(-10000000000000004 << i) + + +# right shift of a negative number +print("right shift negative") +for i in range(8): + print(-1000000000000000000 >> i) + print(-1000000000000000001 >> i) + print(-1000000000000000002 >> i) + print(-1000000000000000003 >> i) + print(-1000000000000000004 >> i) + +# conversion from string +print(int("1234567890123456789")) +print(int("-1234567890123456789")) +print(int("1234567890abcdef", 16)) +print(int("1234567890ABCDEF", 16)) +print(int("-1234567890ABCDEF", 16)) +print(int("ijklmnopqrsz", 36)) + +# numbers close to 64-bit limits +print(int("-9111222333444555666")) +print(int("9111222333444555666")) + +# numbers with preceding 0s +print(int("-00000000000000000000009111222333444555666")) +print(int("0000000000000000000000009111222333444555666")) + +# invalid characters in string +try: + print(int("1234567890abcdef")) +except ValueError: + print('ValueError'); +try: + print(int("123456789\x01")) +except ValueError: + print('ValueError'); + +# test parsing ints just on threshold of small to big +# for 32 bit archs +x = 1073741823 # small +x = -1073741823 # small +x = 1073741824 # big +x = -1073741824 # big +# for 64 bit archs +x = 4611686018427387903 # small +x = -4611686018427387903 # small +x = 4611686018427387904 # big +x = -4611686018427387904 # big + +# sys.maxsize is a constant bigint, so test it's compatible with dynamic ones +import sys +if hasattr(sys, "maxsize"): + print(sys.maxsize + 1 - 1 == sys.maxsize) +else: + print(True) # No maxsize property in this config + +# test extraction of big int value via mp_obj_get_int_maybe +x = 1 << 62 +print('a' * (x + 4 - x)) diff --git a/tests/extmod/uctypes_array_load_store.py b/tests/extmod/uctypes_array_load_store.py index 3b9bb6d7308..df7deb6837a 100644 --- a/tests/extmod/uctypes_array_load_store.py +++ b/tests/extmod/uctypes_array_load_store.py @@ -6,6 +6,13 @@ print("SKIP") raise SystemExit +# 'int' needs to be able to represent UINT64 for this test +try: + int("FF" * 8, 16) +except OverflowError: + print("SKIP") + raise SystemExit + N = 5 for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"): diff --git a/tests/feature_check/int_64.py b/tests/feature_check/int_64.py new file mode 100644 index 00000000000..4d053782ca8 --- /dev/null +++ b/tests/feature_check/int_64.py @@ -0,0 +1,2 @@ +# Check whether 64-bit long integers are supported +print(1 << 62) diff --git a/tests/feature_check/int_64.py.exp b/tests/feature_check/int_64.py.exp new file mode 100644 index 00000000000..aef5454e662 --- /dev/null +++ b/tests/feature_check/int_64.py.exp @@ -0,0 +1 @@ +4611686018427387904 diff --git a/tests/run-tests.py b/tests/run-tests.py index fe338d7ffba..c218afae719 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -628,6 +628,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests = set() skip_native = False skip_int_big = False + skip_int_64 = False skip_bytearray = False skip_set_type = False skip_slice = False @@ -658,6 +659,11 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if output != b"1000000000000000000000000000000000000000000000\n": skip_int_big = True + # Check if 'long long' precision integers are supported, even if arbitrary precision is not + output = run_feature_check(pyb, args, "int_64.py") + if output != b"4611686018427387904\n": + skip_int_64 = True + # Check if bytearray is supported, and skip such tests if it's not output = run_feature_check(pyb, args, "bytearray.py") if output != b"bytearray\n": @@ -885,7 +891,12 @@ def run_one_test(test_file): test_name = os.path.splitext(os.path.basename(test_file))[0] is_native = test_name.startswith("native_") or test_name.startswith("viper_") is_endian = test_name.endswith("_endian") - is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig") + is_int_big = ( + test_name.startswith("int_big") + or test_name.endswith("_intbig") + or test_name.startswith("ffi_int") # these tests contain large integer literals + ) + is_int_64 = test_name.startswith("int_64") or test_name.endswith("_int64") is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") is_set_type = test_name.startswith(("set_", "frozenset")) or test_name.endswith("_set") is_slice = test_name.find("slice") != -1 or test_name in misc_slice_tests @@ -899,6 +910,7 @@ def run_one_test(test_file): skip_it |= skip_native and is_native skip_it |= skip_endian and is_endian skip_it |= skip_int_big and is_int_big + skip_it |= skip_int_64 and is_int_64 skip_it |= skip_bytearray and is_bytearray skip_it |= skip_set_type and is_set_type skip_it |= skip_slice and is_slice From 6d93b150b894c73656c33c56d049e45f70f8e3db Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Mar 2025 13:27:02 +1100 Subject: [PATCH 0925/2098] tests/extmod/json_loads_int_64.py: Add test cases for LONGINT parse. These tests cover the use of mp_obj_new_int_from_str_len when mp_parse_num_integer overflows the SMALLINT limit, and also the case where the value may not be null terminated. Placed in a separate test file so that extmod/json test doesn't rely on bigint support. Signed-off-by: Yoctopuce dev Signed-off-by: Angus Gratton --- tests/extmod/json_loads_int_64.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/extmod/json_loads_int_64.py diff --git a/tests/extmod/json_loads_int_64.py b/tests/extmod/json_loads_int_64.py new file mode 100644 index 00000000000..193a3c28d82 --- /dev/null +++ b/tests/extmod/json_loads_int_64.py @@ -0,0 +1,16 @@ +# Parse 64-bit integers from JSON payloads. +# +# This also exercises parsing integers from strings +# where the value may not be null terminated (last line) +try: + import json +except ImportError: + print("SKIP") + raise SystemExit + + +print(json.loads("9111222333444555666")) +print(json.loads("-9111222333444555666")) +print(json.loads("9111222333444555666")) +print(json.loads("-9111222333444555666")) +print(json.loads("[\"9111222333444555666777\",9111222333444555666]")) From a54b5d9aed871b20d76df3c45cd78bf51d28b249 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Mar 2025 17:09:17 +1100 Subject: [PATCH 0926/2098] unix/variants: Add a 'longlong' variant to test 64-bit bigints in CI. Signed-off-by: Angus Gratton --- .github/workflows/ports_unix.yml | 14 +++++++ .../unix/variants/longlong/mpconfigvariant.h | 37 +++++++++++++++++++ .../unix/variants/longlong/mpconfigvariant.mk | 8 ++++ tools/ci.sh | 8 ++++ 4 files changed, 67 insertions(+) create mode 100644 ports/unix/variants/longlong/mpconfigvariant.h create mode 100644 ports/unix/variants/longlong/mpconfigvariant.mk diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 4b22926eaf8..60c0244a8f9 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -134,6 +134,20 @@ jobs: if: failure() run: tests/run-tests.py --print-failures + longlong: + runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_unix_32bit_setup + - name: Build + run: source tools/ci.sh && ci_unix_longlong_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_longlong_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures + float: runs-on: ubuntu-latest steps: diff --git a/ports/unix/variants/longlong/mpconfigvariant.h b/ports/unix/variants/longlong/mpconfigvariant.h new file mode 100644 index 00000000000..20c52e98f9d --- /dev/null +++ b/ports/unix/variants/longlong/mpconfigvariant.h @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 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. + */ + +// This config exists to test that the MICROPY_LONGINT_IMPL_LONGLONG variant +// (i.e. minimal form of "big integer" that's backed by 64-bit int only) builds +// and passes tests. + +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) + +// Set base feature level. +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) + +// Enable extra Unix features. +#include "../mpconfigvariant_common.h" diff --git a/ports/unix/variants/longlong/mpconfigvariant.mk b/ports/unix/variants/longlong/mpconfigvariant.mk new file mode 100644 index 00000000000..2d2c3706469 --- /dev/null +++ b/ports/unix/variants/longlong/mpconfigvariant.mk @@ -0,0 +1,8 @@ +# build interpreter with "bigints" implemented as "longlong" + +# otherwise, small int is essentially 64-bit +MICROPY_FORCE_32BIT := 1 + +MICROPY_PY_FFI := 0 + +MPY_TOOL_FLAGS = -mlongint-impl longlong diff --git a/tools/ci.sh b/tools/ci.sh index 518eb744971..564b7810f57 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -695,6 +695,14 @@ function ci_unix_nanbox_run_tests { ci_unix_run_tests_full_no_native_helper nanbox PYTHON=python2.7 } +function ci_unix_longlong_build { + ci_unix_build_helper VARIANT=longlong +} + +function ci_unix_longlong_run_tests { + ci_unix_run_tests_full_helper longlong +} + function ci_unix_float_build { ci_unix_build_helper VARIANT=standard CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" ci_unix_build_ffi_lib_helper gcc From 0cf1e7c0598c5daee6d63c8b0dff0d9d67899fec Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 19 Mar 2025 10:35:58 +1100 Subject: [PATCH 0927/2098] tests/thread: Rename thread_lock4 test to thread_lock4_intbig. Relies on arbitrary precision math, so won't run on a port which has threads & limited bigint support. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/thread/{thread_lock4.py => thread_lock4_intbig.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/thread/{thread_lock4.py => thread_lock4_intbig.py} (100%) diff --git a/tests/thread/thread_lock4.py b/tests/thread/thread_lock4_intbig.py similarity index 100% rename from tests/thread/thread_lock4.py rename to tests/thread/thread_lock4_intbig.py From d07f103d68d8bb1b65cba047b9bef093b9375ebd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 25 Mar 2025 09:53:44 +1100 Subject: [PATCH 0928/2098] tests: Skip bm_pidigits perf test if no arbitrary precision int support. The other performance tests run and pass with only 64-bit big integer support. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/perf_bench/bm_pidigits.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/perf_bench/bm_pidigits.py b/tests/perf_bench/bm_pidigits.py index bdaa73cec7e..c935f103c5b 100644 --- a/tests/perf_bench/bm_pidigits.py +++ b/tests/perf_bench/bm_pidigits.py @@ -5,6 +5,12 @@ # This benchmark stresses big integer arithmetic. # Adapted from code on: http://benchmarksgame.alioth.debian.org/ +try: + int("0x10000000000000000", 16) +except: + print("SKIP") # No support for >64-bit integers + raise SystemExit + def compose(a, b): aq, ar, as_, at = a From 516aa02104c3344903bdda078b7c87f71f94938d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 26 Mar 2025 11:07:52 +1100 Subject: [PATCH 0929/2098] py/objint_longlong: Add arithmetic overflow checks. Long long big integer support now raises an exception on overflow rather than returning an undefined result. Also adds an error when shifting by a negative value. The new arithmetic checks are added in the misc.h header. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/misc.h | 105 ++++++++++++++++++++++++++++-- py/objint_longlong.c | 51 +++++++++++---- tests/basics/int_64_basics.py | 13 +++- tests/extmod/uctypes_addressof.py | 7 +- 4 files changed, 154 insertions(+), 22 deletions(-) diff --git a/py/misc.h b/py/misc.h index 5d0893bbdd3..e0344858389 100644 --- a/py/misc.h +++ b/py/misc.h @@ -33,10 +33,15 @@ #include #include #include +#include typedef unsigned char byte; typedef unsigned int uint; +#ifndef __has_builtin +#define __has_builtin(x) (0) +#endif + /** generic ops *************************************************/ #ifndef MIN @@ -374,26 +379,23 @@ static inline bool mp_check(bool value) { static inline uint32_t mp_popcount(uint32_t x) { return __popcnt(x); } -#else +#else // _MSC_VER #define mp_clz(x) __builtin_clz(x) #define mp_clzl(x) __builtin_clzl(x) #define mp_clzll(x) __builtin_clzll(x) #define mp_ctz(x) __builtin_ctz(x) #define mp_check(x) (x) -#if defined __has_builtin #if __has_builtin(__builtin_popcount) #define mp_popcount(x) __builtin_popcount(x) -#endif -#endif -#if !defined(mp_popcount) +#else static inline uint32_t mp_popcount(uint32_t x) { x = x - ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; return (x * 0x01010101) >> 24; } -#endif -#endif +#endif // __has_builtin(__builtin_popcount) +#endif // _MSC_VER #define MP_FIT_UNSIGNED(bits, value) (((value) & (~0U << (bits))) == 0) #define MP_FIT_SIGNED(bits, value) \ @@ -426,4 +428,93 @@ static inline uint32_t mp_clz_mpi(mp_int_t x) { #endif } +// Overflow-checked operations for long long + +// Integer overflow builtins were added to GCC 5, but __has_builtin only in GCC 10 +// +// Note that the builtins has a defined result when overflow occurs, whereas the custom +// functions below don't update the result if an overflow would occur (to avoid UB). +#define MP_GCC_HAS_BUILTIN_OVERFLOW (__GNUC__ >= 5) + +#if __has_builtin(__builtin_umulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW +#define mp_mul_ull_overflow __builtin_umulll_overflow +#else +inline static bool mp_mul_ull_overflow(unsigned long long int x, unsigned long long int y, unsigned long long int *res) { + if (y > 0 && x > (ULLONG_MAX / y)) { + return true; // overflow + } + *res = x * y; + return false; +} +#endif + +#if __has_builtin(__builtin_smulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW +#define mp_mul_ll_overflow __builtin_smulll_overflow +#else +inline static bool mp_mul_ll_overflow(long long int x, long long int y, long long int *res) { + bool overflow; + + // Check for multiply overflow; see CERT INT32-C + if (x > 0) { // x is positive + if (y > 0) { // x and y are positive + overflow = (x > (LLONG_MAX / y)); + } else { // x positive, y nonpositive + overflow = (y < (LLONG_MIN / x)); + } // x positive, y nonpositive + } else { // x is nonpositive + if (y > 0) { // x is nonpositive, y is positive + overflow = (x < (LLONG_MIN / y)); + } else { // x and y are nonpositive + overflow = (x != 0 && y < (LLONG_MAX / x)); + } // End if x and y are nonpositive + } // End if x is nonpositive + + if (!overflow) { + *res = x * y; + } + + return overflow; +} +#endif + +#if __has_builtin(__builtin_saddll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW +#define mp_add_ll_overflow __builtin_saddll_overflow +#else +inline static bool mp_add_ll_overflow(long long int lhs, long long int rhs, long long int *res) { + bool overflow; + + if (rhs > 0) { + overflow = (lhs > LLONG_MAX - rhs); + } else { + overflow = (lhs < LLONG_MIN - rhs); + } + + if (!overflow) { + *res = lhs + rhs; + } + + return overflow; +} +#endif + +#if __has_builtin(__builtin_ssubll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW +#define mp_sub_ll_overflow __builtin_ssubll_overflow +#else +inline static bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long long int *res) { + bool overflow; + + if (rhs > 0) { + overflow = (lhs < LLONG_MIN + rhs); + } else { + overflow = (lhs > LLONG_MAX + rhs); + } + + if (!overflow) { + *res = lhs - rhs; + } + + return overflow; +} +#endif + #endif // MICROPY_INCLUDED_PY_MISC_H diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 5b60eb65ad8..db095032151 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -31,6 +31,7 @@ #include "py/smallint.h" #include "py/objint.h" #include "py/runtime.h" +#include "py/misc.h" #if MICROPY_PY_BUILTINS_FLOAT #include @@ -43,6 +44,10 @@ const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; #endif +static void raise_long_long_overflow(void) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("result overflows long long storage")); +} + mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { int delta = 1; if (!big_endian) { @@ -120,7 +125,6 @@ mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) { // small int if the value fits without truncation case MP_UNARY_OP_HASH: return MP_OBJ_NEW_SMALL_INT((mp_int_t)o->val); - case MP_UNARY_OP_POSITIVE: return o_in; case MP_UNARY_OP_NEGATIVE: @@ -147,6 +151,8 @@ mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) { mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { long long lhs_val; long long rhs_val; + bool overflow = false; + long long result; if (mp_obj_is_small_int(lhs_in)) { lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs_in); @@ -167,13 +173,16 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: - return mp_obj_new_int_from_ll(lhs_val + rhs_val); + overflow = mp_add_ll_overflow(lhs_val, rhs_val, &result); + break; case MP_BINARY_OP_SUBTRACT: case MP_BINARY_OP_INPLACE_SUBTRACT: - return mp_obj_new_int_from_ll(lhs_val - rhs_val); + overflow = mp_sub_ll_overflow(lhs_val, rhs_val, &result); + break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: - return mp_obj_new_int_from_ll(lhs_val * rhs_val); + overflow = mp_mul_ll_overflow(lhs_val, rhs_val, &result); + break; case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: if (rhs_val == 0) { @@ -199,9 +208,21 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i case MP_BINARY_OP_LSHIFT: case MP_BINARY_OP_INPLACE_LSHIFT: - return mp_obj_new_int_from_ll(lhs_val << (int)rhs_val); + if ((int)rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } + result = lhs_val << (int)rhs_val; + // Left-shifting of negative values is implementation defined in C, but assume compiler + // will give us typical 2s complement behaviour unless the value overflows + overflow = rhs_val > 0 && ((lhs_val >= 0 && result < lhs_val) || (lhs_val < 0 && result > lhs_val)); + break; case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: + if ((int)rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } return mp_obj_new_int_from_ll(lhs_val >> (int)rhs_val); case MP_BINARY_OP_POWER: @@ -213,18 +234,18 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); #endif } - long long ans = 1; - while (rhs_val > 0) { + result = 1; + while (rhs_val > 0 && !overflow) { if (rhs_val & 1) { - ans *= lhs_val; + overflow = mp_mul_ll_overflow(result, lhs_val, &result); } - if (rhs_val == 1) { + if (rhs_val == 1 || overflow) { break; } rhs_val /= 2; - lhs_val *= lhs_val; + overflow = mp_mul_ll_overflow(lhs_val, lhs_val, &lhs_val); } - return mp_obj_new_int_from_ll(ans); + break; } case MP_BINARY_OP_LESS: @@ -242,6 +263,12 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i return MP_OBJ_NULL; // op not supported } + if (overflow) { + raise_long_long_overflow(); + } + + return mp_obj_new_int_from_ll(result); + zero_division: mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); } @@ -267,7 +294,7 @@ mp_obj_t mp_obj_new_int_from_ll(long long val) { mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { // TODO raise an exception if the unsigned long long won't fit if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) { - mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ulonglong too large")); + raise_long_long_overflow(); } return mp_obj_new_int_from_ll(val); } diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py index 73a06b64b13..289ea49b65e 100644 --- a/tests/basics/int_64_basics.py +++ b/tests/basics/int_64_basics.py @@ -117,10 +117,21 @@ # sys.maxsize is a constant bigint, so test it's compatible with dynamic ones import sys if hasattr(sys, "maxsize"): - print(sys.maxsize + 1 - 1 == sys.maxsize) + print(sys.maxsize - 1 + 1 == sys.maxsize) else: print(True) # No maxsize property in this config # test extraction of big int value via mp_obj_get_int_maybe x = 1 << 62 print('a' * (x + 4 - x)) + +# negative shifts are invalid +try: + print((1 << 48) >> -4) +except ValueError as e: + print(e) + +try: + print((1 << 48) << -6) +except ValueError as e: + print(e) diff --git a/tests/extmod/uctypes_addressof.py b/tests/extmod/uctypes_addressof.py index c83089d0f72..213fcc05eee 100644 --- a/tests/extmod/uctypes_addressof.py +++ b/tests/extmod/uctypes_addressof.py @@ -12,5 +12,8 @@ print(uctypes.addressof(uctypes.bytearray_at(1 << i, 8))) # Test address that is bigger than the greatest small-int but still within the address range. -large_addr = maxsize + 1 -print(uctypes.addressof(uctypes.bytearray_at(large_addr, 8)) == large_addr) +try: + large_addr = maxsize + 1 + print(uctypes.addressof(uctypes.bytearray_at(large_addr, 8)) == large_addr) +except OverflowError: + print(True) # systems with 64-bit bigints will overflow on the above operation From e9845ab20ec798c1d5bf00bd3b64ff5d96d94500 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 2 May 2025 15:39:35 +1000 Subject: [PATCH 0930/2098] py/smallint: Update mp_small_int_mul_overflow() to perform the multiply. Makes it compatible with the __builtin_mul_overflow() syntax, used in follow-up commit. Includes optimisation in runtime.c to minimise the code size impact from additional param. Signed-off-by: Damien George Signed-off-by: Angus Gratton --- py/parsenum.c | 4 ++-- py/runtime.c | 13 +++++++------ py/smallint.c | 5 ++++- py/smallint.h | 5 ++++- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index 7e6695fbfcd..31b332c180e 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -99,10 +99,10 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m } // add next digi and check for overflow - if (mp_small_int_mul_overflow(int_val, base)) { + if (mp_small_int_mul_overflow(int_val, base, &int_val)) { goto overflow; } - int_val = int_val * base + dig; + int_val += dig; if (!MP_SMALL_INT_FITS(int_val)) { goto overflow; } diff --git a/py/runtime.c b/py/runtime.c index 90587a010a4..0ab0626ef94 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -505,13 +505,14 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs } #endif - if (mp_small_int_mul_overflow(lhs_val, rhs_val)) { + mp_int_t int_res; + if (mp_small_int_mul_overflow(lhs_val, rhs_val, &int_res)) { // use higher precision lhs = mp_obj_new_int_from_ll(lhs_val); goto generic_binary_op; } else { // use standard precision - return MP_OBJ_NEW_SMALL_INT(lhs_val * rhs_val); + return MP_OBJ_NEW_SMALL_INT(int_res); } } case MP_BINARY_OP_FLOOR_DIVIDE: @@ -552,19 +553,19 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs mp_int_t ans = 1; while (rhs_val > 0) { if (rhs_val & 1) { - if (mp_small_int_mul_overflow(ans, lhs_val)) { + if (mp_small_int_mul_overflow(ans, lhs_val, &ans)) { goto power_overflow; } - ans *= lhs_val; } if (rhs_val == 1) { break; } rhs_val /= 2; - if (mp_small_int_mul_overflow(lhs_val, lhs_val)) { + mp_int_t int_res; + if (mp_small_int_mul_overflow(lhs_val, lhs_val, &int_res)) { goto power_overflow; } - lhs_val *= lhs_val; + lhs_val = int_res; } lhs_val = ans; } diff --git a/py/smallint.c b/py/smallint.c index aa542ca7bf2..a494093d61a 100644 --- a/py/smallint.c +++ b/py/smallint.c @@ -26,7 +26,7 @@ #include "py/smallint.h" -bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y) { +bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y, mp_int_t *res) { // Check for multiply overflow; see CERT INT32-C if (x > 0) { // x is positive if (y > 0) { // x and y are positive @@ -49,6 +49,9 @@ bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y) { } } // End if x and y are nonpositive } // End if x is nonpositive + + // Result doesn't overflow + *res = x * y; return false; } diff --git a/py/smallint.h b/py/smallint.h index 584e0018d1b..e50f98651e6 100644 --- a/py/smallint.h +++ b/py/smallint.h @@ -68,7 +68,10 @@ // The number of bits in a MP_SMALL_INT including the sign bit. #define MP_SMALL_INT_BITS (MP_IMAX_BITS(MP_SMALL_INT_MAX) + 1) -bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y); +// Multiply two small ints. +// If returns false, the correct result is stored in 'res' +// If returns true, the multiplication would have overflowed. 'res' is unchanged. +bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y, mp_int_t *res); mp_int_t mp_small_int_modulo(mp_int_t dividend, mp_int_t divisor); mp_int_t mp_small_int_floor_divide(mp_int_t num, mp_int_t denom); From 17fbc5abdc7e139a922f6a11619deb7cb031e0cb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 15 Jul 2025 11:23:28 +1000 Subject: [PATCH 0931/2098] py/parsenum: Extend mp_parse_num_integer() to parse long long. If big integer support is 'long long' then mp_parse_num_integer() can parse to it directly instead of failing over from small int. This means strtoll() is no longer pulled in, and fixes some bugs parsing long long integers (i.e. can now parse negative values correctly, can now parse values which aren't NULL terminated). The (default) smallint parsing compiled code should stay the same here, macros and a typedef are used to abstract some parts of it out. When bigint is long long we parse to 'unsigned long long' first (to avoid the code size hit of pulling in signed 64-bit math routines) and the convert to signed at the end. One tricky case this routine correctly overflows on is int("9223372036854775808") which is one more than LLONG_MAX in decimal. No unit test case added for this as it's too hard to detect 64-bit long integer mode. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/objint_longlong.c | 10 --------- py/parsenum.c | 51 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/py/objint_longlong.c b/py/objint_longlong.c index db095032151..22ac0ba12ef 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -292,22 +292,12 @@ mp_obj_t mp_obj_new_int_from_ll(long long val) { } mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { - // TODO raise an exception if the unsigned long long won't fit if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) { raise_long_long_overflow(); } return mp_obj_new_int_from_ll(val); } -mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) { - // TODO this does not honor the given length of the string, but it all cases it should anyway be null terminated - // TODO check overflow - char *endptr; - mp_obj_t result = mp_obj_new_int_from_ll(strtoll(*str, &endptr, base)); - *str = endptr; - return result; -} - mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); diff --git a/py/parsenum.c b/py/parsenum.c index 31b332c180e..fcc69091737 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -46,6 +46,27 @@ static MP_NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { nlr_raise(exc); } +#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_LONGLONG +// For the common small integer parsing case, we parse directly to mp_int_t and +// check that the value doesn't overflow a smallint (in which case we fail over +// to bigint parsing if supported) +typedef mp_int_t parsed_int_t; + +#define PARSED_INT_MUL_OVERFLOW mp_small_int_mul_overflow +#define PARSED_INT_FITS MP_SMALL_INT_FITS +#else +// In the special case where bigint support is long long, we save code size by +// parsing directly to long long and then return either a bigint or smallint +// from the same result. +// +// To avoid pulling in (slow) signed 64-bit math routines we do the initial +// parsing to an unsigned long long and only convert to signed at the end. +typedef unsigned long long parsed_int_t; + +#define PARSED_INT_MUL_OVERFLOW mp_mul_ull_overflow +#define PARSED_INT_FITS(I) ((I) <= (unsigned long long)LLONG_MAX) +#endif + mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) { const byte *restrict str = (const byte *)str_; const byte *restrict top = str + len; @@ -76,7 +97,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m str += mp_parse_num_base((const char *)str, top - str, &base); // string should be an integer number - mp_int_t int_val = 0; + parsed_int_t parsed_val = 0; const byte *restrict str_val_start = str; for (; str < top; str++) { // get next digit as a value @@ -98,25 +119,29 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m break; } - // add next digi and check for overflow - if (mp_small_int_mul_overflow(int_val, base, &int_val)) { + // add next digit and check for overflow + if (PARSED_INT_MUL_OVERFLOW(parsed_val, base, &parsed_val)) { goto overflow; } - int_val += dig; - if (!MP_SMALL_INT_FITS(int_val)) { + parsed_val += dig; + if (!PARSED_INT_FITS(parsed_val)) { goto overflow; } } - // negate value if needed + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_LONGLONG + // The PARSED_INT_FITS check above ensures parsed_val fits in small int representation + ret_val = MP_OBJ_NEW_SMALL_INT(neg ? (-parsed_val) : parsed_val); +have_ret_val: + #else + // The PARSED_INT_FITS check above ensures parsed_val won't overflow signed long long + long long signed_val = parsed_val; if (neg) { - int_val = -int_val; + signed_val = -signed_val; } + ret_val = mp_obj_new_int_from_ll(signed_val); // Could be large or small int + #endif - // create the small int - ret_val = MP_OBJ_NEW_SMALL_INT(int_val); - -have_ret_val: // check we parsed something if (str == str_val_start) { goto value_error; @@ -135,6 +160,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m return ret_val; overflow: + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_LONGLONG // reparse using long int { const char *s2 = (const char *)str_val_start; @@ -142,6 +168,9 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m str = (const byte *)s2; goto have_ret_val; } + #else + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("result overflows long long storage")); + #endif value_error: { From 7b38fa4fa3a1c44d224492b1f75a7a9f125c6d18 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 8 Jul 2025 10:54:00 -0400 Subject: [PATCH 0932/2098] tests/basics/fun_code_lnotab: Test removal of co_lnotab from v2. Signed-off-by: Anson Mansfield --- tests/basics/fun_code_full.py | 7 ------- tests/basics/fun_code_lnotab.py | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 tests/basics/fun_code_lnotab.py diff --git a/tests/basics/fun_code_full.py b/tests/basics/fun_code_full.py index 5eb23150df0..e1c867939a2 100644 --- a/tests/basics/fun_code_full.py +++ b/tests/basics/fun_code_full.py @@ -6,12 +6,6 @@ print("SKIP") raise SystemExit -try: - import warnings - warnings.simplefilter("ignore") # ignore deprecation warning about co_lnotab -except ImportError: - pass - def f(x, y): a = x + y b = x - y @@ -25,7 +19,6 @@ def f(x, y): print(type(code.co_firstlineno)) # both ints (but mpy points to first line inside, cpy points to declaration) print(code.co_name) print(iter(code.co_names) is not None) # both iterable (but mpy returns dict with names as keys, cpy only the names; and not necessarily the same set) -print(type(code.co_lnotab)) # both bytes co_lines = code.co_lines() diff --git a/tests/basics/fun_code_lnotab.py b/tests/basics/fun_code_lnotab.py new file mode 100644 index 00000000000..9223e5730f0 --- /dev/null +++ b/tests/basics/fun_code_lnotab.py @@ -0,0 +1,34 @@ +# Test deprecation of co_lnotab + +try: + (lambda: 0).__code__.co_code +except AttributeError: + print("SKIP") + raise SystemExit + + +import unittest +import sys + + +mpy_is_v2 = getattr(sys.implementation, '_v2', False) + + +def f(): + pass + + +class Test(unittest.TestCase): + + @unittest.skipIf(mpy_is_v2, "Removed in MicroPython v2 and later.") + def test_co_lnotab_exists(self): + self.assertIsInstance(f.__code__.co_lnotab, bytes) + + @unittest.skipUnless(mpy_is_v2, "Not removed before MicroPython v2.") + def test_co_lnotab_removed(self): + with self.assertRaises(AttributeError): + f.__code__.co_lnotab + + +if __name__ == "__main__": + unittest.main() From ddf2c3afb17c0ea3dd678d02d9c2f01bed5a3020 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 8 Jul 2025 12:50:37 -0400 Subject: [PATCH 0933/2098] py/objcode: Remove co_lnotab from v2 preview. Signed-off-by: Anson Mansfield --- py/objcode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/objcode.c b/py/objcode.c index 52df84d012b..1ee33936c5a 100644 --- a/py/objcode.c +++ b/py/objcode.c @@ -69,6 +69,7 @@ static mp_obj_tuple_t *code_consts(const mp_module_context_t *context, const mp_ return consts; } +#if !MICROPY_PREVIEW_VERSION_2 static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { // const mp_bytecode_prelude_t *prelude = &rc->prelude; uint start = 0; @@ -106,6 +107,7 @@ static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) { m_del(byte, buffer, buffer_size); return o; } +#endif static mp_obj_t code_colines_iter(mp_obj_t); static mp_obj_t code_colines_next(mp_obj_t); @@ -198,12 +200,14 @@ static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { case MP_QSTR_co_names: dest[0] = MP_OBJ_FROM_PTR(o->dict_locals); break; + #if !MICROPY_PREVIEW_VERSION_2 case MP_QSTR_co_lnotab: if (!o->lnotab) { o->lnotab = raw_code_lnotab(rc); } dest[0] = o->lnotab; break; + #endif case MP_QSTR_co_lines: dest[0] = MP_OBJ_FROM_PTR(&code_colines_obj); dest[1] = self_in; From a8d50fb6536a6073e2fc6969cf8ac6a273337332 Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Wed, 9 Jul 2025 09:12:52 +0800 Subject: [PATCH 0934/2098] py/mkrules.mk: Mute blobless errors. This mutes usage error for blobless update from older `git` to reduce noise upon submodule updating. Signed-off-by: Yanfeng Liu --- py/mkrules.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mkrules.mk b/py/mkrules.mk index 495d8d48bd2..3120066fd4f 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -268,7 +268,7 @@ submodules: $(ECHO) "Updating submodules: $(GIT_SUBMODULES)" ifneq ($(GIT_SUBMODULES),) $(Q)cd $(TOP) && git submodule sync $(GIT_SUBMODULES) - $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) || \ + $(Q)cd $(TOP) && git submodule update --init --filter=blob:none $(GIT_SUBMODULES) 2>/dev/null || \ git submodule update --init $(GIT_SUBMODULES) endif .PHONY: submodules From 5f55f8d01acd8a9f2223f62dd56a99b554d7f7a2 Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Wed, 16 Jul 2025 17:15:52 +0800 Subject: [PATCH 0935/2098] tools/pyboard.py: Align execpty prefix. This aligns the prefix string in L285 to that in L284 though the two strings have equal length. Signed-off-by: Yanfeng Liu --- tools/pyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pyboard.py b/tools/pyboard.py index 40928e8bbbe..50ecd33b7ab 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -282,7 +282,7 @@ def __init__( if device.startswith("exec:"): self.serial = ProcessToSerial(device[len("exec:") :]) elif device.startswith("execpty:"): - self.serial = ProcessPtyToTerminal(device[len("qemupty:") :]) + self.serial = ProcessPtyToTerminal(device[len("execpty:") :]) elif device and device[0].isdigit() and device[-1].isdigit() and device.count(".") == 3: # device looks like an IP address self.serial = TelnetToSerial(device, user, password, read_timeout=10) From b63e5280765d2e92c5b98bbfc6714d73a8296184 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 8 Jul 2025 23:06:05 +0200 Subject: [PATCH 0936/2098] examples/natmod/btree: Fix build on RV32 with Picolibc. This commit fixes building the "btree" example natmod on RV32 when Picolibc is being used and uses thread-local storage for storing the errno variable. The fix is surprisingly simple: Picolibc allows overriding the function that will provide a pointer to the "errno" variable, and the btree natmod integration code already has all of this machinery set up as part of its library integration. Redirecting Picolibc to the already existing pointer provider function via a compile-time definition is enough to let the module compile and pass QEMU tests. This workaround will work on any Picolibc versions (Arm, RV32, Xtensa, etc.) even if TLS support was not enabled to begin with, and will effectively do nothing if the toolchain used will rely on Newlib to provide standard C library functions. Given that the btree module now builds and passes the relevant natmod tests, said module is now part of the QEMU port's natmod testing procedure, and CI now will build the btree module for RV32 as part to its checks. Signed-off-by: Alessandro Gatti --- examples/natmod/btree/Makefile | 3 +++ ports/qemu/Makefile | 3 +-- tools/ci.sh | 8 +------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index 6273ccc6572..bcaa7a93d11 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -36,6 +36,9 @@ ifeq ($(ARCH),xtensa) MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld endif +# Use our own errno implementation if Picolibc is used +CFLAGS += -D__PICOLIBC_ERRNO_FUNCTION=__errno + include $(MPY_DIR)/py/dynruntime.mk # btree needs gnu99 defined diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index e9e1e0f957e..646659ceda5 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -191,12 +191,11 @@ test_full: $(BUILD)/firmware.elf cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy cd $(TOP)/tests && ./run-tests.py $(RUN_TESTS_FULL_ARGS) --via-mpy --emit native -# "btree" currently does not build for rv32imc (Picolibc TLS incompatibility). .PHONY: test_natmod test_natmod: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && \ - for natmod in deflate framebuf heapq random_basic re; do \ + for natmod in btree deflate framebuf heapq random_basic re; do \ ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" extmod/$$natmod*.py; \ done diff --git a/tools/ci.sh b/tools/ci.sh index 564b7810f57..510bb3a4d3c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -563,7 +563,7 @@ function ci_native_mpy_modules_build { else arch=$1 fi - for natmod in deflate features1 features3 features4 framebuf heapq random re + for natmod in btree deflate features1 features3 features4 framebuf heapq random re do make -C examples/natmod/$natmod ARCH=$arch clean make -C examples/natmod/$natmod ARCH=$arch @@ -576,12 +576,6 @@ function ci_native_mpy_modules_build { else make -C examples/natmod/features2 ARCH=$arch fi - - # btree requires thread local storage support on rv32imc. - if [ $arch != "rv32imc" ]; then - make -C examples/natmod/btree ARCH=$arch clean - make -C examples/natmod/btree ARCH=$arch - fi } function ci_native_mpy_modules_32bit_build { From e993f53877027bc46b226d026845e523c2cc94ca Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 9 Jul 2025 17:14:13 +0200 Subject: [PATCH 0937/2098] docs/develop/natmod: Add notes on Picolibc and natmods. This commit adds some documentation on what are the limitations of using Picolibc as a standard C library for native modules. This also contains a reference to the "errno" issue when building natmods on RV32 that the PR this commit is part of, as it is not obvious how to approach this issue when encountered for the first time. Signed-off-by: Alessandro Gatti --- docs/develop/natmod.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index 2ccd8328856..072d78b2076 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -67,6 +67,9 @@ The known limitations are: * static BSS variables are not supported; workaround: use global BSS variables +* thread-local storage variables are not supported on rv32imc; workaround: use + global BSS variables or allocate some space on the heap to store them + So, if your C code has writable data, make sure the data is defined globally, without an initialiser, and only written to within functions. @@ -225,6 +228,26 @@ other module, for example:: print(factorial.factorial(10)) # should display 3628800 +Using Picolibc when building modules +------------------------------------ + +Using `Picolibc `_ as your C standard +library is not only supported, but in fact it is the default for the rv32imc +platform. However, there are a couple of things worth mentioning to make sure +you don't run into problems later when building code. + +Some pre-built Picolibc versions (for example, those provided by Ubuntu Linux +as the ``picolibc-arm-none-eabi``, ``picolibc-riscv64-unknown-elf``, and +``picolibc-xtensa-lx106-elf`` packages) assume thread-local storage (TLS) is +available at runtime, but unfortunately MicroPython modules do not support that +on some architectures (namely ``rv32imc``). This means that some +functionalities provided by Picolibc will default to use TLS, returning an +error either during compilation or during linking. + +For an example on how this may affect you, the ``examples/natmod/btree`` +example module contains a workaround to make sure ``errno`` works (look for +``__PICOLIBC_ERRNO_FUNCTION`` in the Makefile and follow the trail from there). + Further examples ---------------- From 18f2e94846111ad05d49e260c59de366f3ae2489 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 15 Jul 2025 13:32:55 +1000 Subject: [PATCH 0938/2098] py/modsys: Add sys.implementation._thread attribute. This is useful to distinguish between GIL and non-GIL builds. Signed-off-by: Damien George --- docs/library/sys.rst | 10 ++++++++++ py/modsys.c | 24 ++++++++++++++++++++---- tests/basics/sys1.py | 6 ++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/docs/library/sys.rst b/docs/library/sys.rst index baefd927051..cf805521785 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -77,6 +77,8 @@ Constants * *_mpy* - supported mpy file-format version (optional attribute) * *_build* - string that can help identify the configuration that MicroPython was built with + * *_thread* - optional string attribute, exists if the target has threading + and is either "GIL" or "unsafe" This object is the recommended way to distinguish MicroPython from other Python implementations (note that it still may not exist in the very @@ -95,6 +97,14 @@ Constants * On microcontroller targets, the first element is the board name and the second element (if present) is the board variant, for example ``'RPI_PICO2-RISCV'`` + The *_thread* entry was added in version 1.26.0 and if it exists then the + target has the ``_thread`` module. If the target enables the GIL (global + interpreter lock) then this attribute is ``"GIL"``. Otherwise the attribute + is ``"unsafe"`` and the target has threading but does not enable the GIL, + and mutable Python objects (such as `bytearray`, `list` and `dict`) that are + shared amongst threads must be protected explicitly by locks such as + ``_thread.allocate_lock``. + .. admonition:: Difference to CPython :class: attention diff --git a/py/modsys.c b/py/modsys.c index 9ab02293b90..ef6273fc8c7 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -103,6 +103,18 @@ static const MP_DEFINE_STR_OBJ(mp_sys_implementation__build_obj, MICROPY_BOARD_B #define SYS_IMPLEMENTATION_ELEMS__BUILD #endif +#if MICROPY_PY_THREAD +#if MICROPY_PY_THREAD_GIL +#define SYS_IMPLEMENTATION_ELEMS__THREAD \ + , MP_ROM_QSTR(MP_QSTR_GIL) +#else +#define SYS_IMPLEMENTATION_ELEMS__THREAD \ + , MP_ROM_QSTR(MP_QSTR_unsafe) +#endif +#else +#define SYS_IMPLEMENTATION_ELEMS__THREAD +#endif + #if MICROPY_PREVIEW_VERSION_2 #define SYS_IMPLEMENTATION_ELEMS__V2 \ , MP_ROM_TRUE @@ -120,6 +132,9 @@ static const qstr impl_fields[] = { #if defined(MICROPY_BOARD_BUILD_NAME) MP_QSTR__build, #endif + #if MICROPY_PY_THREAD + MP_QSTR__thread, + #endif #if MICROPY_PREVIEW_VERSION_2 MP_QSTR__v2, #endif @@ -127,20 +142,21 @@ static const qstr impl_fields[] = { static MP_DEFINE_ATTRTUPLE( mp_sys_implementation_obj, impl_fields, - 3 + MICROPY_PERSISTENT_CODE_LOAD + MICROPY_BOARD_BUILD + MICROPY_PREVIEW_VERSION_2, + 3 + MICROPY_PERSISTENT_CODE_LOAD + MICROPY_BOARD_BUILD + MICROPY_PY_THREAD + MICROPY_PREVIEW_VERSION_2, SYS_IMPLEMENTATION_ELEMS_BASE SYS_IMPLEMENTATION_ELEMS__MPY SYS_IMPLEMENTATION_ELEMS__BUILD + SYS_IMPLEMENTATION_ELEMS__THREAD SYS_IMPLEMENTATION_ELEMS__V2 ); #else static const mp_rom_obj_tuple_t mp_sys_implementation_obj = { {&mp_type_tuple}, 3 + MICROPY_PERSISTENT_CODE_LOAD, - // Do not include SYS_IMPLEMENTATION_ELEMS__BUILD or SYS_IMPLEMENTATION_ELEMS__V2 - // because SYS_IMPLEMENTATION_ELEMS__MPY may be empty if + // Do not include SYS_IMPLEMENTATION_ELEMS__BUILD, SYS_IMPLEMENTATION_ELEMS__THREAD + // or SYS_IMPLEMENTATION_ELEMS__V2 because SYS_IMPLEMENTATION_ELEMS__MPY may be empty if // MICROPY_PERSISTENT_CODE_LOAD is disabled, which means they'll share - // the same index. Cannot query _build or _v2 if MICROPY_PY_ATTRTUPLE is + // the same index. Cannot query _build, _thread or _v2 if MICROPY_PY_ATTRTUPLE is // disabled. { SYS_IMPLEMENTATION_ELEMS_BASE diff --git a/tests/basics/sys1.py b/tests/basics/sys1.py index 94220fe4a60..31081e4236a 100644 --- a/tests/basics/sys1.py +++ b/tests/basics/sys1.py @@ -30,6 +30,12 @@ # Effectively skip subtests print(str) +if hasattr(sys.implementation, '_thread'): + print(sys.implementation._thread in ("GIL", "unsafe")) +else: + # Effectively skip subtests + print(True) + try: print(sys.intern('micropython') == 'micropython') has_intern = True From 97fd18a7e299f5a00207f47c1728a9bb67a2fda0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Jul 2025 16:43:35 +1000 Subject: [PATCH 0939/2098] tests/extmod/select_poll_eintr.py: Pre-allocate global variables. This is a workaround for the case where threading is enabled without a GIL. In such a configuration, creating a new global variable is not atomic and threads have race conditions resizing/accessing the global dict. Signed-off-by: Damien George --- tests/extmod/select_poll_eintr.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/extmod/select_poll_eintr.py b/tests/extmod/select_poll_eintr.py index d9e9b319090..fdc5ee5074a 100644 --- a/tests/extmod/select_poll_eintr.py +++ b/tests/extmod/select_poll_eintr.py @@ -33,6 +33,14 @@ def thread_main(): print("thread gc end") +# Pre-allocate global variables here so the global dict is not resized by the main +# thread while the secondary thread runs. This is a workaround for the bug described +# in https://github.com/micropython/micropython/pull/11604 +poller = None +t0 = None +result = None +dt_ms = None + # Start a thread to interrupt the main thread during its call to poll. lock = _thread.allocate_lock() lock.acquire() From 377924b4438114889d5d873317c02c01078a54a5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Jul 2025 20:26:37 +1000 Subject: [PATCH 0940/2098] tests/thread/stress_aes.py: Reduce test time on PC targets. This thread stress test is quite intensive and can run for a long time on certain targets. For example, builds with stackless enabled are slower than non-stackless, and this test in stackless mode takes around 2 minutes on the unix port of MicroPython with the existing parameters of `n_thread=20` and `n_loop=5`. It's not really necessary to test 20 threads at once, that's not really going to test anything more than 10 at once. So reduce the parameters to keep the running time reasonable. Signed-off-by: Damien George --- tests/thread/stress_aes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/thread/stress_aes.py b/tests/thread/stress_aes.py index d8d0acd568a..ca25f8ad2fd 100644 --- a/tests/thread/stress_aes.py +++ b/tests/thread/stress_aes.py @@ -277,7 +277,7 @@ def thread_entry(n_loop): n_thread = 2 n_loop = 2 else: - n_thread = 20 + n_thread = 10 n_loop = 5 for i in range(n_thread): _thread.start_new_thread(thread_entry, (n_loop,)) From 167c888df99691981587107dad76ddfe8f33bff5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Jul 2025 16:19:17 +1000 Subject: [PATCH 0941/2098] tests/run-tests.py: Detect threading and automatically run thread tests. When detecting the target platform, also check if it has threading and whether the GIL is enabled or not (using the new attribute `sys.implementation._thread`). If threading is available, add the thread tests to the set of tests to run (unless the set of tests is explicitly given). With this change, the unix port no longer needs to explicitly run the set of thread tests, so that line has been removed from the Makefile. This change will make sure thread tests are run with other testing combinations. In particular, thread tests are now run: - on the unix port with the native emitter - on macOS builds - on unix qemu, the architectures MIPS, ARM and RISCV-64 Signed-off-by: Damien George --- ports/unix/Makefile | 1 - tests/feature_check/target_info.py | 4 +++- tests/run-tests.py | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 4e4e81a965c..f1ceabb117f 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -259,7 +259,6 @@ test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py test_full_no_native: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d thread cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython cat $(TOP)/tests/basics/0prelim.py | ./$(BUILD)/$(PROG) | grep -q 'abc' diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index f60f3b31919..9501d808ef2 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -20,4 +20,6 @@ "xtensawin", "rv32imc", ][sys_mpy >> 10] -print(platform, arch) +thread = getattr(sys.implementation, "_thread", None) + +print(platform, arch, thread) diff --git a/tests/run-tests.py b/tests/run-tests.py index c218afae719..e2e95884ab1 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -251,22 +251,27 @@ def detect_test_platform(pyb, args): output = run_feature_check(pyb, args, "target_info.py") if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) - platform, arch = str(output, "ascii").strip().split() + platform, arch, thread = str(output, "ascii").strip().split() if arch == "None": arch = None inlineasm_arch = detect_inline_asm_arch(pyb, args) + if thread == "None": + thread = None args.platform = platform args.arch = arch if arch and not args.mpy_cross_flags: args.mpy_cross_flags = "-march=" + arch args.inlineasm_arch = inlineasm_arch + args.thread = thread print("platform={}".format(platform), end="") if arch: print(" arch={}".format(arch), end="") if inlineasm_arch: print(" inlineasm={}".format(inlineasm_arch), end="") + if thread: + print(" thread={}".format(thread), end="") print() @@ -810,8 +815,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("cmdline/repl_sys_ps1_ps2.py") skip_tests.add("extmod/ssl_poll.py") - # Skip thread mutation tests on targets that don't have the GIL. - if args.platform in PC_PLATFORMS + ("rp2",): + # Skip thread mutation tests on targets that have unsafe threading behaviour. + if args.thread == "unsafe": for t in tests: if t.startswith("thread/mutate_"): skip_tests.add(t) @@ -1329,6 +1334,8 @@ def main(): ) if args.inlineasm_arch is not None: test_dirs += ("inlineasm/{}".format(args.inlineasm_arch),) + if args.thread is not None: + test_dirs += ("thread",) if args.platform == "pyboard": # run pyboard tests test_dirs += ("float", "stress", "ports/stm32") @@ -1337,9 +1344,9 @@ def main(): elif args.platform == "renesas-ra": test_dirs += ("float", "ports/renesas-ra") elif args.platform == "rp2": - test_dirs += ("float", "stress", "thread", "ports/rp2") + test_dirs += ("float", "stress", "ports/rp2") elif args.platform == "esp32": - test_dirs += ("float", "stress", "thread") + test_dirs += ("float", "stress") elif args.platform in ("esp8266", "minimal", "samd", "nrf"): test_dirs += ("float",) elif args.platform == "WiPy": From 081213ec9dbdf78becc1eb0c2f2837397fd47d86 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Jul 2025 01:11:09 +1000 Subject: [PATCH 0942/2098] tools/ci.sh: Increase timeout for unix qemu test runs. The qemu emulation introduces enough overhead that the `tests/thread/stress_aes.py` test overruns the default timeout. So increase it to allow this test to pass. Signed-off-by: Damien George --- tools/ci.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 510bb3a4d3c..a1700fbd3c7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -797,8 +797,10 @@ function ci_unix_qemu_mips_build { } function ci_unix_qemu_mips_run_tests { + # Issues with MIPS tests: + # - thread/stress_aes.py takes around 50 seconds file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py) + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py) } function ci_unix_qemu_arm_setup { @@ -818,8 +820,9 @@ function ci_unix_qemu_arm_build { function ci_unix_qemu_arm_run_tests { # Issues with ARM tests: # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) + # - thread/stress_aes.py takes around 70 seconds file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py --exclude 'vfs_posix.*\.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py') } function ci_unix_qemu_riscv64_setup { @@ -837,8 +840,10 @@ function ci_unix_qemu_riscv64_build { } function ci_unix_qemu_riscv64_run_tests { + # Issues with RISCV-64 tests: + # - thread/stress_aes.py takes around 140 seconds file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py) + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py) } ######################################################################################## From f835b1626d797c98b6603c1a58e270aaa8c27053 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Jul 2025 00:54:21 +1000 Subject: [PATCH 0943/2098] tools/ci.sh: Increase timeout for stackless clang test runs. Stackless mode makes `tests/thread/stress_aes.py` slow, around 75 seconds for this CI job (probably due to contention among the many threads for the GC lock, to allocate frames for function calls). So increase the timeout to allow this test to pass. Signed-off-by: Damien George --- tools/ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index a1700fbd3c7..99168cff6e4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -720,7 +720,8 @@ function ci_unix_stackless_clang_build { } function ci_unix_stackless_clang_run_tests { - ci_unix_run_tests_helper CC=clang + # Timeout needs to be increased for thread/stress_aes.py test. + MICROPY_TEST_TIMEOUT=90 ci_unix_run_tests_helper CC=clang } function ci_unix_float_clang_build { From 279f51d7d27adab0ffbf4db263f839eaf89cb659 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 12 Jul 2025 01:08:17 +1000 Subject: [PATCH 0944/2098] tools/ci.sh: Skip thread/stress_heap.py test on macOS test run. This test passes sometimes and fails other times. Eventually that should be fixed, but for now just skip this test. Signed-off-by: Damien George --- tools/ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 99168cff6e4..dbfbaa54f20 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -780,7 +780,8 @@ function ci_unix_macos_run_tests { # Issues with macOS tests: # - float_parse and float_parse_doubleprec parse/print floats out by a few mantissa bits # - ffi_callback crashes for an unknown reason - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback).py') + # - thread/stress_heap.py is flaky + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback|thread/stress_heap).py') } function ci_unix_qemu_mips_setup { From 92193112bf0750615139b2d980667055a64a92a6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Jul 2025 17:10:53 +1000 Subject: [PATCH 0945/2098] tools/ci.sh: Skip thread/stress_recurse.py on unix qemu test runs. This test passes sometimes and fails other times. Eventually that should be fixed, but for now just skip this test. Signed-off-by: Damien George --- tools/ci.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index dbfbaa54f20..6656b860f1c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -801,8 +801,9 @@ function ci_unix_qemu_mips_build { function ci_unix_qemu_mips_run_tests { # Issues with MIPS tests: # - thread/stress_aes.py takes around 50 seconds + # - thread/stress_recurse.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py) + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py') } function ci_unix_qemu_arm_setup { @@ -823,8 +824,9 @@ function ci_unix_qemu_arm_run_tests { # Issues with ARM tests: # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) # - thread/stress_aes.py takes around 70 seconds + # - thread/stress_recurse.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py|thread/stress_recurse.py') } function ci_unix_qemu_riscv64_setup { @@ -844,8 +846,9 @@ function ci_unix_qemu_riscv64_build { function ci_unix_qemu_riscv64_run_tests { # Issues with RISCV-64 tests: # - thread/stress_aes.py takes around 140 seconds + # - thread/stress_recurse.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py) + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py') } ######################################################################################## From b6460df721893ee89336641afea8e71b4d72f7c5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Jul 2025 22:35:53 +1000 Subject: [PATCH 0946/2098] unix: Allow the GIL to be enabled. The unix port can now be built with the GIL enabled, by passing MICROPY_PY_THREAD_GIL=1 on the make command line. Signed-off-by: Damien George --- ports/unix/Makefile | 4 ++++ ports/unix/mpconfigport.h | 3 +++ ports/unix/mpconfigport.mk | 1 + ports/unix/mphalport.h | 7 ++++++- ports/unix/variants/mpconfigvariant_common.h | 2 +- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index f1ceabb117f..4e9a3736aad 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -132,7 +132,11 @@ ifeq ($(MICROPY_PY_SOCKET),1) CFLAGS += -DMICROPY_PY_SOCKET=1 endif ifeq ($(MICROPY_PY_THREAD),1) +ifeq ($(MICROPY_PY_THREAD_GIL),1) +CFLAGS += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=1 +else CFLAGS += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 +endif LDFLAGS += $(LIBPTHREAD) endif diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 973b5e74ce1..c18859ecbf7 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -137,6 +137,9 @@ typedef long mp_off_t; #define MICROPY_STACKLESS_STRICT (0) #endif +// Recursive mutex is needed when threading is enabled, regardless of GIL setting. +#define MICROPY_PY_THREAD_RECURSIVE_MUTEX (MICROPY_PY_THREAD) + // Implementation of the machine module. #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/unix/modmachine.c" diff --git a/ports/unix/mpconfigport.mk b/ports/unix/mpconfigport.mk index 1557c5461f6..f5ad0a14365 100644 --- a/ports/unix/mpconfigport.mk +++ b/ports/unix/mpconfigport.mk @@ -13,6 +13,7 @@ MICROPY_PY_BTREE = 1 # _thread module using pthreads MICROPY_PY_THREAD = 1 +MICROPY_PY_THREAD_GIL = 0 # Subset of CPython termios module MICROPY_PY_TERMIOS = 1 diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 02b60d8a873..0efd6940b30 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -40,7 +40,12 @@ // // Note that we don't delay for the full TIMEOUT_MS, as execution // can't be woken from the delay. -#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) mp_hal_delay_us(500) +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) \ + do { \ + MP_THREAD_GIL_EXIT(); \ + mp_hal_delay_us(500); \ + MP_THREAD_GIL_ENTER(); \ + } while (0) void mp_hal_set_interrupt_char(char c); diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 9eeed879736..65c87431766 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -29,7 +29,7 @@ // Send raise KeyboardInterrupt directly from the signal handler rather than // scheduling it into the VM. -#define MICROPY_ASYNC_KBD_INTR (1) +#define MICROPY_ASYNC_KBD_INTR (!MICROPY_PY_THREAD_GIL) // Enable helpers for printing debugging information. #ifndef MICROPY_DEBUG_PRINTERS From b15065b95e24a939e09289ee18ce84579605b929 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Jul 2025 22:42:17 +1000 Subject: [PATCH 0947/2098] github/workflows: Add new CI job to test unix port with GIL enabled. Signed-off-by: Damien George --- .github/workflows/ports_unix.yml | 12 ++++++++++++ tools/ci.sh | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 60c0244a8f9..f3f613a789a 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -160,6 +160,18 @@ jobs: if: failure() run: tests/run-tests.py --print-failures + gil_enabled: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: source tools/ci.sh && ci_unix_gil_enabled_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_gil_enabled_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures + stackless_clang: runs-on: ubuntu-latest steps: diff --git a/tools/ci.sh b/tools/ci.sh index 6656b860f1c..30be7ec2b12 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -707,6 +707,15 @@ function ci_unix_float_run_tests { ci_unix_run_tests_helper CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" } +function ci_unix_gil_enabled_build { + ci_unix_build_helper VARIANT=standard MICROPY_PY_THREAD_GIL=1 + ci_unix_build_ffi_lib_helper gcc +} + +function ci_unix_gil_enabled_run_tests { + ci_unix_run_tests_full_helper standard MICROPY_PY_THREAD_GIL=1 +} + function ci_unix_clang_setup { sudo apt-get update sudo apt-get install clang From b070765427283fd994f217b8c7601ef9b1f9a37a Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Jul 2025 22:49:15 +1000 Subject: [PATCH 0948/2098] tests/thread: Allow thread tests to pass with the native emitter. The native emitter will not release/bounce the GIL when running code, so if it runs tight loops then no other threads get a chance to run (if the GIL is enabled). So for the thread tests, explicitly include a call to `time.sleep(0)` (or equivalent) to bounce the GIL and give other threads a chance to run. For some tests (eg `thread_coop.py`) the whole point of the test is to test that the GIL is correctly bounced. So for those cases force the use of the bytecode emitter for the busy functions. Signed-off-by: Damien George --- tests/thread/mutate_bytearray.py | 3 ++- tests/thread/mutate_dict.py | 3 ++- tests/thread/mutate_instance.py | 3 ++- tests/thread/mutate_list.py | 3 ++- tests/thread/mutate_set.py | 3 ++- tests/thread/stress_recurse.py | 3 ++- tests/thread/stress_schedule.py | 4 +++- tests/thread/thread_coop.py | 3 +++ tests/thread/thread_exc1.py | 3 ++- tests/thread/thread_gc1.py | 3 ++- tests/thread/thread_ident1.py | 3 ++- tests/thread/thread_lock3.py | 3 ++- tests/thread/thread_shared1.py | 3 ++- tests/thread/thread_shared2.py | 3 ++- tests/thread/thread_stacksize1.py | 3 ++- tests/thread/thread_stdin.py | 3 ++- 16 files changed, 34 insertions(+), 15 deletions(-) diff --git a/tests/thread/mutate_bytearray.py b/tests/thread/mutate_bytearray.py index b4664781a15..7116d291cfe 100644 --- a/tests/thread/mutate_bytearray.py +++ b/tests/thread/mutate_bytearray.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread # the shared bytearray @@ -36,7 +37,7 @@ def th(n, lo, hi): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) # check bytearray has correct contents print(len(ba)) diff --git a/tests/thread/mutate_dict.py b/tests/thread/mutate_dict.py index 3777af66248..dd5f69e6c5d 100644 --- a/tests/thread/mutate_dict.py +++ b/tests/thread/mutate_dict.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread # the shared dict @@ -38,7 +39,7 @@ def th(n, lo, hi): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) # check dict has correct contents print(sorted(di.items())) diff --git a/tests/thread/mutate_instance.py b/tests/thread/mutate_instance.py index 306ad91c95c..63f7fb1e23d 100644 --- a/tests/thread/mutate_instance.py +++ b/tests/thread/mutate_instance.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -40,7 +41,7 @@ def th(n, lo, hi): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) # check user instance has correct contents print(user.a, user.b, user.c) diff --git a/tests/thread/mutate_list.py b/tests/thread/mutate_list.py index 6f1e8812541..d7398a2f1e0 100644 --- a/tests/thread/mutate_list.py +++ b/tests/thread/mutate_list.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread # the shared list @@ -39,7 +40,7 @@ def th(n, lo, hi): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) # check list has correct contents li.sort() diff --git a/tests/thread/mutate_set.py b/tests/thread/mutate_set.py index 2d9a3e0ce9e..7dcefa1d113 100644 --- a/tests/thread/mutate_set.py +++ b/tests/thread/mutate_set.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread # the shared set @@ -33,7 +34,7 @@ def th(n, lo, hi): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) # check set has correct contents print(sorted(se)) diff --git a/tests/thread/stress_recurse.py b/tests/thread/stress_recurse.py index 73b3a40f33d..ec8b43fe8fc 100644 --- a/tests/thread/stress_recurse.py +++ b/tests/thread/stress_recurse.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -24,5 +25,5 @@ def thread_entry(): # busy wait for thread to finish while not finished: - pass + time.sleep(0) print("done") diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index 97876f0f77c..362d71aa12e 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -27,6 +27,8 @@ def task(x): n += 1 +# This function must always use the bytecode emitter so it bounces the GIL when running. +@micropython.bytecode def thread(): while thread_run: try: @@ -46,7 +48,7 @@ def thread(): # Wait up to 10 seconds for 10000 tasks to be scheduled. t = time.ticks_ms() while n < _NUM_TASKS and time.ticks_diff(time.ticks_ms(), t) < _TIMEOUT_MS: - pass + time.sleep(0) # Stop all threads. thread_run = False diff --git a/tests/thread/thread_coop.py b/tests/thread/thread_coop.py index aefc4af074d..85cda789c93 100644 --- a/tests/thread/thread_coop.py +++ b/tests/thread/thread_coop.py @@ -7,6 +7,7 @@ import _thread import sys from time import ticks_ms, ticks_diff, sleep_ms +import micropython done = False @@ -21,6 +22,8 @@ MAX_DELTA = 100 +# This function must always use the bytecode emitter so the VM can bounce the GIL when running. +@micropython.bytecode def busy_thread(): while not done: pass diff --git a/tests/thread/thread_exc1.py b/tests/thread/thread_exc1.py index cd877409291..cd6599983c1 100644 --- a/tests/thread/thread_exc1.py +++ b/tests/thread/thread_exc1.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -34,5 +35,5 @@ def thread_entry(): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) print("done") diff --git a/tests/thread/thread_gc1.py b/tests/thread/thread_gc1.py index b36ea9d4c84..45c17cc17be 100644 --- a/tests/thread/thread_gc1.py +++ b/tests/thread/thread_gc1.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import gc import _thread @@ -44,6 +45,6 @@ def thread_entry(n): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) print(n_correct == n_finished) diff --git a/tests/thread/thread_ident1.py b/tests/thread/thread_ident1.py index 2a3732eff53..08cfd3eb36e 100644 --- a/tests/thread/thread_ident1.py +++ b/tests/thread/thread_ident1.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -27,6 +28,6 @@ def thread_entry(): new_tid = _thread.start_new_thread(thread_entry, ()) while not finished: - pass + time.sleep(0) print("done", type(new_tid) == int, new_tid == tid) diff --git a/tests/thread/thread_lock3.py b/tests/thread/thread_lock3.py index a927dc6829e..c5acfa21b7d 100644 --- a/tests/thread/thread_lock3.py +++ b/tests/thread/thread_lock3.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread lock = _thread.allocate_lock() @@ -26,4 +27,4 @@ def thread_entry(idx): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) diff --git a/tests/thread/thread_shared1.py b/tests/thread/thread_shared1.py index 251e26fae6c..c2e33abcec7 100644 --- a/tests/thread/thread_shared1.py +++ b/tests/thread/thread_shared1.py @@ -2,6 +2,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -40,5 +41,5 @@ def thread_entry(n, tup): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) print(tup) diff --git a/tests/thread/thread_shared2.py b/tests/thread/thread_shared2.py index a1223c2b94f..4ce9057ca01 100644 --- a/tests/thread/thread_shared2.py +++ b/tests/thread/thread_shared2.py @@ -3,6 +3,7 @@ # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +import time import _thread @@ -31,5 +32,5 @@ def thread_entry(n, lst, idx): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) print(lst) diff --git a/tests/thread/thread_stacksize1.py b/tests/thread/thread_stacksize1.py index 140d165cb34..75e1da9642f 100644 --- a/tests/thread/thread_stacksize1.py +++ b/tests/thread/thread_stacksize1.py @@ -3,6 +3,7 @@ # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd import sys +import time import _thread # different implementations have different minimum sizes @@ -51,5 +52,5 @@ def thread_entry(): # busy wait for threads to finish while n_finished < n_thread: - pass + time.sleep(0) print("done") diff --git a/tests/thread/thread_stdin.py b/tests/thread/thread_stdin.py index a469933f19b..498b0a3a270 100644 --- a/tests/thread/thread_stdin.py +++ b/tests/thread/thread_stdin.py @@ -5,6 +5,7 @@ # This is a regression test for https://github.com/micropython/micropython/issues/15230 # on rp2, but doubles as a general property to test across all ports. import sys +import time import _thread try: @@ -38,7 +39,7 @@ def is_done(self): # have run yet. The actual delay is <20ms but spinning here instead of # sleep(0.1) means the test can run on MP builds without float support. while not thread_waiter.is_done(): - pass + time.sleep(0) # The background thread should have completed its wait by now. print(thread_waiter.is_done()) From 677a0e01248bc7075f342c7df9671b006bf76c89 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Jul 2025 15:04:14 +1000 Subject: [PATCH 0949/2098] docs/reference/speed_python: Document schedule/GIL limitation of native. Signed-off-by: Damien George --- docs/reference/speed_python.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/reference/speed_python.rst b/docs/reference/speed_python.rst index 64fd9df6c05..9360fd61080 100644 --- a/docs/reference/speed_python.rst +++ b/docs/reference/speed_python.rst @@ -246,6 +246,13 @@ There are certain limitations in the current implementation of the native code e * Context managers are not supported (the ``with`` statement). * Generators are not supported. * If ``raise`` is used an argument must be supplied. +* The background scheduler (see `micropython.schedule`) is not run during + execution of native code. +* On targets with thrteading and the GIL, the GIL is not released during + execution of native code. + +To mitigate the last two points, long running native functions should call +``time.sleep(0)`` periodically, which will run the scheduler and bounce the GIL. The trade-off for the improved performance (roughly twice as fast as bytecode) is an increase in compiled code size. From 8a457b8cf9c9aa6fc2f09f64914c3646827dbae2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Jul 2025 22:53:59 +1000 Subject: [PATCH 0950/2098] tools/ci.sh: Change averaging to 1 for run-perfbench.py test. The `run-perfbench.py` test is run as part of CI, but the actual performance results are not used. Rather, the test is just testing that all the performance tests run correctly. So there's no need to run with the default averaging of 8 (which runs each test 8 times and takes the average time for the performance result) which can take a lot of time for slower builds, eg unix sanitize, settrace and stackless builds. This commit changes the averaging to just 1. Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 30be7ec2b12..3e695c63a64 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -538,7 +538,7 @@ function ci_unix_run_tests_helper { function ci_unix_run_tests_full_extra { micropython=$1 (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-multitests.py multi_net/*.py) - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-perfbench.py 1000 1000) + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=$micropython ./run-perfbench.py --average 1 1000 1000) } function ci_unix_run_tests_full_no_native_helper { From b7e734bfb7494e83c7be42ebe222ec1988278f19 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 15 Jul 2025 17:14:10 +1000 Subject: [PATCH 0951/2098] tests/net_inet: Update micropython.org certificate for SSL tests. Signed-off-by: Damien George --- tests/net_inet/mpycert.der | Bin 1289 -> 1290 bytes tests/net_inet/ssl_cert.py | 68 +++++++++++------------ tests/net_inet/test_sslcontext_client.py | 2 +- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/net_inet/mpycert.der b/tests/net_inet/mpycert.der index ac22dcf9e8b888a98c10bd12f2108feb696b68c8..0b0eabc9bc8135de25785cb8574ab78e03947aa9 100644 GIT binary patch delta 850 zcmV-Y1Figt3W^FKFoFdJFoFW^paTK{0s;{LihVv4)-N#cBZj>x2WvF=ksq`HF_F|E zf4YafTpIv6%gPGP)MpU+0X(HnrD4k^%2N9k0k^p*^(Y?gC$l6NIcEtB2<+fGuse4S zz4PTcR6z_*u0htBbly?h?|c7Py{NYn_TDko@^Z;?Y72vOm7GQZWE7e6vDqTo&8U*9 z942}{{_j`?dkB?*+i?$$`{OYQy9vHfe@$PHk#;>=ot}g8>BMHU;Ra?yNEm=+pf?zr zn}jl#v9i6y_!R)1Cy4vvV;7cenxdX~KE%NnGG0P~pV9p#`jaKy3iD!a+Z$ zShz36T$`o437+x(>#&76{iq7>MZ|dPWh%nYUp(I9TumGI3nwe83qp5OPkCw`0*Bd? z^#NHF#m`2h>h#0FdTf=%EwEmaFDB!;lVSp=e@^0wT?zvnkT}rwANg@y_`{E~H;8&~ zuq7YuzPH$h29D_ogw1llf%E*z&r?Y4z4mEB)TI$VHOLdPk{tzK>MqpS_({X9^_iBO z?8TQTRbT9 z47O}D2u;^G?MN*y1!%5-UGGfX*dRe)Xz{yR&EpQR3^9kBL*7Y4;m8C((L5W6MKEco zsd5f^5*#tuJ|kD%r%#~p9D&hyA8*#uxu~-P7J)fSpH;5XYUH!O>ZR46OQXrwe>-yX zO7>K%4E#9vV1VClHOh6NROBzbZWqZKr8l*rn!4=tz;#AXcc|!DR*_~cZr&+@)~1&Q zUSvhSEY9j=LPW5`Pp$B@+yly)n+bxIf(>JeX89R%_|>_I5aNlL2{rkm9KMosBXsGV cAJ;odnuJ&o!t}jdjqGAhZE6k<}r8 z&sXK<#8eXPv`7BQCs=kyP$-NYK8W81oz=A%DZSmbTEF`C&ZEAs03l0aT3S2-FU42` z_KE+F5ku?2wcr#7mXB{A*XO!{ISF-Tx5BR&TQ?>s=cDuoD{UJ8t6031u0a3k%U8s; zTYq>B7-75@=TyeKNs-NN7}0=*?xtmbHoSD|yiiQ){2}u=7L$~^u+wWe2F(@{ddgCD z$gOs=!kk^0MO<%Td&Vv9JQGre0ohmPI2_-NwI7m^n&(O!%tfejRq(LpqX+E$ryFLT zKS+ylgmGBOI{NQk@>@hJJap%evPZ!OYR#Ir4VL#3fhJK@3hZI__x0lPipZ4$4ULob z0a+Bgyu(MO5xQ~qk&w{ z!(IKopm5c(mM9_%(()+J4CmD5LyROM9l=79(Fw1gOZ3QaPaqbtTHOKmANU_(YZNE2 za*boI?&8i%&o|w>qtwmktx&DKzvJvaVm5G@F{kK_-RffE60Y@})IBT=s?eNyzUn=H zTtse`F=JYN)_zYUpi2dRUT@OW>R&TU6V_L@%HC19U84ztbp6M8p0l~E7TQWHmczD- zVvqTB8KRy8-sx|fun*FpTK0up!UVU-)lM?RzXV;pDfah)j|^QrQ^`P(i~F_+Wx1pw z)d}SjgoHPO&JZa4cEScFdPY)B+Ea3Wq$fXOaIC&*NC5k158GRz=+^@wE!WBRnL;^( zL*I7_5(x1n*1D7#R2Yb*#g5?`nUi^I>>T+qK(Nd*0N-9e zOJBmbMm1772Ee_ltWv=uphTCM_Y9yRr=e_dM-~?WXu!&*CS?SCv#!bKlvToiZ<{zB buW(=WzWV|ozh)/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R10 +# 1 s:C=US, O=Let's Encrypt, CN=R11 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 # a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT @@ -14,39 +14,39 @@ # Then convert to hex format using: for i in range(0,len(data),40):print(data[i:i+40].hex()) ca_cert_chain = bytes.fromhex( - "30820505308202eda00302010202104ba85293f79a2fa273064ba8048d75d0300d06092a864886f7" - "0d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e6574" - "2053656375726974792052657365617263682047726f7570311530130603550403130c4953524720" - "526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a30" - "33310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074310c" - "300a0603550403130352313030820122300d06092a864886f70d01010105000382010f003082010a" - "0282010100cf57e5e6c45412edb447fec92758764650288c1d3e88df059dd5b51829bdddb55abffa" - "f6cea3beaf00214b625a5a3c012fc55803f689ff8e1143ebc1b5e01407968f6f1fd7e7ba81390975" - "65b7c2af185b372628e7a3f4072b6d1affab58bc95ae40ffe9cb57c4b55b7f780d1861bc17e754c6" - "bb4991cd6e18d18085eea66536bc74eabc504ceafc21f338169394bab0d36b3806cd16127aca5275" - "c8ad76b2c29c5d98455c6f617bc62dee3c13528601d957e6381cdf8db51f92919ae74a1ccc45a872" - "55f0b0e6a307ecfda71b669e3f488b71847158c93afaef5ef25b442b3c74e78fb247c1076acd9ab7" - "0d96f712812651540aec61f6f7f5e2f28ac8950d8d0203010001a381f83081f5300e0603551d0f01" - "01ff040403020186301d0603551d250416301406082b0601050507030206082b0601050507030130" - "120603551d130101ff040830060101ff020100301d0603551d0e04160414bbbcc347a5e4bca9c6c3" - "a4720c108da235e1c8e8301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6" - "e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f2f" - "78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c01020130270603" - "551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d06" - "092a864886f70d01010b0500038202010092b1e74137eb799d81e6cde225e13a20e9904495a3815c" - "cfc35dfdbda070d5b19628220bd2f228cf0ce7d4e6438c24221dc14292d109af9f4bf4c8704f2016" - "b15add01f61ff81f616b1427b0728d63aeeee2ce4bcf37ddbba3d4cde7ad50adbdbfe3ec3e623670" - "9931a7e88dddea62e212aef59cd43d2c0caad09c79beea3d5c446e9631635a7dd67e4f24a04b057f" - "5e6fd2d4ea5f334b13d657b6cade51b85da3098274fdc7789eb3b9ac16da4a2b96c3b68b628ff974" - "19a29e03dee96f9bb00fd2a05af6855cc204b7c8d54e32c4bf045dbc29f6f7818f0c5d3c53c94090" - "8bfbb60865b9a421d509e51384843782ce1028fc76c206257a46524dda5372a4273f6270acbe6948" - "00fb670fdb5ba1e8d703212dd7c9f69942398343df770a1208f125d6ba9419541888a5c58ee11a99" - "93796bec1cf93140b0cc3200df9f5ee7b492ab9082918d0de01e95ba593b2e4b5fc2b74635523906" - "c0bdaaac52c122a0449799f70ca021a7a16c714716170168c0caa62665047cb3aec9e79455c26f9b" - "3c1ca9f92ec5201af076e0beec18d64fd825fb7611e8bfe6210fe8e8ccb5b6a7d5b8f79f41cf6122" - "466a83b668972e7cea4e95db23eb2ec82b2884a460e949f4442e3bf9ca625701e25d9016f9c9fc7a" - "23488ea6d58172f128fa5dcefbed4e738f942ed241949899dba7af705ff5befb0220bf66276cb4ad" - "fa75120b2b3ece039e" + "30820506308202eea0030201020211008a7d3e13d62f30ef2386bd29076b34f8300d06092a864886" + "f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65" + "742053656375726974792052657365617263682047726f7570311530130603550403130c49535247" + "20526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a" + "3033310b300906035504061302555331163014060355040a130d4c6574277320456e637279707431" + "0c300a0603550403130352313130820122300d06092a864886f70d01010105000382010f00308201" + "0a0282010100ba87bc5c1b0039cbca0acdd46710f9013ca54ea561cb26ca52fb1501b7b928f5281e" + "ed27b324183967090c08ece03ab03b770ebdf3e53954410c4eae41d69974de51dbef7bff58bda8b7" + "13f6de31d5f272c9726a0b8374959c4600641499f3b1d922d9cda892aa1c267a3ffeef58057b0895" + "81db710f8efbe33109bb09be504d5f8f91763d5a9d9e83f2e9c466b3e106664348188065a037189a" + "9b843297b1b2bdc4f815009d2788fbe26317966c9b27674bc4db285e69c279f0495ce02450e1c4bc" + "a105ac7b406d00b4c2413fa758b82fc55c9ba5bb099ef1feebb08539fda80aef45c478eb652ac2cf" + "5f3cdee35c4d1bf70b272baa0b4277534f796a1d87d90203010001a381f83081f5300e0603551d0f" + "0101ff040403020186301d0603551d250416301406082b0601050507030206082b06010505070301" + "30120603551d130101ff040830060101ff020100301d0603551d0e04160414c5cf46a4eaf4c3c07a" + "6c95c42db05e922f26e3b9301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" + "f6e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f" + "2f78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c010201302706" + "03551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d" + "06092a864886f70d01010b050003820201004ee2895d0a031c9038d0f51ff9715cf8c38fb237887a" + "6fb0251fedbeb7d886068ee90984cd72bf81f3fccacf5348edbdf66942d4a5113e35c813b2921d05" + "5fea2ed4d8f849c3adf599969cef26d8e1b4240b48204dfcd354b4a9c621c8e1361bff77642917b9" + "f04bef5deacd79d0bf90bfbe23b290da4aa9483174a9440be1e2f62d8371a4757bd294c10519461c" + "b98ff3c47448252a0de5f5db43e2db939bb919b41f2fdf6a0e8f31d3630fbb29dcdd662c3fb01b67" + "51f8413ce44db9acb8a49c6663f5ab85231dcc53b6ab71aedcc50171da36ee0a182a32fd09317c8f" + "f673e79c9cb54a156a77825acfda8d45fe1f2a6405303e73c2c60cb9d63b634aab4603fe99c04640" + "276063df503a0747d8154a9fea471f995a08620cb66c33084dd738ed482d2e0568ae805def4cdcd8" + "20415f68f1bb5acde30eb00c31879b43de4943e1c8043fd13c1b87453069a8a9720e79121c31d83e" + "2357dda74fa0f01c81d1771f6fd6d2b9a8b3031681394b9f55aed26ae4b3bfeaa5d59f4ba3c9d63b" + "72f34af654ab0cfc38f76080df6e35ca75a154e42fbc6e17c91aa537b5a29abaecf4c075464f77a8" + "e8595691662d6ede2981d6a697055e6445be2cceea644244b0c34fadf0b4dc03ca999b098295820d" + "638a66f91972f8d5b98910e289980935f9a21cbe92732374e99d1fd73b4a9a845810c2f3a7e235ec" + "7e3b45ce3046526bc0c0" ) diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 119a42721fa..0c83abb7333 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -5,7 +5,7 @@ # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R10 +# 1 s:C=US, O=Let's Encrypt, CN=R11 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 # a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT From cc774c3daf68f3e2e1e920d7728b3c07cf6da32e Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Jul 2025 15:27:39 +1000 Subject: [PATCH 0952/2098] tools/pyboard.py: Add timeout argument to Pyboard.exec_/exec. Signed-off-by: Damien George --- tools/pyboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pyboard.py b/tools/pyboard.py index 50ecd33b7ab..4099de299b2 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -530,8 +530,8 @@ def eval(self, expression, parse=False): return ret # In Python3, call as pyboard.exec(), see the setattr call below. - def exec_(self, command, data_consumer=None): - ret, ret_err = self.exec_raw(command, data_consumer=data_consumer) + def exec_(self, command, timeout=10, data_consumer=None): + ret, ret_err = self.exec_raw(command, timeout, data_consumer) if ret_err: raise PyboardError("exception", ret, ret_err) return ret From 941b7e35acd8bd7597ba6a6111cb8e94662094b5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Jul 2025 15:28:15 +1000 Subject: [PATCH 0953/2098] tests/run-tests.py: Use TEST_TIMEOUT as timeout for bare-metal tests. This parameter is already used for PC-based tests (eg unix and webassembly ports), and it makes sense for it to be used for bare-metal ports as well. That way the timeout is configurable for all targets. Because this increases the default timeout from 10s to 30s, this fixes some long-running tests that would previously fail due to a timeout such as `thread/stress_aes.py` on ESP32. Signed-off-by: Damien George --- tests/run-tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index e2e95884ab1..522027c1f3e 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -15,7 +15,7 @@ import threading import tempfile -# Maximum time to run a PC-based test, in seconds. +# Maximum time to run a single test, in seconds. TEST_TIMEOUT = float(os.environ.get('MICROPY_TEST_TIMEOUT', 30)) # See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] @@ -333,7 +333,7 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): try: had_crash = False pyb.enter_raw_repl() - output_mupy = pyb.exec_(script) + output_mupy = pyb.exec_(script, timeout=TEST_TIMEOUT) except pyboard.PyboardError as e: had_crash = True if not is_special and e.args[0] == "exception": From 79b2d4ff222636075a9b6530e1694fa277132106 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Jul 2025 16:49:26 +1000 Subject: [PATCH 0954/2098] esp32/machine_uart: Improve sendbreak so it doesn't reconfig the UART. Currently, `UART.sendbreak()` on esp32 will reconfigure the UART to a slower baudrate and send out a null byte, to synthesise a break condition. That's not great because it changes the baudrate of the RX path as well, which could miss incoming bytes while sending the break. This commit changes the sendbreak implementation to just reconfigure the TX pin as GPIO in output mode, and hold the pin low for the required duration. Signed-off-by: Damien George --- ports/esp32/machine_uart.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 982d9a7e27a..661c07138e5 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -508,20 +508,21 @@ static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { } static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { - // Save settings + // Calculate the length of the break, as 13 bits. uint32_t baudrate; check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); + uint32_t break_delay_us = 13000000 / baudrate; - // Synthesise the break condition by reducing the baud rate, - // and cater for the worst case of 5 data bits, no parity. - check_esp_err(uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000))); - check_esp_err(uart_set_baudrate(self->uart_num, baudrate * 6 / 15)); - char buf[1] = {0}; - uart_write_bytes(self->uart_num, buf, 1); + // Wait for any outstanding data to be transmitted. check_esp_err(uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000))); - // Restore original setting - check_esp_err(uart_set_baudrate(self->uart_num, baudrate)); + // Set the TX pin to output, pull it low, and wait for the break period. + mp_hal_pin_output(self->tx); + mp_hal_pin_write(self->tx, 0); + mp_hal_delay_us(break_delay_us); + + // Restore original UART pin settings. + check_esp_err(uart_set_pin(self->uart_num, self->tx, self->rx, self->rts, self->cts)); } // Configure the timer used for IRQ_RXIDLE From 1ab1f857b3eef3608cebb623526301621086faa1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Jul 2025 16:49:42 +1000 Subject: [PATCH 0955/2098] tests/extmod_hardware/machine_uart_irq_break.py: Remove send_uart. This is no longer needed, the esp32 port can now pass this test using just a single UART. Signed-off-by: Damien George --- .../extmod_hardware/machine_uart_irq_break.py | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/tests/extmod_hardware/machine_uart_irq_break.py b/tests/extmod_hardware/machine_uart_irq_break.py index f255e0f0e67..879f9cee676 100644 --- a/tests/extmod_hardware/machine_uart_irq_break.py +++ b/tests/extmod_hardware/machine_uart_irq_break.py @@ -19,18 +19,13 @@ if "ESP32S2" in _machine or "ESP32C3" in _machine or "ESP32C6" in _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 + uart_id = 1 + tx_pin = 4 + rx_pin = 5 elif "rp2" in sys.platform: - recv_uart_id = 0 - send_uart_id = 0 - recv_tx_pin = "GPIO0" - recv_rx_pin = "GPIO1" + uart_id = 0 + tx_pin = "GPIO0" + rx_pin = "GPIO1" else: print("Please add support for this test on this platform.") raise SystemExit @@ -42,22 +37,17 @@ def irq(u): # 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) + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + uart.irq(irq, uart.IRQ_BREAK) print("write", bits_per_s) for i in range(3): - send_uart.write(str(i)) - send_uart.flush() + uart.write(str(i)) + uart.flush() time.sleep_ms(10) - send_uart.sendbreak() + 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() + uart.read() print("done") From 10ef3e4ac2767b68f746f0ebb995672076eee67d Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 20 Jul 2025 00:19:08 +1000 Subject: [PATCH 0956/2098] esp32: Update to use ESP-IDF v5.4.2. This is a patch release of the IDF. Comparing with 5.4.1, firmware size is up by about 1.5k on ESP32 and 9k on ESP32-S3. But IRAM usage (of the IDF) is down by about 500 byte on ESP32 and DRAM usage is down by about 20k on ESP32 and 10k on ESP32-S3. Testing on ESP32, ESP32-S2, ESP32-S3 and ESP32-C3 shows no regressions, except in BLE MTU ordering (the MTU exchange event occuring before the connect event). Signed-off-by: Damien George --- ports/esp32/README.md | 8 ++++---- ports/esp32/tools/metrics_esp32.py | 2 +- tools/ci.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index d8b55e45f3b..4adff66328d 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -31,7 +31,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.2, v5.2.2, v5.3, v5.4 and v5.4.1. +Currently MicroPython supports v5.2, v5.2.2, v5.3, v5.4, v5.4.1 and v5.4.2. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). @@ -49,10 +49,10 @@ The steps to take are summarised below. To check out a copy of the IDF use git clone: ```bash -$ git clone -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git +$ git clone -b v5.4.2 --recursive https://github.com/espressif/esp-idf.git ``` -You can replace `v5.4.1` with any other supported version. +You can replace `v5.4.2` with any other supported version. (You don't need a full recursive clone; see the `ci_esp32_setup` function in `tools/ci.sh` in this repository for more detailed set-up commands.) @@ -61,7 +61,7 @@ MicroPython and update the submodules using: ```bash $ cd esp-idf -$ git checkout v5.4.1 +$ git checkout v5.4.2 $ git submodule update --init --recursive ``` diff --git a/ports/esp32/tools/metrics_esp32.py b/ports/esp32/tools/metrics_esp32.py index 9efaae63a9b..70f049c3bd4 100755 --- a/ports/esp32/tools/metrics_esp32.py +++ b/ports/esp32/tools/metrics_esp32.py @@ -37,7 +37,7 @@ import subprocess from dataclasses import dataclass -IDF_VERS = ("v5.4.1",) +IDF_VERS = ("v5.4.2",) BUILDS = ( ("ESP32_GENERIC", ""), diff --git a/tools/ci.sh b/tools/ci.sh index 3e695c63a64..594bca80044 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -169,7 +169,7 @@ function ci_cc3200_build { # ports/esp32 # GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) -IDF_VER=v5.4.1 +IDF_VER=v5.4.2 PYTHON=$(command -v python3 2> /dev/null) PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) From c6423d5d8e47ee0dc6b60fe150b4a2b540116940 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 20 Jul 2025 22:01:54 +1000 Subject: [PATCH 0957/2098] tests/multi_bluetooth: Synchronise MTU exchange in BLE MTU tests. With the recent update to ESP-IDF 5.4.2, there is a change in BLE event behaviour which makes `tests/multi_bluetooth/ble_mtu.py` and `tests/multi_bluetooth/ble_mtu_peripheral.py` now fail on ESP32 with IDF 5.4.2. The change in behaviour is that MTU_EXCHANGE events can now occur before CENTRAL_CONNECT/PERIPHERAL_CONNECT events. That seems a bit strange, because the MTU exchange occurs after the connection. And looking at the timing of the events there is exactly 100ms between them, ie MTU_EXCHANGE fires and then exactly 100ms later CENTRAL_CONNECT/PERIPHERAL_CONNECT fires. It's unknown if this is a bug in (Espressif's) NimBLE, a subtle change in scheduling with still valid behaviour, an intended change, a change allowed under the BLE spec, or something else. But in order to move forward with updating to IDF 5.4.2, the relevant tests have been adjusted so they can pass. The test just needs to wait a bit between doing the connect and doing the MTU exchange, so the other side sees the original/correct ordering of events. This wait is done using the multitest synchronisation primitives (broadcast and wait). Signed-off-by: Damien George --- tests/multi_bluetooth/ble_mtu.py | 10 ++++++++++ tests/multi_bluetooth/ble_mtu_peripheral.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py index 5f00b270cc0..92a40fbb4bc 100644 --- a/tests/multi_bluetooth/ble_mtu.py +++ b/tests/multi_bluetooth/ble_mtu.py @@ -106,6 +106,9 @@ def instance0(): # Wait for central to connect to us. conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + # Inform the central that we have been connected to. + multitest.broadcast("peripheral is connected") + mtu = wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS) multitest.wait(f"client:discovery:{i}") @@ -138,6 +141,13 @@ def instance1(): ble.gap_connect(BDADDR[0], BDADDR[1], TIMEOUT_MS) conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + # Wait for peripheral to be ready for MTU exchange. + # On ESP32 with NimBLE and IDF 5.4.2 (at least), if the MTU exchange occurs + # immediately after the peripheral connect event, the peripheral may see its + # _IRQ_MTU_EXCHANGED event before its _IRQ_CENTRAL_CONNECT event. The wait + # here ensures correct event ordering on the peripheral. + multitest.wait("peripheral is connected") + # Central-initiated mtu exchange. print("gattc_exchange_mtu") ble.gattc_exchange_mtu(conn_handle) diff --git a/tests/multi_bluetooth/ble_mtu_peripheral.py b/tests/multi_bluetooth/ble_mtu_peripheral.py index 1c0de40a0c4..50882bcd79c 100644 --- a/tests/multi_bluetooth/ble_mtu_peripheral.py +++ b/tests/multi_bluetooth/ble_mtu_peripheral.py @@ -101,6 +101,13 @@ def instance0(): # Wait for central to connect to us. conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + # Wait for central to be ready for MTU exchange. + # On ESP32 with NimBLE and IDF 5.4.2 (at least), if the MTU exchange occurs + # immediately after the central connect event, the central may see its + # _IRQ_MTU_EXCHANGED event before its _IRQ_PERIPHERAL_CONNECT event. The + # wait here ensures correct event ordering on the central. + multitest.wait("central is connected") + # Peripheral-initiated mtu exchange. print("gattc_exchange_mtu") ble.gattc_exchange_mtu(conn_handle) @@ -142,6 +149,9 @@ def instance1(): ble.gap_connect(BDADDR[0], BDADDR[1], 5000) conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + # Inform the peripheral that we have been connected to. + multitest.broadcast("central is connected") + mtu = wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS) print("gattc_discover_characteristics") From 41e0ec96cb10580c8d77156ed51c2e34bc2fc0ac Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 5 Jun 2025 15:32:01 +1000 Subject: [PATCH 0958/2098] extmod/mbedtls: Implement DTLS HelloVerify cookie support. This is already enabled in the ESP-IDF mbedTLS config, so provide an implementation of the cookie store functions. This allows DTLS connections between two esp32 boards. The session cookie store is a very simple dictionary associated with the SSLContext. To work, the server needs to reuse the same SSLContext (but cookies are never cleaned up, so a server with a high number of clients should recycle the context periodically.) Server code still needs to handle the MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED error by waiting for the next UDP packet from the client. Signed-off-by: Angus Gratton --- extmod/modtls_mbedtls.c | 53 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 71a14adcff1..4bd0aea9ab3 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -62,6 +62,9 @@ #include "mbedtls/ecdsa.h" #include "mbedtls/asn1.h" #endif +#ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY +#include "mbedtls/ssl_cookie.h" +#endif #ifndef MICROPY_MBEDTLS_CONFIG_BARE_METAL #define MICROPY_MBEDTLS_CONFIG_BARE_METAL (0) @@ -92,6 +95,9 @@ typedef struct _mp_obj_ssl_context_t { #if MICROPY_PY_SSL_ECDSA_SIGN_ALT mp_obj_t ecdsa_sign_callback; #endif + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + mbedtls_ssl_cookie_ctx cookie_ctx; + #endif } mp_obj_ssl_context_t; // This corresponds to an SSLSocket object. @@ -117,7 +123,8 @@ static const mp_obj_type_t ssl_socket_type; static const MP_DEFINE_STR_OBJ(mbedtls_version_obj, MBEDTLS_VERSION_STRING_FULL); static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t sock, - bool server_side, bool do_handshake_on_connect, mp_obj_t server_hostname); + bool server_side, bool do_handshake_on_connect, mp_obj_t server_hostname, + mp_obj_t client_id); /******************************************************************************/ // Helper functions. @@ -320,6 +327,16 @@ static mp_obj_t ssl_context_make_new(const mp_obj_type_t *type_in, size_t n_args mbedtls_ssl_conf_dbg(&self->conf, mbedtls_debug, NULL); #endif + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + mbedtls_ssl_cookie_init(&self->cookie_ctx); + ret = mbedtls_ssl_cookie_setup(&self->cookie_ctx, mbedtls_ctr_drbg_random, &self->ctr_drbg); + if (ret != 0) { + mbedtls_raise_error(ret); + } + mbedtls_ssl_conf_dtls_cookies(&self->conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, + &self->cookie_ctx); + #endif + return MP_OBJ_FROM_PTR(self); } @@ -366,6 +383,11 @@ static mp_obj_t ssl_context___del__(mp_obj_t self_in) { mbedtls_ctr_drbg_free(&self->ctr_drbg); mbedtls_entropy_free(&self->entropy); mbedtls_ssl_config_free(&self->conf); + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + if (self->is_dtls_server) { + mbedtls_ssl_cookie_free(&self->cookie_ctx); + } + #endif return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(ssl_context___del___obj, ssl_context___del__); @@ -468,11 +490,14 @@ static mp_obj_t ssl_context_load_verify_locations(mp_obj_t self_in, mp_obj_t cad static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_load_verify_locations_obj, ssl_context_load_verify_locations); static mp_obj_t ssl_context_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_server_side, ARG_do_handshake_on_connect, ARG_server_hostname }; + enum { ARG_server_side, ARG_do_handshake_on_connect, ARG_server_hostname, ARG_client_id }; static const mp_arg_t allowed_args[] = { { MP_QSTR_server_side, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_do_handshake_on_connect, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, { MP_QSTR_server_hostname, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + { MP_QSTR_client_id, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #endif }; // Parse arguments. @@ -481,9 +506,14 @@ static mp_obj_t ssl_context_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_obj_t client_id = mp_const_none; + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + client_id = args[ARG_client_id].u_obj; + #endif + // Create and return the new SSLSocket object. return ssl_socket_make_new(self, sock, args[ARG_server_side].u_bool, - args[ARG_do_handshake_on_connect].u_bool, args[ARG_server_hostname].u_obj); + args[ARG_do_handshake_on_connect].u_bool, args[ARG_server_hostname].u_obj, client_id); } static MP_DEFINE_CONST_FUN_OBJ_KW(ssl_context_wrap_socket_obj, 2, ssl_context_wrap_socket); @@ -580,7 +610,7 @@ static int _mbedtls_timing_get_delay(void *ctx) { #endif static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t sock, - bool server_side, bool do_handshake_on_connect, mp_obj_t server_hostname) { + bool server_side, bool do_handshake_on_connect, mp_obj_t server_hostname, mp_obj_t client_id) { // Store the current SSL context. store_active_context(ssl_context); @@ -634,6 +664,21 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t #ifdef MBEDTLS_SSL_PROTO_DTLS mbedtls_ssl_set_timer_cb(&o->ssl, o, _mbedtls_timing_set_delay, _mbedtls_timing_get_delay); #endif + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + if (client_id != mp_const_none) { + mp_buffer_info_t buf; + if (mp_get_buffer(client_id, &buf, MP_BUFFER_READ)) { + ret = mbedtls_ssl_set_client_transport_id(&o->ssl, buf.buf, buf.len); + } else { + ret = MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } + if (ret != 0) { + goto cleanup; + } + } else { + // TODO: should it be an error not to provide this argument for DTLS server? + } + #endif mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL); From 9b7d85227e67a7edd608aab4ff7eb4a838651f75 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 5 Jun 2025 15:32:38 +1000 Subject: [PATCH 0959/2098] extmod/mbedtls: Implement recommended DTLS features, make optional. - DTLS spec recommends HelloVerify and Anti Replay protection be enabled, and these are enabled in the default mbedTLS config. Implement them here. - To help compensate for the possible increase in code size, add a MICROPY_PY_SSL_DTLS build config macro that's enabled for EXTRA and above by default. This allows bare metal mbedTLS ports to use DTLS with HelloVerify support. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/ssl.rst | 48 ++++++++++++++++++++++---- extmod/mbedtls/mbedtls_config_common.h | 10 +++++- extmod/modtls_mbedtls.c | 29 +++++++++------- py/mpconfig.h | 5 +++ tests/extmod/tls_dtls.py | 12 ++++++- tests/extmod/tls_dtls.py.exp | 1 + 6 files changed, 84 insertions(+), 21 deletions(-) diff --git a/docs/library/ssl.rst b/docs/library/ssl.rst index 4327c74bad6..c86101872c3 100644 --- a/docs/library/ssl.rst +++ b/docs/library/ssl.rst @@ -66,7 +66,7 @@ class SSLContext Set the available ciphers for sockets created with this context. *ciphers* should be a list of strings in the `IANA cipher suite format `_ . -.. method:: SSLContext.wrap_socket(sock, *, server_side=False, do_handshake_on_connect=True, server_hostname=None) +.. method:: SSLContext.wrap_socket(sock, *, server_side=False, do_handshake_on_connect=True, server_hostname=None, client_id=None) Takes a `stream` *sock* (usually socket.socket instance of ``SOCK_STREAM`` type), and returns an instance of ssl.SSLSocket, wrapping the underlying stream. @@ -89,6 +89,9 @@ class SSLContext server certificate. It also sets the name for Server Name Indication (SNI), allowing the server to present the proper certificate. + - *client_id* is a MicroPython-specific extension argument used only when implementing a DTLS + Server. See :ref:`dtls` for details. + .. warning:: Some implementations of ``ssl`` module do NOT validate server certificates, @@ -117,6 +120,8 @@ Exceptions This exception does NOT exist. Instead its base class, OSError, is used. +.. _dtls: + DTLS support ------------ @@ -125,16 +130,47 @@ DTLS support This is a MicroPython extension. -This module supports DTLS in client and server mode via the `PROTOCOL_DTLS_CLIENT` -and `PROTOCOL_DTLS_SERVER` constants that can be used as the ``protocol`` argument -of `SSLContext`. +On most ports, this module supports DTLS in client and server mode via the +`PROTOCOL_DTLS_CLIENT` and `PROTOCOL_DTLS_SERVER` constants that can be used as +the ``protocol`` argument of `SSLContext`. In this case the underlying socket is expected to behave as a datagram socket (i.e. like the socket opened with ``socket.socket`` with ``socket.AF_INET`` as ``af`` and ``socket.SOCK_DGRAM`` as ``type``). -DTLS is only supported on ports that use mbed TLS, and it is not enabled by default: -it requires enabling ``MBEDTLS_SSL_PROTO_DTLS`` in the specific port configuration. +DTLS is only supported on ports that use mbedTLS, and it is enabled by default +in most configurations but can be manually disabled by defining +``MICROPY_PY_SSL_DTLS`` to 0. + +DTLS server support +^^^^^^^^^^^^^^^^^^^ + +MicroPython's DTLS server support is configured with "Hello Verify" as required +for DTLS 1.2. This is transparent for DTLS clients, but there are relevant +considerations when implementing a DTLS server in MicroPython: + +- The server should pass an additional argument *client_id* when calling + `SSLContext.wrap_socket()`. This ID must be a `bytes` object (or similar) with + a transport-specific identifier representing the client. + + The simplest approach is to convert the tuple of ``(client_ip, client_port)`` + returned from ``socket.recv_from()`` into a byte string, i.e.:: + + _, client_addr = sock.recvfrom(1, socket.MSG_PEEK) + sock.connect(client_addr) # Connect back to the client + sock = ssl_ctx.wrap_socket(sock, server_side=True, + client_id=repr(client_addr).encode()) + +- The first time a client connects, the server call to ``wrap_socket`` will fail + with a `OSError` error "Hello Verify Required". This is because the DTLS + "Hello Verify" cookie is not yet known by the client. If the same client + connects a second time then ``wrap_socket`` will succeed. + +- DTLS cookies for "Hello Verify" are associated with the `SSLContext` object, + so the same `SSLContext` object should be used to wrap a subsequent connection + from the same client. The cookie implementation includes a timeout and has + constant memory use regardless of how many clients connect, so it's OK to + reuse the same `SSLContext` object for the lifetime of the server. Constants --------- diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 6cd14befc31..1f7ac88180d 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H +#include "py/mpconfig.h" + // If you want to debug MBEDTLS uncomment the following and // pass "3" to mbedtls_debug_set_threshold in socket_new. // #define MBEDTLS_DEBUG_C @@ -89,12 +91,18 @@ #define MBEDTLS_SHA384_C #define MBEDTLS_SHA512_C #define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_PROTO_DTLS #define MBEDTLS_SSL_SRV_C #define MBEDTLS_SSL_TLS_C #define MBEDTLS_X509_CRT_PARSE_C #define MBEDTLS_X509_USE_C +#if MICROPY_PY_SSL_DTLS +#define MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_DTLS_ANTI_REPLAY +#define MBEDTLS_SSL_DTLS_HELLO_VERIFY +#define MBEDTLS_SSL_COOKIE_C +#endif + // A port may enable this option to select additional bare-metal configuration. #if MICROPY_MBEDTLS_CONFIG_BARE_METAL diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 4bd0aea9ab3..418275440f3 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -78,7 +78,7 @@ #define MP_PROTOCOL_TLS_CLIENT 0 #define MP_PROTOCOL_TLS_SERVER MP_ENDPOINT_IS_SERVER #define MP_PROTOCOL_DTLS_CLIENT MP_TRANSPORT_IS_DTLS -#define MP_PROTOCOL_DTLS_SERVER MP_ENDPOINT_IS_SERVER | MP_TRANSPORT_IS_DTLS +#define MP_PROTOCOL_DTLS_SERVER (MP_ENDPOINT_IS_SERVER | MP_TRANSPORT_IS_DTLS) // This corresponds to an SSLContext object. typedef struct _mp_obj_ssl_context_t { @@ -96,6 +96,7 @@ typedef struct _mp_obj_ssl_context_t { mp_obj_t ecdsa_sign_callback; #endif #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY + bool is_dtls_server; mbedtls_ssl_cookie_ctx cookie_ctx; #endif } mp_obj_ssl_context_t; @@ -328,14 +329,17 @@ static mp_obj_t ssl_context_make_new(const mp_obj_type_t *type_in, size_t n_args #endif #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY - mbedtls_ssl_cookie_init(&self->cookie_ctx); - ret = mbedtls_ssl_cookie_setup(&self->cookie_ctx, mbedtls_ctr_drbg_random, &self->ctr_drbg); - if (ret != 0) { - mbedtls_raise_error(ret); + self->is_dtls_server = (protocol == MP_PROTOCOL_DTLS_SERVER); + if (self->is_dtls_server) { + mbedtls_ssl_cookie_init(&self->cookie_ctx); + ret = mbedtls_ssl_cookie_setup(&self->cookie_ctx, mbedtls_ctr_drbg_random, &self->ctr_drbg); + if (ret != 0) { + mbedtls_raise_error(ret); + } + mbedtls_ssl_conf_dtls_cookies(&self->conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, + &self->cookie_ctx); } - mbedtls_ssl_conf_dtls_cookies(&self->conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, - &self->cookie_ctx); - #endif + #endif // MBEDTLS_SSL_DTLS_HELLO_VERIFY return MP_OBJ_FROM_PTR(self); } @@ -664,19 +668,18 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t #ifdef MBEDTLS_SSL_PROTO_DTLS mbedtls_ssl_set_timer_cb(&o->ssl, o, _mbedtls_timing_set_delay, _mbedtls_timing_get_delay); #endif + #ifdef MBEDTLS_SSL_DTLS_HELLO_VERIFY - if (client_id != mp_const_none) { + if (ssl_context->is_dtls_server) { + // require the client_id parameter for DTLS (as per mbedTLS requirement) + ret = MBEDTLS_ERR_SSL_BAD_INPUT_DATA; mp_buffer_info_t buf; if (mp_get_buffer(client_id, &buf, MP_BUFFER_READ)) { ret = mbedtls_ssl_set_client_transport_id(&o->ssl, buf.buf, buf.len); - } else { - ret = MBEDTLS_ERR_SSL_BAD_INPUT_DATA; } if (ret != 0) { goto cleanup; } - } else { - // TODO: should it be an error not to provide this argument for DTLS server? } #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index 4c127627596..a1025fe5e1b 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1941,6 +1941,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_SSL_MBEDTLS_NEED_ACTIVE_CONTEXT (MICROPY_PY_SSL_ECDSA_SIGN_ALT) #endif +// Whether to support DTLS protocol (non-CPython feature) +#ifndef MICROPY_PY_SSL_DTLS +#define MICROPY_PY_SSL_DTLS (MICROPY_SSL_MBEDTLS && MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to provide the "vfs" module #ifndef MICROPY_PY_VFS #define MICROPY_PY_VFS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES && MICROPY_VFS) diff --git a/tests/extmod/tls_dtls.py b/tests/extmod/tls_dtls.py index b2d716769d3..a475cce8c11 100644 --- a/tests/extmod/tls_dtls.py +++ b/tests/extmod/tls_dtls.py @@ -34,9 +34,19 @@ def ioctl(self, req, arg): # Wrap the DTLS Server dtls_server_ctx = SSLContext(PROTOCOL_DTLS_SERVER) dtls_server_ctx.verify_mode = CERT_NONE -dtls_server = dtls_server_ctx.wrap_socket(server_socket, do_handshake_on_connect=False) +dtls_server = dtls_server_ctx.wrap_socket( + server_socket, do_handshake_on_connect=False, client_id=b'dummy_client_id' +) print("Wrapped DTLS Server") +# wrap DTLS server with invalid client_id +try: + dtls_server = dtls_server_ctx.wrap_socket( + server_socket, do_handshake_on_connect=False, client_id=4 + ) +except OSError: + print("Failed to wrap DTLS Server with invalid client_id") + # Wrap the DTLS Client dtls_client_ctx = SSLContext(PROTOCOL_DTLS_CLIENT) dtls_client_ctx.verify_mode = CERT_NONE diff --git a/tests/extmod/tls_dtls.py.exp b/tests/extmod/tls_dtls.py.exp index 78d72bff188..dbd005d0edf 100644 --- a/tests/extmod/tls_dtls.py.exp +++ b/tests/extmod/tls_dtls.py.exp @@ -1,3 +1,4 @@ Wrapped DTLS Server +Failed to wrap DTLS Server with invalid client_id Wrapped DTLS Client OK From 89f9ee9d7c08bb0912b94fe6190646c4d37508a2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 5 Jun 2025 15:33:56 +1000 Subject: [PATCH 0960/2098] tests/multi_net: Update DTLS multi-net test. The original version of this test had to exchange a 1 byte UDP packet before the DTLS handshake. This is no longer needed due to MSG_PEEK support. The test also doesn't work with HelloVerify enabled, as the first connection attempt always fails with an MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED result. Anticipate this by listening for the client twice on the server side. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/multi_net/tls_dtls_server_client.py | 55 ++++++++++--------- tests/multi_net/tls_dtls_server_client.py.exp | 23 ++++---- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/tests/multi_net/tls_dtls_server_client.py b/tests/multi_net/tls_dtls_server_client.py index d50deb354ed..a81c4cb2823 100644 --- a/tests/multi_net/tls_dtls_server_client.py +++ b/tests/multi_net/tls_dtls_server_client.py @@ -34,28 +34,36 @@ def instance0(): multitest.next() - # Wait for the client to connect. - data, client_addr = s.recvfrom(1) - print("incoming connection", data) - - # Connect back to the client, so the UDP socket can be used like a stream. - s.connect(client_addr) - - # Create the DTLS context and load the certificate. ctx = tls.SSLContext(tls.PROTOCOL_DTLS_SERVER) ctx.load_cert_chain(cert, key) - # Wrap the UDP socket in server mode. - print("wrap socket") - s = ctx.wrap_socket(s, server_side=1) - - # Transfer some data. - for _ in range(4): - print(s.recv(16)) - s.send(b"server to client") - - # Close the DTLS and UDP connection. - s.close() + # Because of "hello verify required", we expect the peer + # to connect twice: once to set the cookie, then second time + # successfully. + # + # As this isn't a real server, we hard-code two connection attempts + for _ in range(2): + print("waiting") + # Wait for the client to connect so we know their address + _, client_addr = s.recvfrom(1, socket.MSG_PEEK) + print("incoming connection") + s.connect(client_addr) # Connect back to the client + + # Wrap the UDP socket in server mode. + try: + s = ctx.wrap_socket(s, server_side=1, client_id=repr(client_addr).encode()) + except OSError as e: + print(e) + continue # wait for second connection + + # Transfer some data. + for i in range(4): + print(s.recv(32)) + s.send(b"server to client " + str(i).encode()) + + # Close the DTLS and UDP connection. + s.close() + break # DTLS client. @@ -68,9 +76,6 @@ def instance1(): print("connect") s.connect(addr) - # Send one byte to indicate a connection, and so the server can obtain our address. - s.write("X") - # Create a DTLS context and load the certificate. ctx = tls.SSLContext(tls.PROTOCOL_DTLS_CLIENT) ctx.verify_mode = tls.CERT_REQUIRED @@ -81,9 +86,9 @@ def instance1(): s = ctx.wrap_socket(s, server_hostname="micropython.local") # Transfer some data. - for _ in range(4): - s.send(b"client to server") - print(s.recv(16)) + for i in range(4): + s.send(b"client to server " + str(i).encode()) + print(s.recv(32)) # Close the DTLS and UDP connection. s.close() diff --git a/tests/multi_net/tls_dtls_server_client.py.exp b/tests/multi_net/tls_dtls_server_client.py.exp index f2ff396e181..3de03056740 100644 --- a/tests/multi_net/tls_dtls_server_client.py.exp +++ b/tests/multi_net/tls_dtls_server_client.py.exp @@ -1,14 +1,17 @@ --- instance0 --- -incoming connection b'X' -wrap socket -b'client to server' -b'client to server' -b'client to server' -b'client to server' +waiting +incoming connection +(-27264, 'MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED') +waiting +incoming connection +b'client to server 0' +b'client to server 1' +b'client to server 2' +b'client to server 3' --- instance1 --- connect wrap socket -b'server to client' -b'server to client' -b'server to client' -b'server to client' +b'server to client 0' +b'server to client 1' +b'server to client 2' +b'server to client 3' From 28082d1d25c55a513677441c8053b82f6786aa3d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Jun 2025 10:36:59 +1000 Subject: [PATCH 0961/2098] extmod/mbedtls: Undefine ARRAY_SIZE if defined by platform. This is an annoying regression caused by including mpconfig.h in 36922df - the mimxrt platform headers define ARRAY_SIZE and mbedtls also defines in some source files, using a different parameter name which is a warning in gcc. Technically mimxrt SDK is to blame here, but as this isn't a named warning in gcc the only way to work around it in the mimxrt port would be to disable all warnings when building this particular mbedTLS source file. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/mbedtls/mbedtls_config_common.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 1f7ac88180d..040b0598dce 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -123,4 +123,8 @@ void m_tracked_free(void *ptr); #endif +// Workaround for a mimxrt platform driver header that defines ARRAY_SIZE, +// which is also defined in some mbedtls source files. +#undef ARRAY_SIZE + #endif // MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H From 6515cd05f17240c5d42d80638a55ce3b5ba4d1bf Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 26 Jun 2025 14:58:06 +1000 Subject: [PATCH 0962/2098] extmod/vfs_lfsx: Allow overriding the LFS2 on-disk version format. Back in LFS2 version 2.6 they updated the on-disk version from 2.0 to 2.1 which broke back compatibility (aka older versions could no long read new version disk format), see https://github.com/littlefs-project/littlefs/releases/tag/v2.6.0 Then in LFS2 v2.7 an optional `config->disk_version` was added to force the library to use an older disk format instead, see: https://github.com/littlefs-project/littlefs/releases/tag/v2.7.0 This commit simply exposes `config->disk_version` as a compile time option if LFS2_MULTIVERSION is set, otherwise there is no change in behavior. This is Useful for compatibility with older LFS versions. Note: LFS2_MULTIVERSION needs to be defined at the make / CFLAGS level, setting it in mpconfigboard.h doesn't work as it's not included in the `lfs2.c` file in any way. Signed-off-by: Andrew Leech --- extmod/vfs_lfsx.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 372037784b4..bbdd21cfb91 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -104,6 +104,12 @@ static void MP_VFS_LFSx(init_config)(MP_OBJ_VFS_LFSx * self, mp_obj_t bdev, size config->read_buffer = m_new(uint8_t, config->cache_size); config->prog_buffer = m_new(uint8_t, config->cache_size); config->lookahead_buffer = m_new(uint8_t, config->lookahead_size); + #ifdef LFS2_MULTIVERSION + // This can be set to override the on-disk lfs version. + // eg. for compat with lfs2 < v2.6 add the following to make: + // CFLAGS += '-DLFS2_MULTIVERSION=0x00020000' + config->disk_version = LFS2_MULTIVERSION; + #endif #endif } From d79000df70d2fe50a7912da7f52813ad00584b3c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 19 Jul 2025 06:32:57 -0500 Subject: [PATCH 0963/2098] tests/extmod: Add (failing) test for VfsPosix in readonly mode. I noticed that operations such as unlink could be performed on a nominally read-only VfsPosix. Signed-off-by: Jeff Epler --- tests/extmod/vfs_posix_readonly.py | 99 ++++++++++++++++++++++++++ tests/extmod/vfs_posix_readonly.py.exp | 7 ++ 2 files changed, 106 insertions(+) create mode 100644 tests/extmod/vfs_posix_readonly.py create mode 100644 tests/extmod/vfs_posix_readonly.py.exp diff --git a/tests/extmod/vfs_posix_readonly.py b/tests/extmod/vfs_posix_readonly.py new file mode 100644 index 00000000000..e7821381006 --- /dev/null +++ b/tests/extmod/vfs_posix_readonly.py @@ -0,0 +1,99 @@ +# Test for VfsPosix + +try: + import gc, os, vfs, errno + + vfs.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# We need a directory for testing that doesn't already exist. +# Skip the test if it does exist. +temp_dir = "vfs_posix_readonly_test_dir" +try: + os.stat(temp_dir) + raise SystemExit("Target directory {} exists".format(temp_dir)) +except OSError: + pass + +# mkdir (skip test if whole filesystem is readonly) +try: + os.mkdir(temp_dir) +except OSError as e: + if e.errno == errno.EROFS: + print("SKIP") + raise SystemExit + +fs_factory = lambda: vfs.VfsPosix(temp_dir) + +# mount +fs = fs_factory() +vfs.mount(fs, "/vfs") + +with open("/vfs/file", "w") as f: + f.write("content") + +# test reading works +with open("/vfs/file") as f: + print("file:", f.read()) + +os.mkdir("/vfs/emptydir") + +# umount +vfs.umount("/vfs") + +# mount read-only +fs = fs_factory() +vfs.mount(fs, "/vfs", readonly=True) + +# test reading works +with open("/vfs/file") as f: + print("file 2:", f.read()) + +# test writing fails +try: + with open("/vfs/test_write", "w"): + pass + print("opened") +except OSError as er: + print(repr(er)) + +# test removing fails +try: + os.unlink("/vfs/file") + print("unlinked") +except OSError as er: + print(repr(er)) + +# test renaming fails +try: + os.rename("/vfs/file2", "/vfs/renamed") + print("renamed") +except OSError as er: + print(repr(er)) + +# test removing directory fails +try: + os.rmdir("/vfs/emptydir") + print("rmdir'd") +except OSError as er: + print(repr(er)) + +# test creating directory fails +try: + os.mkdir("/vfs/emptydir2") + print("mkdir'd") +except OSError as er: + print(repr(er)) + +# umount +vfs.umount("/vfs") + +fs = fs_factory() +vfs.mount(fs, "/vfs") + +os.rmdir("/vfs/emptydir") +os.unlink("/vfs/file") + +os.rmdir(temp_dir) diff --git a/tests/extmod/vfs_posix_readonly.py.exp b/tests/extmod/vfs_posix_readonly.py.exp new file mode 100644 index 00000000000..40e4316775f --- /dev/null +++ b/tests/extmod/vfs_posix_readonly.py.exp @@ -0,0 +1,7 @@ +file: content +file 2: content +OSError(30,) +OSError(30,) +OSError(30,) +OSError(30,) +OSError(30,) From 3c603f7baf25b089a30f41d3cd57d13ba50b194c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 19 Jul 2025 06:33:50 -0500 Subject: [PATCH 0964/2098] extmod/vfs_posix: Add additional readonly checks. Otherwise operations such as unlink can be performed on a nominally read-only VfsPosix. Signed-off-by: Jeff Epler --- extmod/vfs_posix.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index bd9a6d84062..9393d3b5e64 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -160,10 +160,21 @@ static mp_obj_t vfs_posix_umount(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(vfs_posix_umount_obj, vfs_posix_umount); +static inline bool vfs_posix_is_readonly(mp_obj_vfs_posix_t *self) { + return self->readonly; +} + +static void vfs_posix_require_writable(mp_obj_t self_in) { + mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); + if (vfs_posix_is_readonly(self)) { + mp_raise_OSError(MP_EROFS); + } +} + static mp_obj_t vfs_posix_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) { mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); const char *mode = mp_obj_str_get_str(mode_in); - if (self->readonly + if (vfs_posix_is_readonly(self) && (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL || strchr(mode, '+') != NULL)) { mp_raise_OSError(MP_EROFS); } @@ -303,6 +314,7 @@ typedef struct _mp_obj_listdir_t { } mp_obj_listdir_t; static mp_obj_t vfs_posix_mkdir(mp_obj_t self_in, mp_obj_t path_in) { + vfs_posix_require_writable(self_in); mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); const char *path = vfs_posix_get_path_str(self, path_in); MP_THREAD_GIL_EXIT(); @@ -320,11 +332,13 @@ static mp_obj_t vfs_posix_mkdir(mp_obj_t self_in, mp_obj_t path_in) { static MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_mkdir_obj, vfs_posix_mkdir); static mp_obj_t vfs_posix_remove(mp_obj_t self_in, mp_obj_t path_in) { + vfs_posix_require_writable(self_in); return vfs_posix_fun1_helper(self_in, path_in, unlink); } static MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_remove_obj, vfs_posix_remove); static mp_obj_t vfs_posix_rename(mp_obj_t self_in, mp_obj_t old_path_in, mp_obj_t new_path_in) { + vfs_posix_require_writable(self_in); mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); const char *old_path = vfs_posix_get_path_str(self, old_path_in); const char *new_path = vfs_posix_get_path_str(self, new_path_in); @@ -339,6 +353,7 @@ static mp_obj_t vfs_posix_rename(mp_obj_t self_in, mp_obj_t old_path_in, mp_obj_ static MP_DEFINE_CONST_FUN_OBJ_3(vfs_posix_rename_obj, vfs_posix_rename); static mp_obj_t vfs_posix_rmdir(mp_obj_t self_in, mp_obj_t path_in) { + vfs_posix_require_writable(self_in); return vfs_posix_fun1_helper(self_in, path_in, rmdir); } static MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_rmdir_obj, vfs_posix_rmdir); From 59ee59901b9223697cf96f5859fcbf49fe9d21f4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 19 Jul 2025 06:38:18 -0500 Subject: [PATCH 0965/2098] extmod/vfs_posix: Add MICROPY_VFS_POSIX_WRITABLE option. When this configuration flag is set, VfsPosix instances can be written. Otherwise, they will always be created "read only". This flag is useful when fuzzing micropython: Without VfsPosix, the fuzzing input script cannot be read; but with writable VfsPosix, fuzzing scripts can potentially perform undesired operations on the host filesystem. Signed-off-by: Jeff Epler --- extmod/vfs_posix.c | 4 ++-- py/mpconfig.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index 9393d3b5e64..27f833e802f 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -137,7 +137,7 @@ static mp_obj_t vfs_posix_make_new(const mp_obj_type_t *type, size_t n_args, siz vstr_add_char(&vfs->root, '/'); } vfs->root_len = vfs->root.len; - vfs->readonly = false; + vfs->readonly = !MICROPY_VFS_POSIX_WRITABLE; return MP_OBJ_FROM_PTR(vfs); } @@ -161,7 +161,7 @@ static mp_obj_t vfs_posix_umount(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(vfs_posix_umount_obj, vfs_posix_umount); static inline bool vfs_posix_is_readonly(mp_obj_vfs_posix_t *self) { - return self->readonly; + return !MICROPY_VFS_POSIX_WRITABLE || self->readonly; } static void vfs_posix_require_writable(mp_obj_t self_in) { diff --git a/py/mpconfig.h b/py/mpconfig.h index a1025fe5e1b..b812cf032a4 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1084,6 +1084,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_VFS_POSIX (0) #endif +// Whether to include support for writable POSIX filesystems. +#ifndef MICROPY_VFS_POSIX_WRITABLE +#define MICROPY_VFS_POSIX_WRITABLE (1) +#endif + // Support for VFS FAT component, to mount a FAT filesystem within VFS #ifndef MICROPY_VFS_FAT #define MICROPY_VFS_FAT (0) From d6876e227318f974b94655ee7b74dc4995701660 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 17 Jul 2025 23:40:19 +0200 Subject: [PATCH 0966/2098] py/obj: Fix REPR_C bias toward zero. Current implementation of REPR_C works by clearing the two lower bits of the mantissa to zero. As this happens after each floating point operation, this tends to bias floating point numbers towards zero, causing decimals like .9997 instead of rounded numbers. This is visible in test cases involving repeated computations, such as `tests/misc/rge_sm.py` for instance. The suggested fix fills in the missing bits by copying the previous two bits. Although this cannot recreate missing information, it fixes the bias by inserting plausible values for the lost bits, at a relatively low cost. Some float tests involving irrational numbers have to be softened in case of REPR_C, as the 30 bits are not always enough to fulfill the expectations of the original test, and the change may randomly affect the last digits. Such cases have been made explicit by testing for REPR_C or by adding a clear comment. The perf_test fft code was also missing a call to round() before casting a log_2 operation to int, which was causing a failure due to a last-decimal change. Signed-off-by: Yoctopuce dev --- py/obj.h | 4 +++ tests/float/cmath_fun.py | 3 +++ tests/float/math_fun_special.py | 5 ++++ tests/float/string_format_fp30.py | 42 ------------------------------- tests/misc/rge_sm.py | 10 +++++++- tests/perf_bench/bm_fft.py | 2 +- tests/run-tests.py | 6 ----- 7 files changed, 22 insertions(+), 50 deletions(-) delete mode 100644 tests/float/string_format_fp30.py diff --git a/py/obj.h b/py/obj.h index a1df661ff09..6c2153697e6 100644 --- a/py/obj.h +++ b/py/obj.h @@ -206,6 +206,10 @@ static inline mp_float_t mp_obj_float_get(mp_const_obj_t o) { mp_float_t f; mp_uint_t u; } num = {.u = ((mp_uint_t)o - 0x80800000u) & ~3u}; + // Rather than always truncating toward zero, which creates a strong + // bias, copy the two previous bits to fill in the two missing bits. + // This appears to be a pretty good heuristic. + num.u |= (num.u >> 2) & 3u; return num.f; } static inline mp_obj_t mp_obj_new_float(mp_float_t f) { diff --git a/tests/float/cmath_fun.py b/tests/float/cmath_fun.py index 39011733b02..0037d7c6559 100644 --- a/tests/float/cmath_fun.py +++ b/tests/float/cmath_fun.py @@ -51,6 +51,9 @@ print("%.5g" % ret) elif type(ret) == tuple: print("%.5g %.5g" % ret) + elif f_name == "exp": + # exp amplifies REPR_C inaccuracies, so we need to check one digit less + print("complex(%.4g, %.4g)" % (real, ret.imag)) else: # some test (eg cmath.sqrt(-0.5)) disagree with CPython with tiny real part real = ret.real diff --git a/tests/float/math_fun_special.py b/tests/float/math_fun_special.py index e674ec8dfd0..a747f73e9fe 100644 --- a/tests/float/math_fun_special.py +++ b/tests/float/math_fun_special.py @@ -43,10 +43,15 @@ ("lgamma", lgamma, pos_test_values + [50.0, 100.0]), ] +is_REPR_C = float("1.0000001") == float("1.0") + for function_name, function, test_vals in functions: for value in test_vals: try: ans = "{:.4g}".format(function(value)) except ValueError as e: ans = str(e) + # a tiny error in REPR_C value for 1.5204998778 causes a wrong rounded value + if is_REPR_C and function_name == 'erfc' and ans == "1.521": + ans = "1.52" print("{}({:.4g}) = {}".format(function_name, value, ans)) diff --git a/tests/float/string_format_fp30.py b/tests/float/string_format_fp30.py deleted file mode 100644 index 5f0b213daa3..00000000000 --- a/tests/float/string_format_fp30.py +++ /dev/null @@ -1,42 +0,0 @@ -def test(fmt, *args): - print("{:8s}".format(fmt) + ">" + fmt.format(*args) + "<") - - -test("{:10.4}", 123.456) -test("{:10.4e}", 123.456) -test("{:10.4e}", -123.456) -# test("{:10.4f}", 123.456) -# test("{:10.4f}", -123.456) -test("{:10.4g}", 123.456) -test("{:10.4g}", -123.456) -test("{:10.4n}", 123.456) -test("{:e}", 100) -test("{:f}", 200) -test("{:g}", 300) - -test("{:10.4E}", 123.456) -test("{:10.4E}", -123.456) -# test("{:10.4F}", 123.456) -# test("{:10.4F}", -123.456) -test("{:10.4G}", 123.456) -test("{:10.4G}", -123.456) - -test("{:06e}", float("inf")) -test("{:06e}", float("-inf")) -test("{:06e}", float("nan")) - -# The following fails right now -# test("{:10.1}", 0.0) - -print("%.0f" % (1.750000 % 0.08333333333)) -# Below isn't compatible with single-precision float -# print("%.1f" % (1.750000 % 0.08333333333)) -# print("%.2f" % (1.750000 % 0.08333333333)) -# print("%.12f" % (1.750000 % 0.08333333333)) - -# tests for errors in format string - -try: - "{:10.1b}".format(0.0) -except ValueError: - print("ValueError") diff --git a/tests/misc/rge_sm.py b/tests/misc/rge_sm.py index 5e071687c49..f5b0910dd3a 100644 --- a/tests/misc/rge_sm.py +++ b/tests/misc/rge_sm.py @@ -119,6 +119,7 @@ def phaseDiagram(system, trajStart, trajPlot, h=0.1, tend=1.0, range=1.0): def singleTraj(system, trajStart, h=0.02, tend=1.0): + is_REPR_C = float("1.0000001") == float("1.0") tstart = 0.0 # compute the trajectory @@ -130,7 +131,14 @@ def singleTraj(system, trajStart, h=0.02, tend=1.0): for i in range(len(rk.Trajectory)): tr = rk.Trajectory[i] - print(" ".join(["{:.4f}".format(t) for t in tr])) + tr_str = " ".join(["{:.4f}".format(t) for t in tr]) + if is_REPR_C: + # allow two small deviations for REPR_C + if tr_str == "1.0000 0.3559 0.6485 1.1944 0.9271 0.1083": + tr_str = "1.0000 0.3559 0.6485 1.1944 0.9272 0.1083" + if tr_str == "16.0000 0.3894 0.5793 0.7017 0.5686 -0.0168": + tr_str = "16.0000 0.3894 0.5793 0.7017 0.5686 -0.0167" + print(tr_str) # phaseDiagram(sysSM, (lambda i, j: [0.354, 0.654, 1.278, 0.8 + 0.2 * i, 0.1 + 0.1 * j]), (lambda a: (a[4], a[5])), h=0.1, tend=math.log(10**17)) diff --git a/tests/perf_bench/bm_fft.py b/tests/perf_bench/bm_fft.py index 9a2d03d11b9..e35c1216c13 100644 --- a/tests/perf_bench/bm_fft.py +++ b/tests/perf_bench/bm_fft.py @@ -15,7 +15,7 @@ def reverse(x, bits): # Initialization n = len(vector) - levels = int(math.log(n) / math.log(2)) + levels = int(round(math.log(n) / math.log(2))) coef = (2 if inverse else -2) * cmath.pi / n exptable = [cmath.rect(1, i * coef) for i in range(n // 2)] vector = [vector[reverse(i, levels)] for i in range(n)] # Copy with bit-reversed permutation diff --git a/tests/run-tests.py b/tests/run-tests.py index 522027c1f3e..328d69f633c 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -105,9 +105,6 @@ def open(self, path, mode): # Tests to skip on specific targets. # These are tests that are difficult to detect that they should not be run on the given target. platform_tests_to_skip = { - "esp8266": ( - "misc/rge_sm.py", # incorrect values due to object representation C - ), "minimal": ( "basics/class_inplace_op.py", # all special methods not supported "basics/subclass_native_init.py", # native subclassing corner cases not support @@ -788,9 +785,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add( "float/float2int_intbig.py" ) # requires fp32, there's float2int_fp30_intbig.py instead - skip_tests.add( - "float/string_format.py" - ) # requires fp32, there's string_format_fp30.py instead skip_tests.add("float/bytes_construct.py") # requires fp32 skip_tests.add("float/bytearray_construct.py") # requires fp32 skip_tests.add("float/float_format_ints_power10.py") # requires fp32 From 8b3439e26c293e374bf94c674d1e747d5a57034f Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Sun, 20 Jul 2025 16:38:33 +0200 Subject: [PATCH 0967/2098] unix/variants/longlong: Use REPR_C on this variant. There is currently no build using REPR_C in the unix CI tests. As discussed in PR #16953, this is something that combines well with the longlong build. Signed-off-by: Yoctopuce dev --- ports/unix/variants/longlong/mpconfigvariant.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/unix/variants/longlong/mpconfigvariant.h b/ports/unix/variants/longlong/mpconfigvariant.h index 20c52e98f9d..a04ff7a6dda 100644 --- a/ports/unix/variants/longlong/mpconfigvariant.h +++ b/ports/unix/variants/longlong/mpconfigvariant.h @@ -30,6 +30,15 @@ #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) +// We build it on top of REPR C, which uses memory-efficient floating point +// objects encoded directly mp_obj_t (30 bits only). +// Therefore this variant should be built using MICROPY_FORCE_32BIT=1 + +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +typedef int mp_int_t; +typedef unsigned int mp_uint_t; + // Set base feature level. #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) From 6a4306a0df1e936954bfeff65297d74b9b0954e2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Jul 2025 18:23:07 +1000 Subject: [PATCH 0968/2098] unix/mpconfigport: Include time.h to get definition of time_t. Without this there's a build error on macOS (at least). This was likely due to a combination of 9b7d85227e67a7edd608aab4ff7eb4a838651f75 and df05caea6c6437a8b4756ec502a5e6210f4b6256. Signed-off-by: Damien George --- ports/unix/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index c18859ecbf7..68943fb8943 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -29,6 +29,9 @@ // features to work on Unix-like systems, see mpconfigvariant.h (and // mpconfigvariant_common.h) for feature enabling. +// For time_t, needed by MICROPY_TIMESTAMP_IMPL_TIME_T. +#include + // For size_t and ssize_t #include From 7729e80fdddbd9f75cb350de70a84ed26cb601fd Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 22:58:52 +1000 Subject: [PATCH 0969/2098] all: Go back to using default ruff quote style. Commit dc2fcfcc5511a371ff684f7d7772e7a7b479246d seems to have accidentally changed the ruff quote style to "preserve", instead of keeping it at the default which is "double". Put it back to the default and update relevant .py files with this rule. Signed-off-by: Damien George --- pyproject.toml | 1 - tests/cpydiff/core_fstring_parser.py | 4 +-- tests/extmod/deflate_compress_memory_error.py | 2 +- tests/extmod/json_loads.py | 2 +- tests/extmod/json_loads_int_64.py | 2 +- tests/extmod/tls_dtls.py | 2 +- tests/float/math_fun_special.py | 2 +- tests/import/import_star.py | 32 +++++++++---------- tests/import/pkgstar_all_array/__init__.py | 2 +- tests/import/pkgstar_all_miss/__init__.py | 2 +- tests/import/pkgstar_all_tuple/__init__.py | 2 +- tests/run-tests.py | 4 +-- tools/mpremote/mpremote/commands.py | 8 ++--- tools/mpremote/mpremote/main.py | 2 +- tools/mpremote/mpremote/repl.py | 2 +- tools/mpy_ld.py | 22 ++++++------- 16 files changed, 45 insertions(+), 46 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0dd15d06c7b..8c14c2bffa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,4 +68,3 @@ mccabe.max-complexity = 40 # repl_: not real python files # viper_args: uses f(*) exclude = ["tests/basics/*.py", "tests/*/repl_*.py", "tests/micropython/viper_args.py"] -quote-style = "preserve" diff --git a/tests/cpydiff/core_fstring_parser.py b/tests/cpydiff/core_fstring_parser.py index 570b92434a9..04b24cdfb69 100644 --- a/tests/cpydiff/core_fstring_parser.py +++ b/tests/cpydiff/core_fstring_parser.py @@ -5,5 +5,5 @@ workaround: Always use balanced braces and brackets in expressions inside f-strings """ -print(f'{"hello { world"}') -print(f'{"hello ] world"}') +print(f"{'hello { world'}") +print(f"{'hello ] world'}") diff --git a/tests/extmod/deflate_compress_memory_error.py b/tests/extmod/deflate_compress_memory_error.py index 56ce0603081..19bef87bff3 100644 --- a/tests/extmod/deflate_compress_memory_error.py +++ b/tests/extmod/deflate_compress_memory_error.py @@ -28,7 +28,7 @@ # Try to compress. This will try to allocate a large window and fail. try: - g.write('test') + g.write("test") except MemoryError: print("MemoryError") diff --git a/tests/extmod/json_loads.py b/tests/extmod/json_loads.py index 095e67d740b..092402d715d 100644 --- a/tests/extmod/json_loads.py +++ b/tests/extmod/json_loads.py @@ -86,7 +86,7 @@ def my_print(o): # incomplete array declaration try: - my_print(json.loads('[0,')) + my_print(json.loads("[0,")) except ValueError: print("ValueError") diff --git a/tests/extmod/json_loads_int_64.py b/tests/extmod/json_loads_int_64.py index 193a3c28d82..f6236f1904a 100644 --- a/tests/extmod/json_loads_int_64.py +++ b/tests/extmod/json_loads_int_64.py @@ -13,4 +13,4 @@ print(json.loads("-9111222333444555666")) print(json.loads("9111222333444555666")) print(json.loads("-9111222333444555666")) -print(json.loads("[\"9111222333444555666777\",9111222333444555666]")) +print(json.loads('["9111222333444555666777",9111222333444555666]')) diff --git a/tests/extmod/tls_dtls.py b/tests/extmod/tls_dtls.py index a475cce8c11..753ab2fee4f 100644 --- a/tests/extmod/tls_dtls.py +++ b/tests/extmod/tls_dtls.py @@ -35,7 +35,7 @@ def ioctl(self, req, arg): dtls_server_ctx = SSLContext(PROTOCOL_DTLS_SERVER) dtls_server_ctx.verify_mode = CERT_NONE dtls_server = dtls_server_ctx.wrap_socket( - server_socket, do_handshake_on_connect=False, client_id=b'dummy_client_id' + server_socket, do_handshake_on_connect=False, client_id=b"dummy_client_id" ) print("Wrapped DTLS Server") diff --git a/tests/float/math_fun_special.py b/tests/float/math_fun_special.py index a747f73e9fe..ecacedec552 100644 --- a/tests/float/math_fun_special.py +++ b/tests/float/math_fun_special.py @@ -52,6 +52,6 @@ except ValueError as e: ans = str(e) # a tiny error in REPR_C value for 1.5204998778 causes a wrong rounded value - if is_REPR_C and function_name == 'erfc' and ans == "1.521": + if is_REPR_C and function_name == "erfc" and ans == "1.521": ans = "1.52" print("{}({:.4g}) = {}".format(function_name, value, ans)) diff --git a/tests/import/import_star.py b/tests/import/import_star.py index 0947f6a835d..2cb21b877d7 100644 --- a/tests/import/import_star.py +++ b/tests/import/import_star.py @@ -7,39 +7,39 @@ except TypeError: # 2-argument version of next() not supported # we are probably not at MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES - print('SKIP') + print("SKIP") raise SystemExit # 1. test default visibility from pkgstar_default import * -print('visibleFun' in globals()) -print('VisibleClass' in globals()) -print('_hiddenFun' in globals()) -print('_HiddenClass' in globals()) +print("visibleFun" in globals()) +print("VisibleClass" in globals()) +print("_hiddenFun" in globals()) +print("_HiddenClass" in globals()) print(visibleFun()) # 2. test explicit visibility as defined by __all__ (as an array) from pkgstar_all_array import * -print('publicFun' in globals()) -print('PublicClass' in globals()) -print('unlistedFun' in globals()) -print('UnlistedClass' in globals()) -print('_privateFun' in globals()) -print('_PrivateClass' in globals()) +print("publicFun" in globals()) +print("PublicClass" in globals()) +print("unlistedFun" in globals()) +print("UnlistedClass" in globals()) +print("_privateFun" in globals()) +print("_PrivateClass" in globals()) print(publicFun()) # test dynamic import as used in asyncio -print('dynamicFun' in globals()) +print("dynamicFun" in globals()) print(dynamicFun()) # 3. test explicit visibility as defined by __all__ (as an tuple) from pkgstar_all_tuple import * -print('publicFun2' in globals()) -print('PublicClass2' in globals()) -print('unlistedFun2' in globals()) -print('UnlistedClass2' in globals()) +print("publicFun2" in globals()) +print("PublicClass2" in globals()) +print("unlistedFun2" in globals()) +print("UnlistedClass2" in globals()) print(publicFun2()) # 4. test reporting of missing entries in __all__ diff --git a/tests/import/pkgstar_all_array/__init__.py b/tests/import/pkgstar_all_array/__init__.py index 4499a94d591..03b012123fe 100644 --- a/tests/import/pkgstar_all_array/__init__.py +++ b/tests/import/pkgstar_all_array/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['publicFun', 'PublicClass', 'dynamicFun'] +__all__ = ["publicFun", "PublicClass", "dynamicFun"] # Definitions below should always be imported by a star import diff --git a/tests/import/pkgstar_all_miss/__init__.py b/tests/import/pkgstar_all_miss/__init__.py index d960c7d0e29..f9bbb538072 100644 --- a/tests/import/pkgstar_all_miss/__init__.py +++ b/tests/import/pkgstar_all_miss/__init__.py @@ -1,4 +1,4 @@ -__all__ = ('existingFun', 'missingFun') +__all__ = ("existingFun", "missingFun") def existingFun(): diff --git a/tests/import/pkgstar_all_tuple/__init__.py b/tests/import/pkgstar_all_tuple/__init__.py index a97715ed391..433ddc8e976 100644 --- a/tests/import/pkgstar_all_tuple/__init__.py +++ b/tests/import/pkgstar_all_tuple/__init__.py @@ -1,4 +1,4 @@ -__all__ = ('publicFun2', 'PublicClass2') +__all__ = ("publicFun2", "PublicClass2") # Definitions below should always be imported by a star import diff --git a/tests/run-tests.py b/tests/run-tests.py index 328d69f633c..a428665db03 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -16,7 +16,7 @@ import tempfile # Maximum time to run a single test, in seconds. -TEST_TIMEOUT = float(os.environ.get('MICROPY_TEST_TIMEOUT', 30)) +TEST_TIMEOUT = float(os.environ.get("MICROPY_TEST_TIMEOUT", 30)) # See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] # are guaranteed to always work, this one should though. @@ -411,7 +411,7 @@ def get(required=False): def send_get(what): # Detect {\x00} pattern and convert to ctrl-key codes. ctrl_code = lambda m: bytes([int(m.group(1))]) - what = re.sub(rb'{\\x(\d\d)}', ctrl_code, what) + what = re.sub(rb"{\\x(\d\d)}", ctrl_code, what) os.write(master, what) return get() diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 428600baf43..4974c71e2e9 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -309,7 +309,7 @@ def do_filesystem_recursive_rm(state, path, args): os.path.join(r_cwd, path) if not os.path.isabs(path) else path ) if isinstance(state.transport, SerialTransport) and abs_path.startswith( - f'{SerialTransport.fs_hook_mount}/' + f"{SerialTransport.fs_hook_mount}/" ): raise CommandError( f"rm -r not permitted on {SerialTransport.fs_hook_mount} directory" @@ -335,11 +335,11 @@ def do_filesystem_recursive_rm(state, path, args): def human_size(size, decimals=1): - for unit in ['B', 'K', 'M', 'G', 'T']: - if size < 1024.0 or unit == 'T': + for unit in ["B", "K", "M", "G", "T"]: + if size < 1024.0 or unit == "T": break size /= 1024.0 - return f"{size:.{decimals}f}{unit}" if unit != 'B' else f"{int(size)}" + return f"{size:.{decimals}f}{unit}" if unit != "B" else f"{int(size)}" def do_filesystem_tree(state, path, args): diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index 0441857fab7..0aec1efad14 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -598,7 +598,7 @@ def main(): cmd == "fs" and len(command_args) >= 1 and command_args[0] in ("ls", "tree") - and sum(1 for a in command_args if not a.startswith('-')) == 1 + and sum(1 for a in command_args if not a.startswith("-")) == 1 ): command_args.append("") diff --git a/tools/mpremote/mpremote/repl.py b/tools/mpremote/mpremote/repl.py index 4fda04a2e2a..ad7e83ea8bf 100644 --- a/tools/mpremote/mpremote/repl.py +++ b/tools/mpremote/mpremote/repl.py @@ -110,7 +110,7 @@ def _is_disconnect_exception(exception): False otherwise. """ if isinstance(exception, OSError): - if hasattr(exception, 'args') and len(exception.args) > 0: + if hasattr(exception, "args") and len(exception.args) > 0: # IO error, device disappeared if exception.args[0] == 5: return True diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index a600ec12c3d..af8450a8424 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -1521,31 +1521,31 @@ def parse_linkerscript(source): symbols = {} LINE_REGEX = re.compile( - r'^(?PPROVIDE\()?' # optional weak marker start - r'(?P[a-zA-Z_]\w*)' # symbol name - r'=0x(?P
[\da-fA-F]{1,8})*' # symbol address - r'(?(weak)\));$', # optional weak marker end and line terminator + r"^(?PPROVIDE\()?" # optional weak marker start + r"(?P[a-zA-Z_]\w*)" # symbol name + r"=0x(?P
[\da-fA-F]{1,8})*" # symbol address + r"(?(weak)\));$", # optional weak marker end and line terminator re.ASCII, ) inside_comment = False for line in (line.strip() for line in source.readlines()): - if line.startswith('/*') and not inside_comment: - if not line.endswith('*/'): + if line.startswith("/*") and not inside_comment: + if not line.endswith("*/"): inside_comment = True continue if inside_comment: - if line.endswith('*/'): + if line.endswith("*/"): inside_comment = False continue - if line.startswith('//'): + if line.startswith("//"): continue - match = LINE_REGEX.match(''.join(line.split())) + match = LINE_REGEX.match("".join(line.split())) if not match: continue tokens = match.groupdict() - symbol = tokens['symbol'] - address = int(tokens['address'], 16) + symbol = tokens["symbol"] + address = int(tokens["address"], 16) if symbol in symbols: raise ValueError(f"Symbol {symbol} already defined") symbols[symbol] = address From bc77b27badaa4dfbebcc542ecb8ac41480e75787 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 6 Jun 2025 09:56:40 +1000 Subject: [PATCH 0970/2098] unix/mpthreadport: Ensure consistent type of PTHREAD_STACK_MIN. It seems GCC 14 got stricter with warn/errs like -Wsign-compare and types a "bare number" as a long int that can't be compared to a (unsigned) size_t. Signed-off-by: Andrew Leech --- ports/unix/mpthreadport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index ded3bd14aff..141cd0218d9 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -250,8 +250,8 @@ mp_uint_t mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size } // minimum stack size is set by pthreads - if (*stack_size < PTHREAD_STACK_MIN) { - *stack_size = PTHREAD_STACK_MIN; + if (*stack_size < (size_t)PTHREAD_STACK_MIN) { + *stack_size = (size_t)PTHREAD_STACK_MIN; } // ensure there is enough stack to include a stack-overflow margin From cbc6aed8facf8fea2eceb59b0e801f33038f1897 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 22 Jul 2025 09:37:27 +0100 Subject: [PATCH 0971/2098] lib/pico-sdk: Fix Pico SDK fetching develop picotool. SDK 2.1.1 shipped with PICOTOOL_FETCH_FROM_GIT configured to fetch the "develop" branch. This broke downstream CI, which was trusting Pico SDK to fetch the correct version. RPi have added a "2.1.1-correct-picotool" tag which fixes this. lib/pico-sdk: Bump to "2.1.1-correct-picotool" tag. Signed-off-by: Phil Howard --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index bddd20f928c..9a4113fbbae 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit bddd20f928ce76142793bef434d4f75f4af6e433 +Subproject commit 9a4113fbbae65ee82d8cd6537963bc3d3b14bcca From bba15e0a0bf72d03b74610e9a37ac79ffbdacd1a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 14:32:01 +1000 Subject: [PATCH 0972/2098] examples/natmod: Use LINK_RUNTIME=1 when building for armv6m. To get division helper functions, eg `__aeabi_uidiv`, `__aeabi_idiv` and `__aeabi_uidivmod`. Signed-off-by: Damien George --- examples/natmod/btree/Makefile | 5 +++++ examples/natmod/deflate/Makefile | 5 +++++ examples/natmod/framebuf/Makefile | 5 +++++ examples/natmod/re/Makefile | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index bcaa7a93d11..1910c67c1fc 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -39,6 +39,11 @@ endif # Use our own errno implementation if Picolibc is used CFLAGS += -D__PICOLIBC_ERRNO_FUNCTION=__errno +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + include $(MPY_DIR)/py/dynruntime.mk # btree needs gnu99 defined diff --git a/examples/natmod/deflate/Makefile b/examples/natmod/deflate/Makefile index 1f63de20d20..5823aa4d45b 100644 --- a/examples/natmod/deflate/Makefile +++ b/examples/natmod/deflate/Makefile @@ -10,6 +10,11 @@ SRC = deflate.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH ?= x64 +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + ifeq ($(ARCH),xtensa) # Link with libm.a and libgcc.a from the toolchain LINK_RUNTIME = 1 diff --git a/examples/natmod/framebuf/Makefile b/examples/natmod/framebuf/Makefile index cb821736e70..35453c0bb4b 100644 --- a/examples/natmod/framebuf/Makefile +++ b/examples/natmod/framebuf/Makefile @@ -10,6 +10,11 @@ SRC = framebuf.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) ARCH ?= x64 +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + ifeq ($(ARCH),xtensa) MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld endif diff --git a/examples/natmod/re/Makefile b/examples/natmod/re/Makefile index 56b08b98868..6535693dcb8 100644 --- a/examples/natmod/re/Makefile +++ b/examples/natmod/re/Makefile @@ -10,4 +10,9 @@ SRC = re.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + include $(MPY_DIR)/py/dynruntime.mk From e750ecff70572fb11cbcaefb10f535de5033901b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Jul 2025 10:38:39 +1000 Subject: [PATCH 0973/2098] qemu/Makefile: Allow passing flags to test_natmod via RUN_TESTS_EXTRA. Signed-off-by: Damien George --- ports/qemu/Makefile | 2 +- ports/qemu/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 646659ceda5..fc1e557974c 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -196,7 +196,7 @@ test_natmod: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && \ for natmod in btree deflate framebuf heapq random_basic re; do \ - ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" extmod/$$natmod*.py; \ + ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_EXTRA) extmod/$$natmod*.py; \ done $(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ) diff --git a/ports/qemu/README.md b/ports/qemu/README.md index c7d0dc1f4ea..aab88ab5898 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -112,8 +112,8 @@ Extra make options The following options can be specified on the `make` command line: - `CFLAGS_EXTRA`: pass in extra flags for the compiler. -- `RUN_TESTS_EXTRA`: pass in extra flags for `run-tests.py` when invoked via - `make test`. +- `RUN_TESTS_EXTRA`: pass in extra flags for `run-tests.py` and `run-natmodtests.py` + when invoked via `make test` or `make test_natmod`. - `QEMU_DEBUG=1`: when running qemu (via `repl`, `run` or `test` target), qemu will block until a debugger is connected. By default it waits for a gdb connection on TCP port 1234. From f6e23fdef18be122590c6dd7159a93648d1358f1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 14:09:21 +1000 Subject: [PATCH 0974/2098] tools/ci.sh: Test building all natmod examples with all ARM-M archs. And run both armv6m and armv7m under qemu. Signed-off-by: Damien George --- tools/ci.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 594bca80044..d787cbcf073 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -346,9 +346,15 @@ function ci_qemu_build_arm_thumb { ci_qemu_build_arm_prepare make ${MAKEOPTS} -C ports/qemu test_full - # Test building and running native .mpy with armv7m architecture. + # Test building native .mpy with all ARM-M architectures. + ci_native_mpy_modules_build armv6m ci_native_mpy_modules_build armv7m - make ${MAKEOPTS} -C ports/qemu test_natmod + ci_native_mpy_modules_build armv7emsp + ci_native_mpy_modules_build armv7emdp + + # Test running native .mpy with armv6m and armv7m architectures. + make ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv6m" + make ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv7m" } function ci_qemu_build_rv32 { @@ -442,10 +448,6 @@ function ci_stm32_pyb_build { make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBV10 CFLAGS_EXTRA='-DMBOOT_FSLOAD=1 -DMBOOT_VFS_LFS2=1' make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBD_SF6 make ${MAKEOPTS} -C ports/stm32/mboot BOARD=STM32F769DISC CFLAGS_EXTRA='-DMBOOT_ADDRESS_SPACE_64BIT=1 -DMBOOT_SDCARD_ADDR=0x100000000ULL -DMBOOT_SDCARD_BYTE_SIZE=0x400000000ULL -DMBOOT_FSLOAD=1 -DMBOOT_VFS_FAT=1' - - # Test building native .mpy with armv7emsp architecture. - git submodule update --init lib/berkeley-db-1.xx - ci_native_mpy_modules_build armv7emsp } function ci_stm32_nucleo_build { From 9b61bb93f9e34314a527375bd9803693f08a9a63 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Jul 2025 14:59:07 +1000 Subject: [PATCH 0975/2098] webassembly/proxy_c: Provide constants for fixed JsProxy refs. Signed-off-by: Damien George --- ports/webassembly/modjs.c | 2 +- ports/webassembly/proxy_c.c | 2 +- ports/webassembly/proxy_c.h | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/webassembly/modjs.c b/ports/webassembly/modjs.c index bed09086ab7..5558a2cdd82 100644 --- a/ports/webassembly/modjs.c +++ b/ports/webassembly/modjs.c @@ -35,7 +35,7 @@ void mp_module_js_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_jsproxy_t global_this; - global_this.ref = 0; + global_this.ref = MP_OBJ_JSPROXY_REF_GLOBAL_THIS; mp_obj_jsproxy_attr(MP_OBJ_FROM_PTR(&global_this), attr, dest); } diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c index 00abc43bf2b..b7bc87b7634 100644 --- a/ports/webassembly/proxy_c.c +++ b/ports/webassembly/proxy_c.c @@ -202,7 +202,7 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) { out[2] = (uintptr_t)str; } else if (obj == mp_const_undefined) { kind = PROXY_KIND_MP_JSPROXY; - out[1] = 1; + out[1] = MP_OBJ_JSPROXY_REF_UNDEFINED; } else if (mp_obj_is_jsproxy(obj)) { kind = PROXY_KIND_MP_JSPROXY; out[1] = mp_obj_jsproxy_get_ref(obj); diff --git a/ports/webassembly/proxy_c.h b/ports/webassembly/proxy_c.h index d3567c195e7..4ca2b8644e8 100644 --- a/ports/webassembly/proxy_c.h +++ b/ports/webassembly/proxy_c.h @@ -28,6 +28,10 @@ #include "py/obj.h" +// Fixed JsProxy references. +#define MP_OBJ_JSPROXY_REF_GLOBAL_THIS (0) +#define MP_OBJ_JSPROXY_REF_UNDEFINED (1) + // proxy value number of items #define PVN (3) From 45aa65b67d075fa8b2e71d57f1a94566f51207bb Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Jul 2025 14:51:46 +1000 Subject: [PATCH 0976/2098] webassembly/objjsproxy: Fix binding of self to JavaScript methods. Fixes a bug in the binding of self/this to JavaScript methods. The new semantics match Pyodide's behaviour, at least for the included tests. Signed-off-by: Damien George --- ports/webassembly/modjs.c | 4 +- ports/webassembly/objjsproxy.c | 114 +++++++++++++----- ports/webassembly/proxy_c.h | 3 +- .../webassembly/method_bind_behaviour.mjs | 43 +++++++ .../webassembly/method_bind_behaviour.mjs.exp | 11 ++ 5 files changed, 138 insertions(+), 37 deletions(-) create mode 100644 tests/ports/webassembly/method_bind_behaviour.mjs create mode 100644 tests/ports/webassembly/method_bind_behaviour.mjs.exp diff --git a/ports/webassembly/modjs.c b/ports/webassembly/modjs.c index 5558a2cdd82..2f91a012f0f 100644 --- a/ports/webassembly/modjs.c +++ b/ports/webassembly/modjs.c @@ -34,9 +34,7 @@ // js module void mp_module_js_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - mp_obj_jsproxy_t global_this; - global_this.ref = MP_OBJ_JSPROXY_REF_GLOBAL_THIS; - mp_obj_jsproxy_attr(MP_OBJ_FROM_PTR(&global_this), attr, dest); + mp_obj_jsproxy_global_this_attr(attr, dest); } static const mp_rom_map_elem_t mp_module_js_globals_table[] = { diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index 167d4382bc6..28fef901477 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -43,7 +43,7 @@ EM_JS(bool, has_attr, (int jsref, const char *str), { }); // *FORMAT-OFF* -EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), { +EM_JS(int, lookup_attr, (int jsref, const char *str, uint32_t * out), { const base = proxy_js_ref[jsref]; const attr = UTF8ToString(str); @@ -54,23 +54,17 @@ EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), { // - Otherwise, the attribute does not exist. let value = base[attr]; if (value !== undefined || attr in base) { - if (typeof value === "function") { - if (base !== globalThis) { - if ("_ref" in value) { - // This is a proxy of a Python function, it doesn't need - // binding. And not binding it means if it's passed back - // to Python then it can be extracted from the proxy as a - // true Python function. - } else { - // A function that is not a Python function. Bind it. - value = value.bind(base); - } - } - } proxy_convert_js_to_mp_obj_jsside(value, out); - return true; + if (typeof value === "function" && !("_ref" in value)) { + // Attribute found and it's a JavaScript function. + return 2; + } else { + // Attribute found. + return 1; + } } else { - return false; + // Attribute not found. + return 0; } }); // *FORMAT-ON* @@ -98,33 +92,48 @@ EM_JS(void, call0, (int f_ref, uint32_t * out), { proxy_convert_js_to_mp_obj_jsside(ret, out); }); -EM_JS(int, call1, (int f_ref, uint32_t * a0, uint32_t * out), { +EM_JS(int, call1, (int f_ref, bool via_call, uint32_t * a0, uint32_t * out), { const a0_js = proxy_convert_mp_to_js_obj_jsside(a0); const f = proxy_js_ref[f_ref]; - const ret = f(a0_js); + let ret; + if (via_call) { + ret = f.call(a0_js); + } else { + ret = f(a0_js); + } proxy_convert_js_to_mp_obj_jsside(ret, out); }); -EM_JS(int, call2, (int f_ref, uint32_t * a0, uint32_t * a1, uint32_t * out), { +EM_JS(int, call2, (int f_ref, bool via_call, uint32_t * a0, uint32_t * a1, uint32_t * out), { const a0_js = proxy_convert_mp_to_js_obj_jsside(a0); const a1_js = proxy_convert_mp_to_js_obj_jsside(a1); const f = proxy_js_ref[f_ref]; - const ret = f(a0_js, a1_js); + let ret; + if (via_call) { + ret = f.call(a0_js, a1_js); + } else { + ret = f(a0_js, a1_js); + } proxy_convert_js_to_mp_obj_jsside(ret, out); }); -EM_JS(int, calln, (int f_ref, uint32_t n_args, uint32_t * value, uint32_t * out), { +EM_JS(int, calln, (int f_ref, bool via_call, uint32_t n_args, uint32_t * value, uint32_t * out), { const f = proxy_js_ref[f_ref]; const a = []; for (let i = 0; i < n_args; ++i) { const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4); a.push(v); } - const ret = f(... a); + let ret; + if (via_call) { + ret = f.call(... a); + } else { + ret = f(... a); + } proxy_convert_js_to_mp_obj_jsside(ret, out); }); -EM_JS(void, call0_kwarg, (int f_ref, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), { +EM_JS(void, call0_kwarg, (int f_ref, bool via_call, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), { const f = proxy_js_ref[f_ref]; const a = {}; for (let i = 0; i < n_kw; ++i) { @@ -132,11 +141,16 @@ EM_JS(void, call0_kwarg, (int f_ref, uint32_t n_kw, uint32_t * key, uint32_t * v const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4); a[k] = v; } - const ret = f(a); + let ret; + if (via_call) { + ret = f.call(a); + } else { + ret = f(a); + } proxy_convert_js_to_mp_obj_jsside(ret, out); }); -EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), { +EM_JS(void, call1_kwarg, (int f_ref, bool via_call, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), { const f = proxy_js_ref[f_ref]; const a0 = proxy_convert_mp_to_js_obj_jsside(arg0); const a = {}; @@ -145,7 +159,12 @@ EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t * const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4); a[k] = v; } - const ret = f(a0, a); + let ret; + if (via_call) { + ret = f.call(a0, a); + } else { + ret = f(a0, a); + } proxy_convert_js_to_mp_obj_jsside(ret, out); }); @@ -208,12 +227,12 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const } uint32_t out[3]; if (n_args == 0) { - call0_kwarg(self->ref, n_kw, key, value, out); + call0_kwarg(self->ref, self->bind_to_self, n_kw, key, value, out); } else { // n_args == 1 uint32_t arg0[PVN]; proxy_convert_mp_to_js_obj_cside(args[0], arg0); - call1_kwarg(self->ref, arg0, n_kw, key, value, out); + call1_kwarg(self->ref, self->bind_to_self, arg0, n_kw, key, value, out); } return proxy_convert_js_to_mp_obj_cside(out); } @@ -226,7 +245,7 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const uint32_t arg0[PVN]; uint32_t out[PVN]; proxy_convert_mp_to_js_obj_cside(args[0], arg0); - call1(self->ref, arg0, out); + call1(self->ref, self->bind_to_self, arg0, out); return proxy_convert_js_to_mp_obj_cside(out); } else if (n_args == 2) { uint32_t arg0[PVN]; @@ -234,7 +253,7 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const uint32_t arg1[PVN]; proxy_convert_mp_to_js_obj_cside(args[1], arg1); uint32_t out[3]; - call2(self->ref, arg0, arg1, out); + call2(self->ref, self->bind_to_self, arg0, arg1, out); return proxy_convert_js_to_mp_obj_cside(out); } else { uint32_t value[PVN * n_args]; @@ -242,7 +261,7 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const proxy_convert_mp_to_js_obj_cside(args[i], &value[i * PVN]); } uint32_t out[3]; - calln(self->ref, n_args, value, out); + calln(self->ref, self->bind_to_self, n_args, value, out); return proxy_convert_js_to_mp_obj_cside(out); } } @@ -298,17 +317,26 @@ static mp_obj_t jsproxy_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) } } -void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { +static void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { // Load attribute. + int lookup_ret; uint32_t out[PVN]; if (attr == MP_QSTR___del__) { // For finaliser. dest[0] = MP_OBJ_FROM_PTR(&jsproxy___del___obj); dest[1] = self_in; - } else if (lookup_attr(self->ref, qstr_str(attr), out)) { + } else if ((lookup_ret = lookup_attr(self->ref, qstr_str(attr), out)) != 0) { dest[0] = proxy_convert_js_to_mp_obj_cside(out); + if (lookup_ret == 2) { + // The loaded attribute is a JavaScript method, which should be called + // with f.call(self, ...). Indicate this via the bind_to_self member. + // This will either be called immediately (due to the mp_load_method + // optimisation) or turned into a bound_method and called later. + dest[1] = self_in; + ((mp_obj_jsproxy_t *)dest[0])->bind_to_self = true; + } } else if (attr == MP_QSTR_new) { // Special case to handle construction of JS objects. // JS objects don't have a ".new" attribute, doing "Obj.new" is a Pyodide idiom for "new Obj". @@ -546,5 +574,25 @@ MP_DEFINE_CONST_OBJ_TYPE( mp_obj_t mp_obj_new_jsproxy(int ref) { mp_obj_jsproxy_t *o = mp_obj_malloc_with_finaliser(mp_obj_jsproxy_t, &mp_type_jsproxy); o->ref = ref; + o->bind_to_self = false; return MP_OBJ_FROM_PTR(o); } + +// Load/delete/store an attribute from/to the JavaScript globalThis entity. +void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + // Load attribute. + uint32_t out[PVN]; + if (lookup_attr(MP_OBJ_JSPROXY_REF_GLOBAL_THIS, qstr_str(attr), out)) { + dest[0] = proxy_convert_js_to_mp_obj_cside(out); + } + } else if (dest[1] == MP_OBJ_NULL) { + // Delete attribute. + } else { + // Store attribute. + uint32_t value[PVN]; + proxy_convert_mp_to_js_obj_cside(dest[1], value); + store_attr(MP_OBJ_JSPROXY_REF_GLOBAL_THIS, qstr_str(attr), value); + dest[0] = MP_OBJ_NULL; + } +} diff --git a/ports/webassembly/proxy_c.h b/ports/webassembly/proxy_c.h index 4ca2b8644e8..bac0a90bd68 100644 --- a/ports/webassembly/proxy_c.h +++ b/ports/webassembly/proxy_c.h @@ -38,6 +38,7 @@ typedef struct _mp_obj_jsproxy_t { mp_obj_base_t base; int ref; + bool bind_to_self; } mp_obj_jsproxy_t; extern const mp_obj_type_t mp_type_jsproxy; @@ -52,7 +53,7 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out); void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out); mp_obj_t mp_obj_new_jsproxy(int ref); -void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); +void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest); static inline bool mp_obj_is_jsproxy(mp_obj_t o) { return mp_obj_get_type(o) == &mp_type_jsproxy; diff --git a/tests/ports/webassembly/method_bind_behaviour.mjs b/tests/ports/webassembly/method_bind_behaviour.mjs new file mode 100644 index 00000000000..24de0fa3bb1 --- /dev/null +++ b/tests/ports/webassembly/method_bind_behaviour.mjs @@ -0,0 +1,43 @@ +// Test how JavaScript binds self/this when methods are called from Python. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +// Test accessing and calling JavaScript methods from Python. +mp.runPython(` +import js + +# Get the push method to call later on. +push = js.Array.prototype.push + +# Create initial array. +ar = js.Array(1, 2) +js.console.log(ar) + +# Add an element using a method (should implicitly supply "ar" as context). +print(ar.push(3)) +js.console.log(ar) + +# Add an element using prototype function, need to explicitly provide "ar" as context. +print(push.call(ar, 4)) +js.console.log(ar) + +# Add an element using a method with call and explicit context. +print(ar.push.call(ar, 5)) +js.console.log(ar) + +# Add an element using a different instances method with call and explicit context. +print(js.Array().push.call(ar, 6)) +js.console.log(ar) +`); + +// Test assigning Python functions to JavaScript objects, and using them like a method. +mp.runPython(` +import js + +a = js.Object() +a.meth1 = lambda *x: print("meth1", x) +a.meth1(1, 2) + +js.Object.prototype.meth2 = lambda *x: print("meth2", x) +a.meth2(3, 4) +`); diff --git a/tests/ports/webassembly/method_bind_behaviour.mjs.exp b/tests/ports/webassembly/method_bind_behaviour.mjs.exp new file mode 100644 index 00000000000..ab3743f6672 --- /dev/null +++ b/tests/ports/webassembly/method_bind_behaviour.mjs.exp @@ -0,0 +1,11 @@ +[ 1, 2 ] +3 +[ 1, 2, 3 ] +4 +[ 1, 2, 3, 4 ] +5 +[ 1, 2, 3, 4, 5 ] +6 +[ 1, 2, 3, 4, 5, 6 ] +meth1 (1, 2) +meth2 (3, 4) From 3185bb5827acd6a1f27a9299abee52640dd495f2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 13:06:28 +1000 Subject: [PATCH 0977/2098] py/obj: Add new type flag to indicate subscr accepts slice-on-stack. The recently merged 5e9189d6d1c00c92694888bf9c74276779c40716 now allows temporary slices to be allocated on the C stack, which is much better than allocating them on the GC heap. Unfortunately there are cases where the C-allocated slice can escape and be retained as an object, which leads to crashes (because that object points to the C stack which now has other values on it). The fix here is to add a new `MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE`. Native types should set this flag if their subscr method is guaranteed not to hold on to a reference of the slice object. Fixes issue #17733 (see also #17723). Signed-off-by: Damien George --- py/obj.h | 3 +++ py/objarray.c | 4 ++-- py/vm.c | 5 +++-- tests/basics/slice_optimise.py | 23 +++++++++++++++++++++++ tests/basics/slice_optimise.py.exp | 2 ++ 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/basics/slice_optimise.py create mode 100644 tests/basics/slice_optimise.py.exp diff --git a/py/obj.h b/py/obj.h index 6c2153697e6..4ac0cc0c611 100644 --- a/py/obj.h +++ b/py/obj.h @@ -558,6 +558,8 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); // If MP_TYPE_FLAG_ITER_IS_STREAM is set then the type implicitly gets a "return self" // getiter, and mp_stream_unbuffered_iter for iternext. // If MP_TYPE_FLAG_INSTANCE_TYPE is set then this is an instance type (i.e. defined in Python). +// If MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE is set then the "subscr" slot allows a stack +// allocated slice to be passed in (no references to it will be retained after the call). #define MP_TYPE_FLAG_NONE (0x0000) #define MP_TYPE_FLAG_IS_SUBCLASSED (0x0001) #define MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) @@ -571,6 +573,7 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); #define MP_TYPE_FLAG_ITER_IS_CUSTOM (0x0100) #define MP_TYPE_FLAG_ITER_IS_STREAM (MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_ITER_IS_CUSTOM) #define MP_TYPE_FLAG_INSTANCE_TYPE (0x0200) +#define MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE (0x0400) typedef enum { PRINT_STR = 0, diff --git a/py/objarray.c b/py/objarray.c index 1fd02693904..ac4e343d069 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -626,7 +626,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_DEFINE_CONST_OBJ_TYPE( mp_type_bytearray, MP_QSTR_bytearray, - MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER, + MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER | MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE, make_new, bytearray_make_new, print, array_print, iter, array_iterator_new, @@ -654,7 +654,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_DEFINE_CONST_OBJ_TYPE( mp_type_memoryview, MP_QSTR_memoryview, - MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER, + MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_ITER_IS_GETITER | MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE, make_new, memoryview_make_new, iter, array_iterator_new, unary_op, array_unary_op, diff --git a/py/vm.c b/py/vm.c index 6f1179721a9..6b4a878992e 100644 --- a/py/vm.c +++ b/py/vm.c @@ -865,9 +865,10 @@ unwind_jump:; // 3-argument slice includes step step = POP(); } - if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) && mp_obj_is_native_type(mp_obj_get_type(sp[-2]))) { + if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) + && (mp_obj_get_type(sp[-2])->flags & MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE)) { // Fast path optimisation for when the BUILD_SLICE is immediately followed - // by a LOAD/STORE_SUBSCR for a native type to avoid needing to allocate + // by a LOAD/STORE_SUBSCR for an accepting type, to avoid needing to allocate // the slice on the heap. In some cases (e.g. a[1:3] = x) this can result // in no allocations at all. We can't do this for instance types because // the get/set/delattr implementation may keep a reference to the slice. diff --git a/tests/basics/slice_optimise.py b/tests/basics/slice_optimise.py new file mode 100644 index 00000000000..f663e16b8c2 --- /dev/null +++ b/tests/basics/slice_optimise.py @@ -0,0 +1,23 @@ +# Test that the slice-on-stack optimisation does not break various uses of slice +# (see MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE type option). +# +# Note: this test has a corresponding .py.exp file because hashing slice objects +# was not allowed in CPython until 3.12. + +try: + from collections import OrderedDict +except ImportError: + print("SKIP") + raise SystemExit + +# Attempt to index with a slice, error should contain the slice (failed key). +try: + dict()[:] +except KeyError as e: + print("KeyError", e.args) + +# Put a slice and another object into an OrderedDict, and retrieve them. +x = OrderedDict() +x[:"a"] = 1 +x["b"] = 2 +print(list(x.keys()), list(x.values())) diff --git a/tests/basics/slice_optimise.py.exp b/tests/basics/slice_optimise.py.exp new file mode 100644 index 00000000000..3fa59aae15a --- /dev/null +++ b/tests/basics/slice_optimise.py.exp @@ -0,0 +1,2 @@ +KeyError (slice(None, None, None),) +[slice(None, 'a', None), 'b'] [1, 2] From 5d9ef6bfb696106f8052a44c1e392695e908eafa Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 22 Jul 2025 10:48:16 +1000 Subject: [PATCH 0978/2098] py/objint_longlong: Fix left shift of negative values. Previous comment was wrong, left shifting a negative value is UB in C. Use the same approach as small int shifts (from runtime.c). Signed-off-by: Angus Gratton --- py/objint_longlong.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 22ac0ba12ef..339ce7cfd8e 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -208,14 +208,14 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i case MP_BINARY_OP_LSHIFT: case MP_BINARY_OP_INPLACE_LSHIFT: - if ((int)rhs_val < 0) { + if (rhs_val < 0) { // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } - result = lhs_val << (int)rhs_val; - // Left-shifting of negative values is implementation defined in C, but assume compiler - // will give us typical 2s complement behaviour unless the value overflows - overflow = rhs_val > 0 && ((lhs_val >= 0 && result < lhs_val) || (lhs_val < 0 && result > lhs_val)); + overflow = rhs_val >= (sizeof(long long) * MP_BITS_PER_BYTE) + || lhs_val > (LLONG_MAX >> rhs_val) + || lhs_val < (LLONG_MIN >> rhs_val); + result = (unsigned long long)lhs_val << rhs_val; break; case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: From 096ff8b9ee216a8ca0345c00946799f8342570a6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 22 Jul 2025 11:17:23 +1000 Subject: [PATCH 0979/2098] tests/micropython: Rename viper boundary tests that depend on big int. These tests all depend on generating arbitrarily long (>64-bit) integers. It would be possible to have these tests work in this case I think, as the results are always masked to shorter values. But quite fiddly. So just rename them so they are automatically skipped if the target doesn't have big int support. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ...r16_store_boundary.py => viper_ptr16_store_boundary_intbig.py} | 0 ...e_boundary.py.exp => viper_ptr16_store_boundary_intbig.py.exp} | 0 ...r32_store_boundary.py => viper_ptr32_store_boundary_intbig.py} | 0 ...e_boundary.py.exp => viper_ptr32_store_boundary_intbig.py.exp} | 0 ...ptr8_store_boundary.py => viper_ptr8_store_boundary_intbig.py} | 0 ...re_boundary.py.exp => viper_ptr8_store_boundary_intbig.py.exp} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/micropython/{viper_ptr16_store_boundary.py => viper_ptr16_store_boundary_intbig.py} (100%) rename tests/micropython/{viper_ptr16_store_boundary.py.exp => viper_ptr16_store_boundary_intbig.py.exp} (100%) rename tests/micropython/{viper_ptr32_store_boundary.py => viper_ptr32_store_boundary_intbig.py} (100%) rename tests/micropython/{viper_ptr32_store_boundary.py.exp => viper_ptr32_store_boundary_intbig.py.exp} (100%) rename tests/micropython/{viper_ptr8_store_boundary.py => viper_ptr8_store_boundary_intbig.py} (100%) rename tests/micropython/{viper_ptr8_store_boundary.py.exp => viper_ptr8_store_boundary_intbig.py.exp} (100%) diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary_intbig.py similarity index 100% rename from tests/micropython/viper_ptr16_store_boundary.py rename to tests/micropython/viper_ptr16_store_boundary_intbig.py diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary_intbig.py.exp similarity index 100% rename from tests/micropython/viper_ptr16_store_boundary.py.exp rename to tests/micropython/viper_ptr16_store_boundary_intbig.py.exp diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary_intbig.py similarity index 100% rename from tests/micropython/viper_ptr32_store_boundary.py rename to tests/micropython/viper_ptr32_store_boundary_intbig.py diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary_intbig.py.exp similarity index 100% rename from tests/micropython/viper_ptr32_store_boundary.py.exp rename to tests/micropython/viper_ptr32_store_boundary_intbig.py.exp diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary_intbig.py similarity index 100% rename from tests/micropython/viper_ptr8_store_boundary.py rename to tests/micropython/viper_ptr8_store_boundary_intbig.py diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary_intbig.py.exp similarity index 100% rename from tests/micropython/viper_ptr8_store_boundary.py.exp rename to tests/micropython/viper_ptr8_store_boundary_intbig.py.exp From 0c8d35b322554293c0847a618fa90760cefa1b60 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 18 Jul 2025 06:33:11 -0500 Subject: [PATCH 0980/2098] ports: Eliminate define of {U,}INT_FMT where redundant. The default definition in `py/mpconfig.h` for 32-bit architectures is `%u/%d`, so these can be removed. Signed-off-by: Jeff Epler --- ports/cc3200/mpconfigport.h | 3 --- ports/esp32/mpconfigport.h | 3 --- ports/esp8266/mpconfigport.h | 3 --- ports/nrf/mpconfigport.h | 2 -- ports/pic16bit/mpconfigport.h | 2 -- ports/renesas-ra/mpconfigport.h | 2 -- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 2 -- ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 2 -- ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h | 2 -- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 2 -- ports/stm32/mpconfigport.h | 2 -- ports/webassembly/mpconfigport.h | 2 -- 12 files changed, 27 deletions(-) diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index f1ba4bedd00..3e8498e380b 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -155,9 +155,6 @@ #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #define MP_SSIZE_MAX (0x7FFFFFFF) -#define UINT_FMT "%u" -#define INT_FMT "%d" - typedef int32_t mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size typedef long mp_off_t; diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index a6f103cdef7..721f22de11c 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -326,9 +326,6 @@ void *esp_native_code_commit(void *, size_t, void *); #define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f #define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f -#define UINT_FMT "%u" -#define INT_FMT "%d" - typedef int32_t mp_int_t; // must be pointer size typedef uint32_t mp_uint_t; // must be pointer size typedef long mp_off_t; diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 03f3bb643d1..bc295719026 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -150,9 +150,6 @@ #define MP_SSIZE_MAX (0x7fffffff) -#define UINT_FMT "%u" -#define INT_FMT "%d" - typedef int32_t mp_int_t; // must be pointer size typedef uint32_t mp_uint_t; // must be pointer size typedef long mp_off_t; diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index d52b5745d4e..963e1e8836d 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -335,8 +335,6 @@ void *nrf_native_code_commit(void *, unsigned int, void *); #define MP_SSIZE_MAX (0x7fffffff) -#define UINT_FMT "%u" -#define INT_FMT "%d" #define HEX2_FMT "%02x" typedef int mp_int_t; // must be pointer size diff --git a/ports/pic16bit/mpconfigport.h b/ports/pic16bit/mpconfigport.h index d80f7edb9e5..7e6e1c4e02b 100644 --- a/ports/pic16bit/mpconfigport.h +++ b/ports/pic16bit/mpconfigport.h @@ -77,8 +77,6 @@ #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index 8a116269bb4..868cdbc7d6a 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -234,8 +234,6 @@ // Assume that if we already defined the obj repr then we also defined these items #ifndef MICROPY_OBJ_REPR -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size #endif diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index 44f6ce66bc4..17d338fdc33 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -12,8 +12,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-giga-r1-wifi" #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index 47bf1be23d4..1be6189548b 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -12,8 +12,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-nicla-vision" #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size diff --git a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h index f52c8a26a81..fc563b2800e 100644 --- a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h @@ -12,8 +12,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-opta" #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index a9ecf38fbfa..d8818f257cf 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -12,8 +12,6 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-portenta-h7" #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index b910188c5af..35deb93c6a0 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -230,8 +230,6 @@ extern const struct _mp_obj_type_t network_lan_type; // Assume that if we already defined the obj repr then we also defined these items #ifndef MICROPY_OBJ_REPR -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size #endif diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h index ab56162ca2b..eea6f02a026 100644 --- a/ports/webassembly/mpconfigport.h +++ b/ports/webassembly/mpconfigport.h @@ -107,8 +107,6 @@ // different targets may be defined in different ways - either as int // or as long. This requires different printf formatting specifiers // to print such value. So, we avoid int32_t and use int directly. -#define UINT_FMT "%u" -#define INT_FMT "%d" typedef int mp_int_t; // must be pointer size typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; From 7493275918dbe7d787044e648bc8036062cf7b0d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 18 Jul 2025 06:37:26 -0500 Subject: [PATCH 0981/2098] py/mpconfig,ports: Define new HEX_FMT formatting macro. Signed-off-by: Jeff Epler --- ports/powerpc/mpconfigport.h | 1 + ports/qemu/mpconfigport.h | 1 + ports/stm32/mpconfigport_nanbox.h | 1 + ports/unix/variants/nanbox/mpconfigvariant.h | 1 + py/mpconfig.h | 3 +++ 5 files changed, 7 insertions(+) diff --git a/ports/powerpc/mpconfigport.h b/ports/powerpc/mpconfigport.h index b74f374e7f9..25d85c9e61a 100644 --- a/ports/powerpc/mpconfigport.h +++ b/ports/powerpc/mpconfigport.h @@ -96,6 +96,7 @@ // This port is 64-bit #define UINT_FMT "%lu" #define INT_FMT "%ld" +#define HEX_FMT "%lx" typedef signed long mp_int_t; // must be pointer size typedef unsigned long mp_uint_t; // must be pointer size diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index b0250727732..9c879f55dfe 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -72,6 +72,7 @@ #define UINT_FMT "%lu" #define INT_FMT "%ld" +#define HEX_FMT "%lx" typedef int32_t mp_int_t; // must be pointer size typedef uint32_t mp_uint_t; // must be pointer size diff --git a/ports/stm32/mpconfigport_nanbox.h b/ports/stm32/mpconfigport_nanbox.h index f36d55aca9b..ffd87ba2f6c 100644 --- a/ports/stm32/mpconfigport_nanbox.h +++ b/ports/stm32/mpconfigport_nanbox.h @@ -36,6 +36,7 @@ // Types needed for nan-boxing #define UINT_FMT "%llu" #define INT_FMT "%lld" +#define HEX_FMT "%llx" typedef int64_t mp_int_t; typedef uint64_t mp_uint_t; diff --git a/ports/unix/variants/nanbox/mpconfigvariant.h b/ports/unix/variants/nanbox/mpconfigvariant.h index 7b13b7dc6ce..8b23b93a8d3 100644 --- a/ports/unix/variants/nanbox/mpconfigvariant.h +++ b/ports/unix/variants/nanbox/mpconfigvariant.h @@ -48,3 +48,4 @@ typedef int64_t mp_int_t; typedef uint64_t mp_uint_t; #define UINT_FMT "%llu" #define INT_FMT "%lld" +#define HEX_FMT "%llx" diff --git a/py/mpconfig.h b/py/mpconfig.h index b812cf032a4..619bce2ab29 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2187,13 +2187,16 @@ typedef time_t mp_timestamp_t; // Archs where mp_int_t == long, long != int #define UINT_FMT "%lu" #define INT_FMT "%ld" +#define HEX_FMT "%lx" #elif defined(_WIN64) #define UINT_FMT "%llu" #define INT_FMT "%lld" +#define HEX_FMT "%llx" #else // Archs where mp_int_t == int #define UINT_FMT "%u" #define INT_FMT "%d" +#define HEX_FMT "%x" #endif #endif // INT_FMT From 519cba4d050e0c0bf69039908ef255b421d8ce57 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 21 Jun 2025 15:37:35 +0200 Subject: [PATCH 0982/2098] py: Cast type names to qstr explicitly. The name field of type objects is of type `uint16_t` for efficiency, but when the type is passed to `mp_printf` it must be cast explicitly to type `qstr`. These locations were found using an experimental gcc plugin for `mp_printf` error checking, cross-building for x64 windows on Linux. Signed-off-by: Jeff Epler --- py/builtinhelp.c | 2 +- py/obj.c | 2 +- py/objdict.c | 2 +- py/objnamedtuple.c | 2 +- py/objtype.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/py/builtinhelp.c b/py/builtinhelp.c index a3fcc4dfb77..c08c2e3b634 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -135,7 +135,7 @@ static void mp_help_print_obj(const mp_obj_t obj) { // try to print something sensible about the given object mp_print_str(MP_PYTHON_PRINTER, "object "); mp_obj_print(obj, PRINT_STR); - mp_printf(MP_PYTHON_PRINTER, " is of type %q\n", type->name); + mp_printf(MP_PYTHON_PRINTER, " is of type %q\n", (qstr)type->name); mp_map_t *map = NULL; if (type == &mp_type_module) { diff --git a/py/obj.c b/py/obj.c index 58675946076..26a912fc682 100644 --- a/py/obj.c +++ b/py/obj.c @@ -128,7 +128,7 @@ void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t if (MP_OBJ_TYPE_HAS_SLOT(type, print)) { MP_OBJ_TYPE_GET_SLOT(type, print)((mp_print_t *)print, o_in, kind); } else { - mp_printf(print, "<%q>", type->name); + mp_printf(print, "<%q>", (qstr)type->name); } } diff --git a/py/objdict.c b/py/objdict.c index cf64fa9555a..451e8732903 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -84,7 +84,7 @@ static void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ #endif } if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { - mp_printf(print, "%q(", self->base.type->name); + mp_printf(print, "%q(", (qstr)self->base.type->name); } mp_print_str(print, "{"); size_t cur = 0; diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c index f019604d525..e8447ee31ef 100644 --- a/py/objnamedtuple.c +++ b/py/objnamedtuple.c @@ -63,7 +63,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(namedtuple_asdict_obj, namedtuple_asdict); static void namedtuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_namedtuple_t *o = MP_OBJ_TO_PTR(o_in); - mp_printf(print, "%q", o->tuple.base.type->name); + mp_printf(print, "%q", (qstr)o->tuple.base.type->name); const qstr *fields = ((mp_obj_namedtuple_type_t *)o->tuple.base.type)->fields; mp_obj_attrtuple_print_helper(print, fields, &o->tuple); } diff --git a/py/objtype.c b/py/objtype.c index b9af1008995..f2173c79a17 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -977,7 +977,7 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { static void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", self->name); + mp_printf(print, "", (qstr)self->name); } static mp_obj_t type_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { From d0d111356f36373bdda387626162f60b38f2177a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 21 Jun 2025 15:40:38 +0200 Subject: [PATCH 0983/2098] py: Fix mp_printf integer size mismatches. The type of the argument must match the format string. Add casts to ensure that they do. It's possible that casting from `size_t` to `unsigned` loses the correct values by masking off upper bits, but it seems likely that the quantities involved in practice are small enough that the `%u` formatter (32 bits on most platforms, 16 on pic16bit) will in fact hold the correct value. The alternative, casting to a wider type, adds code size. These locations were found using an experimental gcc plugin for `mp_printf` error checking, cross-building for x64 windows on Linux. In one case there was already a cast, but it was written incorrectly and did not have the intended effect. Signed-off-by: Jeff Epler --- py/gc.c | 2 +- py/modmicropython.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py/gc.c b/py/gc.c index eda63187b20..de66137f580 100644 --- a/py/gc.c +++ b/py/gc.c @@ -1202,7 +1202,7 @@ void gc_dump_alloc_table(const mp_print_t *print) { } if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) { // there are at least 2 lines containing only free blocks, so abbreviate their printing - mp_printf(print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE); + mp_printf(print, "\n (%u lines all free)", (uint)((bl2 - bl) / DUMP_BYTES_PER_LINE)); bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1)); if (bl >= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB) { // got to end of heap diff --git a/py/modmicropython.c b/py/modmicropython.c index d1a687f10e1..4d676cb4ab2 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -98,7 +98,7 @@ static mp_obj_t mp_micropython_qstr_info(size_t n_args, const mp_obj_t *args) { size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); mp_printf(&mp_plat_print, "qstr pool: n_pool=%u, n_qstr=%u, n_str_data_bytes=%u, n_total_bytes=%u\n", - n_pool, n_qstr, n_str_data_bytes, n_total_bytes); + (uint)n_pool, (uint)n_qstr, (uint)n_str_data_bytes, (uint)n_total_bytes); if (n_args == 1) { // arg given means dump qstr data qstr_dump_data(); From a1a8eacdce18b953bf16e669b1519b79ca3ab49d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 24 Jun 2025 10:07:37 +0200 Subject: [PATCH 0984/2098] unix/coverage: Avoid type checking an invalid string. We still want this not to crash a runtime but the new static checker wouldn't like it. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 68340d7f239..5bd02f6fbbc 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -230,7 +230,12 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%u\n", 0x80000000); // should print unsigned mp_printf(&mp_plat_print, "%x\n", 0x8000000f); // should print unsigned mp_printf(&mp_plat_print, "%X\n", 0x8000000f); // should print unsigned - mp_printf(&mp_plat_print, "abc\n%"); // string ends in middle of format specifier + // note: storing the string in a variable is enough to prevent the + // format string checker from checking this format string. Otherwise, + // it would be a compile time diagnostic under the format string + // checker. + const char msg[] = "abc\n%"; + mp_printf(&mp_plat_print, msg); // string ends in middle of format specifier mp_printf(&mp_plat_print, "%%\n"); // literal % character mp_printf(&mp_plat_print, ".%-3s.\n", "a"); // left adjust From a06857a11ad16a3ca1e22815687c37a5ebd38fb6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 24 Jun 2025 10:06:58 +0200 Subject: [PATCH 0985/2098] unix/coverage: Cast type names to qstr explicitly. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 5bd02f6fbbc..c27198b3b31 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -218,7 +218,7 @@ static mp_obj_t extra_coverage(void) { } mp_printf(&mp_plat_print, "%p\n", (void *)0x789f); // pointer mp_printf(&mp_plat_print, "%P\n", (void *)0x789f); // pointer uppercase - mp_printf(&mp_plat_print, "%.2s %.3s '%4.4s' '%5.5q' '%.3q'\n", "abc", "abc", "abc", MP_QSTR_True, MP_QSTR_True); // fixed string precision + mp_printf(&mp_plat_print, "%.2s %.3s '%4.4s' '%5.5q' '%.3q'\n", "abc", "abc", "abc", (qstr)MP_QSTR_True, (qstr)MP_QSTR_True); // fixed string precision mp_printf(&mp_plat_print, "%.*s\n", -1, "abc"); // negative string precision mp_printf(&mp_plat_print, "%b %b\n", 0, 1); // bools #ifndef NDEBUG From 61006d80169ea4dc12b62b835d41b9c55f673111 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 10 Jul 2025 19:39:10 +0100 Subject: [PATCH 0986/2098] unix/coverage: Cast values to fit %x formatting code. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the following diagnostic produced by the plugin: error: argument 3: Format ‘%x’ requires a ‘int’ or ‘unsigned int’ (32 bits), not ‘long unsigned int’ [size 64] [-Werror=format=] Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index c27198b3b31..8b4072b9a37 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -528,7 +528,7 @@ static mp_obj_t extra_coverage(void) { // convert a large integer value (stored in a mpz) to mp_uint_t and to ll; mp_obj_t obj_bigint = mp_obj_new_int_from_uint((mp_uint_t)0xdeadbeef); - mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + mp_printf(&mp_plat_print, "%x\n", (int)mp_obj_get_uint(obj_bigint)); obj_bigint = mp_obj_new_int_from_ll(0xc0ffee777c0ffeell); long long value_ll = mp_obj_get_ll(obj_bigint); mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); @@ -536,13 +536,13 @@ static mp_obj_t extra_coverage(void) { // convert a large integer value (stored via a struct object) to uint and to ll // `deadbeef` global is an uctypes.struct defined by extra_coverage.py obj_bigint = mp_load_global(MP_QSTR_deadbeef); - mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + mp_printf(&mp_plat_print, "%x\n", (int)mp_obj_get_uint(obj_bigint)); value_ll = mp_obj_get_ll(obj_bigint); mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); // convert a smaller integer value to mp_uint_t and to ll obj_bigint = mp_obj_new_int_from_uint(0xc0ffee); - mp_printf(&mp_plat_print, "%x\n", mp_obj_get_uint(obj_bigint)); + mp_printf(&mp_plat_print, "%x\n", (int)mp_obj_get_uint(obj_bigint)); value_ll = mp_obj_get_ll(obj_bigint); mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); } From db7e93591702a8817bf6f9adde728698546ef7a2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 24 Jun 2025 10:08:19 +0200 Subject: [PATCH 0987/2098] unix/coverage: Cast values to int for format printing. During the coverage test, all the values encountered are within the range of `%d`. These locations were found using an experimental gcc plugin for `mp_printf` error checking. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 8b4072b9a37..aef123bb6b7 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -171,13 +171,13 @@ static void pairheap_test(size_t nops, int *ops) { if (mp_pairheap_is_empty(pairheap_lt, heap)) { mp_printf(&mp_plat_print, " -"); } else { - mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + mp_printf(&mp_plat_print, " %d", (int)(mp_pairheap_peek(pairheap_lt, heap) - &node[0])); ; } } mp_printf(&mp_plat_print, "\npop all:"); while (!mp_pairheap_is_empty(pairheap_lt, heap)) { - mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + mp_printf(&mp_plat_print, " %d", (int)(mp_pairheap_peek(pairheap_lt, heap) - &node[0])); ; heap = mp_pairheap_pop(pairheap_lt, heap); } @@ -274,7 +274,7 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%p\n", gc_realloc(p, 0, false)); // calling gc_nbytes with a non-heap pointer - mp_printf(&mp_plat_print, "%p\n", gc_nbytes(NULL)); + mp_printf(&mp_plat_print, "%d\n", (int)gc_nbytes(NULL)); } // GC initialisation and allocation stress test, to check the logic behind ALLOC_TABLE_GAP_BYTE @@ -335,7 +335,7 @@ static mp_obj_t extra_coverage(void) { } ptrs[i][j] = j; } - mp_printf(&mp_plat_print, "%d %d\n", i, all_zero); + mp_printf(&mp_plat_print, "%d %d\n", (int)i, (int)all_zero); // hide the pointer from the GC and collect ptrs[i] = FLIP_POINTER(ptrs[i]); @@ -351,7 +351,7 @@ static mp_obj_t extra_coverage(void) { break; } } - mp_printf(&mp_plat_print, "%d %d\n", i, correct_contents); + mp_printf(&mp_plat_print, "%d %d\n", (int)i, (int)correct_contents); } // free the memory blocks @@ -449,7 +449,7 @@ static mp_obj_t extra_coverage(void) { // create a bytearray via mp_obj_new_bytearray mp_buffer_info_t bufinfo; mp_get_buffer_raise(mp_obj_new_bytearray(4, "data"), &bufinfo, MP_BUFFER_RW); - mp_printf(&mp_plat_print, "%.*s\n", bufinfo.len, bufinfo.buf); + mp_printf(&mp_plat_print, "%.*s\n", (int)bufinfo.len, bufinfo.buf); } // mpz @@ -516,11 +516,11 @@ static mp_obj_t extra_coverage(void) { // hash the zero mpz integer mpz_set_from_int(&mpz, 0); - mp_printf(&mp_plat_print, "%d\n", mpz_hash(&mpz)); + mp_printf(&mp_plat_print, "%d\n", (int)mpz_hash(&mpz)); // convert the mpz zero integer to int mp_printf(&mp_plat_print, "%d\n", mpz_as_int_checked(&mpz, &value_signed)); - mp_printf(&mp_plat_print, "%d\n", value_signed); + mp_printf(&mp_plat_print, "%d\n", (int)value_signed); // mpz_set_from_float with 0 as argument mpz_set_from_float(&mpz, 0); @@ -562,7 +562,7 @@ static mp_obj_t extra_coverage(void) { mp_call_function_2_protected(MP_OBJ_FROM_PTR(&mp_builtin_divmod_obj), mp_obj_new_str_from_cstr("abc"), mp_obj_new_str_from_cstr("abc")); // mp_obj_int_get_checked with mp_obj_int_t that has a value that is a small integer - mp_printf(&mp_plat_print, "%d\n", mp_obj_int_get_checked(MP_OBJ_FROM_PTR(mp_obj_int_new_mpz()))); + mp_printf(&mp_plat_print, "%d\n", (int)mp_obj_int_get_checked(MP_OBJ_FROM_PTR(mp_obj_int_new_mpz()))); // mp_obj_int_get_uint_checked with non-negative small-int mp_printf(&mp_plat_print, "%d\n", (int)mp_obj_int_get_uint_checked(MP_OBJ_NEW_SMALL_INT(1))); @@ -674,7 +674,7 @@ static mp_obj_t extra_coverage(void) { #endif mp_vm_return_kind_t ret = mp_execute_bytecode(code_state, MP_OBJ_NULL); - mp_printf(&mp_plat_print, "%d %d\n", ret, mp_obj_get_type(code_state->state[0]) == &mp_type_NotImplementedError); + mp_printf(&mp_plat_print, "%d %d\n", (int)ret, mp_obj_get_type(code_state->state[0]) == &mp_type_NotImplementedError); } // scheduler @@ -754,36 +754,36 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "# ringbuf\n"); // Single-byte put/get with empty ringbuf. - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); ringbuf_put(&ringbuf, 22); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); mp_printf(&mp_plat_print, "%d\n", ringbuf_get(&ringbuf)); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); // Two-byte put/get with empty ringbuf. ringbuf_put16(&ringbuf, 0xaa55); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); // Two-byte put with full ringbuf. for (int i = 0; i < 99; ++i) { ringbuf_put(&ringbuf, i); } - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x11bb)); // Two-byte put with one byte free. ringbuf_get(&ringbuf); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x3377)); ringbuf_get(&ringbuf); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0xcc99)); for (int i = 0; i < 97; ++i) { ringbuf_get(&ringbuf); } mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); - mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", (int)ringbuf_free(&ringbuf), (int)ringbuf_avail(&ringbuf)); // Two-byte put with wrap around on first byte: ringbuf.iput = 0; From aa9152ae0c61cab59691d74c95cf6fe10c3d5ea8 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 24 Jun 2025 10:06:50 +0200 Subject: [PATCH 0988/2098] unix/coverage: Provide argmuents of expected integer types. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index aef123bb6b7..0b97226f7ca 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -203,7 +203,7 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "# mp_printf\n"); mp_printf(&mp_plat_print, "%d %+d % d\n", -123, 123, 123); // sign mp_printf(&mp_plat_print, "%05d\n", -123); // negative number with zero padding - mp_printf(&mp_plat_print, "%ld\n", 123); // long + mp_printf(&mp_plat_print, "%ld\n", 123l); // long mp_printf(&mp_plat_print, "%lx\n", 0x123fl); // long hex mp_printf(&mp_plat_print, "%lX\n", 0x123fl); // capital long hex if (sizeof(mp_int_t) == 8) { From 338ca3b68f3ca8b38838198e7cb147940192cca2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 17 Jul 2025 12:28:18 -0500 Subject: [PATCH 0989/2098] unix/coverage: Remove unused printf arguments. Signed-off-by: Jeff Epler --- ports/unix/coverage.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 0b97226f7ca..f071049eded 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -212,9 +212,9 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%llu\n", ULLONG_MAX); // unsigned long long } else { // fake for platforms without narrower mp_int_t - mp_printf(&mp_plat_print, "7fffffffffffffff\n", LLONG_MAX); - mp_printf(&mp_plat_print, "7FFFFFFFFFFFFFFF\n", LLONG_MAX); - mp_printf(&mp_plat_print, "18446744073709551615\n", ULLONG_MAX); + mp_printf(&mp_plat_print, "7fffffffffffffff\n"); + mp_printf(&mp_plat_print, "7FFFFFFFFFFFFFFF\n"); + mp_printf(&mp_plat_print, "18446744073709551615\n"); } mp_printf(&mp_plat_print, "%p\n", (void *)0x789f); // pointer mp_printf(&mp_plat_print, "%P\n", (void *)0x789f); // pointer uppercase From 18a835e9b4019a1b141f481af902e172daceb8e9 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 24 Jun 2025 10:47:21 +0200 Subject: [PATCH 0990/2098] examples/usercmodule: Cast arguments for printf. These locations were found using an experimental gcc plugin for `mp_printf` error checking. Signed-off-by: Jeff Epler --- examples/usercmodule/cexample/examplemodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c index 83cc3b27c05..a2b4d766f10 100644 --- a/examples/usercmodule/cexample/examplemodule.c +++ b/examples/usercmodule/cexample/examplemodule.c @@ -86,12 +86,12 @@ static void example_AdvancedTimer_print(const mp_print_t *print, mp_obj_t self_i mp_uint_t elapsed = mp_obj_get_int(example_Timer_time(self_in)); // We'll make all representations print at least the class name. - mp_printf(print, "%q()", MP_QSTR_AdvancedTimer); + mp_printf(print, "%q()", (qstr)MP_QSTR_AdvancedTimer); // Decide what else to print based on print kind. if (kind == PRINT_STR) { // For __str__, let's attempt to make it more readable. - mp_printf(print, " # created %d seconds ago", elapsed / 1000); + mp_printf(print, " # created %d seconds ago", (int)(elapsed / 1000)); } } From 2d93909ebe050ab08abf7de63f735b1417e02084 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 6 Jul 2025 08:12:01 +0100 Subject: [PATCH 0991/2098] extmod/modlwip: Print timeout with correct format string. As timeout is of type `mp_uint_t`, it must be printed with UINT_FMT. Before, the compiler plugin produced an error in the PYBD_SF6 build, which is a nanboxing build with 64-bit ints. Signed-off-by: Jeff Epler --- extmod/modlwip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index b53559ed8cf..b84b3b7626b 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -907,7 +907,7 @@ static const mp_obj_type_t lwip_socket_type; static void lwip_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { lwip_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", self->incoming.tcp.pbuf, self->recv_offset); } else { From 4495610f8d5d38bf9f4b82f5568675509019758e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 6 Jul 2025 08:19:21 +0100 Subject: [PATCH 0992/2098] shared/netutils: Cast the ticks value before printing. Before, the compiler plugin produced an error in the PYBD_SF6 build, which is a nanboxing build with 64-bit ints. I made the decision here to cast the value even though some significant bits might be lost after 49.7 days. However, the format used is "% 8d", which produces a consistent width output for small ticks values (up to about 1.1 days). I judged that it was more valuable to preserve the fixed width display than to accurately represent long time periods. Signed-off-by: Jeff Epler --- shared/netutils/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/netutils/trace.c b/shared/netutils/trace.c index a6dfb42c28f..24af4d5ca31 100644 --- a/shared/netutils/trace.c +++ b/shared/netutils/trace.c @@ -56,7 +56,7 @@ static const char *ethertype_str(uint16_t type) { } void netutils_ethernet_trace(const mp_print_t *print, size_t len, const uint8_t *buf, unsigned int flags) { - mp_printf(print, "[% 8d] ETH%cX len=%u", mp_hal_ticks_ms(), flags & NETUTILS_TRACE_IS_TX ? 'T' : 'R', len); + mp_printf(print, "[% 8u] ETH%cX len=%u", (unsigned)mp_hal_ticks_ms(), flags & NETUTILS_TRACE_IS_TX ? 'T' : 'R', len); mp_printf(print, " dst=%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); mp_printf(print, " src=%02x:%02x:%02x:%02x:%02x:%02x", buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]); From ee4f27affa2a4cbf940081da4bdfa36d22351461 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 5 Jul 2025 09:34:29 +0100 Subject: [PATCH 0993/2098] py/objcell: Fix printing of cell ID/pointer. On the nanbox build, `o->obj` is a 64-bit type but `%p` formats a 32-bit type, leading to undefined behavior. Print the cell's ID as a hex integer instead. This location was found using an experimental gcc plugin for `mp_printf` error checking. Signed-off-by: Jeff Epler --- py/objcell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/objcell.c b/py/objcell.c index 95966c7917c..5c030c7405a 100644 --- a/py/objcell.c +++ b/py/objcell.c @@ -30,7 +30,7 @@ static void cell_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_cell_t *o = MP_OBJ_TO_PTR(o_in); - mp_printf(print, "obj); + mp_printf(print, "obj); if (o->obj == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); } else { From 87b7a9d7349c28b3acb13815bb63484d12203895 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 6 Jul 2025 08:14:31 +0100 Subject: [PATCH 0994/2098] stm32: Add casts when printing small integers. All these arguments are of type `mp_{u,}int_t`, but the actual value is always a small integer. Cast it so that it can format with the `%d/%u` formatter. Before, the compiler plugin produced an error in the PYBD_SF6 build, which is a nanboxing build with 64-bit ints. Signed-off-by: Jeff Epler --- ports/stm32/extint.c | 2 +- ports/stm32/led.c | 2 +- ports/stm32/machine_uart.c | 2 +- ports/stm32/pin.c | 2 +- ports/stm32/timer.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/stm32/extint.c b/ports/stm32/extint.c index 9c3c2432536..df0ed6e23e1 100644 --- a/ports/stm32/extint.c +++ b/ports/stm32/extint.c @@ -760,7 +760,7 @@ static mp_obj_t extint_make_new(const mp_obj_type_t *type, size_t n_args, size_t static void extint_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { extint_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", self->line); + mp_printf(print, "", (int)self->line); } static const mp_rom_map_elem_t extint_locals_dict_table[] = { diff --git a/ports/stm32/led.c b/ports/stm32/led.c index 795d8c11096..2fcb2abb60d 100644 --- a/ports/stm32/led.c +++ b/ports/stm32/led.c @@ -305,7 +305,7 @@ void led_debug(int n, int delay) { void led_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_led_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "LED(%u)", self->led_id); + mp_printf(print, "LED(%u)", (int)self->led_id); } /// \classmethod \constructor(id) diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 8f1faea4b69..c93eade5d3d 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -93,7 +93,7 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #endif { mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=", - self->uart_id, uart_get_baudrate(self), bits); + self->uart_id, uart_get_baudrate(self), (int)bits); } if (!(cr1 & USART_CR1_PCE)) { mp_print_str(print, "None"); diff --git a/ports/stm32/pin.c b/ports/stm32/pin.c index 7de87f2c7be..515437ac861 100644 --- a/ports/stm32/pin.c +++ b/ports/stm32/pin.c @@ -232,7 +232,7 @@ static void pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t mp_uint_t af_idx = pin_get_af(self); const pin_af_obj_t *af_obj = pin_find_af_by_index(self, af_idx); if (af_obj == NULL) { - mp_printf(print, ", alt=%d)", af_idx); + mp_printf(print, ", alt=%d)", (int)af_idx); } else { mp_printf(print, ", alt=Pin.%q)", af_obj->name); } diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 4ec467d9db5..8aa0b3a2dda 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -499,7 +499,7 @@ static uint32_t compute_dtg_from_ticks(mp_int_t ticks) { // Given the 8-bit value stored in the DTG field of the BDTR register, compute // the number of ticks. -static mp_int_t compute_ticks_from_dtg(uint32_t dtg) { +static unsigned compute_ticks_from_dtg(uint32_t dtg) { if ((dtg & 0x80) == 0) { return dtg & 0x7F; } From ab620f40844a837546497252849933ef2f685f4d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 18 Jul 2025 06:06:42 -0500 Subject: [PATCH 0995/2098] py/mpprint: Fix printing pointers with upper bit set. On a build like nanbox, `mp_uint_t` is wider than `u/intptr_t`. Using a signed type for fetching pointer values resulted in erroneous results: like `` instead of ``. Signed-off-by: Jeff Epler --- py/mpprint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpprint.c b/py/mpprint.c index e56b949ddda..f1d8bd0c573 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -530,7 +530,7 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { char fmt_chr = *fmt; mp_uint_t val; if (fmt_chr == 'p' || fmt_chr == 'P') { - val = va_arg(args, intptr_t); + val = va_arg(args, uintptr_t); } #if SUPPORT_LL_FORMAT else if (long_long_arg) { From ebc9525c953dfbb6c44ab99320b5278e4314dafe Mon Sep 17 00:00:00 2001 From: Christian Lang Date: Tue, 22 Jul 2025 21:23:12 +0200 Subject: [PATCH 0996/2098] rp2/modmachine: Do not use deprecated XOSC_MHZ and XOSC_KHZ. XOSC_MHZ and XOSC_KHZ may not be defined if we use a custom XIN clock by defining PLL_SYS_REFDIV etc. calculated by vcocalc.py. Signed-off-by: Christian Lang --- ports/rp2/clocks_extra.c | 4 ++-- ports/rp2/modmachine.c | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ports/rp2/clocks_extra.c b/ports/rp2/clocks_extra.c index 73def24b804..ab3e6261f4b 100644 --- a/ports/rp2/clocks_extra.c +++ b/ports/rp2/clocks_extra.c @@ -83,8 +83,8 @@ void runtime_init_clocks_optional_usb(bool init_usb) { clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, // No aux mux - XOSC_KHZ * KHZ, - XOSC_KHZ * KHZ); + XOSC_HZ, + XOSC_HZ); /// \tag::configure_clk_sys[] // CLK SYS = PLL SYS (usually) 125MHz / 1 = 125MHz diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 1f1b0e2f59b..e5cf703edd3 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -163,8 +163,6 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } } - const uint32_t xosc_hz = XOSC_MHZ * 1000000; - uint32_t my_interrupts = MICROPY_BEGIN_ATOMIC_SECTION(); #if MICROPY_PY_NETWORK_CYW43 if (cyw43_poll_is_pending()) { @@ -200,18 +198,18 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif // CLK_REF = XOSC - clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz); + clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, XOSC_HZ, XOSC_HZ); // CLK_SYS = CLK_REF - clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz); + 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); + 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); + clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, XOSC_HZ, XOSC_HZ); // Disable PLLs. pll_deinit(pll_sys); From 062e82a7cd60165db7edd8ee32105ea19a31ae2f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 19 Jul 2025 11:24:34 -0500 Subject: [PATCH 0997/2098] py/objint_mpz: Fix pow3 where third argument is zero. This finding is based on fuzzing MicroPython. I manually minimized the test case it provided. Signed-off-by: Jeff Epler --- py/objint_mpz.c | 7 ++++--- tests/basics/builtin_pow3_intbig.py | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 6f2ea616c77..ea4e409a257 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -356,9 +356,10 @@ static mpz_t *mp_mpz_for_int(mp_obj_t arg, mpz_t *temp) { mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { if (!mp_obj_is_int(base) || !mp_obj_is_int(exponent) || !mp_obj_is_int(modulus)) { mp_raise_TypeError(MP_ERROR_TEXT("pow() with 3 arguments requires integers")); + } else if (modulus == MP_OBJ_NEW_SMALL_INT(0)) { + mp_raise_ValueError(MP_ERROR_TEXT("divide by zero")); } else { - mp_obj_t result = mp_obj_new_int_from_ull(0); // Use the _from_ull version as this forces an mpz int - mp_obj_int_t *res_p = (mp_obj_int_t *)MP_OBJ_TO_PTR(result); + mp_obj_int_t *res_p = mp_obj_int_new_mpz(); mpz_t l_temp, r_temp, m_temp; mpz_t *lhs = mp_mpz_for_int(base, &l_temp); @@ -376,7 +377,7 @@ mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { if (mod == &m_temp) { mpz_deinit(mod); } - return result; + return MP_OBJ_FROM_PTR(res_p); } } #endif diff --git a/tests/basics/builtin_pow3_intbig.py b/tests/basics/builtin_pow3_intbig.py index bedc8b36b7e..41d2acbc0cc 100644 --- a/tests/basics/builtin_pow3_intbig.py +++ b/tests/basics/builtin_pow3_intbig.py @@ -20,3 +20,8 @@ print(hex(pow(y, x-1, x))) # Should be 1, since x is prime print(hex(pow(y, y-1, x))) # Should be a 'big value' print(hex(pow(y, y-1, y))) # Should be a 'big value' + +try: + print(pow(1, 2, 0)) +except ValueError: + print("ValueError") From 3c69277ba9e0f1e66c341c084320f7a763e1bdeb Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Mon, 21 Jul 2025 15:54:27 +0200 Subject: [PATCH 0998/2098] py/objint_longlong: Fix overflow check in mp_obj_int_get_checked. This is to fix an outstanding TODO. The test cases is using a range as this will exist in all builds, but `mp_obj_get_int` is used in many different parts of code where an overflow is more likely to occur. Signed-off-by: Yoctopuce dev --- py/objint_longlong.c | 13 +++++++++++-- tests/basics/int_64_basics.py | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 339ce7cfd8e..8b8fdc62e8b 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -308,8 +308,17 @@ mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { } mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { - // TODO: Check overflow - return mp_obj_int_get_truncated(self_in); + if (mp_obj_is_small_int(self_in)) { + return MP_OBJ_SMALL_INT_VALUE(self_in); + } else { + const mp_obj_int_t *self = self_in; + long long value = self->val; + mp_int_t truncated = (mp_int_t)value; + if ((long long)truncated == value) { + return truncated; + } + } + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); } mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in) { diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py index 289ea49b65e..2a161dac0ba 100644 --- a/tests/basics/int_64_basics.py +++ b/tests/basics/int_64_basics.py @@ -125,6 +125,22 @@ x = 1 << 62 print('a' * (x + 4 - x)) +# test overflow check in mp_obj_get_int_maybe +x = 1 << 32 +r = None +try: + r = range(0, x) +except OverflowError: + # 32-bit target, correctly handled the overflow of x + print("ok") +if r is not None: + if len(r) == x: + # 64-bit target, everything is just a small-int + print("ok") + else: + # 32-bit target that did not handle the overflow of x + print("unhandled overflow") + # negative shifts are invalid try: print((1 << 48) >> -4) From 3a72f95919323d7a36cb3d153d92de90d64853a1 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Sun, 20 Jul 2025 16:38:33 +0200 Subject: [PATCH 0999/2098] py/objint_longlong: Fix longlong interoperability with floats. Current longlong implementation does not allow a float as RHS of mathematic operators, as it lacks the delegation code present in mpz. Signed-off-by: Yoctopuce dev --- py/objint_longlong.c | 17 +++++++++++++++++ tests/float/int_64_float.py | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/float/int_64_float.py diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 8b8fdc62e8b..1a6242b9792 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -165,11 +165,28 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs_in); } else if (mp_obj_is_exact_type(rhs_in, &mp_type_int)) { rhs_val = ((mp_obj_int_t *)rhs_in)->val; + #if MICROPY_PY_BUILTINS_FLOAT + } else if (mp_obj_is_float(rhs_in)) { + return mp_obj_float_binary_op(op, (mp_float_t)lhs_val, rhs_in); + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + } else if (mp_obj_is_type(rhs_in, &mp_type_complex)) { + return mp_obj_complex_binary_op(op, (mp_float_t)lhs_val, 0, rhs_in); + #endif } else { // delegate to generic function to check for extra cases return mp_obj_int_binary_op_extra_cases(op, lhs_in, rhs_in); } + #if MICROPY_PY_BUILTINS_FLOAT + if (op == MP_BINARY_OP_TRUE_DIVIDE || op == MP_BINARY_OP_INPLACE_TRUE_DIVIDE) { + if (rhs_val == 0) { + goto zero_division; + } + return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); + } + #endif + switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: diff --git a/tests/float/int_64_float.py b/tests/float/int_64_float.py new file mode 100644 index 00000000000..ffdb0c71dba --- /dev/null +++ b/tests/float/int_64_float.py @@ -0,0 +1,25 @@ +# test int64 operation with float/complex + +i = 1 << 40 + +# convert int64 to float on rhs +print("%.5g" % (2.0 * i)) + +# negative int64 as float +print("%.5g" % float(-i)) + +# this should convert to float +print("%.5g" % (i / 5)) + +# these should delegate to float +print("%.5g" % (i * 1.2)) +print("%.5g" % (i / 1.2)) + +# negative power should produce float +print("%.5g" % (i**-1)) +print("%.5g" % ((2 + i - i) ** -3)) + +try: + i / 0 +except ZeroDivisionError: + print("ZeroDivisionError") From 82db5c81e027f6ad305a43ec3c90a13ba319e3b4 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 13:01:09 -0400 Subject: [PATCH 1000/2098] tests/basics: Add tests for PEP487 __set_name__. Including the stochastic tests needed to guarantee sensitivity to the potential iterate-while-modifying hazard a naive implementation might have. Signed-off-by: Anson Mansfield --- tests/basics/class_descriptor.py | 20 ++- tests/basics/class_setname_hazard.py | 182 ++++++++++++++++++++++ tests/basics/class_setname_hazard_rand.py | 111 +++++++++++++ 3 files changed, 306 insertions(+), 7 deletions(-) create mode 100644 tests/basics/class_setname_hazard.py create mode 100644 tests/basics/class_setname_hazard_rand.py diff --git a/tests/basics/class_descriptor.py b/tests/basics/class_descriptor.py index 83d31674301..feaed2fbb2a 100644 --- a/tests/basics/class_descriptor.py +++ b/tests/basics/class_descriptor.py @@ -1,22 +1,28 @@ class Descriptor: def __get__(self, obj, cls): - print('get') + print("get") print(type(obj) is Main) print(cls is Main) - return 'result' + return "result" def __set__(self, obj, val): - print('set') + print("set") print(type(obj) is Main) print(val) def __delete__(self, obj): - print('delete') + print("delete") print(type(obj) is Main) + def __set_name__(self, owner, name): + print("set_name", name) + print(owner.__name__ == "Main") + + class Main: Forward = Descriptor() + m = Main() try: m.__class__ @@ -26,15 +32,15 @@ class Main: raise SystemExit r = m.Forward -if 'Descriptor' in repr(r.__class__): +if "Descriptor" in repr(r.__class__): # Target doesn't support descriptors. - print('SKIP') + print("SKIP") raise SystemExit # Test assignment and deletion. print(r) -m.Forward = 'a' +m.Forward = "a" del m.Forward # Test that lookup of descriptors like __get__ are not passed into __getattr__. diff --git a/tests/basics/class_setname_hazard.py b/tests/basics/class_setname_hazard.py new file mode 100644 index 00000000000..77c04093462 --- /dev/null +++ b/tests/basics/class_setname_hazard.py @@ -0,0 +1,182 @@ +# Test that __set_name__ can access and mutate its owner argument. + + +def skip_if_no_descriptors(): + class Descriptor: + def __get__(self, obj, cls): + return + + class TestClass: + Forward = Descriptor() + + a = TestClass() + try: + a.__class__ + except AttributeError: + # Target doesn't support __class__. + print("SKIP") + raise SystemExit + + b = a.Forward + if "Descriptor" in repr(b.__class__): + # Target doesn't support descriptors. + print("SKIP") + raise SystemExit + + +skip_if_no_descriptors() + + +# Test basic accesses and mutations. + + +class GetSibling: + def __set_name__(self, owner, name): + print(getattr(owner, name + "_sib")) + + +class GetSiblingTest: + desc = GetSibling() + desc_sib = 111 + + +t110 = GetSiblingTest() + + +class SetSibling: + def __set_name__(self, owner, name): + setattr(owner, name + "_sib", 121) + + +class SetSiblingTest: + desc = SetSibling() + + +t120 = SetSiblingTest() + +print(t120.desc_sib) + + +class DelSibling: + def __set_name__(self, owner, name): + delattr(owner, name + "_sib") + + +class DelSiblingTest: + desc = DelSibling() + desc_sib = 131 + + +t130 = DelSiblingTest() + +try: + print(t130.desc_sib) +except AttributeError: + print("AttributeError") + + +class GetSelf: + x = 211 + + def __set_name__(self, owner, name): + print(getattr(owner, name).x) + + +class GetSelfTest: + desc = GetSelf() + + +t210 = GetSelfTest() + + +class SetSelf: + def __set_name__(self, owner, name): + setattr(owner, name, 221) + + +class SetSelfTest: + desc = SetSelf() + + +t220 = SetSelfTest() + +print(t220.desc) + + +class DelSelf: + def __set_name__(self, owner, name): + delattr(owner, name) + + +class DelSelfTest: + desc = DelSelf() + + +t230 = DelSelfTest() + +try: + print(t230.desc) +except AttributeError: + print("AttributeError") + + +# Test exception behavior. + + +class Raise: + def __set_name__(self, owner, name): + raise Exception() + + +try: + + class RaiseTest: + desc = Raise() +except Exception as e: # CPython raises RuntimeError, MicroPython propagates the original exception + print("Exception") + + +# Ensure removed/overwritten class members still get __set_name__ called. + + +class SetSpecific: + def __init__(self, sib_name, sib_replace): + self.sib_name = sib_name + self.sib_replace = sib_replace + + def __set_name__(self, owner, name): + setattr(owner, self.sib_name, self.sib_replace) + + +class SetReplaceTest: + a = SetSpecific("b", 312) # one of these is changed first + b = SetSpecific("a", 311) + + +t310 = SetReplaceTest() +print(t310.a) +print(t310.b) + + +class DelSpecific: + def __init__(self, sib_name): + self.sib_name = sib_name + + def __set_name__(self, owner, name): + delattr(owner, self.sib_name) + + +class DelReplaceTest: + a = DelSpecific("b") # one of these is removed first + b = DelSpecific("a") + + +t320 = DelReplaceTest() +try: + print(t320.a) +except AttributeError: + print("AttributeError") +try: + print(t320.b) +except AttributeError: + print("AttributeError") diff --git a/tests/basics/class_setname_hazard_rand.py b/tests/basics/class_setname_hazard_rand.py new file mode 100644 index 00000000000..4c9934c3bf0 --- /dev/null +++ b/tests/basics/class_setname_hazard_rand.py @@ -0,0 +1,111 @@ +# Test to make sure there's no sequence hazard even when a __set_name__ implementation +# mutates and reorders the namespace of its owner class. +# VERY hard bug to prove out except via a stochastic test. + + +try: + from random import choice + import re +except ImportError: + print("SKIP") + raise SystemExit + + +def skip_if_no_descriptors(): + class Descriptor: + def __get__(self, obj, cls): + return + + class TestClass: + Forward = Descriptor() + + a = TestClass() + try: + a.__class__ + except AttributeError: + # Target doesn't support __class__. + print("SKIP") + raise SystemExit + + b = a.Forward + if "Descriptor" in repr(b.__class__): + # Target doesn't support descriptors. + print("SKIP") + raise SystemExit + + +skip_if_no_descriptors() + +letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +# Would be r"[A-Z]{5}", but not all ports support the {n} quantifier. +junk_re = re.compile(r"[A-Z][A-Z][A-Z][A-Z][A-Z]") + + +def junk_fill(obj, n=10): # Add randomly-generated attributes to an object. + for i in range(n): + name = "".join(choice(letters) for j in range(5)) + setattr(obj, name, object()) + + +def junk_clear(obj): # Remove attributes added by junk_fill. + to_del = [name for name in dir(obj) if junk_re.match(name)] + for name in to_del: + delattr(obj, name) + + +def junk_sequencer(): + global runs + try: + while True: + owner, name = yield + runs[name] = runs.get(name, 0) + 1 + junk_fill(owner) + finally: + junk_clear(owner) + + +class JunkMaker: + def __set_name__(self, owner, name): + global seq + seq.send((owner, name)) + + +runs = {} +seq = junk_sequencer() +next(seq) + + +class Main: + a = JunkMaker() + b = JunkMaker() + c = JunkMaker() + d = JunkMaker() + e = JunkMaker() + f = JunkMaker() + g = JunkMaker() + h = JunkMaker() + i = JunkMaker() + j = JunkMaker() + k = JunkMaker() + l = JunkMaker() + m = JunkMaker() + n = JunkMaker() + o = JunkMaker() + p = JunkMaker() + q = JunkMaker() + r = JunkMaker() + s = JunkMaker() + t = JunkMaker() + u = JunkMaker() + v = JunkMaker() + w = JunkMaker() + x = JunkMaker() + y = JunkMaker() + z = JunkMaker() + + +seq.close() + +for k in letters.lower(): + print(k, runs.get(k, 0)) From 4412753f0cf46fb28093a90d58988f541a531df4 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 18:23:45 -0400 Subject: [PATCH 1001/2098] py/objtype: Add support for PEP487 __set_name__. This commit adds support for the `__set_name__` data model method specified by PEP487 - Simpler customisation of class creation. This includes support for methods that mutate the owner class, and avoids the naive modify-while-iterating hazard possible in a naive implementation like micropython/micropython#15503. Note that based on the benchmarks in micropython/micropython#16825, this is also as fast or faster than the naive implementation, thanks to clever data layout in `setname_list_t`, and the way this allows the capture step to run during an existing loop through the class dict. Other rejected approaches for dealing with the hazard include: - python/cpython#72983 During the implementation of this feature for MicroPython, it was discovered that some versions of CPython also have this naive hazard. CPython resolved this bug in BPO-28797 and now makes a complete flat copy of the class's dict to iterate. This design decision doesn't make much sense for a microcontroller though, even if it's perfectly reasonable in the desktop world where memcpy might actually be cheaper than a hard-to-branch-predict conditional; and it's also motivated in their case by error-tracing considerations. - micropython/micropython#16816 This is an equivalent implementation to CPython's approach that places this copy directly on the stack; however it is both slower and has larger code size than the approach taken here. - micropython/micropython#15503 The simplest implementation is to just not worry about it and let the user face the consequences if they mutate the owner class. That's not a very friendly behavior, though, and it's not actually much more performant than this implementation on either time or code size. - micropython/micropython#17693 Another alternative is to do the same as #15503 but leverage MicroPython's existing `is_fixed` field in its dict type to convert attempted mutations of the owner dict into `AttributeError`s. This is safer than just leaving the open hazard, but there's still important use-cases for owner-mutating descriptors, and the performance gain is small enough that it isn't worth missing support for those cases. - combined micropython/micropython#17693 with this Another version of this feature used a new feature define, `MICROPY_PY_METACLASSES_LITE`, to control whether this algorithm or the naive version is used. This was rejected in favor of simplicity, based on the very limited performance margin the naive version has (which in some cases even goes _against_ it). Signed-off-by: Anson Mansfield --- py/objtype.c | 90 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/py/objtype.c b/py/objtype.c index f2173c79a17..818ceeb0589 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -661,8 +661,8 @@ static void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des // try __getattr__ if (attr != MP_QSTR___getattr__) { #if MICROPY_PY_DESCRIPTORS - // With descriptors enabled, don't delegate lookups of __get__/__set__/__delete__. - if (attr == MP_QSTR___get__ || attr == MP_QSTR___set__ || attr == MP_QSTR___delete__) { + // With descriptors enabled, don't delegate lookups of __get__/__set__/__delete__/__set_name__. + if (attr == MP_QSTR___get__ || attr == MP_QSTR___set__ || attr == MP_QSTR___delete__ || attr == MP_QSTR___set_name__) { return; } #endif @@ -960,7 +960,7 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { #endif #if MICROPY_PY_DESCRIPTORS static const uint8_t to_check[] = { - MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, + MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, // not needed for MP_QSTR___set_name__ though }; for (size_t i = 0; i < MP_ARRAY_SIZE(to_check); ++i) { mp_obj_t dest_temp[2]; @@ -974,6 +974,48 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { } #endif +#if MICROPY_PY_DESCRIPTORS +// Shared data layout for the __set_name__ call and a linked list of calls to be made. +typedef union _setname_list_t setname_list_t; +union _setname_list_t { + mp_obj_t call[4]; + struct { + mp_obj_t _meth; + mp_obj_t _self; + setname_list_t *next; // can use the "owner" argument position temporarily for the linked list + mp_obj_t _name; + }; +}; + +// Append any `__set_name__` method on `value` to the setname list, with its per-attr args +static setname_list_t *setname_maybe_bind_append(setname_list_t *tail, mp_obj_t name, mp_obj_t value) { + // make certain our type-punning is safe: + MP_STATIC_ASSERT_NONCONSTEXPR(offsetof(setname_list_t, next) == offsetof(setname_list_t, call[2])); + + // tail is a blank list entry + mp_load_method_maybe(value, MP_QSTR___set_name__, tail->call); + if (tail->call[1] != MP_OBJ_NULL) { + // Each time a __set_name__ is found, leave it in-place in the former tail and allocate a new tail + tail->next = m_new_obj(setname_list_t); + tail->next->next = NULL; + tail->call[3] = name; + return tail->next; + } else { + return tail; + } +} + +// Execute the captured `__set_name__` calls, destroying the setname list in the process. +static inline void setname_consume_call_all(setname_list_t *head, mp_obj_t owner) { + setname_list_t *next; + while ((next = head->next) != NULL) { + head->call[2] = owner; + mp_call_method_n_kw(2, 0, head->call); + head = next; + } +} +#endif + static void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); @@ -1210,20 +1252,38 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } } + #if MICROPY_PY_DESCRIPTORS + // To avoid any dynamic allocations when no __set_name__ exists, + // the head of this list is kept on the stack (marked blank with `next = NULL`). + setname_list_t setname_list = { .next = NULL }; + setname_list_t *setname_tail = &setname_list; + #endif + #if ENABLE_SPECIAL_ACCESSORS - // Check if the class has any special accessor methods - if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { - for (size_t i = 0; i < locals_ptr->map.alloc; i++) { - if (mp_map_slot_is_filled(&locals_ptr->map, i)) { - const mp_map_elem_t *elem = &locals_ptr->map.table[i]; - if (check_for_special_accessors(elem->key, elem->value)) { - o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; - break; - } + // Check if the class has any special accessor methods, + // and accumulate bound __set_name__ methods that need to be called + for (size_t i = 0; i < locals_ptr->map.alloc; i++) { + #if !MICROPY_PY_DESCRIPTORS + // __set_name__ needs to scan the entire locals map, can't early-terminate + if (o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) { + break; + } + #endif + + if (mp_map_slot_is_filled(&locals_ptr->map, i)) { + const mp_map_elem_t *elem = &locals_ptr->map.table[i]; + + if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) // elidable when the early-termination check is enabled + && check_for_special_accessors(elem->key, elem->value)) { + o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } + + #if MICROPY_PY_DESCRIPTORS + setname_tail = setname_maybe_bind_append(setname_tail, elem->key, elem->value); + #endif } } - #endif + #endif // ENABLE_SPECIAL_ACCESSORS const mp_obj_type_t *native_base; size_t num_native_bases = instance_count_native_bases(o, &native_base); @@ -1241,6 +1301,10 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } } + #if MICROPY_PY_DESCRIPTORS + setname_consume_call_all(&setname_list, MP_OBJ_FROM_PTR(o)); + #endif + return MP_OBJ_FROM_PTR(o); } From d5dc5547427ef171e3ede4f8ac35ea9bf6f53fca Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 21 Jul 2025 08:33:17 -0400 Subject: [PATCH 1002/2098] docs: Document PEP487 __set_name__ implementation. Signed-off-by: Anson Mansfield --- docs/differences/python_36.rst | 7 ++++++- py/mpconfig.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/differences/python_36.rst b/docs/differences/python_36.rst index 3315b0594da..18da79f8f84 100644 --- a/docs/differences/python_36.rst +++ b/docs/differences/python_36.rst @@ -25,7 +25,8 @@ Python 3.6 beta 1 was released on 12 Sep 2016, and a summary of the new features +--------------------------------------------------------+--------------------------------------------------+-----------------+ | `PEP 468 `_ | Preserving the order of *kwargs* in a function | | +--------------------------------------------------------+--------------------------------------------------+-----------------+ - | `PEP 487 `_ | Simpler customization of class creation | | + | `PEP 487 `_ | Simpler customization of class creation | Partial | + | | | [#setname]_ | +--------------------------------------------------------+--------------------------------------------------+-----------------+ | `PEP 520 `_ | Preserving Class Attribute Definition Order | | +--------------------------------------------------------+--------------------------------------------------+-----------------+ @@ -198,3 +199,7 @@ Changes to built-in modules: +--------------------------------------------------------------------------------------------------------------+----------------+ | The *compress()* and *decompress()* functions now accept keyword arguments | | +--------------------------------------------------------------------------------------------------------------+----------------+ + +.. rubric:: Notes + +.. [#setname] Currently, only :func:`__set_name__` is implemented. diff --git a/py/mpconfig.h b/py/mpconfig.h index 619bce2ab29..6ef6ec52e7c 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1129,7 +1129,7 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES) #endif -// Whether to support the descriptors __get__, __set__, __delete__ +// Whether to support the descriptors __get__, __set__, __delete__, __set_name__ // This costs some code size and makes load/store/delete of instance // attributes slower for the classes that use this feature #ifndef MICROPY_PY_DESCRIPTORS From c3e77ad6db279c37bd39e467e7bbef7750fd5f7b Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 26 Feb 2025 12:38:08 -0500 Subject: [PATCH 1003/2098] tests/internal_bench/class_create: Benchmark class creation. Signed-off-by: Anson Mansfield --- tests/internal_bench/class_create-0-empty.py | 11 ++++++++++ tests/internal_bench/class_create-1-slots.py | 12 +++++++++++ .../internal_bench/class_create-1.1-slots5.py | 12 +++++++++++ .../class_create-2-classattr.py | 11 ++++++++++ .../class_create-2.1-classattr5.py | 15 +++++++++++++ .../class_create-2.3-classattr5objs.py | 20 ++++++++++++++++++ .../class_create-3-instancemethod.py | 12 +++++++++++ .../class_create-4-classmethod.py | 13 ++++++++++++ .../class_create-4.1-classmethod_implicit.py | 12 +++++++++++ .../class_create-5-staticmethod.py | 13 ++++++++++++ .../class_create-6-getattribute.py | 12 +++++++++++ .../class_create-6.1-getattr.py | 12 +++++++++++ .../class_create-6.2-property.py | 13 ++++++++++++ .../class_create-6.3-descriptor.py | 17 +++++++++++++++ .../internal_bench/class_create-7-inherit.py | 14 +++++++++++++ .../class_create-7.1-inherit_initsubclass.py | 16 ++++++++++++++ .../class_create-8-metaclass_setname.py | 17 +++++++++++++++ .../class_create-8.1-metaclass_setname5.py | 21 +++++++++++++++++++ 18 files changed, 253 insertions(+) create mode 100644 tests/internal_bench/class_create-0-empty.py create mode 100644 tests/internal_bench/class_create-1-slots.py create mode 100644 tests/internal_bench/class_create-1.1-slots5.py create mode 100644 tests/internal_bench/class_create-2-classattr.py create mode 100644 tests/internal_bench/class_create-2.1-classattr5.py create mode 100644 tests/internal_bench/class_create-2.3-classattr5objs.py create mode 100644 tests/internal_bench/class_create-3-instancemethod.py create mode 100644 tests/internal_bench/class_create-4-classmethod.py create mode 100644 tests/internal_bench/class_create-4.1-classmethod_implicit.py create mode 100644 tests/internal_bench/class_create-5-staticmethod.py create mode 100644 tests/internal_bench/class_create-6-getattribute.py create mode 100644 tests/internal_bench/class_create-6.1-getattr.py create mode 100644 tests/internal_bench/class_create-6.2-property.py create mode 100644 tests/internal_bench/class_create-6.3-descriptor.py create mode 100644 tests/internal_bench/class_create-7-inherit.py create mode 100644 tests/internal_bench/class_create-7.1-inherit_initsubclass.py create mode 100644 tests/internal_bench/class_create-8-metaclass_setname.py create mode 100644 tests/internal_bench/class_create-8.1-metaclass_setname5.py diff --git a/tests/internal_bench/class_create-0-empty.py b/tests/internal_bench/class_create-0-empty.py new file mode 100644 index 00000000000..1fd8ccd9257 --- /dev/null +++ b/tests/internal_bench/class_create-0-empty.py @@ -0,0 +1,11 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-1-slots.py b/tests/internal_bench/class_create-1-slots.py new file mode 100644 index 00000000000..9b3e4b9570d --- /dev/null +++ b/tests/internal_bench/class_create-1-slots.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + l = ["x"] + for i in range(num // 40): + + class X: + __slots__ = l + + +bench.run(test) diff --git a/tests/internal_bench/class_create-1.1-slots5.py b/tests/internal_bench/class_create-1.1-slots5.py new file mode 100644 index 00000000000..ccac77dec9d --- /dev/null +++ b/tests/internal_bench/class_create-1.1-slots5.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + l = ["a", "b", "c", "d", "x"] + for i in range(num // 40): + + class X: + __slots__ = l + + +bench.run(test) diff --git a/tests/internal_bench/class_create-2-classattr.py b/tests/internal_bench/class_create-2-classattr.py new file mode 100644 index 00000000000..049a7dab170 --- /dev/null +++ b/tests/internal_bench/class_create-2-classattr.py @@ -0,0 +1,11 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + x = 1 + + +bench.run(test) diff --git a/tests/internal_bench/class_create-2.1-classattr5.py b/tests/internal_bench/class_create-2.1-classattr5.py new file mode 100644 index 00000000000..5051e7dcca7 --- /dev/null +++ b/tests/internal_bench/class_create-2.1-classattr5.py @@ -0,0 +1,15 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + a = 0 + b = 0 + c = 0 + d = 0 + x = 1 + + +bench.run(test) diff --git a/tests/internal_bench/class_create-2.3-classattr5objs.py b/tests/internal_bench/class_create-2.3-classattr5objs.py new file mode 100644 index 00000000000..74540865dcd --- /dev/null +++ b/tests/internal_bench/class_create-2.3-classattr5objs.py @@ -0,0 +1,20 @@ +import bench + + +class Class: + pass + + +def test(num): + instance = Class() + for i in range(num // 40): + + class X: + a = instance + b = instance + c = instance + d = instance + x = instance + + +bench.run(test) diff --git a/tests/internal_bench/class_create-3-instancemethod.py b/tests/internal_bench/class_create-3-instancemethod.py new file mode 100644 index 00000000000..e8c201cb2c3 --- /dev/null +++ b/tests/internal_bench/class_create-3-instancemethod.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + def x(self): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-4-classmethod.py b/tests/internal_bench/class_create-4-classmethod.py new file mode 100644 index 00000000000..f34962bc671 --- /dev/null +++ b/tests/internal_bench/class_create-4-classmethod.py @@ -0,0 +1,13 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + @classmethod + def x(cls): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-4.1-classmethod_implicit.py b/tests/internal_bench/class_create-4.1-classmethod_implicit.py new file mode 100644 index 00000000000..f2d1fcfd188 --- /dev/null +++ b/tests/internal_bench/class_create-4.1-classmethod_implicit.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + def __new__(cls): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-5-staticmethod.py b/tests/internal_bench/class_create-5-staticmethod.py new file mode 100644 index 00000000000..06335566675 --- /dev/null +++ b/tests/internal_bench/class_create-5-staticmethod.py @@ -0,0 +1,13 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + @staticmethod + def x(): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-6-getattribute.py b/tests/internal_bench/class_create-6-getattribute.py new file mode 100644 index 00000000000..10a4fe7ce8d --- /dev/null +++ b/tests/internal_bench/class_create-6-getattribute.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + def __getattribute__(self, name): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-6.1-getattr.py b/tests/internal_bench/class_create-6.1-getattr.py new file mode 100644 index 00000000000..b4b9ba2f552 --- /dev/null +++ b/tests/internal_bench/class_create-6.1-getattr.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + def __getattr__(self, name): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-6.2-property.py b/tests/internal_bench/class_create-6.2-property.py new file mode 100644 index 00000000000..cf847b6dc9c --- /dev/null +++ b/tests/internal_bench/class_create-6.2-property.py @@ -0,0 +1,13 @@ +import bench + + +def test(num): + for i in range(num // 40): + + class X: + @property + def x(self): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-6.3-descriptor.py b/tests/internal_bench/class_create-6.3-descriptor.py new file mode 100644 index 00000000000..7b0a6357263 --- /dev/null +++ b/tests/internal_bench/class_create-6.3-descriptor.py @@ -0,0 +1,17 @@ +import bench + + +class D: + def __get__(self, instance, owner=None): + pass + + +def test(num): + descriptor = D() + for i in range(num // 40): + + class X: + x = descriptor + + +bench.run(test) diff --git a/tests/internal_bench/class_create-7-inherit.py b/tests/internal_bench/class_create-7-inherit.py new file mode 100644 index 00000000000..f48fb215e0a --- /dev/null +++ b/tests/internal_bench/class_create-7-inherit.py @@ -0,0 +1,14 @@ +import bench + + +def test(num): + class B: + pass + + for i in range(num // 40): + + class X(B): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-7.1-inherit_initsubclass.py b/tests/internal_bench/class_create-7.1-inherit_initsubclass.py new file mode 100644 index 00000000000..0660fa86258 --- /dev/null +++ b/tests/internal_bench/class_create-7.1-inherit_initsubclass.py @@ -0,0 +1,16 @@ +import bench + + +def test(num): + class B: + @classmethod + def __init_subclass__(cls): + pass + + for i in range(num // 40): + + class X(B): + pass + + +bench.run(test) diff --git a/tests/internal_bench/class_create-8-metaclass_setname.py b/tests/internal_bench/class_create-8-metaclass_setname.py new file mode 100644 index 00000000000..e4515b54279 --- /dev/null +++ b/tests/internal_bench/class_create-8-metaclass_setname.py @@ -0,0 +1,17 @@ +import bench + + +class D: + def __set_name__(self, owner, name): + pass + + +def test(num): + descriptor = D() + for i in range(num // 40): + + class X: + x = descriptor + + +bench.run(test) diff --git a/tests/internal_bench/class_create-8.1-metaclass_setname5.py b/tests/internal_bench/class_create-8.1-metaclass_setname5.py new file mode 100644 index 00000000000..5daa3f8471b --- /dev/null +++ b/tests/internal_bench/class_create-8.1-metaclass_setname5.py @@ -0,0 +1,21 @@ +import bench + + +class D: + def __set_name__(self, owner, name): + pass + + +def test(num): + descriptor = D() + for i in range(num // 40): + + class X: + a = descriptor + b = descriptor + c = descriptor + d = descriptor + x = descriptor + + +bench.run(test) From b9d6d6af4b1ac1a900351bebe5294604fea0119b Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 20 Jul 2025 12:07:05 -0400 Subject: [PATCH 1004/2098] tests/internal_bench/var: Benchmark descriptor access. Signed-off-by: Anson Mansfield --- .../var-6.2-instance-speciallookup.py | 19 ++++++++++++++++++ .../var-6.3-instance-property.py | 17 ++++++++++++++++ .../var-6.4-instance-descriptor.py | 20 +++++++++++++++++++ .../var-6.5-instance-getattr.py | 16 +++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 tests/internal_bench/var-6.2-instance-speciallookup.py create mode 100644 tests/internal_bench/var-6.3-instance-property.py create mode 100644 tests/internal_bench/var-6.4-instance-descriptor.py create mode 100644 tests/internal_bench/var-6.5-instance-getattr.py diff --git a/tests/internal_bench/var-6.2-instance-speciallookup.py b/tests/internal_bench/var-6.2-instance-speciallookup.py new file mode 100644 index 00000000000..71845f3aaa2 --- /dev/null +++ b/tests/internal_bench/var-6.2-instance-speciallookup.py @@ -0,0 +1,19 @@ +import bench + + +class Foo: + def __init__(self): + self.num = 20000000 + + def __getattr__(self, name): # just trigger the 'special lookups' flag on the class + pass + + +def test(num): + o = Foo() + i = 0 + while i < o.num: + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-6.3-instance-property.py b/tests/internal_bench/var-6.3-instance-property.py new file mode 100644 index 00000000000..b4426ef7928 --- /dev/null +++ b/tests/internal_bench/var-6.3-instance-property.py @@ -0,0 +1,17 @@ +import bench + + +class Foo: + @property + def num(self): + return 20000000 + + +def test(num): + o = Foo() + i = 0 + while i < o.num: + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-6.4-instance-descriptor.py b/tests/internal_bench/var-6.4-instance-descriptor.py new file mode 100644 index 00000000000..b4df69f878f --- /dev/null +++ b/tests/internal_bench/var-6.4-instance-descriptor.py @@ -0,0 +1,20 @@ +import bench + + +class Descriptor: + def __get__(self, instance, owner=None): + return 20000000 + + +class Foo: + num = Descriptor() + + +def test(num): + o = Foo() + i = 0 + while i < o.num: + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-6.5-instance-getattr.py b/tests/internal_bench/var-6.5-instance-getattr.py new file mode 100644 index 00000000000..3b2ef672110 --- /dev/null +++ b/tests/internal_bench/var-6.5-instance-getattr.py @@ -0,0 +1,16 @@ +import bench + + +class Foo: + def __getattr__(self, name): + return 20000000 + + +def test(num): + o = Foo() + i = 0 + while i < o.num: + i += 1 + + +bench.run(test) From 88cb6bc818546e7fe8b862c784e1e4a6b8a12b8a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 20 Jul 2025 13:47:01 -0400 Subject: [PATCH 1005/2098] tests/run-internalbench.py: Allow running internalbench on hardware. Signed-off-by: Anson Mansfield --- tests/run-internalbench.py | 101 ++++++++++++++++++++++++++++--------- tests/run-tests.py | 52 +++++++++++-------- 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/tests/run-internalbench.py b/tests/run-internalbench.py index c9f783e474c..99c6304afe9 100755 --- a/tests/run-internalbench.py +++ b/tests/run-internalbench.py @@ -8,6 +8,10 @@ from glob import glob from collections import defaultdict +run_tests_module = __import__("run-tests") +sys.path.append(run_tests_module.base_path("../tools")) +import pyboard + if os.name == "nt": MICROPYTHON = os.getenv( "MICROPY_MICROPYTHON", "../ports/windows/build-standard/micropython.exe" @@ -15,13 +19,39 @@ else: MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/build-standard/micropython") +injected_bench_code = b""" +import time + +class bench_class: + ITERS = 20000000 + + @staticmethod + def run(test): + t = time.ticks_us() + test(bench_class.ITERS) + t = time.ticks_diff(time.ticks_us(), t) + s, us = divmod(t, 1_000_000) + print("{}.{:06}".format(s, us)) + +import sys +sys.modules['bench'] = bench_class +""" + -def run_tests(pyb, test_dict): +def execbench(pyb, filename, iters): + with open(filename, "rb") as f: + pyfile = f.read() + code = (injected_bench_code + pyfile).replace(b"20000000", str(iters).encode("utf-8")) + return pyb.exec(code).replace(b"\r\n", b"\n") + + +def run_tests(pyb, test_dict, iters): test_count = 0 testcase_count = 0 for base_test, tests in sorted(test_dict.items()): print(base_test + ":") + baseline = None for test_file in tests: # run MicroPython if pyb is None: @@ -36,20 +66,25 @@ def run_tests(pyb, test_dict): # run on pyboard pyb.enter_raw_repl() try: - output_mupy = pyb.execfile(test_file).replace(b"\r\n", b"\n") + output_mupy = execbench(pyb, test_file[0], iters) except pyboard.PyboardError: output_mupy = b"CRASH" - output_mupy = float(output_mupy.strip()) + try: + output_mupy = float(output_mupy.strip()) + except ValueError: + output_mupy = -1 test_file[1] = output_mupy testcase_count += 1 - test_count += 1 - baseline = None - for t in tests: if baseline is None: - baseline = t[1] - print(" %.3fs (%+06.2f%%) %s" % (t[1], (t[1] * 100 / baseline) - 100, t[0])) + baseline = test_file[1] + print( + " %.3fs (%+06.2f%%) %s" + % (test_file[1], (test_file[1] * 100 / baseline) - 100, test_file[0]) + ) + + test_count += 1 print("{} tests performed ({} individual testcases)".format(test_count, testcase_count)) @@ -58,27 +93,47 @@ def run_tests(pyb, test_dict): def main(): - cmd_parser = argparse.ArgumentParser(description="Run tests for MicroPython.") - cmd_parser.add_argument("--pyboard", action="store_true", help="run the tests on the pyboard") + cmd_parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=f"""Run and manage tests for MicroPython. + +{run_tests_module.test_instance_description} +{run_tests_module.test_directory_description} +""", + epilog=run_tests_module.test_instance_epilog, + ) + cmd_parser.add_argument( + "-t", "--test-instance", default="unix", help="the MicroPython instance to test" + ) + cmd_parser.add_argument( + "-b", "--baudrate", default=115200, help="the baud rate of the serial device" + ) + cmd_parser.add_argument("-u", "--user", default="micro", help="the telnet login username") + cmd_parser.add_argument("-p", "--password", default="python", help="the telnet login password") + cmd_parser.add_argument( + "-d", "--test-dirs", nargs="*", help="input test directories (if no files given)" + ) + cmd_parser.add_argument( + "-I", + "--iters", + type=int, + default=200_000, + help="number of test iterations, only for remote instances (default 200,000)", + ) cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() # Note pyboard support is copied over from run-tests.py, not tests, and likely needs revamping - if args.pyboard: - import pyboard - - pyb = pyboard.Pyboard("/dev/ttyACM0") - pyb.enter_raw_repl() - else: - pyb = None + pyb = run_tests_module.get_test_instance( + args.test_instance, args.baudrate, args.user, args.password + ) if len(args.files) == 0: - if pyb is None: - # run PC tests - test_dirs = ("internal_bench",) + if args.test_dirs: + test_dirs = tuple(args.test_dirs) else: - # run pyboard tests - test_dirs = ("basics", "float", "pyb") + test_dirs = ("internal_bench",) + tests = sorted( test_file for test_files in (glob("{}/*.py".format(dir)) for dir in test_dirs) @@ -95,7 +150,7 @@ def main(): continue test_dict[m.group(1)].append([t, None]) - if not run_tests(pyb, test_dict): + if not run_tests(pyb, test_dict, args.iters): sys.exit(1) diff --git a/tests/run-tests.py b/tests/run-tests.py index a428665db03..59aec327fa0 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1145,29 +1145,11 @@ def __call__(self, parser, args, value, option): args.filters.append((option, re.compile(value))) -def main(): - global injected_import_hook_code - - cmd_parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description="""Run and manage tests for MicroPython. - +test_instance_description = """\ By default the tests are run against the unix port of MicroPython. To run it against something else, use the -t option. See below for details. - -Tests are discovered by scanning test directories for .py files or using the -specified test files. If test files nor directories are specified, the script -expects to be ran in the tests directory (where this file is located) and the -builtin tests suitable for the target platform are ran. - -When running tests, run-tests.py compares the MicroPython output of the test with the output -produced by running the test through CPython unless a .exp file is found, in which -case it is used as comparison. - -If a test fails, run-tests.py produces a pair of .out and .exp files in the result -directory with the MicroPython output and the expectations, respectively. -""", - epilog="""\ +""" +test_instance_epilog = """\ The -t option accepts the following for the test instance: - unix - use the unix port of MicroPython, specified by the MICROPY_MICROPYTHON environment variable (which defaults to the standard variant of either the unix @@ -1183,7 +1165,35 @@ def main(): - execpty: - execute a command and attach to the printed /dev/pts/ device - ... - connect to the given IPv4 address - anything else specifies a serial port +""" +test_directory_description = """\ +Tests are discovered by scanning test directories for .py files or using the +specified test files. If test files nor directories are specified, the script +expects to be ran in the tests directory (where this file is located) and the +builtin tests suitable for the target platform are ran. +""" + + +def main(): + global injected_import_hook_code + + cmd_parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=f"""Run and manage tests for MicroPython. + +{test_instance_description} +{test_directory_description} + +When running tests, run-tests.py compares the MicroPython output of the test with the output +produced by running the test through CPython unless a .exp file is found, in which +case it is used as comparison. + +If a test fails, run-tests.py produces a pair of .out and .exp files in the result +directory with the MicroPython output and the expectations, respectively. +""", + epilog=f"""\ +{test_instance_epilog} Options -i and -e can be multiple and processed in the order given. Regex "search" (vs "match") operation is used. An action (include/exclude) of the last matching regex is used: From ca9916968c06437fa39549b0995ceb811209f994 Mon Sep 17 00:00:00 2001 From: TianShuang Ke Date: Thu, 27 Jun 2024 22:36:18 +0800 Subject: [PATCH 1006/2098] esp32: Add support for ESP32-C2 (aka ESP8684). Includes: esp32/esp32c2: Adapt to target chip ESP32C2. esp32/esp32c2: Fix heap size is too small to enable Bluetooth. Signed-off-by: TianShuangKe Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 3 +++ ports/esp32/README.md | 4 ++-- .../esp32/boards/ESP32_GENERIC_C2/board.json | 22 +++++++++++++++++++ ports/esp32/boards/ESP32_GENERIC_C2/board.md | 3 +++ .../ESP32_GENERIC_C2/mpconfigboard.cmake | 7 ++++++ .../boards/ESP32_GENERIC_C2/mpconfigboard.h | 7 ++++++ ports/esp32/boards/sdkconfig.base | 6 ++--- ports/esp32/boards/sdkconfig.c2 | 11 ++++++++++ ports/esp32/esp32_rmt.c | 6 ++++- ports/esp32/machine_adc.c | 6 +++++ ports/esp32/machine_bitstream.c | 11 ++++++---- ports/esp32/machine_i2c.c | 6 ++++- ports/esp32/machine_pin.c | 2 +- ports/esp32/machine_pin.h | 17 ++++++++++++++ ports/esp32/modesp32.c | 2 ++ ports/esp32/modmachine.c | 6 +++++ ports/esp32/mpconfigport.h | 4 +++- tests/extmod/machine_spi_rate.py | 2 +- tests/ports/esp32/esp32_idf_heap_info.py | 17 ++++++++++++-- tests/ports/esp32/esp32_idf_heap_info.py.exp | 4 ++-- 20 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 ports/esp32/boards/ESP32_GENERIC_C2/board.json create mode 100644 ports/esp32/boards/ESP32_GENERIC_C2/board.md create mode 100644 ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake create mode 100644 ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h create mode 100644 ports/esp32/boards/sdkconfig.c2 diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index c394414a76f..49f546a17c0 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -802,6 +802,9 @@ The RMT is ESP32-specific and allows generation of accurate digital pulses with # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns +The ESP32-C2 family does not include any RMT peripheral, so this class is +unavailable on those SoCs. + OneWire driver -------------- diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 4adff66328d..dd4584772cf 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -5,8 +5,8 @@ This is a port of MicroPython to the Espressif ESP32 series of microcontrollers. It uses the ESP-IDF framework and MicroPython runs as a task under FreeRTOS. -Currently supports ESP32, ESP32-C3, ESP32-C6, ESP32-S2 and ESP32-S3 -(ESP8266 is supported by a separate MicroPython port). +Currently supports ESP32, ESP32-C2 (aka ESP8684), ESP32-C3, ESP32-C6, ESP32-S2 +and ESP32-S3 (ESP8266 is supported by a separate MicroPython port). Supported features include: - REPL (Python prompt) over UART0. diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/board.json b/ports/esp32/boards/ESP32_GENERIC_C2/board.json new file mode 100644 index 00000000000..da0931a0e44 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C2/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "WiFi" + ], + "images": [ + "esp32c2_devkitmini.jpg" + ], + "mcu": "esp32c2", + "product": "ESP32-C2", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/board.md b/ports/esp32/boards/ESP32_GENERIC_C2/board.md new file mode 100644 index 00000000000..b8ed10069fe --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C2/board.md @@ -0,0 +1,3 @@ +The following files are firmware images that should work on most ESP32-C2-based +boards with at least 4MiB of flash and 26MHz crystal frequency. This includes +ESP8684-WROOM and ESP8684-MINI modules. diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake new file mode 100644 index 00000000000..e9d4989959f --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(IDF_TARGET esp32c2) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.c2 +) diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h new file mode 100644 index 00000000000..ee918aaba54 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h @@ -0,0 +1,7 @@ +// This configuration is for a generic ESP32C2 board with 4MiB (or more) of flash. + +#define MICROPY_HW_BOARD_NAME "ESP32C2 module" +#define MICROPY_HW_MCU_NAME "ESP32C2" + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 30740af434d..69abc63bdf0 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -125,9 +125,9 @@ CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y CONFIG_ETH_SPI_ETHERNET_DM9051=y # Using newlib "nano" formatting saves size on SoCs where "nano" formatting -# functions are in ROM. Note some newer chips (c2,c6) have "full" newlib -# formatting in ROM instead and should override this, check -# ESP_ROM_HAS_NEWLIB_NANO_FORMAT. +# functions are in ROM. ESP32-C6 (and possibly other new chips) have "full" +# newlib formatting in ROM instead and should override this, check +# ESP_ROM_HAS_NEWLIB_NANO_FORMAT in ESP-IDF. CONFIG_NEWLIB_NANO_FORMAT=y # IRAM/DRAM split protection is a memory protection feature on some parts diff --git a/ports/esp32/boards/sdkconfig.c2 b/ports/esp32/boards/sdkconfig.c2 new file mode 100644 index 00000000000..e85bcdba35a --- /dev/null +++ b/ports/esp32/boards/sdkconfig.c2 @@ -0,0 +1,11 @@ +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_26=y +# CONFIG_XTAL_FREQ_40 is not set +CONFIG_XTAL_FREQ=26 +# Increase NimBLE stack size for functional BT +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=5120 + +# Decrease mDNS stack size to save RAM +CONFIG_MDNS_TASK_STACK_SIZE=3072 diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 6890e16bf79..f3bfbecdd11 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -30,6 +30,8 @@ #include "modesp32.h" #include "esp_task.h" + +#if SOC_RMT_SUPPORTED #include "driver/rmt.h" // This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: @@ -105,7 +107,7 @@ esp_err_t rmt_driver_install_core1(uint8_t channel_id) { return rmt_driver_install(channel_id, 0, 0); } -#endif +#endif // MP_TASK_COREID==0 static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { static const mp_arg_t allowed_args[] = { @@ -387,3 +389,5 @@ MP_DEFINE_CONST_OBJ_TYPE( print, esp32_rmt_print, locals_dict, &esp32_rmt_locals_dict ); + +#endif // SOC_RMT_SUPPORTED diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index 02acaa22da0..c5575d45ec7 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -87,6 +87,12 @@ static const machine_adc_obj_t madc_obj[] = { {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_27}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_25}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_26}, + #elif CONFIG_IDF_TARGET_ESP32C2 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, #elif CONFIG_IDF_TARGET_ESP32C3 {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index 6296ff06708..ed7fcc407df 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -90,6 +90,7 @@ static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, u mp_hal_quiet_timing_exit(irq_state); } +#if SOC_RMT_SUPPORTED /******************************************************************************/ // RMT implementation @@ -172,16 +173,18 @@ static void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timin // Cancel RMT output to GPIO pin. esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); } - +#endif /******************************************************************************/ // Interface to machine.bitstream void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { - if (esp32_rmt_bitstream_channel_id < 0) { - machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); - } else { + #if SOC_RMT_SUPPORTED + if (esp32_rmt_bitstream_channel_id >= 0) { machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); + return; } + #endif + machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); } #endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 259101ee7e1..4a5fd717631 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -55,7 +55,11 @@ #endif #if SOC_I2C_SUPPORT_XTAL -#define I2C_SCLK_FREQ XTAL_CLK_FREQ +#if CONFIG_XTAL_FREQ > 0 +#define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) +#else +#error "I2C uses XTAL but no configured freq" +#endif // CONFIG_XTAL_FREQ #elif SOC_I2C_SUPPORT_APB #define I2C_SCLK_FREQ APB_CLK_FREQ #else diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 4ab79f0a264..9999223b59d 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -152,7 +152,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ // reset the pin to digital if this is a mode-setting init (grab it back from ADC) if (args[ARG_mode].u_obj != mp_const_none) { if (rtc_gpio_is_valid_gpio(index)) { - #if !(CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6) + #if !(CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6) rtc_gpio_deinit(index); #endif } diff --git a/ports/esp32/machine_pin.h b/ports/esp32/machine_pin.h index 47f1ddebebf..6fe9ef0e778 100644 --- a/ports/esp32/machine_pin.h +++ b/ports/esp32/machine_pin.h @@ -71,6 +71,23 @@ #define MICROPY_HW_ENABLE_GPIO38 (1) #define MICROPY_HW_ENABLE_GPIO39 (1) +#elif CONFIG_IDF_TARGET_ESP32C2 + +#define MICROPY_HW_ENABLE_GPIO0 (1) +#define MICROPY_HW_ENABLE_GPIO1 (1) +#define MICROPY_HW_ENABLE_GPIO2 (1) +#define MICROPY_HW_ENABLE_GPIO3 (1) +#define MICROPY_HW_ENABLE_GPIO4 (1) +#define MICROPY_HW_ENABLE_GPIO5 (1) +#define MICROPY_HW_ENABLE_GPIO6 (1) +#define MICROPY_HW_ENABLE_GPIO7 (1) +#define MICROPY_HW_ENABLE_GPIO8 (1) +#define MICROPY_HW_ENABLE_GPIO9 (1) +#define MICROPY_HW_ENABLE_GPIO10 (1) +#define MICROPY_HW_ENABLE_GPIO18 (1) +#define MICROPY_HW_ENABLE_GPIO19 (1) // UART0_RXD +#define MICROPY_HW_ENABLE_GPIO20 (1) // UART0_TXD + #elif CONFIG_IDF_TARGET_ESP32C3 #define MICROPY_HW_ENABLE_GPIO0 (1) diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index fcd6ed9fa83..bf7aec39442 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -299,7 +299,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, + #if SOC_RMT_SUPPORTED { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, + #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, #endif diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 0d7ea44c669..06360e8e899 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -99,6 +99,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]) / 1000000; + #if CONFIG_IDF_TARGET_ESP32C2 + if (freq != 80 && freq != 120) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 80MHz or 120MHz")); + } + #else if (freq != 20 && freq != 40 && freq != 80 && freq != 160 #if !(CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6) && freq != 240 @@ -110,6 +115,7 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz")); #endif } + #endif esp_pm_config_t pm = { .max_freq_mhz = freq, .min_freq_mhz = freq, diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 721f22de11c..142c1b1be2e 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -32,7 +32,7 @@ #ifndef MICROPY_GC_INITIAL_HEAP_SIZE #if CONFIG_IDF_TARGET_ESP32 #define MICROPY_GC_INITIAL_HEAP_SIZE (56 * 1024) -#elif CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_SPIRAM +#elif CONFIG_IDF_TARGET_ESP32C2 || (CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_SPIRAM) #define MICROPY_GC_INITIAL_HEAP_SIZE (36 * 1024) #else #define MICROPY_GC_INITIAL_HEAP_SIZE (64 * 1024) @@ -165,6 +165,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32s2" #elif CONFIG_IDF_TARGET_ESP32S3 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32s3" +#elif CONFIG_IDF_TARGET_ESP32C2 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c2" #elif CONFIG_IDF_TARGET_ESP32C3 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c3" #elif CONFIG_IDF_TARGET_ESP32C6 diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index c65095f22a1..fe15b66fe64 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -25,7 +25,7 @@ spi_instances = ((0, Pin(18), Pin(19), Pin(16)),) elif "esp32" in sys.platform: impl = str(sys.implementation) - if "ESP32C3" in impl or "ESP32C6" in impl: + if any(soc in impl for soc in ("ESP32C2", "ESP32C3", "ESP32C6")): spi_instances = ((1, Pin(4), Pin(5), Pin(6)),) else: spi_instances = ((1, Pin(18), Pin(19), Pin(21)), (2, Pin(18), Pin(19), Pin(21))) diff --git a/tests/ports/esp32/esp32_idf_heap_info.py b/tests/ports/esp32/esp32_idf_heap_info.py index fdd89161f43..2f45295938d 100644 --- a/tests/ports/esp32/esp32_idf_heap_info.py +++ b/tests/ports/esp32/esp32_idf_heap_info.py @@ -5,6 +5,19 @@ print("SKIP") raise SystemExit +import sys + +# idf_heap_info() is expected to return at least this many +# regions for HEAP_DATA and HEAP_EXEC, respectively. +MIN_DATA = 3 +MIN_EXEC = 3 + +impl = str(sys.implementation) +if "ESP32C2" in impl: + # ESP32-C2 is less fragmented (yay!) and only has two memory regions + MIN_DATA = 2 + MIN_EXEC = 2 + # region tuple is: (size, free, largest free, min free) # we check that each region's size is > 0 and that the free amounts are smaller than the size @@ -22,12 +35,12 @@ def chk_heap(kind, regions): # try getting heap regions regions = esp32.idf_heap_info(esp32.HEAP_DATA) -print("HEAP_DATA >2:", len(regions) > 2) +print("HEAP_DATA >=MIN:", len(regions) >= MIN_DATA) chk_heap("HEAP_DATA", regions) # try getting code regions regions = esp32.idf_heap_info(esp32.HEAP_EXEC) -print("HEAP_EXEC >2:", len(regions) > 2) +print("HEAP_EXEC >=MIN:", len(regions) >= MIN_EXEC) chk_heap("HEAP_EXEC", regions) # try invalid param diff --git a/tests/ports/esp32/esp32_idf_heap_info.py.exp b/tests/ports/esp32/esp32_idf_heap_info.py.exp index 2b63bf3259f..7fc50fc1624 100644 --- a/tests/ports/esp32/esp32_idf_heap_info.py.exp +++ b/tests/ports/esp32/esp32_idf_heap_info.py.exp @@ -1,5 +1,5 @@ -HEAP_DATA >2: True +HEAP_DATA >=MIN: True HEAP_DATA [True, True, True, True] -HEAP_EXEC >2: True +HEAP_EXEC >=MIN: True HEAP_EXEC [True, True, True, True] [] From 77c9eb7795da13dfe499f45f19e867362799b38a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 25 Jul 2025 13:22:00 +1000 Subject: [PATCH 1007/2098] esp32: Add "Free RAM" optimisation config flags. This is necessary for ESP32-C2 Wi-Fi & BT to work reliably (and for TLS to work at all). On IDF 5.4.2 the free static RAM goes from 60KB to 100KB, and there will also be a reduction in lwIP & Wi-Fi memory use at runtime. The performance trade-off seems low for most use cases, although it will probably be significant for certain combinations of load (i.e. heavy TCP/IP, heavy BT throughput, and some peripheral driver functions). Added as a set of config flags because this is potentially useful on other SoCs where the goal is to maximise RAM available for MicroPython. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .../ESP32_GENERIC_C2/mpconfigboard.cmake | 2 + ports/esp32/boards/sdkconfig.free_ram | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 ports/esp32/boards/sdkconfig.free_ram diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake index e9d4989959f..d935c22b886 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake @@ -4,4 +4,6 @@ set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.ble boards/sdkconfig.c2 + # C2 has unusably low free RAM without these optimisations + boards/sdkconfig.free_ram ) diff --git a/ports/esp32/boards/sdkconfig.free_ram b/ports/esp32/boards/sdkconfig.free_ram new file mode 100644 index 00000000000..36c6455e3e6 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.free_ram @@ -0,0 +1,44 @@ +# This is a collection of sdkconfig settings that frees RAM at runtime, +# at the expense of performance. +# +# Not all options will work on all SoC families, but adding this sdkconfig +# set to a board should increase the free memory. +# +# - Many options free IRAM, which on most ESP32 families leads to +# free DRAM at runtime (original ESP32 and S2 may not). +# - The other options reduce runtime DRAM usage from the heap. +# +# IMPORTANT: If you enable these config settings on a custom build then you may +# encounter bugs or crashes. If you choose to open a MicroPython bug report then +# please mention these config settings! + +# Place functions in flash whenever possible to free IRAM +CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y + +# Use the SPI flash functions in ROM (when available). This may limit flash chip +# support and cause issues with some flash chips. Each SoC family has different +# set of chip support baked into ROM. +CONFIG_SPI_FLASH_ROM_IMPL=y + +# Run the Bluetooth controller from flash not IRAM +CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY=y + +# lwIP adjustments to limit runtime memory usage (at expense of performance, and/or +# a reduction in number of active connections). +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16 +CONFIG_LWIP_MAX_SOCKETS=6 +CONFIG_LWIP_MAX_ACTIVE_TCP=8 + +# These lwIP values are recommended to scale relative to the Wi-Fi buffer numbers +CONFIG_LWIP_TCP_WND_DEFAULT=3072 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=3072 + +# Wi-Fi adjustments to reduce peak runtime memory usage, at expense of peak +# performance +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=8 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=12 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=12 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=2 +CONFIG_ESP_WIFI_RX_BA_WIN=12 From d4399b3230c0020d0f5bc2dd42f19c4579f3bf3e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Jul 2025 12:35:24 +1000 Subject: [PATCH 1008/2098] esp32: Fix first line ESP32-C2 serial output after reset or deepsleep. ESP32-C2 ROM prints at 74880bps (same as ESP8266), so need a newline before first MicroPython output to avoid it being appended on end of a line of noise. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .../boards/ESP32_GENERIC_C2/board_init.c | 35 +++++++++++++++++++ .../ESP32_GENERIC_C2/mpconfigboard.cmake | 5 +++ .../boards/ESP32_GENERIC_C2/mpconfigboard.h | 3 ++ ports/esp32/boards/sdkconfig.c2 | 6 ++++ 4 files changed, 49 insertions(+) create mode 100644 ports/esp32/boards/ESP32_GENERIC_C2/board_init.c diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/board_init.c b/ports/esp32/boards/ESP32_GENERIC_C2/board_init.c new file mode 100644 index 00000000000..355fe2bf0b2 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C2/board_init.c @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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 "py/mpconfig.h" + +void GENERIC_C2_board_startup(void) { + // With a 26MHz crystal the ESP32-C2 ROM prints output at 74880 which is + // interpreted mostly as noise, then boot.py output and/or the REPL banner + // prints at the end of a line of noise unless we inject a newline here + printf("\n"); + + boardctrl_startup(); +} diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake index d935c22b886..7a8b0e0b3d4 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake @@ -7,3 +7,8 @@ set(SDKCONFIG_DEFAULTS # C2 has unusably low free RAM without these optimisations boards/sdkconfig.free_ram ) + +set(MICROPY_SOURCE_BOARD + ${MICROPY_BOARD_DIR}/board_init.c +) + diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h index ee918aaba54..999465373e8 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h @@ -5,3 +5,6 @@ #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_I2S (0) + +#define MICROPY_BOARD_STARTUP GENERIC_C2_board_startup +void GENERIC_C2_board_startup(void); diff --git a/ports/esp32/boards/sdkconfig.c2 b/ports/esp32/boards/sdkconfig.c2 index e85bcdba35a..194d815b6f2 100644 --- a/ports/esp32/boards/sdkconfig.c2 +++ b/ports/esp32/boards/sdkconfig.c2 @@ -4,6 +4,12 @@ CONFIG_XTAL_FREQ_26=y # CONFIG_XTAL_FREQ_40 is not set CONFIG_XTAL_FREQ=26 + +# When using 26MHz crystal the baud rate defaults to 74880, +# same as ESP8266 - MicroPython uses 115200, so switch early +CONFIG_ESP_CONSOLE_UART_CUSTOM=y +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 + # Increase NimBLE stack size for functional BT CONFIG_BT_NIMBLE_TASK_STACK_SIZE=5120 From ab4af2c1a61550cd300ff8dcd57fce6099f3338e Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Tue, 29 Jul 2025 18:10:17 +0800 Subject: [PATCH 1009/2098] py/mphal: Add stddef.h header for size_t. This includes "stddef.h" for `size_t` to resolve NuttX integration build issues. Signed-off-by: Yanfeng Liu --- py/mphal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/py/mphal.h b/py/mphal.h index a4f222d0b1e..d52e10be44c 100644 --- a/py/mphal.h +++ b/py/mphal.h @@ -27,6 +27,7 @@ #define MICROPY_INCLUDED_PY_MPHAL_H #include +#include #include "py/mpconfig.h" #ifdef MICROPY_MPHALPORT_H From 135c1cc7cd6077eda2dc860b3b7b51d742f6f630 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Jul 2025 14:44:11 +1000 Subject: [PATCH 1010/2098] extmod/modtls_mbedtls: Do gc_collect and retry ssl_init on any error. Contrary to the docs, mbedtls can return more than just MBEDTLS_ERR_SSL_ALLOC_FAILED when `mbedtls_ssl_setup()` fails. At least MBEDTLS_ERR_MD_ALLOC_FAILED was also seen on ESP32_GENERIC, but there could possibly be other error codes. To cover all these codes, just check if `ret` is non-0, and in that case do a `gc_collect()` and retry the init. Signed-off-by: Damien George --- extmod/modtls_mbedtls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 418275440f3..58634257328 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -639,7 +639,7 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t ret = mbedtls_ssl_setup(&o->ssl, &ssl_context->conf); #if !MICROPY_MBEDTLS_CONFIG_BARE_METAL - if (ret == MBEDTLS_ERR_SSL_ALLOC_FAILED) { + if (ret != 0) { // If mbedTLS relies on platform libc heap for buffers (i.e. esp32 // port), then run a GC pass and then try again. This is useful because // it may free a Python object (like an old SSL socket) whose finaliser From 68434b4be7ea9d212d5f402b75e2ffac8434ee04 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 15 Jul 2025 12:00:37 +1000 Subject: [PATCH 1011/2098] zephyr/mpconfigport_minimal: Use MICROPY_CONFIG_ROM_LEVEL_MINIMUM. This commit adjusts the configuration of the minimal zephyr build to use MICROPY_CONFIG_ROM_LEVEL_MINIMUM. That's a lot cleaner than explicitly enabling/disabling options. Prior to this change the minimal build for qemu_cortex_m3 had size: Memory region Used Size Region Size %age Used FLASH: 114436 B 256 KB 43.65% RAM: 26320 B 64 KB 40.16% and had the following test results (running using the CI settings, ie `-d basics float --exclude inf_nan_arith`): 352 tests performed (7092 individual testcases) 352 tests passed 254 tests skipped: ... With the changes here the qemu_cortex_m3 size is now: Memory region Used Size Region Size %age Used FLASH: 99428 B 256 KB 37.93% RAM: 26312 B 64 KB 40.15% That's a good decrease of about 15k firmware size. And the test suite still passes with: 342 tests performed (6776 individual testcases) 341 tests passed 265 tests skipped: ... Signed-off-by: Damien George --- ports/zephyr/mpconfigport_minimal.h | 37 +++++++++++++---------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index a0a7f973946..24e0c9f1adc 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -30,41 +30,36 @@ // Included here to get basic Zephyr environment (macros, etc.) #include +// Use the minimum configuration level to get a small but useful system. +#ifndef MICROPY_CONFIG_ROM_LEVEL +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM) +#endif + // Usually passed from Makefile #ifndef MICROPY_HEAP_SIZE #define MICROPY_HEAP_SIZE (16 * 1024) #endif +#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) #define MICROPY_STACK_CHECK (1) #define MICROPY_ENABLE_GC (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_AUTO_INDENT (1) #define MICROPY_KBD_EXCEPTION (1) -#define MICROPY_CPYTHON_COMPAT (0) -#define MICROPY_PY_ASYNC_AWAIT (0) -#define MICROPY_PY_ATTRTUPLE (0) -#define MICROPY_PY_BUILTINS_ENUMERATE (0) -#define MICROPY_PY_BUILTINS_FILTER (0) -#define MICROPY_PY_BUILTINS_MIN_MAX (0) -#define MICROPY_PY_BUILTINS_PROPERTY (0) -#define MICROPY_PY_BUILTINS_RANGE_ATTRS (0) -#define MICROPY_PY_BUILTINS_REVERSED (0) -#define MICROPY_PY_BUILTINS_SET (0) -#define MICROPY_PY_BUILTINS_SLICE (0) -#define MICROPY_PY_ARRAY (0) -#define MICROPY_PY_COLLECTIONS (0) -#define MICROPY_PY_CMATH (0) -#define MICROPY_PY_IO (0) -#define MICROPY_PY_STRUCT (0) -#define MICROPY_PY_SYS_MODULES (0) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_PY_BUILTINS_COMPLEX (0) -// Saving extra crumbs to make sure binary fits in 128K -#define MICROPY_COMP_CONST_FOLDING (0) -#define MICROPY_COMP_CONST (0) -#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0) +// These features are enabled to get the test suite passing. +#define MICROPY_FULL_CHECKS (1) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (1) +#define MICROPY_MULTIPLE_INHERITANCE (1) +#define MICROPY_PY_ASSIGN_EXPR (1) +#define MICROPY_PY_BUILTINS_STR_OP_MODULO (1) +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1) +#define MICROPY_PY_SYS (1) #ifdef CONFIG_BOARD #define MICROPY_HW_BOARD_NAME "zephyr-" CONFIG_BOARD From 4360da16845051d6744669f1a33a715f147c1494 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 15 Jul 2025 12:30:09 +1000 Subject: [PATCH 1012/2098] zephyr/mpconfigport: Use MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES. This commit adjusts the configuration of the standard zephyr build to use MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES. That's a lot cleaner than explicitly enabling/disabling options, and allows boards to more easily fine-tune the settings, eg select a different feature level. Features that are now enabled are: - async/await keyword support - `filter`, `property` and `reversed` builtins - `range` attributes - `str.count()` method - `array` module with `array.array` object - `collections` module with `collections.namedtuple` object - `struct` module with everything - `id = const()` and constant folding in the compiler Bulding qemu_cortex_m3, the code size was originally: Memory region Used Size Region Size %age Used FLASH: 193864 B 256 KB 73.95% RAM: 61992 B 64 KB 94.59% and with this commit it is now: Memory region Used Size Region Size %age Used FLASH: 200698 B 256 KB 76.56% RAM: 61992 B 64 KB 94.59% That's a mild increase of +6834 bytes flash usage for a good selection of new features. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 62226a2ded7..848e04b389e 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -31,6 +31,11 @@ #include #include +// Use the basic configuration level to get a balance between size and features. +#ifndef MICROPY_CONFIG_ROM_LEVEL +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) +#endif + // Usually passed from Makefile #ifndef MICROPY_HEAP_SIZE #define MICROPY_HEAP_SIZE (16 * 1024) @@ -48,20 +53,11 @@ #define MICROPY_REPL_AUTO_INDENT (1) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_KBD_EXCEPTION (1) -#define MICROPY_PY_ASYNC_AWAIT (0) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) -#define MICROPY_PY_BUILTINS_FILTER (0) -#define MICROPY_PY_BUILTINS_PROPERTY (0) -#define MICROPY_PY_BUILTINS_RANGE_ATTRS (0) -#define MICROPY_PY_BUILTINS_REVERSED (0) -#define MICROPY_PY_BUILTINS_STR_COUNT (0) #define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_HELP (1) #define MICROPY_PY_BUILTINS_HELP_TEXT zephyr_help_text -#define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) -#define MICROPY_PY_COLLECTIONS (0) -#define MICROPY_PY_CMATH (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" @@ -80,7 +76,6 @@ #endif #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/zephyr/machine_pwm.c" -#define MICROPY_PY_STRUCT (0) #ifdef CONFIG_NETWORKING // If we have networking, we likely want errno comfort #define MICROPY_PY_ERRNO (1) @@ -96,7 +91,6 @@ #define MICROPY_PY_BINASCII (1) #define MICROPY_PY_HASHLIB (1) #define MICROPY_PY_OS (1) -#define MICROPY_PY_TIME (1) #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/zephyr/modtime.c" #define MICROPY_PY_ZEPHYR (1) @@ -117,11 +111,6 @@ #define MICROPY_FATFS_RPATH (2) #define MICROPY_FATFS_NORTC (1) -// Saving extra crumbs to make sure binary fits in 128K -#define MICROPY_COMP_CONST_FOLDING (0) -#define MICROPY_COMP_CONST (0) -#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0) - // When CONFIG_THREAD_CUSTOM_DATA is enabled, MICROPY_PY_THREAD is enabled automatically #ifdef CONFIG_THREAD_CUSTOM_DATA #define MICROPY_PY_THREAD (1) From 6a8c45b6c4683fa2dc68cab62219b0edad4dd15d Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 20 Jul 2025 21:37:35 +1000 Subject: [PATCH 1013/2098] docs/library/bluetooth: Document all allowed args to UUID constructor. Signed-off-by: Damien George --- docs/library/bluetooth.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/library/bluetooth.rst b/docs/library/bluetooth.rst index b09c370abd4..251ff399eca 100644 --- a/docs/library/bluetooth.rst +++ b/docs/library/bluetooth.rst @@ -764,4 +764,5 @@ Constructor The **value** can be either: - A 16-bit integer. e.g. ``0x2908``. + - An object with the buffer protocol and that is 2, 4 or 16 bytes long, e.g. ``b'\x08\x29'``. - A 128-bit UUID string. e.g. ``'6E400001-B5A3-F393-E0A9-E50E24DCCA9E'``. From a9b038a57e2ca730dd95e79fc89491d0d1154e6e Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 20 Jul 2025 21:38:03 +1000 Subject: [PATCH 1014/2098] examples/bluetooth/ble_advertising.py: Fix decoding UUIDs. The UUID32 case was incorrect: first, the " --- examples/bluetooth/ble_advertising.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/bluetooth/ble_advertising.py b/examples/bluetooth/ble_advertising.py index 2fe17d640b9..f0b97d7f5d9 100644 --- a/examples/bluetooth/ble_advertising.py +++ b/examples/bluetooth/ble_advertising.py @@ -79,12 +79,9 @@ def decode_name(payload): def decode_services(payload): services = [] - for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE): - services.append(bluetooth.UUID(struct.unpack(" Date: Tue, 22 Jul 2025 14:52:17 +1000 Subject: [PATCH 1015/2098] tests/run-natmodtests.py: Automatically skip tests that are too large. This follows a similar change made for `run-tests.py` in commit 229104558fb7f9d283b7302bc3720bc35c5c49cf. The change here uses the same logic to detect if a natmod test is too big for the target (eg overflows (I)RAM loading the native .mpy), by printing "START TEST" at the start of the test. Typical output is now something like this: ... pass extmod/random_basic.py pass extmod/random_extra_float.py pass extmod/random_extra.py SKIP extmod/random_seed_default.py LRGE extmod/re1.py SKIP extmod/re_debug.py pass extmod/re_error.py pass extmod/re_group.py pass extmod/re_groups.py ... and the tests that are too large are reported at the end, and written to the `_result.json` file. Signed-off-by: Damien George --- tests/run-natmodtests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index f9d2074f6f0..55adb6049a3 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -170,6 +170,7 @@ def run_tests(target_truth, target, args, resolved_arch): print("skip {} - mpy file not compiled".format(test_file)) continue test_script += bytes(injected_import_hook_code.format(test_module), "ascii") + test_script += b"print('START TEST')\n" test_script += test_file_data # Run test under MicroPython @@ -177,8 +178,18 @@ def run_tests(target_truth, target, args, resolved_arch): # Work out result of test extra = "" + result_out = result_out.removeprefix(b"START TEST\n") if error is None and result_out == b"SKIP\n": result = "SKIP" + elif ( + error is not None + and error.args[0] == "exception" + and error.args[1] == b"" + and b"MemoryError" in error.args[2] + ): + # Test had a MemoryError before anything (should be at least "START TEST") + # was printed, so the test is too big for the target. + result = "LRGE" elif error is not None: result = "FAIL" extra = " - " + str(error) @@ -203,6 +214,8 @@ def run_tests(target_truth, target, args, resolved_arch): test_results.append((test_file, "pass", "")) elif result == "SKIP": test_results.append((test_file, "skip", "")) + elif result == "LRGE": + test_results.append((test_file, "skip", "too large")) else: test_results.append((test_file, "fail", "")) From 4bdf2a2dc0b418aa014ef1c28b55a563f0c9f7e2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 29 Jul 2025 10:30:10 +1000 Subject: [PATCH 1016/2098] tests/multi_bluetooth: Extend the deep sleep test timeout. As per comment, if a boot.py is present that connects to Wi-Fi then waking can take a little longer. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/multi_bluetooth/stress_deepsleep_reconnect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/multi_bluetooth/stress_deepsleep_reconnect.py b/tests/multi_bluetooth/stress_deepsleep_reconnect.py index 7c34c036067..b588b4000b4 100644 --- a/tests/multi_bluetooth/stress_deepsleep_reconnect.py +++ b/tests/multi_bluetooth/stress_deepsleep_reconnect.py @@ -5,7 +5,9 @@ from micropython import const import time, machine, bluetooth -TIMEOUT_MS = 4000 +# Note: This value can be much lower most of the time, but an ESP32 with a boot.py +# that connects to Wi-Fi may take an extra 5 seconds after reboot. +TIMEOUT_MS = 8000 _IRQ_CENTRAL_CONNECT = const(1) _IRQ_CENTRAL_DISCONNECT = const(2) From fdbd23268d69d77a411aa5c2d792eaf5e77d454a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 29 Jul 2025 10:45:58 +1000 Subject: [PATCH 1017/2098] tests/run-multitests.py: Escape encoding errors instead of crashing. It's possible for a test to output non-ASCII characters (for example, due to a hard fault or serial noise or memory corruption). Rather than crashing the test runner, backslash escape those characters and treat them as program output. Refactors the string encoding step to a single helper to avoid copy-paste. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/run-multitests.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 92bd64193d8..4412d0fde7f 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -132,6 +132,11 @@ def get_host_ip(_ip_cache=[]): return _ip_cache[0] +def decode(output): + # Convenience function to convert raw process or serial output to ASCII + return str(output, "ascii", "backslashreplace") + + class PyInstance: def __init__(self): pass @@ -190,7 +195,7 @@ def run_script(self, script): output = p.stdout except subprocess.CalledProcessError as er: err = er - return str(output.strip(), "ascii"), err + return decode(output.strip()), err def start_script(self, script): self.popen = subprocess.Popen( @@ -217,7 +222,7 @@ def readline(self): self.finished = self.popen.poll() is not None return None, None else: - return str(out.rstrip(), "ascii"), None + return decode(out.rstrip()), None def write(self, data): self.popen.stdin.write(data) @@ -229,7 +234,7 @@ def is_finished(self): def wait_finished(self): self.popen.wait() out = self.popen.stdout.read() - return str(out, "ascii"), "" + return decode(out), "" class PyInstancePyboard(PyInstance): @@ -264,7 +269,7 @@ def run_script(self, script): output = self.pyb.exec_(script) except pyboard.PyboardError as er: err = er - return str(output.strip(), "ascii"), err + return decode(output.strip()), err def start_script(self, script): self.pyb.enter_raw_repl() @@ -283,13 +288,13 @@ def readline(self): if out.endswith(b"\x04"): self.finished = True out = out[:-1] - err = str(self.pyb.read_until(1, b"\x04"), "ascii") + err = decode(self.pyb.read_until(1, b"\x04")) err = err[:-1] if not out and not err: return None, None else: err = None - return str(out.rstrip(), "ascii"), err + return decode(out.rstrip()), err def write(self, data): self.pyb.serial.write(data) @@ -299,7 +304,7 @@ def is_finished(self): def wait_finished(self): out, err = self.pyb.follow(10, None) - return str(out, "ascii"), str(err, "ascii") + return decode(out), decode(err) def prepare_test_file_list(test_files): From 241ee163c0c8a33267c699af59492641d0770f30 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 25 Jul 2025 00:09:18 +1000 Subject: [PATCH 1018/2098] py/objboundmeth: Add option to use mp_is_equal instead of == comparison. This option is needed for ports such as webassembly where objects are proxied and can be identical without being the same C pointer. Signed-off-by: Damien George --- py/mpconfig.h | 8 ++++++++ py/objboundmeth.c | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/py/mpconfig.h b/py/mpconfig.h index 6ef6ec52e7c..c316aa4b2d8 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1129,6 +1129,14 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES) #endif +// Whether bound_method can just use == (feature disabled), or requires a call to +// mp_obj_equal (feature enabled), to test equality of the self and meth entities. +// This is only needed if objects and functions can be identical without being the +// same thing, eg when using an object proxy. +#ifndef MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK +#define MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK (0) +#endif + // Whether to support the descriptors __get__, __set__, __delete__, __set_name__ // This costs some code size and makes load/store/delete of instance // attributes slower for the classes that use this feature diff --git a/py/objboundmeth.c b/py/objboundmeth.c index e3503ff154a..6df67f7bf9e 100644 --- a/py/objboundmeth.c +++ b/py/objboundmeth.c @@ -102,7 +102,11 @@ static mp_obj_t bound_meth_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_ } mp_obj_bound_meth_t *lhs = MP_OBJ_TO_PTR(lhs_in); mp_obj_bound_meth_t *rhs = MP_OBJ_TO_PTR(rhs_in); + #if MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK + return mp_obj_new_bool(mp_obj_equal(lhs->self, rhs->self) && mp_obj_equal(lhs->meth, rhs->meth)); + #else return mp_obj_new_bool(lhs->self == rhs->self && lhs->meth == rhs->meth); + #endif } #if MICROPY_PY_FUNCTION_ATTRS From 813f0c1cb9460502d0d8de24106f7ee8cc0723ee Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Jul 2025 23:40:47 +1000 Subject: [PATCH 1019/2098] webassembly/objjsproxy: Implement equality for JsProxy objects. Signed-off-by: Damien George --- ports/webassembly/mpconfigport.h | 1 + ports/webassembly/objjsproxy.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h index eea6f02a026..544e76f8054 100644 --- a/ports/webassembly/mpconfigport.h +++ b/ports/webassembly/mpconfigport.h @@ -54,6 +54,7 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK (1) #define MICROPY_EPOCH_IS_1970 (1) #define MICROPY_PY_ASYNCIO_TASK_QUEUE_PUSH_CALLBACK (1) diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index 28fef901477..45f329d7e33 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -266,6 +266,23 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const } } +static mp_obj_t jsproxy_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + if (!mp_obj_is_type(rhs_in, &mp_type_jsproxy)) { + return MP_OBJ_NULL; // op not supported + } + + mp_obj_jsproxy_t *lhs = MP_OBJ_TO_PTR(lhs_in); + mp_obj_jsproxy_t *rhs = MP_OBJ_TO_PTR(rhs_in); + + switch (op) { + case MP_BINARY_OP_EQUAL: + return mp_obj_new_bool(lhs->ref == rhs->ref); + + default: + return MP_OBJ_NULL; // op not supported + } +} + EM_JS(void, proxy_js_free_obj, (int js_ref), { if (js_ref >= PROXY_JS_REF_NUM_STATIC) { proxy_js_ref[js_ref] = undefined; @@ -566,6 +583,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_TYPE_FLAG_ITER_IS_GETITER, print, jsproxy_print, call, jsproxy_call, + binary_op, jsproxy_binary_op, attr, mp_obj_jsproxy_attr, subscr, jsproxy_subscr, iter, jsproxy_getiter From ffa98cb0143c43af9f4c61142784a08a19f660c5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Jul 2025 23:41:16 +1000 Subject: [PATCH 1020/2098] webassembly/proxy_js: Reuse JsProxy ref if object matches. This reduces memory use by reusing objects, and improves identity/equality relationships of JavaScript objects on the Python side. In 77bd8fe5b80b0e7e02cdb6b4272c401ae3dca638 PyProxy's were reused when the same Python object was proxied across to JavaScript. This commit does the same thing but for JsProxy's going from JS to Python. If an existing JsProxy reference exists for the JS object about to be proxied across, then it's reused. This helps reduce the number of alive objects (memory use), and, more importantly, improves equality relationships of JavaScript objects on the Python side. Eg we now get, on the Python side: import js print(js.Object == js.Object) that prints True. Previously it was False. Note that this change does not make identity work with `is`, for example `js.Object is js.Object` is actually False. With more work that could be made True but for now we leave that as-is. The behaviour with this commit matches Pyodide semantics. Signed-off-by: Damien George --- ports/webassembly/objjsproxy.c | 1 + ports/webassembly/proxy_js.js | 12 +++++++++++- tests/ports/webassembly/py_proxy_identity.mjs | 9 +++++++++ tests/ports/webassembly/py_proxy_identity.mjs.exp | 9 ++++++++- tests/ports/webassembly/run_python_async.mjs.exp | 6 +++--- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index 45f329d7e33..a8b21a74456 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -285,6 +285,7 @@ static mp_obj_t jsproxy_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t r EM_JS(void, proxy_js_free_obj, (int js_ref), { if (js_ref >= PROXY_JS_REF_NUM_STATIC) { + proxy_js_ref_map.delete(proxy_js_ref[js_ref]); proxy_js_ref[js_ref] = undefined; if (js_ref < proxy_js_ref_next) { proxy_js_ref_next = js_ref; diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js index 9e7c233e30b..cbd6e5b0088 100644 --- a/ports/webassembly/proxy_js.js +++ b/ports/webassembly/proxy_js.js @@ -62,6 +62,7 @@ class PythonError extends Error { function proxy_js_init() { globalThis.proxy_js_ref = [globalThis, undefined]; globalThis.proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC; + globalThis.proxy_js_ref_map = new Map(); globalThis.proxy_js_map = new Map(); globalThis.proxy_js_existing = [undefined]; globalThis.pyProxyFinalizationRegistry = new FinalizationRegistry( @@ -95,8 +96,15 @@ function proxy_js_check_existing(c_ref) { return globalThis.proxy_js_existing.length - 1; } -// js_obj cannot be undefined +// The `js_obj` argument cannot be `undefined`. +// Returns an integer reference to the given `js_obj`. function proxy_js_add_obj(js_obj) { + // See if there is an existing JsProxy reference, and use that if there is. + const existing_ref = proxy_js_ref_map.get(js_obj); + if (existing_ref !== undefined) { + return existing_ref; + } + // Search for the first free slot in proxy_js_ref. while (proxy_js_ref_next < proxy_js_ref.length) { if (proxy_js_ref[proxy_js_ref_next] === undefined) { @@ -104,6 +112,7 @@ function proxy_js_add_obj(js_obj) { const id = proxy_js_ref_next; ++proxy_js_ref_next; proxy_js_ref[id] = js_obj; + proxy_js_ref_map.set(js_obj, id); return id; } ++proxy_js_ref_next; @@ -113,6 +122,7 @@ function proxy_js_add_obj(js_obj) { const id = proxy_js_ref.length; proxy_js_ref[id] = js_obj; proxy_js_ref_next = proxy_js_ref.length; + proxy_js_ref_map.set(js_obj, id); return id; } diff --git a/tests/ports/webassembly/py_proxy_identity.mjs b/tests/ports/webassembly/py_proxy_identity.mjs index d4a720b738a..97dab2e7836 100644 --- a/tests/ports/webassembly/py_proxy_identity.mjs +++ b/tests/ports/webassembly/py_proxy_identity.mjs @@ -23,4 +23,13 @@ js.eventTarget.addEventListener("event", callback) js.eventTarget.dispatchEvent(js.event) js.eventTarget.removeEventListener("event", callback) js.eventTarget.dispatchEvent(js.event) + +print("Object equality") +print(js.Object == js.Object) +print(js.Object.assign == js.Object.assign) + +print("Array equality") +print(js.Array == js.Array) +print(js.Array.prototype == js.Array.prototype) +print(js.Array.prototype.push == js.Array.prototype.push) `); diff --git a/tests/ports/webassembly/py_proxy_identity.mjs.exp b/tests/ports/webassembly/py_proxy_identity.mjs.exp index 01ccf0d8926..344a0a20236 100644 --- a/tests/ports/webassembly/py_proxy_identity.mjs.exp +++ b/tests/ports/webassembly/py_proxy_identity.mjs.exp @@ -1,3 +1,10 @@ PyProxy { _ref: 3 } PyProxy { _ref: 3 } true -callback +callback +Object equality +True +True +Array equality +True +True +True diff --git a/tests/ports/webassembly/run_python_async.mjs.exp b/tests/ports/webassembly/run_python_async.mjs.exp index ad6c49e336e..4dff64a6053 100644 --- a/tests/ports/webassembly/run_python_async.mjs.exp +++ b/tests/ports/webassembly/run_python_async.mjs.exp @@ -2,16 +2,16 @@ 1 py 1 - + py 2 2 resolved 123 3 = TEST 2 ========== 1 - + py 1 - + py 2 2 setTimeout resolved From e4e1c9f4132f839dac0291557d9b992f67577fd3 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Fri, 4 Jul 2025 22:32:00 +0200 Subject: [PATCH 1021/2098] py/parsenum: Refactor float parsing code. This commit extracts from the current float parsing code two functions which could be reused elsewhere in MicroPython. The code used to multiply a float x by a power of 10 is also simplified by applying the binary exponent separately from the power of 5. This avoids the risk of overflow in the intermediate stage, before multiplying by x. Signed-off-by: Yoctopuce dev --- py/parsenum.c | 211 +++++++++++++++++++++++++++----------------------- py/parsenum.h | 5 ++ 2 files changed, 117 insertions(+), 99 deletions(-) diff --git a/py/parsenum.c b/py/parsenum.c index fcc69091737..019491b5132 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -195,6 +195,8 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m } } +#if MICROPY_PY_BUILTINS_FLOAT + enum { REAL_IMAG_STATE_START = 0, REAL_IMAG_STATE_HAVE_REAL = 1, @@ -207,25 +209,39 @@ typedef enum { PARSE_DEC_IN_EXP, } parse_dec_in_t; -#if MICROPY_PY_BUILTINS_FLOAT // MANTISSA_MAX is used to retain precision while not overflowing mantissa -// SMALL_NORMAL_VAL is the smallest power of 10 that is still a normal float -// EXACT_POWER_OF_10 is the largest value of x so that 10^x can be stored exactly in a float -// Note: EXACT_POWER_OF_10 is at least floor(log_5(2^mantissa_length)). Indeed, 10^n = 2^n * 5^n -// so we only have to store the 5^n part in the mantissa (the 2^n part will go into the float's -// exponent). +#define MANTISSA_MAX (sizeof(mp_float_uint_t) == 8 ? 0x1999999999999998ULL : 0x19999998U) + +// MAX_EXACT_POWER_OF_5 is the largest value of x so that 5^x can be stored exactly in a float #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT -#define MANTISSA_MAX 0x19999998U -#define SMALL_NORMAL_VAL (1e-37F) -#define SMALL_NORMAL_EXP (-37) -#define EXACT_POWER_OF_10 (9) +#define MAX_EXACT_POWER_OF_5 (10) #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE -#define MANTISSA_MAX 0x1999999999999998ULL -#define SMALL_NORMAL_VAL (1e-307) -#define SMALL_NORMAL_EXP (-307) -#define EXACT_POWER_OF_10 (22) +#define MAX_EXACT_POWER_OF_5 (22) #endif +// Helper to compute `num * (10.0 ** dec_exp)` +mp_float_t mp_decimal_exp(mp_float_t num, int dec_exp) { + + if (dec_exp == 0 || num == MICROPY_FLOAT_CONST(0.0)) { + return num; + } + mp_float_union_t res = {num}; + // Multiply first by (2.0 ** dec_exp) via the exponent + // - this will ensure that the result of `pow()` is always in mp_float_t range + // when the result is expected to be in mp_float_t range (e.g. during format) + // - we don't need to care about p.exp overflow, as (5.0 ** dec_exp) will anyway + // force the final result toward the proper edge if needed (0.0 or inf) + res.p.exp += dec_exp; + // Use positive exponents when they are more precise then negative + if (dec_exp < 0 && dec_exp >= -MAX_EXACT_POWER_OF_5) { + res.f /= MICROPY_FLOAT_C_FUN(pow)(5, -dec_exp); + } else { + res.f *= MICROPY_FLOAT_C_FUN(pow)(5, dec_exp); + } + return (mp_float_t)res.f; +} + + // Break out inner digit accumulation routine to ease trailing zero deferral. static mp_float_uint_t accept_digit(mp_float_uint_t p_mantissa, unsigned int dig, int *p_exp_extra, int in) { // Core routine to ingest an additional digit. @@ -244,6 +260,85 @@ static mp_float_uint_t accept_digit(mp_float_uint_t p_mantissa, unsigned int dig return p_mantissa; } } + +// Helper to parse an unsigned decimal number into a mp_float_t +const char *mp_parse_float_internal(const char *str, size_t len, mp_float_t *res) { + const char *top = str + len; + + parse_dec_in_t in = PARSE_DEC_IN_INTG; + bool exp_neg = false; + mp_float_uint_t mantissa = 0; + int exp_val = 0; + int exp_extra = 0; + int trailing_zeros_intg = 0, trailing_zeros_frac = 0; + while (str < top) { + unsigned int dig = *str++; + if ('0' <= dig && dig <= '9') { + dig -= '0'; + if (in == PARSE_DEC_IN_EXP) { + // don't overflow exp_val when adding next digit, instead just truncate + // it and the resulting float will still be correct, either inf or 0.0 + // (use INT_MAX/2 to allow adding exp_extra at the end without overflow) + if (exp_val < (INT_MAX / 2 - 9) / 10) { + exp_val = 10 * exp_val + dig; + } + } else { + if (dig == 0 || mantissa >= MANTISSA_MAX) { + // Defer treatment of zeros in fractional part. If nothing comes afterwards, ignore them. + // Also, once we reach MANTISSA_MAX, treat every additional digit as a trailing zero. + if (in == PARSE_DEC_IN_INTG) { + ++trailing_zeros_intg; + } else { + ++trailing_zeros_frac; + } + } else { + // Time to un-defer any trailing zeros. Intg zeros first. + while (trailing_zeros_intg) { + mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_INTG); + --trailing_zeros_intg; + } + while (trailing_zeros_frac) { + mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_FRAC); + --trailing_zeros_frac; + } + mantissa = accept_digit(mantissa, dig, &exp_extra, in); + } + } + } else if (in == PARSE_DEC_IN_INTG && dig == '.') { + in = PARSE_DEC_IN_FRAC; + } else if (in != PARSE_DEC_IN_EXP && ((dig | 0x20) == 'e')) { + in = PARSE_DEC_IN_EXP; + if (str < top) { + if (str[0] == '+') { + str++; + } else if (str[0] == '-') { + str++; + exp_neg = true; + } + } + if (str == top) { + return NULL; + } + } else if (dig == '_') { + continue; + } else { + // unknown character + str--; + break; + } + } + + // work out the exponent + if (exp_neg) { + exp_val = -exp_val; + } + exp_val += exp_extra + trailing_zeros_intg; + + // At this point, we just need to multiply the mantissa by its base 10 exponent. + *res = (mp_float_t)mp_decimal_exp(mantissa, exp_val); + + return str; +} #endif // MICROPY_PY_BUILTINS_FLOAT #if MICROPY_PY_BUILTINS_COMPLEX @@ -295,91 +390,9 @@ parse_start:; dec_val = MICROPY_FLOAT_C_FUN(nan)(""); } else { // string should be a decimal number - parse_dec_in_t in = PARSE_DEC_IN_INTG; - bool exp_neg = false; - mp_float_uint_t mantissa = 0; - int exp_val = 0; - int exp_extra = 0; - int trailing_zeros_intg = 0, trailing_zeros_frac = 0; - while (str < top) { - unsigned int dig = *str++; - if ('0' <= dig && dig <= '9') { - dig -= '0'; - if (in == PARSE_DEC_IN_EXP) { - // don't overflow exp_val when adding next digit, instead just truncate - // it and the resulting float will still be correct, either inf or 0.0 - // (use INT_MAX/2 to allow adding exp_extra at the end without overflow) - if (exp_val < (INT_MAX / 2 - 9) / 10) { - exp_val = 10 * exp_val + dig; - } - } else { - if (dig == 0 || mantissa >= MANTISSA_MAX) { - // Defer treatment of zeros in fractional part. If nothing comes afterwards, ignore them. - // Also, once we reach MANTISSA_MAX, treat every additional digit as a trailing zero. - if (in == PARSE_DEC_IN_INTG) { - ++trailing_zeros_intg; - } else { - ++trailing_zeros_frac; - } - } else { - // Time to un-defer any trailing zeros. Intg zeros first. - while (trailing_zeros_intg) { - mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_INTG); - --trailing_zeros_intg; - } - while (trailing_zeros_frac) { - mantissa = accept_digit(mantissa, 0, &exp_extra, PARSE_DEC_IN_FRAC); - --trailing_zeros_frac; - } - mantissa = accept_digit(mantissa, dig, &exp_extra, in); - } - } - } else if (in == PARSE_DEC_IN_INTG && dig == '.') { - in = PARSE_DEC_IN_FRAC; - } else if (in != PARSE_DEC_IN_EXP && ((dig | 0x20) == 'e')) { - in = PARSE_DEC_IN_EXP; - if (str < top) { - if (str[0] == '+') { - str++; - } else if (str[0] == '-') { - str++; - exp_neg = true; - } - } - if (str == top) { - goto value_error; - } - } else if (dig == '_') { - continue; - } else { - // unknown character - str--; - break; - } - } - - // work out the exponent - if (exp_neg) { - exp_val = -exp_val; - } - - // apply the exponent, making sure it's not a subnormal value - exp_val += exp_extra + trailing_zeros_intg; - dec_val = (mp_float_t)mantissa; - if (exp_val < SMALL_NORMAL_EXP) { - exp_val -= SMALL_NORMAL_EXP; - dec_val *= SMALL_NORMAL_VAL; - } - - // At this point, we need to multiply the mantissa by its base 10 exponent. If possible, - // we would rather manipulate numbers that have an exact representation in IEEE754. It - // turns out small positive powers of 10 do, whereas small negative powers of 10 don't. - // So in that case, we'll yield a division of exact values rather than a multiplication - // of slightly erroneous values. - if (exp_val < 0 && exp_val >= -EXACT_POWER_OF_10) { - dec_val /= MICROPY_FLOAT_C_FUN(pow)(10, -exp_val); - } else { - dec_val *= MICROPY_FLOAT_C_FUN(pow)(10, exp_val); + str = mp_parse_float_internal(str, top - str, &dec_val); + if (!str) { + goto value_error; } } diff --git a/py/parsenum.h b/py/parsenum.h index f444632d230..a807cb09d05 100644 --- a/py/parsenum.h +++ b/py/parsenum.h @@ -34,6 +34,11 @@ mp_obj_t mp_parse_num_integer(const char *restrict str, size_t len, int base, mp_lexer_t *lex); +#if MICROPY_PY_BUILTINS_FLOAT +mp_float_t mp_decimal_exp(mp_float_t num, int dec_exp); +const char *mp_parse_float_internal(const char *str, size_t len, mp_float_t *res); +#endif + #if MICROPY_PY_BUILTINS_COMPLEX mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool force_complex, mp_lexer_t *lex); From dbbaa959c85c04dbbcde5908b5d0775b574e44e7 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Fri, 6 Jun 2025 14:55:21 +0200 Subject: [PATCH 1022/2098] py/formatfloat: Improve accuracy of float formatting code. Following discussions in PR #16666, this commit updates the float formatting code to improve the `repr` reversibility, i.e. the percentage of valid floating point numbers that do parse back to the same number when formatted by `repr` (in CPython it's 100%). This new code offers a choice of 3 float conversion methods, depending on the desired tradeoff between code size and conversion precision: - BASIC method is the smallest code footprint - APPROX method uses an iterative method to approximate the exact representation, which is a bit slower but but does not have a big impact on code size. It provides `repr` reversibility on >99.8% of the cases in double precision, and on >98.5% in single precision (except with REPR_C, where reversibility is 100% as the last two bits are not taken into account). - EXACT method uses higher-precision floats during conversion, which provides perfect results but has a higher impact on code size. It is faster than APPROX method, and faster than the CPython equivalent implementation. It is however not available on all compilers when using FLOAT_IMPL_DOUBLE. Here is the table comparing the impact of the three conversion methods on code footprint on PYBV10 (using single-precision floats) and reversibility rate for both single-precision and double-precision floats. The table includes current situation as a baseline for the comparison: PYBV10 REPR_C FLOAT DOUBLE current = 364688 12.9% 27.6% 37.9% basic = 364812 85.6% 60.5% 85.7% approx = 365080 100.0% 98.5% 99.8% exact = 366408 100.0% 100.0% 100.0% Signed-off-by: Yoctopuce dev --- ports/esp8266/mpconfigport.h | 1 + ports/unix/coverage.c | 20 - py/formatfloat.c | 757 ++++++++++++++--------- py/formatfloat.h | 1 + py/misc.h | 19 + py/mpconfig.h | 21 + py/mpprint.c | 12 +- py/mpprint.h | 1 + py/objcomplex.c | 31 +- py/objfloat.c | 18 +- py/parsenum.c | 50 +- py/parsenum.h | 2 +- tests/float/float_format.py | 17 +- tests/float/float_format_accuracy.py | 73 +++ tests/float/float_format_ints.py | 32 +- tests/float/float_struct_e.py | 2 +- tests/float/float_struct_e_doubleprec.py | 43 ++ tests/float/float_struct_e_fp30.py | 43 ++ tests/float/string_format_modulo3.py | 2 +- tests/float/string_format_modulo3.py.exp | 2 - tests/ports/unix/extra_coverage.py.exp | 4 - tests/ports/unix/ffi_float2.py | 3 +- tests/ports/unix/ffi_float2.py.exp | 12 +- tests/run-tests.py | 4 + 24 files changed, 775 insertions(+), 395 deletions(-) create mode 100644 tests/float/float_format_accuracy.py create mode 100644 tests/float/float_struct_e_doubleprec.py create mode 100644 tests/float/float_struct_e_fp30.py delete mode 100644 tests/float/string_format_modulo3.py.exp diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index bc295719026..323fba67f5b 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -106,6 +106,7 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#define MICROPY_FLOAT_FORMAT_IMPL (MICROPY_FLOAT_FORMAT_IMPL_BASIC) #define MICROPY_WARNINGS (1) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) #define MICROPY_STREAMS_POSIX_API (1) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index f071049eded..b7c3d2c25ef 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -612,26 +612,6 @@ static mp_obj_t extra_coverage(void) { mp_emitter_warning(MP_PASS_CODE_SIZE, "test"); } - // format float - { - mp_printf(&mp_plat_print, "# format float\n"); - - // format with inadequate buffer size - char buf[5]; - mp_format_float(1, buf, sizeof(buf), 'g', 0, '+'); - mp_printf(&mp_plat_print, "%s\n", buf); - - // format with just enough buffer so that precision must be - // set from 0 to 1 twice - char buf2[8]; - mp_format_float(1, buf2, sizeof(buf2), 'g', 0, '+'); - mp_printf(&mp_plat_print, "%s\n", buf2); - - // format where precision is trimmed to avoid buffer overflow - mp_format_float(1, buf2, sizeof(buf2), 'e', 0, '+'); - mp_printf(&mp_plat_print, "%s\n", buf2); - } - // binary { mp_printf(&mp_plat_print, "# binary\n"); diff --git a/py/formatfloat.c b/py/formatfloat.c index 7cd471018da..1ea34f84bf7 100644 --- a/py/formatfloat.c +++ b/py/formatfloat.c @@ -33,392 +33,537 @@ #include #include #include "py/formatfloat.h" +#include "py/parsenum.h" /*********************************************************************** Routine for converting a arbitrary floating point number into a string. - The code in this function was inspired from Fred Bayer's pdouble.c. - Since pdouble.c was released as Public Domain, I'm releasing this - code as public domain as well. + The code in this function was inspired from Dave Hylands's previous + version, which was itself inspired from Fred Bayer's pdouble.c. The original code can be found in https://github.com/dhylands/format-float - Dave Hylands - ***********************************************************************/ -#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT -// 1 sign bit, 8 exponent bits, and 23 mantissa bits. -// exponent values 0 and 255 are reserved, exponent can be 1 to 254. -// exponent is stored with a bias of 127. -// The min and max floats are on the order of 1x10^37 and 1x10^-37 - -#define FPTYPE float -#define FPCONST(x) x##F -#define FPROUND_TO_ONE 0.9999995F -#define FPDECEXP 32 -#define FPMIN_BUF_SIZE 6 // +9e+99 +// Float formatting debug code is intended for use in ports/unix only, +// as it uses the libc float printing function as a reference. +#define DEBUG_FLOAT_FORMATTING 0 + +#if DEBUG_FLOAT_FORMATTING +#define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG_PRINTF(...) +#endif + +#if MICROPY_FLOAT_FORMAT_IMPL == MICROPY_FLOAT_FORMAT_IMPL_EXACT || MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define MP_FFUINT_FMT "%lu" +#else +#define MP_FFUINT_FMT "%u" +#endif + +static inline int fp_expval(mp_float_t x) { + mp_float_union_t fb = { x }; + return (int)fb.p.exp - MP_FLOAT_EXP_OFFSET; +} -#define FLT_SIGN_MASK 0x80000000 +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE -static inline int fp_signbit(float x) { - mp_float_union_t fb = {x}; - return fb.i & FLT_SIGN_MASK; +static inline int fp_isless1(mp_float_t x) { + return x < 1.0; } -#define fp_isnan(x) isnan(x) -#define fp_isinf(x) isinf(x) -static inline int fp_iszero(float x) { - mp_float_union_t fb = {x}; - return fb.i == 0; + +static inline int fp_iszero(mp_float_t x) { + return x == 0.0; } -static inline int fp_isless1(float x) { - mp_float_union_t fb = {x}; + +#if MICROPY_FLOAT_FORMAT_IMPL != MICROPY_FLOAT_FORMAT_IMPL_APPROX +static inline int fp_equal(mp_float_t x, mp_float_t y) { + return x == y; +} +#else +static inline mp_float_t fp_diff(mp_float_t x, mp_float_t y) { + return x - y; +} +#endif + +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + +// The functions below are roughly equivalent to the ones above, +// but they are optimized to reduce code footprint by skipping +// handling for special values such as nan, inf, +/-0.0 +// for ports where FP support is done in software. +// +// They also take into account lost bits of REPR_C as needed. + +static inline int fp_isless1(mp_float_t x) { + mp_float_union_t fb = { x }; return fb.i < 0x3f800000; } -#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +static inline int fp_iszero(mp_float_t x) { + mp_float_union_t x_check = { x }; + return !x_check.i; // this is valid for REPR_C as well +} -#define FPTYPE double -#define FPCONST(x) x -#define FPROUND_TO_ONE 0.999999999995 -#define FPDECEXP 256 -#define FPMIN_BUF_SIZE 7 // +9e+199 -#define fp_signbit(x) signbit(x) -#define fp_isnan(x) isnan(x) -#define fp_isinf(x) isinf(x) -#define fp_iszero(x) (x == 0) -#define fp_isless1(x) (x < 1.0) +#if MICROPY_FLOAT_FORMAT_IMPL != MICROPY_FLOAT_FORMAT_IMPL_APPROX +static inline int fp_equal(mp_float_t x, mp_float_t y) { + mp_float_union_t x_check = { x }; + mp_float_union_t y_check = { y }; + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + return (x_check.i & ~3) == (y_check.i & ~3); + #else + return x_check.i == y_check.i; + #endif +} +#else +static inline mp_float_t fp_diff(mp_float_t x, mp_float_t y) { + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + mp_float_union_t x_check = { x }; + mp_float_union_t y_check = { y }; + x_check.i &= ~3; + y_check.i &= ~3; + return x_check.f - y_check.f; + #else + return x - y; + #endif +} +#endif -#endif // MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT/DOUBLE +#endif -static inline int fp_expval(FPTYPE x) { - mp_float_union_t fb = {x}; - return (int)((fb.i >> MP_FLOAT_FRAC_BITS) & (~(0xFFFFFFFF << MP_FLOAT_EXP_BITS))) - MP_FLOAT_EXP_OFFSET; +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define FPMIN_BUF_SIZE 6 // +9e+99 +#define MAX_MANTISSA_DIGITS (9) +#define SAFE_MANTISSA_DIGITS (6) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define FPMIN_BUF_SIZE 7 // +9e+199 +#define MAX_MANTISSA_DIGITS (19) +#define SAFE_MANTISSA_DIGITS (16) +#endif + +// Internal formatting flags +#define FMT_MODE_E 0x01 // render using scientific notation (%e) +#define FMT_MODE_G 0x02 // render using general format (%g) +#define FMT_MODE_F 0x04 // render using using expanded fixed-point format (%f) +#define FMT_E_CASE 0x20 // don't change this value (used for case conversion!) + +static char *mp_prepend_zeros(char *s, int cnt) { + *s++ = '0'; + *s++ = '.'; + while (cnt > 0) { + *s++ = '0'; + cnt--; + } + return s; } -int mp_format_float(FPTYPE f, char *buf, size_t buf_size, char fmt, int prec, char sign) { +// Helper to convert a decimal mantissa (provided as an mp_large_float_uint_t) to string +static int mp_format_mantissa(mp_large_float_uint_t mantissa, mp_large_float_uint_t mantissa_cap, char *buf, char *s, + int num_digits, int max_exp_zeros, int trailing_zeros, int dec, int e, int fmt_flags) { - char *s = buf; + DEBUG_PRINTF("mantissa=" MP_FFUINT_FMT " exp=%d (cap=" MP_FFUINT_FMT "):\n", mantissa, e, mantissa_cap); - if (buf_size <= FPMIN_BUF_SIZE) { - // FPMIN_BUF_SIZE is the minimum size needed to store any FP number. - // If the buffer does not have enough room for this (plus null terminator) - // then don't try to format the float. + if (mantissa) { + // If rounding/searching created an extra digit or removed too many, fix mantissa first + if (mantissa >= mantissa_cap) { + if (fmt_flags & FMT_MODE_F) { + assert(e >= 0); + num_digits++; + dec++; + } else { + mantissa /= 10; + e++; + } + } + } - if (buf_size >= 2) { - *s++ = '?'; + // When 'g' format is used, replace small exponents by explicit zeros + if ((fmt_flags & FMT_MODE_G) && e != 0) { + if (e >= 0) { + // If 0 < e < max_exp_zeros, expand positive exponent into trailing zeros + if (e < max_exp_zeros) { + dec += e; + if (dec >= num_digits) { + trailing_zeros = dec - (num_digits - 1); + } + e = 0; + } + } else { + // If -4 <= e < 0, expand negative exponent without losing significant digits + if (e >= -4) { + int cnt = 0; + while (e < 0 && !(mantissa % 10)) { + mantissa /= 10; + cnt++; + e++; + } + num_digits -= cnt; + s = mp_prepend_zeros(s, cnt - e - 1); + dec = 255; + e = 0; + } } - if (buf_size >= 1) { - *s = '\0'; + } + + // Convert the integer mantissa to string + for (int digit = num_digits - 1; digit >= 0; digit--) { + int digit_ofs = (digit > dec ? digit + 1 : digit); + s[digit_ofs] = '0' + (int)(mantissa % 10); + mantissa /= 10; + } + int dot = (dec >= 255); + if (dec + 1 < num_digits) { + dot = 1; + s++; + s[dec] = '.'; + } + s += num_digits; + #if DEBUG_FLOAT_FORMATTING + *s = 0; + DEBUG_PRINTF(" = %s exp=%d num_digits=%d zeros=%d dec=%d\n", buf, e, num_digits, trailing_zeros, dec); + #endif + + // Append or remove trailing zeros, as required by format + if (trailing_zeros) { + dec -= num_digits - 1; + while (trailing_zeros--) { + if (!dec--) { + *s++ = '.'; + dot = 1; + } + *s++ = '0'; } - return buf_size >= 2; } - if (fp_signbit(f) && !fp_isnan(f)) { - *s++ = '-'; - f = -f; - } else { - if (sign) { - *s++ = sign; + if (fmt_flags & FMT_MODE_G) { + // 'g' format requires to remove trailing zeros after decimal point + if (dot) { + while (s[-1] == '0') { + s--; + } + if (s[-1] == '.') { + s--; + } + } + } + + // Append the exponent if needed + if (((e != 0) || (fmt_flags & FMT_MODE_E)) && !(fmt_flags & FMT_MODE_F)) { + *s++ = 'E' | (fmt_flags & FMT_E_CASE); + if (e >= 0) { + *s++ = '+'; + } else { + *s++ = '-'; + e = -e; } + if (e >= 100) { + *s++ = '0' + (e / 100); + } + *s++ = '0' + ((e / 10) % 10); + *s++ = '0' + (e % 10); } + *s = '\0'; + DEBUG_PRINTF(" ===> %s\n", buf); - // buf_remaining contains bytes available for digits and exponent. - // It is buf_size minus room for the sign and null byte. - int buf_remaining = buf_size - 1 - (s - buf); + return s - buf; +} +// minimal value expected for buf_size, to avoid checking everywhere for overflow +#define MIN_BUF_SIZE (MAX_MANTISSA_DIGITS + 10) + +int mp_format_float(mp_float_t f_entry, char *buf_entry, size_t buf_size, char fmt, int prec, char sign) { + assert(buf_size >= MIN_BUF_SIZE); + + // Handle sign + mp_float_t f = f_entry; + char *buf = buf_entry; + if (signbit(f_entry) && !isnan(f_entry)) { + f = -f; + sign = '-'; + } + if (sign) { + *buf++ = sign; + buf_size--; + } + + // Handle inf/nan + char uc = fmt & 0x20; { - char uc = fmt & 0x20; - if (fp_isinf(f)) { + char *s = buf; + if (isinf(f)) { *s++ = 'I' ^ uc; *s++ = 'N' ^ uc; *s++ = 'F' ^ uc; goto ret; - } else if (fp_isnan(f)) { + } else if (isnan(f)) { *s++ = 'N' ^ uc; *s++ = 'A' ^ uc; *s++ = 'N' ^ uc; ret: *s = '\0'; - return s - buf; + return s - buf_entry; } } + // Decode format character + int fmt_flags = (unsigned char)uc; // setup FMT_E_CASE, clear all other bits + char lofmt = (char)(fmt | 0x20); // fmt in lowercase + if (lofmt == 'f') { + fmt_flags |= FMT_MODE_F; + } else if (lofmt == 'g') { + fmt_flags |= FMT_MODE_G; + } else { + fmt_flags |= FMT_MODE_E; + } + + // When precision is unspecified, default to 6 if (prec < 0) { prec = 6; } - char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt - fmt |= 0x20; // Force fmt to be lowercase - char org_fmt = fmt; - if (fmt == 'g' && prec == 0) { - prec = 1; + // Use high precision for `repr`, but switch to exponent mode + // after 16 digits in any case to match CPython behaviour + int max_exp_zeros = (prec < (int)buf_size - 3 ? prec : (int)buf_size - 3); + if (prec == MP_FLOAT_REPR_PREC) { + prec = MAX_MANTISSA_DIGITS; + max_exp_zeros = 16; } - int e; - int dec = 0; - char e_sign = '\0'; - int num_digits = 0; - int signed_e = 0; - // Approximate power of 10 exponent from binary exponent. - // abs(e_guess) is lower bound on abs(power of 10 exponent). - int e_guess = (int)(fp_expval(f) * FPCONST(0.3010299956639812)); // 1/log2(10). - if (fp_iszero(f)) { - e = 0; - if (fmt == 'f') { - // Truncate precision to prevent buffer overflow - if (prec + 2 > buf_remaining) { - prec = buf_remaining - 2; - } - num_digits = prec + 1; - } else { - // Truncate precision to prevent buffer overflow - if (prec + 6 > buf_remaining) { - prec = buf_remaining - 6; - } - if (fmt == 'e') { - e_sign = '+'; - } + // Precompute the exact decimal exponent of f, such that + // abs(e) is lower bound on abs(power of 10 exponent). + int e = 0; + if (!fp_iszero(f)) { + // Approximate power of 10 exponent from binary exponent. + e = (int)(fp_expval(f) * MICROPY_FLOAT_CONST(0.3010299956639812)); // 1/log2(10). + int positive_exp = !fp_isless1(f); + mp_float_t u_base = (mp_float_t)mp_decimal_exp((mp_large_float_t)1.0, e + positive_exp); + while ((f >= u_base) == positive_exp) { + e += (positive_exp ? 1 : -1); + u_base = (mp_float_t)mp_decimal_exp((mp_large_float_t)1.0, e + positive_exp); } - } else if (fp_isless1(f)) { - FPTYPE f_entry = f; // Save f in case we go to 'f' format. - // Build negative exponent - e = -e_guess; - FPTYPE u_base = MICROPY_FLOAT_C_FUN(pow)(10, -e); - while (u_base > f) { - ++e; - u_base = MICROPY_FLOAT_C_FUN(pow)(10, -e); - } - // Normalize out the inferred unit. Use divide because - // pow(10, e) * pow(10, -e) is slightly < 1 for some e in float32 - // (e.g. print("%.12f" % ((1e13) * (1e-13)))) - f /= u_base; - - // If the user specified 'g' format, and e is <= 4, then we'll switch - // to the fixed format ('f') - - if (fmt == 'f' || (fmt == 'g' && e <= 4)) { - fmt = 'f'; - dec = 0; + } - if (org_fmt == 'g') { - prec += (e - 1); - } + // For 'e' format, prec is # digits after the decimal + // For 'f' format, prec is # digits after the decimal + // For 'g' format, prec is the max number of significant digits + // + // For 'e' & 'g' format, there will be a single digit before the decimal + // For 'f' format, zeros must be expanded instead of using an exponent. + // Make sure there is enough room in the buffer for them, or switch to format 'g'. + if ((fmt_flags & FMT_MODE_F) && e > 0) { + int req_size = e + prec + 2; + if (req_size > (int)buf_size) { + fmt_flags ^= FMT_MODE_F; + fmt_flags |= FMT_MODE_G; + prec++; + } + } - // truncate precision to prevent buffer overflow - if (prec + 2 > buf_remaining) { - prec = buf_remaining - 2; + // To work independently of the format, we precompute: + // - the max number of significant digits to produce + // - the number of leading zeros to prepend (mode f only) + // - the number of trailing zeros to append + int max_digits = prec; + int lead_zeros = 0; + int trail_zeros = 0; + if (fmt_flags & FMT_MODE_F) { + if (max_digits > (int)buf_size - 3) { + // cannot satisfy requested number of decimals given buf_size, sorry + max_digits = (int)buf_size - 3; + } + if (e < 0) { + if (max_digits > 2 && e < -2) { + // Insert explicit leading zeros + lead_zeros = (-e < max_digits ? -e : max_digits) - 2; + max_digits -= lead_zeros; + } else { + max_digits++; } - - num_digits = prec; - signed_e = 0; - f = f_entry; - ++num_digits; } else { - // For e & g formats, we'll be printing the exponent, so set the - // sign. - e_sign = '-'; - dec = 0; - - if (prec > (buf_remaining - FPMIN_BUF_SIZE)) { - prec = buf_remaining - FPMIN_BUF_SIZE; - if (fmt == 'g') { - prec++; - } - } - signed_e = -e; + max_digits += e + 1; } } else { - // Build positive exponent. - // We don't modify f at this point to avoid inaccuracies from - // scaling it. Instead, we find the product of powers of 10 - // that is not greater than it, and use that to start the - // mantissa. - e = e_guess; - FPTYPE next_u = MICROPY_FLOAT_C_FUN(pow)(10, e + 1); - while (f >= next_u) { - ++e; - next_u = MICROPY_FLOAT_C_FUN(pow)(10, e + 1); + if (!(fmt_flags & FMT_MODE_G) || max_digits == 0) { + max_digits++; } + } + if (max_digits > MAX_MANTISSA_DIGITS) { + // use trailing zeros to avoid overflowing the mantissa + trail_zeros = max_digits - MAX_MANTISSA_DIGITS; + max_digits = MAX_MANTISSA_DIGITS; + } + int overhead = (fmt_flags & FMT_MODE_F ? 3 : FPMIN_BUF_SIZE + 1); + if (trail_zeros > (int)buf_size - max_digits - overhead) { + // cannot satisfy requested number of decimals given buf_size, sorry + trail_zeros = (int)buf_size - max_digits - overhead; + } - // If the user specified fixed format (fmt == 'f') and e makes the - // number too big to fit into the available buffer, then we'll - // switch to the 'e' format. - - if (fmt == 'f') { - if (e >= buf_remaining) { - fmt = 'e'; - } else if ((e + prec + 2) > buf_remaining) { - prec = buf_remaining - e - 2; - if (prec < 0) { - // This means no decimal point, so we can add one back - // for the decimal. - prec++; - } - } - } - if (fmt == 'e' && prec > (buf_remaining - FPMIN_BUF_SIZE)) { - prec = buf_remaining - FPMIN_BUF_SIZE; - } - if (fmt == 'g') { - // Truncate precision to prevent buffer overflow - if (prec + (FPMIN_BUF_SIZE - 1) > buf_remaining) { - prec = buf_remaining - (FPMIN_BUF_SIZE - 1); - } - } - // If the user specified 'g' format, and e is < prec, then we'll switch - // to the fixed format. + // When the caller asks for more precision than available for sure, + // Look for a shorter (rounded) representation first, and only dig + // into more digits if there is no short representation. + int num_digits = (SAFE_MANTISSA_DIGITS < max_digits ? SAFE_MANTISSA_DIGITS : max_digits); +try_again: + ; - if (fmt == 'g' && e < prec) { - fmt = 'f'; - prec -= (e + 1); - } - if (fmt == 'f') { - dec = e; - num_digits = prec + e + 1; + char *s = buf; + int extra_zeros = trail_zeros + (max_digits - num_digits); + int decexp; + int dec = 0; + + if (fp_iszero(f)) { + // no need for scaling 0.0 + decexp = 0; + } else if (fmt_flags & FMT_MODE_F) { + decexp = num_digits - 1; + if (e < 0) { + // Negative exponent: we keep a single leading zero in the mantissa, + // as using more would waste precious digits needed for accuracy. + if (lead_zeros > 0) { + // We are using leading zeros + s = mp_prepend_zeros(s, lead_zeros); + decexp += lead_zeros + 1; + dec = 255; // no decimal dot + } else { + // Small negative exponent, work directly on the mantissa + dec = 0; + } } else { - e_sign = '+'; + // Positive exponent: we will add trailing zeros separately + decexp -= e; + dec = e; } - signed_e = e; + } else { + decexp = num_digits - e - 1; } - if (prec < 0) { - // This can happen when the prec is trimmed to prevent buffer overflow - prec = 0; + DEBUG_PRINTF("input=%.19g e=%d fmt=%c max_d=%d num_d=%d decexp=%d dec=%d l0=%d r0=%d\n", + (double)f, e, lofmt, max_digits, num_digits, decexp, dec, lead_zeros, extra_zeros); + + // At this point, + // - buf points to beginning of output buffer for the unsigned representation + // - num_digits == the number of mantissa digits to add + // - (dec + 1) == the number of digits to print before adding a decimal point + // - decexp == the power of 10 exponent to apply to f to get the decimal mantissa + // - e == the power of 10 exponent to append ('e' or 'g' format) + mp_large_float_uint_t mantissa_cap = 10; + for (int n = 1; n < num_digits; n++) { + mantissa_cap *= 10; } - // At this point e contains the absolute value of the power of 10 exponent. - // (dec + 1) == the number of dgits before the decimal. - - // For e, prec is # digits after the decimal - // For f, prec is # digits after the decimal - // For g, prec is the max number of significant digits - // - // For e & g there will be a single digit before the decimal - // for f there will be e digits before the decimal - - if (fmt == 'e') { - num_digits = prec + 1; - } else if (fmt == 'g') { - if (prec == 0) { - prec = 1; + // Build the decimal mantissa into a large uint + mp_large_float_uint_t mantissa = 1; + if (sizeof(mp_large_float_t) == sizeof(mp_float_t) && num_digits > SAFE_MANTISSA_DIGITS && decexp > 1) { + // if we don't have large floats, use integer multiply to produce the last digits + if (num_digits > SAFE_MANTISSA_DIGITS + 1 && decexp > 2) { + mantissa = 100; + decexp -= 2; + } else { + mantissa = 10; + decexp -= 1; } - num_digits = prec; } - - int d = 0; - for (int digit_index = signed_e; num_digits >= 0; --digit_index) { - FPTYPE u_base = FPCONST(1.0); - if (digit_index > 0) { - // Generate 10^digit_index for positive digit_index. - u_base = MICROPY_FLOAT_C_FUN(pow)(10, digit_index); - } - for (d = 0; d < 9; ++d) { - if (f < u_base) { - break; - } - f -= u_base; - } - // We calculate one more digit than we display, to use in rounding - // below. So only emit the digit if it's one that we display. - if (num_digits > 0) { - // Emit this number (the leading digit). - *s++ = '0' + d; - if (dec == 0 && prec > 0) { - *s++ = '.'; - } - } - --dec; - --num_digits; - if (digit_index <= 0) { - // Once we get below 1.0, we scale up f instead of calculating - // negative powers of 10 in u_base. This provides better - // renditions of exact decimals like 1/16 etc. - f *= FPCONST(10.0); + mp_large_float_t mantissa_f = mp_decimal_exp((mp_large_float_t)f, decexp); + mantissa *= (mp_large_float_uint_t)(mantissa_f + (mp_large_float_t)0.5); + DEBUG_PRINTF("input=%.19g fmt=%c num_digits=%d dec=%d mantissa=" MP_FFUINT_FMT " r0=%d\n", (double)f, lofmt, num_digits, dec, mantissa, extra_zeros); + + // Finally convert the decimal mantissa to a floating-point string, according to formatting rules + int reprlen = mp_format_mantissa(mantissa, mantissa_cap, buf, s, num_digits, max_exp_zeros, extra_zeros, dec, e, fmt_flags); + assert(reprlen + 1 <= (int)buf_size); + + #if MICROPY_FLOAT_FORMAT_IMPL != MICROPY_FLOAT_FORMAT_IMPL_APPROX + + if (num_digits < max_digits) { + // The initial precision might not be sufficient for an exact representation + // for all numbers. If the result is not exact, restart using next precision. + // parse the resulting number and compare against the original + mp_float_t check; + DEBUG_PRINTF("input=%.19g, compare to float('%s')\n", (double)f, buf); + mp_parse_float_internal(buf, reprlen, &check); + if (!fp_equal(check, f)) { + num_digits++; + DEBUG_PRINTF("Not perfect, retry using more digits (%d)\n", num_digits); + goto try_again; } } - // Rounding. If the next digit to print is >= 5, round up. - if (d >= 5) { - char *rs = s; - rs--; - while (1) { - if (*rs == '.') { - rs--; - continue; - } - if (*rs < '0' || *rs > '9') { - // + or - - rs++; // So we sit on the digit to the right of the sign - break; + + #else + + // The initial decimal mantissa might not have been be completely accurate due + // to the previous loating point operations. The best way to verify this is to + // parse the resulting number and compare against the original + mp_float_t check; + DEBUG_PRINTF("input=%.19g, compare to float('%s')\n", (double)f, buf); + mp_parse_float_internal(buf, reprlen, &check); + mp_float_t diff = fp_diff(check, f); + mp_float_t best_diff = diff; + mp_large_float_uint_t best_mantissa = mantissa; + + if (fp_iszero(diff)) { + // we have a perfect match + DEBUG_PRINTF(MP_FFUINT_FMT ": perfect match (direct)\n", mantissa); + } else { + // In order to get the best possible representation, we will perform a + // dichotomic search for a reversible representation. + // This will also provide optimal rounding on the fly. + unsigned err_range = 1; + if (num_digits > SAFE_MANTISSA_DIGITS) { + err_range <<= 3 * (num_digits - SAFE_MANTISSA_DIGITS); + } + int maxruns = 3 + 3 * (MAX_MANTISSA_DIGITS - SAFE_MANTISSA_DIGITS); + while (maxruns-- > 0) { + // update mantissa according to dichotomic search + if (signbit(diff)) { + mantissa += err_range; + } else { + // mantissa is expected to always have more significant digits than err_range + assert(mantissa >= err_range); + mantissa -= err_range; } - if (*rs < '9') { - (*rs)++; + // retry conversion + reprlen = mp_format_mantissa(mantissa, mantissa_cap, buf, s, num_digits, max_exp_zeros, extra_zeros, dec, e, fmt_flags); + assert(reprlen + 1 <= (int)buf_size); + DEBUG_PRINTF("input=%.19g, compare to float('%s')\n", (double)f, buf); + mp_parse_float_internal(buf, reprlen, &check); + DEBUG_PRINTF("check=%.19g num_digits=%d e=%d mantissa=" MP_FFUINT_FMT "\n", (double)check, num_digits, e, mantissa); + diff = fp_diff(check, f); + if (fp_iszero(diff)) { + // we have a perfect match + DEBUG_PRINTF(MP_FFUINT_FMT ": perfect match\n", mantissa); break; } - *rs = '0'; - if (rs == buf) { - break; + // keep track of our best estimate + mp_float_t delta = MICROPY_FLOAT_C_FUN(fabs)(diff) - MICROPY_FLOAT_C_FUN(fabs)(best_diff); + if (signbit(delta) || (fp_iszero(delta) && !(mantissa % 10u))) { + best_diff = diff; + best_mantissa = mantissa; } - rs--; - } - if (*rs == '0') { - // We need to insert a 1 - if (rs[1] == '.' && fmt != 'f') { - // We're going to round 9.99 to 10.00 - // Move the decimal point - rs[0] = '.'; - rs[1] = '0'; - if (e_sign == '-') { - e--; - if (e == 0) { - e_sign = '+'; - } - } else { - e++; - } + // string repr is not perfect: continue a dichotomic improvement + DEBUG_PRINTF(MP_FFUINT_FMT ": %.19g, err_range=%d\n", mantissa, (double)check, err_range); + if (err_range > 1) { + err_range >>= 1; } else { - // Need at extra digit at the end to make room for the leading '1' - // but if we're at the buffer size limit, just drop the final digit. - if ((size_t)(s + 1 - buf) < buf_size) { - s++; + // We have tried all possible mantissa, without finding a reversible repr. + // Check if we have an alternate precision to try. + if (num_digits < max_digits) { + num_digits++; + DEBUG_PRINTF("Failed to find a perfect match, try with more digits (%d)\n", num_digits); + goto try_again; } + // Otherwise, keep the closest one, which is either the first one or the last one. + if (mantissa == best_mantissa) { + // Last guess is the best one + DEBUG_PRINTF(MP_FFUINT_FMT ": last guess was the best one\n", mantissa); + } else { + // We had a better guess earlier + DEBUG_PRINTF(MP_FFUINT_FMT ": use best guess\n", mantissa); + reprlen = mp_format_mantissa(best_mantissa, mantissa_cap, buf, s, num_digits, max_exp_zeros, extra_zeros, dec, e, fmt_flags); + } + break; } - char *ss = s; - while (ss > rs) { - *ss = ss[-1]; - ss--; - } - *rs = '1'; } } + #endif - // verify that we did not overrun the input buffer so far - assert((size_t)(s + 1 - buf) <= buf_size); - - if (org_fmt == 'g' && prec > 0) { - // Remove trailing zeros and a trailing decimal point - while (s[-1] == '0') { - s--; - } - if (s[-1] == '.') { - s--; - } - } - // Append the exponent - if (e_sign) { - *s++ = e_char; - *s++ = e_sign; - if (FPMIN_BUF_SIZE == 7 && e >= 100) { - *s++ = '0' + (e / 100); - } - *s++ = '0' + ((e / 10) % 10); - *s++ = '0' + (e % 10); - } - *s = '\0'; - - // verify that we did not overrun the input buffer - assert((size_t)(s + 1 - buf) <= buf_size); - - return s - buf; + return buf + reprlen - buf_entry; } #endif // MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE diff --git a/py/formatfloat.h b/py/formatfloat.h index 9a1643b4ddf..7b1414672b7 100644 --- a/py/formatfloat.h +++ b/py/formatfloat.h @@ -29,6 +29,7 @@ #include "py/mpconfig.h" #if MICROPY_PY_BUILTINS_FLOAT +#define MP_FLOAT_REPR_PREC (99) // magic `prec` value for optimal `repr` behaviour int mp_format_float(mp_float_t f, char *buf, size_t bufSize, char fmt, int prec, char sign); #endif diff --git a/py/misc.h b/py/misc.h index e0344858389..86ac2ec9a10 100644 --- a/py/misc.h +++ b/py/misc.h @@ -277,6 +277,25 @@ typedef union _mp_float_union_t { mp_float_uint_t i; } mp_float_union_t; +#if MICROPY_FLOAT_FORMAT_IMPL == MICROPY_FLOAT_FORMAT_IMPL_EXACT + +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +// Exact float conversion requires using internally a bigger sort of floating point +typedef double mp_large_float_t; +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +typedef long double mp_large_float_t; +#endif +// Always use a 64 bit mantissa for formatting and parsing +typedef uint64_t mp_large_float_uint_t; + +#else // MICROPY_FLOAT_FORMAT_IMPL != MICROPY_FLOAT_FORMAT_IMPL_EXACT + +// No bigger floating points +typedef mp_float_t mp_large_float_t; +typedef mp_float_uint_t mp_large_float_uint_t; + +#endif + #endif // MICROPY_PY_BUILTINS_FLOAT /** ROM string compression *************/ diff --git a/py/mpconfig.h b/py/mpconfig.h index c316aa4b2d8..caa63fef3f6 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -861,6 +861,27 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT) #endif +// Float to string conversion implementations +// +// Note that the EXACT method is only available if the compiler supports +// floating points larger than mp_float_t: +// - with MICROPY_FLOAT_IMPL_FLOAT, the compiler needs to support `double` +// - with MICROPY_FLOAT_IMPL_DOUBLE, the compiler needs to support `long double` +// +#define MICROPY_FLOAT_FORMAT_IMPL_BASIC (0) // smallest code, but inexact +#define MICROPY_FLOAT_FORMAT_IMPL_APPROX (1) // slightly bigger, almost perfect +#define MICROPY_FLOAT_FORMAT_IMPL_EXACT (2) // bigger code, and 100% exact repr + +#ifndef MICROPY_FLOAT_FORMAT_IMPL +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define MICROPY_FLOAT_FORMAT_IMPL (MICROPY_FLOAT_FORMAT_IMPL_APPROX) +#elif defined(__SIZEOF_LONG_DOUBLE__) && __SIZEOF_LONG_DOUBLE__ > __SIZEOF_DOUBLE__ +#define MICROPY_FLOAT_FORMAT_IMPL (MICROPY_FLOAT_FORMAT_IMPL_EXACT) +#else +#define MICROPY_FLOAT_FORMAT_IMPL (MICROPY_FLOAT_FORMAT_IMPL_APPROX) +#endif +#endif + // Whether to use the native _Float16 for 16-bit float support #ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 #ifdef __FLT16_MAX__ diff --git a/py/mpprint.c b/py/mpprint.c index f1d8bd0c573..bd7a250878b 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -338,7 +338,7 @@ int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, unsigned int base, int #if MICROPY_PY_BUILTINS_FLOAT int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int flags, char fill, int width, int prec) { - char buf[32]; + char buf[36]; char sign = '\0'; int chrs = 0; @@ -349,11 +349,17 @@ int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int sign = ' '; } - int len = mp_format_float(f, buf, sizeof(buf), fmt, prec, sign); + int len = mp_format_float(f, buf, sizeof(buf) - 3, fmt, prec, sign); char *s = buf; - if ((flags & PF_FLAG_ADD_PERCENT) && (size_t)(len + 1) < sizeof(buf)) { + if ((flags & PF_FLAG_ALWAYS_DECIMAL) && strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) { + buf[len++] = '.'; + buf[len++] = '0'; + buf[len] = '\0'; + } + + if (flags & PF_FLAG_ADD_PERCENT) { buf[len++] = '%'; buf[len] = '\0'; } diff --git a/py/mpprint.h b/py/mpprint.h index 583f00bda80..250ea24b878 100644 --- a/py/mpprint.h +++ b/py/mpprint.h @@ -36,6 +36,7 @@ #define PF_FLAG_CENTER_ADJUST (0x020) #define PF_FLAG_ADD_PERCENT (0x040) #define PF_FLAG_SHOW_OCTAL_LETTER (0x080) +#define PF_FLAG_ALWAYS_DECIMAL (0x100) #define PF_FLAG_SEP_POS (9) // must be above all the above PF_FLAGs #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES diff --git a/py/objcomplex.c b/py/objcomplex.c index 85b58528457..805899edf43 100644 --- a/py/objcomplex.c +++ b/py/objcomplex.c @@ -45,29 +45,18 @@ typedef struct _mp_obj_complex_t { static void complex_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_obj_complex_t *o = MP_OBJ_TO_PTR(o_in); - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - char buf[16]; - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C - const int precision = 6; - #else - const int precision = 7; - #endif - #else - char buf[32]; - const int precision = 16; - #endif - if (o->real == 0) { - mp_format_float(o->imag, buf, sizeof(buf), 'g', precision, '\0'); - mp_printf(print, "%sj", buf); + const char *suffix; + int flags = 0; + if (o->real != 0) { + mp_print_str(print, "("); + mp_print_float(print, o->real, 'g', 0, '\0', -1, MP_FLOAT_REPR_PREC); + flags = PF_FLAG_SHOW_SIGN; + suffix = "j)"; } else { - mp_format_float(o->real, buf, sizeof(buf), 'g', precision, '\0'); - mp_printf(print, "(%s", buf); - if (o->imag >= 0 || isnan(o->imag)) { - mp_print_str(print, "+"); - } - mp_format_float(o->imag, buf, sizeof(buf), 'g', precision, '\0'); - mp_printf(print, "%sj)", buf); + suffix = "j"; } + mp_print_float(print, o->imag, 'g', flags, '\0', -1, MP_FLOAT_REPR_PREC); + mp_print_str(print, suffix); } static mp_obj_t complex_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { diff --git a/py/objfloat.c b/py/objfloat.c index 81b0daa6209..125b576fb61 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -110,23 +110,7 @@ mp_int_t mp_float_hash(mp_float_t src) { static void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_float_t o_val = mp_obj_float_get(o_in); - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - char buf[16]; - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C - const int precision = 6; - #else - const int precision = 7; - #endif - #else - char buf[32]; - const int precision = 16; - #endif - mp_format_float(o_val, buf, sizeof(buf), 'g', precision, '\0'); - mp_print_str(print, buf); - if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) { - // Python floats always have decimal point (unless inf or nan) - mp_print_str(print, ".0"); - } + mp_print_float(print, o_val, 'g', PF_FLAG_ALWAYS_DECIMAL, '\0', -1, MP_FLOAT_REPR_PREC); } static mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { diff --git a/py/parsenum.c b/py/parsenum.c index 019491b5132..e18002306a2 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -210,7 +210,7 @@ typedef enum { } parse_dec_in_t; // MANTISSA_MAX is used to retain precision while not overflowing mantissa -#define MANTISSA_MAX (sizeof(mp_float_uint_t) == 8 ? 0x1999999999999998ULL : 0x19999998U) +#define MANTISSA_MAX (sizeof(mp_large_float_uint_t) == 8 ? 0x1999999999999998ULL : 0x19999998U) // MAX_EXACT_POWER_OF_5 is the largest value of x so that 5^x can be stored exactly in a float #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT @@ -220,11 +220,45 @@ typedef enum { #endif // Helper to compute `num * (10.0 ** dec_exp)` -mp_float_t mp_decimal_exp(mp_float_t num, int dec_exp) { - - if (dec_exp == 0 || num == MICROPY_FLOAT_CONST(0.0)) { +mp_large_float_t mp_decimal_exp(mp_large_float_t num, int dec_exp) { + if (dec_exp == 0 || num == (mp_large_float_t)(0.0)) { return num; } + + #if MICROPY_FLOAT_FORMAT_IMPL == MICROPY_FLOAT_FORMAT_IMPL_EXACT + + // If the assert below fails, it means you have chosen MICROPY_FLOAT_FORMAT_IMPL_EXACT + // manually on a platform where `larger floats` are not supported, which would + // result in inexact conversions. To fix this issue, change your `mpconfigport.h` + // and select MICROPY_FLOAT_FORMAT_IMPL_APPROX instead + assert(sizeof(mp_large_float_t) > sizeof(mp_float_t)); + + // Perform power using simple multiplications, to avoid + // dependency to higher-precision pow() function + int neg_exp = (dec_exp < 0); + if (neg_exp) { + dec_exp = -dec_exp; + } + mp_large_float_t res = num; + mp_large_float_t expo = (mp_large_float_t)10.0; + while (dec_exp) { + if (dec_exp & 1) { + if (neg_exp) { + res /= expo; + } else { + res *= expo; + } + } + dec_exp >>= 1; + if (dec_exp) { + expo *= expo; + } + } + return res; + + #else + // MICROPY_FLOAT_FORMAT_IMPL != MICROPY_FLOAT_FORMAT_IMPL_EXACT + mp_float_union_t res = {num}; // Multiply first by (2.0 ** dec_exp) via the exponent // - this will ensure that the result of `pow()` is always in mp_float_t range @@ -238,12 +272,14 @@ mp_float_t mp_decimal_exp(mp_float_t num, int dec_exp) { } else { res.f *= MICROPY_FLOAT_C_FUN(pow)(5, dec_exp); } - return (mp_float_t)res.f; + return (mp_large_float_t)res.f; + + #endif } // Break out inner digit accumulation routine to ease trailing zero deferral. -static mp_float_uint_t accept_digit(mp_float_uint_t p_mantissa, unsigned int dig, int *p_exp_extra, int in) { +static mp_large_float_uint_t accept_digit(mp_large_float_uint_t p_mantissa, unsigned int dig, int *p_exp_extra, int in) { // Core routine to ingest an additional digit. if (p_mantissa < MANTISSA_MAX) { // dec_val won't overflow so keep accumulating @@ -267,7 +303,7 @@ const char *mp_parse_float_internal(const char *str, size_t len, mp_float_t *res parse_dec_in_t in = PARSE_DEC_IN_INTG; bool exp_neg = false; - mp_float_uint_t mantissa = 0; + mp_large_float_uint_t mantissa = 0; int exp_val = 0; int exp_extra = 0; int trailing_zeros_intg = 0, trailing_zeros_frac = 0; diff --git a/py/parsenum.h b/py/parsenum.h index a807cb09d05..d532cb194a5 100644 --- a/py/parsenum.h +++ b/py/parsenum.h @@ -35,7 +35,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str, size_t len, int base, mp_lexer_t *lex); #if MICROPY_PY_BUILTINS_FLOAT -mp_float_t mp_decimal_exp(mp_float_t num, int dec_exp); +mp_large_float_t mp_decimal_exp(mp_large_float_t num, int dec_exp); const char *mp_parse_float_internal(const char *str, size_t len, mp_float_t *res); #endif diff --git a/tests/float/float_format.py b/tests/float/float_format.py index 98ed0eb096f..0eb8b232b06 100644 --- a/tests/float/float_format.py +++ b/tests/float/float_format.py @@ -2,14 +2,25 @@ # general rounding for val in (116, 1111, 1234, 5010, 11111): - print("%.0f" % val) - print("%.1f" % val) - print("%.3f" % val) + print("Test on %d / 1000:" % val) + for fmt in ("%.5e", "%.3e", "%.1e", "%.0e", "%.3f", "%.1f", "%.0f", "%.3g", "%.1g", "%.0g"): + print(fmt, fmt % (val / 1000)) + +# make sure round-up to the next unit is handled properly +for val in range(4, 9): + divi = 10**val + print("Test on 99994 / (10 ** %d):" % val) + for fmt in ("%.5e", "%.3e", "%.1e", "%.0e", "%.3f", "%.1f", "%.0f", "%.3g", "%.1g", "%.0g"): + print(fmt, fmt % (99994 / divi)) # make sure rounding is done at the correct precision for prec in range(8): print(("%%.%df" % prec) % 6e-5) +# make sure trailing zeroes are added properly +for prec in range(8): + print(("%%.%df" % prec) % 1e19) + # check certain cases that had a digit value of 10 render as a ":" character print("%.2e" % float("9" * 51 + "e-39")) print("%.2e" % float("9" * 40 + "e-21")) diff --git a/tests/float/float_format_accuracy.py b/tests/float/float_format_accuracy.py new file mode 100644 index 00000000000..f9467f9c05d --- /dev/null +++ b/tests/float/float_format_accuracy.py @@ -0,0 +1,73 @@ +# Test accuracy of `repr` conversions. +# This test also increases code coverage for corner cases. + +try: + import array, math, random +except ImportError: + print("SKIP") + raise SystemExit + +# The largest errors come from seldom used very small numbers, near the +# limit of the representation. So we keep them out of this test to keep +# the max relative error display useful. +if float("1e-100") == 0.0: + # single-precision + float_type = "f" + float_size = 4 + # testing range + min_expo = -96 # i.e. not smaller than 1.0e-29 + # Expected results (given >=50'000 samples): + # - MICROPY_FLTCONV_IMPL_EXACT: 100% exact conversions + # - MICROPY_FLTCONV_IMPL_APPROX: >=98.53% exact conversions, max relative error <= 1.01e-7 + min_success = 0.980 # with only 1200 samples, the success rate is lower + max_rel_err = 1.1e-7 + # REPR_C is typically used with FORMAT_IMPL_BASIC, which has a larger error + is_REPR_C = float("1.0000001") == float("1.0") + if is_REPR_C: # REPR_C + min_success = 0.83 + max_rel_err = 5.75e-07 +else: + # double-precision + float_type = "d" + float_size = 8 + # testing range + min_expo = -845 # i.e. not smaller than 1.0e-254 + # Expected results (given >=200'000 samples): + # - MICROPY_FLTCONV_IMPL_EXACT: 100% exact conversions + # - MICROPY_FLTCONV_IMPL_APPROX: >=99.83% exact conversions, max relative error <= 2.7e-16 + min_success = 0.997 # with only 1200 samples, the success rate is lower + max_rel_err = 2.7e-16 + + +# Deterministic pseudorandom generator. Designed to be uniform +# on mantissa values and exponents, not on the represented number +def pseudo_randfloat(): + rnd_buff = bytearray(float_size) + for _ in range(float_size): + rnd_buff[_] = random.getrandbits(8) + return array.array(float_type, rnd_buff)[0] + + +random.seed(42) +stats = 0 +N = 1200 +max_err = 0 +for _ in range(N): + f = pseudo_randfloat() + while type(f) is not float or math.isinf(f) or math.isnan(f) or math.frexp(f)[1] <= min_expo: + f = pseudo_randfloat() + + str_f = repr(f) + f2 = float(str_f) + if f2 == f: + stats += 1 + else: + error = abs((f2 - f) / f) + if max_err < error: + max_err = error + +print(N, "values converted") +if stats / N >= min_success and max_err <= max_rel_err: + print("float format accuracy OK") +else: + print("FAILED: repr rate=%.3f%% max_err=%.3e" % (100 * stats / N, max_err)) diff --git a/tests/float/float_format_ints.py b/tests/float/float_format_ints.py index df4444166c5..7b7b30c4b34 100644 --- a/tests/float/float_format_ints.py +++ b/tests/float/float_format_ints.py @@ -12,14 +12,42 @@ print(title, "with format", f_fmt, "gives", f_fmt.format(f)) print(title, "with format", g_fmt, "gives", g_fmt.format(f)) +# The tests below check border cases involving all mantissa bits. +# In case of REPR_C, where the mantissa is missing two bits, the +# the string representation for such numbers might not always be exactly +# the same but nevertheless be correct, so we must allow a few exceptions. +is_REPR_C = float("1.0000001") == float("1.0") + # 16777215 is 2^24 - 1, the largest integer that can be completely held # in a float32. -print("{:f}".format(16777215)) +val_str = "{:f}".format(16777215) + +# When using REPR_C, 16777215.0 is the same as 16777212.0 or 16777214.4 +# (depending on the implementation of pow() function, the result may differ) +if is_REPR_C and (val_str == "16777212.000000" or val_str == "16777214.400000"): + val_str = "16777215.000000" + +print(val_str) + # 4294967040 = 16777215 * 128 is the largest integer that is exactly # represented by a float32 and that will also fit within a (signed) int32. # The upper bound of our integer-handling code is actually double this, # but that constant might cause trouble on systems using 32 bit ints. -print("{:f}".format(2147483520)) +val_str = "{:f}".format(2147483520) + +# When using FLOAT_IMPL_FLOAT, 2147483520.0 == 2147483500.0 +# Both representations are valid, the second being "simpler" +is_float32 = float("1e300") == float("inf") +if is_float32 and val_str == "2147483500.000000": + val_str = "2147483520.000000" + +# When using REPR_C, 2147483520.0 is the same as 2147483200.0 +# Both representations are valid, the second being "simpler" +if is_REPR_C and val_str == "2147483200.000000": + val_str = "2147483520.000000" + +print(val_str) + # Very large positive integers can be a test for precision and resolution. # This is a weird way to represent 1e38 (largest power of 10 for float32). print("{:.6e}".format(float("9" * 30 + "e8"))) diff --git a/tests/float/float_struct_e.py b/tests/float/float_struct_e.py index 403fbc5db4c..ba4134f3393 100644 --- a/tests/float/float_struct_e.py +++ b/tests/float/float_struct_e.py @@ -32,7 +32,7 @@ for i in (j, -j): x = struct.pack(" Date: Fri, 1 Aug 2025 10:30:55 +1000 Subject: [PATCH 1023/2098] github/workflows: Build unix port for docs and run workflow more often. The unix port is needed to build the docs, due to the cpydiff tests which run both CPython and MicroPython (unix port). That was previously not failing the CI because the output from MicroPython was: /bin/sh: 1: ../ports/unix/build-standard/micropython: not found which doesn't match the CPython output for any of the cpydiff tests, and so it was considered a "pass" in terms of the output differing. Also, run the docs workflow when py/ or tests/cpydiff/ changes, because the cpydiff results may change when the core code changes. Signed-off-by: Damien George --- .github/workflows/docs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d01a4b50c98..62a6f69fc39 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,8 @@ on: pull_request: paths: - docs/** + - py/** + - tests/cpydiff/** concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,5 +21,7 @@ jobs: - uses: actions/setup-python@v5 - name: Install Python packages run: pip install -r docs/requirements.txt + - name: Build unix port + run: source tools/ci.sh && ci_unix_build_helper - name: Build docs run: make -C docs/ html From 947d5448b477343fb1b113e24cb3d3b904a9d3e7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 1 Aug 2025 11:12:08 +1000 Subject: [PATCH 1024/2098] tests/cpydiff: Remove passing types_float_rounding test. Since commit dbbaa959c85c04dbbcde5908b5d0775b574e44e7, this test now produces the same output on MicroPython as CPython does, namely -1e+01. Signed-off-by: Damien George --- tests/cpydiff/types_float_rounding.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 tests/cpydiff/types_float_rounding.py diff --git a/tests/cpydiff/types_float_rounding.py b/tests/cpydiff/types_float_rounding.py deleted file mode 100644 index 206e359ed9b..00000000000 --- a/tests/cpydiff/types_float_rounding.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -categories: Types,float -description: uPy and CPython outputs formats may differ -cause: Unknown -workaround: Unknown -""" - -print("%.1g" % -9.9) From f8f6d71940cb34c762d919a255f084d18fbcf1c1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Jul 2025 18:52:45 +1000 Subject: [PATCH 1025/2098] nrf/drivers/bluetooth: Change soft-device download URL to self hosted. The existing URLs have started to return a HTTP 403. The simplest way around this is to host the files at micropython.org, and point to them from the download script. The soft-device files have been retrieved from: - https://www.nordicsemi.com/Products/Development-software/s110/download - https://www.nordicsemi.com/Products/Development-software/s132/download - https://www.nordicsemi.com/Products/Development-software/s140/download Signed-off-by: Damien George --- .../drivers/bluetooth/download_ble_stack.sh | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/ports/nrf/drivers/bluetooth/download_ble_stack.sh b/ports/nrf/drivers/bluetooth/download_ble_stack.sh index 7886682b718..5004d7a28ef 100755 --- a/ports/nrf/drivers/bluetooth/download_ble_stack.sh +++ b/ports/nrf/drivers/bluetooth/download_ble_stack.sh @@ -10,12 +10,9 @@ function download_s110_nrf51_8_0_0 mkdir -p $1/s110_nrf51_8.0.0 cd $1/s110_nrf51_8.0.0 - wget --post-data="fileName=DeviceDownload&ids=DBBEB2467E4A4EBCB791C2E7BE3FC7A8" https://www.nordicsemi.com/api/sitecore/Products/MedialibraryZipDownload2 - mv MedialibraryZipDownload2 temp.zip - unzip -u temp.zip + wget https://micropython.org/resources/nrf-soft-device/s110nrf51800.zip unzip -u s110nrf51800.zip rm s110nrf51800.zip - rm temp.zip cd - } @@ -29,12 +26,9 @@ function download_s132_nrf52_6_1_1 mkdir -p $1/s132_nrf52_6.1.1 cd $1/s132_nrf52_6.1.1 - wget --post-data="fileName=DeviceDownload&ids=3AB3E86666FE4361A4A3B7E0D1CBB9B9" https://www.nordicsemi.com/api/sitecore/Products/MedialibraryZipDownload2 - mv MedialibraryZipDownload2 temp.zip - unzip -u temp.zip + wget https://micropython.org/resources/nrf-soft-device/s132nrf52611.zip unzip -u s132nrf52611.zip rm s132nrf52611.zip - rm temp.zip cd - } @@ -48,12 +42,9 @@ function download_s140_nrf52_6_1_1 mkdir -p $1/s140_nrf52_6.1.1 cd $1/s140_nrf52_6.1.1 - wget --post-data="fileName=DeviceDownload&ids=CE89BA7633C540AFA48AB88E934DBF05" https://www.nordicsemi.com/api/sitecore/Products/MedialibraryZipDownload2 - mv MedialibraryZipDownload2 temp.zip - unzip -u temp.zip + wget https://micropython.org/resources/nrf-soft-device/s140nrf52611.zip unzip -u s140nrf52611.zip rm s140nrf52611.zip - rm temp.zip cd - } @@ -67,12 +58,9 @@ function download_s140_nrf52_7_3_0 mkdir -p $1/s140_nrf52_7.3.0 cd $1/s140_nrf52_7.3.0 - wget --post-data="fileName=DeviceDownload&ids=59452FDD13BA46EEAD0810A57359F294" https://www.nordicsemi.com/api/sitecore/Products/MedialibraryZipDownload2 - mv MedialibraryZipDownload2 temp.zip - unzip -u temp.zip + wget https://micropython.org/resources/nrf-soft-device/s140_nrf52_7.3.0.zip unzip -u s140_nrf52_7.3.0.zip rm s140_nrf52_7.3.0.zip - rm temp.zip cd - } From f67a3703118be7d97629130d99630996ff3cb255 Mon Sep 17 00:00:00 2001 From: SiZiOUS Date: Sat, 19 Jul 2025 18:45:42 +0200 Subject: [PATCH 1026/2098] embed/port: Fix alloca include for Windows platforms. When building the embedded port on MinGW-w64, I receive the following error: fatal error: alloca.h: No such file or directory MinGW-w64 (used on MSYS2) doesn't include `alloca.h`, but `alloca()` is provided via `malloc.h` instead. And this fix is also needed for other Windows build systems. Signed-off-by: SiZiOUS --- ports/embed/port/mpconfigport_common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/embed/port/mpconfigport_common.h b/ports/embed/port/mpconfigport_common.h index 8e19859ed2e..aa65640fcd5 100644 --- a/ports/embed/port/mpconfigport_common.h +++ b/ports/embed/port/mpconfigport_common.h @@ -34,8 +34,13 @@ typedef long mp_off_t; // Need to provide a declaration/definition of alloca() #if defined(__FreeBSD__) || defined(__NetBSD__) +// BSD #include +#elif defined(_WIN32) +// Windows +#include #else +// Other OS #include #endif From 69ead7d98ef30df3b6bd4485633490e80fca1718 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Tue, 28 Jan 2025 00:26:08 +0100 Subject: [PATCH 1027/2098] py/parse: Add support for math module constants and float folding. Add a new MICROPY_COMP_CONST_FLOAT feature, enabled by in mpy-cross and when compiling with MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES. The new feature leverages the code of MICROPY_COMP_CONST_FOLDING to support folding of floating point constants. If MICROPY_COMP_MODULE_CONST is defined as well, math module constants are made available at compile time. For example: _DEG_TO_GRADIANT = const(math.pi / 180) _INVALID_VALUE = const(math.nan) A few corner cases had to be handled: - The float const folding code should not fold expressions resulting into complex results, as the mpy parser for complex immediates has limitations. - The constant generation code must distinguish between -0.0 and 0.0, which are different even if C consider them as ==. This change removes previous limitations on the use of `const()` expressions that would result in floating point number, so the test cases of micropython/const_error have to be updated. Additional test cases have been added to cover the new repr() code (from a previous commit). A few other simple test cases have been added to handle the use of floats in `const()` expressions, but the float folding code itself is also tested when running general float test cases, as float expressions often get resolved at compile-time (with this change). Signed-off-by: Yoctopuce dev --- mpy-cross/mpconfigport.h | 4 +- py/builtin.h | 1 + py/emitcommon.c | 17 ++++- py/mpconfig.h | 7 +++ py/parse.c | 89 +++++++++++++++++++-------- tests/float/float_parse_doubleprec.py | 6 ++ tests/micropython/const_error.py | 2 - tests/micropython/const_error.py.exp | 2 - tests/micropython/const_float.py | 23 +++++++ tests/micropython/const_float.py.exp | 4 ++ tests/micropython/const_math.py | 18 ++++++ tests/micropython/const_math.py.exp | 1 + 12 files changed, 143 insertions(+), 31 deletions(-) create mode 100644 tests/micropython/const_float.py create mode 100644 tests/micropython/const_float.py.exp create mode 100644 tests/micropython/const_math.py create mode 100644 tests/micropython/const_math.py.exp diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 94a598c9954..81cbfc2eeb9 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -55,6 +55,7 @@ #define MICROPY_COMP_CONST_FOLDING (1) #define MICROPY_COMP_MODULE_CONST (1) #define MICROPY_COMP_CONST (1) +#define MICROPY_COMP_CONST_FLOAT (1) #define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) #define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) #define MICROPY_COMP_RETURN_IF_EXPR (1) @@ -88,7 +89,8 @@ #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ATTRTUPLE (0) #define MICROPY_PY_COLLECTIONS (0) -#define MICROPY_PY_MATH (0) +#define MICROPY_PY_MATH (MICROPY_COMP_CONST_FLOAT) +#define MICROPY_PY_MATH_CONSTANTS (MICROPY_COMP_CONST_FLOAT) #define MICROPY_PY_CMATH (0) #define MICROPY_PY_GC (0) #define MICROPY_PY_IO (0) diff --git a/py/builtin.h b/py/builtin.h index 6efe3e8faca..388bc847005 100644 --- a/py/builtin.h +++ b/py/builtin.h @@ -138,6 +138,7 @@ extern const mp_obj_module_t mp_module_sys; extern const mp_obj_module_t mp_module_errno; extern const mp_obj_module_t mp_module_uctypes; extern const mp_obj_module_t mp_module_machine; +extern const mp_obj_module_t mp_module_math; extern const char MICROPY_PY_BUILTINS_HELP_TEXT[]; diff --git a/py/emitcommon.c b/py/emitcommon.c index a9eb6e2021f..1f701db80a0 100644 --- a/py/emitcommon.c +++ b/py/emitcommon.c @@ -25,6 +25,7 @@ */ #include +#include #include "py/emit.h" #include "py/nativeglue.h" @@ -72,7 +73,21 @@ static bool strictly_equal(mp_obj_t a, mp_obj_t b) { } return true; } else { - return mp_obj_equal(a, b); + if (!mp_obj_equal(a, b)) { + return false; + } + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_COMP_CONST_FLOAT + if (a_type == &mp_type_float) { + mp_float_t a_val = mp_obj_float_get(a); + if (a_val == (mp_float_t)0.0) { + // Although 0.0 == -0.0, they are not strictly_equal and + // must be stored as two different constants in .mpy files + mp_float_t b_val = mp_obj_float_get(b); + return signbit(a_val) == signbit(b_val); + } + } + #endif + return true; } } diff --git a/py/mpconfig.h b/py/mpconfig.h index caa63fef3f6..ed5ccccb6a5 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -490,6 +490,13 @@ #define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif +// Whether to enable float constant folding like 1.2+3.4 (when MICROPY_COMP_CONST_FOLDING is also enabled) +// and constant optimisation like id = const(1.2) (when MICROPY_COMP_CONST is also enabled) +// and constant lookup like math.inf (when MICROPY_COMP_MODULE_CONST is also enabled) +#ifndef MICROPY_COMP_CONST_FLOAT +#define MICROPY_COMP_CONST_FLOAT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + // Whether to enable optimisation of: a, b = c, d // Costs 124 bytes (Thumb2) #ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN diff --git a/py/parse.c b/py/parse.c index db89fb58450..91eea3e3689 100644 --- a/py/parse.c +++ b/py/parse.c @@ -336,18 +336,34 @@ static uint8_t peek_rule(parser_t *parser, size_t n) { } #endif -bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) { +#if MICROPY_COMP_CONST_FOLDING || MICROPY_EMIT_INLINE_ASM +static bool mp_parse_node_get_number_maybe(mp_parse_node_t pn, mp_obj_t *o) { if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { *o = MP_OBJ_NEW_SMALL_INT(MP_PARSE_NODE_LEAF_SMALL_INT(pn)); return true; } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; *o = mp_parse_node_extract_const_object(pns); - return mp_obj_is_int(*o); + return mp_obj_is_int(*o) + #if MICROPY_COMP_CONST_FLOAT + || mp_obj_is_float(*o) + #endif + ; } else { return false; } } +#endif + +#if MICROPY_EMIT_INLINE_ASM +bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) { + return mp_parse_node_get_number_maybe(pn, o) + #if MICROPY_COMP_CONST_FLOAT + && mp_obj_is_int(*o) + #endif + ; +} +#endif #if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST static bool mp_parse_node_is_const(mp_parse_node_t pn) { @@ -642,12 +658,32 @@ static const mp_rom_map_elem_t mp_constants_table[] = { #if MICROPY_PY_UCTYPES { MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) }, #endif + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH && MICROPY_COMP_CONST_FLOAT + { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) }, + #endif // Extra constants as defined by a port MICROPY_PORT_CONSTANTS }; static MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table); #endif +static bool binary_op_maybe(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs, mp_obj_t *res) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t tmp = mp_binary_op(op, lhs, rhs); + #if MICROPY_PY_BUILTINS_COMPLEX + if (mp_obj_is_type(tmp, &mp_type_complex)) { + return false; + } + #endif + *res = tmp; + nlr_pop(); + return true; + } else { + return false; + } +} + static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) { if (rule_id == RULE_or_test || rule_id == RULE_and_test) { @@ -706,7 +742,7 @@ static bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu } static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { - // this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4 + // this code does folding of arbitrary numeric expressions, eg 1 + 2 * 3 + 4 // it does not do partial folding, eg 1 + 2 + x -> 3 + x mp_obj_t arg0; @@ -716,7 +752,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { || rule_id == RULE_power) { // folding for binary ops: | ^ & ** mp_parse_node_t pn = peek_result(parser, num_args - 1); - if (!mp_parse_node_get_int_maybe(pn, &arg0)) { + if (!mp_parse_node_get_number_maybe(pn, &arg0)) { return false; } mp_binary_op_t op; @@ -732,58 +768,61 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { for (ssize_t i = num_args - 2; i >= 0; --i) { pn = peek_result(parser, i); mp_obj_t arg1; - if (!mp_parse_node_get_int_maybe(pn, &arg1)) { + if (!mp_parse_node_get_number_maybe(pn, &arg1)) { return false; } + #if !MICROPY_COMP_CONST_FLOAT if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) { // ** can't have negative rhs return false; } - arg0 = mp_binary_op(op, arg0, arg1); + #endif + if (!binary_op_maybe(op, arg0, arg1, &arg0)) { + return false; + } } } else if (rule_id == RULE_shift_expr || rule_id == RULE_arith_expr || rule_id == RULE_term) { // folding for binary ops: << >> + - * @ / % // mp_parse_node_t pn = peek_result(parser, num_args - 1); - if (!mp_parse_node_get_int_maybe(pn, &arg0)) { + if (!mp_parse_node_get_number_maybe(pn, &arg0)) { return false; } for (ssize_t i = num_args - 2; i >= 1; i -= 2) { pn = peek_result(parser, i - 1); mp_obj_t arg1; - if (!mp_parse_node_get_int_maybe(pn, &arg1)) { + if (!mp_parse_node_get_number_maybe(pn, &arg1)) { return false; } mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, i)); - if (tok == MP_TOKEN_OP_AT || tok == MP_TOKEN_OP_SLASH) { - // Can't fold @ or / + if (tok == MP_TOKEN_OP_AT) { + // Can't fold @ + return false; + } + #if !MICROPY_COMP_CONST_FLOAT + if (tok == MP_TOKEN_OP_SLASH) { + // Can't fold / return false; } + #endif mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS); - int rhs_sign = mp_obj_int_sign(arg1); - if (op <= MP_BINARY_OP_RSHIFT) { - // << and >> can't have negative rhs - if (rhs_sign < 0) { - return false; - } - } else if (op >= MP_BINARY_OP_FLOOR_DIVIDE) { - // % and // can't have zero rhs - if (rhs_sign == 0) { - return false; - } + if (!binary_op_maybe(op, arg0, arg1, &arg0)) { + return false; } - arg0 = mp_binary_op(op, arg0, arg1); } } else if (rule_id == RULE_factor_2) { // folding for unary ops: + - ~ mp_parse_node_t pn = peek_result(parser, 0); - if (!mp_parse_node_get_int_maybe(pn, &arg0)) { + if (!mp_parse_node_get_number_maybe(pn, &arg0)) { return false; } mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, 1)); mp_unary_op_t op; if (tok == MP_TOKEN_OP_TILDE) { + if (!mp_obj_is_int(arg0)) { + return false; + } op = MP_UNARY_OP_INVERT; } else { assert(tok == MP_TOKEN_OP_PLUS || tok == MP_TOKEN_OP_MINUS); // should be @@ -855,7 +894,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { return false; } // id1.id2 - // look it up in constant table, see if it can be replaced with an integer + // look it up in constant table, see if it can be replaced with an integer or a float mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn1; assert(MP_PARSE_NODE_IS_ID(pns1->nodes[0])); qstr q_base = MP_PARSE_NODE_LEAF_ARG(pn0); @@ -866,7 +905,7 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { } mp_obj_t dest[2]; mp_load_method_maybe(elem->value, q_attr, dest); - if (!(dest[0] != MP_OBJ_NULL && mp_obj_is_int(dest[0]) && dest[1] == MP_OBJ_NULL)) { + if (!(dest[0] != MP_OBJ_NULL && (mp_obj_is_int(dest[0]) || mp_obj_is_float(dest[0])) && dest[1] == MP_OBJ_NULL)) { return false; } arg0 = dest[0]; diff --git a/tests/float/float_parse_doubleprec.py b/tests/float/float_parse_doubleprec.py index 81fcadcee88..c1b0b4823b0 100644 --- a/tests/float/float_parse_doubleprec.py +++ b/tests/float/float_parse_doubleprec.py @@ -19,3 +19,9 @@ print(float("1.00000000000000000000e-307")) print(float("10.0000000000000000000e-308")) print(float("100.000000000000000000e-309")) + +# ensure repr() adds an extra digit when needed for accurate parsing +print(float(repr(float("2.0") ** 100)) == float("2.0") ** 100) + +# ensure repr does not add meaningless extra digits (1.234999999999) +print(repr(1.2345)) diff --git a/tests/micropython/const_error.py b/tests/micropython/const_error.py index d35be530a7c..950360e4dc7 100644 --- a/tests/micropython/const_error.py +++ b/tests/micropython/const_error.py @@ -18,8 +18,6 @@ def test_syntax(code): # these operations are not supported within const test_syntax("A = const(1 @ 2)") -test_syntax("A = const(1 / 2)") -test_syntax("A = const(1 ** -2)") test_syntax("A = const(1 << -2)") test_syntax("A = const(1 >> -2)") test_syntax("A = const(1 % 0)") diff --git a/tests/micropython/const_error.py.exp b/tests/micropython/const_error.py.exp index 3edc3efe9c3..bef69eb32ea 100644 --- a/tests/micropython/const_error.py.exp +++ b/tests/micropython/const_error.py.exp @@ -5,5 +5,3 @@ SyntaxError SyntaxError SyntaxError SyntaxError -SyntaxError -SyntaxError diff --git a/tests/micropython/const_float.py b/tests/micropython/const_float.py new file mode 100644 index 00000000000..c3a0df0276b --- /dev/null +++ b/tests/micropython/const_float.py @@ -0,0 +1,23 @@ +# test constant optimisation, with consts that are floats + +from micropython import const + +# check we can make consts from floats +F1 = const(2.5) +F2 = const(-0.3) +print(type(F1), F1) +print(type(F2), F2) + +# check arithmetic with floats +F3 = const(F1 + F2) +F4 = const(F1**2) +print(F3, F4) + +# check int operations with float results +F5 = const(1 / 2) +F6 = const(2**-2) +print(F5, F6) + +# note: we also test float expression folding when +# we're compiling test cases in tests/float, as +# many expressions are resolved at compile time. diff --git a/tests/micropython/const_float.py.exp b/tests/micropython/const_float.py.exp new file mode 100644 index 00000000000..17a86a6d936 --- /dev/null +++ b/tests/micropython/const_float.py.exp @@ -0,0 +1,4 @@ + 2.5 + -0.3 +2.2 6.25 +0.5 0.25 diff --git a/tests/micropython/const_math.py b/tests/micropython/const_math.py new file mode 100644 index 00000000000..7ee5edc6d32 --- /dev/null +++ b/tests/micropython/const_math.py @@ -0,0 +1,18 @@ +# Test expressions based on math module constants +try: + import math +except ImportError: + print("SKIP") + raise SystemExit + +from micropython import const + +# check that we can make consts from math constants +# (skip if the target has MICROPY_COMP_MODULE_CONST disabled) +try: + exec("two_pi = const(2.0 * math.pi)") +except SyntaxError: + print("SKIP") + raise SystemExit + +print(math.cos(two_pi)) diff --git a/tests/micropython/const_math.py.exp b/tests/micropython/const_math.py.exp new file mode 100644 index 00000000000..d3827e75a5c --- /dev/null +++ b/tests/micropython/const_math.py.exp @@ -0,0 +1 @@ +1.0 From f39434e9fb17405754277dd62dfea788a42d5afb Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Sun, 27 Jul 2025 22:16:24 +0100 Subject: [PATCH 1028/2098] py/asmthumb: Don't corrupt base register in large offset store. asm_thumb_store_reg_reg_offset() modifies the base register when storing with a large offset which triggers the generic path. If a variable lives in that register, this corrupts it. Fix this by saving the base register on the stack before modifying it. Signed-off-by: Chris Webb --- py/asmthumb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/asmthumb.c b/py/asmthumb.c index 18c3db9e4e2..93860d2fdc1 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -492,8 +492,10 @@ void asm_thumb_store_reg_reg_offset(asm_thumb_t *as, uint reg_src, uint reg_base asm_thumb_op32(as, (OP_LDR_STR_W_HI(operation_size, reg_base) | OP_STR_W), OP_LDR_STR_W_LO(reg_src, (offset << operation_size))); } else { // Must use the generic sequence + asm_thumb_op16(as, OP_PUSH_RLIST(1 << reg_base)); asm_thumb_add_reg_reg_offset(as, reg_base, reg_base, offset - 31, operation_size); asm_thumb_op16(as, ((OP_LDR_STR_TABLE[operation_size] | OP_STR) << 11) | (31 << 6) | (reg_base << 3) | reg_src); + asm_thumb_op16(as, OP_POP_RLIST(1 << reg_base)); } } From 953da2080eb9115536101627c7406727c5d44f8b Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Sun, 27 Jul 2025 22:35:02 +0100 Subject: [PATCH 1029/2098] tests/micropython: Test that viper offset stores don't clobber base reg. When running the viper boundary tests, assert that the offset stores don't clobber the base register, which is saved and temporarily modified on some architectures. Signed-off-by: Chris Webb --- tests/micropython/viper_ptr16_store_boundary_intbig.py | 4 ++++ tests/micropython/viper_ptr32_store_boundary_intbig.py | 4 ++++ tests/micropython/viper_ptr8_store_boundary_intbig.py | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/tests/micropython/viper_ptr16_store_boundary_intbig.py b/tests/micropython/viper_ptr16_store_boundary_intbig.py index 1694c61ac0a..2193eddae13 100644 --- a/tests/micropython/viper_ptr16_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr16_store_boundary_intbig.py @@ -3,7 +3,9 @@ SET_TEMPLATE = """ @micropython.viper def set{off}(dest: ptr16): + saved = dest dest[{off}] = {val} + assert int(saved) == int(dest) set{off}(buffer) print(hex(get_index(buffer, {off}))) """ @@ -15,7 +17,9 @@ def set{off}(dest: ptr16): @micropython.viper def set_index(dest: ptr16, i: int, val: uint): + saved = dest dest[i] = val + assert int(saved) == int(dest) def get_index(src, i): diff --git a/tests/micropython/viper_ptr32_store_boundary_intbig.py b/tests/micropython/viper_ptr32_store_boundary_intbig.py index 5109abb9dca..b44f31b00af 100644 --- a/tests/micropython/viper_ptr32_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr32_store_boundary_intbig.py @@ -3,7 +3,9 @@ SET_TEMPLATE = """ @micropython.viper def set{off}(dest: ptr32): + saved = dest dest[{off}] = {val} + assert int(saved) == int(dest) set{off}(buffer) print(hex(get_index(buffer, {off}))) """ @@ -15,7 +17,9 @@ def set{off}(dest: ptr32): @micropython.viper def set_index(dest: ptr32, i: int, val: uint): + saved = dest dest[i] = val + assert int(saved) == int(dest) def get_index(src, i): diff --git a/tests/micropython/viper_ptr8_store_boundary_intbig.py b/tests/micropython/viper_ptr8_store_boundary_intbig.py index e1fe6dcae32..d22a0627433 100644 --- a/tests/micropython/viper_ptr8_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr8_store_boundary_intbig.py @@ -3,7 +3,9 @@ SET_TEMPLATE = """ @micropython.viper def set{off}(dest: ptr8): + saved = dest dest[{off}] = {val} + assert int(saved) == int(dest) set{off}(buffer) print(hex(get_index(buffer, {off}))) """ @@ -15,7 +17,9 @@ def set{off}(dest: ptr8): @micropython.viper def set_index(dest: ptr8, i: int, val: uint): + saved = dest dest[i] = val + assert int(dest) == int(saved) def get_index(src: ptr8, i: int): From 4ba626ab5a6c47588d25c40ac1dc20d59e33760d Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 28 Jul 2025 16:30:04 +0200 Subject: [PATCH 1030/2098] tools/mpremote: Fix errno.ENOTBLK attribute error on Windows. Not all errors defined in stdlib errno are available on Windows. Specifically, errno.ENOTBLK is not. Fixes issue #17773. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/mp_errno.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/mp_errno.py b/tools/mpremote/mpremote/mp_errno.py index 37cb1e0cb9b..e2554ef1db6 100644 --- a/tools/mpremote/mpremote/mp_errno.py +++ b/tools/mpremote/mpremote/mp_errno.py @@ -1,4 +1,5 @@ import errno +import platform # This table maps numeric values defined by `py/mperrno.h` to host errno code. MP_ERRNO_TABLE = { @@ -16,7 +17,6 @@ 12: errno.ENOMEM, 13: errno.EACCES, 14: errno.EFAULT, - 15: errno.ENOTBLK, 16: errno.EBUSY, 17: errno.EEXIST, 18: errno.EXDEV, @@ -51,3 +51,5 @@ 115: errno.EINPROGRESS, 125: errno.ECANCELED, } +if platform.system() != "Windows": + MP_ERRNO_TABLE[15] = errno.ENOTBLK From dea949e860c0adc615171d25c7df79b59639f8ed Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 28 Jul 2025 16:52:11 +0200 Subject: [PATCH 1031/2098] tools/mpremote: Update ESPxxx detection for USB-CDC ports. Detection of ESP-XX devices was based on just the names of the USB driver name, and did not account for the switch of the newer ESP-xx devices to USB-CDC. On Windows this caused unwanted/unneeded resets as the DTR/RTS signals are also used for automatic device reset over USB-CDC. See https://github.com/micropython/micropython/issues/9659#issuecomment-3124704572 This commit uses the Espressif registered VID 0x303A to detect USB-CDC ports, to enable the same DTR/RTS settings as used on the UART-USB connection. Also improved the robustness of the code using `getattr()`. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/transport_serial.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index 53fc48553b1..daeff02b594 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -40,6 +40,8 @@ from .console import VT_ENABLED from .transport import TransportError, TransportExecError, Transport +VID_ESPRESSIF = 0x303A # Espressif Incorporated + class SerialTransport(Transport): fs_hook_mount = "/remote" # MUST match the mount point in fs_hook_code @@ -71,7 +73,10 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None self.serial = serial.Serial(**serial_kwargs) self.serial.port = device portinfo = list(serial.tools.list_ports.grep(device)) # type: ignore - if portinfo and portinfo[0].manufacturer != "Microsoft": + if portinfo and ( + getattr(portinfo[0], "vid", 0) == VID_ESPRESSIF + or getattr(portinfo[0], "manufacturer", "") != "Microsoft" + ): # ESP8266/ESP32 boards use RTS/CTS for flashing and boot mode selection. # DTR False: to avoid using the reset button will hang the MCU in bootloader mode # RTS False: to prevent pulses on rts on serial.close() that would POWERON_RESET an ESPxx From 41987c6cf43c18ff387e88d227469bf8b8e29b55 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Jul 2025 17:19:09 +1000 Subject: [PATCH 1032/2098] rp2/rp2_pio: Configure jmp_pin for PIO use if it's isolation is set. Signed-off-by: Damien George --- ports/rp2/rp2_pio.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index d936553b555..8fc7c0e7867 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -683,8 +683,10 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel } // Configure jmp pin, if needed. + int jmp_pin = -1; if (args[ARG_jmp_pin].u_obj != mp_const_none) { - sm_config_set_jmp_pin(&config, mp_hal_get_pin_obj(args[ARG_jmp_pin].u_obj)); + jmp_pin = mp_hal_get_pin_obj(args[ARG_jmp_pin].u_obj); + sm_config_set_jmp_pin(&config, jmp_pin); } // Configure sideset pin, if needed. @@ -716,6 +718,18 @@ static mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel if (set_config.base >= 0) { asm_pio_init_gpio(self->pio, self->sm, &set_config); } + #if !PICO_RP2040 + if (jmp_pin >= 0) { + // On RP2350 pins by default have their isolation enabled. This means they will + // not work as input to a PIO without further configuration. That's different to + // RP2040 where pins can work as PIO input from a reset. To make RP2350 have + // similar behaviour as RP2040, configure the jmp pin for PIO use if it's isolation + // is enabled (which means it's probably unconfigured from reset). + if (pads_bank0_hw->io[jmp_pin] & PADS_BANK0_GPIO0_ISO_BITS) { + pio_gpio_init(self->pio, jmp_pin); + } + } + #endif if (sideset_config.base >= 0) { asm_pio_init_gpio(self->pio, self->sm, &sideset_config); } From a9a606bf5d518375efb2b836a07df7d95dbd7b56 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Jul 2025 17:25:35 +1000 Subject: [PATCH 1033/2098] docs/library/rp2.StateMachine: Add a note about PIO in and jmp pins. Signed-off-by: Damien George --- docs/library/rp2.StateMachine.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst index 1cb87e90b6e..4984be0b218 100644 --- a/docs/library/rp2.StateMachine.rst +++ b/docs/library/rp2.StateMachine.rst @@ -58,6 +58,11 @@ Methods - *pull_thresh* is the threshold in bits before auto-pull or conditional re-pulling is triggered. + Note: pins used for *in_base* need to be configured manually for input (or + otherwise) so that the PIO can see the desired signal (they could be input + pins, output pins, or connected to a different peripheral). The *jmp_pin* + can also be configured manually, but by default will be an input pin. + .. method:: StateMachine.active([value]) Gets or sets whether the state machine is currently running. From c9b52b2b7f164745eda7210ccf6640fce3737f59 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 31 Jul 2025 15:37:46 -0600 Subject: [PATCH 1034/2098] rp2/CMakeLists.txt: Fix flash size check logic. Follow up to 6bfb83e30aa28e7bbfb0f77f378da05b32574f3d, if the variable `PICO_FLASH_SIZE_BYTES` is not a numeric constant, eg "(2 * 1024 * 1024)", then it won't pass the GREATER check. So change the if logic to just test if it's defined. Signed-off-by: Dryw Wade --- ports/rp2/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 49ba8d77d63..120d07bcce1 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -528,7 +528,7 @@ target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--wrap=runtime_init_clocks ) -if(PICO_FLASH_SIZE_BYTES GREATER 0) +if(DEFINED PICO_FLASH_SIZE_BYTES) target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--defsym=__micropy_flash_size__=${PICO_FLASH_SIZE_BYTES} ) From 769453c7504ebf71d65a57767fbeea41f2de63f4 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 17 Jun 2025 23:17:43 -0400 Subject: [PATCH 1035/2098] rp2/rp2_pio: Fix use of PIO2 in prog data structure. The RP2350 PIO2 State Machines (8, 9, 10, 11) did not work. The data structure used to pass the PIO arguments was missing an entry for PIO2, thus causing the PIO2 instances to write wrong data to wrong locations. Fixes issue #17509. Signed-off-by: Matt Westveld --- ports/rp2/modules/rp2.py | 15 ++++++++------- ports/rp2/rp2_pio.c | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ports/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py index 6068926036b..442a802b3e9 100644 --- a/ports/rp2/modules/rp2.py +++ b/ports/rp2/modules/rp2.py @@ -7,12 +7,13 @@ _PROG_DATA = const(0) _PROG_OFFSET_PIO0 = const(1) _PROG_OFFSET_PIO1 = const(2) -_PROG_EXECCTRL = const(3) -_PROG_SHIFTCTRL = const(4) -_PROG_OUT_PINS = const(5) -_PROG_SET_PINS = const(6) -_PROG_SIDESET_PINS = const(7) -_PROG_MAX_FIELDS = const(8) +_PROG_OFFSET_PIO2 = const(3) +_PROG_EXECCTRL = const(4) +_PROG_SHIFTCTRL = const(5) +_PROG_OUT_PINS = const(6) +_PROG_SET_PINS = const(7) +_PROG_SIDESET_PINS = const(8) +_PROG_MAX_FIELDS = const(9) class PIOASMError(Exception): @@ -50,7 +51,7 @@ def __init__( | autopull << 17 | autopush << 16 ) - self.prog = [array("H"), -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init] + self.prog = [array("H"), -1, -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init] self.wrap_used = False diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 8fc7c0e7867..611e74a1587 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -212,6 +212,7 @@ enum { PROG_DATA, PROG_OFFSET_PIO0, PROG_OFFSET_PIO1, + PROG_OFFSET_PIO2, PROG_EXECCTRL, PROG_SHIFTCTRL, PROG_OUT_PINS, From ab7c5a1733b394af43035bdcf844b91fd7756dd9 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Thu, 10 Jul 2025 01:50:55 +0900 Subject: [PATCH 1036/2098] docs/library/btree: Fix method links to explicitly specify class. So they don't clash with other potential references. Signed-off-by: Koudai Aono --- docs/library/btree.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/library/btree.rst b/docs/library/btree.rst index 9d1dcf1110d..57466955111 100644 --- a/docs/library/btree.rst +++ b/docs/library/btree.rst @@ -151,10 +151,10 @@ Constants .. data:: INCL - A flag for `keys()`, `values()`, `items()` methods to specify that + A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that scanning should be inclusive of the end key. .. data:: DESC - A flag for `keys()`, `values()`, `items()` methods to specify that + A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that scanning should be in descending direction of keys. From 1b578fe2c04402b1fe85b49fb5a89c23ae961f89 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 12:21:57 +1000 Subject: [PATCH 1037/2098] extmod/machine_i2c_target: Add new machine.I2CTarget class. This commit implements a generic I2C target/peripheral/"slave" device, called `machine.I2CTarget`. It can work in two separate modes: - A general device with interrupts/events/callbacks for low-level I2C operations like address match, read request and stop. - A memory device that allows reading/writing a specific region of memory (or "registers") on the target I2C device. To make a memory device is very simple: from machine import I2CTarget mem = bytearray(8) i2c = I2CTarget(addr=67, mem=mem) That's all that's needed to start the I2C target. From then on it will respond to any I2C controller on the bus, allowing reads and writes to the mem bytearray. It's also possible to register to receive events. For example to be notified when the memory is read/written: from machine import I2CTarget def irq_handler(i2c_target): flags = i2c_target.irq().flags() if flags & I2CTarget.IRQ_END_READ: print("controller read target at addr", i2c_target.memaddr) if flags & I2CTarget.IRQ_END_WRITE: print("controller wrote target at addr", i2c_target.memaddr) mem = bytearray(8) i2c = I2CTarget(addr=67, mem=mem) i2c.irq(irq_handler) Instead of a memory device, an arbitrary I2C device can be implemented using all the events (see docs). This is based on the discussion in #3935. Signed-off-by: Damien George --- extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/machine_i2c_target.c | 424 ++++++++++++++++++++++++++++++++++++ extmod/modmachine.c | 3 + extmod/modmachine.h | 6 + 5 files changed, 435 insertions(+) create mode 100644 extmod/machine_i2c_target.c diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 7cacd0a4332..9ca869582e0 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -11,6 +11,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/machine_adc_block.c ${MICROPY_EXTMOD_DIR}/machine_bitstream.c ${MICROPY_EXTMOD_DIR}/machine_i2c.c + ${MICROPY_EXTMOD_DIR}/machine_i2c_target.c ${MICROPY_EXTMOD_DIR}/machine_i2s.c ${MICROPY_EXTMOD_DIR}/machine_mem.c ${MICROPY_EXTMOD_DIR}/machine_pulse.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 997dd3ba98e..37151ad120e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -6,6 +6,7 @@ SRC_EXTMOD_C += \ extmod/machine_adc_block.c \ extmod/machine_bitstream.c \ extmod/machine_i2c.c \ + extmod/machine_i2c_target.c \ extmod/machine_i2s.c \ extmod/machine_mem.c \ extmod/machine_pinbase.c \ diff --git a/extmod/machine_i2c_target.c b/extmod/machine_i2c_target.c new file mode 100644 index 00000000000..f63be183c86 --- /dev/null +++ b/extmod/machine_i2c_target.c @@ -0,0 +1,424 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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" + +#if MICROPY_PY_MACHINE_I2C_TARGET + +#include "extmod/modmachine.h" +#include "shared/runtime/mpirq.h" + +enum { + // Events exposed to Python. + I2C_TARGET_IRQ_ADDR_MATCH_READ = 1 << 0, + I2C_TARGET_IRQ_ADDR_MATCH_WRITE = 1 << 1, + I2C_TARGET_IRQ_READ_REQ = 1 << 2, + I2C_TARGET_IRQ_WRITE_REQ = 1 << 3, + I2C_TARGET_IRQ_END_READ = 1 << 4, + I2C_TARGET_IRQ_END_WRITE = 1 << 5, + + // Internal event, not exposed to Python. + I2C_TARGET_IRQ_MEM_ADDR_MATCH = 1 << 6, +}; + +// Define the IRQs that require a hard interrupt. +#define I2C_TARGET_IRQ_ALL_HARD ( \ + I2C_TARGET_IRQ_ADDR_MATCH_READ \ + | I2C_TARGET_IRQ_ADDR_MATCH_WRITE \ + | I2C_TARGET_IRQ_READ_REQ \ + | I2C_TARGET_IRQ_WRITE_REQ \ + ) + +enum { + STATE_INACTIVE, + STATE_IDLE, + STATE_ADDR_MATCH_READ, + STATE_ADDR_MATCH_WRITE, + STATE_MEM_ADDR_SELECT, + STATE_READING, + STATE_WRITING, +}; + +typedef struct _machine_i2c_target_data_t { + uint8_t state; + uint8_t mem_addr_count; + uint8_t mem_addrsize; + uint32_t mem_addr_last; + uint32_t mem_addr; + uint32_t mem_len; + uint8_t *mem_buf; +} machine_i2c_target_data_t; + +typedef struct _machine_i2c_target_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; +} machine_i2c_target_irq_obj_t; + +// The port must provide implementations of these low-level I2C target functions. + +static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq); + +// Read up to N bytes, and return the number of bytes read. +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf); + +// Write (or buffer) N bytes, and return the number of bytes written/buffered. +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf); + +// Configure the given events to trigger an interrupt. +static void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger); + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self); + +static const mp_irq_methods_t machine_i2c_target_irq_methods; + +static machine_i2c_target_data_t machine_i2c_target_data[MICROPY_PY_MACHINE_I2C_TARGET_MAX]; + +// Needed to retain a root pointer to the memory object. +MP_REGISTER_ROOT_POINTER(mp_obj_t machine_i2c_target_mem_obj[MICROPY_PY_MACHINE_I2C_TARGET_MAX]); + +// Needed to retain a root pointer to the IRQ object. +MP_REGISTER_ROOT_POINTER(void *machine_i2c_target_irq_obj[MICROPY_PY_MACHINE_I2C_TARGET_MAX]); + +static bool handle_event(machine_i2c_target_data_t *data, unsigned int trigger) { + unsigned int id = data - &machine_i2c_target_data[0]; + if (trigger & I2C_TARGET_IRQ_MEM_ADDR_MATCH) { + data->mem_addr_last = data->mem_addr; + } + machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[id]); + if (irq != NULL && (trigger & irq->trigger)) { + irq->flags = trigger & irq->trigger; + mp_machine_i2c_target_event_callback(irq); + return true; // irq handled + } + return false; // irq not handled +} + +static void machine_i2c_target_data_init(machine_i2c_target_data_t *data, mp_obj_t mem_obj, mp_int_t mem_addrsize) { + data->state = STATE_IDLE; + data->mem_addr_count = 0; + data->mem_addrsize = 0; + data->mem_addr_last = 0; + data->mem_addr = 0; + data->mem_len = 0; + data->mem_buf = NULL; + if (mem_obj != mp_const_none) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(mem_obj, &bufinfo, MP_BUFFER_RW); + if (mem_addrsize < 0 || mem_addrsize > 32 || mem_addrsize % 8 != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("mem_addrsize must be 0, 8, 16, 24 or 32")); + } + data->mem_addrsize = mem_addrsize / 8; + data->mem_len = bufinfo.len; + data->mem_buf = bufinfo.buf; + } +} + +static void machine_i2c_target_data_reset_helper(machine_i2c_target_data_t *data) { + if (data->state == STATE_READING) { + handle_event(data, I2C_TARGET_IRQ_END_READ); + } else if (data->state == STATE_ADDR_MATCH_WRITE || data->state == STATE_WRITING) { + handle_event(data, I2C_TARGET_IRQ_END_WRITE); + } + data->state = STATE_IDLE; +} + +static void machine_i2c_target_data_addr_match(machine_i2c_target_data_t *data, bool read) { + machine_i2c_target_data_reset_helper(data); + if (read) { + handle_event(data, I2C_TARGET_IRQ_ADDR_MATCH_READ); + data->state = STATE_ADDR_MATCH_READ; + } else { + handle_event(data, I2C_TARGET_IRQ_ADDR_MATCH_WRITE); + data->state = STATE_ADDR_MATCH_WRITE; + } +} + +static void machine_i2c_target_data_read_request(machine_i2c_target_obj_t *self, machine_i2c_target_data_t *data) { + // Let the user handle the read request. + bool event_handled = handle_event(data, I2C_TARGET_IRQ_READ_REQ); + if (data->mem_buf == NULL) { + data->state = STATE_READING; + if (!event_handled) { + // No data source, just write out a zero. + uint8_t val = 0; + mp_machine_i2c_target_write_bytes(self, 1, &val); + } + } else { + // Have a buffer. + if (data->state == STATE_MEM_ADDR_SELECT) { + // Got a short memory address. + data->mem_addr %= data->mem_len; + handle_event(data, I2C_TARGET_IRQ_MEM_ADDR_MATCH); + } + if (data->state != STATE_READING) { + data->state = STATE_READING; + } + uint8_t val = data->mem_buf[data->mem_addr++]; + if (data->mem_addr >= data->mem_len) { + data->mem_addr = 0; + } + mp_machine_i2c_target_write_bytes(self, 1, &val); + } +} + +static void machine_i2c_target_data_write_request(machine_i2c_target_obj_t *self, machine_i2c_target_data_t *data) { + // Let the user handle the write request. + bool event_handled = handle_event(data, I2C_TARGET_IRQ_WRITE_REQ); + if (data->mem_buf == NULL) { + data->state = STATE_WRITING; + if (!event_handled) { + // No data sink, just read and discard the incoming byte. + uint8_t buf = 0; + mp_machine_i2c_target_read_bytes(self, 1, &buf); + } + } else { + // Have a buffer. + uint8_t buf[4] = {0}; + size_t n = mp_machine_i2c_target_read_bytes(self, sizeof(buf), &buf[0]); + for (size_t i = 0; i < n; ++i) { + uint8_t val = buf[i]; + if (data->state == STATE_ADDR_MATCH_WRITE) { + data->state = STATE_MEM_ADDR_SELECT; + data->mem_addr = 0; + data->mem_addr_count = data->mem_addrsize; + } + if (data->state == STATE_MEM_ADDR_SELECT && data->mem_addr_count > 0) { + data->mem_addr = data->mem_addr << 8 | val; + --data->mem_addr_count; + if (data->mem_addr_count == 0) { + data->mem_addr %= data->mem_len; + handle_event(data, I2C_TARGET_IRQ_MEM_ADDR_MATCH); + } + } else { + if (data->state == STATE_MEM_ADDR_SELECT) { + data->state = STATE_WRITING; + } + data->mem_buf[data->mem_addr++] = val; + if (data->mem_addr >= data->mem_len) { + data->mem_addr = 0; + } + } + } + } +} + +static inline void machine_i2c_target_data_restart_or_stop(machine_i2c_target_data_t *data) { + machine_i2c_target_data_reset_helper(data); +} + +static inline void machine_i2c_target_data_stop(machine_i2c_target_data_t *data) { + machine_i2c_target_data_reset_helper(data); +} + +// The port provides implementations of its bindings in this file. +#include MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE + +static void machine_i2c_target_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t index = mp_machine_i2c_target_get_index(self); + machine_i2c_target_data_t *data = &machine_i2c_target_data[index]; + if (dest[0] == MP_OBJ_NULL) { + // Load attribute. + if (attr == MP_QSTR_memaddr) { + dest[0] = mp_obj_new_int(data->mem_addr_last); + } else { + // Continue lookup in locals_dict. + dest[1] = MP_OBJ_SENTINEL; + } + } +} + +// I2CTarget.deinit() +static mp_obj_t machine_i2c_target_deinit(mp_obj_t self_in) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t index = mp_machine_i2c_target_get_index(self); + if (machine_i2c_target_data[index].state != STATE_INACTIVE) { + machine_i2c_target_data[index].state = STATE_INACTIVE; + mp_machine_i2c_target_deinit(self); + MP_STATE_PORT(machine_i2c_target_mem_obj[index]) = MP_OBJ_NULL; + MP_STATE_PORT(machine_i2c_target_irq_obj[index]) = NULL; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_target_deinit_obj, machine_i2c_target_deinit); + +// I2CTarget.readinto(buf) +static mp_obj_t machine_i2c_target_readinto(mp_obj_t self_in, mp_obj_t buf_in) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + return MP_OBJ_NEW_SMALL_INT(mp_machine_i2c_target_read_bytes(self, bufinfo.len, bufinfo.buf)); +} +static MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_target_readinto_obj, machine_i2c_target_readinto); + +// I2CTarget.write(data) +static mp_obj_t machine_i2c_target_write(mp_obj_t self_in, mp_obj_t data_in) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ); + return MP_OBJ_NEW_SMALL_INT(mp_machine_i2c_target_write_bytes(self, bufinfo.len, bufinfo.buf)); +} +static MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_target_write_obj, machine_i2c_target_write); + +static machine_i2c_target_irq_obj_t *machine_i2c_target_get_irq(machine_i2c_target_obj_t *self) { + // Get the IRQ object. + size_t index = mp_machine_i2c_target_get_index(self); + machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[index]); + + // Allocate the IRQ object if it doesn't already exist. + if (irq == NULL) { + irq = m_new_obj(machine_i2c_target_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&machine_i2c_target_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + MP_STATE_PORT(machine_i2c_target_irq_obj[index]) = irq; + } + return irq; +} + +// I2CTarget.irq(handler=None, trigger=IRQ_END_READ|IRQ_END_WRITE, hard=False) +static mp_obj_t machine_i2c_target_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = I2C_TARGET_IRQ_END_READ | I2C_TARGET_IRQ_END_WRITE} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + machine_i2c_target_irq_obj_t *irq = machine_i2c_target_get_irq(self); + + if (n_args > 1 || kw_args->used != 0) { + // Update IRQ data. + mp_obj_t handler = args[ARG_handler].u_obj; + mp_uint_t trigger = args[ARG_trigger].u_int; + bool hard = args[ARG_hard].u_bool; + + #if MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ + if ((trigger & I2C_TARGET_IRQ_ALL_HARD) && !hard) { + mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ required")); + } + #else + if (hard) { + mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ unsupported")); + } + #endif + + // Disable all IRQs while data is updated. + mp_machine_i2c_target_irq_config(self, 0); + + // Update IRQ data. + irq->base.handler = handler; + irq->base.ishard = hard; + irq->flags = 0; + irq->trigger = trigger; + + // Enable IRQ if a handler is given. + if (handler != mp_const_none && trigger != 0) { + mp_machine_i2c_target_irq_config(self, trigger); + } + } + return MP_OBJ_FROM_PTR(irq); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_target_irq_obj, 1, machine_i2c_target_irq); + +static const mp_rom_map_elem_t machine_i2c_target_locals_dict_table[] = { + #if MICROPY_PY_MACHINE_I2C_TARGET_FINALISER + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_i2c_target_deinit_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2c_target_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&machine_i2c_target_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&machine_i2c_target_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2c_target_irq_obj) }, + + #if MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ + { MP_ROM_QSTR(MP_QSTR_IRQ_ADDR_MATCH_READ), MP_ROM_INT(I2C_TARGET_IRQ_ADDR_MATCH_READ) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_ADDR_MATCH_WRITE), MP_ROM_INT(I2C_TARGET_IRQ_ADDR_MATCH_WRITE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_READ_REQ), MP_ROM_INT(I2C_TARGET_IRQ_READ_REQ) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_WRITE_REQ), MP_ROM_INT(I2C_TARGET_IRQ_WRITE_REQ) }, + #endif + { MP_ROM_QSTR(MP_QSTR_IRQ_END_READ), MP_ROM_INT(I2C_TARGET_IRQ_END_READ) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_END_WRITE), MP_ROM_INT(I2C_TARGET_IRQ_END_WRITE) }, +}; +static MP_DEFINE_CONST_DICT(machine_i2c_target_locals_dict, machine_i2c_target_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_i2c_target_type, + MP_QSTR_I2CTarget, + MP_TYPE_FLAG_NONE, + make_new, mp_machine_i2c_target_make_new, + print, mp_machine_i2c_target_print, + attr, &machine_i2c_target_attr, + locals_dict, &machine_i2c_target_locals_dict + ); + +static mp_uint_t machine_i2c_target_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t index = mp_machine_i2c_target_get_index(self); + machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[index]); + mp_machine_i2c_target_irq_config(self, 0); + irq->flags = 0; + irq->trigger = new_trigger; + mp_machine_i2c_target_irq_config(self, new_trigger); + return 0; +} + +static mp_uint_t machine_i2c_target_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t index = mp_machine_i2c_target_get_index(self); + machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[index]); + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +static const mp_irq_methods_t machine_i2c_target_irq_methods = { + .trigger = machine_i2c_target_irq_trigger, + .info = machine_i2c_target_irq_info, +}; + +#if !MICROPY_PY_MACHINE_I2C_TARGET_FINALISER +void mp_machine_i2c_target_deinit_all(void) { + for (size_t i = 0; i < MICROPY_PY_MACHINE_I2C_TARGET_MAX; ++i) { + if (machine_i2c_target_data[i].state != STATE_INACTIVE) { + machine_i2c_target_deinit(MP_OBJ_FROM_PTR(&machine_i2c_target_obj[i])); + } + } +} +#endif + +#endif // MICROPY_PY_MACHINE_I2C_TARGET diff --git a/extmod/modmachine.c b/extmod/modmachine.c index f2570123e37..28b60683b1e 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -219,6 +219,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, #endif + #if MICROPY_PY_MACHINE_I2C_TARGET + { MP_ROM_QSTR(MP_QSTR_I2CTarget), MP_ROM_PTR(&machine_i2c_target_type) }, + #endif #if MICROPY_PY_MACHINE_I2S { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) }, #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 26010be8e18..ef507aca740 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -129,6 +129,7 @@ // A port must provide these types, but they are otherwise opaque. typedef struct _machine_adc_obj_t machine_adc_obj_t; typedef struct _machine_adc_block_obj_t machine_adc_block_obj_t; +typedef struct _machine_i2c_target_obj_t machine_i2c_target_obj_t; typedef struct _machine_i2s_obj_t machine_i2s_obj_t; typedef struct _machine_pwm_obj_t machine_pwm_obj_t; typedef struct _machine_uart_obj_t machine_uart_obj_t; @@ -203,6 +204,7 @@ extern const machine_mem_obj_t machine_mem32_obj; extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_adc_block_type; extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t machine_i2c_target_type; extern const mp_obj_type_t machine_i2s_type; extern const mp_obj_type_t machine_mem_type; extern const mp_obj_type_t machine_pin_type; @@ -261,6 +263,10 @@ int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n int mp_machine_soft_i2c_transfer(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); #endif +#if MICROPY_PY_MACHINE_I2C_TARGET +void mp_machine_i2c_target_deinit_all(void); +#endif + #if MICROPY_PY_MACHINE_SPI mp_obj_t mp_machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_machine_spi_read_obj); From 9b1778fc77af2f1631143adc0f4f760e184260ac Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 11:06:08 +1000 Subject: [PATCH 1038/2098] stm32/i2c: Move I2C IRQ handlers from stm32_it.c to i2c.c. And add MP_STATIC_ASSERT to statically check that the IRQ names are correct on the MCU that it's compiled for. Signed-off-by: Damien George --- ports/stm32/i2c.c | 68 ++++++++++++++++++++++++++++++++++++++++++ ports/stm32/stm32_it.c | 61 ------------------------------------- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index a1fde7e6ba1..883734bb1af 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -590,4 +590,72 @@ int i2c_find_peripheral(mp_obj_t id) { return i2c_id; } +#if MICROPY_PY_PYB_LEGACY + +#if defined(MICROPY_HW_I2C1_SCL) +void I2C1_EV_IRQHandler(void) { + MP_STATIC_ASSERT(I2C1_EV_IRQn > 0); + IRQ_ENTER(I2C1_EV_IRQn); + i2c_ev_irq_handler(1); + IRQ_EXIT(I2C1_EV_IRQn); +} + +void I2C1_ER_IRQHandler(void) { + MP_STATIC_ASSERT(I2C1_ER_IRQn > 0); + IRQ_ENTER(I2C1_ER_IRQn); + i2c_er_irq_handler(1); + IRQ_EXIT(I2C1_ER_IRQn); +} +#endif // defined(MICROPY_HW_I2C1_SCL) + +#if defined(MICROPY_HW_I2C2_SCL) +void I2C2_EV_IRQHandler(void) { + MP_STATIC_ASSERT(I2C2_EV_IRQn > 0); + IRQ_ENTER(I2C2_EV_IRQn); + i2c_ev_irq_handler(2); + IRQ_EXIT(I2C2_EV_IRQn); +} + +void I2C2_ER_IRQHandler(void) { + MP_STATIC_ASSERT(I2C2_ER_IRQn > 0); + IRQ_ENTER(I2C2_ER_IRQn); + i2c_er_irq_handler(2); + IRQ_EXIT(I2C2_ER_IRQn); +} +#endif // defined(MICROPY_HW_I2C2_SCL) + +#if defined(MICROPY_HW_I2C3_SCL) +void I2C3_EV_IRQHandler(void) { + MP_STATIC_ASSERT(I2C3_EV_IRQn > 0); + IRQ_ENTER(I2C3_EV_IRQn); + i2c_ev_irq_handler(3); + IRQ_EXIT(I2C3_EV_IRQn); +} + +void I2C3_ER_IRQHandler(void) { + MP_STATIC_ASSERT(I2C3_ER_IRQn > 0); + IRQ_ENTER(I2C3_ER_IRQn); + i2c_er_irq_handler(3); + IRQ_EXIT(I2C3_ER_IRQn); +} +#endif // defined(MICROPY_HW_I2C3_SCL) + +#if defined(MICROPY_HW_I2C4_SCL) +void I2C4_EV_IRQHandler(void) { + MP_STATIC_ASSERT(I2C4_EV_IRQn > 0); + IRQ_ENTER(I2C4_EV_IRQn); + i2c_ev_irq_handler(4); + IRQ_EXIT(I2C4_EV_IRQn); +} + +void I2C4_ER_IRQHandler(void) { + MP_STATIC_ASSERT(I2C4_ER_IRQn > 0); + IRQ_ENTER(I2C4_ER_IRQn); + i2c_er_irq_handler(4); + IRQ_EXIT(I2C4_ER_IRQn); +} +#endif // defined(MICROPY_HW_I2C4_SCL) + +#endif // MICROPY_PY_PYB_LEGACY + #endif // MICROPY_HW_ENABLE_HW_I2C diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 3639e2f0499..9eda3cb2397 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -80,7 +80,6 @@ #include "uart.h" #include "storage.h" #include "dma.h" -#include "i2c.h" #include "usb.h" #if defined(MICROPY_HW_USB_FS) @@ -987,63 +986,3 @@ void LPUART2_IRQHandler(void) { IRQ_EXIT(LPUART2_IRQn); } #endif - -#if MICROPY_PY_PYB_LEGACY - -#if defined(MICROPY_HW_I2C1_SCL) -void I2C1_EV_IRQHandler(void) { - IRQ_ENTER(I2C1_EV_IRQn); - i2c_ev_irq_handler(1); - IRQ_EXIT(I2C1_EV_IRQn); -} - -void I2C1_ER_IRQHandler(void) { - IRQ_ENTER(I2C1_ER_IRQn); - i2c_er_irq_handler(1); - IRQ_EXIT(I2C1_ER_IRQn); -} -#endif // defined(MICROPY_HW_I2C1_SCL) - -#if defined(MICROPY_HW_I2C2_SCL) -void I2C2_EV_IRQHandler(void) { - IRQ_ENTER(I2C2_EV_IRQn); - i2c_ev_irq_handler(2); - IRQ_EXIT(I2C2_EV_IRQn); -} - -void I2C2_ER_IRQHandler(void) { - IRQ_ENTER(I2C2_ER_IRQn); - i2c_er_irq_handler(2); - IRQ_EXIT(I2C2_ER_IRQn); -} -#endif // defined(MICROPY_HW_I2C2_SCL) - -#if defined(MICROPY_HW_I2C3_SCL) -void I2C3_EV_IRQHandler(void) { - IRQ_ENTER(I2C3_EV_IRQn); - i2c_ev_irq_handler(3); - IRQ_EXIT(I2C3_EV_IRQn); -} - -void I2C3_ER_IRQHandler(void) { - IRQ_ENTER(I2C3_ER_IRQn); - i2c_er_irq_handler(3); - IRQ_EXIT(I2C3_ER_IRQn); -} -#endif // defined(MICROPY_HW_I2C3_SCL) - -#if defined(MICROPY_HW_I2C4_SCL) -void I2C4_EV_IRQHandler(void) { - IRQ_ENTER(I2C4_EV_IRQn); - i2c_ev_irq_handler(4); - IRQ_EXIT(I2C4_EV_IRQn); -} - -void I2C4_ER_IRQHandler(void) { - IRQ_ENTER(I2C4_ER_IRQn); - i2c_er_irq_handler(4); - IRQ_EXIT(I2C4_ER_IRQn); -} -#endif // defined(MICROPY_HW_I2C4_SCL) - -#endif // MICROPY_PY_PYB_LEGACY From a4ca42f094f0c33ba8d4e8db2f9351586c3104e2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 19 Jul 2025 01:59:41 +1000 Subject: [PATCH 1039/2098] stm32/i2cslave: Change irq handler name to i2c_slave_irq_handler. Remove the "ev" part, so this handler can be generalised to also handle error IRQs. Signed-off-by: Damien George --- ports/stm32/i2cslave.c | 4 ++-- ports/stm32/i2cslave.h | 2 +- ports/stm32/mboot/main.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index a575c530851..44604db69f2 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -35,7 +35,7 @@ void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { i2c->CR1 = I2C_CR1_ACK | I2C_CR1_PE; } -void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { +void i2c_slave_irq_handler(i2c_slave_t *i2c) { uint32_t sr1 = i2c->SR1; if (sr1 & I2C_SR1_ADDR) { // Address matched @@ -70,7 +70,7 @@ void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { i2c->CR1 |= I2C_CR1_PE; } -void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { +void i2c_slave_irq_handler(i2c_slave_t *i2c) { uint32_t isr = i2c->ISR; if (isr & I2C_ISR_ADDR) { // Address matched diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index cc4e7f9be92..a2108ab2384 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -78,7 +78,7 @@ static inline void i2c_slave_shutdown(i2c_slave_t *i2c, int irqn) { NVIC_DisableIRQ(irqn); } -void i2c_slave_ev_irq_handler(i2c_slave_t *i2c); +void i2c_slave_irq_handler(i2c_slave_t *i2c); // These should be provided externally int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw); diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 2be8793351e..59f2d875798 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -1755,7 +1755,7 @@ void SysTick_Handler(void) { #if defined(MBOOT_I2C_SCL) void I2Cx_EV_IRQHandler(void) { - i2c_slave_ev_irq_handler(MBOOT_I2Cx); + i2c_slave_irq_handler(MBOOT_I2Cx); } #endif From 17d0449ac80723fd5ab4f5cbba28b39818046814 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 1 Aug 2025 17:02:53 +1000 Subject: [PATCH 1040/2098] stm32/i2cslave: Add functions to read/write I2C data. Instead of requiring the callback to consume/provide the data. This allows the data to be consumed/provided later on, which will stretch the I2C clock until that occurs. Signed-off-by: Damien George --- ports/stm32/i2cslave.c | 12 ++++++++---- ports/stm32/i2cslave.h | 24 ++++++++++++++++++++++-- ports/stm32/mboot/main.c | 14 ++++++++------ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index 44604db69f2..e60edf53b00 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -45,10 +45,12 @@ void i2c_slave_irq_handler(i2c_slave_t *i2c) { i2c_slave_process_addr_match(i2c, (sr2 >> I2C_SR2_TRA_Pos) & 1); } if (sr1 & I2C_SR1_TXE) { - i2c->DR = i2c_slave_process_tx_byte(i2c); + // This callback must call i2c_slave_write_byte. + i2c_slave_process_tx_byte(i2c); } if (sr1 & I2C_SR1_RXNE) { - i2c_slave_process_rx_byte(i2c, i2c->DR); + // This callback must call i2c_slave_read_byte. + i2c_slave_process_rx_byte(i2c); } if (sr1 & I2C_SR1_STOPF) { // STOPF only set at end of RX mode (in TX mode AF is set on NACK) @@ -80,10 +82,12 @@ void i2c_slave_irq_handler(i2c_slave_t *i2c) { i2c_slave_process_addr_match(i2c, (i2c->ISR >> I2C_ISR_DIR_Pos) & 1); } if (isr & I2C_ISR_TXIS) { - i2c->TXDR = i2c_slave_process_tx_byte(i2c); + // This callback must call i2c_slave_write_byte. + i2c_slave_process_tx_byte(i2c); } if (isr & I2C_ISR_RXNE) { - i2c_slave_process_rx_byte(i2c, i2c->RXDR); + // This callback must call i2c_slave_read_byte. + i2c_slave_process_rx_byte(i2c); } if (isr & I2C_ISR_STOPF) { // STOPF only set for STOP condition, not a repeated START diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index a2108ab2384..edead6cb2c5 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -28,6 +28,8 @@ #include STM32_HAL_H +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) + #if !defined(I2C2_BASE) // This MCU doesn't have I2C2_BASE, define it so that the i2c_idx calculation works. #define I2C2_BASE (I2C1_BASE + ((I2C3_BASE - I2C1_BASE) / 2)) @@ -78,13 +80,31 @@ static inline void i2c_slave_shutdown(i2c_slave_t *i2c, int irqn) { NVIC_DisableIRQ(irqn); } +static inline void i2c_slave_write_byte(i2c_slave_t *i2c, uint8_t value) { + #if defined(STM32F4) + i2c->DR = value; + #else + i2c->TXDR = value; + #endif +} + +static inline uint8_t i2c_slave_read_byte(i2c_slave_t *i2c) { + #if defined(STM32F4) + return i2c->DR; + #else + return i2c->RXDR; + #endif +} + void i2c_slave_irq_handler(i2c_slave_t *i2c); // These should be provided externally int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw); -int i2c_slave_process_rx_byte(i2c_slave_t *i2c, uint8_t val); +int i2c_slave_process_rx_byte(i2c_slave_t *i2c); void i2c_slave_process_rx_end(i2c_slave_t *i2c); -uint8_t i2c_slave_process_tx_byte(i2c_slave_t *i2c); +void i2c_slave_process_tx_byte(i2c_slave_t *i2c); void i2c_slave_process_tx_end(i2c_slave_t *i2c); +#endif + #endif // MICROPY_INCLUDED_STM32_I2CSLAVE_H diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 59f2d875798..bd796c89638 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -805,9 +805,9 @@ int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw) { return 0; // ACK } -int i2c_slave_process_rx_byte(i2c_slave_t *i2c, uint8_t val) { +int i2c_slave_process_rx_byte(i2c_slave_t *i2c) { if (i2c_obj.cmd_buf_pos < sizeof(i2c_obj.cmd_buf)) { - i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++] = val; + i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++] = i2c_slave_read_byte(i2c); } return 0; // ACK } @@ -909,15 +909,17 @@ void i2c_slave_process_rx_end(i2c_slave_t *i2c) { i2c_obj.cmd_arg_sent = false; } -uint8_t i2c_slave_process_tx_byte(i2c_slave_t *i2c) { +void i2c_slave_process_tx_byte(i2c_slave_t *i2c) { + uint8_t value; if (i2c_obj.cmd_send_arg) { i2c_obj.cmd_arg_sent = true; - return i2c_obj.cmd_arg; + value = i2c_obj.cmd_arg; } else if (i2c_obj.cmd_buf_pos < sizeof(i2c_obj.cmd_buf)) { - return i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++]; + value = i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++]; } else { - return 0; + value = 0; } + i2c_slave_write_byte(i2c, value); } void i2c_slave_process_tx_end(i2c_slave_t *i2c) { From 2443878bd903a79c1522bd0a572dcd93e5ae777e Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 1 Aug 2025 17:06:26 +1000 Subject: [PATCH 1041/2098] stm32/i2cslave: Support i2c_slave_process_tx_end callback on F4. The rounds out the F4 implementation to match the other supported MCUs. Signed-off-by: Damien George --- ports/stm32/i2cslave.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index e60edf53b00..f3bb232f7fb 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -28,8 +28,14 @@ #if defined(STM32F4) +// The hardware triggers the following IRQs for the given scenarios: +// - scan (0-length write): ADDR STOPF +// - write of n bytes: ADDR RXNE*n STOPF +// - read of n bytes: ADDR TXE*(n+1) AF +// - write of n bytes then read of m bytes: ADDR RXNE*n ADDR TXE*(m+1) AF + void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { - i2c->CR2 = I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | 4 << I2C_CR2_FREQ_Pos; + i2c->CR2 = I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | 4 << I2C_CR2_FREQ_Pos | I2C_CR2_ITERREN; i2c->OAR1 = 1 << 14 | addr << 1; i2c->OAR2 = 0; i2c->CR1 = I2C_CR1_ACK | I2C_CR1_PE; @@ -37,6 +43,14 @@ void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { void i2c_slave_irq_handler(i2c_slave_t *i2c) { uint32_t sr1 = i2c->SR1; + + // Clear all error flags. + i2c->SR1 &= ~(I2C_SR1_SMBALERT | I2C_SR1_TIMEOUT | I2C_SR1_PECERR | I2C_SR1_OVR | I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR); + + if (sr1 & I2C_SR1_AF) { + // A NACK in TX mode, which is a stop condition. + i2c_slave_process_tx_end(i2c); + } if (sr1 & I2C_SR1_ADDR) { // Address matched // Read of SR1, SR2 needed to clear ADDR bit From 78d16672e1bbc2727b879ecaa8ce12be69616c1e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Jul 2025 00:19:24 +1000 Subject: [PATCH 1042/2098] stm32/i2cslave: Account for slow addr_match callback. Signed-off-by: Damien George --- ports/stm32/i2cslave.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index f3bb232f7fb..0e4fbf48913 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -94,6 +94,9 @@ void i2c_slave_irq_handler(i2c_slave_t *i2c) { i2c->ISR = I2C_ISR_TXE; i2c->ICR = I2C_ICR_ADDRCF; i2c_slave_process_addr_match(i2c, (i2c->ISR >> I2C_ISR_DIR_Pos) & 1); + // Re-read ISR in case i2c_slave_process_addr_match() took some time + // to process and TXIS/RXNE was set in the meantime. + isr = i2c->ISR; } if (isr & I2C_ISR_TXIS) { // This callback must call i2c_slave_write_byte. From 01e570a347c1c679db93931a7f143c74762174fa Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 12:22:23 +1000 Subject: [PATCH 1043/2098] stm32/machine_i2c_target: Implement I2CTarget class. Works, tested on PYBV10, PYBD_SF2 and PYBD_SF6: buf = bytearray(16) machine.I2CTargetMemory("X", addr=67, mem=buf) Signed-off-by: Damien George --- ports/stm32/Makefile | 1 + ports/stm32/i2c.c | 95 +++++++++++++-- ports/stm32/i2c.h | 2 + ports/stm32/irq.h | 2 + ports/stm32/machine_i2c_target.c | 179 +++++++++++++++++++++++++++++ ports/stm32/main.c | 3 + ports/stm32/mboot/main.c | 1 + ports/stm32/mpconfigboard_common.h | 6 + ports/stm32/mpconfigport.h | 4 + 9 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 ports/stm32/machine_i2c_target.c diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 37d70dcdbf0..37ce8fbd838 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -266,6 +266,7 @@ SRC_C += \ bufhelper.c \ dma.c \ i2c.c \ + i2cslave.c \ pyb_i2c.c \ spi.c \ pyb_spi.c \ diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index 883734bb1af..4effb23438c 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -29,6 +29,7 @@ #include "py/mphal.h" #include "py/runtime.h" #include "i2c.h" +#include "i2cslave.h" #if MICROPY_HW_ENABLE_HW_I2C @@ -551,6 +552,10 @@ static const uint8_t i2c_available = #endif ; +#if MICROPY_HW_ENABLE_HW_I2C_TARGET +uint8_t i2c_target_enabled; +#endif + int i2c_find_peripheral(mp_obj_t id) { int i2c_id = 0; if (mp_obj_is_str(id)) { @@ -590,20 +595,38 @@ int i2c_find_peripheral(mp_obj_t id) { return i2c_id; } -#if MICROPY_PY_PYB_LEGACY +#if MICROPY_HW_ENABLE_HW_I2C_TARGET || MICROPY_PY_PYB_LEGACY #if defined(MICROPY_HW_I2C1_SCL) void I2C1_EV_IRQHandler(void) { MP_STATIC_ASSERT(I2C1_EV_IRQn > 0); IRQ_ENTER(I2C1_EV_IRQn); - i2c_ev_irq_handler(1); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 1) { + i2c_slave_irq_handler(I2C1); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_ev_irq_handler(1); + #endif + } IRQ_EXIT(I2C1_EV_IRQn); } void I2C1_ER_IRQHandler(void) { MP_STATIC_ASSERT(I2C1_ER_IRQn > 0); IRQ_ENTER(I2C1_ER_IRQn); - i2c_er_irq_handler(1); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 1) { + i2c_slave_irq_handler(I2C1); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_er_irq_handler(1); + #endif + } IRQ_EXIT(I2C1_ER_IRQn); } #endif // defined(MICROPY_HW_I2C1_SCL) @@ -612,14 +635,32 @@ void I2C1_ER_IRQHandler(void) { void I2C2_EV_IRQHandler(void) { MP_STATIC_ASSERT(I2C2_EV_IRQn > 0); IRQ_ENTER(I2C2_EV_IRQn); - i2c_ev_irq_handler(2); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 2) { + i2c_slave_irq_handler(I2C2); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_ev_irq_handler(2); + #endif + } IRQ_EXIT(I2C2_EV_IRQn); } void I2C2_ER_IRQHandler(void) { MP_STATIC_ASSERT(I2C2_ER_IRQn > 0); IRQ_ENTER(I2C2_ER_IRQn); - i2c_er_irq_handler(2); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 2) { + i2c_slave_irq_handler(I2C2); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_er_irq_handler(2); + #endif + } IRQ_EXIT(I2C2_ER_IRQn); } #endif // defined(MICROPY_HW_I2C2_SCL) @@ -628,14 +669,32 @@ void I2C2_ER_IRQHandler(void) { void I2C3_EV_IRQHandler(void) { MP_STATIC_ASSERT(I2C3_EV_IRQn > 0); IRQ_ENTER(I2C3_EV_IRQn); - i2c_ev_irq_handler(3); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 4) { + i2c_slave_irq_handler(I2C3); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_ev_irq_handler(3); + #endif + } IRQ_EXIT(I2C3_EV_IRQn); } void I2C3_ER_IRQHandler(void) { MP_STATIC_ASSERT(I2C3_ER_IRQn > 0); IRQ_ENTER(I2C3_ER_IRQn); - i2c_er_irq_handler(3); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 4) { + i2c_slave_irq_handler(I2C3); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_er_irq_handler(3); + #endif + } IRQ_EXIT(I2C3_ER_IRQn); } #endif // defined(MICROPY_HW_I2C3_SCL) @@ -644,14 +703,32 @@ void I2C3_ER_IRQHandler(void) { void I2C4_EV_IRQHandler(void) { MP_STATIC_ASSERT(I2C4_EV_IRQn > 0); IRQ_ENTER(I2C4_EV_IRQn); - i2c_ev_irq_handler(4); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 8) { + i2c_slave_irq_handler(I2C4); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_ev_irq_handler(4); + #endif + } IRQ_EXIT(I2C4_EV_IRQn); } void I2C4_ER_IRQHandler(void) { MP_STATIC_ASSERT(I2C4_ER_IRQn > 0); IRQ_ENTER(I2C4_ER_IRQn); - i2c_er_irq_handler(4); + #if MICROPY_HW_ENABLE_HW_I2C_TARGET + if (i2c_target_enabled & 8) { + i2c_slave_irq_handler(I2C4); + } else + #endif + { + #if MICROPY_PY_PYB_LEGACY + i2c_er_irq_handler(4); + #endif + } IRQ_EXIT(I2C4_ER_IRQn); } #endif // defined(MICROPY_HW_I2C4_SCL) diff --git a/ports/stm32/i2c.h b/ports/stm32/i2c.h index a48076842cb..26c55ec00e6 100644 --- a/ports/stm32/i2c.h +++ b/ports/stm32/i2c.h @@ -46,6 +46,8 @@ extern I2C_HandleTypeDef I2CHandle4; extern const mp_obj_type_t pyb_i2c_type; extern const pyb_i2c_obj_t pyb_i2c_obj[4]; +extern uint8_t i2c_target_enabled; + void i2c_init0(void); int pyb_i2c_init(I2C_HandleTypeDef *i2c); int pyb_i2c_init_freq(const pyb_i2c_obj_t *self, mp_int_t freq); diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index dfe901ff74b..3348175420c 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -177,6 +177,8 @@ static inline void restore_irq_pri(uint32_t state) { #define IRQ_PRI_HSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 10, 0) +#define IRQ_PRI_I2C NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 12, 0) + // Interrupt priority for non-special timers. #define IRQ_PRI_TIMX NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 13, 0) diff --git a/ports/stm32/machine_i2c_target.c b/ports/stm32/machine_i2c_target.c new file mode 100644 index 00000000000..83031677ebb --- /dev/null +++ b/ports/stm32/machine_i2c_target.c @@ -0,0 +1,179 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include "i2c.h" +#include "i2cslave.h" +#include "irq.h" + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + I2C_TypeDef *i2c; + uint16_t irqn_ev; + uint16_t irqn_er; + mp_hal_pin_obj_t scl; + mp_hal_pin_obj_t sda; +} machine_i2c_target_obj_t; + +static const machine_i2c_target_obj_t machine_i2c_target_obj[] = { + #if defined(MICROPY_HW_I2C1_SCL) + {{&machine_i2c_target_type}, I2C1, I2C1_EV_IRQn, I2C1_ER_IRQn, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, + #else + {{NULL}, NULL, 0, 0, NULL, NULL}, + #endif + #if defined(MICROPY_HW_I2C2_SCL) + {{&machine_i2c_target_type}, I2C2, I2C2_EV_IRQn, I2C2_ER_IRQn, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, + #else + {{NULL}, NULL, 0, 0, NULL, NULL}, + #endif + #if defined(MICROPY_HW_I2C3_SCL) + {{&machine_i2c_target_type}, I2C3, I2C3_EV_IRQn, I2C3_ER_IRQn, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, + #else + {{NULL}, NULL, 0, 0, NULL, NULL}, + #endif + #if defined(MICROPY_HW_I2C4_SCL) + {{&machine_i2c_target_type}, I2C4, I2C4_EV_IRQn, I2C4_ER_IRQn, MICROPY_HW_I2C4_SCL, MICROPY_HW_I2C4_SDA}, + #else + {{NULL}, NULL, 0, 0, NULL, NULL}, + #endif +}; + +/******************************************************************************/ +// stm32 hardware bindings + +static machine_i2c_target_obj_t *get_self(i2c_slave_t *i2c) { + size_t i2c_id = ((uintptr_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE); + return (machine_i2c_target_obj_t *)&machine_i2c_target_obj[i2c_id]; +} + +static machine_i2c_target_data_t *get_data(i2c_slave_t *i2c) { + size_t i2c_id = ((uintptr_t)i2c - I2C1_BASE) / (I2C2_BASE - I2C1_BASE); + return &machine_i2c_target_data[i2c_id]; +} + +int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw) { + machine_i2c_target_data_addr_match(get_data(i2c), rw); + return 0; +} + +int i2c_slave_process_rx_byte(i2c_slave_t *i2c) { + machine_i2c_target_data_write_request(get_self(i2c), get_data(i2c)); + return 0; +} + +void i2c_slave_process_rx_end(i2c_slave_t *i2c) { + machine_i2c_target_data_stop(get_data(i2c)); +} + +void i2c_slave_process_tx_byte(i2c_slave_t *i2c) { + machine_i2c_target_data_read_request(get_self(i2c), get_data(i2c)); +} + +void i2c_slave_process_tx_end(i2c_slave_t *i2c) { + machine_i2c_target_data_stop(get_data(i2c)); +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self - &machine_i2c_target_obj[0]; +} + +static inline void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + if (len > 0) { + buf[0] = i2c_slave_read_byte(self->i2c); + len = 1; + } + return len; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + if (len > 0) { + i2c_slave_write_byte(self->i2c, buf[0]); + len = 1; + } + return len; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Parse arguments. + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Work out I2C bus. + int i2c_id = i2c_find_peripheral(args[ARG_id].u_obj); + + // Get static target object. + machine_i2c_target_obj_t *self = (machine_i2c_target_obj_t *)&machine_i2c_target_obj[i2c_id - 1]; + + // Initialise data. + MP_STATE_PORT(machine_i2c_target_mem_obj)[i2c_id - 1] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id - 1]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Initialise the I2C target. + mp_hal_pin_config_alt(self->scl, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_NONE, AF_FN_I2C, i2c_id); + mp_hal_pin_config_alt(self->sda, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_NONE, AF_FN_I2C, i2c_id); + i2c_slave_init(self->i2c, self->irqn_ev, IRQ_PRI_I2C, args[ARG_addr].u_int); + NVIC_SetPriority(self->irqn_er, IRQ_PRI_I2C); + NVIC_EnableIRQ(self->irqn_er); + + i2c_target_enabled |= 1 << (i2c_id - 1); + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2CTarget(%u, addr=%u)", + self - &machine_i2c_target_obj[0] + 1, + (self->i2c->OAR1 >> 1) & 0x7f); +} + +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + i2c_slave_shutdown(self->i2c, self->irqn_ev); + NVIC_DisableIRQ(self->irqn_er); +} diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 137e1328174..af4d7f8bbb6 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -746,6 +746,9 @@ void stm32_main(uint32_t reset_mode) { #if MICROPY_PY_PYB_LEGACY && MICROPY_HW_ENABLE_HW_I2C pyb_i2c_deinit_all(); #endif + #if MICROPY_PY_MACHINE_I2C_TARGET + mp_machine_i2c_target_deinit_all(); + #endif #if MICROPY_HW_ENABLE_CAN pyb_can_deinit_all(); #endif diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index bd796c89638..e40413e4e7a 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -61,6 +61,7 @@ // IRQ priorities (encoded values suitable for NVIC_SetPriority) // Most values are defined in irq.h. +#undef IRQ_PRI_I2C #define IRQ_PRI_I2C (NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0)) #if defined(MBOOT_CLK_PLLM) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 9fa9bf77148..dcbbbccceb6 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -639,8 +639,14 @@ #if defined(MICROPY_HW_I2C1_SCL) || defined(MICROPY_HW_I2C2_SCL) \ || defined(MICROPY_HW_I2C3_SCL) || defined(MICROPY_HW_I2C4_SCL) #define MICROPY_HW_ENABLE_HW_I2C (1) +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#define MICROPY_HW_ENABLE_HW_I2C_TARGET (1) +#else +#define MICROPY_HW_ENABLE_HW_I2C_TARGET (0) +#endif #else #define MICROPY_HW_ENABLE_HW_I2C (0) +#define MICROPY_HW_ENABLE_HW_I2C_TARGET (0) #endif // Enable CAN if there are any peripherals defined diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 35deb93c6a0..191503cd495 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -129,6 +129,10 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_I2C (MICROPY_HW_ENABLE_HW_I2C) +#define MICROPY_PY_MACHINE_I2C_TARGET (MICROPY_HW_ENABLE_HW_I2C_TARGET) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/stm32/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (4) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_I2S_INCLUDEFILE "ports/stm32/machine_i2s.c" #define MICROPY_PY_MACHINE_I2S_CONSTANT_RX (I2S_MODE_MASTER_RX) From 56d2b47370d90616ae7ad6b6f078446efd69d0ff Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Jul 2025 11:23:11 +1000 Subject: [PATCH 1044/2098] rp2/machine_i2c: Factor default pin macros to header file. So they can be reused by the I2CTarget implementation. Signed-off-by: Damien George --- ports/rp2/machine_i2c.c | 40 +----------------------- ports/rp2/machine_i2c.h | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 ports/rp2/machine_i2c.h diff --git a/ports/rp2/machine_i2c.c b/ports/rp2/machine_i2c.c index 94212fb4870..99a94ec2f1a 100644 --- a/ports/rp2/machine_i2c.c +++ b/ports/rp2/machine_i2c.c @@ -28,51 +28,13 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/modmachine.h" +#include "machine_i2c.h" #include "hardware/i2c.h" #define DEFAULT_I2C_FREQ (400000) #define DEFAULT_I2C_TIMEOUT (50000) -#ifdef MICROPY_HW_I2C_NO_DEFAULT_PINS - -// With no default I2C, need to require the pin args. -#define MICROPY_HW_I2C0_SCL (0) -#define MICROPY_HW_I2C0_SDA (0) -#define MICROPY_HW_I2C1_SCL (0) -#define MICROPY_HW_I2C1_SDA (0) -#define MICROPY_I2C_PINS_ARG_OPTS MP_ARG_REQUIRED - -#else - -// Most boards do not require pin args. -#define MICROPY_I2C_PINS_ARG_OPTS 0 - -#ifndef MICROPY_HW_I2C0_SCL -#if PICO_DEFAULT_I2C == 0 -#define MICROPY_HW_I2C0_SCL (PICO_DEFAULT_I2C_SCL_PIN) -#define MICROPY_HW_I2C0_SDA (PICO_DEFAULT_I2C_SDA_PIN) -#else -#define MICROPY_HW_I2C0_SCL (9) -#define MICROPY_HW_I2C0_SDA (8) -#endif -#endif - -#ifndef MICROPY_HW_I2C1_SCL -#if PICO_DEFAULT_I2C == 1 -#define MICROPY_HW_I2C1_SCL (PICO_DEFAULT_I2C_SCL_PIN) -#define MICROPY_HW_I2C1_SDA (PICO_DEFAULT_I2C_SDA_PIN) -#else -#define MICROPY_HW_I2C1_SCL (7) -#define MICROPY_HW_I2C1_SDA (6) -#endif -#endif -#endif - -// SDA/SCL on even/odd pins, I2C0/I2C1 on even/odd pairs of pins. -#define IS_VALID_SCL(i2c, pin) (((pin) & 1) == 1 && (((pin) & 2) >> 1) == (i2c)) -#define IS_VALID_SDA(i2c, pin) (((pin) & 1) == 0 && (((pin) & 2) >> 1) == (i2c)) - typedef struct _machine_i2c_obj_t { mp_obj_base_t base; i2c_inst_t *const i2c_inst; diff --git a/ports/rp2/machine_i2c.h b/ports/rp2/machine_i2c.h new file mode 100644 index 00000000000..da8cb5f8567 --- /dev/null +++ b/ports/rp2/machine_i2c.h @@ -0,0 +1,68 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2025 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. + */ +#ifndef MICROPY_INCLUDED_RP2_MACHINE_I2C_H +#define MICROPY_INCLUDED_RP2_MACHINE_I2C_H + +#ifdef MICROPY_HW_I2C_NO_DEFAULT_PINS + +// With no default I2C, need to require the pin args. +#define MICROPY_HW_I2C0_SCL (0) +#define MICROPY_HW_I2C0_SDA (0) +#define MICROPY_HW_I2C1_SCL (0) +#define MICROPY_HW_I2C1_SDA (0) +#define MICROPY_I2C_PINS_ARG_OPTS MP_ARG_REQUIRED + +#else + +// Most boards do not require pin args. +#define MICROPY_I2C_PINS_ARG_OPTS 0 + +#ifndef MICROPY_HW_I2C0_SCL +#if PICO_DEFAULT_I2C == 0 +#define MICROPY_HW_I2C0_SCL (PICO_DEFAULT_I2C_SCL_PIN) +#define MICROPY_HW_I2C0_SDA (PICO_DEFAULT_I2C_SDA_PIN) +#else +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) +#endif +#endif + +#ifndef MICROPY_HW_I2C1_SCL +#if PICO_DEFAULT_I2C == 1 +#define MICROPY_HW_I2C1_SCL (PICO_DEFAULT_I2C_SCL_PIN) +#define MICROPY_HW_I2C1_SDA (PICO_DEFAULT_I2C_SDA_PIN) +#else +#define MICROPY_HW_I2C1_SCL (7) +#define MICROPY_HW_I2C1_SDA (6) +#endif +#endif +#endif + +// SDA/SCL on even/odd pins, I2C0/I2C1 on even/odd pairs of pins. +#define IS_VALID_SCL(i2c, pin) (((pin) & 1) == 1 && (((pin) & 2) >> 1) == (i2c)) +#define IS_VALID_SDA(i2c, pin) (((pin) & 1) == 0 && (((pin) & 2) >> 1) == (i2c)) + +#endif // MICROPY_INCLUDED_RP2_MACHINE_I2C_H From 1839340dda5040fab0d05a5c58660ad0f4b5513e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 14:00:05 +1000 Subject: [PATCH 1045/2098] rp2/machine_i2c_target: Implement I2CTarget class. Signed-off-by: Damien George --- ports/rp2/machine_i2c_target.c | 304 +++++++++++++++++++++++++++++++++ ports/rp2/main.c | 4 + ports/rp2/mpconfigport.h | 4 + 3 files changed, 312 insertions(+) create mode 100644 ports/rp2/machine_i2c_target.c diff --git a/ports/rp2/machine_i2c_target.c b/ports/rp2/machine_i2c_target.c new file mode 100644 index 00000000000..dc3010727b5 --- /dev/null +++ b/ports/rp2/machine_i2c_target.c @@ -0,0 +1,304 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include "machine_i2c.h" +#include "hardware/i2c.h" + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + i2c_inst_t *const i2c_inst; + mp_hal_pin_obj_t scl; + mp_hal_pin_obj_t sda; + uint8_t state; + bool stop_pending; + bool irq_active; +} machine_i2c_target_obj_t; + +static machine_i2c_target_obj_t machine_i2c_target_obj[] = { + {{&machine_i2c_target_type}, i2c0, MICROPY_HW_I2C0_SCL, MICROPY_HW_I2C0_SDA}, + {{&machine_i2c_target_type}, i2c1, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, +}; + +/******************************************************************************/ +// RP2xxx hardware bindings + +static void check_stop_pending(machine_i2c_target_obj_t *self) { + if (self->irq_active) { + return; + } + if (self->stop_pending && !(self->i2c_inst->hw->status & I2C_IC_STATUS_RFNE_BITS)) { + unsigned int i2c_id = self - &machine_i2c_target_obj[0]; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + self->stop_pending = false; + self->state = STATE_IDLE; + machine_i2c_target_data_restart_or_stop(data); + } +} + +static void i2c_target_handler(i2c_inst_t *i2c) { + unsigned int i2c_id = i2c == i2c0 ? 0 : 1; + machine_i2c_target_obj_t *self = &machine_i2c_target_obj[i2c_id]; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + + self->irq_active = true; + + // Get the interrupt status. + uint32_t intr_stat = i2c->hw->intr_stat; + + if (intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) { + // Clear the TX_ABRT condition. + (void)i2c->hw->clr_tx_abrt; + } + + if (intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) { + // Controller sent a start condition. + // Reset all state machines in case something went wrong. + (void)i2c->hw->clr_start_det; + if (self->state != STATE_IDLE) { + machine_i2c_target_data_reset_helper(data); + self->state = STATE_IDLE; + } + } + + if (intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { + // Data from controller is available for reading. + // Mask interrupt until I2C_DATA_CMD is read from. + i2c->hw->intr_mask &= ~I2C_IC_INTR_MASK_M_RX_FULL_BITS; + if (self->state != STATE_WRITING) { + machine_i2c_target_data_addr_match(data, false); + } + machine_i2c_target_data_write_request(self, data); + self->state = STATE_WRITING; + } + + if (intr_stat & (I2C_IC_INTR_STAT_R_RD_REQ_BITS | I2C_IC_INTR_STAT_R_RX_DONE_BITS)) { + // Controller is requesting data. + (void)i2c->hw->clr_rx_done; + (void)i2c->hw->clr_rd_req; + i2c->hw->intr_mask &= ~I2C_IC_INTR_MASK_M_RD_REQ_BITS; + if (self->state != STATE_READING) { + machine_i2c_target_data_addr_match(data, true); + } + machine_i2c_target_data_read_request(self, data); + self->state = STATE_READING; + } + + if (intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) { + // Controller has generated a stop condition. + (void)i2c->hw->clr_stop_det; + if (self->state == STATE_IDLE) { + machine_i2c_target_data_addr_match(data, false); + } + if (i2c->hw->status & I2C_IC_STATUS_RFNE_BITS) { + self->stop_pending = true; + } else { + machine_i2c_target_data_restart_or_stop(data); + self->state = STATE_IDLE; + } + } + + self->irq_active = false; + check_stop_pending(self); +} + +static void i2c_target_irq_handler(void) { + uint i2c_index = __get_current_exception() - VTABLE_FIRST_IRQ - I2C0_IRQ; + i2c_inst_t *i2c = i2c_get_instance(i2c_index); + i2c_target_handler(i2c); +} + +static void i2c_target_init(i2c_inst_t *i2c, uint16_t addr, bool addr_10bit) { + i2c->hw->enable = 0; + + // Configure general settings, target address and FIFO levels. + i2c->hw->con = + I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL_BITS + | I2C_IC_CON_STOP_DET_IFADDRESSED_BITS; + if (addr_10bit) { + i2c->hw->con |= I2C_IC_CON_IC_10BITADDR_SLAVE_BITS; + } + i2c->hw->sar = addr; + i2c->hw->tx_tl = 1; + i2c->hw->rx_tl = 0; // interrupt when at least 1 byte is available + (void)i2c->hw->clr_intr; + + // Enable interrupts. + i2c->hw->intr_mask = + I2C_IC_INTR_MASK_M_START_DET_BITS + | I2C_IC_INTR_MASK_M_STOP_DET_BITS + | I2C_IC_INTR_MASK_M_RX_DONE_BITS + | I2C_IC_INTR_MASK_M_TX_ABRT_BITS + | I2C_IC_INTR_MASK_M_RD_REQ_BITS + | I2C_IC_INTR_MASK_M_RX_FULL_BITS + ; + + i2c->hw->enable = 1; + + // Enable interrupt for current core. + uint i2c_index = i2c_hw_index(i2c); + uint num = I2C0_IRQ + i2c_index; + irq_set_exclusive_handler(num, i2c_target_irq_handler); + irq_set_enabled(num, true); +} + +static void i2c_target_deinit(i2c_inst_t *i2c) { + uint i2c_index = i2c_hw_index(i2c); + uint num = I2C0_IRQ + i2c_index; + irq_set_enabled(num, false); + irq_remove_handler(num, i2c_target_irq_handler); + + i2c->hw->intr_mask = 0; + i2c->hw->enable = 0; + i2c->hw->con = I2C_IC_CON_IC_SLAVE_DISABLE_BITS; +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self - &machine_i2c_target_obj[0]; +} + +static inline void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + i2c_hw_t *i2c_hw = self->i2c_inst->hw; + + // Read from the RX FIFO. + size_t i = 0; + while (i < len && (i2c_hw->status & I2C_IC_STATUS_RFNE_BITS)) { + buf[i++] = i2c_hw->data_cmd; + } + + // Re-enable RX_FULL interrupt. + i2c_hw->intr_mask |= I2C_IC_INTR_MASK_M_RX_FULL_BITS; + + check_stop_pending(self); + + return i; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + i2c_hw_t *i2c_hw = self->i2c_inst->hw; + + // Write to the TX FIFO. + size_t i = 0; + while (i < len && (i2c_hw->status & I2C_IC_STATUS_TFNF_BITS)) { + i2c_hw->data_cmd = buf[i++]; + } + + // Re-enable RD_REQ interrupt. + i2c_hw->intr_mask |= I2C_IC_INTR_MASK_M_RD_REQ_BITS; + + return i; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + #ifdef PICO_DEFAULT_I2C + { MP_QSTR_id, MP_ARG_INT, {.u_int = PICO_DEFAULT_I2C} }, + #else + { MP_QSTR_id, MP_ARG_INT | MP_ARG_REQUIRED }, + #endif + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_scl, MICROPY_I2C_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MICROPY_I2C_PINS_ARG_OPTS | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid + if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_target_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2CTarget(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object. + machine_i2c_target_obj_t *self = &machine_i2c_target_obj[i2c_id]; + + // Set SCL/SDA pins if configured. + if (args[ARG_scl].u_obj != mp_const_none) { + int scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + if (!IS_VALID_SCL(i2c_id, scl)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SCL pin")); + } + self->scl = scl; + } + if (args[ARG_sda].u_obj != mp_const_none) { + int sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + if (!IS_VALID_SDA(i2c_id, sda)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SDA pin")); + } + self->sda = sda; + } + + // Initialise I2C target state and data. + self->state = STATE_IDLE; + self->stop_pending = false; + self->irq_active = false; + MP_STATE_PORT(machine_i2c_target_mem_obj)[i2c_id] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Initialise I2C target hardware. + i2c_target_init(self->i2c_inst, args[ARG_addr].u_int, args[ARG_addrsize].u_int == 10); + gpio_set_function(self->scl, GPIO_FUNC_I2C); + gpio_set_function(self->sda, GPIO_FUNC_I2C); + gpio_set_pulls(self->scl, true, 0); + gpio_set_pulls(self->sda, true, 0); + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + i2c_hw_t *i2c_hw = i2c_get_hw(self->i2c_inst); + mp_printf(print, "I2CTarget(%u, addr=%u, scl=%u, sda=%u)", + self - &machine_i2c_target_obj[0], i2c_hw->sar, self->scl, self->sda); +} + +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + gpio_set_function(self->scl, GPIO_FUNC_SIO); + gpio_set_function(self->sda, GPIO_FUNC_SIO); + i2c_target_deinit(self->i2c_inst); +} diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 0f10f63c6d2..1ffcabdfa0d 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -34,6 +34,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "extmod/modbluetooth.h" +#include "extmod/modmachine.h" #include "extmod/modnetwork.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -257,6 +258,9 @@ int main(int argc, char **argv) { machine_pwm_deinit_all(); machine_pin_deinit(); machine_uart_deinit_all(); + #if MICROPY_PY_MACHINE_I2C_TARGET + mp_machine_i2c_target_deinit_all(); + #endif #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 35afea4fac5..d35563bd070 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -171,6 +171,10 @@ #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/rp2/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_I2C_TARGET (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/rp2/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_I2S (1) #define MICROPY_PY_MACHINE_I2S_INCLUDEFILE "ports/rp2/machine_i2s.c" From 0c50343145c04b0b67c4b1eb3a337c4b0ea70b1f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 23 Jun 2025 12:41:01 +1000 Subject: [PATCH 1046/2098] zephyr/machine_i2c_target: Implement I2CTarget class. Tested and working on rpi_pico and nucleo_wb55rg. Signed-off-by: Damien George --- ports/zephyr/boards/nucleo_wb55rg.conf | 1 + ports/zephyr/boards/rpi_pico.conf | 1 + ports/zephyr/machine_i2c_target.c | 191 +++++++++++++++++++++++++ ports/zephyr/main.c | 4 + ports/zephyr/mpconfigport.h | 6 + 5 files changed, 203 insertions(+) create mode 100644 ports/zephyr/machine_i2c_target.c diff --git a/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf index adfab367c89..1c9c2f794cb 100644 --- a/ports/zephyr/boards/nucleo_wb55rg.conf +++ b/ports/zephyr/boards/nucleo_wb55rg.conf @@ -4,6 +4,7 @@ CONFIG_NETWORKING=n CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_I2C=y +CONFIG_I2C_TARGET=y CONFIG_SPI=y # Bluetooth diff --git a/ports/zephyr/boards/rpi_pico.conf b/ports/zephyr/boards/rpi_pico.conf index 6b31bc9f98b..683279ddc2c 100644 --- a/ports/zephyr/boards/rpi_pico.conf +++ b/ports/zephyr/boards/rpi_pico.conf @@ -13,6 +13,7 @@ CONFIG_NETWORKING=n CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_I2C=y +CONFIG_I2C_TARGET=y CONFIG_SPI=y # MicroPython config. diff --git a/ports/zephyr/machine_i2c_target.c b/ports/zephyr/machine_i2c_target.c new file mode 100644 index 00000000000..236f1334883 --- /dev/null +++ b/ports/zephyr/machine_i2c_target.c @@ -0,0 +1,191 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include + +#include "zephyr_device.h" + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + const struct device *dev; + struct i2c_target_config cfg; + uint8_t state; + uint8_t data_byte; +} machine_i2c_target_obj_t; + +static machine_i2c_target_obj_t machine_i2c_target_obj[] = { + {.base = {&machine_i2c_target_type}, .dev = NULL}, +}; + +/******************************************************************************/ +// zephyr bindings +// +// Note that it's possible to get callbacks in either of these sequences: +// - read_requested read_processed read_processed ... (eg STM32) +// - read_requested read_processed read_requested read_processed ... (eg RP2xxx / Design Ware) + +static int i2c_target_write_requested(struct i2c_target_config *config) { + machine_i2c_target_obj_t *self = CONTAINER_OF(config, machine_i2c_target_obj_t, cfg); + self->state = STATE_WRITING; + machine_i2c_target_data_addr_match(&machine_i2c_target_data[0], false); + return 0; +} + +static int i2c_target_write_received(struct i2c_target_config *config, uint8_t val) { + machine_i2c_target_obj_t *self = CONTAINER_OF(config, machine_i2c_target_obj_t, cfg); + machine_i2c_target_data_t *data = &machine_i2c_target_data[0]; + self->data_byte = val; + machine_i2c_target_data_write_request(self, data); + return 0; +} + +static int i2c_target_read_requested(struct i2c_target_config *config, uint8_t *val) { + machine_i2c_target_obj_t *self = CONTAINER_OF(config, machine_i2c_target_obj_t, cfg); + machine_i2c_target_data_t *data = &machine_i2c_target_data[0]; + if (self->state != STATE_READING) { + machine_i2c_target_data_addr_match(data, true); + machine_i2c_target_data_read_request(self, data); + self->state = STATE_READING; + } + *val = self->data_byte; + return 0; +} + +static int i2c_target_read_processed(struct i2c_target_config *config, uint8_t *val) { + machine_i2c_target_obj_t *self = CONTAINER_OF(config, machine_i2c_target_obj_t, cfg); + machine_i2c_target_data_t *data = &machine_i2c_target_data[0]; + machine_i2c_target_data_read_request(self, data); + *val = self->data_byte; + return 0; +} + +// called only on stop, not restart +static int i2c_target_stop(struct i2c_target_config *config) { + machine_i2c_target_obj_t *self = CONTAINER_OF(config, machine_i2c_target_obj_t, cfg); + if (self->state == STATE_IDLE) { + // Assume a stop without a start is a 0-byte write. + machine_i2c_target_data_addr_match(&machine_i2c_target_data[0], false); + } + self->state = STATE_IDLE; + machine_i2c_target_data_t *data = &machine_i2c_target_data[0]; + machine_i2c_target_data_stop(data); + return 0; +} + +static struct i2c_target_callbacks i2c_target_callbacks = { + .write_requested = i2c_target_write_requested, + .read_requested = i2c_target_read_requested, + .write_received = i2c_target_write_received, + .read_processed = i2c_target_read_processed, + .stop = i2c_target_stop, +}; + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return 0; +} + +static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + char dummy; + void *orig_top = MP_STATE_THREAD(stack_top); + mp_uint_t orig_limit = MP_STATE_THREAD(stack_limit); + MP_STATE_THREAD(stack_top) = &dummy; + MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; + mp_irq_handler(&irq->base); + MP_STATE_THREAD(stack_top) = orig_top; + MP_STATE_THREAD(stack_limit) = orig_limit; +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + buf[0] = self->data_byte; + return 1; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + self->data_byte = buf[0]; + return 1; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + }; + + // Parse arguments. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const struct device *dev = zephyr_device_find(args[ARG_id].u_obj); + + machine_i2c_target_obj_t *self = &machine_i2c_target_obj[0]; + if (!(self->dev == NULL || self->dev == dev)) { + mp_raise_ValueError(MP_ERROR_TEXT("only one I2CTarget supported")); + } + self->dev = dev; + self->cfg.flags = 0; + self->cfg.address = args[ARG_addr].u_int; + self->cfg.callbacks = &i2c_target_callbacks; + + // Initialise data. + self->state = STATE_IDLE; + MP_STATE_PORT(machine_i2c_target_mem_obj)[0] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[0]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Initialise the I2C target. + int ret = i2c_target_register(self->dev, &self->cfg); + if (ret < 0) { + mp_raise_OSError(-ret); + } + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2CTarget(%s, addr=%u)", + self->dev == NULL ? "" : self->dev->name, self->cfg.address); +} + +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + i2c_target_unregister(self->dev, &self->cfg); + self->dev = NULL; +} diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index eaef34a7868..f7ac997d92a 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -53,6 +53,7 @@ #include "shared/runtime/pyexec.h" #include "shared/readline/readline.h" #include "extmod/modbluetooth.h" +#include "extmod/modmachine.h" #if MICROPY_VFS #include "extmod/vfs.h" @@ -197,6 +198,9 @@ int real_main(void) { #if MICROPY_PY_MACHINE machine_pin_deinit(); #endif + #if MICROPY_PY_MACHINE_I2C_TARGET + mp_machine_i2c_target_deinit_all(); + #endif #if MICROPY_PY_THREAD mp_thread_deinit(); diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 848e04b389e..fbf8dbcc7a7 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -62,6 +62,12 @@ #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" #define MICROPY_PY_MACHINE_I2C (1) +#ifdef CONFIG_I2C_TARGET +#define MICROPY_PY_MACHINE_I2C_TARGET (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/zephyr/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) +#endif #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_TRANSFER_MSB) From 6e72cae619f4172205249b831382bd43205a4b10 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Jul 2025 16:30:56 +1000 Subject: [PATCH 1047/2098] alif/machine_i2c_target: Implement I2CTarget class. Signed-off-by: Damien George --- ports/alif/irq.h | 1 + ports/alif/machine_i2c_target.c | 325 ++++++++++++++++++++++++++++++++ ports/alif/main.c | 4 + ports/alif/mpconfigport.h | 4 + 4 files changed, 334 insertions(+) create mode 100644 ports/alif/machine_i2c_target.c diff --git a/ports/alif/irq.h b/ports/alif/irq.h index 02df524a49c..86b739795c1 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -49,6 +49,7 @@ #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) #define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) #define IRQ_PRI_GPIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 50, 0) +#define IRQ_PRI_I2C NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 60, 0) #define IRQ_PRI_RTC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 100, 0) #define IRQ_PRI_CYW43 NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 126, 0) #define IRQ_PRI_PENDSV NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 127, 0) diff --git a/ports/alif/machine_i2c_target.c b/ports/alif/machine_i2c_target.c new file mode 100644 index 00000000000..cdc106049a0 --- /dev/null +++ b/ports/alif/machine_i2c_target.c @@ -0,0 +1,325 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include "i2c.h" + +#define I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL (1 << 9) +#define I2C_IC_CON_TX_EMPTY_CTRL (1 << 8) +#define I2C_IC_CON_STOP_DET_IFADDRESSED (1 << 7) + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + I2C_Type *i2c; + mp_hal_pin_obj_t scl; + mp_hal_pin_obj_t sda; + uint8_t state; + bool stop_pending; + bool irq_active; +} machine_i2c_target_obj_t; + +static machine_i2c_target_obj_t machine_i2c_target_obj[] = { + #if defined(MICROPY_HW_I2C0_SCL) + [0] = {{&machine_i2c_target_type}, (I2C_Type *)I2C0_BASE, MICROPY_HW_I2C0_SCL, MICROPY_HW_I2C0_SDA}, + #endif + #if defined(MICROPY_HW_I2C1_SCL) + [1] = {{&machine_i2c_target_type}, (I2C_Type *)I2C1_BASE, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, + #endif + #if defined(MICROPY_HW_I2C2_SCL) + [2] = {{&machine_i2c_target_type}, (I2C_Type *)I2C2_BASE, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, + #endif + #if defined(MICROPY_HW_I2C3_SCL) + [3] = {{&machine_i2c_target_type}, (I2C_Type *)I2C3_BASE, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, + #endif +}; + +/******************************************************************************/ +// Alif I2C hardware bindings +// +// The hardware triggers the following IRQs for the given scenarios: +// - scan (0-byte write) of another target: START_DET +// - scan (0-byte write) of us: START_DET STOP_DET +// - write of n bytes: START_DET RX_FULL*n STOP_DET +// - write of n bytes then read of m bytes: START_DET RX_FULL*n START_DET RD_REQ*m RX_DONE STOP_DET + +static inline unsigned int i2c_reg_base_to_index(I2C_Type *i2c) { + return ((uintptr_t)i2c - I2C0_BASE) / (I2C1_BASE - I2C0_BASE); +} + +static const uint32_t i2c_irq_num[] = { I2C0_IRQ_IRQn, I2C1_IRQ_IRQn, I2C2_IRQ_IRQn, I2C3_IRQ_IRQn }; + +static void check_stop_pending(machine_i2c_target_obj_t *self) { + if (self->irq_active) { + return; + } + if (self->stop_pending && !(self->i2c->I2C_STATUS & I2C_IC_STATUS_RECEIVE_FIFO_NOT_EMPTY)) { + unsigned int i2c_id = self - &machine_i2c_target_obj[0]; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + self->stop_pending = false; + self->state = STATE_IDLE; + machine_i2c_target_data_restart_or_stop(data); + } +} + +static void i2c_target_irq_handler(machine_i2c_target_obj_t *self) { + unsigned int i2c_id = self - &machine_i2c_target_obj[0]; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + I2C_Type *i2c = self->i2c; + + self->irq_active = true; + + // Get the interrupt status. + uint32_t intr_stat = i2c->I2C_RAW_INTR_STAT; + + if (intr_stat & I2C_IC_INTR_STAT_TX_ABRT) { + // Clear the TX_ABRT condition. + (void)i2c->I2C_CLR_TX_ABRT; + } + + if (intr_stat & I2C_IC_INTR_STAT_START_DET) { + // Controller sent a start condition. + // Reset all state machines in case something went wrong. + (void)i2c->I2C_CLR_START_DET; + if (self->state != STATE_IDLE) { + machine_i2c_target_data_reset_helper(data); + self->state = STATE_IDLE; + } + } + + if (intr_stat & I2C_IC_INTR_STAT_RX_FULL) { + // Data from controller is available for reading. + // Mask interrupt until I2C_DATA_CMD is read from. + i2c->I2C_INTR_MASK &= ~I2C_IC_INTR_STAT_RX_FULL; + if (self->state != STATE_WRITING) { + machine_i2c_target_data_addr_match(data, false); + } + machine_i2c_target_data_write_request(self, data); + self->state = STATE_WRITING; + } + + if (intr_stat & (I2C_IC_INTR_STAT_RD_REQ | I2C_IC_INTR_STAT_RX_DONE)) { + // Controller is requesting data. + // Note: for RX_DONE interrupt, no data needs to be written but this event is + // needed to match the expected I2CTarget event behaviour. A TX_ABTR interrupt + // will be fired after the unused byte is written to I2C_DATA_CMD, and clearing + // that abort is required to reset the hardware I2C state machine. + (void)i2c->I2C_CLR_RX_DONE; + (void)i2c->I2C_CLR_RD_REQ; + i2c->I2C_INTR_MASK &= ~I2C_IC_INTR_STAT_RD_REQ; + if (self->state != STATE_READING) { + machine_i2c_target_data_addr_match(data, true); + } + machine_i2c_target_data_read_request(self, data); + self->state = STATE_READING; + } + + if (intr_stat & I2C_IC_INTR_STAT_STOP_DET) { + // Controller has generated a stop condition. + (void)i2c->I2C_CLR_STOP_DET; + if (self->state == STATE_IDLE) { + machine_i2c_target_data_addr_match(data, false); + } + if (i2c->I2C_STATUS & I2C_IC_STATUS_RECEIVE_FIFO_NOT_EMPTY) { + self->stop_pending = true; + } else { + machine_i2c_target_data_restart_or_stop(data); + self->state = STATE_IDLE; + } + } + + self->irq_active = false; + check_stop_pending(self); +} + +void I2C0_IRQHandler(void) { + i2c_target_irq_handler(&machine_i2c_target_obj[0]); +} + +void I2C1_IRQHandler(void) { + i2c_target_irq_handler(&machine_i2c_target_obj[1]); +} + +void I2C2_IRQHandler(void) { + i2c_target_irq_handler(&machine_i2c_target_obj[2]); +} + +void I2C3_IRQHandler(void) { + i2c_target_irq_handler(&machine_i2c_target_obj[3]); +} + +static void i2c_target_init(I2C_Type *i2c, uint16_t addr, bool addr_10bit) { + i2c_disable(i2c); + + uint32_t ic_con_reg = 0; + ic_con_reg |= I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL; + ic_con_reg |= I2C_IC_CON_STOP_DET_IFADDRESSED; + if (addr_10bit) { + ic_con_reg |= I2C_SLAVE_10BIT_ADDR_MODE; + } + i2c->I2C_CON = ic_con_reg; + i2c->I2C_SAR = addr & I2C_IC_SAR_10BIT_ADDR_MASK; + i2c->I2C_TX_TL = 1; + i2c->I2C_RX_TL = 0; // interrupt when at least 1 byte is available + i2c_clear_all_interrupt(i2c); + + // Enable interrupts. + i2c->I2C_INTR_MASK = + I2C_IC_INTR_STAT_STOP_DET + | I2C_IC_INTR_STAT_RX_DONE + | I2C_IC_INTR_STAT_TX_ABRT + | I2C_IC_INTR_STAT_RD_REQ + | I2C_IC_INTR_STAT_RX_FULL + ; + + i2c_enable(i2c); + + // Enable I2C interrupts. + uint32_t irq_num = i2c_irq_num[i2c_reg_base_to_index(i2c)]; + NVIC_ClearPendingIRQ(irq_num); + NVIC_SetPriority(irq_num, IRQ_PRI_I2C); + NVIC_EnableIRQ(irq_num); +} + +static void i2c_target_deinit(I2C_Type *i2c) { + uint32_t irq_num = i2c_irq_num[i2c_reg_base_to_index(i2c)]; + NVIC_DisableIRQ(irq_num); + i2c_disable(i2c); +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self - &machine_i2c_target_obj[0]; +} + +static inline void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + I2C_Type *i2c = self->i2c; + + // Read from the RX FIFO. + size_t i = 0; + while (i < len && (i2c->I2C_STATUS & I2C_IC_STATUS_RECEIVE_FIFO_NOT_EMPTY)) { + buf[i++] = i2c->I2C_DATA_CMD; + } + + // Re-enable RX_FULL interrupt. + i2c->I2C_INTR_MASK |= I2C_IC_INTR_STAT_RX_FULL; + + check_stop_pending(self); + + return i; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + // Write to the TX FIFO. + size_t i = 0; + while (i < len && (self->i2c->I2C_STATUS & I2C_IC_STATUS_TRANSMIT_FIFO_NOT_FULL)) { + self->i2c->I2C_DATA_CMD = buf[i++]; + } + + // Re-enable RD_REQ interrupt. + self->i2c->I2C_INTR_MASK |= I2C_IC_INTR_STAT_RD_REQ; + + return 1; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse arguments. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid + if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_target_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2CTarget(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object. + machine_i2c_target_obj_t *self = &machine_i2c_target_obj[i2c_id]; + + // Disable I2C controller. + i2c_disable(self->i2c); + + // Initialise data. + self->state = STATE_IDLE; + self->stop_pending = false; + self->irq_active = false; + MP_STATE_PORT(machine_i2c_target_mem_obj)[i2c_id] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Set SCL/SDA pins if given. + if (args[ARG_scl].u_obj != mp_const_none) { + self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + } + if (args[ARG_sda].u_obj != mp_const_none) { + self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + } + + // Configure I2C pins. + mp_hal_pin_config(self->scl, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SCL, i2c_id), true); + mp_hal_pin_config(self->sda, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, + MP_HAL_PIN_SPEED_LOW, MP_HAL_PIN_DRIVE_8MA, MP_HAL_PIN_ALT(I2C_SDA, i2c_id), true); + + // Initialise the I2C target. + i2c_target_init(self->i2c, args[ARG_addr].u_int, args[ARG_addrsize].u_int == 10); + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2CTarget(%u, addr=%u, scl=" MP_HAL_PIN_FMT ", sda=" MP_HAL_PIN_FMT ")", + self - &machine_i2c_target_obj[0], self->i2c->I2C_SAR, mp_hal_pin_name(self->scl), mp_hal_pin_name(self->sda)); +} + +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + i2c_target_deinit(self->i2c); +} diff --git a/ports/alif/main.c b/ports/alif/main.c index ab5e85d5b9d..308d8df900e 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -31,6 +31,7 @@ #include "py/mphal.h" #include "py/stackctrl.h" #include "extmod/modbluetooth.h" +#include "extmod/modmachine.h" #include "extmod/modnetwork.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -164,6 +165,9 @@ int main(void) { #if MICROPY_PY_BLUETOOTH mp_bluetooth_deinit(); #endif + #if MICROPY_PY_MACHINE_I2C_TARGET + mp_machine_i2c_target_deinit_all(); + #endif soft_timer_deinit(); machine_pin_irq_deinit(); gc_sweep_all(); diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index ddfc551bf89..a8c8c842977 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -132,6 +132,10 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (MICROPY_HW_ENABLE_HW_I2C) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) +#define MICROPY_PY_MACHINE_I2C_TARGET (MICROPY_HW_ENABLE_HW_I2C) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/alif/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (4) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) From 67a442d8fabef4f203dd6b13ded46b3836232522 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Jul 2025 23:52:39 +1000 Subject: [PATCH 1048/2098] alif/machine_i2c: Allow changing I2C SCL/SDA pins. Signed-off-by: Damien George --- ports/alif/machine_i2c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ports/alif/machine_i2c.c b/ports/alif/machine_i2c.c index a710aeeb011..356c893dc70 100644 --- a/ports/alif/machine_i2c.c +++ b/ports/alif/machine_i2c.c @@ -125,9 +125,12 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n self->freq = args[ARG_freq].u_int; self->timeout = args[ARG_timeout].u_int; - // here we would check the scl/sda pins and configure them, but it's not implemented - if (args[ARG_scl].u_obj != mp_const_none || args[ARG_sda].u_obj != mp_const_none) { - mp_raise_ValueError(MP_ERROR_TEXT("explicit choice of scl/sda is not implemented")); + // Set SCL/SDA pins if given. + if (args[ARG_scl].u_obj != mp_const_none) { + self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + } + if (args[ARG_sda].u_obj != mp_const_none) { + self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); } // Disable I2C controller. From 5c78762c166a22d66269357bc0ffe2129586dca0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 29 May 2025 09:48:02 +0200 Subject: [PATCH 1049/2098] mimxrt/machine_i2c_target: Support I2C target mode. The functionality is similar to the RP2 implementation. The supported address size is 7 bit. In order to achieve a sufficient response, the target I2C IRQ handler has to run from RAM, causing much more code moved to RAM than required. Tested with Teensy 4.1, MIMXRT1021EVK, MIMXRT1011EVK and MIMXRT1170, using both a On-Board SoftI2C as controller and a RP2 Pico as external controller. Signed-off-by: Damien George Signed-off-by: robert-hh --- ports/mimxrt/boards/common.ld | 2 +- ports/mimxrt/machine_i2c.c | 13 +-- ports/mimxrt/machine_i2c.h | 38 +++++++ ports/mimxrt/machine_i2c_target.c | 168 ++++++++++++++++++++++++++++++ ports/mimxrt/main.c | 1 + ports/mimxrt/mpconfigport.h | 5 + 6 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 ports/mimxrt/machine_i2c.h create mode 100644 ports/mimxrt/machine_i2c_target.c diff --git a/ports/mimxrt/boards/common.ld b/ports/mimxrt/boards/common.ld index 477ba38bc89..dcbc0a42366 100644 --- a/ports/mimxrt/boards/common.ld +++ b/ports/mimxrt/boards/common.ld @@ -98,7 +98,7 @@ SECTIONS .text : { . = ALIGN(4); - *(EXCLUDE_FILE(*fsl_flexspi.o *gc.o *vm.o *parse*.o *runtime*.o *map.o *mpirq.o ) .text*) /* .text* sections (code) */ + *(EXCLUDE_FILE(*fsl_flexspi.o *gc.o *vm.o *runtime*.o *map.o *mpirq.o *machine_i2c_target.o *fsl_lpi2c.o) .text*) /* .text* sections (code) */ *(.rodata) /* .rodata sections (constants, strings, etc.) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ *(.glue_7) /* glue arm to thumb code */ diff --git a/ports/mimxrt/machine_i2c.c b/ports/mimxrt/machine_i2c.c index d170804f4f0..aa128e6ff6f 100644 --- a/ports/mimxrt/machine_i2c.c +++ b/ports/mimxrt/machine_i2c.c @@ -33,11 +33,7 @@ #include "fsl_iomuxc.h" #include "fsl_lpi2c.h" - -#define DEFAULT_I2C_ID (0) -#define DEFAULT_I2C_FREQ (400000) -#define DEFAULT_I2C_DRIVE (6) -#define DEFAULT_I2C_TIMEOUT (50000) +#include "machine_i2c.h" typedef struct _machine_i2c_obj_t { mp_obj_base_t base; @@ -57,12 +53,11 @@ typedef struct _iomux_table_t { uint32_t configRegister; } iomux_table_t; -static const uint8_t i2c_index_table[] = MICROPY_HW_I2C_INDEX; -static LPI2C_Type *i2c_base_ptr_table[] = LPI2C_BASE_PTRS; +const uint8_t i2c_index_table[] = MICROPY_HW_I2C_INDEX; +LPI2C_Type *i2c_base_ptr_table[] = LPI2C_BASE_PTRS; +const uint8_t micropy_hw_i2c_num = MICROPY_HW_I2C_NUM; static const iomux_table_t iomux_table[] = { IOMUX_TABLE_I2C }; -#define MICROPY_HW_I2C_NUM ARRAY_SIZE(i2c_index_table) - #define SCL (iomux_table[index]) #define SDA (iomux_table[index + 1]) diff --git a/ports/mimxrt/machine_i2c.h b/ports/mimxrt/machine_i2c.h new file mode 100644 index 00000000000..c6d561ca585 --- /dev/null +++ b/ports/mimxrt/machine_i2c.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Damien P. George + * Copyright (c) 2025 Robert Hammelrath + * + * 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. + */ + +#define DEFAULT_I2C_ID (0) +#define DEFAULT_I2C_FREQ (400000) +#define DEFAULT_I2C_DRIVE (6) +#define DEFAULT_I2C_TIMEOUT (50000) +#define DEFAULT_I2C_FILTER_NS (200) +#define MICROPY_HW_I2C_NUM ARRAY_SIZE(i2c_index_table) + +extern const uint8_t i2c_index_table[]; +extern LPI2C_Type *i2c_base_ptr_table[]; +extern bool lpi2c_set_iomux(int8_t hw_i2c, uint8_t drive); +extern const uint8_t micropy_hw_i2c_num; diff --git a/ports/mimxrt/machine_i2c_target.c b/ports/mimxrt/machine_i2c_target.c new file mode 100644 index 00000000000..aa408071f31 --- /dev/null +++ b/ports/mimxrt/machine_i2c_target.c @@ -0,0 +1,168 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Damien P. George + * Copyright (c) 2025 Robert Hammelrath + * + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include "fsl_lpi2c.h" +#include "machine_i2c.h" +#include CLOCK_CONFIG_H + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + LPI2C_Type *i2c_inst; + uint8_t i2c_id; + uint8_t addr; + lpi2c_slave_config_t slave_config; + lpi2c_slave_handle_t handle; +} machine_i2c_target_obj_t; + +static void lpi2c_slave_callback(LPI2C_Type *base, lpi2c_slave_transfer_t *xfer, void *param) { + machine_i2c_target_obj_t *self = (machine_i2c_target_obj_t *)param; + machine_i2c_target_data_t *data = &machine_i2c_target_data[self->i2c_id]; + + switch (xfer->event) { + case kLPI2C_SlaveAddressMatchEvent: + // Controller addressed us. + machine_i2c_target_data_addr_match(data, xfer->receivedAddress & 1); + break; + case kLPI2C_SlaveReceiveEvent: + // Data from controller is available for reading. + machine_i2c_target_data_write_request(self, data); + break; + case kLPI2C_SlaveTransmitEvent: + // Controller is requesting data. + machine_i2c_target_data_read_request(self, data); + break; + case kLPI2C_SlaveCompletionEvent: + // Transfer done. + machine_i2c_target_data_stop(data); + break; + default: + break; + } +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self->i2c_id; +} + +static inline void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + // LPI2C_Type *i2c_inst = self->i2c_inst; + // mp_int_t i = 0; + // mp_int_t val = 0; + // while (i < len && !((val = i2c_inst->SRDR) & LPI2C_SRDR_RXEMPTY_MASK)) { + // buf[i++] = (uint8_t)(val & 0xff); + // } + // return i; + // Simple and fast version for len == 1 + buf[0] = (uint8_t)(self->i2c_inst->SRDR); + return 1; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + self->i2c_inst->STDR = buf[0]; + return 1; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_drive }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = DEFAULT_I2C_ID} }, + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_drive, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_DRIVE} }, + }; + + // Parse arguments. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus. + int i2c_id = args[ARG_id].u_int; + if (i2c_id < 0 || i2c_id >= micropy_hw_i2c_num || i2c_index_table[i2c_id] == 0) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + int i2c_hw_id = i2c_index_table[i2c_id]; // the hw i2c number 1..n + + // Get I2C Object. + machine_i2c_target_obj_t *self = mp_obj_malloc_with_finaliser(machine_i2c_target_obj_t, &machine_i2c_target_type); + self->i2c_id = i2c_id; + self->i2c_inst = i2c_base_ptr_table[i2c_hw_id]; + uint8_t drive = args[ARG_drive].u_int; + if (drive < 1 || drive > 7) { + drive = DEFAULT_I2C_DRIVE; + } + // Set the target address. + self->addr = args[ARG_addr].u_int; + // Initialise data. + MP_STATE_PORT(machine_i2c_target_mem_obj)[i2c_id] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[self->i2c_id]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Initialise the GPIO pins + lpi2c_set_iomux(i2c_hw_id, drive); + // Initialise the I2C peripheral + LPI2C_SlaveGetDefaultConfig(&self->slave_config); + self->slave_config.address0 = self->addr; + self->slave_config.sdaGlitchFilterWidth_ns = DEFAULT_I2C_FILTER_NS; + self->slave_config.sclGlitchFilterWidth_ns = DEFAULT_I2C_FILTER_NS; + LPI2C_SlaveInit(self->i2c_inst, &self->slave_config, BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT); + // Create the LPI2C handle for the non-blocking transfer + LPI2C_SlaveTransferCreateHandle(self->i2c_inst, &self->handle, lpi2c_slave_callback, self); + // Start accepting I2C transfers on the LPI2C slave peripheral + status_t reVal = LPI2C_SlaveTransferNonBlocking(self->i2c_inst, &self->handle, + kLPI2C_SlaveAddressMatchEvent | kLPI2C_SlaveTransmitEvent | kLPI2C_SlaveReceiveEvent | kLPI2C_SlaveCompletionEvent); + if (reVal != kStatus_Success) { + mp_raise_ValueError(MP_ERROR_TEXT("cannot start I2C")); + } + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2CTarget(%u, addr=%u)", self->i2c_id, self->addr); +} + +// Stop the Slave transfer and free the memory objects. +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + LPI2C_SlaveDeinit(self->i2c_inst); +} diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 6b9d4fa17af..7166171f17c 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -55,6 +55,7 @@ #endif #include "systick.h" +#include "extmod/modmachine.h" #include "extmod/modnetwork.h" #include "extmod/vfs.h" diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index e1c605f452a..8ceb3b41837 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -92,6 +92,11 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/mimxrt/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_I2C_TARGET (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/mimxrt/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (FSL_FEATURE_SOC_LPI2C_COUNT) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_FINALISER (1) #ifndef MICROPY_PY_MACHINE_I2S #define MICROPY_PY_MACHINE_I2S (0) #endif From 79d182deb28a478413c187e1db33681a46e1d20e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 5 Jun 2025 20:48:25 +0200 Subject: [PATCH 1050/2098] samd/machine_i2c_target: Support I2C target mode. Supporting readfrom_mem*(). writeto_mem() and a set of IRQs. Enabled by default for SAMD51 devices and SAMD21 devices with external flash. Tested with ItsyBitsy M4 and ItsyBitsy M0 with both on-board SoftI2C and a RP2 Pico as controller. Signed-off-by: Damien George Signed-off-by: robert-hh --- .../ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h | 1 + ports/samd/machine_i2c.c | 38 ++-- ports/samd/machine_i2c_target.c | 213 ++++++++++++++++++ ports/samd/main.c | 3 +- ports/samd/mcu/samd21/mpconfigmcu.h | 3 + ports/samd/mcu/samd51/mpconfigmcu.h | 3 + ports/samd/mpconfigport.h | 4 + ports/samd/pin_af.c | 16 ++ ports/samd/pin_af.h | 2 + ports/samd/samd_soc.c | 2 +- 10 files changed, 259 insertions(+), 26 deletions(-) create mode 100644 ports/samd/machine_i2c_target.c diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h index eb4704ff8cb..bf44bd661c0 100644 --- a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h @@ -8,6 +8,7 @@ #define MICROPY_PY_MACHINE_SOFTI2C (0) #define MICROPY_PY_MACHINE_SOFTSPI (0) #define MICROPY_PY_MACHINE_I2C (0) +#define MICROPY_PY_MACHINE_I2C_TARGET (0) #define MICROPY_PY_MACHINE_SPI (0) #define MICROPY_PY_MACHINE_UART (0) #define MICROPY_PY_MACHINE_ADC (0) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 172518523d2..50548b62a7d 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -29,13 +29,11 @@ #if MICROPY_PY_MACHINE_I2C -#include "py/mphal.h" #include "py/mperrno.h" #include "extmod/modmachine.h" #include "samd_soc.h" #include "pin_af.h" #include "genhdr/pins.h" -#include "clock_config.h" #define DEFAULT_I2C_FREQ (400000) #define RISETIME_NS (200) @@ -79,9 +77,9 @@ static void i2c_send_command(Sercom *i2c, uint8_t command) { } void common_i2c_irq_handler(int i2c_id) { - // handle Sercom I2C IRQ + // Handle Sercom I2C IRQ for controller mode. machine_i2c_obj_t *self = MP_STATE_PORT(sercom_table[i2c_id]); - // Handle IRQ + if (self != NULL) { Sercom *i2c = self->instance; // For now, clear all interrupts @@ -114,7 +112,8 @@ void common_i2c_irq_handler(int i2c_id) { } else { // On any error, e.g. ARBLOST or BUSERROR, stop the transmission self->len = 0; self->state = state_buserr; - i2c->I2CM.INTFLAG.reg |= SERCOM_I2CM_INTFLAG_ERROR; + i2c->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR | + SERCOM_I2CM_INTFLAG_SB | SERCOM_I2CM_INTFLAG_MB; } } } @@ -158,28 +157,19 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n // Get the peripheral object. machine_i2c_obj_t *self = mp_obj_malloc(machine_i2c_obj_t, &machine_i2c_type); self->id = id; - self->instance = sercom_instance[self->id]; + self->instance = sercom_instance[id]; // Set SCL/SDA pins. - self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); - self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + self->sda = pin_config_for_i2c(args[ARG_sda].u_obj, id, 0); + self->scl = pin_config_for_i2c(args[ARG_scl].u_obj, id, 1); + MP_STATE_PORT(sercom_table[id]) = self; - sercom_pad_config_t scl_pad_config = get_sercom_config(self->scl, self->id); - sercom_pad_config_t sda_pad_config = get_sercom_config(self->sda, self->id); - if (sda_pad_config.pad_nr != 0 || scl_pad_config.pad_nr != 1) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid sda/scl pin")); - } - MP_STATE_PORT(sercom_table[self->id]) = self; self->freq = args[ARG_freq].u_int; // The unit for ARG_timeout is us, but the code uses ms. self->timeout = args[ARG_timeout].u_int / 1000; - // Configure the Pin mux. - mp_hal_set_pin_mux(self->scl, scl_pad_config.alt_fct); - mp_hal_set_pin_mux(self->sda, sda_pad_config.alt_fct); - // Set up the clocks - enable_sercom_clock(self->id); + enable_sercom_clock(id); // Initialise the I2C peripheral Sercom *i2c = self->instance; @@ -207,13 +197,13 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n i2c->I2CM.BAUD.reg = baud; // Enable interrupts - sercom_register_irq(self->id, &common_i2c_irq_handler); + sercom_register_irq(id, &common_i2c_irq_handler); #if defined(MCU_SAMD21) - NVIC_EnableIRQ(SERCOM0_IRQn + self->id); + NVIC_EnableIRQ(SERCOM0_IRQn + id); #elif defined(MCU_SAMD51) - NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id); // MB interrupt - NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id + 1); // SB interrupt - NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id + 3); // ERROR interrupt + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id); // MB interrupt + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id + 1); // SB interrupt + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id + 3); // ERROR interrupt #endif // Now enable I2C. diff --git a/ports/samd/machine_i2c_target.c b/ports/samd/machine_i2c_target.c new file mode 100644 index 00000000000..054ca81dd1a --- /dev/null +++ b/ports/samd/machine_i2c_target.c @@ -0,0 +1,213 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * Copyright (c) 2022-2025 Robert Hammelrath + * + * 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 "samd_soc.h" +#include "pin_af.h" +#include "genhdr/pins.h" + +#define TRANSMIT (1) +#define RECEIVE (0) +#define NACK_RECVD (i2c->I2CS.STATUS.bit.RXNACK == 1) +#define IRQ_AMATCH (i2c->I2CS.INTFLAG.bit.AMATCH == 1) +#define IRQ_DRDY (i2c->I2CS.INTFLAG.bit.DRDY == 1) +#define IRQ_STOP (i2c->I2CS.INTFLAG.bit.PREC == 1) + +#define PREPARE_ACK i2c->I2CS.CTRLB.bit.ACKACT = 0 +#define PREPARE_NACK i2c->I2CS.CTRLB.bit.ACKACT = 1 + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + Sercom *instance; + uint8_t id; + uint8_t scl; + uint8_t sda; + uint8_t addr; + uint8_t direction; +} machine_i2c_target_obj_t; + +void common_i2c_target_irq_handler(int i2c_id) { + // Handle Sercom I2C IRQ for target memory mode. + machine_i2c_target_obj_t *self = MP_STATE_PORT(sercom_table[i2c_id]); + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + + if (self != NULL) { + Sercom *i2c = self->instance; + + if (IRQ_AMATCH) { + // Address match. + self->direction = i2c->I2CS.STATUS.bit.DIR; + machine_i2c_target_data_addr_match(data, self->direction); + // Send ACK + i2c->I2CS.CTRLB.bit.CMD = 3; + + } else if (IRQ_DRDY) { + // Data to be handled, depending in the direction + if (self->direction == TRANSMIT) { + machine_i2c_target_data_read_request(self, data); + } else { + machine_i2c_target_data_write_request(self, data); + } + // ACK will be sent in mp_machine_i2c_target_read_bytes/mp_machine_i2c_target_write_bytes. + } else if (IRQ_STOP) { + // Stop detected. Just reset the data machine. + machine_i2c_target_data_stop(data); + i2c->I2CS.INTFLAG.reg |= SERCOM_I2CS_INTFLAG_PREC; + + } else { // On any error clear the interrupts and reset the data state. + machine_i2c_target_data_stop(data); + i2c->I2CS.INTFLAG.reg = SERCOM_I2CS_INTFLAG_ERROR | SERCOM_I2CS_INTFLAG_AMATCH | + SERCOM_I2CS_INTFLAG_DRDY | SERCOM_I2CS_INTFLAG_PREC; + } + } +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self->id; +} + +static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + Sercom *i2c = self->instance; + buf[0] = i2c->I2CS.DATA.reg; + i2c->I2CS.CTRLB.bit.CMD = 3; // send ACK + return 1; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + Sercom *i2c = self->instance; + i2c->I2CS.DATA.reg = buf[0]; + i2c->I2CS.CTRLB.bit.CMD = 3; // send ACK + return 1; +} + +static inline void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { + (void)self; + (void)trigger; +} + +mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + #if MICROPY_HW_DEFAULT_I2C_ID < 0 + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + #else + { MP_QSTR_id, MP_ARG_INT, {.u_int = MICROPY_HW_DEFAULT_I2C_ID} }, + #endif + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + #if defined(pin_SCL) && defined(pin_SDA) + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = pin_SCL} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = pin_SDA} }, + #else + { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + #endif + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus. + int id = args[ARG_id].u_int; + if (id < 0 || id >= SERCOM_INST_NUM) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), id); + } + + // Get the peripheral object. + machine_i2c_target_obj_t *self = mp_obj_malloc_with_finaliser(machine_i2c_target_obj_t, &machine_i2c_target_type); + self->id = id; + self->instance = sercom_instance[id]; + + // Set SCL/SDA pins. + self->sda = pin_config_for_i2c(args[ARG_sda].u_obj, id, 0); + self->scl = pin_config_for_i2c(args[ARG_scl].u_obj, id, 1); + + MP_STATE_PORT(sercom_table[id]) = self; + + // Get the address and initialise data. + self->addr = args[ARG_addr].u_int; + MP_STATE_PORT(machine_i2c_target_mem_obj)[id] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[id]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Set up the clocks + enable_sercom_clock(id); + + // Initialise the I2C peripheral + Sercom *i2c = self->instance; + // Reset the device + i2c->I2CS.CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST; + while (i2c->I2CS.SYNCBUSY.bit.SWRST == 1) { + } + + // Set to slave mode, enable SCl timeout, set the address + i2c->I2CS.CTRLA.reg = SERCOM_I2CS_CTRLA_MODE(0x04) + | SERCOM_I2CS_CTRLA_SEXTTOEN | SERCOM_I2CS_CTRLA_LOWTOUTEN; + i2c->I2CS.ADDR.reg = self->addr << 1; + + // Enable interrupts + sercom_register_irq(id, &common_i2c_target_irq_handler); + #if defined(MCU_SAMD21) + NVIC_EnableIRQ(SERCOM0_IRQn + id); + #elif defined(MCU_SAMD51) + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id); + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id + 1); + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id + 2); + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * id + 3); + #endif + i2c->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_DRDY | SERCOM_I2CS_INTENSET_AMATCH | + SERCOM_I2CS_INTENSET_PREC | SERCOM_I2CS_INTENSET_ERROR; + + // Now enable I2C. + sercom_enable(i2c, 1); + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2C(%u, scl=\"%q\", sda=\"%q\", addr=%u)", + self->id, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name, + self->addr); +} + +// Stop the Slave transfer and free the memory objects. +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + // Disable I2C + sercom_enable(self->instance, 0); + MP_STATE_PORT(sercom_table[self->id]) = NULL; +} diff --git a/ports/samd/main.c b/ports/samd/main.c index a7da95582f7..475f57703d0 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -30,6 +30,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "py/stackctrl.h" +#include "extmod/modmachine.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" #include "shared/runtime/pyexec.h" @@ -101,7 +102,7 @@ void samd_main(void) { mp_usbd_deinit(); #endif gc_sweep_all(); - #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART + #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_I2C_TARGET || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART sercom_deinit_all(); #endif mp_deinit(); diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index f0a7a73e0c0..a29d5c0a04d 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -78,6 +78,9 @@ unsigned long trng_random_u32(int delay); #ifndef MICROPY_PY_ONEWIRE #define MICROPY_PY_ONEWIRE (SAMD21_EXTRA_FEATURES) #endif +#ifndef MICROPY_PY_MACHINE_I2C_TARGET +#define MICROPY_PY_MACHINE_I2C_TARGET (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 8cce90b886c..a1ff208eb59 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -16,6 +16,9 @@ #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); #define MICROPY_PY_MACHINE_UART_IRQ (1) +#ifndef MICROPY_PY_MACHINE_I2C_TARGET +#define MICROPY_PY_MACHINE_I2C_TARGET (1) +#endif // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 514f3839488..7b423bf0bab 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -127,6 +127,10 @@ #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/samd/machine_wdt.c" #define MICROPY_PY_MACHINE_WDT_TIMEOUT_MS (1) #define MICROPY_PLATFORM_VERSION "ASF4" +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/samd/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (SERCOM_INST_NUM) +#define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) +#define MICROPY_PY_MACHINE_I2C_TARGET_FINALISER (1) #define MP_STATE_PORT MP_STATE_VM diff --git a/ports/samd/pin_af.c b/ports/samd/pin_af.c index 5d05b6d18d5..35cac27aa57 100644 --- a/ports/samd/pin_af.c +++ b/ports/samd/pin_af.c @@ -161,3 +161,19 @@ pwm_config_t get_pwm_config(int pin_id, int wanted_dev, uint8_t device_status[]) } #endif + +#if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_I2C_TARGET + +// Configure a I2C pin. Used by machine_i2c.c and machine_i2c_target.c. +uint8_t pin_config_for_i2c(mp_obj_t pin_obj, uint8_t id, uint8_t pad_nr) { + uint8_t pin = mp_hal_get_pin_obj(pin_obj); + sercom_pad_config_t pad_config = get_sercom_config(pin, id); + if (pad_config.pad_nr != pad_nr) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid sda/scl pin")); + } + // Configure the Pin mux. + mp_hal_set_pin_mux(pin, pad_config.alt_fct); + return pin; +} + +#endif diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h index 83839a05032..bc65e8ae23b 100644 --- a/ports/samd/pin_af.h +++ b/ports/samd/pin_af.h @@ -100,3 +100,5 @@ adc_config_t get_adc_config(int pin_id, int32_t flag); pwm_config_t get_pwm_config(int pin_id, int wanted_dev, uint8_t used_dev[]); const machine_pin_obj_t *pin_find_by_id(int pin_id); const machine_pin_obj_t *pin_find(mp_obj_t pin); + +uint8_t pin_config_for_i2c(mp_obj_t pin_obj, uint8_t id, uint8_t pad_nr); diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index e78032513c2..fb6eb2083a3 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -122,7 +122,7 @@ void samd_init(void) { machine_rtc_start(false); } -#if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART +#if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_I2C_TARGET || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART Sercom *sercom_instance[] = SERCOM_INSTS; MP_REGISTER_ROOT_POINTER(void *sercom_table[SERCOM_INST_NUM]); From ac5b1bce9986775e787a24f77f827c4ad50ffff5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Jul 2025 11:26:42 +1000 Subject: [PATCH 1051/2098] esp32/machine_i2c: Factor default pin macros to header file. So the implementation of I2CTarget can use them. Signed-off-by: Damien George --- ports/esp32/machine_i2c.c | 21 +--------------- ports/esp32/machine_i2c.h | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 ports/esp32/machine_i2c.h diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 4a5fd717631..a9e5f0d3b32 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -28,32 +28,13 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/modmachine.h" +#include "machine_i2c.h" #include "driver/i2c.h" #include "hal/i2c_ll.h" #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C -#ifndef MICROPY_HW_I2C0_SCL -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 -#define MICROPY_HW_I2C0_SCL (GPIO_NUM_9) -#define MICROPY_HW_I2C0_SDA (GPIO_NUM_8) -#else -#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) -#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) -#endif -#endif - -#ifndef MICROPY_HW_I2C1_SCL -#if CONFIG_IDF_TARGET_ESP32 -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) -#else -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) -#endif -#endif - #if SOC_I2C_SUPPORT_XTAL #if CONFIG_XTAL_FREQ > 0 #define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) diff --git a/ports/esp32/machine_i2c.h b/ports/esp32/machine_i2c.h new file mode 100644 index 00000000000..299baa6dd68 --- /dev/null +++ b/ports/esp32/machine_i2c.h @@ -0,0 +1,52 @@ + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2025 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. + */ +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_I2C_H +#define MICROPY_INCLUDED_ESP32_MACHINE_I2C_H + +// Configure default I2C0 pins. +#ifndef MICROPY_HW_I2C0_SCL +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_8) +#else +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) +#endif +#endif + +// Configure default I2C1 pins. +#ifndef MICROPY_HW_I2C1_SCL +#if CONFIG_IDF_TARGET_ESP32 +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) +#else +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) +#endif +#endif + +#endif // MICROPY_INCLUDED_ESP32_MACHINE_I2C_H From 7bc83afee2115d1a470bc2e901d998b63cef7b25 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Jul 2025 15:20:19 +1000 Subject: [PATCH 1052/2098] esp32/machine_i2c_target: Implement I2CTarget class. Only soft IRQs are supported. Signed-off-by: Damien George --- ports/esp32/boards/sdkconfig.base | 4 + ports/esp32/machine_i2c_target.c | 225 ++++++++++++++++++++++++++++++ ports/esp32/main.c | 5 + ports/esp32/mpconfigport.h | 4 + 4 files changed, 238 insertions(+) create mode 100644 ports/esp32/machine_i2c_target.c diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 69abc63bdf0..2f1835c9242 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -139,3 +139,7 @@ CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=n # Further limit total sockets in TIME-WAIT when there are many short-lived # connections. CONFIG_LWIP_MAX_ACTIVE_TCP=12 + +# Enable new I2C slave API, and disable conflict check. +CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y +CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2=y diff --git a/ports/esp32/machine_i2c_target.c b/ports/esp32/machine_i2c_target.c new file mode 100644 index 00000000000..84795e6f138 --- /dev/null +++ b/ports/esp32/machine_i2c_target.c @@ -0,0 +1,225 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_i2c_target.c via MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE. + +#include "machine_i2c.h" +#include "driver/i2c_slave.h" + +// These headers are needed to call i2c_ll_txfifo_rst(). +#include "hal/i2c_ll.h" +#include "../i2c_private.h" + +typedef struct _machine_i2c_target_obj_t { + mp_obj_base_t base; + i2c_slave_dev_handle_t handle; + i2c_slave_config_t config; + uint8_t state; + bool stop_pending; + bool irq_active; + int index; + const i2c_slave_rx_done_event_data_t *rx_done_event_data; +} machine_i2c_target_obj_t; + +static machine_i2c_target_obj_t machine_i2c_target_obj[I2C_NUM_MAX]; + +/******************************************************************************/ +// ESP-IDF hardware bindings + +// Called when the controller is about to read from the TX/send buffer. +static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg) { + machine_i2c_target_obj_t *self = arg; + machine_i2c_target_data_t *data = &machine_i2c_target_data[self->config.i2c_port]; + + // Flush hardware TX FIFO to get rid of any data from a previous read. + i2c_ll_txfifo_rst(self->handle->base->hal.dev); + + // Perform an entire read transaction, including start, read and stop events. + // We don't know how much data the controller will read, so write the entire + // memory buffer to the TX FIFO. + machine_i2c_target_data_addr_match(data, true); + for (uint32_t i = 0; i < data->mem_len; ++i) { + machine_i2c_target_data_read_request(self, data); + } + machine_i2c_target_data_restart_or_stop(data); + + // A higher priority task was not woken up. + return false; +} + +// Called when the controller has written into the RX/receive buffer. +static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg) { + machine_i2c_target_obj_t *self = arg; + machine_i2c_target_data_t *data = &machine_i2c_target_data[self->config.i2c_port]; + + // Perform an entire write transaction, including start, read and stop events. + machine_i2c_target_data_addr_match(data, false); + self->index = 0; + self->rx_done_event_data = evt_data; + while (self->index < self->rx_done_event_data->length) { + machine_i2c_target_data_write_request(self, data); + } + machine_i2c_target_data_restart_or_stop(data); + + // A higher priority task was not woken up. + return false; +} + +static void i2c_target_init(machine_i2c_target_obj_t *self, machine_i2c_target_data_t *data, uint32_t addr, uint32_t addrsize, bool first_init) { + if (!first_init && self->handle != NULL) { + i2c_del_slave_device(self->handle); + self->handle = NULL; + } + + self->config.clk_source = I2C_CLK_SRC_DEFAULT; + self->config.slave_addr = addr; + self->config.send_buf_depth = data->mem_len; + self->config.receive_buf_depth = data->mem_len; + if (addrsize == 7) { + self->config.addr_bit_len = I2C_ADDR_BIT_LEN_7; + } else { + #if SOC_I2C_SUPPORT_10BIT_ADDR + self->config.addr_bit_len = I2C_ADDR_BIT_LEN_10; + #else + mp_raise_ValueError(MP_ERROR_TEXT("10-bit address unsupported")); + #endif + } + self->config.intr_priority = 0; // 0 selects the default + self->config.flags.allow_pd = 0; + self->config.flags.enable_internal_pullup = 1; + + ESP_ERROR_CHECK(i2c_new_slave_device(&self->config, &self->handle)); + i2c_slave_event_callbacks_t cbs = { + .on_receive = i2c_slave_receive_cb, + .on_request = i2c_slave_request_cb, + }; + ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(self->handle, &cbs, self)); +} + +/******************************************************************************/ +// I2CTarget port implementation + +static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *self) { + return self->config.i2c_port; +} + +static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { + mp_irq_handler(&irq->base); +} + +static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { + size_t i = 0; + while (i < len && self->index < self->rx_done_event_data->length) { + buf[i++] = self->rx_done_event_data->buffer[self->index++]; + } + return i; +} + +static size_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf) { + uint32_t write_len; + i2c_slave_write(self->handle, buf, len, &write_len, 1000); + return write_len; +} + +static void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger) { +} + +static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_addr, ARG_addrsize, ARG_mem, ARG_mem_addrsize, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 7} }, + { MP_QSTR_mem, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mem_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid + if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_target_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2CTarget(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object. + machine_i2c_target_obj_t *self = &machine_i2c_target_obj[i2c_id]; + + bool first_init = false; + if (self->base.type == NULL) { + // Created for the first time, set default pins + self->base.type = &machine_i2c_target_type; + self->config.i2c_port = i2c_id; + if (self->config.i2c_port == 0) { + self->config.scl_io_num = MICROPY_HW_I2C0_SCL; + self->config.sda_io_num = MICROPY_HW_I2C0_SDA; + } else { + self->config.scl_io_num = MICROPY_HW_I2C1_SCL; + self->config.sda_io_num = MICROPY_HW_I2C1_SDA; + } + first_init = true; + } + + // Initialise data. + self->state = STATE_IDLE; + self->stop_pending = false; + self->irq_active = false; + MP_STATE_PORT(machine_i2c_target_mem_obj)[i2c_id] = args[ARG_mem].u_obj; + machine_i2c_target_data_t *data = &machine_i2c_target_data[i2c_id]; + machine_i2c_target_data_init(data, args[ARG_mem].u_obj, args[ARG_mem_addrsize].u_int); + + // Set SCL/SDA pins if configured. + if (args[ARG_scl].u_obj != mp_const_none) { + self->config.scl_io_num = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + } + if (args[ARG_sda].u_obj != mp_const_none) { + self->config.sda_io_num = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + } + + // Initialise the I2C target. + i2c_target_init(self, data, args[ARG_addr].u_int, args[ARG_addrsize].u_int, first_init); + + return MP_OBJ_FROM_PTR(self); +} + +static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2CTarget(%u, addr=%u, scl=%u, sda=%u)", + self->config.i2c_port, self->config.slave_addr, self->config.scl_io_num, self->config.sda_io_num); +} + +static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self) { + if (self->handle != NULL) { + i2c_del_slave_device(self->handle); + self->handle = NULL; + } +} diff --git a/ports/esp32/main.c b/ports/esp32/main.c index f048aa85f5f..f85fb6c084f 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -51,6 +51,7 @@ #include "py/repl.h" #include "py/gc.h" #include "py/mphal.h" +#include "extmod/modmachine.h" #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "shared/timeutils/timeutils.h" @@ -199,7 +200,11 @@ void mp_task(void *pvParameter) { machine_pwm_deinit_all(); // TODO: machine_rmt_deinit_all(); machine_pins_deinit(); + #if MICROPY_PY_MACHINE_I2C_TARGET + mp_machine_i2c_target_deinit_all(); + #endif machine_deinit(); + #if MICROPY_PY_SOCKET_EVENTS socket_events_deinit(); #endif diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 142c1b1be2e..1844018030b 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -138,6 +138,10 @@ #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) +// I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs. +#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32) +#define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/esp32/machine_i2c_target.c" +#define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) From 6558d519a22154795c0da4101bc7bbd1527ed176 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 14:00:31 +1000 Subject: [PATCH 1053/2098] tests/extmod_hardware: Add self unittest for I2CTarget. This test uses a SoftI2C controller wired to an I2CTarget on the one board, and tests all functionality of the I2CTarget class. Signed-off-by: Damien George --- tests/extmod_hardware/machine_i2c_target.py | 307 ++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 tests/extmod_hardware/machine_i2c_target.py diff --git a/tests/extmod_hardware/machine_i2c_target.py b/tests/extmod_hardware/machine_i2c_target.py new file mode 100644 index 00000000000..763e6f4771e --- /dev/null +++ b/tests/extmod_hardware/machine_i2c_target.py @@ -0,0 +1,307 @@ +# Test machine.I2CTarget. +# +# IMPORTANT: This test requires hardware connections: a SoftI2C instance must be +# wired to a hardware I2C target. See pin definitions below. + +import sys + +try: + from machine import Pin, SoftI2C, I2CTarget +except ImportError: + print("SKIP") + raise SystemExit + +import unittest + +ADDR = 67 + +kwargs_target = {} + +# Configure pins based on the target. +if sys.platform == "alif" and sys.implementation._build == "ALIF_ENSEMBLE": + args_controller = {"scl": "P1_1", "sda": "P1_0"} + args_target = (0,) # on pins P0_3/P0_2 +elif sys.platform == "esp32": + args_controller = {"scl": 5, "sda": 6} + args_target = (0,) # on pins 9/8 for C3 and S3, 18/19 for others + kwargs_target = {"scl": 9, "sda": 8} +elif sys.platform == "rp2": + args_controller = {"scl": 5, "sda": 4} + args_target = (1,) +elif sys.platform == "pyboard": + if sys.implementation._build == "NUCLEO_WB55": + args_controller = {"scl": "B8", "sda": "B9"} + args_target = (3,) + else: + args_controller = {"scl": "X1", "sda": "X2"} + args_target = ("X",) +elif "zephyr-nucleo_wb55rg" in sys.implementation._machine: + # PB8=I2C1_SCL, PB9=I2C1_SDA (on Arduino header D15/D14) + # PC0=I2C3_SCL, PC1=I2C3_SDA (on Arduino header A0/A1) + args_controller = {"scl": Pin(("gpiob", 8)), "sda": Pin(("gpiob", 9))} + args_target = ("i2c3",) +elif "zephyr-rpi_pico" in sys.implementation._machine: + args_controller = {"scl": Pin(("gpio0", 5)), "sda": Pin(("gpio0", 4))} + args_target = ("i2c1",) # on gpio7/gpio6 +elif sys.platform == "mimxrt": + if "Teensy" in sys.implementation._machine: + args_controller = {"scl": "A6", "sda": "A3"} # D20/D17 + else: + args_controller = {"scl": "D0", "sda": "D1"} + args_target = (0,) # pins 19/18 On Teensy 4.x +elif sys.platform == "samd": + args_controller = {"scl": "D5", "sda": "D1"} + args_target = () +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def config_pull_up(): + Pin(args_controller["scl"], Pin.OPEN_DRAIN, Pin.PULL_UP) + Pin(args_controller["sda"], Pin.OPEN_DRAIN, Pin.PULL_UP) + + +class TestMemory(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.mem = bytearray(8) + cls.i2c = SoftI2C(**args_controller) + cls.i2c_target = I2CTarget(*args_target, **kwargs_target, addr=ADDR, mem=cls.mem) + config_pull_up() + + @classmethod + def tearDownClass(cls): + cls.i2c_target.deinit() + + def test_scan(self): + self.assertIn(ADDR, self.i2c.scan()) + + def test_write(self): + self.mem[:] = b"01234567" + self.i2c.writeto_mem(ADDR, 0, b"test") + self.assertEqual(self.mem, bytearray(b"test4567")) + self.i2c.writeto_mem(ADDR, 4, b"TEST") + self.assertEqual(self.mem, bytearray(b"testTEST")) + + def test_write_wrap(self): + self.mem[:] = b"01234567" + self.i2c.writeto_mem(ADDR, 6, b"test") + self.assertEqual(self.mem, bytearray(b"st2345te")) + + @unittest.skipIf(sys.platform == "esp32", "write lengths larger than buffer unsupported") + def test_write_wrap_large(self): + self.mem[:] = b"01234567" + self.i2c.writeto_mem(ADDR, 0, b"testTESTmore") + self.assertEqual(self.mem, bytearray(b"moreTEST")) + + def test_read(self): + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.readfrom_mem(ADDR, 0, 4), b"0123") + self.assertEqual(self.i2c.readfrom_mem(ADDR, 4, 4), b"4567") + + def test_read_wrap(self): + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.readfrom_mem(ADDR, 0, 4), b"0123") + self.assertEqual(self.i2c.readfrom_mem(ADDR, 2, 4), b"2345") + self.assertEqual(self.i2c.readfrom_mem(ADDR, 6, 4), b"6701") + + @unittest.skipIf(sys.platform == "esp32", "read lengths larger than buffer unsupported") + def test_read_wrap_large(self): + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.readfrom_mem(ADDR, 0, 12), b"012345670123") + + def test_write_read(self): + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.writeto(ADDR, b"\x02"), 1) + self.assertEqual(self.i2c.readfrom(ADDR, 4), b"2345") + + @unittest.skipIf(sys.platform == "esp32", "read after read unsupported") + def test_write_read_read(self): + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.writeto(ADDR, b"\x02"), 1) + self.assertEqual(self.i2c.readfrom(ADDR, 4), b"2345") + self.assertEqual(self.i2c.readfrom(ADDR, 4), b"7012") + + +@unittest.skipUnless(hasattr(I2CTarget, "IRQ_END_READ"), "IRQ unsupported") +class TestMemoryIRQ(unittest.TestCase): + @staticmethod + def irq_handler(i2c_target): + flags = i2c_target.irq().flags() + TestMemoryIRQ.events[TestMemoryIRQ.num_events] = flags + TestMemoryIRQ.events[TestMemoryIRQ.num_events + 1] = i2c_target.memaddr + TestMemoryIRQ.num_events += 2 + + @classmethod + def setUpClass(cls): + cls.mem = bytearray(8) + cls.events = [0] * 8 + cls.num_events = 0 + cls.i2c = SoftI2C(**args_controller) + cls.i2c_target = I2CTarget(*args_target, **kwargs_target, addr=ADDR, mem=cls.mem) + cls.i2c_target.irq(TestMemoryIRQ.irq_handler) + config_pull_up() + + @classmethod + def tearDownClass(cls): + cls.i2c_target.deinit() + + @unittest.skipIf(sys.platform == "esp32", "scan doesn't trigger IRQ_END_WRITE") + def test_scan(self): + TestMemoryIRQ.num_events = 0 + self.i2c.scan() + self.assertEqual(self.events[: self.num_events], [I2CTarget.IRQ_END_WRITE, 0]) + + def test_write(self): + TestMemoryIRQ.num_events = 0 + self.mem[:] = b"01234567" + self.i2c.writeto_mem(ADDR, 2, b"test") + self.assertEqual(self.mem, bytearray(b"01test67")) + self.assertEqual(self.events[: self.num_events], [I2CTarget.IRQ_END_WRITE, 2]) + + def test_read(self): + TestMemoryIRQ.num_events = 0 + self.mem[:] = b"01234567" + self.assertEqual(self.i2c.readfrom_mem(ADDR, 2, 4), b"2345") + self.assertEqual(self.events[: self.num_events], [I2CTarget.IRQ_END_READ, 2]) + + +@unittest.skipUnless(hasattr(I2CTarget, "IRQ_WRITE_REQ"), "IRQ unsupported") +@unittest.skipIf(sys.platform == "mimxrt", "not working") +@unittest.skipIf(sys.platform == "pyboard", "can't queue more than one byte") +@unittest.skipIf(sys.platform == "samd", "not working") +@unittest.skipIf(sys.platform == "zephyr", "must call readinto/write in IRQ handler") +class TestPolling(unittest.TestCase): + @staticmethod + def irq_handler(i2c_target, buf=bytearray(1)): + flags = i2c_target.irq().flags() + if flags & I2CTarget.IRQ_READ_REQ: + i2c_target.write(b"0123") + + @classmethod + def setUpClass(cls): + cls.i2c = SoftI2C(**args_controller) + cls.i2c_target = I2CTarget(*args_target, addr=ADDR) + cls.i2c_target.irq( + TestPolling.irq_handler, + I2CTarget.IRQ_WRITE_REQ | I2CTarget.IRQ_READ_REQ, + hard=True, + ) + config_pull_up() + + @classmethod + def tearDownClass(cls): + cls.i2c_target.deinit() + + def test_read(self): + # Can't write data up front, must wait until IRQ_READ_REQ. + # self.assertEqual(self.i2c_target.write(b"abcd"), 4) + self.assertEqual(self.i2c.readfrom(ADDR, 4), b"0123") + + def test_write(self): + # Can do the read outside the IRQ, but requires IRQ_WRITE_REQ trigger to be set. + self.assertEqual(self.i2c.writeto(ADDR, b"0123"), 4) + buf = bytearray(8) + self.assertEqual(self.i2c_target.readinto(buf), 4) + self.assertEqual(buf, b"0123\x00\x00\x00\x00") + + +@unittest.skipUnless(hasattr(I2CTarget, "IRQ_ADDR_MATCH_READ"), "IRQ unsupported") +class TestIRQ(unittest.TestCase): + @staticmethod + def irq_handler(i2c_target, buf=bytearray(1)): + flags = i2c_target.irq().flags() + TestIRQ.events[TestIRQ.num_events] = flags + TestIRQ.num_events += 1 + if flags & I2CTarget.IRQ_READ_REQ: + i2c_target.write(b"Y") + if flags & I2CTarget.IRQ_WRITE_REQ: + i2c_target.readinto(buf) + TestIRQ.events[TestIRQ.num_events] = buf[0] + TestIRQ.num_events += 1 + + @classmethod + def setUpClass(cls): + cls.events = [0] * 8 + cls.num_events = 0 + cls.i2c = SoftI2C(**args_controller) + cls.i2c_target = I2CTarget(*args_target, addr=ADDR) + cls.i2c_target.irq( + TestIRQ.irq_handler, + I2CTarget.IRQ_ADDR_MATCH_READ + | I2CTarget.IRQ_ADDR_MATCH_WRITE + | I2CTarget.IRQ_WRITE_REQ + | I2CTarget.IRQ_READ_REQ + | I2CTarget.IRQ_END_READ + | I2CTarget.IRQ_END_WRITE, + hard=True, + ) + config_pull_up() + + @classmethod + def tearDownClass(cls): + cls.i2c_target.deinit() + + def test_scan(self): + TestIRQ.num_events = 0 + self.i2c.scan() + self.assertEqual( + self.events[: self.num_events], + [ + I2CTarget.IRQ_ADDR_MATCH_WRITE, + I2CTarget.IRQ_END_WRITE, + ], + ) + + def test_write(self): + TestIRQ.num_events = 0 + self.i2c.writeto(ADDR, b"XYZ") + self.assertEqual( + self.events[: self.num_events], + [ + I2CTarget.IRQ_ADDR_MATCH_WRITE, + I2CTarget.IRQ_WRITE_REQ, + ord(b"X"), + I2CTarget.IRQ_WRITE_REQ, + ord(b"Y"), + I2CTarget.IRQ_WRITE_REQ, + ord(b"Z"), + I2CTarget.IRQ_END_WRITE, + ], + ) + + def test_read(self): + TestIRQ.num_events = 0 + self.assertEqual(self.i2c.readfrom(ADDR, 1), b"Y") + self.assertEqual( + self.events[: self.num_events], + [ + I2CTarget.IRQ_ADDR_MATCH_READ, + I2CTarget.IRQ_READ_REQ, + I2CTarget.IRQ_READ_REQ, + I2CTarget.IRQ_END_READ, + ], + ) + + def test_write_read(self): + TestIRQ.num_events = 0 + self.i2c.writeto(ADDR, b"X", False) + self.assertEqual(self.i2c.readfrom(ADDR, 1), b"Y") + self.assertEqual( + self.events[: self.num_events], + [ + I2CTarget.IRQ_ADDR_MATCH_WRITE, + I2CTarget.IRQ_WRITE_REQ, + ord(b"X"), + I2CTarget.IRQ_END_WRITE, + I2CTarget.IRQ_ADDR_MATCH_READ, + I2CTarget.IRQ_READ_REQ, + I2CTarget.IRQ_READ_REQ, + I2CTarget.IRQ_END_READ, + ], + ) + + +if __name__ == "__main__": + unittest.main() From 277b615f261e3ab9c9c19d8b6d2afb6c5f9a5612 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Jul 2025 18:21:06 +1000 Subject: [PATCH 1054/2098] tests/multi_extmod: Add I2CTarget multi tests. These require two boards wired together, SCL-SCL and SDA-SDA. Signed-off-by: Damien George --- tests/multi_extmod/machine_i2c_target_irq.py | 137 ++++++++++++++++++ .../machine_i2c_target_irq.py.exp | 15 ++ .../multi_extmod/machine_i2c_target_memory.py | 79 ++++++++++ .../machine_i2c_target_memory.py.exp | 16 ++ 4 files changed, 247 insertions(+) create mode 100644 tests/multi_extmod/machine_i2c_target_irq.py create mode 100644 tests/multi_extmod/machine_i2c_target_irq.py.exp create mode 100644 tests/multi_extmod/machine_i2c_target_memory.py create mode 100644 tests/multi_extmod/machine_i2c_target_memory.py.exp diff --git a/tests/multi_extmod/machine_i2c_target_irq.py b/tests/multi_extmod/machine_i2c_target_irq.py new file mode 100644 index 00000000000..eafd9dfdca8 --- /dev/null +++ b/tests/multi_extmod/machine_i2c_target_irq.py @@ -0,0 +1,137 @@ +# Test I2CTarget IRQs and clock stretching. +# +# Requires two instances with their SCL and SDA lines connected together. +# Any combination of the below supported boards can be used. +# +# Notes: +# - pull-up resistors may be needed +# - alif use 1.8V signalling + +import sys +import time +from machine import I2C, I2CTarget + +if not hasattr(I2CTarget, "IRQ_ADDR_MATCH_READ"): + print("SKIP") + raise SystemExit + +ADDR = 67 +clock_stretch_us = 200 + +# Configure pins based on the target. +if sys.platform == "alif": + i2c_args = (1,) # pins P3_7/P3_6 + i2c_kwargs = {} +elif sys.platform == "mimxrt": + i2c_args = (0,) # pins 19/18 on Teensy 4.x + i2c_kwargs = {} + clock_stretch_us = 50 # mimxrt cannot delay too long in the IRQ handler +elif sys.platform == "rp2": + i2c_args = (0,) + i2c_kwargs = {"scl": 9, "sda": 8} +elif sys.platform == "pyboard": + i2c_args = ("Y",) + i2c_kwargs = {} +elif sys.platform == "samd": + i2c_args = () # pins SCL/SDA + i2c_kwargs = {} +elif "zephyr-rpi_pico" in sys.implementation._machine: + i2c_args = ("i2c1",) # on gpio7/gpio6 + i2c_kwargs = {} +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def simple_irq(i2c_target): + flags = i2c_target.irq().flags() + if flags & I2CTarget.IRQ_ADDR_MATCH_READ: + print("IRQ_ADDR_MATCH_READ") + if flags & I2CTarget.IRQ_ADDR_MATCH_WRITE: + print("IRQ_ADDR_MATCH_WRITE") + + # Force clock stretching. + time.sleep_us(clock_stretch_us) + + +class I2CTargetMemory: + def __init__(self, i2c_target, mem): + self.buf1 = bytearray(1) + self.mem = mem + self.memaddr = 0 + self.state = 0 + i2c_target.irq( + self.irq, + I2CTarget.IRQ_ADDR_MATCH_WRITE | I2CTarget.IRQ_READ_REQ | I2CTarget.IRQ_WRITE_REQ, + hard=True, + ) + + def irq(self, i2c_target): + # Force clock stretching. + time.sleep_us(clock_stretch_us) + + flags = i2c_target.irq().flags() + if flags & I2CTarget.IRQ_ADDR_MATCH_WRITE: + self.state = 0 + if flags & I2CTarget.IRQ_READ_REQ: + self.buf1[0] = self.mem[self.memaddr] + self.memaddr += 1 + i2c_target.write(self.buf1) + if flags & I2CTarget.IRQ_WRITE_REQ: + i2c_target.readinto(self.buf1) + if self.state == 0: + self.state = 1 + self.memaddr = self.buf1[0] + else: + self.mem[self.memaddr] = self.buf1[0] + self.memaddr += 1 + self.memaddr %= len(self.mem) + + # Force clock stretching. + time.sleep_us(clock_stretch_us) + + +# I2C controller +def instance0(): + i2c = I2C(*i2c_args, **i2c_kwargs) + multitest.next() + for iteration in range(2): + print("controller iteration", iteration) + multitest.wait("target stage 1") + i2c.writeto_mem(ADDR, 2, "0123") + multitest.broadcast("controller stage 2") + multitest.wait("target stage 3") + print(i2c.readfrom_mem(ADDR, 2, 4)) + multitest.broadcast("controller stage 4") + print("done") + + +# I2C target +def instance1(): + multitest.next() + + for iteration in range(2): + print("target iteration", iteration) + buf = bytearray(b"--------") + if iteration == 0: + # Use built-in memory capability of I2CTarget. + i2c_target = I2CTarget(*i2c_args, **i2c_kwargs, addr=ADDR, mem=buf) + i2c_target.irq( + simple_irq, + I2CTarget.IRQ_ADDR_MATCH_READ | I2CTarget.IRQ_ADDR_MATCH_WRITE, + hard=True, + ) + else: + # Implement a memory device by hand. + i2c_target = I2CTarget(*i2c_args, **i2c_kwargs, addr=ADDR) + I2CTargetMemory(i2c_target, buf) + + multitest.broadcast("target stage 1") + multitest.wait("controller stage 2") + print(buf) + multitest.broadcast("target stage 3") + multitest.wait("controller stage 4") + + i2c_target.deinit() + + print("done") diff --git a/tests/multi_extmod/machine_i2c_target_irq.py.exp b/tests/multi_extmod/machine_i2c_target_irq.py.exp new file mode 100644 index 00000000000..a17c8f43858 --- /dev/null +++ b/tests/multi_extmod/machine_i2c_target_irq.py.exp @@ -0,0 +1,15 @@ +--- instance0 --- +controller iteration 0 +b'0123' +controller iteration 1 +b'0123' +done +--- instance1 --- +target iteration 0 +IRQ_ADDR_MATCH_WRITE +bytearray(b'--0123--') +IRQ_ADDR_MATCH_WRITE +IRQ_ADDR_MATCH_READ +target iteration 1 +bytearray(b'--0123--') +done diff --git a/tests/multi_extmod/machine_i2c_target_memory.py b/tests/multi_extmod/machine_i2c_target_memory.py new file mode 100644 index 00000000000..6b3f0d03eb7 --- /dev/null +++ b/tests/multi_extmod/machine_i2c_target_memory.py @@ -0,0 +1,79 @@ +# Test basic use of I2CTarget and a memory buffer. +# +# Requires two instances with their SCL and SDA lines connected together. +# Any combination of the below supported boards can be used. +# +# Notes: +# - pull-up resistors may be needed +# - alif use 1.8V signalling + +import sys +from machine import I2C, I2CTarget + +ADDR = 67 + +# Configure pins based on the target. +if sys.platform == "alif": + i2c_args = (1,) # pins P3_7/P3_6 + i2c_kwargs = {} +elif sys.platform == "esp32": + i2c_args = (1,) # on pins 9/8 + i2c_kwargs = {} +elif sys.platform == "mimxrt": + i2c_args = (0,) # pins 19/18 on Teensy 4.x + i2c_kwargs = {} +elif sys.platform == "rp2": + i2c_args = (0,) + i2c_kwargs = {"scl": 9, "sda": 8} +elif sys.platform == "pyboard": + i2c_args = ("Y",) + i2c_kwargs = {} +elif sys.platform == "samd": + i2c_args = () # pins SCL/SDA + i2c_kwargs = {} +elif "zephyr-rpi_pico" in sys.implementation._machine: + i2c_args = ("i2c1",) # on gpio7/gpio6 + i2c_kwargs = {} +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def simple_irq(i2c_target): + flags = i2c_target.irq().flags() + if flags & I2CTarget.IRQ_END_READ: + print("IRQ_END_READ", i2c_target.memaddr) + if flags & I2CTarget.IRQ_END_WRITE: + print("IRQ_END_WRITE", i2c_target.memaddr) + + +# I2C controller +def instance0(): + i2c = I2C(*i2c_args, **i2c_kwargs) + multitest.next() + for iteration in range(2): + print("controller iteration", iteration) + multitest.wait("target stage 1") + i2c.writeto_mem(ADDR, 2 + iteration, "0123") + multitest.broadcast("controller stage 2") + multitest.wait("target stage 3") + print(i2c.readfrom_mem(ADDR, 2 + iteration, 4)) + multitest.broadcast("controller stage 4") + print("done") + + +# I2C target +def instance1(): + buf = bytearray(b"--------") + i2c_target = I2CTarget(*i2c_args, **i2c_kwargs, addr=ADDR, mem=buf) + i2c_target.irq(simple_irq) + multitest.next() + for iteration in range(2): + print("target iteration", iteration) + multitest.broadcast("target stage 1") + multitest.wait("controller stage 2") + print(buf) + multitest.broadcast("target stage 3") + multitest.wait("controller stage 4") + i2c_target.deinit() + print("done") diff --git a/tests/multi_extmod/machine_i2c_target_memory.py.exp b/tests/multi_extmod/machine_i2c_target_memory.py.exp new file mode 100644 index 00000000000..71386cfe769 --- /dev/null +++ b/tests/multi_extmod/machine_i2c_target_memory.py.exp @@ -0,0 +1,16 @@ +--- instance0 --- +controller iteration 0 +b'0123' +controller iteration 1 +b'0123' +done +--- instance1 --- +target iteration 0 +IRQ_END_WRITE 2 +bytearray(b'--0123--') +IRQ_END_READ 2 +target iteration 1 +IRQ_END_WRITE 3 +bytearray(b'--00123-') +IRQ_END_READ 3 +done From bf6f229cf3feb6767d37b9df9dcf1e2d9abc49bd Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 May 2025 15:55:46 +1000 Subject: [PATCH 1055/2098] docs/library: Document the new machine.I2CTarget class. With some working examples that show how to use all the features. Signed-off-by: Damien George --- docs/library/machine.I2CTarget.rst | 174 +++++++++++++++++++++++++++++ docs/library/machine.rst | 1 + 2 files changed, 175 insertions(+) create mode 100644 docs/library/machine.I2CTarget.rst diff --git a/docs/library/machine.I2CTarget.rst b/docs/library/machine.I2CTarget.rst new file mode 100644 index 00000000000..0e4af84cb68 --- /dev/null +++ b/docs/library/machine.I2CTarget.rst @@ -0,0 +1,174 @@ +.. currentmodule:: machine +.. _machine.I2CTarget: + +class I2CTarget -- an I2C target device +======================================= + +An I2C target is a device which connects to an I2C bus and is controlled by an +I2C controller. I2C targets can take many forms. The :class:`machine.I2CTarget` +class implements an I2C target that can be configured as a memory/register device, +or as an arbitrary I2C device by using callbacks (if supported by the port). + +Example usage for the case of a memory device:: + + from machine import I2CTarget + + # Create the backing memory for the I2C target. + mem = bytearray(8) + + # Create an I2C target. Depending on the port, extra parameters + # may be required to select the peripheral and/or pins to use. + i2c = I2CTarget(addr=67, mem=mem) + + # At this point an I2C controller can read and write `mem`. + ... + + # Deinitialise the I2C target. + i2c.deinit() + +Note that some ports require an ``id``, and maybe ``scl`` and ``sda`` pins, to be +passed to the `I2CTarget` constructor, to select the hardware I2C instance and +pins that it connects to. + +When configured as a memory device, it's also possible to register to receive events. +For example to be notified when the memory is read/written:: + + from machine import I2CTarget + + # Define an IRQ handler, for I2C events. + def irq_handler(i2c_target): + flags = i2c_target.irq().flags() + if flags & I2CTarget.IRQ_END_READ: + print("controller read target at addr", i2c_target.memaddr) + if flags & I2CTarget.IRQ_END_WRITE: + print("controller wrote target at addr", i2c_target.memaddr) + + # Create the I2C target and register to receive default events. + mem = bytearray(8) + i2c = I2CTarget(addr=67, mem=mem) + i2c.irq(irq_handler) + +More complicated I2C devices can be implemented using the full set of events. For +example, to see the raw events as they are triggered:: + + from machine import I2CTarget + + # Define an IRQ handler that prints the event id and responds to reads/writes. + def irq_handler(i2c_target, buf=bytearray(1)): + flags = i2c_target.irq().flags() + print(flags) + if flags & I2CTarget.IRQ_READ_REQ: + i2c_target.write(buf) + if flags & I2CTarget.IRQ_WRITE_REQ: + i2c_target.readinto(buf) + + # Create the I2C target and register to receive all events. + i2c = I2CTarget(addr=67) + all_triggers = ( + I2CTarget.IRQ_ADDR_MATCH_READ + | I2CTarget.IRQ_ADDR_MATCH_WRITE + | I2CTarget.IRQ_READ_REQ + | I2CTarget.IRQ_WRITE_REQ + | I2CTarget.IRQ_END_READ + | I2CTarget.IRQ_END_WRITE + ) + i2c.irq(irq_handler, trigger=all_triggers, hard=True) + +Constructors +------------ + +.. class:: I2CTarget(id, addr, *, addrsize=7, mem=None, mem_addrsize=8, scl=None, sda=None) + + Construct and return a new I2CTarget object using the following parameters: + + - *id* identifies a particular I2C peripheral. Allowed values depend on the + particular port/board. Some ports have a default in which case this parameter + can be omitted. + - *addr* is the I2C address of the target. + - *addrsize* is the number of bits in the I2C target address. Valid values + are 7 and 10. + - *mem* is an object with the buffer protocol that is writable. If not + specified then there is no backing memory and data must be read/written + using the :meth:`I2CTarget.readinto` and :meth:`I2CTarget.write` methods. + - *mem_addrsize* is the number of bits in the memory address. Valid values + are 0, 8, 16, 24 and 32. + - *scl* is a pin object specifying the pin to use for SCL. + - *sda* is a pin object specifying the pin to use for SDA. + + Note that some ports/boards will have default values of *scl* and *sda* + that can be changed in this constructor. Others will have fixed values + of *scl* and *sda* that cannot be changed. + +General Methods +--------------- + +.. method:: I2CTarget.deinit() + + Deinitialise the I2C target. After this method is called the hardware will no + longer respond to requests on the I2C bus, and no other methods can be called. + +.. method:: I2CTarget.readinto(buf) + + Read into the given buffer any pending bytes written by the I2C controller. + Returns the number of bytes read. + +.. method:: I2CTarget.write(buf) + + Write out the bytes from the given buffer, to be passed to the I2C controller + after it sends a read request. Returns the number of bytes written. Most ports + only accept one byte at a time to this method. + +.. method:: I2CTarget.irq(handler=None, trigger=IRQ_END_READ|IRQ_END_WRITE, hard=False) + + Configure an IRQ *handler* to be called when an event occurs. The possible events are + given by the following constants, which can be or'd together and passed to the *trigger* + argument: + + - ``IRQ_ADDR_MATCH_READ`` indicates that the target was addressed by a + controller for a read transaction. + - ``IRQ_ADDR_MATCH_READ`` indicates that the target was addressed by a + controller for a write transaction. + - ``IRQ_READ_REQ`` indicates that the controller is requesting data, and this + request must be satisfied by calling `I2CTarget.write` with the data to be + passed back to the controller. + - ``IRQ_WRITE_REQ`` indicates that the controller has written data, and the + data must be read by calling `I2CTarget.readinto`. + - ``IRQ_END_READ`` indicates that the controller has finished a read transaction. + - ``IRQ_END_WRITE`` indicates that the controller has finished a write transaction. + + Not all triggers are available on all ports. If a port has the constant then that + event is available. + + Note the following restrictions: + + - ``IRQ_ADDR_MATCH_READ``, ``IRQ_ADDR_MATCH_READ``, ``IRQ_READ_REQ`` and + ``IRQ_WRITE_REQ`` must be handled by a hard IRQ callback (with the *hard* argument + set to ``True``). This is because these events have very strict timing requirements + and must usually be satisfied synchronously with the hardware event. + + - ``IRQ_END_READ`` and ``IRQ_END_WRITE`` may be handled by either a soft or hard + IRQ callback (although note that all events must be registered with the same handler, + so if any events need a hard callback then all events must be hard). + + - If a memory buffer has been supplied in the constructor then ``IRQ_END_WRITE`` + is not emitted for the transaction that writes the memory address. This is to + allow ``IRQ_END_READ`` and ``IRQ_END_WRITE`` to function correctly as soft IRQ + callbacks, where the IRQ handler may be called quite some time after the actual + hardware event. + +.. attribute:: I2CTarget.memaddr + + The integer value of the most recent memory address that was selected by the I2C + controller (only valid if ``mem`` was specified in the constructor). + +Constants +--------- + +.. data:: I2CTarget.IRQ_ADDR_MATCH_READ + I2CTarget.IRQ_ADDR_MATCH_WRITE + I2CTarget.IRQ_READ_REQ + I2CTarget.IRQ_WRITE_REQ + I2CTarget.IRQ_END_READ + I2CTarget.IRQ_END_WRITE + + IRQ trigger sources. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 76d111f11ef..3c22aa9af65 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -264,6 +264,7 @@ Classes machine.UART.rst machine.SPI.rst machine.I2C.rst + machine.I2CTarget.rst machine.I2S.rst machine.RTC.rst machine.Timer.rst From c3f3339c87444acec814a150fdad28e650483405 Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Tue, 15 Aug 2023 13:03:45 +0100 Subject: [PATCH 1056/2098] esp32/modesp32: Add esp32.PCNT class. Add a new `esp32.PCNT` class that provides complete, low-level support to the ESP32 PCNT pulse counting hardware units. This can be used as a building block to implement the higher-level `machine.Counter` and `machine.Encoder` classes. This is enabled by default on all OG, S2, S3, C6 boards, but not on C3 (as the PCNT peripheral is not supported). Original implementation by: Jonathan Hogg Signed-off-by: Jim Mussared Signed-off-by: Angus Gratton --- ports/esp32/boards/sdkconfig.base | 1 + ports/esp32/esp32_common.cmake | 1 + ports/esp32/esp32_pcnt.c | 513 ++++++++++++++++++++++++++++++ ports/esp32/main.c | 5 + ports/esp32/modesp32.c | 3 + ports/esp32/modesp32.h | 6 + ports/esp32/mpconfigport.h | 4 + 7 files changed, 533 insertions(+) create mode 100644 ports/esp32/esp32_pcnt.c diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 2f1835c9242..4bbccf77d17 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -117,6 +117,7 @@ CONFIG_ADC_CAL_LUT_ENABLE=y CONFIG_UART_ISR_IN_IRAM=y # IDF 5 deprecated +CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN=y CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y CONFIG_ETH_USE_SPI_ETHERNET=y diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 09b12039130..9e8acf8897c 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -127,6 +127,7 @@ list(APPEND MICROPY_SOURCE_PORT modesp.c esp32_nvs.c esp32_partition.c + esp32_pcnt.c esp32_rmt.c esp32_ulp.c modesp32.c diff --git a/ports/esp32/esp32_pcnt.c b/ports/esp32/esp32_pcnt.c new file mode 100644 index 00000000000..36d43ab4559 --- /dev/null +++ b/ports/esp32/esp32_pcnt.c @@ -0,0 +1,513 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021-22 Jonathan Hogg + * + * 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/obj.h" + +#if MICROPY_PY_ESP32_PCNT + +#include "shared/runtime/mpirq.h" + +#include "modesp32.h" +#include "driver/pcnt.h" + +#if !MICROPY_ENABLE_FINALISER +#error "esp32.PCNT requires MICROPY_ENABLE_FINALISER." +#endif + +typedef struct _esp32_pcnt_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; +} esp32_pcnt_irq_obj_t; + +typedef struct _esp32_pcnt_obj_t { + mp_obj_base_t base; + pcnt_unit_t unit; + esp32_pcnt_irq_obj_t *irq; + struct _esp32_pcnt_obj_t *next; +} esp32_pcnt_obj_t; + +// Linked list of PCNT units. +MP_REGISTER_ROOT_POINTER(struct _esp32_pcnt_obj_t *esp32_pcnt_obj_head); + +// Once off installation of the PCNT ISR service (using the default service). +// Persists across soft reset. +static bool pcnt_isr_service_installed = false; + +static mp_obj_t esp32_pcnt_deinit(mp_obj_t self_in); + +void esp32_pcnt_deinit_all(void) { + esp32_pcnt_obj_t **pcnt = &MP_STATE_PORT(esp32_pcnt_obj_head); + while (*pcnt != NULL) { + esp32_pcnt_deinit(MP_OBJ_FROM_PTR(*pcnt)); + *pcnt = (*pcnt)->next; + } +} + +static void esp32_pcnt_init_helper(esp32_pcnt_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_channel, + ARG_pin, + ARG_rising, + ARG_falling, + ARG_mode_pin, + ARG_mode_low, + ARG_mode_high, + ARG_min, + ARG_max, + ARG_filter, + ARG_threshold0, + ARG_threshold1, + ARG_value, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + // Applies to the channel. + { MP_QSTR_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rising, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_falling, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_low, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode_high, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + // Applies to the whole unit. + { MP_QSTR_min, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_max, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_filter, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_threshold0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_threshold1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + // Implicitly zero if min, max, threshold0/1 are set. + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // The pin/mode_pin, rising, falling, mode_low, mode_high args all apply + // to the channel (defaults to channel zero). + mp_uint_t channel = args[ARG_channel].u_int; + if (channel >= PCNT_CHANNEL_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("channel")); + } + + if (args[ARG_pin].u_obj != MP_OBJ_NULL || args[ARG_mode_pin].u_obj != MP_OBJ_NULL) { + // If you set mode_pin, you must also set pin. + if (args[ARG_pin].u_obj == MP_OBJ_NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("pin")); + } + + mp_hal_pin_obj_t pin = PCNT_PIN_NOT_USED; + mp_hal_pin_obj_t mode_pin = PCNT_PIN_NOT_USED; + + // Set to None to disable pin/mode_pin. + if (args[ARG_pin].u_obj != mp_const_none) { + pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj); + } + if (args[ARG_mode_pin].u_obj != MP_OBJ_NULL && args[ARG_mode_pin].u_obj != mp_const_none) { + mode_pin = mp_hal_get_pin_obj(args[ARG_mode_pin].u_obj); + } + + pcnt_set_pin(self->unit, channel, pin, mode_pin); + } + + if ( + args[ARG_rising].u_obj != MP_OBJ_NULL || args[ARG_falling].u_obj != MP_OBJ_NULL || + args[ARG_mode_low].u_obj != MP_OBJ_NULL || args[ARG_mode_high].u_obj != MP_OBJ_NULL + ) { + mp_uint_t rising = args[ARG_rising].u_obj == MP_OBJ_NULL ? PCNT_COUNT_DIS : mp_obj_get_int(args[ARG_rising].u_obj); + mp_uint_t falling = args[ARG_falling].u_obj == MP_OBJ_NULL ? PCNT_COUNT_DIS : mp_obj_get_int(args[ARG_falling].u_obj); + mp_uint_t mode_low = args[ARG_mode_low].u_obj == MP_OBJ_NULL ? PCNT_MODE_KEEP : mp_obj_get_int(args[ARG_mode_low].u_obj); + mp_uint_t mode_high = args[ARG_mode_high].u_obj == MP_OBJ_NULL ? PCNT_MODE_KEEP : mp_obj_get_int(args[ARG_mode_high].u_obj); + if (rising >= PCNT_COUNT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("rising")); + } + if (falling >= PCNT_COUNT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("falling")); + } + if (mode_low >= PCNT_MODE_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("mode_low")); + } + if (mode_high >= PCNT_MODE_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("mode_high")); + } + pcnt_set_mode(self->unit, channel, rising, falling, mode_high, mode_low); + } + + // The rest of the arguments apply to the whole unit. + + if (args[ARG_filter].u_obj != MP_OBJ_NULL) { + mp_uint_t filter = mp_obj_get_int(args[ARG_filter].u_obj); + if (filter > 1023) { + mp_raise_ValueError(MP_ERROR_TEXT("filter")); + } + if (filter) { + check_esp_err(pcnt_set_filter_value(self->unit, filter)); + check_esp_err(pcnt_filter_enable(self->unit)); + } else { + check_esp_err(pcnt_filter_disable(self->unit)); + } + } + + bool clear = false; + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + mp_int_t value = mp_obj_get_int(args[ARG_value].u_obj); + if (value != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("value")); + } + clear = true; + } + + if (args[ARG_min].u_obj != MP_OBJ_NULL) { + mp_int_t minimum = mp_obj_get_int(args[ARG_min].u_obj); + if (minimum < -32768 || minimum > 0) { + mp_raise_ValueError(MP_ERROR_TEXT("minimum")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_L_LIM, minimum)); + clear = true; + } + + if (args[ARG_max].u_obj != MP_OBJ_NULL) { + mp_int_t maximum = mp_obj_get_int(args[ARG_max].u_obj); + if (maximum < 0 || maximum > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("maximum")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_H_LIM, maximum)); + clear = true; + } + + if (args[ARG_threshold0].u_obj != MP_OBJ_NULL) { + mp_int_t threshold0 = mp_obj_get_int(args[ARG_threshold0].u_obj); + if (threshold0 < -32768 || threshold0 > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("threshold0")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_0, threshold0)); + clear = true; + } + + if (args[ARG_threshold1].u_obj != MP_OBJ_NULL) { + mp_int_t threshold1 = mp_obj_get_int(args[ARG_threshold1].u_obj); + if (threshold1 < -32768 || threshold1 > 32767) { + mp_raise_ValueError(MP_ERROR_TEXT("threshold1")); + } + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_1, threshold1)); + clear = true; + } + + if (clear) { + check_esp_err(pcnt_counter_clear(self->unit)); + } +} + +// Disable any events, and remove the ISR handler for this unit. +static void esp32_pcnt_disable_events_for_unit(esp32_pcnt_obj_t *self) { + if (!self->irq) { + return; + } + + // Disable all possible events and remove the ISR. + for (pcnt_evt_type_t evt_type = PCNT_EVT_THRES_1; evt_type <= PCNT_EVT_ZERO; evt_type <<= 1) { + check_esp_err(pcnt_event_disable(self->unit, evt_type)); + } + check_esp_err(pcnt_isr_handler_remove(self->unit)); + + // Clear IRQ object state. + self->irq->base.handler = mp_const_none; + self->irq->trigger = 0; +} + +static mp_obj_t esp32_pcnt_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + if (n_pos_args < 1) { + mp_raise_TypeError(MP_ERROR_TEXT("id")); + } + + pcnt_unit_t unit = mp_obj_get_int(args[0]); + if (unit < 0 || unit >= PCNT_UNIT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); + } + + // Try and find an existing instance for this unit. + esp32_pcnt_obj_t *self = MP_STATE_PORT(esp32_pcnt_obj_head); + while (self) { + if (self->unit == unit) { + break; + } + self = self->next; + } + + if (!self) { + // Unused unit, create a new esp32_pcnt_obj_t instance and put it at + // the head of the list. + self = mp_obj_malloc(esp32_pcnt_obj_t, &esp32_pcnt_type); + self->unit = unit; + self->irq = NULL; + self->next = MP_STATE_PORT(esp32_pcnt_obj_head); + MP_STATE_PORT(esp32_pcnt_obj_head) = self; + + // Ensure the unit is in a known (deactivated) state. + esp32_pcnt_deinit(MP_OBJ_FROM_PTR(self)); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + esp32_pcnt_init_helper(self, 0, args + n_pos_args, &kw_args); + + // Ensure the global PCNT ISR service is installed. + if (!pcnt_isr_service_installed) { + check_esp_err(pcnt_isr_service_install(ESP_INTR_FLAG_IRAM)); + pcnt_isr_service_installed = true; + } + + // And enable for this unit. + check_esp_err(pcnt_intr_enable(self->unit)); + + return MP_OBJ_FROM_PTR(self); +} + +static void esp32_pcnt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PCNT(%u)", self->unit); +} + +static mp_obj_t esp32_pcnt_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + esp32_pcnt_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_pcnt_init_obj, 1, esp32_pcnt_init); + +static mp_obj_t esp32_pcnt_deinit(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Remove IRQ and events. + esp32_pcnt_disable_events_for_unit(self); + + // Deactivate both channels. + pcnt_config_t channel_config = { + .unit = self->unit, + .pulse_gpio_num = PCNT_PIN_NOT_USED, + .pos_mode = PCNT_COUNT_DIS, + .neg_mode = PCNT_COUNT_DIS, + .ctrl_gpio_num = PCNT_PIN_NOT_USED, + .lctrl_mode = PCNT_MODE_KEEP, + .hctrl_mode = PCNT_MODE_KEEP, + .counter_l_lim = 0, + .counter_h_lim = 0, + }; + for (pcnt_channel_t channel = 0; channel <= 1; ++channel) { + channel_config.channel = channel; + check_esp_err(pcnt_unit_config(&channel_config)); + } + + // Disable filters & thresholds, pause & clear. + check_esp_err(pcnt_filter_disable(self->unit)); + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_0, 0)); + check_esp_err(pcnt_set_event_value(self->unit, PCNT_EVT_THRES_1, 0)); + check_esp_err(pcnt_counter_pause(self->unit)); + check_esp_err(pcnt_counter_clear(self->unit)); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_deinit_obj, esp32_pcnt_deinit); + +static mp_obj_t esp32_pcnt_value(size_t n_args, const mp_obj_t *pos_args) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + // Optionally use pcnt.value(True) to clear the counter but only support a + // value of zero. Note: This can lead to skipped counts. + if (n_args == 2) { + if (mp_obj_get_int(pos_args[1]) != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("value")); + } + } + + // This loop ensures that the caller's state (as inferred from IRQs, e.g. + // under/overflow) corresponds to the returned value, by synchronously + // flushing all pending IRQs. + int16_t value; + while (true) { + check_esp_err(pcnt_get_counter_value(self->unit, &value)); + if (self->irq && self->irq->flags && self->irq->base.handler != mp_const_none) { + // The handler must call irq.flags() to clear self->irq->base.flags, + // otherwise this will be an infinite loop. + mp_call_function_1(self->irq->base.handler, self->irq->base.parent); + } else { + break; + } + } + + if (n_args == 2) { + // Value was given, and we've already checked it was zero, so clear + // the counter. + check_esp_err(pcnt_counter_clear(self->unit)); + } + + return MP_OBJ_NEW_SMALL_INT(value); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_pcnt_value_obj, 1, 2, esp32_pcnt_value); + +static mp_uint_t esp32_pcnt_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->irq->trigger = new_trigger; + for (pcnt_evt_type_t evt_type = PCNT_EVT_THRES_1; evt_type <= PCNT_EVT_ZERO; evt_type <<= 1) { + if (new_trigger & evt_type) { + pcnt_event_enable(self->unit, evt_type); + } else { + pcnt_event_disable(self->unit, evt_type); + } + } + return 0; +} + +static mp_uint_t esp32_pcnt_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + // Atomically get-and-clear the flags. + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + mp_uint_t flags = self->irq->flags; + self->irq->flags = 0; + MICROPY_END_ATOMIC_SECTION(atomic_state); + return flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->irq->trigger; + } + return 0; +} + +static const mp_irq_methods_t esp32_pcnt_irq_methods = { + .trigger = esp32_pcnt_irq_trigger, + .info = esp32_pcnt_irq_info, +}; + +static IRAM_ATTR void esp32_pcnt_intr_handler(void *arg) { + esp32_pcnt_obj_t *self = (esp32_pcnt_obj_t *)arg; + pcnt_unit_t unit = self->unit; + uint32_t status; + pcnt_get_event_status(unit, &status); + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + self->irq->flags |= status; + MICROPY_END_ATOMIC_SECTION(atomic_state); + mp_irq_handler(&self->irq->base); +} + +static mp_obj_t esp32_pcnt_irq(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = PCNT_EVT_ZERO} }, + }; + + esp32_pcnt_obj_t *self = pos_args[0]; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (!self->irq) { + // Create IRQ object if necessary. This instance persists across a + // de-init. + self->irq = mp_obj_malloc(esp32_pcnt_irq_obj_t, &mp_irq_type); + self->irq->base.methods = (mp_irq_methods_t *)&esp32_pcnt_irq_methods; + self->irq->base.parent = MP_OBJ_FROM_PTR(self); + self->irq->base.ishard = false; + self->irq->base.handler = mp_const_none; + self->irq->trigger = 0; + } + + if (n_pos_args > 1 || kw_args->used != 0) { + // Update IRQ data. + + mp_obj_t handler = args[ARG_handler].u_obj; + mp_uint_t trigger = args[ARG_trigger].u_int; + + if (trigger < PCNT_EVT_THRES_1 || trigger >= (PCNT_EVT_ZERO << 1)) { + mp_raise_ValueError(MP_ERROR_TEXT("trigger")); + } + + if (handler != mp_const_none) { + self->irq->base.handler = handler; + self->irq->trigger = trigger; + pcnt_isr_handler_add(self->unit, esp32_pcnt_intr_handler, (void *)self); + esp32_pcnt_irq_trigger(MP_OBJ_FROM_PTR(self), trigger); + } else { + // Remove the ISR, disable all events, clear the IRQ object state. + esp32_pcnt_disable_events_for_unit(self); + } + } + + return MP_OBJ_FROM_PTR(self->irq); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_pcnt_irq_obj, 1, esp32_pcnt_irq); + +static mp_obj_t esp32_pcnt_start(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(pcnt_counter_resume(self->unit)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_start_obj, esp32_pcnt_start); + +static mp_obj_t esp32_pcnt_stop(mp_obj_t self_in) { + esp32_pcnt_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(pcnt_counter_pause(self->unit)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_pcnt_stop_obj, esp32_pcnt_stop); + +static const mp_rom_map_elem_t esp32_pcnt_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_pcnt_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&esp32_pcnt_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&esp32_pcnt_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&esp32_pcnt_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&esp32_pcnt_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_pcnt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_pcnt_deinit_obj) }, + + // Constants + { MP_ROM_QSTR(MP_QSTR_IGNORE), MP_ROM_INT(PCNT_COUNT_DIS) }, + { MP_ROM_QSTR(MP_QSTR_INCREMENT), MP_ROM_INT(PCNT_COUNT_INC) }, + { MP_ROM_QSTR(MP_QSTR_DECREMENT), MP_ROM_INT(PCNT_COUNT_DEC) }, + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(PCNT_MODE_KEEP) }, + { MP_ROM_QSTR(MP_QSTR_REVERSE), MP_ROM_INT(PCNT_MODE_REVERSE) }, + { MP_ROM_QSTR(MP_QSTR_HOLD), MP_ROM_INT(PCNT_MODE_DISABLE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_ZERO), MP_ROM_INT(PCNT_EVT_ZERO) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_THRESHOLD0), MP_ROM_INT(PCNT_EVT_THRES_0) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_THRESHOLD1), MP_ROM_INT(PCNT_EVT_THRES_1) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_MIN), MP_ROM_INT(PCNT_EVT_L_LIM) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_MAX), MP_ROM_INT(PCNT_EVT_H_LIM) }, +}; +static MP_DEFINE_CONST_DICT(esp32_pcnt_locals_dict, esp32_pcnt_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_pcnt_type, + MP_QSTR_PCNT, + MP_TYPE_FLAG_NONE, + make_new, esp32_pcnt_make_new, + print, esp32_pcnt_print, + locals_dict, &esp32_pcnt_locals_dict + ); + +#endif // MICROPY_PY_ESP32_PCNT diff --git a/ports/esp32/main.c b/ports/esp32/main.c index f85fb6c084f..1523e07f916 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -61,6 +61,7 @@ #include "uart.h" #include "usb.h" #include "usb_serial_jtag.h" +#include "modesp32.h" #include "modmachine.h" #include "modnetwork.h" @@ -181,6 +182,10 @@ void mp_task(void *pvParameter) { machine_timer_deinit_all(); + #if MICROPY_PY_ESP32_PCNT + esp32_pcnt_deinit_all(); + #endif + #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index bf7aec39442..858be2ed05f 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -299,6 +299,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, + #if MICROPY_PY_ESP32_PCNT + { MP_ROM_QSTR(MP_QSTR_PCNT), MP_ROM_PTR(&esp32_pcnt_type) }, + #endif #if SOC_RMT_SUPPORTED { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, #endif diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index a685b7b38fe..81ab94dc633 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -66,6 +66,12 @@ extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; +#if MICROPY_PY_ESP32_PCNT +extern const mp_obj_type_t esp32_pcnt_type; + +void esp32_pcnt_deinit_all(void); +#endif + esp_err_t rmt_driver_install_core1(uint8_t channel_id); #endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 1844018030b..48ad39ef7ed 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -70,6 +70,7 @@ #define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys #define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf #define MICROPY_SCHEDULER_DEPTH (8) +#define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_VFS (1) // control over Python builtins @@ -194,6 +195,9 @@ #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_SOCKET_EVENTS (MICROPY_PY_WEBREPL) #define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) +#ifndef MICROPY_PY_ESP32_PCNT +#define MICROPY_PY_ESP32_PCNT (SOC_PCNT_SUPPORTED) +#endif // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (1) From e54553c496bf915ed0263c2d2a61d31d61032dea Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Sat, 10 Sep 2022 17:08:27 +0100 Subject: [PATCH 1057/2098] docs/esp32: Add documentation for esp32.PCNT. Document the new `esp32.PCNT` class for hardware pulse counting. Originally authored by: Jonathan Hogg Signed-off-by: Jim Mussared --- docs/esp32/quickref.rst | 21 ++++++ docs/library/esp32.rst | 142 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 49f546a17c0..7127cc402bd 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -566,6 +566,27 @@ ESP32 S2: Provided to deinit the adc driver. +Pulse Counter (pin pulse/edge counting) +--------------------------------------- + +The ESP32 provides up to 8 pulse counter peripherals depending on the hardware, +with id 0..7. These can be configured to count rising and/or falling edges on +any input pin. + +Use the :ref:`esp32.PCNT ` class:: + + from machine import Pin + from esp32 import PCNT + + counter = PCNT(0, pin=Pin(2), rising=PCNT.INCREMENT) # create counter + counter.start() # start counter + count = counter.value() # read count, -32768..32767 + counter.value(0) # reset counter + count = counter.value(0) # read and reset + +The PCNT hardware supports monitoring multiple pins in a single unit to +implement quadrature decoding or up/down signal counters. + Software SPI bus ---------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 4be6dc2671c..0cf0ebc6263 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -195,6 +195,148 @@ Constants Used in `idf_heap_info`. + +.. _esp32.PCNT: + +PCNT +---- + +This class provides access to the ESP32 hardware support for pulse counting. +There are 8 pulse counter units, with id 0..7. + +See the :ref:`machine.Counter ` and +:ref:`machine.Encoder ` classes for simpler and portable +abstractions of common pulse counting applications. These classes are +implemented as thin Python shims around :class:`PCNT`. + +.. class:: PCNT(id, *, ...) + + Returns the singleton PCNT instance for the given unit ``id``. + + Keyword arguments are passed to the ``init()`` method as described + below. + +.. method:: PCNT.init(*, ...) + + (Re-)initialise a pulse counter unit. Supported keyword arguments are: + + - ``channel``: see description below + - ``pin``: the input Pin to monitor for pulses + - ``rising``: an action to take on a rising edge - one of + ``PCNT.INCREMENT``, ``PCNT.DECREMENT`` or ``PCNT.IGNORE`` (the default) + - ``falling``: an action to take on a falling edge (takes the save values + as the ``rising`` argument). + - ``mode_pin``: ESP32 pulse counters support monitoring a second pin and + altering the behaviour of the counter based on its level - set this + keyword to any input Pin + - ``mode_low``: set to either ``PCNT.HOLD`` or ``PCNT.REVERSE`` to + either suspend counting or reverse the direction of the counter (i.e., + ``PCNT.INCREMENT`` behaves as ``PCNT.DECREMENT`` and vice versa) + when ``mode_pin`` is low + - ``mode_high``: as ``mode_low`` but for the behaviour when ``mode_pin`` + is high + - ``filter``: set to a value 1..1023, in ticks of the 80MHz clock, to + enable the pulse width filter + - ``min``: set to the minimum level of the counter value when + decrementing (-32768..-1) or 0 to disable + - ``max``: set to the maximum level of the counter value when + incrementing (1..32767) or 0 to disable + - ``threshold0``: sets the counter value for the + ``PCNT.IRQ_THRESHOLD0`` event (see ``irq`` method) + - ``threshold1``: sets the counter value for the + ``PCNT.IRQ_THRESHOLD1`` event (see ``irq`` method) + - ``value``: can be set to ``0`` to reset the counter value + + The hardware initialisation is done in stages and so some of the keyword + arguments can be used in groups or in isolation to partially reconfigure a + unit: + + - the ``pin`` keyword (optionally combined with ``mode_pin``) can be used + to change just the bound pin(s) + - ``rising``, ``falling``, ``mode_low`` and ``mode_high`` can be used + (singly or together) to change the counting logic - omitted keywords + use their default (``PCNT.IGNORE`` or ``PCNT.NORMAL``) + - ``filter`` can be used to change only the pulse width filter (with 0 + disabling it) + - each of ``min``, ``max``, ``threshold0`` and ``threshold1`` can + be used to change these limit/event values individually; however, + setting any will reset the counter to zero (i.e., they imply + ``value=0``) + + Each pulse counter unit supports two channels, 0 and 1, each able to + monitor different pins with different counting logic but updating the same + counter value. Use ``channel=1`` with the ``pin``, ``rising``, ``falling``, + ``mode_pin``, ``mode_low`` and ``mode_high`` keywords to configure the + second channel. + + The second channel can be used to configure 4X quadrature decoding with a + single counter unit:: + + pin_a = Pin(2, Pin.INPUT, pull=Pin.PULL_UP) + pin_b = Pin(3, Pin.INPUT, pull=Pin.PULL_UP) + rotary = PCNT(0, min=-32000, max=32000) + rotary.init(channel=0, pin=pin_a, falling=PCNT.INCREMENT, rising=PCNT.DECREMENT, mode_pin=pin_b, mode_low=PCNT.REVERSE) + rotary.init(channel=1, pin=pin_b, falling=PCNT.DECREMENT, rising=PCNT.INCREMENT, mode_pin=pin_a, mode_low=PCNT.REVERSE) + rotary.start() + +.. method:: PCNT.value([value]) + + Call this method with no arguments to return the current counter value. + + If the optional *value* argument is set to ``0`` then the counter is + reset (but the previous value is returned). Read and reset is not atomic and + so it is possible for a pulse to be missed. Any value other than ``0`` will + raise an error. + +.. method:: PCNT.irq(handler=None, trigger=PCNT.IRQ_ZERO) + + ESP32 pulse counters support interrupts on these counter events: + + - ``PCNT.IRQ_ZERO``: the counter has reset to zero + - ``PCNT.IRQ_MIN``: the counter has hit the ``min`` value + - ``PCNT.IRQ_MAX``: the counter has hit the ``max`` value + - ``PCNT.IRQ_THRESHOLD0``: the counter has hit the ``threshold0`` value + - ``PCNT.IRQ_THRESHOLD1``: the counter has hit the ``threshold1`` value + + ``trigger`` should be a bit-mask of the desired events OR'ed together. The + ``handler`` function should take a single argument which is the + :class:`PCNT` instance that raised the event. + + This method returns a callback object. The callback object can be used to + access the bit-mask of events that are outstanding on the PCNT unit.:: + + def pcnt_irq(pcnt): + flags = pcnt.irq().flags() + if flags & PCNT.IRQ_ZERO: + # reset + if flags & PCNT.IRQ_MAX: + # overflow... + ... etc + + pcnt.irq(handler=pcnt_irq, trigger=PCNT.IRQ_ZERO | PCNT.IRQ_MAX | ...) + + **Note:** Accessing ``irq.flags()`` will clear the flags, so only call it + once per invocation of the handler. + + The handler is called with the MicroPython scheduler and so will run at a + point after the interrupt. If another interrupt occurs before the handler + has been called then the events will be coalesced together into a single + call and the bit mask will indicate all events that have occurred. + + To avoid race conditions between a handler being called and retrieving the + current counter value, the ``value()`` method will force execution of any + pending events before returning the current counter value (and potentially + resetting the value). + + Only one handler can be in place per-unit. Set ``handler`` to ``None`` to + disable the event interrupt. + +.. Note:: + ESP32 pulse counters reset to *zero* when reaching the minimum or maximum + value. Thus the ``IRQ_ZERO`` event will also trigger when either of these + events occurs. + + .. _esp32.RMT: RMT From 327655905e9f523070301f2f35459197d46db4fb Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Sat, 10 Sep 2022 16:47:19 +0100 Subject: [PATCH 1058/2098] esp32/modules/machine.py: Add Counter and Encoder classes. Adds a Python override of the `machine` module, which delegates to the built-in module and adds an implementation of `Counter` and `Encoder`, based on the `esp32.PCNT` class. Original implementation by: Jonathan Hogg Signed-off-by: Jim Mussared --- ports/esp32/modules/machine.py | 192 +++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 ports/esp32/modules/machine.py diff --git a/ports/esp32/modules/machine.py b/ports/esp32/modules/machine.py new file mode 100644 index 00000000000..9cfda12f177 --- /dev/null +++ b/ports/esp32/modules/machine.py @@ -0,0 +1,192 @@ +import sys + +_path = sys.path +sys.path = () +try: + import machine as _machine +finally: + sys.path = _path + del _path + del sys + + +from micropython import const +import esp32 + +if hasattr(esp32, "PCNT"): + _PCNT_RANGE = const(32000) + + class _CounterBase: + _PCNT = esp32.PCNT + # Singletons, keyed by PCNT unit_id (shared by both Counter & Encoder). + _INSTANCES = {} + + # Use __new__ to implement a singleton rather than a factory function, + # because we need to be able to provide class attributes, e.g. + # Counter.RISING, which is not possible if Counter was a function + # (functions cannot have attributes in MicroPython). + def __new__(cls, unit_id, *_args, **_kwargs): + # Find an existing instance for this PCNT unit id. + self = cls._INSTANCES.get(unit_id) + + if self: + # Verify that this PCNT is being used for the same type + # (Encoder or Counter). + if not isinstance(self, cls): + raise ValueError("PCNT in use") + else: + # Previously unused PCNT unit. + self = object.__new__(cls) + cls._INSTANCES[unit_id] = self + + # __init__ will now be called with the same args. + return self + + def __init__(self, unit_id, *args, filter_ns=0, **kwargs): + self._unit_id = unit_id + + if not hasattr(self, "_pcnt"): + # New instance, or previously deinit-ed. + self._pcnt = self._PCNT(unit_id, min=-_PCNT_RANGE, max=_PCNT_RANGE) + elif not (args or kwargs): + # Existing instance, and no args, so accessing the existing + # singleton without reconfiguring. Note: This means that + # Counter/Encoder cannot be partially re-initalised. Either + # you get the existing instance as-is (by passing no arguments + # other than the id), or you must pass all the necessary + # arguments to additionally re-configure it. + return + + # Counter- or Encoder-specific configuration of self._pcnt. + self._configure(*args, **kwargs) + + # Common unit configuration. + self._pcnt.init( + filter=min(max(0, filter_ns * 80 // 1000), 1023), + value=0, + ) + + # Note: We track number-of-overflows rather than the actual count in + # order to avoid the IRQ handler overflowing MicroPython's "small int" + # range. This gives an effective range of 2**30 overflows. User code + # should use counter.value(0) to reset the overflow count. + # The ESP32 PCNT resets to zero on under/overflow (i.e. it does not wrap + # around to the opposite limit), so each overflow corresponds to exactly + # _PCNT_RANGE counts. + + # Reset counter state. + self._overflows = 0 + self._offset = 0 + + # Install IRQ handler to handle under/overflow. + self._pcnt.irq(self._overflow, self._PCNT.IRQ_MIN | self._PCNT.IRQ_MAX) + + # Start counting. + self._pcnt.start() + + # Handle counter under/overflow. + def _overflow(self, pcnt): + mask = pcnt.irq().flags() + if mask & self._PCNT.IRQ_MIN: + self._overflows -= 1 + elif mask & self._PCNT.IRQ_MAX: + self._overflows += 1 + + # Public machine.Counter & machine.Encoder API. + def init(self, *args, **kwargs): + self.__init__(self._unit_id, *args, **kwargs) + + # Public machine.Counter & machine.Encoder API. + def deinit(self): + if hasattr(self, "_pcnt"): + self._pcnt.deinit() + del self._pcnt + + # Public machine.Counter & machine.Encoder API. + def value(self, value=None): + if not hasattr(self, "_pcnt"): + raise RuntimeError("not initialised") + + # This loop deals with the possibility that a PCNT overflow occurs + # between retrieving self._overflows and self._pcnt.value(). + while True: + overflows = self._overflows + current = self._pcnt.value() + # Calling PCNT.value() forces any pending interrupts to run + # for this PCNT unit. So self._overflows must now be the the + # value corresponding to the value we read. + if self._overflows == overflows: + break + + # Compute the result including the number of times we've cycled + # through the range, and any applied offset. + result = overflows * _PCNT_RANGE + current + self._offset + + # If a new value is specified, then zero out the overflows, and set + # self._offset so that it zeros out the current PCNT value. The + # mutation to self._overflows is atomic w.r.t. the overflow IRQ + # handler because the scheduler only runs on branch instructions. + if value is not None: + self._overflows -= overflows + self._offset = value - current + + return result + + class Counter(_CounterBase): + # Public machine.Counter API. + RISING = 1 + FALLING = 2 + UP = _CounterBase._PCNT.INCREMENT + DOWN = _CounterBase._PCNT.DECREMENT + + # Counter-specific configuration. + def _configure(self, src, edge=RISING, direction=UP): + # Only use the first channel. + self._pcnt.init( + channel=0, + pin=src, + rising=direction if edge & Counter.RISING else self._PCNT.IGNORE, + falling=direction if edge & Counter.FALLING else self._PCNT.IGNORE, + ) + + class Encoder(_CounterBase): + # Encoder-specific configuration. + def _configure(self, phase_a, phase_b, phases=1): + if phases not in (1, 2, 4): + raise ValueError("phases") + # Configure the first channel. + self._pcnt.init( + channel=0, + pin=phase_a, + falling=self._PCNT.INCREMENT, + rising=self._PCNT.DECREMENT, + mode_pin=phase_b, + mode_low=self._PCNT.HOLD if phases == 1 else self._PCNT.REVERSE, + ) + if phases == 4: + # For 4x quadrature, enable the second channel. + self._pcnt.init( + channel=1, + pin=phase_b, + falling=self._PCNT.DECREMENT, + rising=self._PCNT.INCREMENT, + mode_pin=phase_a, + mode_low=self._PCNT.REVERSE, + ) + else: + # For 1x and 2x quadrature, disable the second channel. + self._pcnt.init(channel=1, pin=None, rising=self._PCNT.IGNORE) + self._phases = phases + + def phases(self): + return self._phases + + del _CounterBase + + +del esp32 + + +# Delegate to built-in machine module. +def __getattr__(attr): + return getattr(_machine, attr) From 641ca2eb0624fc0df2b358f1a4652525af123fd7 Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Mon, 12 Sep 2022 18:37:22 +0100 Subject: [PATCH 1059/2098] docs/library/machine: Add docs for Counter and Encoder. Add documentation for `machine.Counter` and `machine.Encoder` as currently implemented by the esp32 port, but intended to be implemented by other ports. Originally authored by: Ihor Nehrutsa and Jonathan Hogg . Signed-off-by: Jim Mussared --- docs/esp32/quickref.rst | 15 ++++++ docs/library/esp32.rst | 3 ++ docs/library/machine.Counter.rst | 93 ++++++++++++++++++++++++++++++++ docs/library/machine.Encoder.rst | 72 +++++++++++++++++++++++++ docs/library/machine.rst | 2 + docs/reference/glossary.rst | 7 +++ 6 files changed, 192 insertions(+) create mode 100644 docs/library/machine.Counter.rst create mode 100644 docs/library/machine.Encoder.rst diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 7127cc402bd..2c667a0f014 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -587,6 +587,21 @@ Use the :ref:`esp32.PCNT ` class:: The PCNT hardware supports monitoring multiple pins in a single unit to implement quadrature decoding or up/down signal counters. +See the :ref:`machine.Counter ` and +:ref:`machine.Encoder ` classes for simpler abstractions of +common pulse counting applications:: + + from machine import Pin, Counter + + counter = Counter(0, Pin(2)) # create a counter as above and start it + count = counter.value() # read the count as an arbitrary precision signed integer + + encoder = Encoder(0, Pin(12), Pin(14)) # create an encoder and begin counting + count = encoder.value() # read the count as an arbitrary precision signed integer + +Note that the id passed to these ``Counter()`` and ``Encoder()`` objects must be +a PCNT id. + Software SPI bus ---------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 0cf0ebc6263..e5f39c7f59a 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -336,6 +336,9 @@ implemented as thin Python shims around :class:`PCNT`. value. Thus the ``IRQ_ZERO`` event will also trigger when either of these events occurs. +See the :ref:`machine.Counter ` and +:ref:`machine.Encoder ` classes for simpler abstractions of +common pulse counting applications. .. _esp32.RMT: diff --git a/docs/library/machine.Counter.rst b/docs/library/machine.Counter.rst new file mode 100644 index 00000000000..f89a6d5b4f1 --- /dev/null +++ b/docs/library/machine.Counter.rst @@ -0,0 +1,93 @@ +.. currentmodule:: machine +.. _machine.Counter: + +class Counter -- pulse counter +============================== + +Counter implements pulse counting by monitoring an input signal and counting +rising or falling edges. + +Minimal example usage:: + + from machine import Pin, Counter + + counter = Counter(0, Pin(0, Pin.IN)) # create Counter for pin 0 and begin counting + value = counter.value() # retrieve current pulse count + +Availability: **ESP32** + +Constructors +------------ + +.. class:: Counter(id, ...) + + Returns the singleton Counter object for the the given *id*. Values of *id* + depend on a particular port and its hardware. Values 0, 1, etc. are commonly + used to select hardware block #0, #1, etc. + + Additional arguments are passed to the :meth:`init` method described below, + and will cause the Counter instance to be re-initialised and reset. + + On ESP32, the *id* corresponds to a :ref:`PCNT unit `. + +Methods +------- + +.. method:: Counter.init(src, *, ...) + + Initialise and reset the Counter with the given parameters: + + - *src* specifies the input pin as a :ref:`machine.Pin ` object. + May be omitted on ports that have a predefined pin for a given hardware + block. + + Additional keyword-only parameters that may be supported by a port are: + + - *edge* specifies the edge to count. Either ``Counter.RISING`` (the default) + or ``Counter.FALLING``. *(Supported on ESP32)* + + - *direction* specifies the direction to count. Either ``Counter.UP`` (the + default) or ``Counter.DOWN``. *(Supported on ESP32)* + + - *filter_ns* specifies a minimum period of time in nanoseconds that the + source signal needs to be stable for a pulse to be counted. Implementations + should use the longest filter supported by the hardware that is less than + or equal to this value. The default is 0 (no filter). *(Supported on ESP32)* + +.. method:: Counter.deinit() + + Stops the Counter, disabling any interrupts and releasing hardware resources. + A Soft Reset should deinitialize all Counter objects. + +.. method:: Counter.value([value]) + + Get, and optionally set, the counter value as a signed integer. + Implementations must aim to do the get and set atomically (i.e. without + leading to skipped counts). + + This counter value could exceed the range of a :term:`small integer`, which + means that calling :meth:`Counter.value` could cause a heap allocation, but + implementations should aim to ensure that internal state only uses small + integers and therefore will not allocate until the user calls + :meth:`Counter.value`. + + For example, on ESP32, the internal state counts overflows of the hardware + counter (every 32000 counts), which means that it will not exceed the small + integer range until ``2**30 * 32000`` counts (slightly over 1 year at 1MHz). + + In general, it is recommended that you should use ``Counter.value(0)`` to reset + the counter (i.e. to measure the counts since the last call), and this will + avoid this problem. + +Constants +--------- + +.. data:: Counter.RISING + Counter.FALLING + + Select the pulse edge. + +.. data:: Counter.UP + Counter.DOWN + + Select the counting direction. diff --git a/docs/library/machine.Encoder.rst b/docs/library/machine.Encoder.rst new file mode 100644 index 00000000000..fc2de320848 --- /dev/null +++ b/docs/library/machine.Encoder.rst @@ -0,0 +1,72 @@ +.. currentmodule:: machine +.. _machine.Encoder: + +class Encoder -- quadrature decoding +==================================== + +Encoder implements decoding of quadrature signals as commonly output from +rotary encoders, by counting either up or down depending on the order of two +input pulses. + +Minimal example usage:: + + from machine import Pin, Encoder + + counter = Counter(0, Pin(0, Pin.IN), Pin(1, Pin.IN)) # create Encoder for pins 0, 1 and begin counting + value = counter.value() # retrieve current count + +Availability: **ESP32** + +Constructors +------------ + +.. class:: Encoder(id, ...) + + Returns the singleton Encoder object for the the given *id*. Values of *id* + depend on a particular port and its hardware. Values 0, 1, etc. are commonly + used to select hardware block #0, #1, etc. + + Additional arguments are passed to the :meth:`init` method described below, + and will cause the Encoder instance to be re-initialised and reset. + + On ESP32, the *id* corresponds to a :ref:`PCNT unit `. + +Methods +------- + +.. method:: Encoder.init(phase_a, phase_b, *, ...) + + Initialise and reset the Encoder with the given parameters: + + - *phase_a* specifies the first input pin as a + :ref:`machine.Pin ` object. + + - *phase_b* specifies the second input pin as a + :ref:`machine.Pin ` object. + + These pins may be omitted on ports that have predefined pins for a given + hardware block. + + Additional keyword-only parameters that may be supported by a port are: + + - *filter_ns* specifies a minimum period of time in nanoseconds that the + source signal needs to be stable for a pulse to be counted. Implementations + should use the longest filter supported by the hardware that is less than + or equal to this value. The default is 0 (no filter). *(Supported on ESP32)* + + - *phases* specifies the number of signal edges to count and thus the + granularity of the decoding. e.g. 4 phases corresponds to "4x quadrature + decoding", and will result in four counts per pulse. Ports may support + either 1, 2, or 4 phases and the default is 1 phase. *(Supported on ESP32)* + +.. method:: Encoder.deinit() + + Stops the Encoder, disabling any interrupts and releasing hardware resources. + A Soft Reset should deinitialize all Encoder objects. + +.. method:: Encoder.value([value]) + + Get, and optionally set, the encoder value as a signed integer. + Implementations should aim to do the get and set atomically. + + See :meth:`machine.Counter.value` for details about overflow of this value. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 3c22aa9af65..7acaddde815 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -268,6 +268,8 @@ Classes machine.I2S.rst machine.RTC.rst machine.Timer.rst + machine.Counter.rst + machine.Encoder.rst machine.WDT.rst machine.SD.rst machine.SDCard.rst diff --git a/docs/reference/glossary.rst b/docs/reference/glossary.rst index efaa8f607f7..ab0b22049bd 100644 --- a/docs/reference/glossary.rst +++ b/docs/reference/glossary.rst @@ -188,6 +188,13 @@ Glossary Most MicroPython boards make a REPL available over a UART, and this is typically accessible on a host PC via USB. + small integer + MicroPython optimises the internal representation of integers such that + "small" values do not take up space on the heap, and calculations with + them do not require heap allocation. On most 32-bit ports, this + corresponds to values in the interval ``-2**30 <= x < 2**30``, but this + should be considered an implementation detail and not relied upon. + stream Also known as a "file-like object". A Python object which provides sequential read-write access to the underlying data. A stream object From 907c5e99769635678e4bfb8dda5c3c0e2aec040f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 30 Jul 2025 17:20:36 +1000 Subject: [PATCH 1060/2098] tests/extmod_hardware: Add basic tests for machine.Counter and Encoder. These don't test any advanced features, just the basics. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/extmod_hardware/machine_counter.py | 90 +++++++++++++++++++++ tests/extmod_hardware/machine_encoder.py | 99 ++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 tests/extmod_hardware/machine_counter.py create mode 100644 tests/extmod_hardware/machine_encoder.py diff --git a/tests/extmod_hardware/machine_counter.py b/tests/extmod_hardware/machine_counter.py new file mode 100644 index 00000000000..62ac1fed47c --- /dev/null +++ b/tests/extmod_hardware/machine_counter.py @@ -0,0 +1,90 @@ +# Test machine.Counter implementation +# +# IMPORTANT: This test requires hardware connections: the out_pin and in_pin +# must be wired together. + +try: + from machine import Counter +except ImportError: + print("SKIP") + raise SystemExit + +import sys +from machine import Pin + +if "esp32" in sys.platform: + id = 0 + out_pin = 4 + in_pin = 5 +else: + print("Please add support for this test on this platform.") + raise SystemExit + +import unittest + +out_pin = Pin(out_pin, mode=Pin.OUT) +in_pin = Pin(in_pin, mode=Pin.IN) + + +def toggle(times): + for _ in range(times): + out_pin(1) + out_pin(0) + + +class TestCounter(unittest.TestCase): + def setUp(self): + out_pin(0) + self.counter = Counter(id, in_pin) + + def tearDown(self): + self.counter.deinit() + + def assertCounter(self, value): + self.assertEqual(self.counter.value(), value) + + def test_connections(self): + # Test the hardware connections are correct. If this test fails, all tests will fail. + out_pin(1) + self.assertEqual(1, in_pin()) + out_pin(0) + self.assertEqual(0, in_pin()) + + def test_count_rising(self): + self.assertCounter(0) + toggle(100) + self.assertCounter(100) + out_pin(1) + self.assertEqual(self.counter.value(0), 101) + self.assertCounter(0) # calling value(0) resets + out_pin(0) + self.assertCounter(0) # no rising edge + out_pin(1) + self.assertCounter(1) + + def test_change_directions(self): + self.assertCounter(0) + toggle(100) + self.assertCounter(100) + self.counter.init(in_pin, direction=Counter.DOWN) + self.assertCounter(0) # calling init() zeroes the counter + self.counter.value(100) # need to manually reset the value + self.assertCounter(100) + toggle(25) + self.assertCounter(75) + + def test_count_falling(self): + self.counter.init(in_pin, direction=Counter.UP, edge=Counter.FALLING) + toggle(20) + self.assertCounter(20) + out_pin(1) + self.assertCounter(20) # no falling edge + out_pin(0) + self.assertCounter(21) + self.counter.value(-(2**24)) + toggle(20) + self.assertCounter(-(2**24 - 20)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod_hardware/machine_encoder.py b/tests/extmod_hardware/machine_encoder.py new file mode 100644 index 00000000000..9bd2bb46417 --- /dev/null +++ b/tests/extmod_hardware/machine_encoder.py @@ -0,0 +1,99 @@ +# Test machine.Encoder implementation +# +# IMPORTANT: This test requires hardware connections: +# - out0_pin and in0_pin must be wired together. +# - out1_pin and in1_pin must be wired together. + +try: + from machine import Encoder +except ImportError: + print("SKIP") + raise SystemExit + +import sys +from machine import Pin + +if "esp32" in sys.platform: + id = 0 + out0_pin = 4 + in0_pin = 5 + out1_pin = 12 + in1_pin = 13 +else: + print("Please add support for this test on this platform.") + raise SystemExit + +import unittest + +out0_pin = Pin(out0_pin, mode=Pin.OUT) +in0_pin = Pin(in0_pin, mode=Pin.IN) +out1_pin = Pin(out1_pin, mode=Pin.OUT) +in1_pin = Pin(in1_pin, mode=Pin.IN) + + +class TestEncoder(unittest.TestCase): + def setUp(self): + out0_pin(0) + out1_pin(0) + self.enc = Encoder(id, in0_pin, in1_pin) + self.pulses = 0 # track the expected encoder position in software + + def tearDown(self): + self.enc.deinit() + + def rotate(self, pulses): + for _ in range(abs(pulses)): + self.pulses += 1 if (pulses > 0) else -1 + q = self.pulses % 4 + # Only one pin should change state each "step" so output won't glitch + out0_pin(q in (1, 2)) + out1_pin(q in (2, 3)) + + def assertPosition(self, value): + self.assertEqual(self.enc.value(), value) + + def test_connections(self): + # Test the hardware connections are correct. If this test fails, all tests will fail. + for ch, outp, inp in ((0, out0_pin, in0_pin), (1, out1_pin, in1_pin)): + print("Testing channel ", ch) + outp(1) + self.assertEqual(1, inp()) + outp(0) + self.assertEqual(0, inp()) + + def test_basics(self): + self.assertPosition(0) + self.rotate(100) + self.assertPosition(100 // 4) + self.rotate(-100) + self.assertPosition(0) + + def test_partial(self): + # With phase=1 (default), need 4x pulses to count a rotation + self.assertPosition(0) + self.rotate(1) + self.assertPosition(0) + self.rotate(1) + self.assertPosition(0) + self.rotate(1) + self.assertPosition(1) # only 3 pulses to count first rotation? + self.rotate(1) + self.assertPosition(1) + self.rotate(1) + self.assertPosition(1) + self.rotate(1) + self.assertPosition(1) + self.rotate(1) + self.assertPosition(2) # 4 for next rotation + self.rotate(-1) + self.assertPosition(1) + self.rotate(-4) + self.assertPosition(0) + self.rotate(-4) + self.assertPosition(-1) + self.rotate(-3) + self.assertPosition(-1) + + +if __name__ == "__main__": + unittest.main() From 026a20da3e803328c4fc9bce66b9931d18f12c1e Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 1 May 2025 17:12:45 +0200 Subject: [PATCH 1061/2098] tools/mpremote: Add platformdirs dependency to requirements.txt. Needed to easily find the user configuration file. Signed-off-by: Jos Verlinde --- tools/mpremote/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/mpremote/requirements.txt b/tools/mpremote/requirements.txt index 6209cde5c33..0aa2bb42a95 100644 --- a/tools/mpremote/requirements.txt +++ b/tools/mpremote/requirements.txt @@ -1,2 +1,3 @@ pyserial >= 3.3 importlib_metadata >= 1.4; python_version < "3.8" +platformdirs >= 4.3.7 From 64b3944b0121710199d8d5958415ce71f21d67a7 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 1 May 2025 17:17:06 +0200 Subject: [PATCH 1062/2098] tools/mpremote: Locate config.py location across different host OSes. Use `platformdirs.user_config_dir()` (see https://platformdirs.readthedocs.io/en/latest/api.html#user-config-directory) to provide portability across many different OSes and configuration styles. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/main.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index 0aec1efad14..b31186ba2e1 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -22,6 +22,8 @@ from collections.abc import Mapping from textwrap import dedent +import platformdirs + from .commands import ( CommandError, do_connect, @@ -439,13 +441,7 @@ def load_user_config(): config.commands = {} # Get config file name. - path = os.getenv("XDG_CONFIG_HOME") - if path is None: - path = os.getenv("HOME") - if path is None: - return config - path = os.path.join(path, ".config") - path = os.path.join(path, _PROG) + path = platformdirs.user_config_dir(appname=_PROG, appauthor=False) config_file = os.path.join(path, "config.py") # Check if config file exists. @@ -457,6 +453,9 @@ def load_user_config(): config_data = f.read() prev_cwd = os.getcwd() os.chdir(path) + # Pass in the config path so that the config file can use it. + config.__dict__["config_path"] = path + config.__dict__["__file__"] = config_file exec(config_data, config.__dict__) os.chdir(prev_cwd) From a9dd741e66a6afba3a6e862d134246d157a56777 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 1 May 2025 17:25:19 +0200 Subject: [PATCH 1063/2098] docs/reference/mpremote: Document location of config file. Signed-off-by: Jos Verlinde --- docs/reference/mpremote.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index bee008c6373..8aecc3a5d48 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -108,7 +108,7 @@ The full list of supported commands are: **Note:** Instead of using the ``connect`` command, there are several :ref:`pre-defined shortcuts ` for common device paths. For example the ``a0`` shortcut command is equivalent to - ``connect /dev/ttyACM0`` (Linux), or ``c0`` for ``COM0`` (Windows). + ``connect /dev/ttyACM0`` (Linux), or ``c1`` for ``COM1`` (Windows). **Note:** The ``auto`` option will only detect USB serial ports, i.e. a serial port that has an associated USB VID/PID (i.e. CDC/ACM or FTDI-style @@ -487,9 +487,16 @@ Shortcuts can be defined using the macro system. Built-in shortcuts are: - ``cat``, ``edit``, ``ls``, ``cp``, ``rm``, ``mkdir``, ``rmdir``, ``touch``: Aliases for ``fs `` -Additional shortcuts can be defined by in user-configuration files, which is -located at ``.config/mpremote/config.py``. This file should define a -dictionary named ``commands``. The keys of this dictionary are the shortcuts +Additional shortcuts can be defined in the user configuration file ``mpremote/config.py``, +located in the User Configuration Directory. +The correct location for each OS is determined using the ``platformdirs`` module. + +This is typically: +- ``$XDG_CONFIG_HOME/mpremote/config.py`` +- ``$HOME/.config/mpremote/config.py`` +- ``$env:LOCALAPPDATA/mpremote/config.py`` + +The ``config.py``` file should define a dictionary named ``commands``. The keys of this dictionary are the shortcuts and the values are either a string or a list-of-strings: .. code-block:: python3 From e6739fc87e9d633484aeb72fa2efac14e4ff7681 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 8 Jul 2025 14:02:42 +0100 Subject: [PATCH 1064/2098] rp2/rp2_flash: Add binary info for ROMFS. This describes the ROMFS location and size in Pico SDK's binary declaration format, so it can be read from a .uf2 file for use with various tools. Signed-off-by: Phil Howard --- ports/rp2/rp2_flash.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index d6b9e136533..4d41d45733c 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -103,6 +103,18 @@ bi_decl(bi_block_device( BINARY_INFO_BLOCK_DEV_FLAG_WRITE | BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); +#if MICROPY_HW_ROMFS_BYTES +// Tag the ROMFS partition in the binary +bi_decl(bi_block_device( + BINARY_INFO_TAG_MICROPYTHON, + "ROMFS", + XIP_BASE + MICROPY_HW_ROMFS_BASE, + MICROPY_HW_ROMFS_BYTES, + NULL, + BINARY_INFO_BLOCK_DEV_FLAG_READ | + BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); +#endif + // This is a workaround to pico-sdk #2201: https://github.com/raspberrypi/pico-sdk/issues/2201 // which means the multicore_lockout_victim_is_initialized returns true even after core1 is reset. static bool use_multicore_lockout(void) { From 3c9546ea0911b50d4b85ad4046864c90f84b3fd3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 2 Aug 2025 08:52:05 +1000 Subject: [PATCH 1065/2098] esp32/mpconfigport: Disable I2CTarget on ESP32-C6 to reduce code size. I2CTarget costs about 8k of flash size on ESP32-S2, and about 11k on ESP32-C6. The ESP32-C6 only has about 8k remaining, so disable I2CTarget on that SoC until more flash can be made available. Signed-off-by: Damien George --- ports/esp32/mpconfigport.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 48ad39ef7ed..cc10fd0b7bb 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -140,7 +140,8 @@ #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) // I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs. -#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32) +// ESP32C6 does not have enough flash space so also disable it on that SoC. +#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32 && !CONFIG_IDF_TARGET_ESP32C6) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/esp32/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) #define MICROPY_PY_MACHINE_SOFTI2C (1) From 658a2e3dbd0316204a13f2478d98ea3f8649f064 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 2 Aug 2025 08:39:16 +1000 Subject: [PATCH 1066/2098] github/workflows: Add a CI job to build ESP32-C2 and ESP32-C6 boards. So that all six supported SoCs are built by CI. Signed-off-by: Damien George --- .github/workflows/ports_esp32.yml | 1 + tools/ci.sh | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 4c07f894377..36ab341cfcd 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -25,6 +25,7 @@ jobs: ci_func: # names are functions in ci.sh - esp32_build_cmod_spiram_s2 - esp32_build_s3_c3 + - esp32_build_c2_c6 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/tools/ci.sh b/tools/ci.sh index d787cbcf073..8f045639b80 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -214,6 +214,13 @@ function ci_esp32_build_s3_c3 { make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C3 } +function ci_esp32_build_c2_c6 { + ci_esp32_build_common + + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C2 + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C6 +} + ######################################################################################## # ports/esp8266 From c0252d73c6499cf8c09fa57e17f4b530315b0ee0 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 2 Aug 2025 15:24:45 -0500 Subject: [PATCH 1067/2098] py/parse: Fix missing nlr_pop call in complex path of binary_op_maybe. Reproducer (needs to be run as one compilation unit): ans = (-1) ** 2.3 aa Fixes issue #17815. Signed-off-by: Jeff Epler --- py/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/parse.c b/py/parse.c index 91eea3e3689..6260987b28a 100644 --- a/py/parse.c +++ b/py/parse.c @@ -671,13 +671,13 @@ static bool binary_op_maybe(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs, mp_ob nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t tmp = mp_binary_op(op, lhs, rhs); + nlr_pop(); #if MICROPY_PY_BUILTINS_COMPLEX if (mp_obj_is_type(tmp, &mp_type_complex)) { return false; } #endif *res = tmp; - nlr_pop(); return true; } else { return false; From 7c8ae78a0384ce3fac2e9421df2590ef7d7031ef Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 4 Aug 2025 01:45:30 +1000 Subject: [PATCH 1068/2098] lib/micropython-lib: Update submodule to latest. This brings in: - lora: fix SNR value in SX126x received packets - utop: add initial implementation for ESP32 - utop: print MicroPython memory info - utop: print IDF heap details - urllib.urequest: add support for headers to urequest.urlopen - aiorepl: use blocking reads for raw REPL and raw paste - errno: add ENOTCONN constant - logging: allow logging.exception helper to handle tracebacks - aioble-l2cap: raise correct error if l2cap disconnects during send - abc: add ABC base class - aiohttp: fix partial reads by using readexactly - aiorepl: handle stream shutdown Signed-off-by: Damien George --- lib/micropython-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/micropython-lib b/lib/micropython-lib index 5b496e944ec..34c4ee1647a 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit 5b496e944ec045177afa1620920a168410b7f60b +Subproject commit 34c4ee1647ac4b177ae40adf0ec514660e433dc0 From 255d74b5a8c31ec06d1c2c528f7888f6c2dfc246 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 4 Aug 2025 02:00:47 +1000 Subject: [PATCH 1069/2098] renesas-ra/mpconfigport: Enable MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE. This setting was missed in df05caea6c6437a8b4756ec502a5e6210f4b6256. It's needed for this port to pass its `tests/ports/renesas-ra/modtime.py` test. Signed-off-by: Damien George --- ports/renesas-ra/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index 868cdbc7d6a..70d38e7d20e 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -99,6 +99,7 @@ #ifndef MICROPY_FLOAT_IMPL // can be configured by each board via mpconfigboard.mk #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #endif +#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (1) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_SCHEDULER_STATIC_NODES (1) From d5ecda05eb743839e9011f3d23657b39cd05e6c0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 7 Aug 2025 10:33:26 +1000 Subject: [PATCH 1070/2098] ports: Allow MICROPY_PY_MACHINE_I2C_TARGET to be disabled by board cfg. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 2 ++ ports/esp32/mpconfigport.h | 2 ++ ports/mimxrt/mpconfigport.h | 2 ++ ports/rp2/mpconfigport.h | 2 ++ ports/stm32/mpconfigboard_common.h | 2 ++ 5 files changed, 10 insertions(+) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index a8c8c842977..8a65721bb0f 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -132,10 +132,12 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (MICROPY_HW_ENABLE_HW_I2C) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) +#ifndef MICROPY_PY_MACHINE_I2C_TARGET #define MICROPY_PY_MACHINE_I2C_TARGET (MICROPY_HW_ENABLE_HW_I2C) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/alif/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (4) #define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) +#endif #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index cc10fd0b7bb..e47b333c736 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -139,11 +139,13 @@ #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) +#ifndef MICROPY_PY_MACHINE_I2C_TARGET // I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs. // ESP32C6 does not have enough flash space so also disable it on that SoC. #define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32 && !CONFIG_IDF_TARGET_ESP32C6) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/esp32/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) +#endif #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SOFTSPI (1) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 8ceb3b41837..3cf6550d7b0 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -92,11 +92,13 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/mimxrt/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) +#ifndef MICROPY_PY_MACHINE_I2C_TARGET #define MICROPY_PY_MACHINE_I2C_TARGET (1) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/mimxrt/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (FSL_FEATURE_SOC_LPI2C_COUNT) #define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_I2C_TARGET_FINALISER (1) +#endif #ifndef MICROPY_PY_MACHINE_I2S #define MICROPY_PY_MACHINE_I2S (0) #endif diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index d35563bd070..c312293ace6 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -171,10 +171,12 @@ #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/rp2/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) +#ifndef MICROPY_PY_MACHINE_I2C_TARGET #define MICROPY_PY_MACHINE_I2C_TARGET (1) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/rp2/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) #define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) +#endif #define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_I2S (1) #define MICROPY_PY_MACHINE_I2S_INCLUDEFILE "ports/rp2/machine_i2s.c" diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index dcbbbccceb6..a8e50be5c6a 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -639,11 +639,13 @@ #if defined(MICROPY_HW_I2C1_SCL) || defined(MICROPY_HW_I2C2_SCL) \ || defined(MICROPY_HW_I2C3_SCL) || defined(MICROPY_HW_I2C4_SCL) #define MICROPY_HW_ENABLE_HW_I2C (1) +#ifndef MICROPY_HW_ENABLE_HW_I2C_TARGET #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) #define MICROPY_HW_ENABLE_HW_I2C_TARGET (1) #else #define MICROPY_HW_ENABLE_HW_I2C_TARGET (0) #endif +#endif #else #define MICROPY_HW_ENABLE_HW_I2C (0) #define MICROPY_HW_ENABLE_HW_I2C_TARGET (0) From ce109af712452474dfe3864f82b5430f4e2a5ff0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 15:47:15 +1000 Subject: [PATCH 1071/2098] esp32/machine_timer: Enable timer clock source for ESP32C6. Otherwise the PLL is not enabled. These changes are adapted from `timer_legacy.h` in ESP-IDF. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_timer.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index a104288f6ee..faa47d59afc 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -38,6 +38,8 @@ #include "hal/timer_hal.h" #include "hal/timer_ll.h" #include "soc/timer_periph.h" +#include "esp_private/esp_clk_tree_common.h" +#include "esp_private/periph_ctrl.h" #include "machine_timer.h" #define TIMER_DIVIDER 8 @@ -163,8 +165,18 @@ static void machine_timer_isr_handler(machine_timer_obj_t *self) { void machine_timer_enable(machine_timer_obj_t *self) { // Initialise the timer. timer_hal_init(&self->hal_context, self->group, self->index); + + PERIPH_RCC_ACQUIRE_ATOMIC(timer_group_periph_signals.groups[self->index].module, ref_count) { + if (ref_count == 0) { + timer_ll_enable_bus_clock(self->index, true); + timer_ll_reset_register(self->index); + } + } + timer_ll_enable_counter(self->hal_context.dev, self->index, false); + esp_clk_tree_enable_src(GPTIMER_CLK_SRC_DEFAULT, true); timer_ll_set_clock_source(self->hal_context.dev, self->index, GPTIMER_CLK_SRC_DEFAULT); + timer_ll_enable_clock(self->hal_context.dev, self->index, true); timer_ll_set_clock_prescale(self->hal_context.dev, self->index, TIMER_DIVIDER); timer_hal_set_counter_value(&self->hal_context, 0); timer_ll_set_count_direction(self->hal_context.dev, self->index, GPTIMER_COUNT_UP); From 593ae04eeb06b6fd7c6232eb41912b8d26170ddd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 15:57:38 +1000 Subject: [PATCH 1072/2098] esp32/machine_timer: Fix machine.Timer() tick frequency on ESP32C2,C6. Also future-proofs this code for other chips. Apart form C6 and C2, all currently supported chips use APB clock for GPTIMER_CLK_SRC_DEFAULT. ESP32-C2 uses 40MHz PLL but APB_CLK_FREQ was 26MHz. ESP32-C6 uses 80MHz PLL but APB_CLK_FREQ was 40MHz. Implementation now gets the correct frequency from ESP-IDF. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_timer.c | 24 +++++++++++++++--------- ports/esp32/machine_timer.h | 9 ++------- ports/esp32/machine_uart.c | 4 ++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index faa47d59afc..3fb893aadf1 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -42,11 +42,9 @@ #include "esp_private/periph_ctrl.h" #include "machine_timer.h" +#define TIMER_CLK_SRC GPTIMER_CLK_SRC_DEFAULT #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 const mp_obj_type_t machine_timer_type; @@ -54,6 +52,14 @@ const mp_obj_type_t machine_timer_type; 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); static mp_obj_t machine_timer_deinit(mp_obj_t self_in); +uint32_t machine_timer_freq_hz(void) { + // The timer source clock is APB or a fixed PLL (depending on chip), both constant frequency. + uint32_t freq; + check_esp_err(esp_clk_tree_src_get_freq_hz(TIMER_CLK_SRC, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq)); + assert(freq % TIMER_DIVIDER == 0); // Source clock should divide evenly into TIMER_DIVIDER + return freq / TIMER_DIVIDER; +} + void machine_timer_deinit_all(void) { // Disable, deallocate and remove all timers from list machine_timer_obj_t **t = &MP_STATE_PORT(machine_timer_obj_head); @@ -68,7 +74,7 @@ void machine_timer_deinit_all(void) { static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_timer_obj_t *self = self_in; qstr mode = self->repeat ? MP_QSTR_PERIODIC : MP_QSTR_ONE_SHOT; - uint64_t period = self->period / (TIMER_SCALE / 1000); // convert to ms + uint64_t period = self->period / (machine_timer_freq_hz() / 1000); // convert to ms #if SOC_TIMER_GROUP_TIMERS_PER_GROUP == 1 mp_printf(print, "Timer(%u, mode=%q, period=%lu)", self->group, mode, period); #else @@ -174,8 +180,8 @@ void machine_timer_enable(machine_timer_obj_t *self) { } timer_ll_enable_counter(self->hal_context.dev, self->index, false); - esp_clk_tree_enable_src(GPTIMER_CLK_SRC_DEFAULT, true); - timer_ll_set_clock_source(self->hal_context.dev, self->index, GPTIMER_CLK_SRC_DEFAULT); + esp_clk_tree_enable_src(TIMER_CLK_SRC, true); + timer_ll_set_clock_source(self->hal_context.dev, self->index, TIMER_CLK_SRC); timer_ll_enable_clock(self->hal_context.dev, self->index, true); timer_ll_set_clock_prescale(self->hal_context.dev, self->index, TIMER_DIVIDER); timer_hal_set_counter_value(&self->hal_context, 0); @@ -236,7 +242,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n #if MICROPY_PY_BUILTINS_FLOAT if (args[ARG_freq].u_obj != mp_const_none) { - self->period = (uint64_t)(TIMER_SCALE / mp_obj_get_float(args[ARG_freq].u_obj)); + self->period = (uint64_t)(machine_timer_freq_hz() / mp_obj_get_float(args[ARG_freq].u_obj)); } #else if (args[ARG_freq].u_int != 0xffffffff) { @@ -244,7 +250,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n } #endif else { - self->period = (((uint64_t)args[ARG_period].u_int) * TIMER_SCALE) / args[ARG_tick_hz].u_int; + self->period = (((uint64_t)args[ARG_period].u_int) * machine_timer_freq_hz()) / args[ARG_tick_hz].u_int; } self->repeat = args[ARG_mode].u_int; @@ -280,7 +286,7 @@ static mp_obj_t machine_timer_value(mp_obj_t self_in) { mp_raise_ValueError(MP_ERROR_TEXT("timer not set")); } uint64_t result = timer_ll_get_counter_value(self->hal_context.dev, self->index); - return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result / (TIMER_SCALE / 1000))); // value in ms + return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result / (machine_timer_freq_hz() / 1000))); // value in ms } static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_value_obj, machine_timer_value); diff --git a/ports/esp32/machine_timer.h b/ports/esp32/machine_timer.h index 10fe2f39c90..5dd0ce95ac8 100644 --- a/ports/esp32/machine_timer.h +++ b/ports/esp32/machine_timer.h @@ -34,13 +34,6 @@ #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; @@ -64,4 +57,6 @@ machine_timer_obj_t *machine_timer_create(mp_uint_t timer); void machine_timer_enable(machine_timer_obj_t *self); void machine_timer_disable(machine_timer_obj_t *self); +uint32_t machine_timer_freq_hz(void); + #endif // MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 661c07138e5..c4dab2cae5c 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -58,7 +58,7 @@ #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 +#define RXIDLE_TIMER_MIN (machine_timer_freq_hz() * 5 / 10000) // 500us minimum rxidle time #define UART_QUEUE_SIZE (3) enum { @@ -535,7 +535,7 @@ static void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger 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; + mp_int_t period = machine_timer_freq_hz() * 20 / baudrate + 1; if (period < RXIDLE_TIMER_MIN) { period = RXIDLE_TIMER_MIN; } From 4ce2dd2cdab6e57f3982fc899f15a2103d71b0be Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 9 Aug 2025 23:31:31 +1000 Subject: [PATCH 1073/2098] all: Bump version to 1.26.0. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index ed5ccccb6a5..8775b74bb61 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -32,7 +32,7 @@ #define MICROPY_VERSION_MAJOR 1 #define MICROPY_VERSION_MINOR 26 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 1 +#define MICROPY_VERSION_PRERELEASE 0 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 7e8705fe2bdb083535e85c206b9fed9d2754e665 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 08:42:11 +1000 Subject: [PATCH 1074/2098] all: Bump version to 1.27.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 8775b74bb61..dd41ff6a708 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -30,9 +30,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 26 +#define MICROPY_VERSION_MINOR 27 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From c35427cbdbe105c8a3be099f8fd2946fa96c4a22 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Jul 2025 17:31:24 +1000 Subject: [PATCH 1075/2098] tests/run-tests.py: Move tests to skip with native emitter to a list. This makes `run-tests.py` a little more organised, by putting all the tests-to-skip-when-using-the-native-emitter in a dedicated list. This should make it easier to maintain the list, and understand why a test is there. Signed-off-by: Damien George --- tests/run-tests.py | 85 ++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 48dfc2f26e0..cb3a2c3cd16 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -102,6 +102,48 @@ def open(self, path, mode): # Platforms associated with the unix port, values of `sys.platform`. PC_PLATFORMS = ("darwin", "linux", "win32") +# Tests to skip for specific emitters. +emitter_tests_to_skip = { + # Some tests are known to fail with native emitter. + # Remove them from the below when they work. + "native": ( + # These require raise_varargs. + "basics/gen_yield_from_close.py", + "basics/try_finally_return2.py", + "basics/try_reraise.py", + "basics/try_reraise2.py", + "misc/features.py", + # These require checking for unbound local. + "basics/annotate_var.py", + "basics/del_deref.py", + "basics/del_local.py", + "basics/scope_implicit.py", + "basics/unboundlocal.py", + # These require "raise from". + "basics/exception_chain.py", + # These require proper traceback info. + "basics/sys_tracebacklimit.py", + "misc/print_exception.py", + "micropython/emg_exc.py", + "micropython/heapalloc_traceback.py", + "micropython/opt_level_lineno.py", + # These require stack-allocated slice optimisation. + "micropython/heapalloc_slice.py", + # These require running the scheduler. + "micropython/schedule.py", + "extmod/asyncio_event_queue.py", + "extmod/asyncio_iterator_event.py", + # These require sys.exc_info(). + "misc/sys_exc_info.py", + # These require sys.settrace(). + "misc/sys_settrace_features.py", + "misc/sys_settrace_generator.py", + "misc/sys_settrace_loop.py", + # These are bytecode-specific tests. + "stress/bytecode_limit.py", + ), +} + # Tests to skip on specific targets. # These are tests that are difficult to detect that they should not be run on the given target. platform_tests_to_skip = { @@ -824,6 +866,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("basics/exception_chain.py") # warning is not printed skip_tests.add("micropython/meminfo.py") # output is very different to PC output + # Skip emitter-specific tests. + skip_tests.update(emitter_tests_to_skip.get(args.emit, ())) + # Skip platform-specific tests. skip_tests.update(platform_tests_to_skip.get(args.platform, ())) @@ -837,46 +882,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Works but CPython uses '\' path separator skip_tests.add("import/import_file.py") - # Some tests are known to fail with native emitter - # Remove them from the below when they work - if args.emit == "native": - skip_tests.add("basics/gen_yield_from_close.py") # require raise_varargs - skip_tests.update( - {"basics/%s.py" % t for t in "try_reraise try_reraise2".split()} - ) # require raise_varargs - skip_tests.add("basics/annotate_var.py") # requires checking for unbound local - skip_tests.add("basics/del_deref.py") # requires checking for unbound local - skip_tests.add("basics/del_local.py") # requires checking for unbound local - skip_tests.add("basics/exception_chain.py") # raise from is not supported - skip_tests.add("basics/scope_implicit.py") # requires checking for unbound local - skip_tests.add("basics/sys_tracebacklimit.py") # requires traceback info - skip_tests.add("basics/try_finally_return2.py") # requires raise_varargs - skip_tests.add("basics/unboundlocal.py") # requires checking for unbound local - skip_tests.add("misc/features.py") # requires raise_varargs - skip_tests.add( - "misc/print_exception.py" - ) # because native doesn't have proper traceback info - skip_tests.add("misc/sys_exc_info.py") # sys.exc_info() is not supported for native - skip_tests.add("misc/sys_settrace_features.py") # sys.settrace() not supported - skip_tests.add("misc/sys_settrace_generator.py") # sys.settrace() not supported - skip_tests.add("misc/sys_settrace_loop.py") # sys.settrace() not supported - skip_tests.add( - "micropython/emg_exc.py" - ) # because native doesn't have proper traceback info - skip_tests.add( - "micropython/heapalloc_slice.py" - ) # because native doesn't do the stack-allocated slice optimisation - skip_tests.add( - "micropython/heapalloc_traceback.py" - ) # because native doesn't have proper traceback info - skip_tests.add( - "micropython/opt_level_lineno.py" - ) # native doesn't have proper traceback info - skip_tests.add("micropython/schedule.py") # native code doesn't check pending events - skip_tests.add("stress/bytecode_limit.py") # bytecode specific test - skip_tests.add("extmod/asyncio_event_queue.py") # native can't run schedule - skip_tests.add("extmod/asyncio_iterator_event.py") # native can't run schedule - def run_one_test(test_file): test_file = test_file.replace("\\", "/") test_file_abspath = os.path.abspath(test_file).replace("\\", "/") From 1401fdb8b39bf08e9ed9d4c1293207a6bdc93cf8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Jul 2025 19:11:36 +1000 Subject: [PATCH 1076/2098] tests/run-tests.py: Run tests-with-regex-output as normal tests. Some tests (currently given by the `special_tests` list) have output which must be mached via a regex, because it can change from run to run (eg the address of an object is printed). These tests are currently classified as `is_special` in the test runner, which means they get special treatment. In particular they don't set the emitter as specified by `args.emit`. That means these tests do not run via .mpy or using the native emitter, even if those options are given on the command line. This commit fixes that by considering `is_special` as different to `tests_with_regex_output`. The former is used for things like target feature detection (which are not really tests) and when extra command line options need to be passed to the unix micropython executable. The latter (now called `tests_with_regex_output`) are specifically for tests that have output to be matched via regex. The `thread_exc2.py` test now needs to be excluded when running using the native emitter, because the native emitter doesn't print traceback info. And the `sys_settrace_cov.py` test needs to be excluded because set-trace output is different with the native emitter. Signed-off-by: Damien George --- tests/run-tests.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index cb3a2c3cd16..859f16f484a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -127,6 +127,7 @@ def open(self, path, mode): "micropython/emg_exc.py", "micropython/heapalloc_traceback.py", "micropython/opt_level_lineno.py", + "thread/thread_exc2.py", # These require stack-allocated slice optimisation. "micropython/heapalloc_slice.py", # These require running the scheduler. @@ -136,6 +137,7 @@ def open(self, path, mode): # These require sys.exc_info(). "misc/sys_exc_info.py", # These require sys.settrace(). + "misc/sys_settrace_cov.py", "misc/sys_settrace_features.py", "misc/sys_settrace_generator.py", "misc/sys_settrace_loop.py", @@ -392,7 +394,7 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): return had_crash, output_mupy -special_tests = [ +tests_with_regex_output = [ base_path(file) for file in ( "micropython/meminfo.py", @@ -409,10 +411,7 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): had_crash = False if pyb is None: # run on PC - if ( - test_file_abspath.startswith((base_path("cmdline/"), base_path("feature_check/"))) - or test_file_abspath in special_tests - ): + if test_file_abspath.startswith((base_path("cmdline/"), base_path("feature_check/"))): # special handling for tests of the unix cmdline program is_special = True @@ -544,7 +543,7 @@ def send_get(what): if is_special and not had_crash and b"\nSKIP\n" in output_mupy: return b"SKIP\n" - if is_special or test_file_abspath in special_tests: + if is_special or test_file_abspath in tests_with_regex_output: # convert parts of the output that are not stable across runs with open(test_file + ".exp", "rb") as f: lines_exp = [] From 1273751a3b6f8a14679378e2e082e03979cb2ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 22 Jul 2025 15:57:49 +0200 Subject: [PATCH 1077/2098] esp32: Support building against IDFv5.5. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- ports/esp32/boards/sdkconfig.base | 1 + ports/esp32/esp32_common.cmake | 2 +- ports/esp32/machine_timer.c | 5 +++++ ports/esp32/modespnow.c | 11 ++++++++++- ports/esp32/network_wlan.c | 7 ++++++- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 4bbccf77d17..78b09ec6b24 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -119,6 +119,7 @@ CONFIG_UART_ISR_IN_IRAM=y # IDF 5 deprecated CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN=y CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y +CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN=y CONFIG_ETH_USE_SPI_ETHERNET=y CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 9e8acf8897c..79a60adac9f 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -265,7 +265,7 @@ target_include_directories(${MICROPY_TARGET} PUBLIC # Add additional extmod and usermod components. if (MICROPY_PY_BTREE) - target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) + target_link_libraries(${MICROPY_TARGET} $) endif() target_link_libraries(${MICROPY_TARGET} usermod) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 3fb893aadf1..b0292a0379f 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -181,8 +181,13 @@ void machine_timer_enable(machine_timer_obj_t *self) { timer_ll_enable_counter(self->hal_context.dev, self->index, false); esp_clk_tree_enable_src(TIMER_CLK_SRC, true); + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) timer_ll_set_clock_source(self->hal_context.dev, self->index, TIMER_CLK_SRC); timer_ll_enable_clock(self->hal_context.dev, self->index, true); + #else + timer_ll_set_clock_source(self->group, self->index, TIMER_CLK_SRC); + timer_ll_enable_clock(self->group, self->index, true); + #endif timer_ll_set_clock_prescale(self->hal_context.dev, self->index, TIMER_DIVIDER); timer_hal_set_counter_value(&self->hal_context, 0); timer_ll_set_count_direction(self->hal_context.dev, self->index, GPTIMER_COUNT_UP); diff --git a/ports/esp32/modespnow.c b/ports/esp32/modespnow.c index 7873ff89778..ab50032ffeb 100644 --- a/ports/esp32/modespnow.c +++ b/ports/esp32/modespnow.c @@ -179,7 +179,11 @@ static mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, } // Forward declare the send and recv ESPNow callbacks +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status); +#else +static void send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t status); +#endif static void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len); @@ -539,7 +543,12 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_send_obj, 2, 4, espnow_send); // Callback triggered when a sent packet is acknowledged by the peer (or not). // Just count the number of responses and number of failures. // These are used in the send() logic. -static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) +static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) +#else +static void send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t status) +#endif +{ esp_espnow_obj_t *self = _get_singleton(); self->tx_responses++; if (status != ESP_NOW_SEND_SUCCESS) { diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index e85d1328fdc..e20af4806c4 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -767,6 +767,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA3_ENTERPRISE) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_ENT), MP_ROM_INT(WIFI_AUTH_WPA_ENTERPRISE) }, + #endif { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, @@ -774,7 +777,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) +_Static_assert(WIFI_AUTH_MAX == 17, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) _Static_assert(WIFI_AUTH_MAX == 16, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) _Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); From 361c615f3e272df012fc18315e226f9131d25c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 10 Jul 2025 11:05:19 +0200 Subject: [PATCH 1078/2098] esp32/network_ppp: Use thread-safe API for PPPoS input. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A small follow-up to 3b1e22c66947271e8b60eddf4e8aa6dadc6d9a7d, in which the entire PPP implementation was reworked to more closely resemble the extmod version. One of the differences between extmod and the ESP32 port is that the ESP32 port uses the thread-safe API, but in that changeset the PPP input function was accidentally changed to use the non-safe API. Signed-off-by: Daniël van de Giessen --- ports/esp32/network_ppp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 8b700c98ef3..77e4385ca1e 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -163,7 +163,7 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } mp_printf(&mp_plat_print, ")\n"); #endif - pppos_input(self->pcb, (u8_t *)buf, len); + pppos_input_tcpip(self->pcb, (u8_t *)buf, len); total_len += len; } From adcfdf625be4ce1a8506dccc33ea60ab5d84ab46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 10 Jul 2025 14:43:29 +0200 Subject: [PATCH 1079/2098] esp32/network_ppp: Use non-thread-safe API inside status callback. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The status callback runs on the lwIP tcpip_thread, and thus must use the non-thread-safe API because the thread-safe API would cause a deadlock. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 8 +++++--- ports/esp32/network_ppp.c | 11 ++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 2c3dac92012..883275f4819 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -62,8 +62,6 @@ typedef struct _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_stream_uart_irq_disable(network_ppp_obj_t *self) { if (self->stream == mp_const_none) { return; @@ -88,8 +86,12 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; } + network_ppp_stream_uart_irq_disable(self); // Clean up the PPP PCB. - network_ppp___del__(MP_OBJ_FROM_PTR(self)); + if (ppp_free(pcb) == ERR_OK) { + self->state = STATE_INACTIVE; + self->pcb = NULL; + } break; default: self->state = STATE_ERROR; diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 77e4385ca1e..3a9cb239db8 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -68,8 +68,6 @@ typedef struct _network_ppp_obj_t { const mp_obj_type_t esp_network_ppp_lwip_type; -static mp_obj_t network_ppp___del__(mp_obj_t self_in); - static void network_ppp_stream_uart_irq_disable(network_ppp_obj_t *self) { if (self->stream == mp_const_none) { return; @@ -94,8 +92,15 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; } + network_ppp_stream_uart_irq_disable(self); // Clean up the PPP PCB. - network_ppp___del__(MP_OBJ_FROM_PTR(self)); + // Note: Because we use pppapi_close instead of ppp_close, this + // callback will run on the lwIP tcpip_thread, thus to prevent a + // deadlock we must use the non-threadsafe function here. + if (ppp_free(pcb) == ERR_OK) { + self->state = STATE_INACTIVE; + self->pcb = NULL; + } break; default: self->state = STATE_ERROR; From 8c47ff7153a7a54eced6c8177698339abe4973ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 8 Jul 2025 15:52:17 +0200 Subject: [PATCH 1080/2098] esp32/network_ppp: Correctly clean up PPP PCB after close. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If PPP is still connected, freeing the PCB will fail and thus instead we should trigger a disconnect and wait for the lwIP callback to actually free the PCB. When PPP is not connected we should check if the freeing failed, warn the user if so, and only mark the connection as inactive if not. When all this happens during garbage collection the best case is that the PPP connection is already dead, which means the callback will be called immediately and cleanup will happen correctly. The worst case is that the connection is still alive, thus we are unable to free the PCB (lwIP won't let us) and it remains referenced in the netif_list, meaning a use-after-free happens later when lwIP traverses that linked list. This change does not fully prevent that, but it *does* improve how the PPP.active(False) method on the ESP32 port behaves: It no longer immediately tries to free (and fails), but instead triggers a disconnect and lets the cleanup happen correctly through the status callback. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 19 ++++++++++--------- ports/esp32/network_ppp.c | 18 +++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 883275f4819..12205521f67 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -119,17 +119,18 @@ static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, s 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 stream. - // Force the connection to close, with nocarrier=1. - self->state = STATE_INACTIVE; - ppp_close(self->pcb, 1); - } - network_ppp_stream_uart_irq_disable(self); + + network_ppp_stream_uart_irq_disable(self); + if (self->state >= STATE_ERROR) { + // Still connected over the stream. + // Force the connection to close, with nocarrier=1. + ppp_close(self->pcb, 1); + } else if (self->state >= STATE_ACTIVE) { // Free PPP PCB and reset state. + if (ppp_free(self->pcb) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_free failed")); + } self->state = STATE_INACTIVE; - ppp_free(self->pcb); self->pcb = NULL; } return mp_const_none; diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 3a9cb239db8..18e0c881688 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -128,17 +128,17 @@ static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, s 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 stream. - // Force the connection to close, with nocarrier=1. - self->state = STATE_INACTIVE; - pppapi_close(self->pcb, 1); - } - network_ppp_stream_uart_irq_disable(self); + network_ppp_stream_uart_irq_disable(self); + if (self->state >= STATE_ERROR) { + // Still connected over the stream. + // Force the connection to close, with nocarrier=1. + pppapi_close(self->pcb, 1); + } else if (self->state >= STATE_ACTIVE) { // Free PPP PCB and reset state. + if (pppapi_free(self->pcb) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("pppapi_free failed")); + } self->state = STATE_INACTIVE; - pppapi_free(self->pcb); self->pcb = NULL; } return mp_const_none; From 20e1ae07336a89e7054562a1bef26fae99802210 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sun, 3 Aug 2025 00:09:09 +0200 Subject: [PATCH 1081/2098] tools/mpremote: Fix encoding error in PyboardCommand. This is a fix for utf-8 decoding errors that are thrown when non-utf-8 content is received. For instance during a reboot of an ESP8266 module. The fix is to handle conversion errors by replacing illegal characters. Note that these illegal characters most often occur during an MCU reboot sequence when the MCU is using baudrates different from 115200. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/transport_serial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index daeff02b594..e2490a7caf8 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -795,7 +795,7 @@ def rd_str(self): if n == 0: return "" else: - return str(self.fin.read(n), "utf8") + return str(self.fin.read(n), "utf8", errors="backslashreplace") def wr_s8(self, i): self.fout.write(struct.pack(" {n}") From 6e450dba7e5042355462b807400ba8d389fc17e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 7 Aug 2025 13:30:13 +0200 Subject: [PATCH 1082/2098] tools/codeformat.py: Iterate lines instead of modifying list in-place. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Lechner Signed-off-by: Daniël van de Giessen --- tools/codeformat.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tools/codeformat.py b/tools/codeformat.py index c65174ddc12..afba5c336d8 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -92,21 +92,20 @@ def fixup_c(filename): # Write out file with fixups. with open(filename, "w", newline="") as f: dedent_stack = [] - while lines: - # Get next line. - l = lines.pop(0) - + for line_number, line in enumerate(lines, 1): # Dedent #'s to match indent of following line (not previous line). - m = re.match(r"( +)#(if |ifdef |ifndef |elif |else|endif)", l) + m = re.match(r"( +)#(if |ifdef |ifndef |elif |else|endif)", line) if m: indent = len(m.group(1)) directive = m.group(2) if directive in ("if ", "ifdef ", "ifndef "): - l_next = lines[0] - indent_next = len(re.match(r"( *)", l_next).group(1)) - if indent - 4 == indent_next and re.match(r" +(} else |case )", l_next): + # Line numbers are 1-based, and lists are always 0-based, + # thus this retrieves the next line, not the current one + line_next = lines[line_number] + indent_next = len(re.match(r"( *)", line_next).group(1)) + if indent - 4 == indent_next and re.match(r" +(} else |case )", line_next): # This #-line (and all associated ones) needs dedenting by 4 spaces. - l = l[4:] + line = line[4:] dedent_stack.append(indent - 4) else: # This #-line does not need dedenting. @@ -116,12 +115,12 @@ def fixup_c(filename): # This associated #-line needs dedenting to match the #if. indent_diff = indent - dedent_stack[-1] assert indent_diff >= 0 - l = l[indent_diff:] + line = line[indent_diff:] if directive == "endif": dedent_stack.pop() # Write out line. - f.write(l) + f.write(line) assert not dedent_stack, filename From 485dac783b8ba7b88fdbf28fcdf54eb053cd8ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 5 Aug 2025 13:21:59 +0200 Subject: [PATCH 1083/2098] tools/codeformat.py: Print filename + linenumber when dedenting fails. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- tools/codeformat.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/codeformat.py b/tools/codeformat.py index afba5c336d8..7f13a059f45 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -75,6 +75,10 @@ UNCRUSTIFY_CFG = os.path.join(TOP, "tools/uncrustify.cfg") +class IndentationError(ValueError): + pass + + def list_files(paths, exclusions=None, prefix=""): files = set() for pattern in paths: @@ -111,6 +115,10 @@ def fixup_c(filename): # This #-line does not need dedenting. dedent_stack.append(-1) else: + if len(dedent_stack) == 0: + raise IndentationError( + f'dedent stack is empty for "{directive}" at {filename}:{line_number}' + ) if dedent_stack[-1] >= 0: # This associated #-line needs dedenting to match the #if. indent_diff = indent - dedent_stack[-1] From 14ccdeb4d7b9b88ab012258e77d3070340fbc3da Mon Sep 17 00:00:00 2001 From: Jared Hancock Date: Mon, 25 Mar 2024 20:58:51 -0500 Subject: [PATCH 1084/2098] extmod/modre: Add support for start- and endpos. Pattern objects have two additional parameters for the ::search and ::match methods to define the starting and ending position of the subject within the string to be searched. This allows for searching a sub-string without creating a slice. However, one caveat of using the start-pos rather than a slice is that the start anchor (`^`) remains anchored to the beginning of the text. Signed-off-by: Jared Hancock --- docs/library/re.rst | 14 +++++- extmod/modre.c | 25 +++++++++- tests/extmod/re_start_end_pos.py | 78 ++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 tests/extmod/re_start_end_pos.py diff --git a/docs/library/re.rst b/docs/library/re.rst index 19b15d2d2c2..b8aeefd90cf 100644 --- a/docs/library/re.rst +++ b/docs/library/re.rst @@ -154,8 +154,8 @@ Regex objects Compiled regular expression. Instances of this class are created using `re.compile()`. -.. method:: regex.match(string) - regex.search(string) +.. method:: regex.match(string, [pos, [endpos]]) + regex.search(string, [pos, [endpos]]) regex.sub(replace, string, count=0, flags=0, /) Similar to the module-level functions :meth:`match`, :meth:`search` @@ -163,6 +163,16 @@ Compiled regular expression. Instances of this class are created using Using methods is (much) more efficient if the same regex is applied to multiple strings. + The optional second parameter *pos* gives an index in the string where the + search is to start; it defaults to ``0``. This is not completely equivalent + to slicing the string; the ``'^'`` pattern character matches at the real + beginning of the string and at positions just after a newline, but not + necessarily at the index where the search is to start. + + The optional parameter *endpos* limits how far the string will be searched; + it will be as if the string is *endpos* characters long, so only the + characters from *pos* to ``endpos - 1`` will be searched for a match. + .. method:: regex.split(string, max_split=-1, /) Split a *string* using regex. If *max_split* is given, it specifies diff --git a/extmod/modre.c b/extmod/modre.c index d17ec68d50e..85e5d1b0f74 100644 --- a/extmod/modre.c +++ b/extmod/modre.c @@ -196,10 +196,11 @@ static void re_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t // Note: this function can't be named re_exec because it may clash with system headers, eg on FreeBSD static mp_obj_t re_exec_helper(bool is_anchored, uint n_args, const mp_obj_t *args) { - (void)n_args; mp_obj_re_t *self; + bool was_compiled = false; if (mp_obj_is_type(args[0], (mp_obj_type_t *)&re_type)) { self = MP_OBJ_TO_PTR(args[0]); + was_compiled = true; } else { self = MP_OBJ_TO_PTR(mod_re_compile(1, args)); } @@ -207,6 +208,28 @@ static mp_obj_t re_exec_helper(bool is_anchored, uint n_args, const mp_obj_t *ar size_t len; subj.begin_line = subj.begin = mp_obj_str_get_data(args[1], &len); subj.end = subj.begin + len; + + if (was_compiled && n_args > 2) { + // Arg #2 is starting-pos + mp_int_t startpos = mp_obj_get_int(args[2]); + if (startpos > (mp_int_t)len) { + startpos = len; + } else if (startpos < 0) { + startpos = 0; + } + subj.begin += startpos; + if (n_args > 3) { + // Arg #3 is ending-pos + mp_int_t endpos = mp_obj_get_int(args[3]); + if (endpos > (mp_int_t)len) { + endpos = len; + } else if (endpos < startpos) { + endpos = startpos; + } + subj.end = subj.begin_line + endpos; + } + } + int caps_num = (self->re.sub + 1) * 2; mp_obj_match_t *match = m_new_obj_var(mp_obj_match_t, caps, char *, caps_num); // cast is a workaround for a bug in msvc: it treats const char** as a const pointer instead of a pointer to pointer to const char diff --git a/tests/extmod/re_start_end_pos.py b/tests/extmod/re_start_end_pos.py new file mode 100644 index 00000000000..bd16584374b --- /dev/null +++ b/tests/extmod/re_start_end_pos.py @@ -0,0 +1,78 @@ +# test start and end pos specification + +try: + import re +except ImportError: + print("SKIP") + raise SystemExit + + +def print_groups(match): + print("----") + try: + if match is not None: + i = 0 + while True: + print(match.group(i)) + i += 1 + except IndexError: + pass + + +p = re.compile(r"o") +m = p.match("dog") +print_groups(m) + +m = p.match("dog", 1) +print_groups(m) + +m = p.match("dog", 2) +print_groups(m) + +# No match past end of input +m = p.match("dog", 5) +print_groups(m) + +m = p.match("dog", 0, 1) +print_groups(m) + +# Caret only matches the actual beginning +p = re.compile(r"^o") +m = p.match("dog", 1) +print_groups(m) + +# End at beginning means searching empty string +p = re.compile(r"o") +m = p.match("dog", 1, 1) +print_groups(m) + +# End before the beginning doesn't match anything +m = p.match("dog", 2, 1) +print_groups(m) + +# Negative starting values don't crash +m = p.search("dog", -2) +print_groups(m) + +m = p.search("dog", -2, -5) +print_groups(m) + +# Search also works +print("--search") + +p = re.compile(r"o") +m = p.search("dog") +print_groups(m) + +m = p.search("dog", 1) +print_groups(m) + +m = p.search("dog", 2) +print_groups(m) + +# Negative starting values don't crash +m = p.search("dog", -2) +print_groups(m) + +m = p.search("dog", -2, -5) +print_groups(m) From f10707febb41e042e929efaf9ee4a66b847a68d4 Mon Sep 17 00:00:00 2001 From: Jared Hancock Date: Fri, 6 Jun 2025 18:12:29 -0500 Subject: [PATCH 1085/2098] extmod/modlwip: Support `family` specification in getaddrinfo. `socket.getaddrinfo()` supports the specification of an address family; however, the LwIP implementation does not use it. This change allows the application to specify the address family request in DNS resolution. If no family is specified, it falls back to the default preference configured with `network.ipconfig()`. Signed-off-by: Jared Hancock --- extmod/modlwip.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index b84b3b7626b..26aab6e8109 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -1802,10 +1802,11 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { mp_obj_t host_in = args[0], port_in = args[1]; const char *host = mp_obj_str_get_str(host_in); mp_int_t port = mp_obj_get_int(port_in); + mp_int_t family = 0; // If constraints were passed then check they are compatible with the supported params if (n_args > 2) { - mp_int_t family = mp_obj_get_int(args[2]); + family = mp_obj_get_int(args[2]); mp_int_t type = 0; mp_int_t proto = 0; mp_int_t flags = 0; @@ -1818,7 +1819,7 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { } } } - if (!((family == 0 || family == MOD_NETWORK_AF_INET) + if (!((family == 0 || family == MOD_NETWORK_AF_INET || family == MOD_NETWORK_AF_INET6) && (type == 0 || type == MOD_NETWORK_SOCK_STREAM) && proto == 0 && flags == 0)) { @@ -1829,11 +1830,23 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { getaddrinfo_state_t state; state.status = 0; + #if LWIP_VERSION_MAJOR >= 2 + // If family was specified, then try and resolve the address type as + // requested. Otherwise, use the default from network configuration. + if (family == MOD_NETWORK_AF_INET) { + family = LWIP_DNS_ADDRTYPE_IPV4; + } else if (family == MOD_NETWORK_AF_INET6) { + family = LWIP_DNS_ADDRTYPE_IPV6; + } else { + family = mp_mod_network_prefer_dns_use_ip_version == 4 ? LWIP_DNS_ADDRTYPE_IPV4_IPV6 : LWIP_DNS_ADDRTYPE_IPV6_IPV4; + } + #endif + MICROPY_PY_LWIP_ENTER #if LWIP_VERSION_MAJOR < 2 err_t ret = dns_gethostbyname(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state); #else - err_t ret = dns_gethostbyname_addrtype(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state, mp_mod_network_prefer_dns_use_ip_version == 4 ? LWIP_DNS_ADDRTYPE_IPV4_IPV6 : LWIP_DNS_ADDRTYPE_IPV6_IPV4); + err_t ret = dns_gethostbyname_addrtype(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state, family); #endif MICROPY_PY_LWIP_EXIT From b1d5c656de3f11fc1729f165c8d9709594ccbfb6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Jul 2025 23:48:01 +0200 Subject: [PATCH 1086/2098] tests/micropython: Remove big ints dependence for viper boundary tests. This commit provides an implementation for viper boundary tests that can work even without big int support. Since it uses a fixed-size buffer to hold values to work with, this should work on any platform as long as its integers are at least 32 bits wide, regardless its configuration on how big integers can get. Signed-off-by: Alessandro Gatti --- ...ntbig.py => viper_ptr16_store_boundary.py} | 43 ++++++++++--------- ....exp => viper_ptr16_store_boundary.py.exp} | 0 ...ntbig.py => viper_ptr32_store_boundary.py} | 42 +++++++++--------- ....exp => viper_ptr32_store_boundary.py.exp} | 0 ...intbig.py => viper_ptr8_store_boundary.py} | 42 +++++++++--------- ...y.exp => viper_ptr8_store_boundary.py.exp} | 0 6 files changed, 67 insertions(+), 60 deletions(-) rename tests/micropython/{viper_ptr16_store_boundary_intbig.py => viper_ptr16_store_boundary.py} (57%) rename tests/micropython/{viper_ptr16_store_boundary_intbig.py.exp => viper_ptr16_store_boundary.py.exp} (100%) rename tests/micropython/{viper_ptr32_store_boundary_intbig.py => viper_ptr32_store_boundary.py} (60%) rename tests/micropython/{viper_ptr32_store_boundary_intbig.py.exp => viper_ptr32_store_boundary.py.exp} (100%) rename tests/micropython/{viper_ptr8_store_boundary_intbig.py => viper_ptr8_store_boundary.py} (55%) rename tests/micropython/{viper_ptr8_store_boundary_intbig.py.exp => viper_ptr8_store_boundary.py.exp} (100%) diff --git a/tests/micropython/viper_ptr16_store_boundary_intbig.py b/tests/micropython/viper_ptr16_store_boundary.py similarity index 57% rename from tests/micropython/viper_ptr16_store_boundary_intbig.py rename to tests/micropython/viper_ptr16_store_boundary.py index 2193eddae13..3501a05685e 100644 --- a/tests/micropython/viper_ptr16_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -15,6 +15,23 @@ def set{off}(dest: ptr16): MASK = (1 << (8 * SIZE)) - 1 +next_int = 1 +test_buffer = bytearray(SIZE) + + +def next_value() -> uint: + global next_int + global test_buffer + for index in range(1, SIZE): + test_buffer[index - 1] = test_buffer[index] + test_buffer[SIZE - 1] = next_int + next_int += 1 + output = 0 + for byte in test_buffer: + output = (output << 8) | byte + return output & MASK + + @micropython.viper def set_index(dest: ptr16, i: int, val: uint): saved = dest @@ -27,8 +44,6 @@ def get_index(src, i): buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) -next = 1 -val = 0 for bit in BIT_THRESHOLDS: print("---", bit) pre, idx, post = ( @@ -36,22 +51,10 @@ def get_index(src, i): (((1 << bit) - (1 * SIZE)) // SIZE), ((1 << bit) // SIZE), ) - val = (val << 8) + next - next += 1 - set_index(buffer, pre, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, idx, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, post, val & MASK) - val = (val << 8) + next - next += 1 + set_index(buffer, pre, next_value()) + set_index(buffer, idx, next_value()) + set_index(buffer, post, next_value()) print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=post, val=val & MASK)) + exec(SET_TEMPLATE.format(off=pre, val=next_value())) + exec(SET_TEMPLATE.format(off=idx, val=next_value())) + exec(SET_TEMPLATE.format(off=post, val=next_value())) diff --git a/tests/micropython/viper_ptr16_store_boundary_intbig.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp similarity index 100% rename from tests/micropython/viper_ptr16_store_boundary_intbig.py.exp rename to tests/micropython/viper_ptr16_store_boundary.py.exp diff --git a/tests/micropython/viper_ptr32_store_boundary_intbig.py b/tests/micropython/viper_ptr32_store_boundary.py similarity index 60% rename from tests/micropython/viper_ptr32_store_boundary_intbig.py rename to tests/micropython/viper_ptr32_store_boundary.py index b44f31b00af..8c207278ec5 100644 --- a/tests/micropython/viper_ptr32_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -14,6 +14,22 @@ def set{off}(dest: ptr32): SIZE = 4 MASK = (1 << (8 * SIZE)) - 1 +next_int = 1 +test_buffer = bytearray(SIZE) + + +def next_value() -> uint: + global next_int + global test_buffer + for index in range(1, SIZE): + test_buffer[index - 1] = test_buffer[index] + test_buffer[SIZE - 1] = next_int + next_int += 1 + output = 0 + for byte in test_buffer: + output = (output << 8) | byte + return output & MASK + @micropython.viper def set_index(dest: ptr32, i: int, val: uint): @@ -32,8 +48,6 @@ def get_index(src, i): buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) -next = 1 -val = 0 for bit in BIT_THRESHOLDS: print("---", bit) pre, idx, post = ( @@ -41,22 +55,10 @@ def get_index(src, i): (((1 << bit) - (1 * SIZE)) // SIZE), ((1 << bit) // SIZE), ) - val = (val << 8) + next - next += 1 - set_index(buffer, pre, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, idx, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, post, val & MASK) - val = (val << 8) + next - next += 1 + set_index(buffer, pre, next_value()) + set_index(buffer, idx, next_value()) + set_index(buffer, post, next_value()) print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=post, val=val & MASK)) + exec(SET_TEMPLATE.format(off=pre, val=next_value())) + exec(SET_TEMPLATE.format(off=idx, val=next_value())) + exec(SET_TEMPLATE.format(off=post, val=next_value())) diff --git a/tests/micropython/viper_ptr32_store_boundary_intbig.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp similarity index 100% rename from tests/micropython/viper_ptr32_store_boundary_intbig.py.exp rename to tests/micropython/viper_ptr32_store_boundary.py.exp diff --git a/tests/micropython/viper_ptr8_store_boundary_intbig.py b/tests/micropython/viper_ptr8_store_boundary.py similarity index 55% rename from tests/micropython/viper_ptr8_store_boundary_intbig.py rename to tests/micropython/viper_ptr8_store_boundary.py index d22a0627433..d3cba17e163 100644 --- a/tests/micropython/viper_ptr8_store_boundary_intbig.py +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -14,6 +14,22 @@ def set{off}(dest: ptr8): SIZE = 1 MASK = (1 << (8 * SIZE)) - 1 +next_int = 1 +test_buffer = bytearray(SIZE) + + +def next_value() -> uint: + global next_int + global test_buffer + for index in range(1, SIZE): + test_buffer[index - 1] = test_buffer[index] + test_buffer[SIZE - 1] = next_int + next_int += 1 + output = 0 + for byte in test_buffer: + output = (output << 8) | byte + return output & MASK + @micropython.viper def set_index(dest: ptr8, i: int, val: uint): @@ -27,27 +43,13 @@ def get_index(src: ptr8, i: int): buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) -next = 1 -val = 0 for bit in BIT_THRESHOLDS: print("---", bit) pre, idx, post = (((1 << bit) - (2 * SIZE)), ((1 << bit) - (1 * SIZE)), (1 << bit)) - val = (val << 8) + next - next += 1 - set_index(buffer, pre, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, idx, val & MASK) - val = (val << 8) + next - next += 1 - set_index(buffer, post, val & MASK) - val = (val << 8) + next - next += 1 + set_index(buffer, pre, next_value()) + set_index(buffer, idx, next_value()) + set_index(buffer, post, next_value()) print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=idx, val=val & MASK)) - val = (val << 8) + next - next += 1 - exec(SET_TEMPLATE.format(off=post, val=val & MASK)) + exec(SET_TEMPLATE.format(off=pre, val=next_value())) + exec(SET_TEMPLATE.format(off=idx, val=next_value())) + exec(SET_TEMPLATE.format(off=post, val=next_value())) diff --git a/tests/micropython/viper_ptr8_store_boundary_intbig.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp similarity index 100% rename from tests/micropython/viper_ptr8_store_boundary_intbig.py.exp rename to tests/micropython/viper_ptr8_store_boundary.py.exp From 0ee3f99da24e678a415a1d0b4575c38ad51430fe Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 10 Jul 2025 00:50:15 +0200 Subject: [PATCH 1087/2098] py/asmrv32: Make lt/le comparisons emitter shorter. This commit simplifies the emitter code in charge of generating opcodes performing less-than and less-than-or-equal comparisons. By rewriting the SLT/SLTU opcode generator (handling less-than comparisons) and de-inlining the less-than comparison generator call in the less-than-or-equal generator, the output binary is ~80 bytes smaller (measurements taken from the QEMU port). Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 158b5521917..723d32cdd7c 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -573,12 +573,8 @@ void asm_rv32_meta_comparison_ne(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 } void asm_rv32_meta_comparison_lt(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison) { - // slt(u) rd, rs1, rs2 - if (unsigned_comparison) { - asm_rv32_opcode_sltu(state, rd, rs1, rs2); - } else { - asm_rv32_opcode_slt(state, rd, rs1, rs2); - } + // slt|sltu rd, rs1, rs2 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, (0x02 | (unsigned_comparison ? 1 : 0)), 0x00, rd, rs1, rs2)); } void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison) { @@ -588,11 +584,7 @@ void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 // ... ; PC + 8 asm_rv32_opcode_cli(state, rd, 1); asm_rv32_opcode_beq(state, rs1, rs2, 8); - if (unsigned_comparison) { - asm_rv32_opcode_sltu(state, rd, rs1, rs2); - } else { - asm_rv32_opcode_slt(state, rd, rs1, rs2); - } + asm_rv32_meta_comparison_lt(state, rs1, rs2, rd, unsigned_comparison); } #endif // MICROPY_EMIT_RV32 From 4614ee9e682dfffa26a4226645baca0208ecfac6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 21 Jul 2025 13:31:59 -0500 Subject: [PATCH 1088/2098] py/binary: Add MICROPY_PY_STRUCT_UNSAFE_TYPECODES. This adds a compile-time flag to disable some "unsafe" and non-standard typecodes in struct, array and related modules. This is useful to turn off when fuzzing, as improper use of these typecodes can crash MicroPython. Signed-off-by: Jeff Epler --- py/binary.c | 20 ++++++++++++++++---- py/mpconfig.h | 7 +++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/py/binary.c b/py/binary.c index 48d3421bca9..ef2857b4318 100644 --- a/py/binary.c +++ b/py/binary.c @@ -69,11 +69,13 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { case 'Q': size = 8; break; + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES case 'P': case 'O': case 'S': size = sizeof(void *); break; + #endif case 'e': size = 2; break; @@ -119,12 +121,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { align = alignof(long long); size = sizeof(long long); break; + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES case 'P': case 'O': case 'S': align = alignof(void *); size = sizeof(void *); break; + #endif case 'e': align = 2; size = 2; @@ -280,12 +284,14 @@ mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { case 'd': return mp_obj_new_float_from_d(((double *)p)[index]); #endif - // Extension to CPython: array of objects + // Extension to CPython: array of objects + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES case 'O': return ((mp_obj_t *)p)[index]; // Extension to CPython: array of pointers case 'P': return mp_obj_new_int((mp_int_t)(uintptr_t)((void **)p)[index]); + #endif } return MP_OBJ_NEW_SMALL_INT(val); } @@ -334,9 +340,9 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * long long val = mp_binary_get_int(size, is_signed(val_type), (struct_type == '>'), p); - if (val_type == 'O') { + if (MICROPY_PY_STRUCT_UNSAFE_TYPECODES && val_type == 'O') { return (mp_obj_t)(mp_uint_t)val; - } else if (val_type == 'S') { + } else if (MICROPY_PY_STRUCT_UNSAFE_TYPECODES && val_type == 'S') { const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val; return mp_obj_new_str_from_cstr(s_val); #if MICROPY_PY_BUILTINS_FLOAT @@ -407,9 +413,11 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p mp_uint_t val; switch (val_type) { + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES case 'O': val = (mp_uint_t)val_in; break; + #endif #if MICROPY_PY_BUILTINS_FLOAT case 'e': val = mp_encode_half_float(mp_obj_get_float_to_f(val_in)); @@ -474,10 +482,12 @@ void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_ ((double *)p)[index] = mp_obj_get_float_to_d(val_in); break; #endif + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES // Extension to CPython: array of objects case 'O': ((mp_obj_t *)p)[index] = val_in; break; + #endif default: #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (mp_obj_is_exact_type(val_in, &mp_type_int)) { @@ -534,9 +544,11 @@ void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_i ((double *)p)[index] = (double)val; break; #endif - // Extension to CPython: array of pointers + // Extension to CPython: array of pointers + #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES case 'P': ((void **)p)[index] = (void *)(uintptr_t)val; break; + #endif } } diff --git a/py/mpconfig.h b/py/mpconfig.h index dd41ff6a708..5fe0e822f77 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1601,6 +1601,13 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_STRUCT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif +// Whether struct module provides unsafe and non-standard typecodes O, P, S. +// These typecodes are not in CPython and can cause crashes by accessing arbitrary +// memory. +#ifndef MICROPY_PY_STRUCT_UNSAFE_TYPECODES +#define MICROPY_PY_STRUCT_UNSAFE_TYPECODES (1) +#endif + // Whether to provide "sys" module #ifndef MICROPY_PY_SYS #define MICROPY_PY_SYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) From 744270ac1b9ed3929cd41d1a6e1f6ea0e785745d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 10:35:22 +1000 Subject: [PATCH 1089/2098] py/misc: Add explicit dependency on py/mpconfig.h. Macros in misc.h depend on values defined by including mpconfig.h. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/misc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/misc.h b/py/misc.h index 86ac2ec9a10..5c1cc2f7a83 100644 --- a/py/misc.h +++ b/py/misc.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_PY_MISC_H #define MICROPY_INCLUDED_PY_MISC_H +#include "py/mpconfig.h" + // a mini library of useful types and functions /** types *******************************************************/ From f493075d88be9ce851c4d401d041b801701975c3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 22:23:26 +1000 Subject: [PATCH 1090/2098] tests/run-tests.py: Automatically include float tests when possible. This simplifies the code by removing the explicit addition of the "float/" test directory for certain targets. It also means the tests won't be added incorrectly, eg on a unix build without float. Signed-off-by: Damien George --- tests/feature_check/float.py | 13 ----------- tests/feature_check/float.py.exp | 1 - tests/feature_check/target_info.py | 13 ++++++++++- tests/run-tests.py | 37 +++++++++++++----------------- 4 files changed, 28 insertions(+), 36 deletions(-) delete mode 100644 tests/feature_check/float.py delete mode 100644 tests/feature_check/float.py.exp diff --git a/tests/feature_check/float.py b/tests/feature_check/float.py deleted file mode 100644 index d6d2a99d242..00000000000 --- a/tests/feature_check/float.py +++ /dev/null @@ -1,13 +0,0 @@ -# detect how many bits of precision the floating point implementation has - -try: - float -except NameError: - print(0) -else: - if float("1.0000001") == float("1.0"): - print(30) - elif float("1e300") == float("inf"): - print(32) - else: - print(64) diff --git a/tests/feature_check/float.py.exp b/tests/feature_check/float.py.exp deleted file mode 100644 index 900731ffd51..00000000000 --- a/tests/feature_check/float.py.exp +++ /dev/null @@ -1 +0,0 @@ -64 diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index 9501d808ef2..6d95f8b163c 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -22,4 +22,15 @@ ][sys_mpy >> 10] thread = getattr(sys.implementation, "_thread", None) -print(platform, arch, thread) +# Detect how many bits of precision the floating point implementation has. +try: + if float("1.0000001") == float("1.0"): + float_prec = 30 + elif float("1e300") == float("inf"): + float_prec = 32 + else: + float_prec = 64 +except NameError: + float_prec = 0 + +print(platform, arch, thread, float_prec) diff --git a/tests/run-tests.py b/tests/run-tests.py index 859f16f484a..498370f70e9 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -292,12 +292,13 @@ def detect_test_platform(pyb, args): output = run_feature_check(pyb, args, "target_info.py") if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) - platform, arch, thread = str(output, "ascii").strip().split() + platform, arch, thread, float_prec = str(output, "ascii").strip().split() if arch == "None": arch = None inlineasm_arch = detect_inline_asm_arch(pyb, args) if thread == "None": thread = None + float_prec = int(float_prec) args.platform = platform args.arch = arch @@ -305,6 +306,7 @@ def detect_test_platform(pyb, args): args.mpy_cross_flags = "-march=" + arch args.inlineasm_arch = inlineasm_arch args.thread = thread + args.float_prec = float_prec print("platform={}".format(platform), end="") if arch: @@ -313,6 +315,8 @@ def detect_test_platform(pyb, args): print(" inlineasm={}".format(inlineasm_arch), end="") if thread: print(" thread={}".format(thread), end="") + if float_prec: + print(" float={}-bit".format(float_prec), end="") print() @@ -685,8 +689,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): has_complex = True has_coverage = False - upy_float_precision = 32 - if True: # Even if we run completely different tests in a different directory, # we need to access feature_checks from the same directory as the @@ -774,11 +776,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("cmdline/repl_words_move.py") upy_byteorder = run_feature_check(pyb, args, "byteorder.py") - upy_float_precision = run_feature_check(pyb, args, "float.py") - try: - upy_float_precision = int(upy_float_precision) - except ValueError: - upy_float_precision = 0 has_complex = run_feature_check(pyb, args, "complex.py") == b"complex\n" has_coverage = run_feature_check(pyb, args, "coverage.py") == b"coverage\n" cpy_byteorder = subprocess.check_output( @@ -814,7 +811,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # fails with stack overflow on Debug builds skip_tests.add("misc/sys_settrace_features.py") - if upy_float_precision == 0: + if args.float_prec == 0: skip_tests.add("extmod/uctypes_le_float.py") skip_tests.add("extmod/uctypes_native_float.py") skip_tests.add("extmod/uctypes_sizeof_float.py") @@ -822,7 +819,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("extmod/json_loads_float.py") skip_tests.add("extmod/random_extra_float.py") skip_tests.add("misc/rge_sm.py") - if upy_float_precision < 32: + if args.float_prec < 32: skip_tests.add( "float/float2int_intbig.py" ) # requires fp32, there's float2int_fp30_intbig.py instead @@ -832,7 +829,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("float/bytes_construct.py") # requires fp32 skip_tests.add("float/bytearray_construct.py") # requires fp32 skip_tests.add("float/float_format_ints_power10.py") # requires fp32 - if upy_float_precision < 64: + if args.float_prec < 64: skip_tests.add("float/float_divmod.py") # tested by float/float_divmod_relaxed.py instead skip_tests.add("float/float2int_doubleprec_intbig.py") skip_tests.add("float/float_struct_e_doubleprec.py") @@ -1348,26 +1345,25 @@ def main(): test_dirs += ("inlineasm/{}".format(args.inlineasm_arch),) if args.thread is not None: test_dirs += ("thread",) + if args.float_prec > 0: + test_dirs += ("float",) if args.platform == "pyboard": # run pyboard tests - test_dirs += ("float", "stress", "ports/stm32") + test_dirs += ("stress", "ports/stm32") elif args.platform == "mimxrt": - test_dirs += ("float", "stress") + test_dirs += ("stress",) elif args.platform == "renesas-ra": - test_dirs += ("float", "ports/renesas-ra") + test_dirs += ("ports/renesas-ra") elif args.platform == "rp2": - test_dirs += ("float", "stress", "ports/rp2") + test_dirs += ("stress", "ports/rp2") elif args.platform == "esp32": - test_dirs += ("float", "stress") - elif args.platform in ("esp8266", "minimal", "samd", "nrf"): - test_dirs += ("float",) + test_dirs += ("stress",) elif args.platform == "WiPy": # run WiPy tests test_dirs += ("ports/cc3200",) elif args.platform in PC_PLATFORMS: # run PC tests test_dirs += ( - "float", "import", "io", "stress", @@ -1377,11 +1373,10 @@ def main(): ) elif args.platform == "qemu": test_dirs += ( - "float", "ports/qemu", ) elif args.platform == "webassembly": - test_dirs += ("float", "ports/webassembly") + test_dirs += ("ports/webassembly",) else: # run tests from these directories test_dirs = args.test_dirs From 656582795504cb6c9b025e716f20a2468e5d71ac Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 22:25:15 +1000 Subject: [PATCH 1091/2098] tests/run-tests.py: Always include stress/ tests directory in tests. Ports that now run the stress tests, that didn't prior to this commit are: cc3200, esp8266, minimal, nrf, renesas-ra, samd, qemu, webassembly. Signed-off-by: Damien George --- tests/run-tests.py | 10 +++------- tests/stress/dict_copy.py | 7 ++++++- tests/stress/dict_create.py | 6 +++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 498370f70e9..21e0ab8872a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1340,6 +1340,7 @@ def main(): "micropython", "misc", "extmod", + "stress", ) if args.inlineasm_arch is not None: test_dirs += ("inlineasm/{}".format(args.inlineasm_arch),) @@ -1349,15 +1350,11 @@ def main(): test_dirs += ("float",) if args.platform == "pyboard": # run pyboard tests - test_dirs += ("stress", "ports/stm32") - elif args.platform == "mimxrt": - test_dirs += ("stress",) + test_dirs += ("ports/stm32",) elif args.platform == "renesas-ra": test_dirs += ("ports/renesas-ra") elif args.platform == "rp2": - test_dirs += ("stress", "ports/rp2") - elif args.platform == "esp32": - test_dirs += ("stress",) + test_dirs += ("ports/rp2",) elif args.platform == "WiPy": # run WiPy tests test_dirs += ("ports/cc3200",) @@ -1366,7 +1363,6 @@ def main(): test_dirs += ( "import", "io", - "stress", "unicode", "cmdline", "ports/unix", diff --git a/tests/stress/dict_copy.py b/tests/stress/dict_copy.py index 73d3a5b51d6..f9b742e20f7 100644 --- a/tests/stress/dict_copy.py +++ b/tests/stress/dict_copy.py @@ -1,6 +1,11 @@ # copying a large dictionary -a = {i: 2 * i for i in range(1000)} +try: + a = {i: 2 * i for i in range(1000)} +except MemoryError: + print("SKIP") + raise SystemExit + b = a.copy() for i in range(1000): print(i, b[i]) diff --git a/tests/stress/dict_create.py b/tests/stress/dict_create.py index e9db40a8e6c..91a83a12f9d 100644 --- a/tests/stress/dict_create.py +++ b/tests/stress/dict_create.py @@ -3,6 +3,10 @@ d = {} x = 1 while x < 1000: - d[x] = x + try: + d[x] = x + except MemoryError: + print("SKIP") + raise SystemExit x += 1 print(d[500]) From 1db71f9e553f2775b2c0326a3de778319a647f90 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 11:13:56 +1000 Subject: [PATCH 1092/2098] tests/run-tests.py: Generalise addition of port specific test directory. Signed-off-by: Damien George --- tests/run-tests.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 21e0ab8872a..8735f93f2b3 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -102,6 +102,11 @@ def open(self, path, mode): # Platforms associated with the unix port, values of `sys.platform`. PC_PLATFORMS = ("darwin", "linux", "win32") +# Mapping from `sys.platform` to the port name, for special cases. +# See `platform_to_port()` function. +platform_to_port_map = {"pyboard": "stm32", "WiPy": "cc3200"} +platform_to_port_map.update({p: "unix" for p in PC_PLATFORMS}) + # Tests to skip for specific emitters. emitter_tests_to_skip = { # Some tests are known to fail with native emitter. @@ -252,6 +257,10 @@ def convert_regex_escapes(line): return bytes("".join(cs), "utf8") +def platform_to_port(platform): + return platform_to_port_map.get(platform, platform) + + def get_test_instance(test_instance, baudrate, user, password): if test_instance.startswith("port:"): _, port = test_instance.split(":", 1) @@ -1348,31 +1357,17 @@ def main(): test_dirs += ("thread",) if args.float_prec > 0: test_dirs += ("float",) - if args.platform == "pyboard": - # run pyboard tests - test_dirs += ("ports/stm32",) - elif args.platform == "renesas-ra": - test_dirs += ("ports/renesas-ra") - elif args.platform == "rp2": - test_dirs += ("ports/rp2",) - elif args.platform == "WiPy": - # run WiPy tests - test_dirs += ("ports/cc3200",) - elif args.platform in PC_PLATFORMS: + port_specific_test_dir = "ports/{}".format(platform_to_port(args.platform)) + if os.path.isdir(port_specific_test_dir): + test_dirs += (port_specific_test_dir,) + if args.platform in PC_PLATFORMS: # run PC tests test_dirs += ( "import", "io", "unicode", "cmdline", - "ports/unix", - ) - elif args.platform == "qemu": - test_dirs += ( - "ports/qemu", ) - elif args.platform == "webassembly": - test_dirs += ("ports/webassembly",) else: # run tests from these directories test_dirs = args.test_dirs From e2744ce679269692ceed2bed1e6f4f6a7840b49b Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 11:22:32 +1000 Subject: [PATCH 1093/2098] tests/run-tests.py: Autodetect if the target has unicode support. The unicode tests are now run on all targets that enable unicode. And other unicode tests (namely `extmod/json_loads.py`) are now properly skipped if the target doesn't have unicode support. Signed-off-by: Damien George --- tests/feature_check/target_info.py | 2 +- tests/run-tests.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index 6d95f8b163c..962ebf4154b 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -33,4 +33,4 @@ except NameError: float_prec = 0 -print(platform, arch, thread, float_prec) +print(platform, arch, thread, float_prec, len("α") == 1) diff --git a/tests/run-tests.py b/tests/run-tests.py index 8735f93f2b3..e2bdf7c2c02 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -301,13 +301,14 @@ def detect_test_platform(pyb, args): output = run_feature_check(pyb, args, "target_info.py") if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) - platform, arch, thread, float_prec = str(output, "ascii").strip().split() + platform, arch, thread, float_prec, unicode = str(output, "ascii").strip().split() if arch == "None": arch = None inlineasm_arch = detect_inline_asm_arch(pyb, args) if thread == "None": thread = None float_prec = int(float_prec) + unicode = unicode == "True" args.platform = platform args.arch = arch @@ -316,6 +317,7 @@ def detect_test_platform(pyb, args): args.inlineasm_arch = inlineasm_arch args.thread = thread args.float_prec = float_prec + args.unicode = unicode print("platform={}".format(platform), end="") if arch: @@ -326,6 +328,8 @@ def detect_test_platform(pyb, args): print(" thread={}".format(thread), end="") if float_prec: print(" float={}-bit".format(float_prec), end="") + if unicode: + print(" unicode", end="") print() @@ -845,6 +849,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("float/float_format_ints_doubleprec.py") skip_tests.add("float/float_parse_doubleprec.py") + if not args.unicode: + skip_tests.add("extmod/json_loads.py") # tests loading a utf-8 character + if not has_complex: skip_tests.add("float/complex1.py") skip_tests.add("float/complex1_intbig.py") @@ -870,6 +877,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if args.platform not in PC_PLATFORMS: skip_tests.add("basics/exception_chain.py") # warning is not printed skip_tests.add("micropython/meminfo.py") # output is very different to PC output + skip_tests.add("unicode/file1.py") # requires local file access + skip_tests.add("unicode/file2.py") # requires local file access + skip_tests.add("unicode/file_invalid.py") # requires local file access # Skip emitter-specific tests. skip_tests.update(emitter_tests_to_skip.get(args.emit, ())) @@ -1357,6 +1367,8 @@ def main(): test_dirs += ("thread",) if args.float_prec > 0: test_dirs += ("float",) + if args.unicode: + test_dirs += ("unicode",) port_specific_test_dir = "ports/{}".format(platform_to_port(args.platform)) if os.path.isdir(port_specific_test_dir): test_dirs += (port_specific_test_dir,) @@ -1365,7 +1377,6 @@ def main(): test_dirs += ( "import", "io", - "unicode", "cmdline", ) else: From 54e6cfc6e3f3c2056bf2f06d32a58932d29f1ac3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 11:45:55 +1000 Subject: [PATCH 1094/2098] tests/basics: Skip tests of io module individually using SKIP. Instead of using a feature check. This is more consistent with how other optional modules are skipped. Signed-off-by: Damien George --- tests/basics/io_buffered_writer.py | 6 +++--- tests/basics/io_bytesio_cow.py | 8 +++++++- tests/basics/io_bytesio_ext.py | 8 +++++++- tests/basics/io_bytesio_ext2.py | 7 ++++++- tests/basics/io_iobase.py | 7 ++++--- tests/basics/io_stringio1.py | 7 ++++++- tests/basics/io_stringio_base.py | 6 +++++- tests/basics/io_stringio_with.py | 7 ++++++- tests/basics/io_write_ext.py | 5 +++-- tests/feature_check/io_module.py | 6 ------ tests/feature_check/io_module.py.exp | 0 tests/run-tests.py | 8 -------- 12 files changed, 47 insertions(+), 28 deletions(-) delete mode 100644 tests/feature_check/io_module.py delete mode 100644 tests/feature_check/io_module.py.exp diff --git a/tests/basics/io_buffered_writer.py b/tests/basics/io_buffered_writer.py index 60cf2c837d1..3cfee0103f7 100644 --- a/tests/basics/io_buffered_writer.py +++ b/tests/basics/io_buffered_writer.py @@ -1,9 +1,9 @@ -import io - try: + import io + io.BytesIO io.BufferedWriter -except AttributeError: +except (AttributeError, ImportError): print('SKIP') raise SystemExit diff --git a/tests/basics/io_bytesio_cow.py b/tests/basics/io_bytesio_cow.py index 2edb7136a96..543c12ad42a 100644 --- a/tests/basics/io_bytesio_cow.py +++ b/tests/basics/io_bytesio_cow.py @@ -1,6 +1,12 @@ # Make sure that write operations on io.BytesIO don't # change original object it was constructed from. -import io + +try: + import io +except ImportError: + print("SKIP") + raise SystemExit + b = b"foobar" a = io.BytesIO(b) diff --git a/tests/basics/io_bytesio_ext.py b/tests/basics/io_bytesio_ext.py index 4d4c60c1363..92e71517811 100644 --- a/tests/basics/io_bytesio_ext.py +++ b/tests/basics/io_bytesio_ext.py @@ -1,5 +1,11 @@ # Extended stream operations on io.BytesIO -import io + +try: + import io +except ImportError: + print("SKIP") + raise SystemExit + a = io.BytesIO(b"foobar") a.seek(10) print(a.read(10)) diff --git a/tests/basics/io_bytesio_ext2.py b/tests/basics/io_bytesio_ext2.py index 414ac90a3b0..f60a6a9a604 100644 --- a/tests/basics/io_bytesio_ext2.py +++ b/tests/basics/io_bytesio_ext2.py @@ -1,4 +1,9 @@ -import io +try: + import io +except ImportError: + print("SKIP") + raise SystemExit + a = io.BytesIO(b"foobar") try: a.seek(-10) diff --git a/tests/basics/io_iobase.py b/tests/basics/io_iobase.py index d3824c177f3..c01ca6a5073 100644 --- a/tests/basics/io_iobase.py +++ b/tests/basics/io_iobase.py @@ -1,8 +1,9 @@ -import io try: + import io + io.IOBase -except AttributeError: - print('SKIP') +except (AttributeError, ImportError): + print("SKIP") raise SystemExit diff --git a/tests/basics/io_stringio1.py b/tests/basics/io_stringio1.py index 7d355930f5a..889e3ce6973 100644 --- a/tests/basics/io_stringio1.py +++ b/tests/basics/io_stringio1.py @@ -1,4 +1,9 @@ -import io +try: + import io +except ImportError: + print("SKIP") + raise SystemExit + a = io.StringIO() print('io.StringIO' in repr(a)) print(a.getvalue()) diff --git a/tests/basics/io_stringio_base.py b/tests/basics/io_stringio_base.py index 0f65fb3fabc..c8890dab73a 100644 --- a/tests/basics/io_stringio_base.py +++ b/tests/basics/io_stringio_base.py @@ -1,7 +1,11 @@ # Checks that an instance type inheriting from a native base that uses # MP_TYPE_FLAG_ITER_IS_STREAM will still have a getiter. -import io +try: + import io +except ImportError: + print("SKIP") + raise SystemExit a = io.StringIO() a.write("hello\nworld\nmicro\npython\n") diff --git a/tests/basics/io_stringio_with.py b/tests/basics/io_stringio_with.py index a3aa6ec84e0..0155ad5382d 100644 --- a/tests/basics/io_stringio_with.py +++ b/tests/basics/io_stringio_with.py @@ -1,4 +1,9 @@ -import io +try: + import io +except ImportError: + print("SKIP") + raise SystemExit + # test __enter__/__exit__ with io.StringIO() as b: b.write("foo") diff --git a/tests/basics/io_write_ext.py b/tests/basics/io_write_ext.py index 695abccef44..5af1de7a6c3 100644 --- a/tests/basics/io_write_ext.py +++ b/tests/basics/io_write_ext.py @@ -1,10 +1,11 @@ # This tests extended (MicroPython-specific) form of write: # write(buf, len) and write(buf, offset, len) -import io try: + import io + io.BytesIO -except AttributeError: +except (AttributeError, ImportError): print('SKIP') raise SystemExit diff --git a/tests/feature_check/io_module.py b/tests/feature_check/io_module.py deleted file mode 100644 index 9094e605316..00000000000 --- a/tests/feature_check/io_module.py +++ /dev/null @@ -1,6 +0,0 @@ -try: - import io - - print("io") -except ImportError: - print("no") diff --git a/tests/feature_check/io_module.py.exp b/tests/feature_check/io_module.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/run-tests.py b/tests/run-tests.py index e2bdf7c2c02..958ddb1dc80 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -695,7 +695,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_async = False skip_const = False skip_revops = False - skip_io_module = False skip_fstring = False skip_endian = False skip_inlineasm = False @@ -752,11 +751,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if output == b"TypeError\n": skip_revops = True - # Check if io module exists, and skip such tests if it doesn't - output = run_feature_check(pyb, args, "io_module.py") - if output != b"io\n": - skip_io_module = True - # Check if fstring feature is enabled, and skip such tests if it doesn't output = run_feature_check(pyb, args, "fstring.py") if output != b"a=1\n": @@ -925,7 +919,6 @@ def run_one_test(test_file): is_slice = test_name.find("slice") != -1 or test_name in misc_slice_tests is_async = test_name.startswith(("async_", "asyncio_")) or test_name.endswith("_async") is_const = test_name.startswith("const") - is_io_module = test_name.startswith("io_") is_fstring = test_name.startswith("string_fstring") is_inlineasm = test_name.startswith("asm") @@ -940,7 +933,6 @@ def run_one_test(test_file): skip_it |= skip_async and is_async skip_it |= skip_const and is_const skip_it |= skip_revops and "reverse_op" in test_name - skip_it |= skip_io_module and is_io_module skip_it |= skip_fstring and is_fstring skip_it |= skip_inlineasm and is_inlineasm From f9b6d8e6088545f4d934ca4b4c2baa69ee62e89e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 12:26:40 +1000 Subject: [PATCH 1095/2098] github/workflows: Run webassembly and zephyr workflows if tests/ change. Because these ports run tests as part of CI, and need to be run if any of the tests change, to check those changes. Signed-off-by: Damien George --- .github/workflows/ports_webassembly.yml | 1 + .github/workflows/ports_zephyr.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ports_webassembly.yml b/.github/workflows/ports_webassembly.yml index 880f15ab344..ceb35f83cd8 100644 --- a/.github/workflows/ports_webassembly.yml +++ b/.github/workflows/ports_webassembly.yml @@ -11,6 +11,7 @@ on: - 'shared/**' - 'lib/**' - 'ports/webassembly/**' + - 'tests/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index eb85af6a361..ca3c36e004c 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -11,6 +11,7 @@ on: - 'shared/**' - 'lib/**' - 'ports/zephyr/**' + - 'tests/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From c16fe5b5ede3dde468bb65cc15e3e072101b0295 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 13:26:19 +1000 Subject: [PATCH 1096/2098] webassembly: Enable C-stack checking. This gets the recursive stress-tests working on this port. For relatively small Python functions the maximum recursive depth is about 150 nested calls. Signed-off-by: Damien George --- ports/webassembly/main.c | 5 +++++ ports/webassembly/mpconfigport.h | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c index 770dfbe0ca5..e0e2d59ae6b 100644 --- a/ports/webassembly/main.c +++ b/ports/webassembly/main.c @@ -49,6 +49,9 @@ // the top-level call into C. static size_t external_call_depth = 0; +// Emscripten defaults to a 64k C-stack, so our limit should be less than that. +#define CSTACK_SIZE (32 * 1024) + #if MICROPY_GC_SPLIT_HEAP_AUTO static void gc_collect_top_level(void); #endif @@ -67,6 +70,8 @@ void external_call_depth_dec(void) { } void mp_js_init(int pystack_size, int heap_size) { + mp_cstack_init_with_sp_here(CSTACK_SIZE); + #if MICROPY_ENABLE_PYSTACK mp_obj_t *pystack = (mp_obj_t *)malloc(pystack_size * sizeof(mp_obj_t)); mp_pystack_init(pystack, pystack + pystack_size); diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h index 544e76f8054..0783aacbbcd 100644 --- a/ports/webassembly/mpconfigport.h +++ b/ports/webassembly/mpconfigport.h @@ -44,7 +44,6 @@ #define MICROPY_READER_VFS (MICROPY_VFS) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_PYSTACK (1) -#define MICROPY_STACK_CHECK (0) #define MICROPY_KBD_EXCEPTION (1) #define MICROPY_REPL_EVENT_DRIVEN (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) From 0cb2c69b3f25816416f7141ca32697a3135d8e53 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 13:51:58 +1000 Subject: [PATCH 1097/2098] tests/misc/rge_sm.py: Remove unused code from the test. This cleans up the test to remove all unused code, making it smaller, a bit faster to deploy to a target to run, and also use less RAM on the target (which may help it run on targets that are just slightly out of memory running it). Signed-off-by: Damien George --- tests/misc/rge_sm.py | 49 -------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/tests/misc/rge_sm.py b/tests/misc/rge_sm.py index f5b0910dd3a..56dad574977 100644 --- a/tests/misc/rge_sm.py +++ b/tests/misc/rge_sm.py @@ -39,14 +39,6 @@ def solve(self, finishtime): if not self.iterate(): break - def solveNSteps(self, nSteps): - for i in range(nSteps): - if not self.iterate(): - break - - def series(self): - return zip(*self.Trajectory) - # 1-loop RGES for the main parameters of the SM # couplings are: g1, g2, g3 of U(1), SU(2), SU(3); yt (top Yukawa), lambda (Higgs quartic) @@ -79,45 +71,6 @@ def series(self): ) -def drange(start, stop, step): - r = start - while r < stop: - yield r - r += step - - -def phaseDiagram(system, trajStart, trajPlot, h=0.1, tend=1.0, range=1.0): - tstart = 0.0 - for i in drange(0, range, 0.1 * range): - for j in drange(0, range, 0.1 * range): - rk = RungeKutta(system, trajStart(i, j), tstart, h) - rk.solve(tend) - # draw the line - for tr in rk.Trajectory: - x, y = trajPlot(tr) - print(x, y) - print() - # draw the arrow - continue - l = (len(rk.Trajectory) - 1) / 3 - if l > 0 and 2 * l < len(rk.Trajectory): - p1 = rk.Trajectory[l] - p2 = rk.Trajectory[2 * l] - x1, y1 = trajPlot(p1) - x2, y2 = trajPlot(p2) - dx = -0.5 * (y2 - y1) # orthogonal to line - dy = 0.5 * (x2 - x1) # orthogonal to line - # l = math.sqrt(dx*dx + dy*dy) - # if abs(l) > 1e-3: - # l = 0.1 / l - # dx *= l - # dy *= l - print(x1 + dx, y1 + dy) - print(x2, y2) - print(x1 - dx, y1 - dy) - print() - - def singleTraj(system, trajStart, h=0.02, tend=1.0): is_REPR_C = float("1.0000001") == float("1.0") tstart = 0.0 @@ -141,7 +94,5 @@ def singleTraj(system, trajStart, h=0.02, tend=1.0): print(tr_str) -# phaseDiagram(sysSM, (lambda i, j: [0.354, 0.654, 1.278, 0.8 + 0.2 * i, 0.1 + 0.1 * j]), (lambda a: (a[4], a[5])), h=0.1, tend=math.log(10**17)) - # initial conditions at M_Z singleTraj(sysSM, [0.354, 0.654, 1.278, 0.983, 0.131], h=0.5, tend=math.log(10**17)) # true values From 0f5f6484a2e6f330d03ee4786011ccb8062ac325 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 12:05:32 +1000 Subject: [PATCH 1098/2098] tests/run-tests.py: Add support for .native.exp expected output files. There are currently a few tests that are excluded when using the native emitter because they test printing of exception tracebacks, which includes line numbers. And the native emitter doesn't store line numbers, so gets these tests wrong. But we'd still like to run these tests using the native emitter, because they test useful things even if the line number info is not in the traceback (eg that threads which crash print out their exception). This commit adds support for native-specific .exp files, which are of the form `.py.native.exp`. If such an .exp file exists then it take precedence over any normal `.py.exp` file. (Actually, the implementation here is general enough that it also supports `.py.bytecode.exp` as well, if bytecode ever needs a specific exp file.) Signed-off-by: Damien George --- tests/run-tests.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 958ddb1dc80..d0a9121b4a4 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -994,7 +994,11 @@ def run_one_test(test_file): # Expected output is result of running unittest. output_expected = None else: - test_file_expected = test_file + ".exp" + # Prefer emitter-specific expected output. + test_file_expected = test_file + "." + args.emit + ".exp" + if not os.path.isfile(test_file_expected): + # Fall back to generic expected output. + test_file_expected = test_file + ".exp" if os.path.isfile(test_file_expected): # Expected output given by a file, so read that in. with open(test_file_expected, "rb") as f: @@ -1202,8 +1206,8 @@ def main(): {test_directory_description} When running tests, run-tests.py compares the MicroPython output of the test with the output -produced by running the test through CPython unless a .exp file is found, in which -case it is used as comparison. +produced by running the test through CPython unless a .exp file is found (or a +.native.exp file when using the native emitter), in which case it is used as comparison. If a test fails, run-tests.py produces a pair of .out and .exp files in the result directory with the MicroPython output and the expectations, respectively. From 95d1794afdbef4d0b57af321c0a5d69320dbe9fd Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 12:04:58 +1000 Subject: [PATCH 1099/2098] tests/misc/print_exception.py: Use "raise e" instead of no-arg "raise". This allows the test to run with the native emitter. The test semantics remain the same. Signed-off-by: Damien George --- tests/misc/print_exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/misc/print_exception.py b/tests/misc/print_exception.py index 92754733b58..d41478360fa 100644 --- a/tests/misc/print_exception.py +++ b/tests/misc/print_exception.py @@ -71,7 +71,7 @@ def g(): except Exception as e: print("reraise") print_exc(e) - raise + raise e except Exception as e: print("caught") print_exc(e) From 3c72c3a1e60cecc6ab05d5a81a1b50f42999f10f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 14:07:09 +1000 Subject: [PATCH 1100/2098] tests/micropython/opt_level_lineno.py: Force test func to use bytecode. So that the test can run the same on all targets when used with the native emitter. Signed-off-by: Damien George --- tests/micropython/opt_level_lineno.py | 13 ++++++++++++- tests/micropython/opt_level_lineno.py.exp | 2 +- tests/run-tests.py | 1 - 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/micropython/opt_level_lineno.py b/tests/micropython/opt_level_lineno.py index d8253e54b41..dda9092d868 100644 --- a/tests/micropython/opt_level_lineno.py +++ b/tests/micropython/opt_level_lineno.py @@ -3,4 +3,15 @@ # check that level 3 doesn't store line numbers # the expected output is that any line is printed as "line 1" micropython.opt_level(3) -exec("try:\n xyz\nexcept NameError as er:\n import sys\n sys.print_exception(er)") + +# force bytecode emitter, because native emitter doesn't store line numbers +exec(""" +@micropython.bytecode +def f(): + try: + xyz + except NameError as er: + import sys + sys.print_exception(er) +f() +""") diff --git a/tests/micropython/opt_level_lineno.py.exp b/tests/micropython/opt_level_lineno.py.exp index 469b90ba793..b50f0628c81 100644 --- a/tests/micropython/opt_level_lineno.py.exp +++ b/tests/micropython/opt_level_lineno.py.exp @@ -1,3 +1,3 @@ Traceback (most recent call last): - File "", line 1, in + File "", line 1, in f NameError: name 'xyz' isn't defined diff --git a/tests/run-tests.py b/tests/run-tests.py index d0a9121b4a4..f8174b2d3b0 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -131,7 +131,6 @@ def open(self, path, mode): "misc/print_exception.py", "micropython/emg_exc.py", "micropython/heapalloc_traceback.py", - "micropython/opt_level_lineno.py", "thread/thread_exc2.py", # These require stack-allocated slice optimisation. "micropython/heapalloc_slice.py", From a279c64046445c9dbde0a5d3d53d467cb7e43550 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 12:06:16 +1000 Subject: [PATCH 1101/2098] tests: Add .native.exp output files for tests that differ with native. Signed-off-by: Damien George --- tests/basics/sys_tracebacklimit.py.native.exp | 22 +++++++++++++++++++ tests/micropython/emg_exc.py.native.exp | 2 ++ .../heapalloc_traceback.py.native.exp | 3 +++ tests/misc/print_exception.py.native.exp | 18 +++++++++++++++ tests/run-tests.py | 6 ----- tests/thread/thread_exc2.py.native.exp | 3 +++ 6 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 tests/basics/sys_tracebacklimit.py.native.exp create mode 100644 tests/micropython/emg_exc.py.native.exp create mode 100644 tests/micropython/heapalloc_traceback.py.native.exp create mode 100644 tests/misc/print_exception.py.native.exp create mode 100644 tests/thread/thread_exc2.py.native.exp diff --git a/tests/basics/sys_tracebacklimit.py.native.exp b/tests/basics/sys_tracebacklimit.py.native.exp new file mode 100644 index 00000000000..f9d30c05856 --- /dev/null +++ b/tests/basics/sys_tracebacklimit.py.native.exp @@ -0,0 +1,22 @@ +ValueError: value + +limit 4 +ValueError: value + +limit 3 +ValueError: value + +limit 2 +ValueError: value + +limit 1 +ValueError: value + +limit 0 +ValueError: value + +limit -1 +ValueError: value + +True +False diff --git a/tests/micropython/emg_exc.py.native.exp b/tests/micropython/emg_exc.py.native.exp new file mode 100644 index 00000000000..9677c526a9c --- /dev/null +++ b/tests/micropython/emg_exc.py.native.exp @@ -0,0 +1,2 @@ +ValueError: 1 + diff --git a/tests/micropython/heapalloc_traceback.py.native.exp b/tests/micropython/heapalloc_traceback.py.native.exp new file mode 100644 index 00000000000..d6ac26aa829 --- /dev/null +++ b/tests/micropython/heapalloc_traceback.py.native.exp @@ -0,0 +1,3 @@ +StopIteration +StopIteration: + diff --git a/tests/misc/print_exception.py.native.exp b/tests/misc/print_exception.py.native.exp new file mode 100644 index 00000000000..59e856ae3c4 --- /dev/null +++ b/tests/misc/print_exception.py.native.exp @@ -0,0 +1,18 @@ +caught +Exception: msg + +caught +Exception: fail + +finally +caught +Exception: fail + +reraise +Exception: fail + +caught +Exception: fail + +AttributeError: 'function' object has no attribute 'X' + diff --git a/tests/run-tests.py b/tests/run-tests.py index f8174b2d3b0..aeea0bad5e7 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -126,12 +126,6 @@ def open(self, path, mode): "basics/unboundlocal.py", # These require "raise from". "basics/exception_chain.py", - # These require proper traceback info. - "basics/sys_tracebacklimit.py", - "misc/print_exception.py", - "micropython/emg_exc.py", - "micropython/heapalloc_traceback.py", - "thread/thread_exc2.py", # These require stack-allocated slice optimisation. "micropython/heapalloc_slice.py", # These require running the scheduler. diff --git a/tests/thread/thread_exc2.py.native.exp b/tests/thread/thread_exc2.py.native.exp new file mode 100644 index 00000000000..9b2e715ef8d --- /dev/null +++ b/tests/thread/thread_exc2.py.native.exp @@ -0,0 +1,3 @@ +Unhandled exception in thread started by +ValueError: +done From 2bba507148e8a751123257fa2b075c70d67e1160 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 10:48:16 +1000 Subject: [PATCH 1102/2098] tests: Require SSL certificate file to be available for test to run. Previously, any test needing an SSL certificate file would automatically skip if the file could not be found. But that makes it too easy to accidentally skip tests. Instead, change it so that the test fails if the certificate file doesn't exist. That matches, for example, the fact that the test fails if networking (LAN, WiFi) is not active. Signed-off-by: Damien George --- tests/multi_net/asyncio_tls_server_client.py | 7 ------- .../asyncio_tls_server_client_cert_required_error.py | 7 ------- tests/multi_net/asyncio_tls_server_client_readline.py | 7 ------- tests/multi_net/asyncio_tls_server_client_verify_error.py | 7 ------- tests/multi_net/ssl_cert_ec.py | 7 ------- tests/multi_net/ssl_cert_rsa.py | 7 ------- tests/multi_net/sslcontext_check_hostname_error.py | 7 ------- tests/multi_net/sslcontext_getpeercert.py | 7 ------- tests/multi_net/sslcontext_server_client_files.py | 7 ------- tests/multi_net/sslcontext_verify_error.py | 7 ------- tests/multi_net/sslcontext_verify_time_error.py | 7 ------- tests/net_inet/asyncio_tls_open_connection_readline.py | 6 ------ tests/net_inet/test_sslcontext_client.py | 5 ----- 13 files changed, 88 deletions(-) diff --git a/tests/multi_net/asyncio_tls_server_client.py b/tests/multi_net/asyncio_tls_server_client.py index 98f15c6625f..016f57970c0 100644 --- a/tests/multi_net/asyncio_tls_server_client.py +++ b/tests/multi_net/asyncio_tls_server_client.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - async def handle_connection(reader, writer): data = await reader.read(100) diff --git a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py index 178ad392740..eac9a98beae 100644 --- a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py +++ b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - async def handle_connection(reader, writer): print("handle connection") diff --git a/tests/multi_net/asyncio_tls_server_client_readline.py b/tests/multi_net/asyncio_tls_server_client_readline.py index da5f1afee2a..6093628ce5b 100644 --- a/tests/multi_net/asyncio_tls_server_client_readline.py +++ b/tests/multi_net/asyncio_tls_server_client_readline.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - async def handle_connection(reader, writer): data = await reader.readline() diff --git a/tests/multi_net/asyncio_tls_server_client_verify_error.py b/tests/multi_net/asyncio_tls_server_client_verify_error.py index 362f0fc8ecf..6dbff0482c8 100644 --- a/tests/multi_net/asyncio_tls_server_client_verify_error.py +++ b/tests/multi_net/asyncio_tls_server_client_verify_error.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - async def handle_connection(reader, writer): print("handle connection") diff --git a/tests/multi_net/ssl_cert_ec.py b/tests/multi_net/ssl_cert_ec.py index 2c5734e0526..8ecbd4d34f6 100644 --- a/tests/multi_net/ssl_cert_ec.py +++ b/tests/multi_net/ssl_cert_ec.py @@ -13,13 +13,6 @@ certfile = "ec_cert.der" keyfile = "ec_key.der" -try: - os.stat(certfile) - os.stat(keyfile) -except OSError: - print("SKIP") - raise SystemExit - with open(certfile, "rb") as cf: cert = cadata = cf.read() diff --git a/tests/multi_net/ssl_cert_rsa.py b/tests/multi_net/ssl_cert_rsa.py index d148c8ebcfe..b99493b0ae5 100644 --- a/tests/multi_net/ssl_cert_rsa.py +++ b/tests/multi_net/ssl_cert_rsa.py @@ -13,13 +13,6 @@ certfile = "rsa_cert.der" keyfile = "rsa_key.der" -try: - os.stat(certfile) - os.stat(keyfile) -except OSError: - print("SKIP") - raise SystemExit - with open(certfile, "rb") as cf: cert = cadata = cf.read() diff --git a/tests/multi_net/sslcontext_check_hostname_error.py b/tests/multi_net/sslcontext_check_hostname_error.py index d85363f00bd..6bd911ddfd7 100644 --- a/tests/multi_net/sslcontext_check_hostname_error.py +++ b/tests/multi_net/sslcontext_check_hostname_error.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - # Server def instance0(): diff --git a/tests/multi_net/sslcontext_getpeercert.py b/tests/multi_net/sslcontext_getpeercert.py index e9d96be248c..e2d2a425116 100644 --- a/tests/multi_net/sslcontext_getpeercert.py +++ b/tests/multi_net/sslcontext_getpeercert.py @@ -15,13 +15,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - # Server def instance0(): diff --git a/tests/multi_net/sslcontext_server_client_files.py b/tests/multi_net/sslcontext_server_client_files.py index 64a4215c75b..48ff6376fad 100644 --- a/tests/multi_net/sslcontext_server_client_files.py +++ b/tests/multi_net/sslcontext_server_client_files.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - # Server def instance0(): diff --git a/tests/multi_net/sslcontext_verify_error.py b/tests/multi_net/sslcontext_verify_error.py index 5dc461e7708..07dcc690b7c 100644 --- a/tests/multi_net/sslcontext_verify_error.py +++ b/tests/multi_net/sslcontext_verify_error.py @@ -14,13 +14,6 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - # Server def instance0(): diff --git a/tests/multi_net/sslcontext_verify_time_error.py b/tests/multi_net/sslcontext_verify_time_error.py index fbefdecf9f4..7b986322d15 100644 --- a/tests/multi_net/sslcontext_verify_time_error.py +++ b/tests/multi_net/sslcontext_verify_time_error.py @@ -14,13 +14,6 @@ cert = cafile = "expired_cert.der" key = "ec_key.der" -try: - os.stat(cafile) - os.stat(key) -except OSError: - print("SKIP") - raise SystemExit - # Server def instance0(): diff --git a/tests/net_inet/asyncio_tls_open_connection_readline.py b/tests/net_inet/asyncio_tls_open_connection_readline.py index 70145d91a79..a0df88be4af 100644 --- a/tests/net_inet/asyncio_tls_open_connection_readline.py +++ b/tests/net_inet/asyncio_tls_open_connection_readline.py @@ -20,12 +20,6 @@ ca_cert_chain = "isrg.der" -try: - os.stat(ca_cert_chain) -except OSError: - print("SKIP") - raise SystemExit - with open(ca_cert_chain, "rb") as ca: cadata = ca.read() diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 0c83abb7333..77f68da496a 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -13,11 +13,6 @@ # $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER ca_cert_chain = "mpycert.der" -try: - os.stat(ca_cert_chain) -except OSError: - print("SKIP") - raise SystemExit def main(use_stream=True): From e15219800e4874a6c2653cfbdd0553c37610aeff Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 5 Aug 2025 17:19:02 -0500 Subject: [PATCH 1103/2098] extmod/modframebuf: Fix crash in scroll() for large inputs. If mp_int_t is wider than int, then the tests such as `xend < 0` can fail even when the amount of scrolling requested is out of range. This resulted in a segmentation fault when attempting an out-of-bounds access to the framebuffer. Signed-off-by: Jeff Epler --- extmod/modframebuf.c | 29 +++++++++++++++-------------- tests/extmod/framebuf_scroll.py | 5 +++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index 5c4b9abf0c0..16606109ccd 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -787,39 +787,40 @@ static mp_obj_t framebuf_scroll(mp_obj_t self_in, mp_obj_t xstep_in, mp_obj_t ys mp_obj_framebuf_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t xstep = mp_obj_get_int(xstep_in); mp_int_t ystep = mp_obj_get_int(ystep_in); - int sx, y, xend, yend, dx, dy; + unsigned int sx, y, xend, yend; + int dx, dy; if (xstep < 0) { - sx = 0; - xend = self->width + xstep; - if (xend <= 0) { + if (-xstep >= self->width) { return mp_const_none; } + sx = 0; + xend = self->width + (int)xstep; dx = 1; } else { - sx = self->width - 1; - xend = xstep - 1; - if (xend >= sx) { + if (xstep >= self->width) { return mp_const_none; } + sx = self->width - 1; + xend = (int)xstep - 1; dx = -1; } if (ystep < 0) { - y = 0; - yend = self->height + ystep; - if (yend <= 0) { + if (-ystep >= self->height) { return mp_const_none; } + y = 0; + yend = self->height + (int)ystep; dy = 1; } else { - y = self->height - 1; - yend = ystep - 1; - if (yend >= y) { + if (ystep >= self->height) { return mp_const_none; } + y = self->height - 1; + yend = (int)ystep - 1; dy = -1; } for (; y != yend; y += dy) { - for (int x = sx; x != xend; x += dx) { + for (unsigned x = sx; x != xend; x += dx) { setpixel(self, x, y, getpixel(self, x - xstep, y - ystep)); } } diff --git a/tests/extmod/framebuf_scroll.py b/tests/extmod/framebuf_scroll.py index db9b6ea1e96..d7c07b1c657 100644 --- a/tests/extmod/framebuf_scroll.py +++ b/tests/extmod/framebuf_scroll.py @@ -42,4 +42,9 @@ def prepare_buffer(): fbuf.scroll(15, 7) fbuf.scroll(10, -1) fbuf.scroll(1, -10) +try: + fbuf.scroll(1000000000000, -1) +except OverflowError: + # When mp_int_t is 32 bits, this throws OverflowError. + pass printbuf() From 803da9645f735edd3d1a80a3f920039e0f0298f2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 11 Aug 2025 11:48:34 -0500 Subject: [PATCH 1104/2098] extmod/modframebuf: Save code size in setpixel. There's a slight code size increase paid for by using setpixel_checked for the last pixel of a line, instead of repeating the checks inline. Signed-off-by: Jeff Epler --- extmod/modframebuf.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index 16606109ccd..593125aa16f 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -483,9 +483,7 @@ static void line(const mp_obj_framebuf_t *fb, mp_int_t x1, mp_int_t y1, mp_int_t e += 2 * dy; } - if (0 <= x2 && x2 < fb->width && 0 <= y2 && y2 < fb->height) { - setpixel(fb, x2, y2, col); - } + setpixel_checked(fb, x2, y2, col, 1); } static mp_obj_t framebuf_line(size_t n_args, const mp_obj_t *args_in) { From 0615d13963ba33650a5b2070368584c7aec9c9dd Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 6 Aug 2025 10:15:57 -0500 Subject: [PATCH 1105/2098] py/objringio: Detect incorrect constructor calls. ringbuffer.size must be at least 2, and is a 16-bit quantity. This fixes several cases including the one the fuzzer discovered, which would lead to a fatal signal when accessing the object. Fixes issue #17847. Signed-off-by: Jeff Epler --- py/objringio.c | 19 ++++++++----------- tests/micropython/ringio.py | 18 ++++++++++++++++++ tests/micropython/ringio.py.exp | 3 +++ tests/micropython/ringio_big.py | 29 +++++++++++++++++++++++++++++ tests/micropython/ringio_big.py.exp | 2 ++ 5 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 tests/micropython/ringio_big.py create mode 100644 tests/micropython/ringio_big.py.exp diff --git a/py/objringio.c b/py/objringio.c index ba1ec25307e..0025b26be3b 100644 --- a/py/objringio.c +++ b/py/objringio.c @@ -39,22 +39,19 @@ typedef struct _micropython_ringio_obj_t { static mp_obj_t micropython_ringio_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, 1, false); - mp_int_t buff_size = -1; mp_buffer_info_t bufinfo = {NULL, 0, 0}; if (!mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) { - buff_size = mp_obj_get_int(args[0]); + bufinfo.len = mp_obj_get_int(args[0]) + 1; + bufinfo.buf = m_new(uint8_t, bufinfo.len); } - micropython_ringio_obj_t *self = mp_obj_malloc(micropython_ringio_obj_t, type); - if (bufinfo.buf != NULL) { - // buffer passed in, use it directly for ringbuffer. - self->ringbuffer.buf = bufinfo.buf; - self->ringbuffer.size = bufinfo.len; - self->ringbuffer.iget = self->ringbuffer.iput = 0; - } else { - // Allocate new buffer, add one extra to buff_size as ringbuf consumes one byte for tracking. - ringbuf_alloc(&(self->ringbuffer), buff_size + 1); + if (bufinfo.len < 2 || bufinfo.len > UINT16_MAX) { + mp_raise_ValueError(NULL); } + micropython_ringio_obj_t *self = mp_obj_malloc(micropython_ringio_obj_t, type); + self->ringbuffer.buf = bufinfo.buf; + self->ringbuffer.size = bufinfo.len; + self->ringbuffer.iget = self->ringbuffer.iput = 0; return MP_OBJ_FROM_PTR(self); } diff --git a/tests/micropython/ringio.py b/tests/micropython/ringio.py index a0102ef63da..41092887984 100644 --- a/tests/micropython/ringio.py +++ b/tests/micropython/ringio.py @@ -46,3 +46,21 @@ micropython.RingIO(None) except TypeError as ex: print(type(ex)) + +try: + # Buffer may not be empty + micropython.RingIO(bytearray(0)) +except ValueError as ex: + print(type(ex)) + +try: + # Buffer may not be too small + micropython.RingIO(bytearray(1)) +except ValueError as ex: + print(type(ex)) + +try: + # Size may not be too small + micropython.RingIO(0) +except ValueError as ex: + print(type(ex)) diff --git a/tests/micropython/ringio.py.exp b/tests/micropython/ringio.py.exp index 65bae06472f..c391529a41e 100644 --- a/tests/micropython/ringio.py.exp +++ b/tests/micropython/ringio.py.exp @@ -14,3 +14,6 @@ b'\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01' 0 b'\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01' + + + diff --git a/tests/micropython/ringio_big.py b/tests/micropython/ringio_big.py new file mode 100644 index 00000000000..d55c4c00b7c --- /dev/null +++ b/tests/micropython/ringio_big.py @@ -0,0 +1,29 @@ +# Check that micropython.RingIO works correctly. + +import micropython + +try: + micropython.RingIO +except AttributeError: + print("SKIP") + raise SystemExit + +try: + # The maximum possible size + micropython.RingIO(bytearray(65535)) + micropython.RingIO(65534) + + try: + # Buffer may not be too big + micropython.RingIO(bytearray(65536)) + except ValueError as ex: + print(type(ex)) + + try: + # Size may not be too big + micropython.RingIO(65535) + except ValueError as ex: + print(type(ex)) +except MemoryError: + print("SKIP") + raise SystemExit diff --git a/tests/micropython/ringio_big.py.exp b/tests/micropython/ringio_big.py.exp new file mode 100644 index 00000000000..72af34b3838 --- /dev/null +++ b/tests/micropython/ringio_big.py.exp @@ -0,0 +1,2 @@ + + From 141f7d0c35516403c39ce6266be7c6fb4551ef6f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 12 Aug 2025 11:51:53 +0100 Subject: [PATCH 1106/2098] py/mkrules.cmake: Clean genhdr and frozen_mpy dirs. CMake builds relied upon the parent Makefile removing the entire build directory to successfully clean build artifacts. py/mkrules.cmake: Add ADDITIONAL_CLEAN_FILES properties to ensure a "make clean" from within the build directory removes the genhdr and frozen_mpy directories. Signed-off-by: Phil Howard --- py/mkrules.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index 27d8b24f67a..e3d769cc59b 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -100,6 +100,12 @@ if(MICROPY_ROM_TEXT_COMPRESSION) ) endif() +# Ensure genhdr directory is removed on clean + +set_property(TARGET ${MICROPY_TARGET} APPEND PROPERTY ADDITIONAL_CLEAN_FILES + "${MICROPY_GENHDR_DIR}" +) + # Command to force the build of another command # Generate mpversion.h @@ -244,6 +250,10 @@ add_custom_command( if(MICROPY_FROZEN_MANIFEST) set(MICROPY_FROZEN_CONTENT "${CMAKE_BINARY_DIR}/frozen_content.c") + set_property(TARGET ${MICROPY_TARGET} APPEND PROPERTY ADDITIONAL_CLEAN_FILES + "${CMAKE_BINARY_DIR}/frozen_mpy" + ) + target_sources(${MICROPY_TARGET} PRIVATE ${MICROPY_FROZEN_CONTENT} ) From 3efbd726eb89a1514c01a51857100b2718f12a50 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 14:44:48 +1000 Subject: [PATCH 1107/2098] py/parse: Remove explicit checks for invalid folding operations. They are instead checked by `binary_op_maybe()`, which catches exceptions for invalid int/float operations. This is a follow-up to 69ead7d98ef30df3b6bd4485633490e80fca1718 Signed-off-by: Damien George --- py/parse.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/py/parse.c b/py/parse.c index 6260987b28a..1a50b13b5c7 100644 --- a/py/parse.c +++ b/py/parse.c @@ -771,12 +771,6 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { if (!mp_parse_node_get_number_maybe(pn, &arg1)) { return false; } - #if !MICROPY_COMP_CONST_FLOAT - if (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) { - // ** can't have negative rhs - return false; - } - #endif if (!binary_op_maybe(op, arg0, arg1, &arg0)) { return false; } @@ -796,16 +790,6 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { return false; } mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(peek_result(parser, i)); - if (tok == MP_TOKEN_OP_AT) { - // Can't fold @ - return false; - } - #if !MICROPY_COMP_CONST_FLOAT - if (tok == MP_TOKEN_OP_SLASH) { - // Can't fold / - return false; - } - #endif mp_binary_op_t op = MP_BINARY_OP_LSHIFT + (tok - MP_TOKEN_OP_DBL_LESS); if (!binary_op_maybe(op, arg0, arg1, &arg0)) { return false; From 40cc4e4f74b1f39076100136bc2cb8db5ff24ea5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 16:35:16 +1000 Subject: [PATCH 1108/2098] py/objtype: Make mp_obj_new_type a static function. It's only used once, in the same file it's defined, and making it static reduces code size. Along with this, the associated example code comment in `ports/unix/main.c` has been removed. Signed-off-by: Damien George --- ports/unix/main.c | 13 ------------- py/obj.h | 1 - py/objtype.c | 3 ++- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 530e20a3863..5f6b99f989a 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -616,19 +616,6 @@ MP_NOINLINE int main_(int argc, char **argv) { } #endif - // Here is some example code to create a class and instance of that class. - // First is the Python, then the C code. - // - // class TestClass: - // pass - // test_obj = TestClass() - // test_obj.attr = 42 - // - // mp_obj_t test_class_type, test_class_instance; - // test_class_type = mp_obj_new_type(qstr_from_str("TestClass"), mp_const_empty_tuple, mp_obj_new_dict(0)); - // mp_store_name(qstr_from_str("test_obj"), test_class_instance = mp_call_function_0(test_class_type)); - // mp_store_attr(test_class_instance, qstr_from_str("attr"), mp_obj_new_int(42)); - /* printf("bytes:\n"); printf(" total %d\n", m_get_total_bytes_allocated()); diff --git a/py/obj.h b/py/obj.h index 4ac0cc0c611..69d6e626db3 100644 --- a/py/obj.h +++ b/py/obj.h @@ -987,7 +987,6 @@ void *mp_obj_malloc_with_finaliser_helper(size_t num_bytes, const mp_obj_type_t bool mp_obj_is_dict_or_ordereddict(mp_obj_t o); #define mp_obj_is_fun(o) (mp_obj_is_obj(o) && (((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->name == MP_QSTR_function)) -mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict); static inline mp_obj_t mp_obj_new_bool(mp_int_t x) { return x ? mp_const_true : mp_const_false; } diff --git a/py/objtype.c b/py/objtype.c index 818ceeb0589..25c2dee994d 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -44,6 +44,7 @@ #define ENABLE_SPECIAL_ACCESSORS \ (MICROPY_PY_DESCRIPTORS || MICROPY_PY_DELATTR_SETATTR || MICROPY_PY_BUILTINS_PROPERTY) +static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict); static mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo); static mp_obj_t static_class_method_make_new(const mp_obj_type_t *self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); @@ -1164,7 +1165,7 @@ MP_DEFINE_CONST_OBJ_TYPE( attr, type_attr ); -mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) { +static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) { // Verify input objects have expected type if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { mp_raise_TypeError(NULL); From b3cd1a355ebf50e06ec9f0c1594e47abd7ee339c Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 11:47:52 +1000 Subject: [PATCH 1109/2098] extmod/modtime: Move tuple creation to common localtime implementation. This factors code out of the ports and into the common `time.localtime` implementation in `extmod/modtime.c`. That helps to reduce code duplication, prevent errors in implementation, and reduce code size on some ports (mimxrt and stm32 at least). Signed-off-by: Damien George --- extmod/modtime.c | 26 +++++++++++++------------- ports/cc3200/mods/modtime.c | 19 +++---------------- ports/esp32/modtime.c | 18 +++--------------- ports/esp8266/modtime.c | 18 +++--------------- ports/mimxrt/modtime.c | 23 ++++++++++------------- ports/renesas-ra/modtime.c | 23 ++++++++++------------- ports/rp2/modtime.c | 18 +++--------------- ports/samd/modtime.c | 22 +++++----------------- ports/stm32/modtime.c | 23 ++++++++++------------- ports/webassembly/modtime.c | 18 +++--------------- 10 files changed, 63 insertions(+), 145 deletions(-) diff --git a/extmod/modtime.c b/extmod/modtime.c index 999b81230bc..ee898828a4a 100644 --- a/extmod/modtime.c +++ b/extmod/modtime.c @@ -53,26 +53,26 @@ // - weekday is 0-6 for Mon-Sun // - yearday is 1-366 static mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { + timeutils_struct_time_t tm; if (n_args == 0 || args[0] == mp_const_none) { // Get current date and time. - return mp_time_localtime_get(); + mp_time_localtime_get(&tm); } else { // Convert given seconds to tuple. mp_timestamp_t seconds = timeutils_obj_get_timestamp(args[0]); - timeutils_struct_time_t tm; timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); - mp_obj_t tuple[8] = { - tuple[0] = mp_obj_new_int(tm.tm_year), - tuple[1] = mp_obj_new_int(tm.tm_mon), - tuple[2] = mp_obj_new_int(tm.tm_mday), - tuple[3] = mp_obj_new_int(tm.tm_hour), - tuple[4] = mp_obj_new_int(tm.tm_min), - tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), - }; - return mp_obj_new_tuple(8, tuple); } + mp_obj_t tuple[8] = { + 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); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_time_localtime_obj, 0, 1, time_localtime); diff --git a/ports/cc3200/mods/modtime.c b/ports/cc3200/mods/modtime.c index 254678fb2dd..d1116670011 100644 --- a/ports/cc3200/mods/modtime.c +++ b/ports/cc3200/mods/modtime.c @@ -29,23 +29,10 @@ #include "shared/timeutils/timeutils.h" #include "pybrtc.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { - timeutils_struct_time_t tm; - +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { // get the seconds from the RTC - timeutils_seconds_since_2000_to_struct_time(pyb_rtc_get_seconds(), &tm); - mp_obj_t tuple[8] = { - 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); + timeutils_seconds_since_2000_to_struct_time(pyb_rtc_get_seconds(), tm); } // Returns the number of seconds, as an integer, since the Epoch. diff --git a/ports/esp32/modtime.c b/ports/esp32/modtime.c index 991f2cf5787..64f9359db69 100644 --- a/ports/esp32/modtime.c +++ b/ports/esp32/modtime.c @@ -31,23 +31,11 @@ #include "py/obj.h" #include "shared/timeutils/timeutils.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { struct timeval tv; gettimeofday(&tv, NULL); - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); - mp_obj_t tuple[8] = { - tuple[0] = mp_obj_new_int(tm.tm_year), - tuple[1] = mp_obj_new_int(tm.tm_mon), - tuple[2] = mp_obj_new_int(tm.tm_mday), - tuple[3] = mp_obj_new_int(tm.tm_hour), - tuple[4] = mp_obj_new_int(tm.tm_min), - tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), - }; - return mp_obj_new_tuple(8, tuple); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, tm); } // Return the number of seconds since the Epoch. diff --git a/ports/esp8266/modtime.c b/ports/esp8266/modtime.c index e99d920fde8..c0c1dccfe47 100644 --- a/ports/esp8266/modtime.c +++ b/ports/esp8266/modtime.c @@ -29,22 +29,10 @@ #include "shared/timeutils/timeutils.h" #include "modmachine.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { mp_uint_t seconds = pyb_rtc_get_us_since_epoch() / 1000u / 1000u; - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); - mp_obj_t tuple[8] = { - tuple[0] = mp_obj_new_int(tm.tm_year), - tuple[1] = mp_obj_new_int(tm.tm_mon), - tuple[2] = mp_obj_new_int(tm.tm_mday), - tuple[3] = mp_obj_new_int(tm.tm_hour), - tuple[4] = mp_obj_new_int(tm.tm_min), - tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), - }; - return mp_obj_new_tuple(8, tuple); + timeutils_seconds_since_epoch_to_struct_time(seconds, tm); } // Returns the number of seconds, as an integer, since the Epoch. diff --git a/ports/mimxrt/modtime.c b/ports/mimxrt/modtime.c index 3bcfac411c0..fe77b8a733b 100644 --- a/ports/mimxrt/modtime.c +++ b/ports/mimxrt/modtime.c @@ -29,22 +29,19 @@ #include "shared/timeutils/timeutils.h" #include "fsl_snvs_lp.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { // Get current date and time. snvs_lp_srtc_datetime_t t; SNVS_LP_SRTC_GetDatetime(SNVS, &t); - 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.minute), - mp_obj_new_int(t.second), - mp_obj_new_int(timeutils_calc_weekday(t.year, t.month, t.day)), - mp_obj_new_int(timeutils_year_day(t.year, t.month, t.day)), - }; - return mp_obj_new_tuple(8, tuple); + tm->tm_year = t.year; + tm->tm_mon = t.month; + tm->tm_mday = t.day; + tm->tm_hour = t.hour; + tm->tm_min = t.minute; + tm->tm_sec = t.second; + tm->tm_wday = timeutils_calc_weekday(t.year, t.month, t.day); + tm->tm_yday = timeutils_year_day(t.year, t.month, t.day); } // Return the number of seconds since the Epoch. diff --git a/ports/renesas-ra/modtime.c b/ports/renesas-ra/modtime.c index e1358f82bc5..b778ab2fe5e 100644 --- a/ports/renesas-ra/modtime.c +++ b/ports/renesas-ra/modtime.c @@ -28,23 +28,20 @@ #include "shared/timeutils/timeutils.h" #include "rtc.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { // get current date and time rtc_init_finalise(); ra_rtc_t time; ra_rtc_get_time(&time); - mp_obj_t tuple[8] = { - mp_obj_new_int(time.year), - mp_obj_new_int(time.month), - mp_obj_new_int(time.date), - mp_obj_new_int(time.hour), - mp_obj_new_int(time.minute), - mp_obj_new_int(time.second), - mp_obj_new_int(time.weekday - 1), - mp_obj_new_int(timeutils_year_day(time.year, time.month, time.date)), - }; - return mp_obj_new_tuple(8, tuple); + tm->tm_year = time.year; + tm->tm_mon = time.month; + tm->tm_mday = time.date; + tm->tm_hour = time.hour; + tm->tm_min = time.minute; + tm->tm_sec = time.second; + tm->tm_wday = time.weekday - 1; + tm->tm_yday = timeutils_year_day(time.year, time.month, time.date); } // Returns the number of seconds, as an integer, since the Epoch. diff --git a/ports/rp2/modtime.c b/ports/rp2/modtime.c index 7d6dd8fd971..a860903285e 100644 --- a/ports/rp2/modtime.c +++ b/ports/rp2/modtime.c @@ -28,23 +28,11 @@ #include "shared/timeutils/timeutils.h" #include "pico/aon_timer.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { 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(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); + timeutils_seconds_since_epoch_to_struct_time(ts.tv_sec, tm); } // Return the number of seconds since the Epoch. diff --git a/ports/samd/modtime.c b/ports/samd/modtime.c index 0bed3cb83a8..6168c645d63 100644 --- a/ports/samd/modtime.c +++ b/ports/samd/modtime.c @@ -30,23 +30,11 @@ static uint64_t time_us_64_offset_from_epoch; -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { - timeutils_struct_time_t tm; - rtc_gettime(&tm); - tm.tm_wday = timeutils_calc_weekday(tm.tm_year, tm.tm_mon, tm.tm_mday); - tm.tm_yday = timeutils_year_day(tm.tm_year, tm.tm_mon, tm.tm_mday); - mp_obj_t tuple[8] = { - tuple[0] = mp_obj_new_int(tm.tm_year), - tuple[1] = mp_obj_new_int(tm.tm_mon), - tuple[2] = mp_obj_new_int(tm.tm_mday), - tuple[3] = mp_obj_new_int(tm.tm_hour), - tuple[4] = mp_obj_new_int(tm.tm_min), - tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), - }; - return mp_obj_new_tuple(8, tuple); +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { + rtc_gettime(tm); + tm->tm_wday = timeutils_calc_weekday(tm->tm_year, tm->tm_mon, tm->tm_mday); + tm->tm_yday = timeutils_year_day(tm->tm_year, tm->tm_mon, tm->tm_mday); } // Returns the number of seconds, as an integer, since the Epoch. diff --git a/ports/stm32/modtime.c b/ports/stm32/modtime.c index 87a4536b043..e7051065187 100644 --- a/ports/stm32/modtime.c +++ b/ports/stm32/modtime.c @@ -28,8 +28,8 @@ #include "shared/timeutils/timeutils.h" #include "rtc.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { // get current date and time // note: need to call get time then get date to correctly access the registers rtc_init_finalise(); @@ -37,17 +37,14 @@ static mp_obj_t mp_time_localtime_get(void) { RTC_TimeTypeDef time; HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); - mp_obj_t tuple[8] = { - mp_obj_new_int(2000 + date.Year), - mp_obj_new_int(date.Month), - mp_obj_new_int(date.Date), - mp_obj_new_int(time.Hours), - mp_obj_new_int(time.Minutes), - mp_obj_new_int(time.Seconds), - mp_obj_new_int(date.WeekDay - 1), - mp_obj_new_int(timeutils_year_day(2000 + date.Year, date.Month, date.Date)), - }; - return mp_obj_new_tuple(8, tuple); + tm->tm_year = 2000 + date.Year; + tm->tm_mon = date.Month; + tm->tm_mday = date.Date; + tm->tm_hour = time.Hours; + tm->tm_min = time.Minutes; + tm->tm_sec = time.Seconds; + tm->tm_wday = date.WeekDay - 1; + tm->tm_yday = timeutils_year_day(tm->tm_year, date.Month, date.Date); } // Returns the number of seconds, as an integer, since 1/1/2000. diff --git a/ports/webassembly/modtime.c b/ports/webassembly/modtime.c index 1b1e63d4ddf..b6c0cda96da 100644 --- a/ports/webassembly/modtime.c +++ b/ports/webassembly/modtime.c @@ -28,21 +28,9 @@ #include "shared/timeutils/timeutils.h" #include "library.h" -// Return the localtime as an 8-tuple. -static mp_obj_t mp_time_localtime_get(void) { - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(mp_hal_time_ms() / 1000, &tm); - mp_obj_t tuple[8] = { - 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); +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { + timeutils_seconds_since_epoch_to_struct_time(mp_hal_time_ms() / 1000, tm); } // Returns the number of seconds, as a float, since the Epoch. From e58848a21eb6dafce53df991c52ac8db52354daa Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:00:11 +1000 Subject: [PATCH 1110/2098] alif/machine_rtc: Implement RTC.datetime to get and set the RTC. The LPRTC peripheral is a 32-bit counter with a 16-bit prescaler. It's configured here to count at 1Hz (to get maximum date range) and then the prescaler value is used to get 30 microsecond resolution. That's essentially a 32+15=47-bit counter. Signed-off-by: Damien George --- ports/alif/machine_rtc.c | 104 ++++++++++++++++++++++++++++++++++++--- ports/alif/mphalport.h | 2 + 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/ports/alif/machine_rtc.c b/ports/alif/machine_rtc.c index 6473d1d80fb..739fcab4b6c 100644 --- a/ports/alif/machine_rtc.c +++ b/ports/alif/machine_rtc.c @@ -28,9 +28,20 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/modmachine.h" +#include "shared/timeutils/timeutils.h" #include "rtc.h" #include "sys_ctrl_rtc.h" +// The LPRTC (low-power real-time counter) is a 32-bit counter with a 16-bit prescaler, +// and usually clocked by a 32768Hz clock source. To get a large date range of around +// 136 years, the prescaler is set to 32768 and so the counter clocks at 1Hz. Then the +// counter counts the number of seconds since the 1970 Epoch. The prescaler is used to +// get the subseconds which are then converted to microseconds. +// +// The combined counter+prescaler counts starting at 0 from the year 1970 up to the year +// 2106, with a resolution of 30.52 microseconds. +#define LPRTC_PRESCALER_SETTING (32768) + typedef struct _machine_rtc_obj_t { mp_obj_base_t base; LPRTC_Type *rtc; @@ -44,24 +55,97 @@ void LPRTC_IRQHandler(void) { lprtc_interrupt_disable(machine_rtc.rtc); } +// Returns the number of seconds and microseconds since the Epoch. +uint32_t mp_hal_time_get(uint32_t *microseconds) { + uint32_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + uint32_t count = lprtc_get_count(machine_rtc.rtc); + if (microseconds == NULL) { + MICROPY_END_ATOMIC_SECTION(atomic_state); + return count; + } + uint32_t prescaler = machine_rtc.rtc->LPRTC_CPCVR; + uint32_t count2 = lprtc_get_count(machine_rtc.rtc); + if (count != count2) { + // The counter incremented during sampling of the prescaler, so resample the prescaler. + prescaler = machine_rtc.rtc->LPRTC_CPCVR; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + + // Compute the microseconds from the up-counting prescaler value. + MP_STATIC_ASSERT(LPRTC_PRESCALER_SETTING == 32768); + *microseconds = 15625UL * prescaler / 512UL; + + return count2; +} + 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) { const machine_rtc_obj_t *self = &machine_rtc; // Check arguments. mp_arg_check_num(n_args, n_kw, 0, 0, false); - enable_lprtc_clk(); - lprtc_prescaler_disable(self->rtc); - lprtc_counter_wrap_disable(self->rtc); lprtc_interrupt_disable(self->rtc); lprtc_interrupt_unmask(self->rtc); + // Initialise the LPRTC if it's not already enabled. + if (!((VBAT->RTC_CLK_EN & RTC_CLK_ENABLE) + && (self->rtc->LPRTC_CCR & CCR_LPRTC_EN) + && (self->rtc->LPRTC_CPSR == LPRTC_PRESCALER_SETTING))) { + enable_lprtc_clk(); + self->rtc->LPRTC_CCR = 0; + lprtc_load_prescaler(self->rtc, LPRTC_PRESCALER_SETTING); + lprtc_load_count(self->rtc, 0); + self->rtc->LPRTC_CCR = CCR_LPRTC_PSCLR_EN | CCR_LPRTC_EN; + } + NVIC_SetPriority(LPRTC_IRQ_IRQn, IRQ_PRI_RTC); NVIC_ClearPendingIRQ(LPRTC_IRQ_IRQn); NVIC_EnableIRQ(LPRTC_IRQ_IRQn); + return MP_OBJ_FROM_PTR(self); } +static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + // Get datetime. + uint32_t microseconds; + mp_timestamp_t s = mp_hal_time_get(µseconds); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(s, &tm); + mp_obj_t tuple[8] = { + 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(microseconds), + }; + return mp_obj_new_tuple(8, tuple); + } else { + // Set datetime. + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 8, &items); + 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]), + }; + mp_timestamp_t s = timeutils_seconds_since_epoch(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + // Disable then re-enable the LPRTC so that the prescaler counter resets to 0. + machine_rtc.rtc->LPRTC_CCR = 0; + lprtc_load_count(machine_rtc.rtc, s); + machine_rtc.rtc->LPRTC_CCR = CCR_LPRTC_PSCLR_EN | CCR_LPRTC_EN; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); + static mp_obj_t machine_rtc_alarm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_id, ARG_time, ARG_repeat }; @@ -80,13 +164,18 @@ static mp_obj_t machine_rtc_alarm(size_t n_args, const mp_obj_t *pos_args, mp_ma if (mp_obj_is_int(args[ARG_time].u_obj)) { uint32_t seconds = mp_obj_get_int(args[1].u_obj) / 1000; - lprtc_counter_disable(self->rtc); - lprtc_load_count(self->rtc, 1); - lprtc_load_counter_match_register(self->rtc, seconds * 32768); + // Make sure we are guaranteed an interrupt: + // - if seconds = 0 it won't fire + // - if seconds = 1 it may miss if the counter rolls over just after it's read + // - if seconds >= 2 then it will always fire (when read/written close enough) + seconds = MAX(2, seconds); + // Configure the counter match as atomically as possible. + uint32_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); lprtc_interrupt_ack(self->rtc); + lprtc_load_counter_match_register(self->rtc, lprtc_get_count(self->rtc) + seconds); lprtc_interrupt_enable(self->rtc); - lprtc_counter_enable(self->rtc); + MICROPY_END_ATOMIC_SECTION(atomic_state); } else { mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s)")); } @@ -96,6 +185,7 @@ static mp_obj_t machine_rtc_alarm(size_t n_args, const mp_obj_t *pos_args, mp_ma static MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_alarm_obj, 1, machine_rtc_alarm); static const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&machine_rtc_alarm_obj) }, }; static MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h index f03dc7c1c1f..731ac14fc57 100644 --- a/ports/alif/mphalport.h +++ b/ports/alif/mphalport.h @@ -373,3 +373,5 @@ enum { void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); + +uint32_t mp_hal_time_get(uint32_t *microseconds); From 0feb4f5ea419c038c69a6fd0b295b788c09f06a2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:12:22 +1000 Subject: [PATCH 1111/2098] alif/mphalport: Implement mp_hal_time_ns. Signed-off-by: Damien George --- ports/alif/mphalport.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 39528a4b9f2..3bf8bb0aed7 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -167,7 +167,9 @@ void mp_hal_delay_ms(mp_uint_t ms) { } uint64_t mp_hal_time_ns(void) { - return 0; + uint32_t microseconds; + uint32_t s = mp_hal_time_get(µseconds); + return (uint64_t)s * 1000000000ULL + (uint64_t)microseconds * 1000ULL; } void mp_hal_pin_config(const machine_pin_obj_t *pin, uint32_t mode, From 46b366d7b245360e37443ca096f120c5d601f11d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:04:20 +1000 Subject: [PATCH 1112/2098] alif/fatfs_port: Implement get_fattime. Signed-off-by: Damien George --- ports/alif/fatfs_port.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ports/alif/fatfs_port.c b/ports/alif/fatfs_port.c index 5883c9f3b94..20b7920ebf0 100644 --- a/ports/alif/fatfs_port.c +++ b/ports/alif/fatfs_port.c @@ -24,15 +24,12 @@ * THE SOFTWARE. */ +#include "py/mphal.h" +#include "shared/timeutils/timeutils.h" #include "lib/oofatfs/ff.h" DWORD get_fattime(void) { - // TODO - int year = 2024; - int month = 1; - int day = 1; - int hour = 0; - int min = 0; - int sec = 0; - return ((year - 1980) << 25) | (month << 21) | (day << 16) | (hour << 11) | (min << 5) | (sec / 2); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(mp_hal_time_get(NULL), &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); } From 326730d8b283245462f7c9dc7d191e7c01cb3756 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:05:09 +1000 Subject: [PATCH 1113/2098] alif/mbedtls: Implement the mbedTLS time function. Signed-off-by: Damien George --- ports/alif/mbedtls/mbedtls_port.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/ports/alif/mbedtls/mbedtls_port.c b/ports/alif/mbedtls/mbedtls_port.c index a8c155e31ae..14e24f021f9 100644 --- a/ports/alif/mbedtls/mbedtls_port.c +++ b/ports/alif/mbedtls/mbedtls_port.c @@ -24,15 +24,10 @@ * THE SOFTWARE. */ -#include "py/obj.h" +#include "py/mphal.h" #include "se_services.h" #include "mbedtls_config_port.h" -#if defined(MBEDTLS_HAVE_TIME) -#include "shared/timeutils/timeutils.h" -#include "mbedtls/platform_time.h" -#endif - int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) { uint32_t val = 0; int n = 0; @@ -52,14 +47,7 @@ int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t #if defined(MBEDTLS_HAVE_TIME) time_t alif_mbedtls_time(time_t *timer) { - // TODO implement proper RTC time - unsigned int year = 2025; - unsigned int month = 1; - unsigned int date = 1; - unsigned int hours = 12; - unsigned int minutes = 0; - unsigned int seconds = 0; - return timeutils_seconds_since_epoch(year, month, date, hours, minutes, seconds); + return mp_hal_time_get(NULL); } #endif From f1462448d023107e34bdc193d6947a2a78af7d54 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:05:39 +1000 Subject: [PATCH 1114/2098] alif/modtime: Implement the rest of the time module. Adds: `time.time()`, `time.time_ns()`, `time.localtime()`, `time.mktime()` and `time.gmtime()`. Signed-off-by: Damien George --- ports/alif/modtime.c | 39 +++++++++++++++++++++++++++++++++++++++ ports/alif/mpconfigport.h | 4 +++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 ports/alif/modtime.c diff --git a/ports/alif/modtime.c b/ports/alif/modtime.c new file mode 100644 index 00000000000..6d40ec2cc37 --- /dev/null +++ b/ports/alif/modtime.c @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/timeutils/timeutils.h" + +// Get the localtime. +static void mp_time_localtime_get(timeutils_struct_time_t *tm) { + mp_timestamp_t s = mp_hal_time_get(NULL); + timeutils_seconds_since_epoch_to_struct_time(s, tm); +} + +// Return the number of seconds since the Epoch. +static mp_obj_t mp_time_time_get(void) { + return mp_obj_new_int_from_uint(mp_hal_time_get(NULL)); +} diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 8a65721bb0f..6b30ea2e624 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -119,7 +119,9 @@ #define MICROPY_PY_OS_UNAME (1) #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (se_services_rand64()) -#define MICROPY_PY_TIME (1) +#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) +#define MICROPY_PY_TIME_TIME_TIME_NS (1) +#define MICROPY_PY_TIME_INCLUDEFILE "ports/alif/modtime.c" #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/alif/modmachine.c" #define MICROPY_PY_MACHINE_RESET (1) From f0f5abb7a3dd37551736fbbfbb5792875b0a539a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 12 Aug 2025 13:30:09 +1000 Subject: [PATCH 1115/2098] alif/mpconfigport: Enable cryptolib and hashlib.md5/sha1. They are enabled when SSL/mbedTLS is included in the firmware. These new features cost around +1400 bytes of code size. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 6b30ea2e624..08b27e2786e 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -112,6 +112,9 @@ // Extended modules #define MICROPY_EPOCH_IS_1970 (1) +#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) +#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) +#define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) #define MICROPY_PY_OS_INCLUDEFILE "ports/alif/modos.c" #define MICROPY_PY_OS_DUPTERM (1) #define MICROPY_PY_OS_SEP (1) From b7cfafc1ee5c0bf8148cb27c6b82a597c3a1aae0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 13 Aug 2025 10:39:44 +1000 Subject: [PATCH 1116/2098] alif/alif.mk: Add MPY_CROSS_FLAGS setting. The HP and HE CPUs have double-precision hardware floating point, so can use the armv7emdp architecture. This allows frozen code to use native/viper/asm_thumb decorators. Fixes issue #17896. Signed-off-by: Damien George --- ports/alif/alif.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 265418aa074..d9e7c32578c 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -22,6 +22,8 @@ include $(TOP)/extmod/extmod.mk ################################################################################ # Project specific settings and compiler/linker flags +MPY_CROSS_FLAGS += -march=armv7emdp + CROSS_COMPILE ?= arm-none-eabi- ALIF_DFP_REL_TOP ?= lib/alif_ensemble-cmsis-dfp ALIF_DFP_REL_HERE ?= $(TOP)/lib/alif_ensemble-cmsis-dfp From bba354201809dd715287fd32c85818a23f978d97 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 13 Aug 2025 12:37:23 -0500 Subject: [PATCH 1117/2098] extmod/modlwip: Remove unused include and functions. The lwIP includes are now port-specific. The `sys_arch_{,un}protect` functions are not used according to line 34 of `extmod/lwip-include/lwipopts_common.h`. Neither have been touched in nearly a decade. Signed-off-by: Thomas Watson --- extmod/lwip-include/arch/cc.h | 41 --------------------------------- extmod/lwip-include/arch/perf.h | 7 ------ extmod/lwip-include/lwipopts.h | 35 ---------------------------- extmod/modlwip.c | 12 ---------- 4 files changed, 95 deletions(-) delete mode 100644 extmod/lwip-include/arch/cc.h delete mode 100644 extmod/lwip-include/arch/perf.h delete mode 100644 extmod/lwip-include/lwipopts.h diff --git a/extmod/lwip-include/arch/cc.h b/extmod/lwip-include/arch/cc.h deleted file mode 100644 index 400dc6ec75d..00000000000 --- a/extmod/lwip-include/arch/cc.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_CC_H -#define MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_CC_H - -#include - -// Generate lwip's internal types from stdint - -typedef uint8_t u8_t; -typedef int8_t s8_t; -typedef uint16_t u16_t; -typedef int16_t s16_t; -typedef uint32_t u32_t; -typedef int32_t s32_t; - -typedef u32_t mem_ptr_t; - -#define U16_F "hu" -#define S16_F "hd" -#define X16_F "hx" -#define U32_F "u" -#define S32_F "d" -#define X32_F "x" - -#define X8_F "02x" -#define SZT_F "u" - -#define BYTE_ORDER LITTLE_ENDIAN - -#define LWIP_CHKSUM_ALGORITHM 2 - -#include -#define LWIP_PLATFORM_DIAG(x) -#define LWIP_PLATFORM_ASSERT(x) { assert(1); } - -//#define PACK_STRUCT_FIELD(x) x __attribute__((packed)) -#define PACK_STRUCT_FIELD(x) x -#define PACK_STRUCT_STRUCT __attribute__((packed)) -#define PACK_STRUCT_BEGIN -#define PACK_STRUCT_END - -#endif // MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_CC_H diff --git a/extmod/lwip-include/arch/perf.h b/extmod/lwip-include/arch/perf.h deleted file mode 100644 index d310fc339f1..00000000000 --- a/extmod/lwip-include/arch/perf.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_PERF_H -#define MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_PERF_H - -#define PERF_START /* null definition */ -#define PERF_STOP(x) /* null definition */ - -#endif // MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_ARCH_PERF_H diff --git a/extmod/lwip-include/lwipopts.h b/extmod/lwip-include/lwipopts.h deleted file mode 100644 index 2122f30f044..00000000000 --- a/extmod/lwip-include/lwipopts.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_LWIPOPTS_H -#define MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_LWIPOPTS_H - -#include -#include -#include - -// We're running without an OS for this port. We don't provide any services except light protection. -#define NO_SYS 1 - -#define SYS_LIGHTWEIGHT_PROT 1 -#include -typedef uint32_t sys_prot_t; - -#define TCP_LISTEN_BACKLOG 1 - -// We'll put these into a proper ifdef once somebody implements an ethernet driver -#define LWIP_ARP 0 -#define LWIP_ETHERNET 0 - -#define LWIP_DNS 1 - -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 - -#ifdef MICROPY_PY_LWIP_SLIP -#define LWIP_HAVE_SLIPIF 1 -#endif - -// For now, we can simply define this as a macro for the timer code. But this function isn't -// universal and other ports will need to do something else. It may be necessary to move -// things like this into a port-provided header file. -#define sys_now mp_hal_ticks_ms - -#endif // MICROPY_INCLUDED_EXTMOD_LWIP_INCLUDE_LWIPOPTS_H diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 26aab6e8109..4b1c1b8f3a5 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -1724,18 +1724,6 @@ static MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &lwip_socket_locals_dict ); -/******************************************************************************/ -// Support functions for memory protection. lwIP has its own memory management -// routines for its internal structures, and since they might be called in -// interrupt handlers, they need some protection. -sys_prot_t sys_arch_protect() { - return (sys_prot_t)MICROPY_BEGIN_ATOMIC_SECTION(); -} - -void sys_arch_unprotect(sys_prot_t state) { - MICROPY_END_ATOMIC_SECTION((mp_uint_t)state); -} - /******************************************************************************/ // Polling callbacks for the interfaces connected to lwIP. Right now it calls // itself a "list" but isn't; we only support a single interface. From afa7265ffa911917f381ac6790ffe032b2b7baf8 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Fri, 25 Jul 2025 16:35:27 -0500 Subject: [PATCH 1118/2098] zephyr: Upgrade to Zephyr v4.2.0. Updates the Zephyr port build instructions and CI to use the latest Zephyr release tag. Tested on max32690fthr and frdm_k64f. Signed-off-by: Maureen Helm --- docs/zephyr/tutorial/repl.rst | 4 ++-- ports/zephyr/README.md | 8 ++++---- tools/ci.sh | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/zephyr/tutorial/repl.rst b/docs/zephyr/tutorial/repl.rst index 199dda2b7ae..1b16c5ad21d 100644 --- a/docs/zephyr/tutorial/repl.rst +++ b/docs/zephyr/tutorial/repl.rst @@ -31,8 +31,8 @@ With your serial program open (PuTTY, screen, picocom, etc) you may see a blank screen with a flashing cursor. Press Enter (or reset the board) and you should be presented with the following text:: - *** Booting Zephyr OS build v4.0.0 *** - MicroPython v1.24.0-preview.179.g5b85b24bd on 2024-08-05; zephyr-frdm_k64f with mk64f12 + *** Booting Zephyr OS build v4.2.0 *** + MicroPython v1.26.0-preview.451.gebc9525c9 on 2025-07-25; zephyr-frdm_k64f with mk64f12 Type "help()" for more information. >>> diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 17c1f613de4..8384fabcf02 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -5,8 +5,8 @@ This is a work-in-progress port of MicroPython to Zephyr RTOS (http://zephyrproject.org). This port tries to support all Zephyr versions supported upstream, -i.e. currently v3.7 (LTS), v4.0 and the development branch. The CI is -setup to use the latest version, i.e. v4.0. +i.e. currently v3.7 (LTS), v4.2 and the development branch. The CI is +setup to use the latest version, i.e. v4.2. All boards supported by Zephyr (with standard level of features support, like UART console) should work with MicroPython (but not all @@ -43,13 +43,13 @@ setup is correct. If you already have Zephyr installed but are having issues building the MicroPython port then try installing the correct version of Zephyr via: - $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v4.0.0 + $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v4.2.0 Alternatively, you don't have to redo the Zephyr installation to just switch from master to a tagged release, you can instead do: $ cd zephyrproject/zephyr - $ git checkout v4.0.0 + $ git checkout v4.2.0 $ west update With Zephyr installed you may then need to configure your environment, diff --git a/tools/ci.sh b/tools/ci.sh index 8f045639b80..9152c2e2dd6 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -886,9 +886,9 @@ function ci_windows_build { ######################################################################################## # ports/zephyr -ZEPHYR_DOCKER_VERSION=v0.27.4 -ZEPHYR_SDK_VERSION=0.17.0 -ZEPHYR_VERSION=v4.0.0 +ZEPHYR_DOCKER_VERSION=v0.28.1 +ZEPHYR_SDK_VERSION=0.17.2 +ZEPHYR_VERSION=v4.2.0 function ci_zephyr_setup { IMAGE=ghcr.io/zephyrproject-rtos/ci:${ZEPHYR_DOCKER_VERSION} From 1588c455c41b445bfd1c65b5055847ab63114c96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:37:54 +0000 Subject: [PATCH 1119/2098] github/workflows: Bump actions/checkout from 4 to 5. Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/biome.yml | 2 +- .github/workflows/code_formatting.yml | 2 +- .github/workflows/code_size.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/commit_formatting.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/examples.yml | 2 +- .github/workflows/mpremote.yml | 2 +- .github/workflows/mpy_format.yml | 2 +- .github/workflows/ports.yml | 2 +- .github/workflows/ports_alif.yml | 2 +- .github/workflows/ports_cc3200.yml | 2 +- .github/workflows/ports_esp32.yml | 2 +- .github/workflows/ports_esp8266.yml | 2 +- .github/workflows/ports_mimxrt.yml | 2 +- .github/workflows/ports_nrf.yml | 2 +- .github/workflows/ports_powerpc.yml | 2 +- .github/workflows/ports_qemu.yml | 4 +-- .github/workflows/ports_renesas-ra.yml | 2 +- .github/workflows/ports_rp2.yml | 2 +- .github/workflows/ports_samd.yml | 2 +- .github/workflows/ports_stm32.yml | 2 +- .github/workflows/ports_unix.yml | 38 ++++++++++++------------- .github/workflows/ports_webassembly.yml | 2 +- .github/workflows/ports_windows.yml | 6 ++-- .github/workflows/ports_zephyr.yml | 2 +- .github/workflows/ruff.yml | 2 +- 27 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/biome.yml b/.github/workflows/biome.yml index 88744f16ca7..fea9c9c8bd7 100644 --- a/.github/workflows/biome.yml +++ b/.github/workflows/biome.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Biome uses: biomejs/setup-biome@v2 with: diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 9f30f048cfd..5669779946e 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -10,7 +10,7 @@ jobs: code-formatting: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 - name: Install packages run: source tools/ci.sh && ci_c_code_formatting_setup diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 67261933798..8587ef01794 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -25,7 +25,7 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 100 - name: Install packages diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 1d6b1dc9d2b..688134b4251 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -6,7 +6,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # codespell version should be kept in sync with .pre-commit-config.yml - run: pip install --user codespell==2.4.1 tomli - run: codespell diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml index fcbcaa7092e..2e1def95c36 100644 --- a/.github/workflows/commit_formatting.yml +++ b/.github/workflows/commit_formatting.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 100 - uses: actions/setup-python@v5 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 62a6f69fc39..f9d61125b50 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 - name: Install Python packages run: pip install -r docs/requirements.txt diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 6613f106625..d16122b720b 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -18,7 +18,7 @@ jobs: embedding: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: make -C examples/embedding -f micropython_embed.mk && make -C examples/embedding - name: Run diff --git a/.github/workflows/mpremote.yml b/.github/workflows/mpremote.yml index ee91b6360b9..359d8882864 100644 --- a/.github/workflows/mpremote.yml +++ b/.github/workflows/mpremote.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # Setting this to zero means fetch all history and tags, # which hatch-vcs can use to discover the version tag. diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index b6768a46c3c..4043b63288a 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -17,7 +17,7 @@ jobs: test: runs-on: ubuntu-22.04 # use 22.04 to get python2 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_mpy_format_setup - name: Test mpy-tool.py diff --git a/.github/workflows/ports.yml b/.github/workflows/ports.yml index 1f262b0ba4b..d4e89bd1aa9 100644 --- a/.github/workflows/ports.yml +++ b/.github/workflows/ports.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build ports download metadata run: mkdir boards && ./tools/autobuild/build-downloads.py . ./boards diff --git a/.github/workflows/ports_alif.yml b/.github/workflows/ports_alif.yml index 0e96e7d816e..a06b3f96ffa 100644 --- a/.github/workflows/ports_alif.yml +++ b/.github/workflows/ports_alif.yml @@ -26,7 +26,7 @@ jobs: - alif_ae3_build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_alif_setup - name: Build ci_${{matrix.ci_func }} diff --git a/.github/workflows/ports_cc3200.yml b/.github/workflows/ports_cc3200.yml index f178a140587..b60ff370daf 100644 --- a/.github/workflows/ports_cc3200.yml +++ b/.github/workflows/ports_cc3200.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_cc3200_setup - name: Build diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 36ab341cfcd..b86c6a76f82 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -28,7 +28,7 @@ jobs: - esp32_build_c2_c6 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: idf_ver name: Read the ESP-IDF version (including Python version) diff --git a/.github/workflows/ports_esp8266.yml b/.github/workflows/ports_esp8266.yml index 5236edf40b9..3293abed598 100644 --- a/.github/workflows/ports_esp8266.yml +++ b/.github/workflows/ports_esp8266.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_esp8266_setup && ci_esp8266_path >> $GITHUB_PATH - name: Build diff --git a/.github/workflows/ports_mimxrt.yml b/.github/workflows/ports_mimxrt.yml index 7743e036ab3..ae9a80ec580 100644 --- a/.github/workflows/ports_mimxrt.yml +++ b/.github/workflows/ports_mimxrt.yml @@ -24,7 +24,7 @@ jobs: run: working-directory: 'micropython repo' # test build with space in path steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: 'micropython repo' - name: Install packages diff --git a/.github/workflows/ports_nrf.yml b/.github/workflows/ports_nrf.yml index 76727c9d1f6..ce86617af05 100644 --- a/.github/workflows/ports_nrf.yml +++ b/.github/workflows/ports_nrf.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_nrf_setup - name: Build diff --git a/.github/workflows/ports_powerpc.yml b/.github/workflows/ports_powerpc.yml index c41b13e5ddf..81f71ca8a96 100644 --- a/.github/workflows/ports_powerpc.yml +++ b/.github/workflows/ports_powerpc.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_powerpc_setup - name: Build diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index ac09dde8640..85764577662 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -29,7 +29,7 @@ jobs: - thumb runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_qemu_setup_arm - name: Build and run test suite ci_qemu_build_arm_${{ matrix.ci_func }} @@ -41,7 +41,7 @@ jobs: build_and_test_rv32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_qemu_setup_rv32 - name: Build and run test suite diff --git a/.github/workflows/ports_renesas-ra.yml b/.github/workflows/ports_renesas-ra.yml index b9fa74331dc..bf99ed25fed 100644 --- a/.github/workflows/ports_renesas-ra.yml +++ b/.github/workflows/ports_renesas-ra.yml @@ -21,7 +21,7 @@ jobs: build_renesas_ra_board: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_renesas_ra_setup - name: Build diff --git a/.github/workflows/ports_rp2.yml b/.github/workflows/ports_rp2.yml index 748f38e1438..22d2a968801 100644 --- a/.github/workflows/ports_rp2.yml +++ b/.github/workflows/ports_rp2.yml @@ -24,7 +24,7 @@ jobs: run: working-directory: 'micropython repo' # test build with space in path steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: 'micropython repo' - name: Install packages diff --git a/.github/workflows/ports_samd.yml b/.github/workflows/ports_samd.yml index 5bf1826cd17..dbea255c79a 100644 --- a/.github/workflows/ports_samd.yml +++ b/.github/workflows/ports_samd.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_samd_setup - name: Build diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 8800f145189..43659a5d5dd 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -28,7 +28,7 @@ jobs: - stm32_misc_build runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_stm32_setup - name: Build ci_${{matrix.ci_func }} diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index f3f613a789a..8fd8e1aec23 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -23,7 +23,7 @@ jobs: minimal: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: source tools/ci.sh && ci_unix_minimal_build - name: Run main test suite @@ -35,7 +35,7 @@ jobs: reproducible: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build with reproducible date run: source tools/ci.sh && ci_unix_minimal_build env: @@ -46,7 +46,7 @@ jobs: standard: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: source tools/ci.sh && ci_unix_standard_build - name: Run main test suite @@ -58,7 +58,7 @@ jobs: standard_v2: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: source tools/ci.sh && ci_unix_standard_v2_build - name: Run main test suite @@ -70,7 +70,7 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -105,7 +105,7 @@ jobs: coverage_32bit: runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_32bit_setup - name: Build @@ -123,7 +123,7 @@ jobs: nanbox: runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_32bit_setup - name: Build @@ -137,7 +137,7 @@ jobs: longlong: runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_32bit_setup - name: Build @@ -151,7 +151,7 @@ jobs: float: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: source tools/ci.sh && ci_unix_float_build - name: Run main test suite @@ -163,7 +163,7 @@ jobs: gil_enabled: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build run: source tools/ci.sh && ci_unix_gil_enabled_build - name: Run main test suite @@ -175,7 +175,7 @@ jobs: stackless_clang: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_clang_setup - name: Build @@ -189,7 +189,7 @@ jobs: float_clang: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_clang_setup - name: Build @@ -203,7 +203,7 @@ jobs: settrace_stackless: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -220,7 +220,7 @@ jobs: macos: runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.8' @@ -236,7 +236,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_qemu_mips_setup - name: Build @@ -251,7 +251,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_qemu_arm_setup - name: Build @@ -266,7 +266,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_unix_qemu_riscv64_setup - name: Build @@ -280,7 +280,7 @@ jobs: sanitize_address: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -305,7 +305,7 @@ jobs: sanitize_undefined: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. diff --git a/.github/workflows/ports_webassembly.yml b/.github/workflows/ports_webassembly.yml index ceb35f83cd8..14399950b56 100644 --- a/.github/workflows/ports_webassembly.yml +++ b/.github/workflows/ports_webassembly.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_webassembly_setup - name: Build diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index f33277d471d..6b492640a1f 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -58,7 +58,7 @@ jobs: - uses: microsoft/setup-msbuild@v2 with: vs-version: ${{ matrix.vs_version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build mpy-cross.exe run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} - name: Update submodules @@ -125,7 +125,7 @@ jobs: git diffutils path-type: inherit # Remove when setup-python is removed - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build mpy-cross.exe run: make -C mpy-cross -j2 - name: Update submodules @@ -143,7 +143,7 @@ jobs: cross-build-on-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_windows_setup - name: Build diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index ca3c36e004c..9ce70343986 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -30,7 +30,7 @@ jobs: large-packages: false docker-images: false swap-storage: false - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: versions name: Read Zephyr version run: source tools/ci.sh && echo "ZEPHYR=$ZEPHYR_VERSION" | tee "$GITHUB_OUTPUT" diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 4c4a2a3162e..633b0cdf82e 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -6,7 +6,7 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # ruff version should be kept in sync with .pre-commit-config.yaml & also micropython-lib - run: pipx install ruff==0.11.6 - run: ruff check --output-format=github . From 1b35116c920550ba7bfc0f06bd4bad7d20f33b21 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Tue, 22 Jul 2025 20:14:14 +0900 Subject: [PATCH 1120/2098] stm32/dac: Fix 12-bit DAC issue on STM32H5. For STM32H5, to use 12-bit DAC, the DMA parameter should set: - Actual DMA source datawidth to CTR1. - The length is the amount of data to be transferred from source to destination in bytes. Also, this commit modifies the (dummy) definition of DMA_CIRCULAR for STM32H5 to prevent conflict with data width specification. Signed-off-by: Yuuki NAGAO --- ports/stm32/dac.c | 7 +++++-- ports/stm32/dma.c | 4 ++-- ports/stm32/dma.h | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ports/stm32/dac.c b/ports/stm32/dac.c index 54d5acc6cea..0be0fd9300b 100644 --- a/ports/stm32/dac.c +++ b/ports/stm32/dac.c @@ -174,7 +174,7 @@ static void dac_start_dma(uint32_t dac_channel, const dma_descr_t *dma_descr, ui // For STM32G4, DAC registers have to be accessed by words (32-bit). dma_align = DMA_MDATAALIGN_BYTE | DMA_PDATAALIGN_WORD; #elif defined(STM32H5) - dma_align = 0; + dma_align = DMA_SRC_DATAWIDTH_BYTE | DMA_DEST_DATAWIDTH_WORD; #else dma_align = DMA_MDATAALIGN_BYTE | DMA_PDATAALIGN_BYTE; #endif @@ -183,7 +183,7 @@ static void dac_start_dma(uint32_t dac_channel, const dma_descr_t *dma_descr, ui // For STM32G4, DAC registers have to be accessed by words (32-bit). dma_align = DMA_MDATAALIGN_HALFWORD | DMA_PDATAALIGN_WORD; #elif defined(STM32H5) - dma_align = 0; + dma_align = DMA_SRC_DATAWIDTH_HALFWORD | DMA_DEST_DATAWIDTH_WORD; #else dma_align = DMA_MDATAALIGN_HALFWORD | DMA_PDATAALIGN_HALFWORD; #endif @@ -490,7 +490,10 @@ mp_obj_t pyb_dac_write_timed(size_t n_args, const mp_obj_t *pos_args, mp_map_t * align = DAC_ALIGN_8B_R; } else { align = DAC_ALIGN_12B_R; + // For STM32H5, the length is the amount of data to be transferred from source to destination in bytes. + #if !defined(STM32H5) bufinfo.len /= 2; + #endif } dac_start_dma(self->dac_channel, tx_dma_descr, args[2].u_int, self->bits, align, bufinfo.len, bufinfo.buf); diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index c252770740d..c2923f7ae73 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -1737,10 +1737,10 @@ void dma_nohal_init(const dma_descr_t *descr, uint32_t config) { dma->CCR = init->Priority; uint32_t ctr1reg = 0; - ctr1reg |= init->SrcDataWidth; + ctr1reg |= config & DMA_CTR1_SDW_LOG2_Msk; ctr1reg |= init->SrcInc; ctr1reg |= (((init->SrcBurstLength - 1) << DMA_CTR1_SBL_1_Pos)) & DMA_CTR1_SBL_1_Msk; - ctr1reg |= init->DestDataWidth; + ctr1reg |= config & DMA_CTR1_DDW_LOG2_Msk; ctr1reg |= init->DestInc; ctr1reg |= (((init->DestBurstLength - 1) << DMA_CTR1_DBL_1_Pos)) & DMA_CTR1_DBL_1_Msk; diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h index f05b22b5d05..75e007bc9fa 100644 --- a/ports/stm32/dma.h +++ b/ports/stm32/dma.h @@ -33,7 +33,7 @@ typedef struct _dma_descr_t dma_descr_t; #if defined(STM32H5) // STM32H5 GPDMA doesn't feature circular mode directly, so define doesn't exist in // stm32 driver header. Define it here to make users like DAC driver happy. -#define DMA_CIRCULAR 0x00000001 +#define DMA_CIRCULAR 0x20000000 #endif #if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) || defined(STM32G0) || defined(STM32H5) || defined(STM32H7) From 152a0782e6ef8fc510b3e0106df82e27595847c2 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Fri, 25 Jul 2025 20:14:03 +0900 Subject: [PATCH 1121/2098] stm32/dac: Add support for DAC feature on STM32G0. DAC.write() and DAC.write_timed() are now available on STM32G0. Tested on NUCLEO_G0B1RE. Signed-off-by: Yuuki NAGAO --- ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h | 2 +- ports/stm32/dac.c | 2 +- ports/stm32/dma.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h index 092ee177925..ead36ed6c76 100644 --- a/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h @@ -5,7 +5,7 @@ #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RNG (0) #define MICROPY_HW_ENABLE_RTC (1) -#define MICROPY_HW_ENABLE_DAC (0) +#define MICROPY_HW_ENABLE_DAC (1) #define MICROPY_HW_ENABLE_USB (0) // can be enabled if USB cable connected to PA11/PA12 #define MICROPY_PY_PYB_LEGACY (0) diff --git a/ports/stm32/dac.c b/ports/stm32/dac.c index 0be0fd9300b..040892553d3 100644 --- a/ports/stm32/dac.c +++ b/ports/stm32/dac.c @@ -97,7 +97,7 @@ static uint32_t TIMx_Config(mp_obj_t timer) { // work out the trigger channel (only certain ones are supported) if (tim->Instance == TIM2) { return DAC_TRIGGER_T2_TRGO; - #if defined(TIM4) + #if defined(TIM4) && defined(DAC_TRIGGER_T4_TRGO) // G0B1 doesn't have this } else if (tim->Instance == TIM4) { return DAC_TRIGGER_T4_TRGO; #endif diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index c2923f7ae73..ad199b1fb91 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -1657,7 +1657,7 @@ static void dma_idle_handler(uint32_t tick) { } #endif -#if defined(STM32F0) || defined(STM32G4) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) +#if defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32L0) || defined(STM32L1) || defined(STM32L4) void dma_nohal_init(const dma_descr_t *descr, uint32_t config) { DMA_Channel_TypeDef *dma = descr->instance; @@ -1680,7 +1680,7 @@ void dma_nohal_init(const dma_descr_t *descr, uint32_t config) { } else { __HAL_DMA2_REMAP(descr->sub_instance); } - #elif defined(STM32G4) + #elif defined(STM32G0) || defined(STM32G4) uint32_t *dmamux_ctrl = (void *)(DMAMUX1_Channel0_BASE + 0x04 * descr->id); *dmamux_ctrl = (*dmamux_ctrl & ~(0x7f)) | descr->sub_instance; #elif defined(STM32L1) @@ -1807,7 +1807,7 @@ void dma_nohal_start(const dma_descr_t *descr, uint32_t src_addr, uint32_t dst_a dma->CCR |= DMA_CCR_EN; } -#elif defined(STM32G0) || defined(STM32N6) || defined(STM32WB) || defined(STM32WL) +#elif defined(STM32N6) || defined(STM32WB) || defined(STM32WL) // These functions are currently not implemented or needed for this MCU. From 365329cd540f631f1cfe805b475f792cf00257e0 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 27 Jul 2025 22:03:42 +0900 Subject: [PATCH 1122/2098] stm32/dac: Fix DAC write for MCUs that have D-Cache. To prevent wrong DAC output, clean D-cache before starting DMA. For more details, please refer to the following document: https://www.st.com/resource/en/application_note/DM00272913.pdf Signed-off-by: Yuuki NAGAO --- ports/stm32/dac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/dac.c b/ports/stm32/dac.c index 040892553d3..8022fd274ce 100644 --- a/ports/stm32/dac.c +++ b/ports/stm32/dac.c @@ -485,6 +485,9 @@ mp_obj_t pyb_dac_write_timed(size_t n_args, const mp_obj_t *pos_args, mp_map_t * #endif } + // To prevent invalid dac output, clean D-cache before starting dma. + MP_HAL_CLEAN_DCACHE(bufinfo.buf, bufinfo.len); + uint32_t align; if (self->bits == 8) { align = DAC_ALIGN_8B_R; From c7ddf0c54fcc9c6abdb38db27a558ab635ba9763 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 11 Aug 2025 14:20:36 +0200 Subject: [PATCH 1123/2098] stm32/Makefile: Add .gc.blocks.table section to generated binary. The generated binary file was missing this section, which caused a hard fault when loading bin or dfu firmware (eg on ARDUINO_GIGA). Signed-off-by: iabdalkader --- ports/stm32/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 37ce8fbd838..abca3a05f8c 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -611,7 +611,7 @@ TEXT0_ADDR ?= 0x08000000 ifeq ($(TEXT1_ADDR),) # No TEXT1_ADDR given so put all firmware at TEXT0_ADDR location -TEXT0_SECTIONS ?= .isr_vector .isr_extratext .text .data .ARM +TEXT0_SECTIONS ?= .isr_vector .isr_extratext .text .gc.blocks.table .data .ARM deploy-stlink: $(BUILD)/firmware.bin $(call RUN_STLINK,$^,$(TEXT0_ADDR)) @@ -629,7 +629,7 @@ else # TEXT0_ADDR and TEXT1_ADDR are specified so split firmware between these locations TEXT0_SECTIONS ?= .isr_vector .isr_extratext -TEXT1_SECTIONS ?= .text .data .ARM +TEXT1_SECTIONS ?= .text .gc.blocks.table .data .ARM deploy-stlink: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin $(call RUN_STLINK,$(word 1,$^),$(TEXT0_ADDR)) From 020eeba41246f6015fb56f830141077d43d2334b Mon Sep 17 00:00:00 2001 From: Tico06 Date: Mon, 11 Aug 2025 19:30:47 +0200 Subject: [PATCH 1124/2098] stm32/eth_phy: Fix typo in header guard macro. Typo on line 29 (PYH instead of PHY). Compilation failing, here is the output: eth_phy.h:28: error: header guard 'MICROPY_INCLUDED_STM32_PHY_H' followed by '#define' of a different macro [Werror=header-guard] 28 | #ifndef MICROPY_INCLUDED_STM32_PHY_H eth_phy.h:29: note: 'MICROPY_INCLUDED_STM32_PYH_H' is defined here; did you mean 'MICROPY_INCLUDED_STM32_PHY_H'? 29 | #define MICROPY_INCLUDED_STM32_PYH_H Signed-off-by: Tico06 --- ports/stm32/eth_phy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h index 5036905c1f5..dccfb7951ad 100644 --- a/ports/stm32/eth_phy.h +++ b/ports/stm32/eth_phy.h @@ -26,7 +26,7 @@ */ #ifndef MICROPY_INCLUDED_STM32_PHY_H -#define MICROPY_INCLUDED_STM32_PYH_H +#define MICROPY_INCLUDED_STM32_PHY_H #if defined(MICROPY_HW_ETH_MDC) From d81d56cc4d7c44978761f967841bcb6e8e84b54d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 22 Jul 2025 10:56:22 +1000 Subject: [PATCH 1125/2098] tools/ci: Add UBSan to longlong CI build. Also rewrite the sanitizer argument variables to not assume a variant. longlong variant currently fails in this config, due to a bug fixed in follow-up commit. Signed-off-by: Angus Gratton --- tools/ci.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 9152c2e2dd6..095d96cf39b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -518,13 +518,11 @@ CI_UNIX_OPTS_QEMU_RISCV64=( ) CI_UNIX_OPTS_SANITIZE_ADDRESS=( - VARIANT=coverage CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" LDFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" ) CI_UNIX_OPTS_SANITIZE_UNDEFINED=( - VARIANT=coverage CFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" LDFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" ) @@ -699,7 +697,7 @@ function ci_unix_nanbox_run_tests { } function ci_unix_longlong_build { - ci_unix_build_helper VARIANT=longlong + ci_unix_build_helper VARIANT=longlong "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" } function ci_unix_longlong_run_tests { @@ -765,23 +763,23 @@ function ci_unix_settrace_stackless_run_tests { function ci_unix_sanitize_undefined_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix submodules - make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + make ${MAKEOPTS} -C ports/unix VARIANT=coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" ci_unix_build_ffi_lib_helper gcc } function ci_unix_sanitize_undefined_run_tests { - MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage VARIANT=coverage "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" } function ci_unix_sanitize_address_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix submodules - make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" + make ${MAKEOPTS} -C ports/unix VARIANT=coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" ci_unix_build_ffi_lib_helper gcc } function ci_unix_sanitize_address_run_tests { - MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" + MICROPY_TEST_TIMEOUT=60 ci_unix_run_tests_full_helper coverage VARIANT=coverage "${CI_UNIX_OPTS_SANITIZE_ADDRESS[@]}" } function ci_unix_macos_build { From 3faf2298537ee34648d27a662529ff2aa73cec1c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 10:19:43 +1000 Subject: [PATCH 1126/2098] py/misc: Add a way to detect sanitizer builds. Clang and gcc>=14 can use __has_feature() to detect if a sanitizer is enabled, but older GCC has no mechanism - need to set a macro explicitly for this to be recognised. Necessary for increasing some resource limits in sanitizer builds. Important not to use to avoid real issues! This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/misc.h | 24 ++++++++++++++++++++++++ tools/ci.sh | 6 ++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/py/misc.h b/py/misc.h index 5c1cc2f7a83..081163cadf9 100644 --- a/py/misc.h +++ b/py/misc.h @@ -43,6 +43,11 @@ typedef unsigned int uint; #ifndef __has_builtin #define __has_builtin(x) (0) #endif +#ifndef __has_feature +// This macro is supported by Clang and gcc>=14 +#define __has_feature(x) (0) +#endif + /** generic ops *************************************************/ @@ -538,4 +543,23 @@ inline static bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long } #endif + +// Helper macros for detecting if sanitizers are enabled +// +// Use sparingly, not for masking issues reported by sanitizers! +// +// Can be detected automatically in Clang and gcc>=14, need to be +// set manually otherwise. +#ifndef MP_UBSAN +#define MP_UBSAN __has_feature(undefined_behavior_sanitizer) +#endif + +#ifndef MP_ASAN +#define MP_ASAN __has_feature(address_sanitizer) +#endif + +#ifndef MP_SANITIZER_BUILD +#define MP_SANITIZER_BUILD (MP_UBSAN || MP_ASAN) +#endif + #endif // MICROPY_INCLUDED_PY_MISC_H diff --git a/tools/ci.sh b/tools/ci.sh index 095d96cf39b..e165cb2cf3f 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -518,12 +518,14 @@ CI_UNIX_OPTS_QEMU_RISCV64=( ) CI_UNIX_OPTS_SANITIZE_ADDRESS=( - CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" + # Macro MP_ASAN allows detecting ASan on gcc<=13 + CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0 -DMP_ASAN=1" LDFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" ) CI_UNIX_OPTS_SANITIZE_UNDEFINED=( - CFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" + # Macro MP_UBSAN allows detecting UBSan on gcc<=13 + CFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute -DMP_UBSAN=1" LDFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" ) From d8672f4cde83c0a5632f9e6ac5a55eb68fed64f9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 10:20:57 +1000 Subject: [PATCH 1127/2098] unix: Increase stack sizes if running with sanitizers. The specific problem seems to be that the runtime "Python stack frame" function call is several times more expensive in stack usage when running with UBSan on older GCC (observed on gcc 11.4 as used in CI, would get 'RuntimeError: maximum recursion depth exceeded' when running some tests with UBSan enabled.) Other stack usage (i.e. from pushing things on the stack in Python) stays the same. Whatever causes the usage seems to be mostly gone in later GCC versions. Includes a refactor to apply the same stack size multipliers for the default thread stack size same as the main stack size. This goes in a new port-specific header as it depends on macros in misc.h, so can't be in mpconfigport.h. A side effect of this is that the default thread stack size is now doubled on ARM, same as the main stack size. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/unix/main.c | 7 ++--- ports/unix/mpthreadport.c | 5 ++-- ports/unix/stack_size.h | 54 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 ports/unix/stack_size.h diff --git a/ports/unix/main.c b/ports/unix/main.c index 5f6b99f989a..dd0788aaad9 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -54,6 +54,7 @@ #include "extmod/vfs_posix.h" #include "genhdr/mpversion.h" #include "input.h" +#include "stack_size.h" // Command line options, with their defaults static bool compile_only = false; @@ -479,11 +480,7 @@ int main(int argc, char **argv) { #endif // Define a reasonable stack limit to detect stack overflow. - mp_uint_t stack_size = 40000 * (sizeof(void *) / 4); - #if defined(__arm__) && !defined(__thumb2__) - // ARM (non-Thumb) architectures require more stack. - stack_size *= 2; - #endif + mp_uint_t stack_size = 40000 * UNIX_STACK_MULTIPLIER; // We should capture stack top ASAP after start, and it should be // captured guaranteedly before any other stack variables are allocated. diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 141cd0218d9..a41b3ec9f47 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -31,6 +31,7 @@ #include "py/runtime.h" #include "py/mpthread.h" #include "py/gc.h" +#include "stack_size.h" #if MICROPY_PY_THREAD @@ -244,9 +245,9 @@ void mp_thread_start(void) { } mp_uint_t mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { - // default stack size is 8k machine-words + // default stack size if (*stack_size == 0) { - *stack_size = 8192 * sizeof(void *); + *stack_size = 32768 * UNIX_STACK_MULTIPLIER; } // minimum stack size is set by pthreads diff --git a/ports/unix/stack_size.h b/ports/unix/stack_size.h new file mode 100644 index 00000000000..f6159bb69d5 --- /dev/null +++ b/ports/unix/stack_size.h @@ -0,0 +1,54 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + */ +#ifndef MICROPY_INCLUDED_UNIX_STACK_SIZE_H +#define MICROPY_INCLUDED_UNIX_STACK_SIZE_H + +#include "py/misc.h" + +// Define scaling factors for the stack size (also applies to main thread) +#ifndef UNIX_STACK_MULTIPLIER + +#if defined(__arm__) && !defined(__thumb2__) +// ARM (non-Thumb) architectures require more stack. +#define UNIX_STACK_MUL_ARM 2 +#else +#define UNIX_STACK_MUL_ARM 1 +#endif + +#if MP_SANITIZER_BUILD +// Sanitizer features consume significant stack in some cases +// This multiplier can probably be removed when using GCC 12 or newer. +#define UNIX_STACK_MUL_SANITIZERS 4 +#else +#define UNIX_STACK_MUL_SANITIZERS 1 +#endif + +// Double the stack size for 64-bit builds, plus additional scaling +#define UNIX_STACK_MULTIPLIER ((sizeof(void *) / 4) * UNIX_STACK_MUL_ARM * UNIX_STACK_MUL_SANITIZERS) + +#endif // UNIX_STACK_MULTIPLIER + +#endif // MICROPY_INCLUDED_UNIX_STACK_SIZE_H From 22deeeb8db1305b292e9146b7b00a58207755693 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 12 Aug 2025 11:03:50 +1000 Subject: [PATCH 1128/2098] tests/stress/recursive_iternext: Rewrite to find its own limit. Necessary on the unix port when running with sanitizers, as the newly increased stack size can run all tests at N=5000 without raising RuntimeError, and increasing N to fix this causes issues on other configurations. This way the test progressively builds a deeper data structure until it fails with RuntimeError. This is theoretically slower, but not noticeably so in reality. Signed-off-by: Angus Gratton --- tests/stress/recursive_iternext.py | 64 ++++++++++-------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/tests/stress/recursive_iternext.py b/tests/stress/recursive_iternext.py index bbc389e7262..c737f1e36d7 100644 --- a/tests/stress/recursive_iternext.py +++ b/tests/stress/recursive_iternext.py @@ -1,4 +1,8 @@ # This tests that recursion with iternext doesn't lead to segfault. +# +# This test segfaults CPython, but that's not a bug as CPython doesn't enforce +# limits on C recursion - see +# https://github.com/python/cpython/issues/58218#issuecomment-1093570209 try: enumerate filter @@ -9,49 +13,25 @@ print("SKIP") raise SystemExit -# We need to pick an N that is large enough to hit the recursion -# limit, but not too large that we run out of heap memory. -try: - # large stack/heap, eg unix - [0] * 80000 - N = 5000 -except: - try: - # medium, eg pyboard - [0] * 10000 - N = 1000 - except: - # small, eg esp8266 - N = 100 - -try: - x = (1, 2) - for i in range(N): - x = enumerate(x) - tuple(x) -except RuntimeError: - print("RuntimeError") -try: +# Progressively build a bigger nested iterator structure (10 at a time for speed), +# and then try to evaluate it via tuple(x) which makes deep recursive function calls. +# +# Eventually this should raise a RuntimeError as MicroPython runs out of stack. +# It shouldn't ever raise a MemoryError, if it does then somehow MicroPython has +# run out of heap (for the nested structure) before running out of stack. +def recurse_iternext(nested_fn): x = (1, 2) - for i in range(N): - x = filter(None, x) - tuple(x) -except RuntimeError: - print("RuntimeError") + while True: + for _ in range(10): + x = nested_fn(x) + try: + tuple(x) + except RuntimeError: + print("RuntimeError") + break -try: - x = (1, 2) - for i in range(N): - x = map(max, x, ()) - tuple(x) -except RuntimeError: - print("RuntimeError") -try: - x = (1, 2) - for i in range(N): - x = zip(x) - tuple(x) -except RuntimeError: - print("RuntimeError") +# Test on various nested iterator structures +for nested_fn in [enumerate, lambda x: filter(None, x), lambda x: map(max, x, ()), zip]: + recurse_iternext(nested_fn) From bd413d3d853e2c1fc0382b328176ff912d79da70 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 17 Aug 2025 07:28:35 +0200 Subject: [PATCH 1129/2098] py/asmthumb: Fix T3 encoding of conditional branches. This commit fixes the encoding of conditional branch opcodes emitted for ARMv7-M targets, when the emitter decides to use the T3 encoding for said operation. Fields J1 and J2 are now present in the generated opcode word, along with correcting some minor issues in bitmasks and shifts computation. This fixes #17940. Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 5 ++--- tests/micropython/viper_large_jump.py | 20 ++++++++++++++++++++ tests/micropython/viper_large_jump.py.exp | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/micropython/viper_large_jump.py create mode 100644 tests/micropython/viper_large_jump.py.exp diff --git a/py/asmthumb.c b/py/asmthumb.c index 93860d2fdc1..58cc7aea880 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -267,9 +267,8 @@ bool asm_thumb_b_n_label(asm_thumb_t *as, uint label) { #define OP_BCC_N(cond, byte_offset) (0xd000 | ((cond) << 8) | (((byte_offset) >> 1) & 0x00ff)) -// all these bit-arithmetic operations need coverage testing! -#define OP_BCC_W_HI(cond, byte_offset) (0xf000 | ((cond) << 6) | (((byte_offset) >> 10) & 0x0400) | (((byte_offset) >> 14) & 0x003f)) -#define OP_BCC_W_LO(byte_offset) (0x8000 | ((byte_offset) & 0x2000) | (((byte_offset) >> 1) & 0x0fff)) +#define OP_BCC_W_HI(cond, byte_offset) (0xf000 | ((cond) << 6) | (((byte_offset) >> 10) & 0x0400) | (((byte_offset) >> 12) & 0x003f)) +#define OP_BCC_W_LO(byte_offset) (0x8000 | (((byte_offset) >> 5) & 0x2000) | (((byte_offset) >> 8) & 0x0800) | (((byte_offset) >> 1) & 0x07ff)) bool asm_thumb_bcc_nw_label(asm_thumb_t *as, int cond, uint label, bool wide) { mp_uint_t dest = get_label_dest(as, label); diff --git a/tests/micropython/viper_large_jump.py b/tests/micropython/viper_large_jump.py new file mode 100644 index 00000000000..1c5913dec1e --- /dev/null +++ b/tests/micropython/viper_large_jump.py @@ -0,0 +1,20 @@ +COUNT = 600 + + +try: + code = """ +@micropython.viper +def f() -> int: + x = 0 + while x < 10: +""" + for i in range(COUNT): + code += " x += 1\n" + code += " return x" + exec(code) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit + + +print(f()) diff --git a/tests/micropython/viper_large_jump.py.exp b/tests/micropython/viper_large_jump.py.exp new file mode 100644 index 00000000000..e9f960cf4ac --- /dev/null +++ b/tests/micropython/viper_large_jump.py.exp @@ -0,0 +1 @@ +600 From 36501966821db97feaf27a6b87086bee0eb27775 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 16:52:29 +1000 Subject: [PATCH 1130/2098] py/objtype: Use locals_ptr directly instead of getting it from the slot. This is a very minor code simplification, which reduces code size by about -8 bytes. It should have no functional change. Signed-off-by: Damien George --- py/objtype.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py/objtype.c b/py/objtype.c index 25c2dee994d..d40f619fae2 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1292,8 +1292,7 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals mp_raise_TypeError(MP_ERROR_TEXT("multiple bases have instance lay-out conflict")); } - mp_map_t *locals_map = &MP_OBJ_TYPE_GET_SLOT(o, locals_dict)->map; - mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(MP_QSTR___new__), MP_MAP_LOOKUP); + mp_map_elem_t *elem = mp_map_lookup(&locals_ptr->map, MP_OBJ_NEW_QSTR(MP_QSTR___new__), MP_MAP_LOOKUP); if (elem != NULL) { // __new__ slot exists; check if it is a function if (mp_obj_is_fun(elem->value)) { From 169d382248fcec1b62891db6339c349477020bd2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 17:00:44 +1000 Subject: [PATCH 1131/2098] py/mpconfig: Rename MICROPY_PY___FILE__ to MICROPY_MODULE___FILE__. For consistency with other module-related configuration options. Signed-off-by: Damien George --- mpy-cross/main.c | 2 +- mpy-cross/mpconfigport.h | 2 +- ports/esp8266/mpconfigport.h | 2 +- ports/nrf/mpconfigport.h | 2 +- ports/pic16bit/mpconfigport.h | 2 +- ports/powerpc/mpconfigport.h | 2 +- ports/unix/main.c | 2 +- py/builtinimport.c | 6 +++--- py/mpconfig.h | 10 +++++----- py/objmodule.c | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mpy-cross/main.c b/mpy-cross/main.c index 16f749ae4dc..b7771ce6e79 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -81,7 +81,7 @@ static int compile_and_save(const char *file, const char *output_file, const cha source_name = qstr_from_str(source_file); } - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); #endif diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 81cbfc2eeb9..0a6478b4f3d 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -85,7 +85,7 @@ #define MICROPY_GCREGS_SETJMP (1) #endif -#define MICROPY_PY___FILE__ (0) +#define MICROPY_MODULE___FILE__ (0) #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ATTRTUPLE (0) #define MICROPY_PY_COLLECTIONS (0) diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 323fba67f5b..675751d2b40 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -23,6 +23,7 @@ #define MICROPY_OPT_MATH_FACTORIAL (0) #define MICROPY_REPL_EMACS_KEYS (0) #define MICROPY_PY_BUILTINS_COMPLEX (0) +#define MICROPY_MODULE___FILE__ (0) #define MICROPY_PY_DELATTR_SETATTR (0) #define MICROPY_PY_BUILTINS_STR_CENTER (0) #define MICROPY_PY_BUILTINS_STR_PARTITION (0) @@ -32,7 +33,6 @@ #define MICROPY_PY_BUILTINS_EXECFILE (0) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (0) #define MICROPY_PY_BUILTINS_POW3 (0) -#define MICROPY_PY___FILE__ (0) #define MICROPY_PY_MATH_CONSTANTS (0) #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (0) #define MICROPY_PY_MATH_FACTORIAL (0) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 963e1e8836d..d944fc8a11e 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -264,6 +264,7 @@ #define MICROPY_ERROR_REPORTING (2) #define MICROPY_FULL_CHECKS (1) #define MICROPY_GC_ALLOC_THRESHOLD (1) +#define MICROPY_MODULE___FILE__ (1) #define MICROPY_MODULE_GETATTR (1) #define MICROPY_MULTIPLE_INHERITANCE (1) #define MICROPY_PY_ARRAY (1) @@ -290,7 +291,6 @@ #define MICROPY_PY_STRUCT (1) #define MICROPY_PY_SYS (1) #define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (1) -#define MICROPY_PY___FILE__ (1) #endif #ifndef MICROPY_PY_UBLUEPY diff --git a/ports/pic16bit/mpconfigport.h b/ports/pic16bit/mpconfigport.h index 7e6e1c4e02b..e95f25aa0b6 100644 --- a/ports/pic16bit/mpconfigport.h +++ b/ports/pic16bit/mpconfigport.h @@ -43,6 +43,7 @@ #define MICROPY_ENABLE_SOURCE_LINE (0) #define MICROPY_ENABLE_DOC_STRING (0) #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) +#define MICROPY_MODULE___FILE__ (0) #define MICROPY_PY_ASYNC_AWAIT (0) #define MICROPY_PY_BUILTINS_BYTEARRAY (0) #define MICROPY_PY_BUILTINS_MEMORYVIEW (0) @@ -51,7 +52,6 @@ #define MICROPY_PY_BUILTINS_SLICE (0) #define MICROPY_PY_BUILTINS_PROPERTY (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) -#define MICROPY_PY___FILE__ (0) #define MICROPY_PY_GC (1) #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_COLLECTIONS (0) diff --git a/ports/powerpc/mpconfigport.h b/ports/powerpc/mpconfigport.h index 25d85c9e61a..091e94bdafd 100644 --- a/ports/powerpc/mpconfigport.h +++ b/ports/powerpc/mpconfigport.h @@ -57,6 +57,7 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) #define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_MODULE___FILE__ (0) #define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_PY_BUILTINS_BYTEARRAY (1) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1) @@ -73,7 +74,6 @@ #define MICROPY_PY_BUILTINS_STR_OP_MODULO (1) #define MICROPY_PY_BUILTINS_HELP (1) #define MICROPY_PY_BUILTINS_HELP_MODULES (1) -#define MICROPY_PY___FILE__ (0) #define MICROPY_PY_GC (1) #define MICROPY_PY_ARRAY (1) #define MICROPY_PY_COLLECTIONS (1) diff --git a/ports/unix/main.c b/ports/unix/main.c index dd0788aaad9..51d99ce5f15 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -139,7 +139,7 @@ static int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu qstr source_name = lex->source_name; - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ if (input_kind == MP_PARSE_FILE_INPUT) { mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); } diff --git a/py/builtinimport.c b/py/builtinimport.c index 0611926fdd5..a2737a03e8d 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -153,7 +153,7 @@ static mp_import_stat_t stat_top_level(qstr mod_name, vstr_t *dest) { #if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER static void do_load_from_lexer(mp_module_context_t *context, mp_lexer_t *lex) { - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ qstr source_name = lex->source_name; mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); #endif @@ -166,7 +166,7 @@ static void do_load_from_lexer(mp_module_context_t *context, mp_lexer_t *lex) { #if (MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD) || MICROPY_MODULE_FROZEN_MPY static void do_execute_proto_fun(const mp_module_context_t *context, mp_proto_fun_t proto_fun, qstr source_name) { - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); #else (void)source_name; @@ -225,7 +225,7 @@ static void do_load(mp_module_context_t *module_obj, vstr_t *file) { if (frozen_type == MP_FROZEN_MPY) { const mp_frozen_module_t *frozen = modref; module_obj->constants = frozen->constants; - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ qstr frozen_file_qstr = qstr_from_str(file_str + frozen_path_prefix_len); #else qstr frozen_file_qstr = MP_QSTRnull; diff --git a/py/mpconfig.h b/py/mpconfig.h index 5fe0e822f77..93d65f2b0e2 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -990,6 +990,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_STREAMS_POSIX_API (0) #endif +// Whether to set __file__ on imported modules. +#ifndef MICROPY_MODULE___FILE__ +#define MICROPY_MODULE___FILE__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + // Whether modules can use MP_REGISTER_MODULE_DELEGATION() to delegate failed // attribute lookups to a custom handler function. #ifndef MICROPY_MODULE_ATTR_DELEGATION @@ -1422,11 +1427,6 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_HELP_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif -// Whether to set __file__ for imported modules -#ifndef MICROPY_PY___FILE__ -#define MICROPY_PY___FILE__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) -#endif - // Whether to process __all__ when importing all public symbols from module #ifndef MICROPY_MODULE___ALL__ #define MICROPY_MODULE___ALL__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) diff --git a/py/objmodule.c b/py/objmodule.c index 5ce373b83d3..5ee2f7dc860 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -44,7 +44,7 @@ static void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin module_name = mp_obj_str_get_str(elem->value); } - #if MICROPY_PY___FILE__ + #if MICROPY_MODULE___FILE__ // If we store __file__ to imported modules then try to lookup this // symbol to give more information about the module. elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_MAP_LOOKUP); From 989abae12c9210a0acf966bbc71e90f285d8795c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Aug 2025 17:02:01 +1000 Subject: [PATCH 1132/2098] py/mpconfig: Move MICROPY_MODULE___ALL__ option to other module options. Signed-off-by: Damien George --- py/mpconfig.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 93d65f2b0e2..b5483ee9011 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -990,6 +990,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_STREAMS_POSIX_API (0) #endif +// Whether to process __all__ when importing all public symbols from a module. +#ifndef MICROPY_MODULE___ALL__ +#define MICROPY_MODULE___ALL__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) +#endif + // Whether to set __file__ on imported modules. #ifndef MICROPY_MODULE___FILE__ #define MICROPY_MODULE___FILE__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) @@ -1427,11 +1432,6 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_HELP_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif -// Whether to process __all__ when importing all public symbols from module -#ifndef MICROPY_MODULE___ALL__ -#define MICROPY_MODULE___ALL__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) -#endif - // Whether to provide mem-info related functions in micropython module #ifndef MICROPY_PY_MICROPYTHON_MEM_INFO #define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) From b5fcb33eaa682bb666c839cd4fb301175cc3564f Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Aug 2025 12:56:16 +1000 Subject: [PATCH 1133/2098] py/mpconfig: Enable CRYPTOLIB, HASHLIB_MD5, HASHLIB_SHA1 if SSL enabled. This commit unifies the configuration of MICROPY_PY_CRYPTOLIB, MICROPY_PY_HASHLIB_MD5 and MICROPY_PY_HASHLIB_SHA1, so they are enabled by default if MICROPY_PY_SSL is enabled. This matches the existing configuration of most of the ports. With this change, all ports remain the same except: - reneses-ra now enables MICROPY_PY_CRYPTOLIB, MICROPY_PY_HASHLIB_MD5 and MICROPY_PY_HASHLIB_SHA1. - rp2 now enables MICROPY_PY_HASHLIB_MD5. Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 3 --- ports/esp32/mpconfigport.h | 4 ---- ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h | 6 +----- ports/esp8266/mpconfigport.h | 1 + ports/mimxrt/mpconfigport.h | 3 --- .../rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h | 3 --- ports/rp2/mpconfigport.h | 2 -- ports/stm32/mpconfigport.h | 3 --- ports/unix/variants/mpconfigvariant_common.h | 6 ------ py/mpconfig.h | 6 +++--- 10 files changed, 5 insertions(+), 32 deletions(-) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 08b27e2786e..6b30ea2e624 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -112,9 +112,6 @@ // Extended modules #define MICROPY_EPOCH_IS_1970 (1) -#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) -#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) -#define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) #define MICROPY_PY_OS_INCLUDEFILE "ports/alif/modos.c" #define MICROPY_PY_OS_DUPTERM (1) #define MICROPY_PY_OS_SEP (1) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index e47b333c736..9b12dbd34a7 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -105,10 +105,6 @@ #define MICROPY_BLUETOOTH_NIMBLE (1) #define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) #endif -#define MICROPY_PY_HASHLIB_MD5 (1) -#define MICROPY_PY_HASHLIB_SHA1 (1) -#define MICROPY_PY_HASHLIB_SHA256 (1) -#define MICROPY_PY_CRYPTOLIB (1) #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (esp_random()) #define MICROPY_PY_OS_INCLUDEFILE "ports/esp32/modos.c" #define MICROPY_PY_OS_DUPTERM (1) diff --git a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h index 1f679961e87..cea2267c7cc 100644 --- a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h +++ b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h @@ -12,8 +12,6 @@ #define MICROPY_READER_VFS (MICROPY_VFS) #define MICROPY_VFS (1) -#define MICROPY_PY_CRYPTOLIB (1) - #elif defined(MICROPY_ESP8266_1M) #define MICROPY_HW_BOARD_NAME "ESP module (1M)" @@ -28,9 +26,6 @@ #define MICROPY_READER_VFS (MICROPY_VFS) #define MICROPY_VFS (1) - -#define MICROPY_PY_CRYPTOLIB (1) - #elif defined(MICROPY_ESP8266_512K) #define MICROPY_HW_BOARD_NAME "ESP module (512K)" @@ -45,6 +40,7 @@ #define MICROPY_PY_SYS_STDIO_BUFFER (0) #define MICROPY_PY_ASYNCIO (0) #define MICROPY_PY_RE_SUB (0) +#define MICROPY_PY_CRYPTOLIB (0) #define MICROPY_PY_FRAMEBUF (0) #endif diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 675751d2b40..0321de45d7f 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -56,6 +56,7 @@ #define MICROPY_REPL_EVENT_DRIVEN (0) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_PY_BUILTINS_HELP_TEXT esp_help_text +#define MICROPY_PY_HASHLIB_MD5 (0) #define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL && MICROPY_SSL_AXTLS) #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (*WDEV_HWRNG) #define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 3cf6550d7b0..d6694badbad 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -145,9 +145,6 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_WEBSOCKET (MICROPY_PY_LWIP) #define MICROPY_PY_WEBREPL (MICROPY_PY_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) -#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) -#define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) -#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) #ifndef MICROPY_PY_NETWORK_PPP_LWIP #define MICROPY_PY_NETWORK_PPP_LWIP (MICROPY_PY_LWIP) #endif diff --git a/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h b/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h index 7783c0a17c6..11aa663296f 100644 --- a/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h +++ b/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h @@ -10,9 +10,6 @@ // Enable networking. #define MICROPY_PY_NETWORK (1) -// Enable MD5 hash. -#define MICROPY_PY_HASHLIB_MD5 (1) - // Disable internal error numbers. #define MICROPY_USE_INTERNAL_ERRNO (0) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index c312293ace6..0c226538cda 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -150,8 +150,6 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_RE_MATCH_GROUPS (1) #define MICROPY_PY_RE_MATCH_SPAN_START_END (1) -#define MICROPY_PY_HASHLIB_SHA1 (1) -#define MICROPY_PY_CRYPTOLIB (1) #define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/rp2/modtime.c" diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 191503cd495..fac261f7e24 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -97,9 +97,6 @@ #endif // extended modules -#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) -#define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) -#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) #define MICROPY_PY_OS_INCLUDEFILE "ports/stm32/modos.c" #define MICROPY_PY_OS_DUPTERM (3) #define MICROPY_PY_OS_DUPTERM_BUILTIN_STREAM (1) diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 65c87431766..1ac59c95572 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -104,12 +104,6 @@ #define MICROPY_PY_TIME_CUSTOM_SLEEP (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/unix/modtime.c" -#if MICROPY_PY_SSL -#define MICROPY_PY_HASHLIB_MD5 (1) -#define MICROPY_PY_HASHLIB_SHA1 (1) -#define MICROPY_PY_CRYPTOLIB (1) -#endif - // The "select" module is enabled by default, but disable select.select(). #define MICROPY_PY_SELECT_POSIX_OPTIMISATIONS (1) #define MICROPY_PY_SELECT_SELECT (0) diff --git a/py/mpconfig.h b/py/mpconfig.h index b5483ee9011..877b262c8b7 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1860,11 +1860,11 @@ typedef time_t mp_timestamp_t; #endif #ifndef MICROPY_PY_HASHLIB_MD5 -#define MICROPY_PY_HASHLIB_MD5 (0) +#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) #endif #ifndef MICROPY_PY_HASHLIB_SHA1 -#define MICROPY_PY_HASHLIB_SHA1 (0) +#define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) #endif #ifndef MICROPY_PY_HASHLIB_SHA256 @@ -1872,7 +1872,7 @@ typedef time_t mp_timestamp_t; #endif #ifndef MICROPY_PY_CRYPTOLIB -#define MICROPY_PY_CRYPTOLIB (0) +#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) #endif // Depends on MICROPY_PY_CRYPTOLIB From 14e9c00cb99465236c579da722a72a9cfc4ef12f Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Aug 2025 14:17:09 +1000 Subject: [PATCH 1134/2098] py/builtinimport: Guard code needing sys.path with MICROPY_PY_SYS_PATH. Signed-off-by: Damien George --- py/builtinimport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/builtinimport.c b/py/builtinimport.c index a2737a03e8d..57b5c14e883 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -116,7 +116,7 @@ static mp_import_stat_t stat_module(vstr_t *path) { // path (i.e. "/mod_name(.py)"). static mp_import_stat_t stat_top_level(qstr mod_name, vstr_t *dest) { DEBUG_printf("stat_top_level: '%s'\n", qstr_str(mod_name)); - #if MICROPY_PY_SYS + #if MICROPY_PY_SYS && MICROPY_PY_SYS_PATH size_t path_num; mp_obj_t *path_items; mp_obj_get_array(mp_sys_path, &path_num, &path_items); @@ -367,7 +367,7 @@ static mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, // Immediately return if the module at this level is already loaded. mp_map_elem_t *elem; - #if MICROPY_PY_SYS + #if MICROPY_PY_SYS && MICROPY_PY_SYS_PATH // If sys.path is empty, the intention is to force using a built-in. This // means we should also ignore any loaded modules with the same name // which may have come from the filesystem. From a3f9dec78852caeb56b70f1cf5cd2bd71f36c1ee Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Aug 2025 13:11:51 +1000 Subject: [PATCH 1135/2098] py/mpconfig: Enable the sys module at all feature levels by default. This is a pretty fundamental module, and even minimal ports like unix and zephyr minimal have it enabled. So, enabled it by default at the lowest feature level. Most things in the `sys` module are configurable, and off by default, so it shouldn't add too much to ports that don't already have it enabled (which is just the minimal port). Also note that `sys` is still disabled on the bare-arm port, to keep that ultra minimal. It means we now have bare-arm without `sys` and the minimal port with `sys`. That will allow different code size comparisons if/when new `sys` features are added. Signed-off-by: Damien George --- examples/embedding/mpconfigport.h | 1 + ports/bare-arm/mpconfigport.h | 3 +++ ports/minimal/mpconfigport.h | 6 ++++++ ports/nrf/mpconfigport.h | 1 - ports/powerpc/mpconfigport.h | 1 - ports/unix/variants/minimal/mpconfigvariant.h | 3 +-- ports/zephyr/mpconfigport_minimal.h | 1 - py/mpconfig.h | 2 +- 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/embedding/mpconfigport.h b/examples/embedding/mpconfigport.h index ed34a5d766a..b7a38aa81a7 100644 --- a/examples/embedding/mpconfigport.h +++ b/examples/embedding/mpconfigport.h @@ -13,3 +13,4 @@ #define MICROPY_ENABLE_COMPILER (1) #define MICROPY_ENABLE_GC (1) #define MICROPY_PY_GC (1) +#define MICROPY_PY_SYS (0) diff --git a/ports/bare-arm/mpconfigport.h b/ports/bare-arm/mpconfigport.h index 65bb67f7b9a..f4b87f23eff 100644 --- a/ports/bare-arm/mpconfigport.h +++ b/ports/bare-arm/mpconfigport.h @@ -37,6 +37,9 @@ // Python internal features #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NONE) +// Fine control over Python builtins, classes, modules, etc. +#define MICROPY_PY_SYS (0) + // Type definitions for the specific machine typedef int32_t mp_int_t; // must be pointer size diff --git a/ports/minimal/mpconfigport.h b/ports/minimal/mpconfigport.h index 87287cbc6f3..b542c124d43 100644 --- a/ports/minimal/mpconfigport.h +++ b/ports/minimal/mpconfigport.h @@ -21,6 +21,12 @@ // Use the minimum headroom in the chunk allocator for parse nodes. #define MICROPY_ALLOC_PARSE_CHUNK_INIT (16) +// Disable all optional sys module features. +#define MICROPY_PY_SYS_MODULES (0) +#define MICROPY_PY_SYS_EXIT (0) +#define MICROPY_PY_SYS_PATH (0) +#define MICROPY_PY_SYS_ARGV (0) + // type definitions for the specific machine typedef intptr_t mp_int_t; // must be pointer size diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index d944fc8a11e..d361d01ccef 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -289,7 +289,6 @@ #define MICROPY_PY_GENERATOR_PEND_THROW (1) #define MICROPY_PY_MATH (1) #define MICROPY_PY_STRUCT (1) -#define MICROPY_PY_SYS (1) #define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (1) #endif diff --git a/ports/powerpc/mpconfigport.h b/ports/powerpc/mpconfigport.h index 091e94bdafd..fcb66bab1e9 100644 --- a/ports/powerpc/mpconfigport.h +++ b/ports/powerpc/mpconfigport.h @@ -81,7 +81,6 @@ #define MICROPY_PY_CMATH (0) #define MICROPY_PY_IO (0) #define MICROPY_PY_STRUCT (1) -#define MICROPY_PY_SYS (1) #define MICROPY_MODULE_FROZEN_MPY (1) #define MICROPY_CPYTHON_COMPAT (0) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) diff --git a/ports/unix/variants/minimal/mpconfigvariant.h b/ports/unix/variants/minimal/mpconfigvariant.h index 97ed786b8f4..2edac41c7fd 100644 --- a/ports/unix/variants/minimal/mpconfigvariant.h +++ b/ports/unix/variants/minimal/mpconfigvariant.h @@ -61,6 +61,5 @@ #define MICROPY_PY_BUILTINS_RANGE_ATTRS (1) #define MICROPY_PY_GENERATOR_PEND_THROW (1) -// Enable just the sys and os built-in modules. -#define MICROPY_PY_SYS (1) +// Add just the os built-in module. #define MICROPY_PY_OS (1) diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index 24e0c9f1adc..c802ba52540 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -59,7 +59,6 @@ #define MICROPY_PY_BUILTINS_STR_OP_MODULO (1) #define MICROPY_PY_BUILTINS_BYTEARRAY (1) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1) -#define MICROPY_PY_SYS (1) #ifdef CONFIG_BOARD #define MICROPY_HW_BOARD_NAME "zephyr-" CONFIG_BOARD diff --git a/py/mpconfig.h b/py/mpconfig.h index 877b262c8b7..303eb08f90b 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1610,7 +1610,7 @@ typedef time_t mp_timestamp_t; // Whether to provide "sys" module #ifndef MICROPY_PY_SYS -#define MICROPY_PY_SYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#define MICROPY_PY_SYS (1) #endif // Whether to initialise "sys.path" and "sys.argv" to their defaults in mp_init() From bd7342d9ec31101a850ddaa77c2d67c76f803389 Mon Sep 17 00:00:00 2001 From: Yilin Sun Date: Mon, 1 Apr 2024 16:13:15 +0800 Subject: [PATCH 1136/2098] mimxrt: Restructure nxp_sdk to match official mcux-sdk. The official mcux-sdk follows a slightly different structure to the current nxp_sdk submodule, with many drivers moved to a common location. To ease updating the newer versions of the SDK and/or add new families the nxp_sdk submodule has been updated to follow the structure of mcux-sdk, just trimmed down to families used here to considerably reduce the size. Signed-off-by: Andrew Leech --- .gitmodules | 2 +- lib/nxp_driver | 2 +- ports/mimxrt/Makefile | 112 ++++++++++++------ .../boards/MIMXRT1050_EVK/mpconfigboard.h | 42 +++---- .../boards/SEEED_ARCH_MIX/mpconfigboard.h | 56 ++++----- .../boards/SEEED_ARCH_MIX/mpconfigboard.mk | 1 - ports/mimxrt/eth.c | 8 +- ports/mimxrt/machine_i2s.c | 10 +- ports/mimxrt/machine_pwm.c | 2 + ports/mimxrt/machine_rtc.c | 2 +- 10 files changed, 145 insertions(+), 92 deletions(-) diff --git a/.gitmodules b/.gitmodules index 02849ec9bdd..d2c229dd6d7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -35,7 +35,7 @@ url = https://github.com/bluekitchen/btstack.git [submodule "lib/nxp_driver"] path = lib/nxp_driver - url = https://github.com/hathach/nxp_driver.git + url = https://github.com/micropython/nxp_driver.git [submodule "lib/libhydrogen"] path = lib/libhydrogen url = https://github.com/jedisct1/libhydrogen.git diff --git a/lib/nxp_driver b/lib/nxp_driver index fa5a554c794..91b04b34a59 160000 --- a/lib/nxp_driver +++ b/lib/nxp_driver @@ -1 +1 @@ -Subproject commit fa5a554c7944d2a196626f8d3631e44943f9abcc +Subproject commit 91b04b34a59f6d81661cec6f84611afe6330ce92 diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 7788b54ca57..fea7e56da22 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -52,7 +52,8 @@ include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk # Set SDK directory based on MCU_SERIES -MCU_DIR = lib/nxp_driver/sdk/devices/$(MCU_SERIES) +MCUX_SDK_DIR = lib/nxp_driver/sdk +MCU_DIR = $(MCUX_SDK_DIR)/devices/$(MCU_SERIES) # Select linker scripts based on MCU_SERIES LD_FILES = boards/$(MCU_SERIES).ld boards/common.ld @@ -72,8 +73,6 @@ GEN_PINS_SRC = $(BUILD)/pins_gen.c INC += -I$(BOARD_DIR) INC += -I$(BUILD) INC += -I$(TOP) -INC += -I$(TOP)/$(MCU_DIR) -INC += -I$(TOP)/$(MCU_DIR)/drivers INC += -I$(TOP)/lib/cmsis/inc INC += -I$(TOP)/lib/oofatfs INC += -I$(TOP)/lib/tinyusb/hw @@ -111,35 +110,38 @@ SRC_TINYUSB_C += \ # All settings for Ethernet support are controller by the value of MICROPY_PY_LWIP ifeq ($(MICROPY_PY_LWIP),1) SRC_ETH_C += \ - $(MCU_DIR)/drivers/fsl_enet.c \ + $(MCUX_SDK_DIR)/drivers/enet/fsl_enet.c \ hal/phy/device/phydp83825/fsl_phydp83825.c \ hal/phy/device/phydp83848/fsl_phydp83848.c \ hal/phy/device/phyksz8081/fsl_phyksz8081.c \ hal/phy/device/phylan8720/fsl_phylan8720.c \ hal/phy/device/phyrtl8211f/fsl_phyrtl8211f.c \ hal/phy/mdio/enet/fsl_enet_mdio.c + +INC_HAL_IMX += \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/enet endif # NXP SDK sources SRC_HAL_IMX_C += \ $(MCU_DIR)/drivers/fsl_clock.c \ - $(MCU_DIR)/drivers/fsl_common.c \ - $(MCU_DIR)/drivers/fsl_dmamux.c \ - $(MCU_DIR)/drivers/fsl_edma.c \ - $(MCU_DIR)/drivers/fsl_flexram.c \ - $(MCU_DIR)/drivers/fsl_flexspi.c \ - $(MCU_DIR)/drivers/fsl_gpc.c \ - $(MCU_DIR)/drivers/fsl_gpio.c \ - $(MCU_DIR)/drivers/fsl_gpt.c \ - $(MCU_DIR)/drivers/fsl_lpi2c.c \ - $(MCU_DIR)/drivers/fsl_lpspi.c \ - $(MCU_DIR)/drivers/fsl_lpspi_edma.c \ - $(MCU_DIR)/drivers/fsl_pit.c \ - $(MCU_DIR)/drivers/fsl_pwm.c \ - $(MCU_DIR)/drivers/fsl_sai.c \ - $(MCU_DIR)/drivers/fsl_snvs_hp.c \ - $(MCU_DIR)/drivers/fsl_snvs_lp.c \ - $(MCU_DIR)/drivers/fsl_wdog.c \ + $(MCUX_SDK_DIR)/drivers/common/fsl_common.c \ + $(MCUX_SDK_DIR)/drivers/common/fsl_common_arm.c \ + $(MCUX_SDK_DIR)/drivers/dmamux/fsl_dmamux.c \ + $(MCUX_SDK_DIR)/drivers/edma/fsl_edma.c \ + $(MCUX_SDK_DIR)/drivers/flexram/fsl_flexram.c \ + $(MCUX_SDK_DIR)/drivers/flexspi/fsl_flexspi.c \ + $(MCUX_SDK_DIR)/drivers/igpio/fsl_gpio.c \ + $(MCUX_SDK_DIR)/drivers/gpt/fsl_gpt.c \ + $(MCUX_SDK_DIR)/drivers/lpi2c/fsl_lpi2c.c \ + $(MCUX_SDK_DIR)/drivers/lpspi/fsl_lpspi.c \ + $(MCUX_SDK_DIR)/drivers/lpspi/fsl_lpspi_edma.c \ + $(MCUX_SDK_DIR)/drivers/pit/fsl_pit.c \ + $(MCUX_SDK_DIR)/drivers/pwm/fsl_pwm.c \ + $(MCUX_SDK_DIR)/drivers/sai/fsl_sai.c \ + $(MCUX_SDK_DIR)/drivers/snvs_hp/fsl_snvs_hp.c \ + $(MCUX_SDK_DIR)/drivers/snvs_lp/fsl_snvs_lp.c \ + $(MCUX_SDK_DIR)/drivers/wdog01/fsl_wdog.c \ $(MCU_DIR)/system_$(MCU_SERIES)$(MCU_CORE).c \ # Use a specific boot header for 1062 so the Teensy loader doesn't erase the filesystem. @@ -149,18 +151,42 @@ else SRC_HAL_IMX_C += $(MCU_DIR)/xip/fsl_flexspi_nor_boot.c endif +INC_HAL_IMX += \ + -I$(TOP)/$(MCU_DIR) \ + -I$(TOP)/$(MCU_DIR)/drivers \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/common \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/dmamux \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/edma \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/flexram \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/flexspi \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/igpio \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/gpt \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/lpi2c \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/lpspi \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/lpuart \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/pit \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/pwm \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/sai \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/snvs_hp \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/snvs_lp \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/wdog01 \ + ifeq ($(MICROPY_HW_SDRAM_AVAIL),1) -SRC_HAL_IMX_C += $(MCU_DIR)/drivers/fsl_semc.c +SRC_HAL_IMX_C += $(MCUX_SDK_DIR)/drivers/semc/fsl_semc.c +INC_HAL_IMX += -I$(TOP)/$(MCUX_SDK_DIR)/drivers/semc endif ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES), MIMXRT1021 MIMXRT1052 MIMXRT1062 MIMXRT1064 MIMXRT1176)) -SRC_HAL_IMX_C += $(MCU_DIR)/drivers/fsl_usdhc.c +SRC_HAL_IMX_C += $(MCUX_SDK_DIR)/drivers/usdhc/fsl_usdhc.c +INC_HAL_IMX += -I$(TOP)/$(MCUX_SDK_DIR)/drivers/usdhc endif ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES), MIMXRT1015 MIMXRT1021 MIMXRT1052 MIMXRT1062 MIMXRT1064 MIMXRT1176)) SRC_HAL_IMX_C += \ - $(MCU_DIR)/drivers/fsl_qtmr.c \ + $(MCUX_SDK_DIR)/drivers/qtmr_1/fsl_qtmr.c \ $(MCU_DIR)/drivers/fsl_romapi.c + +INC_HAL_IMX += -I$(TOP)/$(MCUX_SDK_DIR)/drivers/qtmr_1 endif # If not empty, then it is 10xx. @@ -171,24 +197,42 @@ APPLICATION_ADDR := 0x3000C000 endif ifeq ($(MCU_SERIES), MIMXRT1176) -INC += -I$(TOP)/$(MCU_DIR)/drivers/cm7 - SRC_HAL_IMX_C += \ $(MCU_DIR)/drivers/cm7/fsl_cache.c \ $(MCU_DIR)/drivers/fsl_dcdc.c \ $(MCU_DIR)/drivers/fsl_pmu.c \ - $(MCU_DIR)/drivers/fsl_common_arm.c \ $(MCU_DIR)/drivers/fsl_anatop_ai.c \ - $(MCU_DIR)/drivers/fsl_caam.c \ - $(MCU_DIR)/drivers/fsl_lpadc.c \ - $(MCU_DIR)/drivers/fsl_mu.c + $(MCU_DIR)/drivers/fsl_soc_src.c \ + $(MCU_DIR)/drivers/fsl_gpc.c \ + $(MCUX_SDK_DIR)/drivers/caam/fsl_caam.c \ + $(MCUX_SDK_DIR)/drivers/lpadc/fsl_lpadc.c \ + $(MCUX_SDK_DIR)/drivers/mu/fsl_mu.c + +INC_HAL_IMX += \ + -I$(TOP)/$(MCU_DIR)/drivers/cm7 \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/caam \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/lpadc \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/mu + +CFLAGS += -DCACHE_MODE_WRITE_THROUGH=1 else SRC_HAL_IMX_C += \ - $(MCU_DIR)/drivers/fsl_adc.c \ - $(MCU_DIR)/drivers/fsl_cache.c \ - $(MCU_DIR)/drivers/fsl_trng.c + $(MCUX_SDK_DIR)/drivers/adc_12b1msps_sar/fsl_adc.c \ + $(MCUX_SDK_DIR)/drivers/cache/armv7-m7/fsl_cache.c \ + $(MCUX_SDK_DIR)/drivers/gpc_1/fsl_gpc.c \ + $(MCUX_SDK_DIR)/drivers/src/fsl_src.c \ + $(MCUX_SDK_DIR)/drivers/trng/fsl_trng.c + +INC_HAL_IMX += \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/adc_12b1msps_sar \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/cache/armv7-m7 \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/gpc_1 \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/src \ + -I$(TOP)/$(MCUX_SDK_DIR)/drivers/trng endif +INC += $(INC_HAL_IMX) + # C source files SRC_C += \ board_init.c \ @@ -371,8 +415,6 @@ CFLAGS += \ -DMICROPY_HW_FLASH_SIZE=$(MICROPY_HW_FLASH_SIZE) \ -DMICROPY_HW_SDRAM_AVAIL=$(MICROPY_HW_SDRAM_AVAIL) \ -DMICROPY_HW_SDRAM_SIZE=$(MICROPY_HW_SDRAM_SIZE) \ - -DSPI_RETRY_TIMES=1000000 \ - -DUART_RETRY_TIMES=1000000 \ -DXIP_BOOT_HEADER_ENABLE=1 \ -DXIP_EXTERNAL_FLASH=1 \ -fdata-sections \ diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h index 59a5c4fa69f..bf9157e9c66 100644 --- a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h @@ -21,14 +21,14 @@ #define MICROPY_HW_UART_INDEX { 1, 3, 2, 6, 8 } #define IOMUX_TABLE_UART \ - { IOMUXC_GPIO_AD_B0_12_LPUART1_TX }, { IOMUXC_GPIO_AD_B0_13_LPUART1_RX }, \ - { IOMUXC_GPIO_AD_B1_02_LPUART2_TX }, { IOMUXC_GPIO_AD_B1_03_LPUART2_RX }, \ - { IOMUXC_GPIO_AD_B1_06_LPUART3_TX }, { IOMUXC_GPIO_AD_B1_07_LPUART3_RX }, \ + { IOMUXC_GPIO_AD_B0_12_LPUART1_TXD }, { IOMUXC_GPIO_AD_B0_13_LPUART1_RXD }, \ + { IOMUXC_GPIO_AD_B1_02_LPUART2_TXD }, { IOMUXC_GPIO_AD_B1_03_LPUART2_RXD }, \ + { IOMUXC_GPIO_AD_B1_06_LPUART3_TXD }, { IOMUXC_GPIO_AD_B1_07_LPUART3_RXD }, \ { 0 }, { 0 }, \ { 0 }, { 0 }, \ - { IOMUXC_GPIO_AD_B0_02_LPUART6_TX }, { IOMUXC_GPIO_AD_B0_03_LPUART6_RX }, \ + { IOMUXC_GPIO_AD_B0_02_LPUART6_TXD }, { IOMUXC_GPIO_AD_B0_03_LPUART6_RXD }, \ { 0 }, { 0 }, \ - { IOMUXC_GPIO_AD_B1_10_LPUART8_TX }, { IOMUXC_GPIO_AD_B1_11_LPUART8_RX }, + { IOMUXC_GPIO_AD_B1_10_LPUART8_TXD }, { IOMUXC_GPIO_AD_B1_11_LPUART8_RXD }, #define IOMUX_TABLE_UART_CTS_RTS \ { IOMUXC_GPIO_AD_B0_14_LPUART1_CTS_B }, { IOMUXC_GPIO_AD_B0_15_LPUART1_RTS_B }, \ @@ -111,22 +111,22 @@ } // --- SEMC --- // -#define MIMXRT_IOMUXC_SEMC_DATA00 IOMUXC_GPIO_EMC_00_SEMC_DATA00 -#define MIMXRT_IOMUXC_SEMC_DATA01 IOMUXC_GPIO_EMC_01_SEMC_DATA01 -#define MIMXRT_IOMUXC_SEMC_DATA02 IOMUXC_GPIO_EMC_02_SEMC_DATA02 -#define MIMXRT_IOMUXC_SEMC_DATA03 IOMUXC_GPIO_EMC_03_SEMC_DATA03 -#define MIMXRT_IOMUXC_SEMC_DATA04 IOMUXC_GPIO_EMC_04_SEMC_DATA04 -#define MIMXRT_IOMUXC_SEMC_DATA05 IOMUXC_GPIO_EMC_05_SEMC_DATA05 -#define MIMXRT_IOMUXC_SEMC_DATA06 IOMUXC_GPIO_EMC_06_SEMC_DATA06 -#define MIMXRT_IOMUXC_SEMC_DATA07 IOMUXC_GPIO_EMC_07_SEMC_DATA07 -#define MIMXRT_IOMUXC_SEMC_DATA08 IOMUXC_GPIO_EMC_30_SEMC_DATA08 -#define MIMXRT_IOMUXC_SEMC_DATA09 IOMUXC_GPIO_EMC_31_SEMC_DATA09 -#define MIMXRT_IOMUXC_SEMC_DATA10 IOMUXC_GPIO_EMC_32_SEMC_DATA10 -#define MIMXRT_IOMUXC_SEMC_DATA11 IOMUXC_GPIO_EMC_33_SEMC_DATA11 -#define MIMXRT_IOMUXC_SEMC_DATA12 IOMUXC_GPIO_EMC_34_SEMC_DATA12 -#define MIMXRT_IOMUXC_SEMC_DATA13 IOMUXC_GPIO_EMC_35_SEMC_DATA13 -#define MIMXRT_IOMUXC_SEMC_DATA14 IOMUXC_GPIO_EMC_36_SEMC_DATA14 -#define MIMXRT_IOMUXC_SEMC_DATA15 IOMUXC_GPIO_EMC_37_SEMC_DATA15 +#define MIMXRT_IOMUXC_SEMC_DATA00 IOMUXC_GPIO_EMC_00_SEMC_DA00 +#define MIMXRT_IOMUXC_SEMC_DATA01 IOMUXC_GPIO_EMC_01_SEMC_DA01 +#define MIMXRT_IOMUXC_SEMC_DATA02 IOMUXC_GPIO_EMC_02_SEMC_DA02 +#define MIMXRT_IOMUXC_SEMC_DATA03 IOMUXC_GPIO_EMC_03_SEMC_DA03 +#define MIMXRT_IOMUXC_SEMC_DATA04 IOMUXC_GPIO_EMC_04_SEMC_DA04 +#define MIMXRT_IOMUXC_SEMC_DATA05 IOMUXC_GPIO_EMC_05_SEMC_DA05 +#define MIMXRT_IOMUXC_SEMC_DATA06 IOMUXC_GPIO_EMC_06_SEMC_DA06 +#define MIMXRT_IOMUXC_SEMC_DATA07 IOMUXC_GPIO_EMC_07_SEMC_DA07 +#define MIMXRT_IOMUXC_SEMC_DATA08 IOMUXC_GPIO_EMC_30_SEMC_DA08 +#define MIMXRT_IOMUXC_SEMC_DATA09 IOMUXC_GPIO_EMC_31_SEMC_DA09 +#define MIMXRT_IOMUXC_SEMC_DATA10 IOMUXC_GPIO_EMC_32_SEMC_DA10 +#define MIMXRT_IOMUXC_SEMC_DATA11 IOMUXC_GPIO_EMC_33_SEMC_DA11 +#define MIMXRT_IOMUXC_SEMC_DATA12 IOMUXC_GPIO_EMC_34_SEMC_DA12 +#define MIMXRT_IOMUXC_SEMC_DATA13 IOMUXC_GPIO_EMC_35_SEMC_DA13 +#define MIMXRT_IOMUXC_SEMC_DATA14 IOMUXC_GPIO_EMC_36_SEMC_DA14 +#define MIMXRT_IOMUXC_SEMC_DATA15 IOMUXC_GPIO_EMC_37_SEMC_DA15 #define MIMXRT_IOMUXC_SEMC_ADDR00 IOMUXC_GPIO_EMC_09_SEMC_ADDR00 #define MIMXRT_IOMUXC_SEMC_ADDR01 IOMUXC_GPIO_EMC_10_SEMC_ADDR01 diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h index 627b1aa3b13..0ed32653f3a 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h @@ -23,14 +23,14 @@ #define MICROPY_HW_UART_INDEX { 0, 1, 2, 3, 8, 4 } #define IOMUX_TABLE_UART \ - { IOMUXC_GPIO_AD_B0_12_LPUART1_TX }, { IOMUXC_GPIO_AD_B0_13_LPUART1_RX }, \ - { IOMUXC_GPIO_AD_B1_02_LPUART2_TX }, { IOMUXC_GPIO_AD_B1_03_LPUART2_RX }, \ - { IOMUXC_GPIO_AD_B1_06_LPUART3_TX }, { IOMUXC_GPIO_AD_B1_07_LPUART3_RX }, \ - { IOMUXC_GPIO_B1_00_LPUART4_TX }, { IOMUXC_GPIO_B1_01_LPUART4_RX }, \ + { IOMUXC_GPIO_AD_B0_12_LPUART1_TXD }, { IOMUXC_GPIO_AD_B0_13_LPUART1_RXD }, \ + { IOMUXC_GPIO_AD_B1_02_LPUART2_TXD }, { IOMUXC_GPIO_AD_B1_03_LPUART2_RXD }, \ + { IOMUXC_GPIO_AD_B1_06_LPUART3_TXD }, { IOMUXC_GPIO_AD_B1_07_LPUART3_RXD }, \ + { IOMUXC_GPIO_B1_00_LPUART4_TXD }, { IOMUXC_GPIO_B1_01_LPUART4_RXD }, \ { 0 }, { 0 }, \ { 0 }, { 0 }, \ { 0 }, { 0 }, \ - { IOMUXC_GPIO_AD_B1_10_LPUART8_TX }, { IOMUXC_GPIO_AD_B1_11_LPUART8_RX }, + { IOMUXC_GPIO_AD_B1_10_LPUART8_TXD }, { IOMUXC_GPIO_AD_B1_11_LPUART8_RXD }, #define IOMUX_TABLE_UART_CTS_RTS \ { IOMUXC_GPIO_AD_B0_14_LPUART1_CTS_B }, { IOMUXC_GPIO_AD_B0_15_LPUART1_RTS_B }, \ @@ -98,13 +98,13 @@ #define I2S_GPIO_MAP \ { \ - I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), /* pin J4 09 */ \ - I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), /* pin J4 11 */ \ - I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), /* pin J4 10 */ \ - I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), /* pin J4 12 */ \ - I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), /* pin J4 14 */ \ - I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), /* pin J4 15 */ \ - I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00) /* pin J4 13 */ \ + I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), /* pin J4 09 */ \ + I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), /* pin J4 11 */ \ + I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), /* pin J4 10 */ \ + I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), /* pin J4 12 */ \ + I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), /* pin J4 14 */ \ + I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), /* pin J4 15 */ \ + I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00) /* pin J4 13 */ \ } #define USDHC_DUMMY_PIN NULL, 0 @@ -140,22 +140,22 @@ { IOMUXC_GPIO_EMC_40_ENET_MDC, 0, 0xB0E9u }, // --- SEMC --- // -#define MIMXRT_IOMUXC_SEMC_DATA00 IOMUXC_GPIO_EMC_00_SEMC_DATA00 -#define MIMXRT_IOMUXC_SEMC_DATA01 IOMUXC_GPIO_EMC_01_SEMC_DATA01 -#define MIMXRT_IOMUXC_SEMC_DATA02 IOMUXC_GPIO_EMC_02_SEMC_DATA02 -#define MIMXRT_IOMUXC_SEMC_DATA03 IOMUXC_GPIO_EMC_03_SEMC_DATA03 -#define MIMXRT_IOMUXC_SEMC_DATA04 IOMUXC_GPIO_EMC_04_SEMC_DATA04 -#define MIMXRT_IOMUXC_SEMC_DATA05 IOMUXC_GPIO_EMC_05_SEMC_DATA05 -#define MIMXRT_IOMUXC_SEMC_DATA06 IOMUXC_GPIO_EMC_06_SEMC_DATA06 -#define MIMXRT_IOMUXC_SEMC_DATA07 IOMUXC_GPIO_EMC_07_SEMC_DATA07 -#define MIMXRT_IOMUXC_SEMC_DATA08 IOMUXC_GPIO_EMC_30_SEMC_DATA08 -#define MIMXRT_IOMUXC_SEMC_DATA09 IOMUXC_GPIO_EMC_31_SEMC_DATA09 -#define MIMXRT_IOMUXC_SEMC_DATA10 IOMUXC_GPIO_EMC_32_SEMC_DATA10 -#define MIMXRT_IOMUXC_SEMC_DATA11 IOMUXC_GPIO_EMC_33_SEMC_DATA11 -#define MIMXRT_IOMUXC_SEMC_DATA12 IOMUXC_GPIO_EMC_34_SEMC_DATA12 -#define MIMXRT_IOMUXC_SEMC_DATA13 IOMUXC_GPIO_EMC_35_SEMC_DATA13 -#define MIMXRT_IOMUXC_SEMC_DATA14 IOMUXC_GPIO_EMC_36_SEMC_DATA14 -#define MIMXRT_IOMUXC_SEMC_DATA15 IOMUXC_GPIO_EMC_37_SEMC_DATA15 +#define MIMXRT_IOMUXC_SEMC_DATA00 IOMUXC_GPIO_EMC_00_SEMC_DA00 +#define MIMXRT_IOMUXC_SEMC_DATA01 IOMUXC_GPIO_EMC_01_SEMC_DA01 +#define MIMXRT_IOMUXC_SEMC_DATA02 IOMUXC_GPIO_EMC_02_SEMC_DA02 +#define MIMXRT_IOMUXC_SEMC_DATA03 IOMUXC_GPIO_EMC_03_SEMC_DA03 +#define MIMXRT_IOMUXC_SEMC_DATA04 IOMUXC_GPIO_EMC_04_SEMC_DA04 +#define MIMXRT_IOMUXC_SEMC_DATA05 IOMUXC_GPIO_EMC_05_SEMC_DA05 +#define MIMXRT_IOMUXC_SEMC_DATA06 IOMUXC_GPIO_EMC_06_SEMC_DA06 +#define MIMXRT_IOMUXC_SEMC_DATA07 IOMUXC_GPIO_EMC_07_SEMC_DA07 +#define MIMXRT_IOMUXC_SEMC_DATA08 IOMUXC_GPIO_EMC_30_SEMC_DA08 +#define MIMXRT_IOMUXC_SEMC_DATA09 IOMUXC_GPIO_EMC_31_SEMC_DA09 +#define MIMXRT_IOMUXC_SEMC_DATA10 IOMUXC_GPIO_EMC_32_SEMC_DA10 +#define MIMXRT_IOMUXC_SEMC_DATA11 IOMUXC_GPIO_EMC_33_SEMC_DA11 +#define MIMXRT_IOMUXC_SEMC_DATA12 IOMUXC_GPIO_EMC_34_SEMC_DA12 +#define MIMXRT_IOMUXC_SEMC_DATA13 IOMUXC_GPIO_EMC_35_SEMC_DA13 +#define MIMXRT_IOMUXC_SEMC_DATA14 IOMUXC_GPIO_EMC_36_SEMC_DA14 +#define MIMXRT_IOMUXC_SEMC_DATA15 IOMUXC_GPIO_EMC_37_SEMC_DA15 #define MIMXRT_IOMUXC_SEMC_ADDR00 IOMUXC_GPIO_EMC_09_SEMC_ADDR00 #define MIMXRT_IOMUXC_SEMC_ADDR01 IOMUXC_GPIO_EMC_10_SEMC_ADDR01 diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk index 7ea107b00df..1bc38ccbad7 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk @@ -19,4 +19,3 @@ USE_UF2_BOOTLOADER = 1 FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py -CFLAGS += -DSPI_RETRY_TIMES=1000000 diff --git a/ports/mimxrt/eth.c b/ports/mimxrt/eth.c index 1ac19b83d6d..1fbd9d3895f 100644 --- a/ports/mimxrt/eth.c +++ b/ports/mimxrt/eth.c @@ -397,9 +397,11 @@ void eth_init_0(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int ph enet_config.txAccelerConfig = kENET_TxAccelIpCheckEnabled | kENET_TxAccelProtoCheckEnabled; // Set interrupt enet_config.interrupt |= ENET_TX_INTERRUPT | ENET_RX_INTERRUPT; + // Set callback + enet_config.callback = eth_irq_handler; + enet_config.userData = (void *)self; ENET_Init(ENET, &g_handle, &enet_config, &buffConfig[0], hw_addr, source_clock); - ENET_SetCallback(&g_handle, eth_irq_handler, (void *)self); NVIC_SetPriority(ENET_IRQn, IRQ_PRI_PENDSV); ENET_EnableInterrupts(ENET, ENET_RX_INTERRUPT); ENET_ClearInterruptStatus(ENET, ENET_TX_INTERRUPT | ENET_RX_INTERRUPT | ENET_ERR_INTERRUPT); @@ -461,9 +463,11 @@ void eth_init_1(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int ph enet_config.txAccelerConfig = kENET_TxAccelIpCheckEnabled | kENET_TxAccelProtoCheckEnabled; // Set interrupt enet_config.interrupt = ENET_TX_INTERRUPT | ENET_RX_INTERRUPT; + // Set callback + enet_config.callback = eth_irq_handler; + enet_config.userData = (void *)self; ENET_Init(ENET_1, &g_handle_1, &enet_config, &buffConfig_1[0], hw_addr_1, source_clock); - ENET_SetCallback(&g_handle_1, eth_irq_handler, (void *)self); ENET_ClearInterruptStatus(ENET_1, ENET_TX_INTERRUPT | ENET_RX_INTERRUPT | ENET_ERR_INTERRUPT); ENET_EnableInterrupts(ENET_1, ENET_RX_INTERRUPT); ENET_ActiveRead(ENET_1); diff --git a/ports/mimxrt/machine_i2s.c b/ports/mimxrt/machine_i2s.c index 8553cb285b6..b57da4ad190 100644 --- a/ports/mimxrt/machine_i2s.c +++ b/ports/mimxrt/machine_i2s.c @@ -35,8 +35,14 @@ #include "fsl_iomuxc.h" #include "fsl_dmamux.h" #include "fsl_edma.h" +#include "fsl_common.h" #include "fsl_sai.h" +#ifndef FSL_FEATURE_SAI_FIFO_COUNTn +// Back-compat with mcux-sdk 2.11 +#define FSL_FEATURE_SAI_FIFO_COUNTn(x) FSL_FEATURE_SAI_FIFO_COUNT +#endif + // Notes on this port's specific implementation of I2S: // - the DMA callback is used to implement the asynchronous background operations, for non-blocking mode // - all 3 Modes of operation are implemented using the peripheral drivers in the NXP MCUXpresso SDK @@ -538,14 +544,14 @@ static bool i2s_init(machine_i2s_obj_t *self) { EDMA_PrepareTransfer(&transferConfig, self->dma_buffer_dcache_aligned, bytes_per_sample, (void *)destAddr, bytes_per_sample, - (FSL_FEATURE_SAI_FIFO_COUNT - saiConfig.fifo.fifoWatermark) * bytes_per_sample, + (FSL_FEATURE_SAI_FIFO_COUNTn(self->i2s_inst) - saiConfig.fifo.fifoWatermark) * bytes_per_sample, SIZEOF_DMA_BUFFER_IN_BYTES, kEDMA_MemoryToPeripheral); } else { // RX uint32_t srcAddr = SAI_RxGetDataRegisterAddress(self->i2s_inst, SAI_CHANNEL_0); EDMA_PrepareTransfer(&transferConfig, (void *)srcAddr, bytes_per_sample, self->dma_buffer_dcache_aligned, bytes_per_sample, - (FSL_FEATURE_SAI_FIFO_COUNT - saiConfig.fifo.fifoWatermark) * bytes_per_sample, + (FSL_FEATURE_SAI_FIFO_COUNTn(self->i2s_inst) - saiConfig.fifo.fifoWatermark) * bytes_per_sample, SIZEOF_DMA_BUFFER_IN_BYTES, kEDMA_PeripheralToMemory); } diff --git a/ports/mimxrt/machine_pwm.c b/ports/mimxrt/machine_pwm.c index b68521281ae..df76ed2b8bd 100644 --- a/ports/mimxrt/machine_pwm.c +++ b/ports/mimxrt/machine_pwm.c @@ -241,7 +241,9 @@ static void configure_flexpwm(machine_pwm_obj_t *self) { pwmConfig.pairOperation = kPWM_Independent; } pwmConfig.clockSource = kPWM_BusClock; + #if !defined(FSL_FEATURE_PWM_HAS_NO_WAITEN) || (!FSL_FEATURE_PWM_HAS_NO_WAITEN) pwmConfig.enableWait = false; + #endif pwmConfig.initializationControl = self->sync ? kPWM_Initialize_MasterSync : kPWM_Initialize_LocalSync; if (PWM_Init(self->instance, self->submodule, &pwmConfig) == kStatus_Fail) { diff --git a/ports/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index e6c51999198..b025e392612 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -156,7 +156,7 @@ void machine_rtc_start(void) { SNVS->HPCOMR |= SNVS_HPCOMR_NPSWA_EN_MASK; // Do a basic init. SNVS_LP_Init(SNVS); - #if FSL_FEATURE_SNVS_HAS_MULTIPLE_TAMPER + #if defined(FSL_FEATURE_SNVS_HAS_MULTIPLE_TAMPER) && (FSL_FEATURE_SNVS_HAS_MULTIPLE_TAMPER > 0) // Disable all external Tamper SNVS_LP_DisableAllExternalTamper(SNVS); #endif From 053aade74100d43b57d4302986076e85bedbde17 Mon Sep 17 00:00:00 2001 From: Yilin Sun Date: Fri, 31 Jan 2025 12:49:20 +1100 Subject: [PATCH 1137/2098] mimxrt/boards: Re-generate MIMXRT1052 clock config files. These were regenerated by the NXP Config tool for v2.11. The board_init update was needed to ensure CLOCK_SetMode() is run at the appropriate time during startup. Signed-off-by: Andrew Leech --- ports/mimxrt/board_init.c | 3 + ports/mimxrt/boards/MIMXRT1052_clock_config.c | 130 ++++++++++-------- ports/mimxrt/boards/MIMXRT1052_clock_config.h | 102 +++++++------- 3 files changed, 132 insertions(+), 103 deletions(-) diff --git a/ports/mimxrt/board_init.c b/ports/mimxrt/board_init.c index 88de27ea773..0643da0645e 100644 --- a/ports/mimxrt/board_init.c +++ b/ports/mimxrt/board_init.c @@ -52,6 +52,9 @@ void board_init(void) { SCB_EnableICache(); // Init clock BOARD_BootClockRUN(); + #if !defined(MIMXRT117x_SERIES) + CLOCK_SetMode(kCLOCK_ModeRun); + #endif SystemCoreClockUpdate(); // Enable IOCON clock diff --git a/ports/mimxrt/boards/MIMXRT1052_clock_config.c b/ports/mimxrt/boards/MIMXRT1052_clock_config.c index bff43ae5e54..de65a3e5841 100644 --- a/ports/mimxrt/boards/MIMXRT1052_clock_config.c +++ b/ports/mimxrt/boards/MIMXRT1052_clock_config.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 NXP + * Copyright 2022 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause @@ -22,11 +22,11 @@ /* TEXT BELOW IS USED AS SETTING FOR TOOLS ************************************* !!GlobalInfo -product: Clocks v5.0 +product: Clocks v10.0 processor: MIMXRT1052xxxxB package_id: MIMXRT1052DVL6B mcu_data: ksdk2_0 -processor_version: 0.0.0 +processor_version: 0.12.10 board: IMXRT1050-EVKB * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/ @@ -40,8 +40,6 @@ board: IMXRT1050-EVKB /******************************************************************************* * Variables ******************************************************************************/ -/* System clock frequency. */ -extern uint32_t SystemCoreClock; /******************************************************************************* ************************ BOARD_InitBootClocks function ************************ @@ -64,7 +62,6 @@ called_from_default_init: true - {id: CLK_1M.outFreq, value: 1 MHz} - {id: CLK_24M.outFreq, value: 24 MHz} - {id: CSI_CLK_ROOT.outFreq, value: 12 MHz} -- {id: ENET1_TX_CLK.outFreq, value: 2.4 MHz} - {id: ENET_125M_CLK.outFreq, value: 2.4 MHz} - {id: ENET_25M_REF_CLK.outFreq, value: 1.2 MHz} - {id: FLEXIO1_CLK_ROOT.outFreq, value: 30 MHz} @@ -92,7 +89,7 @@ called_from_default_init: true - {id: SAI3_MCLK3.outFreq, value: 30 MHz} - {id: SEMC_CLK_ROOT.outFreq, value: 75 MHz} - {id: SPDIF0_CLK_ROOT.outFreq, value: 30 MHz} -- {id: TRACE_CLK_ROOT.outFreq, value: 352/3 MHz} +- {id: TRACE_CLK_ROOT.outFreq, value: 132 MHz} - {id: UART_CLK_ROOT.outFreq, value: 80 MHz} - {id: USDHC1_CLK_ROOT.outFreq, value: 198 MHz} - {id: USDHC2_CLK_ROOT.outFreq, value: 198 MHz} @@ -104,7 +101,7 @@ called_from_default_init: true - {id: CCM.LPSPI_PODF.scale, value: '5', locked: true} - {id: CCM.PERCLK_PODF.scale, value: '2', locked: true} - {id: CCM.SEMC_PODF.scale, value: '8'} -- {id: CCM.TRACE_PODF.scale, value: '3', locked: true} +- {id: CCM.TRACE_CLK_SEL.sel, value: CCM_ANALOG.PLL2_MAIN_CLK} - {id: CCM_ANALOG.PLL1_BYPASS.sel, value: CCM_ANALOG.PLL1} - {id: CCM_ANALOG.PLL1_PREDIV.scale, value: '1', locked: true} - {id: CCM_ANALOG.PLL1_VDIV.scale, value: '50', locked: true} @@ -125,31 +122,45 @@ called_from_default_init: true - {id: CCM_ANALOG.PLL4.denom, value: '50'} - {id: CCM_ANALOG.PLL4.div, value: '47'} - {id: CCM_ANALOG.PLL5.denom, value: '1'} -- {id: CCM_ANALOG.PLL5.div, value: '40'} +- {id: CCM_ANALOG.PLL5.div, value: '31', locked: true} - {id: CCM_ANALOG.PLL5.num, value: '0'} +- {id: CCM_ANALOG.PLL5_BYPASS.sel, value: CCM_ANALOG.PLL5_POST_DIV} +- {id: CCM_ANALOG.PLL5_POST_DIV.scale, value: '2'} +- {id: CCM_ANALOG.VIDEO_DIV.scale, value: '4'} - {id: CCM_ANALOG_PLL_ENET_POWERDOWN_CFG, value: 'Yes'} - {id: CCM_ANALOG_PLL_USB1_POWER_CFG, value: 'Yes'} +- {id: CCM_ANALOG_PLL_VIDEO_POWERDOWN_CFG, value: 'No'} sources: -- {id: XTALOSC24M.OSC.outFreq, value: 24 MHz, enabled: true} - {id: XTALOSC24M.RTC_OSC.outFreq, value: 32.768 kHz, enabled: true} * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/ /******************************************************************************* * Variables for BOARD_BootClockRUN configuration ******************************************************************************/ -const clock_arm_pll_config_t armPllConfig_BOARD_BootClockRUN = { - .loopDivider = 100, /* PLL loop divider, Fout = Fin * 50 */ - .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ +const clock_arm_pll_config_t armPllConfig_BOARD_BootClockRUN = +{ + .loopDivider = 100, /* PLL loop divider, Fout = Fin * 50 */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; -const clock_sys_pll_config_t sysPllConfig_BOARD_BootClockRUN = { - .loopDivider = 1, /* PLL loop divider, Fout = Fin * ( 20 + loopDivider*2 + numerator / denominator ) */ - .numerator = 0, /* 30 bit numerator of fractional loop divider */ - .denominator = 1, /* 30 bit denominator of fractional loop divider */ - .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ +const clock_sys_pll_config_t sysPllConfig_BOARD_BootClockRUN = +{ + .loopDivider = 1, /* PLL loop divider, Fout = Fin * ( 20 + loopDivider*2 + numerator / denominator ) */ + .numerator = 0, /* 30 bit numerator of fractional loop divider */ + .denominator = 1, /* 30 bit denominator of fractional loop divider */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; -const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN = { - .loopDivider = 0, /* PLL loop divider, Fout = Fin * 20 */ - .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ +const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN = +{ + .loopDivider = 0, /* PLL loop divider, Fout = Fin * 20 */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ +}; +const clock_video_pll_config_t videoPllConfig_BOARD_BootClockRUN = +{ + .loopDivider = 31, /* PLL loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .postDivider = 8, /* Divider after PLL */ + .numerator = 0, /* 30 bit numerator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .denominator = 1, /* 30 bit denominator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; /******************************************************************************* * Code for BOARD_BootClockRUN configuration @@ -213,10 +224,9 @@ void BOARD_BootClockRUN(void) { CLOCK_SetDiv(kCLOCK_Usdhc2Div, 1); /* Set Usdhc2 clock source. */ CLOCK_SetMux(kCLOCK_Usdhc2Mux, 0); -/* In SDK projects, SDRAM (configured by SEMC) will be initialized in either debug script or dcd. - * With this macro SKIP_SYSCLK_INIT, system pll (selected to be SEMC source clock in SDK projects) will be left - * unchanged. - * Note: If another clock source is selected for SEMC, user may want to avoid changing that clock as well.*/ + /* In SDK projects, SDRAM (configured by SEMC) will be initialized in either debug script or dcd. + * With this macro SKIP_SYSCLK_INIT, system pll (selected to be SEMC source clock in SDK projects) will be left unchanged. + * Note: If another clock source is selected for SEMC, user may want to avoid changing that clock as well.*/ #ifndef SKIP_SYSCLK_INIT /* Disable Semc clock gate. */ CLOCK_DisableClock(kCLOCK_Semc); @@ -227,10 +237,9 @@ void BOARD_BootClockRUN(void) { /* Set Semc clock source. */ CLOCK_SetMux(kCLOCK_SemcMux, 0); #endif -/* In SDK projects, external flash (configured by FLEXSPI) will be initialized by dcd. - * With this macro XIP_EXTERNAL_FLASH, usb1 pll (selected to be FLEXSPI clock source in SDK projects) will be left - * unchanged. - * Note: If another clock source is selected for FLEXSPI, user may want to avoid changing that clock as well.*/ + /* In SDK projects, external flash (configured by FLEXSPI) will be initialized by dcd. + * With this macro XIP_EXTERNAL_FLASH, usb1 pll (selected to be FLEXSPI clock source in SDK projects) will be left unchanged. + * Note: If another clock source is selected for FLEXSPI, user may want to avoid changing that clock as well.*/ #if !(defined(XIP_EXTERNAL_FLASH) && (XIP_EXTERNAL_FLASH == 1)) /* Disable Flexspi clock gate. */ CLOCK_DisableClock(kCLOCK_FlexSpi); @@ -257,9 +266,9 @@ void BOARD_BootClockRUN(void) { /* Disable TRACE clock gate. */ CLOCK_DisableClock(kCLOCK_Trace); /* Set TRACE_PODF. */ - CLOCK_SetDiv(kCLOCK_TraceDiv, 2); + CLOCK_SetDiv(kCLOCK_TraceDiv, 3); /* Set Trace clock source. */ - CLOCK_SetMux(kCLOCK_TraceMux, 2); + CLOCK_SetMux(kCLOCK_TraceMux, 0); /* Disable SAI1 clock gate. */ CLOCK_DisableClock(kCLOCK_Sai1); /* Set SAI1_CLK_PRED. */ @@ -351,10 +360,12 @@ void BOARD_BootClockRUN(void) { /* Init ARM PLL. */ CLOCK_InitArmPll(&armPllConfig_BOARD_BootClockRUN); /* In SDK projects, SDRAM (configured by SEMC) will be initialized in either debug script or dcd. - * With this macro SKIP_SYSCLK_INIT, system pll (selected to be SEMC source clock in SDK projects) will be left - * unchanged. Note: If another clock source is selected for SEMC, user may want to avoid changing that clock as - * well.*/ + * With this macro SKIP_SYSCLK_INIT, system pll (selected to be SEMC source clock in SDK projects) will be left unchanged. + * Note: If another clock source is selected for SEMC, user may want to avoid changing that clock as well.*/ #ifndef SKIP_SYSCLK_INIT + #if defined(XIP_BOOT_HEADER_DCD_ENABLE) && (XIP_BOOT_HEADER_DCD_ENABLE == 1) + #warning "SKIP_SYSCLK_INIT should be defined to keep system pll (selected to be SEMC source clock in SDK projects) unchanged." + #endif /* Init System PLL. */ CLOCK_InitSysPll(&sysPllConfig_BOARD_BootClockRUN); /* Init System pfd0. */ @@ -365,13 +376,10 @@ void BOARD_BootClockRUN(void) { CLOCK_InitSysPfd(kCLOCK_Pfd2, 24); /* Init System pfd3. */ CLOCK_InitSysPfd(kCLOCK_Pfd3, 16); - /* Disable pfd offset. */ - CCM_ANALOG->PLL_SYS &= ~CCM_ANALOG_PLL_SYS_PFD_OFFSET_EN_MASK; #endif /* In SDK projects, external flash (configured by FLEXSPI) will be initialized by dcd. - * With this macro XIP_EXTERNAL_FLASH, usb1 pll (selected to be FLEXSPI clock source in SDK projects) will be left - * unchanged. Note: If another clock source is selected for FLEXSPI, user may want to avoid changing that clock as - * well.*/ + * With this macro XIP_EXTERNAL_FLASH, usb1 pll (selected to be FLEXSPI clock source in SDK projects) will be left unchanged. + * Note: If another clock source is selected for FLEXSPI, user may want to avoid changing that clock as well.*/ #if !(defined(XIP_EXTERNAL_FLASH) && (XIP_EXTERNAL_FLASH == 1)) /* Init Usb1 PLL. */ CLOCK_InitUsb1Pll(&usb1PllConfig_BOARD_BootClockRUN); @@ -395,21 +403,30 @@ void BOARD_BootClockRUN(void) { CCM_ANALOG->MISC2 &= ~CCM_ANALOG_MISC2_AUDIO_DIV_MSB_MASK; /* Enable Audio PLL output. */ CCM_ANALOG->PLL_AUDIO |= CCM_ANALOG_PLL_AUDIO_ENABLE_MASK; - /* DeInit Video PLL. */ - CLOCK_DeinitVideoPll(); - /* Bypass Video PLL. */ - CCM_ANALOG->PLL_VIDEO |= CCM_ANALOG_PLL_VIDEO_BYPASS_MASK; - /* Set divider for Video PLL. */ - CCM_ANALOG->MISC2 = (CCM_ANALOG->MISC2 & (~CCM_ANALOG_MISC2_VIDEO_DIV_MASK)) | CCM_ANALOG_MISC2_VIDEO_DIV(0); - /* Enable Video PLL output. */ - CCM_ANALOG->PLL_VIDEO |= CCM_ANALOG_PLL_VIDEO_ENABLE_MASK; + /* Init Video PLL. */ + uint32_t pllVideo; + /* Disable Video PLL output before initial Video PLL. */ + CCM_ANALOG->PLL_VIDEO &= ~CCM_ANALOG_PLL_VIDEO_ENABLE_MASK; + /* Bypass PLL first */ + CCM_ANALOG->PLL_VIDEO = (CCM_ANALOG->PLL_VIDEO & (~CCM_ANALOG_PLL_VIDEO_BYPASS_CLK_SRC_MASK)) | + CCM_ANALOG_PLL_VIDEO_BYPASS_MASK | CCM_ANALOG_PLL_VIDEO_BYPASS_CLK_SRC(0); + CCM_ANALOG->PLL_VIDEO_NUM = CCM_ANALOG_PLL_VIDEO_NUM_A(0); + CCM_ANALOG->PLL_VIDEO_DENOM = CCM_ANALOG_PLL_VIDEO_DENOM_B(1); + pllVideo = (CCM_ANALOG->PLL_VIDEO & (~(CCM_ANALOG_PLL_VIDEO_DIV_SELECT_MASK | CCM_ANALOG_PLL_VIDEO_POWERDOWN_MASK))) | + CCM_ANALOG_PLL_VIDEO_ENABLE_MASK | CCM_ANALOG_PLL_VIDEO_DIV_SELECT(31); + pllVideo |= CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT(1); + CCM_ANALOG->MISC2 = (CCM_ANALOG->MISC2 & (~CCM_ANALOG_MISC2_VIDEO_DIV_MASK)) | CCM_ANALOG_MISC2_VIDEO_DIV(3); + CCM_ANALOG->PLL_VIDEO = pllVideo; + while ((CCM_ANALOG->PLL_VIDEO & CCM_ANALOG_PLL_VIDEO_LOCK_MASK) == 0) { + } + /* Disable bypass for Video PLL. */ + CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllVideo, 0); /* DeInit Enet PLL. */ CLOCK_DeinitEnetPll(); /* Bypass Enet PLL. */ CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllEnet, 1); /* Set Enet output divider. */ - CCM_ANALOG->PLL_ENET = - (CCM_ANALOG->PLL_ENET & (~CCM_ANALOG_PLL_ENET_DIV_SELECT_MASK)) | CCM_ANALOG_PLL_ENET_DIV_SELECT(1); + CCM_ANALOG->PLL_ENET = (CCM_ANALOG->PLL_ENET & (~CCM_ANALOG_PLL_ENET_DIV_SELECT_MASK)) | CCM_ANALOG_PLL_ENET_DIV_SELECT(1); /* Enable Enet output. */ CCM_ANALOG->PLL_ENET |= CCM_ANALOG_PLL_ENET_ENABLE_MASK; /* Enable Enet25M output. */ @@ -429,8 +446,7 @@ void BOARD_BootClockRUN(void) { /* Set per clock source. */ CLOCK_SetMux(kCLOCK_PerclkMux, 0); /* Set lvds1 clock source. */ - CCM_ANALOG->MISC1 = - (CCM_ANALOG->MISC1 & (~CCM_ANALOG_MISC1_LVDS1_CLK_SEL_MASK)) | CCM_ANALOG_MISC1_LVDS1_CLK_SEL(0); + CCM_ANALOG->MISC1 = (CCM_ANALOG->MISC1 & (~CCM_ANALOG_MISC1_LVDS1_CLK_SEL_MASK)) | CCM_ANALOG_MISC1_LVDS1_CLK_SEL(0); /* Set clock out1 divider. */ CCM->CCOSR = (CCM->CCOSR & (~CCM_CCOSR_CLKO1_DIV_MASK)) | CCM_CCOSR_CLKO1_DIV(0); /* Set clock out1 source. */ @@ -457,13 +473,19 @@ void BOARD_BootClockRUN(void) { IOMUXC_SetSaiMClkClockSource(IOMUXC_GPR, kIOMUXC_GPR_SAI3MClk3Sel, 0); /* Set MQS configuration. */ IOMUXC_MQSConfig(IOMUXC_GPR, kIOMUXC_MqsPwmOverSampleRate32, 0); - /* Set ENET Tx clock source. */ - IOMUXC_EnableMode(IOMUXC_GPR, kIOMUXC_GPR_ENET1RefClkMode, false); + /* Set ENET Ref clock source. */ + #if defined(IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK) + IOMUXC_GPR->GPR1 &= ~IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK; + #elif defined(IOMUXC_GPR_GPR1_ENET1_TX_CLK_DIR_MASK) + /* Backward compatibility for original bitfield name */ + IOMUXC_GPR->GPR1 &= ~IOMUXC_GPR_GPR1_ENET1_TX_CLK_DIR_MASK; + #else + #error "Neither IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK nor IOMUXC_GPR_GPR1_ENET1_TX_CLK_DIR_MASK is defined." + #endif /* defined(IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK) */ /* Set GPT1 High frequency reference clock source. */ IOMUXC_GPR->GPR5 &= ~IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT1_MASK; /* Set GPT2 High frequency reference clock source. */ IOMUXC_GPR->GPR5 &= ~IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_MASK; /* Set SystemCoreClock variable. */ SystemCoreClock = BOARD_BOOTCLOCKRUN_CORE_CLOCK; - CLOCK_SetMode(kCLOCK_ModeRun); } diff --git a/ports/mimxrt/boards/MIMXRT1052_clock_config.h b/ports/mimxrt/boards/MIMXRT1052_clock_config.h index 358a6f03b36..0e7febef35e 100644 --- a/ports/mimxrt/boards/MIMXRT1052_clock_config.h +++ b/ports/mimxrt/boards/MIMXRT1052_clock_config.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 NXP + * Copyright 2022 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause @@ -13,9 +13,9 @@ /******************************************************************************* * Definitions ******************************************************************************/ -#define BOARD_XTAL0_CLK_HZ 24000000U /*!< Board xtal0 frequency in Hz */ +#define BOARD_XTAL0_CLK_HZ 24000000U /*!< Board xtal0 frequency in Hz */ -#define BOARD_XTAL32K_CLK_HZ 32768U /*!< Board xtal32k frequency in Hz */ +#define BOARD_XTAL32K_CLK_HZ 32768U /*!< Board xtal32k frequency in Hz */ /******************************************************************************* ************************ BOARD_InitBootClocks function ************************ ******************************************************************************/ @@ -40,54 +40,55 @@ void BOARD_InitBootClocks(void); /******************************************************************************* * Definitions for BOARD_BootClockRUN configuration ******************************************************************************/ -#define BOARD_BOOTCLOCKRUN_CORE_CLOCK 600000000U /*!< Core clock frequency: 600000000Hz */ +#define BOARD_BOOTCLOCKRUN_CORE_CLOCK 600000000U /*!< Core clock frequency: 600000000Hz */ /* Clock outputs (values are in Hz): */ -#define BOARD_BOOTCLOCKRUN_AHB_CLK_ROOT 600000000UL -#define BOARD_BOOTCLOCKRUN_CAN_CLK_ROOT 40000000UL -#define BOARD_BOOTCLOCKRUN_CKIL_SYNC_CLK_ROOT 32768UL -#define BOARD_BOOTCLOCKRUN_CLKO1_CLK 0UL -#define BOARD_BOOTCLOCKRUN_CLKO2_CLK 0UL -#define BOARD_BOOTCLOCKRUN_CLK_1M 1000000UL -#define BOARD_BOOTCLOCKRUN_CLK_24M 24000000UL -#define BOARD_BOOTCLOCKRUN_CSI_CLK_ROOT 12000000UL -#define BOARD_BOOTCLOCKRUN_ENET1_TX_CLK 2400000UL -#define BOARD_BOOTCLOCKRUN_ENET_125M_CLK 2400000UL -#define BOARD_BOOTCLOCKRUN_ENET_25M_REF_CLK 1200000UL -#define BOARD_BOOTCLOCKRUN_FLEXIO1_CLK_ROOT 30000000UL -#define BOARD_BOOTCLOCKRUN_FLEXIO2_CLK_ROOT 30000000UL -#define BOARD_BOOTCLOCKRUN_FLEXSPI_CLK_ROOT 160000000UL -#define BOARD_BOOTCLOCKRUN_GPT1_IPG_CLK_HIGHFREQ 75000000UL -#define BOARD_BOOTCLOCKRUN_GPT2_IPG_CLK_HIGHFREQ 75000000UL -#define BOARD_BOOTCLOCKRUN_IPG_CLK_ROOT 150000000UL -#define BOARD_BOOTCLOCKRUN_LCDIF_CLK_ROOT 9642857UL -#define BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT 60000000UL -#define BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT 105600000UL -#define BOARD_BOOTCLOCKRUN_LVDS1_CLK 1200000000UL -#define BOARD_BOOTCLOCKRUN_MQS_MCLK 63529411UL -#define BOARD_BOOTCLOCKRUN_PERCLK_CLK_ROOT 75000000UL -#define BOARD_BOOTCLOCKRUN_PLL7_MAIN_CLK 24000000UL -#define BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI1_MCLK1 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI1_MCLK2 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI1_MCLK3 30000000UL -#define BOARD_BOOTCLOCKRUN_SAI2_CLK_ROOT 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI2_MCLK1 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI2_MCLK2 0UL -#define BOARD_BOOTCLOCKRUN_SAI2_MCLK3 30000000UL -#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 63529411UL -#define BOARD_BOOTCLOCKRUN_SAI3_MCLK2 0UL -#define BOARD_BOOTCLOCKRUN_SAI3_MCLK3 30000000UL -#define BOARD_BOOTCLOCKRUN_SEMC_CLK_ROOT 75000000UL -#define BOARD_BOOTCLOCKRUN_SPDIF0_CLK_ROOT 30000000UL -#define BOARD_BOOTCLOCKRUN_SPDIF0_EXTCLK_OUT 0UL -#define BOARD_BOOTCLOCKRUN_TRACE_CLK_ROOT 117333333UL -#define BOARD_BOOTCLOCKRUN_UART_CLK_ROOT 40000000UL -#define BOARD_BOOTCLOCKRUN_USBPHY1_CLK 0UL -#define BOARD_BOOTCLOCKRUN_USBPHY2_CLK 0UL -#define BOARD_BOOTCLOCKRUN_USDHC1_CLK_ROOT 198000000UL -#define BOARD_BOOTCLOCKRUN_USDHC2_CLK_ROOT 198000000UL +#define BOARD_BOOTCLOCKRUN_AHB_CLK_ROOT 600000000UL +#define BOARD_BOOTCLOCKRUN_CAN_CLK_ROOT 40000000UL +#define BOARD_BOOTCLOCKRUN_CKIL_SYNC_CLK_ROOT 32768UL +#define BOARD_BOOTCLOCKRUN_CLKO1_CLK 0UL +#define BOARD_BOOTCLOCKRUN_CLKO2_CLK 0UL +#define BOARD_BOOTCLOCKRUN_CLK_1M 1000000UL +#define BOARD_BOOTCLOCKRUN_CLK_24M 24000000UL +#define BOARD_BOOTCLOCKRUN_CSI_CLK_ROOT 12000000UL +#define BOARD_BOOTCLOCKRUN_ENET_125M_CLK 2400000UL +#define BOARD_BOOTCLOCKRUN_ENET_25M_REF_CLK 1200000UL +#define BOARD_BOOTCLOCKRUN_ENET_REF_CLK 0UL +#define BOARD_BOOTCLOCKRUN_ENET_TX_CLK 0UL +#define BOARD_BOOTCLOCKRUN_FLEXIO1_CLK_ROOT 30000000UL +#define BOARD_BOOTCLOCKRUN_FLEXIO2_CLK_ROOT 30000000UL +#define BOARD_BOOTCLOCKRUN_FLEXSPI_CLK_ROOT 160000000UL +#define BOARD_BOOTCLOCKRUN_GPT1_IPG_CLK_HIGHFREQ 75000000UL +#define BOARD_BOOTCLOCKRUN_GPT2_IPG_CLK_HIGHFREQ 75000000UL +#define BOARD_BOOTCLOCKRUN_IPG_CLK_ROOT 150000000UL +#define BOARD_BOOTCLOCKRUN_LCDIF_CLK_ROOT 67500000UL +#define BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT 60000000UL +#define BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT 105600000UL +#define BOARD_BOOTCLOCKRUN_LVDS1_CLK 1200000000UL +#define BOARD_BOOTCLOCKRUN_MQS_MCLK 63529411UL +#define BOARD_BOOTCLOCKRUN_PERCLK_CLK_ROOT 75000000UL +#define BOARD_BOOTCLOCKRUN_PLL7_MAIN_CLK 24000000UL +#define BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI1_MCLK1 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI1_MCLK2 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI1_MCLK3 30000000UL +#define BOARD_BOOTCLOCKRUN_SAI2_CLK_ROOT 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI2_MCLK1 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI2_MCLK2 0UL +#define BOARD_BOOTCLOCKRUN_SAI2_MCLK3 30000000UL +#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 63529411UL +#define BOARD_BOOTCLOCKRUN_SAI3_MCLK2 0UL +#define BOARD_BOOTCLOCKRUN_SAI3_MCLK3 30000000UL +#define BOARD_BOOTCLOCKRUN_SEMC_CLK_ROOT 75000000UL +#define BOARD_BOOTCLOCKRUN_SPDIF0_CLK_ROOT 30000000UL +#define BOARD_BOOTCLOCKRUN_SPDIF0_EXTCLK_OUT 0UL +#define BOARD_BOOTCLOCKRUN_TRACE_CLK_ROOT 132000000UL +#define BOARD_BOOTCLOCKRUN_UART_CLK_ROOT 80000000UL +#define BOARD_BOOTCLOCKRUN_USBPHY1_CLK 0UL +#define BOARD_BOOTCLOCKRUN_USBPHY2_CLK 0UL +#define BOARD_BOOTCLOCKRUN_USDHC1_CLK_ROOT 198000000UL +#define BOARD_BOOTCLOCKRUN_USDHC2_CLK_ROOT 198000000UL /*! @brief Arm PLL set for BOARD_BootClockRUN configuration. */ @@ -98,6 +99,9 @@ extern const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN; /*! @brief Sys PLL for BOARD_BootClockRUN configuration. */ extern const clock_sys_pll_config_t sysPllConfig_BOARD_BootClockRUN; +/*! @brief Video PLL set for BOARD_BootClockRUN configuration. + */ +extern const clock_video_pll_config_t videoPllConfig_BOARD_BootClockRUN; /******************************************************************************* * API for BOARD_BootClockRUN configuration From 72147c02c7e630505515746e0ac6ac31f34c143b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 12 Aug 2025 13:09:24 +0200 Subject: [PATCH 1138/2098] esp32/network_ppp: Stop polling if stream becomes None. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If while a polling operation is active the stream is removed we should stop polling data from that stream. This was already the intended behaviour, but implemented incorrectly. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 4 ++-- ports/esp32/network_ppp.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 12205521f67..15e3ba02920 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -145,8 +145,8 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } mp_int_t total_len = 0; - mp_obj_t stream = self->stream; - while (stream != mp_const_none) { + mp_obj_t stream; + while ((stream = self->stream) != mp_const_none) { uint8_t buf[256]; int err; mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 18e0c881688..97536267b87 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -153,8 +153,8 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } mp_int_t total_len = 0; - mp_obj_t stream = self->stream; - while (stream != mp_const_none) { + mp_obj_t stream; + while ((stream = self->stream) != mp_const_none) { uint8_t buf[256]; int err; mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); From 1df5ee12e880fcd5a5129c7f0c077abdf2387f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 12 Aug 2025 13:09:24 +0200 Subject: [PATCH 1139/2098] esp32/network_ppp: Stop polling if PPP was disconnected. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When disconnecting from PPP the modem sends a confirmation. This message is received, like all messages, through the poll() method. lwIP may then immediately call our status callback with code PPPERR_USER to indicate the connection was closed. Our callback then immediately proceeds to free the PCB. Thus, during each new iteration of the loop in poll() we must check if we haven't disconnected in the meantime to prevent calling the pppos_input_tcpip with a PCB that is now NULL. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 2 +- ports/esp32/network_ppp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 15e3ba02920..d7a1cf5e0ce 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -146,7 +146,7 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { mp_int_t total_len = 0; mp_obj_t stream; - while ((stream = self->stream) != mp_const_none) { + while (self->state >= STATE_ACTIVE && (stream = self->stream) != mp_const_none) { uint8_t buf[256]; int err; mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 97536267b87..725d2104888 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -154,7 +154,7 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { mp_int_t total_len = 0; mp_obj_t stream; - while ((stream = self->stream) != mp_const_none) { + while (self->state >= STATE_ACTIVE && (stream = self->stream) != mp_const_none) { uint8_t buf[256]; int err; mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); From b0fd0079f48bde7f12578823ef88c91f52757cff Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 19 Aug 2025 14:06:42 +0200 Subject: [PATCH 1140/2098] tests/micropython: Make tests behave in low memory condition. This commit changes the "viper_ptr*_store_boundary" tests to make them fail more gracefully in low memory conditions. The original version of the tests compiled viper code blocks on the fly when it needed them, making them fail at runtime on some boards that do not come with enough memory for this test. This clashes with "run-tests.py"'s ability to look for a particular signature to mark tests as skipped due to not enough memory. Now compiled code blocks are generated at the beginning of the test inside an appropriate exception handler. In case of a memory error when pre-compiling a code block, the running test exits reporting a low memory condition to the test runner. This allows to have clean test runs on all platforms when it comes to viper pointer tests. Signed-off-by: Alessandro Gatti --- .../micropython/viper_ptr16_store_boundary.py | 39 ++++++++------- .../viper_ptr16_store_boundary.py.exp | 26 ++++++---- .../micropython/viper_ptr32_store_boundary.py | 48 ++++++++++--------- .../viper_ptr32_store_boundary.py.exp | 26 ++++++---- .../micropython/viper_ptr8_store_boundary.py | 34 ++++++++----- .../viper_ptr8_store_boundary.py.exp | 26 ++++++---- 6 files changed, 119 insertions(+), 80 deletions(-) diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary.py index 3501a05685e..7c774d4d1ca 100644 --- a/tests/micropython/viper_ptr16_store_boundary.py +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -6,15 +6,12 @@ def set{off}(dest: ptr16): saved = dest dest[{off}] = {val} assert int(saved) == int(dest) -set{off}(buffer) -print(hex(get_index(buffer, {off}))) """ BIT_THRESHOLDS = (5, 8, 11, 12) SIZE = 2 MASK = (1 << (8 * SIZE)) - 1 - next_int = 1 test_buffer = bytearray(SIZE) @@ -32,6 +29,10 @@ def next_value() -> uint: return output & MASK +def get_index(src, i): + return src[i * SIZE] + (src[(i * SIZE) + 1] << 8) + + @micropython.viper def set_index(dest: ptr16, i: int, val: uint): saved = dest @@ -39,22 +40,24 @@ def set_index(dest: ptr16, i: int, val: uint): assert int(saved) == int(dest) -def get_index(src, i): - return src[i * SIZE] + (src[(i * SIZE) + 1] << 8) +try: + buffer = bytearray((((1 << max(BIT_THRESHOLDS)) // 1024) + 1) * 1024) + + for bit in BIT_THRESHOLDS: + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + exec(SET_TEMPLATE.format(off=(offset + index) // SIZE, val=next_value())) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit -buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) for bit in BIT_THRESHOLDS: print("---", bit) - pre, idx, post = ( - (((1 << bit) - (2 * SIZE)) // SIZE), - (((1 << bit) - (1 * SIZE)) // SIZE), - ((1 << bit) // SIZE), - ) - set_index(buffer, pre, next_value()) - set_index(buffer, idx, next_value()) - set_index(buffer, post, next_value()) - print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=next_value())) - exec(SET_TEMPLATE.format(off=idx, val=next_value())) - exec(SET_TEMPLATE.format(off=post, val=next_value())) + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + globals()["set{}".format((offset + index) // SIZE)](buffer) + print(hex(get_index(buffer, (offset + index) // SIZE))) + for index in range(0, 3 * SIZE, SIZE): + set_index(buffer, (offset + index) // SIZE, next_value()) + print(hex(get_index(buffer, (offset + index) // SIZE))) diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp index 1c084da2d9c..007a50b3eda 100644 --- a/tests/micropython/viper_ptr16_store_boundary.py.exp +++ b/tests/micropython/viper_ptr16_store_boundary.py.exp @@ -1,20 +1,28 @@ --- 5 -0x1 0x102 0x203 +0x1 +0x102 +0x203 +0xc0d +0xd0e +0xe0f +--- 8 0x304 0x405 0x506 ---- 8 -0x607 0x708 0x809 -0x90a -0xa0b -0xb0c ---- 11 -0xc0d 0xd0e 0xe0f 0xf10 0x1011 0x1112 +--- 11 +0x607 +0x708 +0x809 +0x1213 +0x1314 +0x1415 --- 12 -0x1213 0x1314 0x1415 +0x90a +0xa0b +0xb0c 0x1516 0x1617 0x1718 diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary.py index 8c207278ec5..96ca74ad3ca 100644 --- a/tests/micropython/viper_ptr32_store_boundary.py +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -6,8 +6,6 @@ def set{off}(dest: ptr32): saved = dest dest[{off}] = {val} assert int(saved) == int(dest) -set{off}(buffer) -print(hex(get_index(buffer, {off}))) """ BIT_THRESHOLDS = (5, 8, 11, 12) @@ -31,13 +29,6 @@ def next_value() -> uint: return output & MASK -@micropython.viper -def set_index(dest: ptr32, i: int, val: uint): - saved = dest - dest[i] = val - assert int(saved) == int(dest) - - def get_index(src, i): return ( src[i * SIZE] @@ -47,18 +38,31 @@ def get_index(src, i): ) -buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) +@micropython.viper +def set_index(dest: ptr32, i: int, val: uint): + saved = dest + dest[i] = val + assert int(dest) == int(saved) + + +try: + buffer = bytearray((((1 << max(BIT_THRESHOLDS)) // 1024) + 1) * 1024) + + for bit in BIT_THRESHOLDS: + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + exec(SET_TEMPLATE.format(off=(offset + index) // SIZE, val=next_value())) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit + + for bit in BIT_THRESHOLDS: print("---", bit) - pre, idx, post = ( - (((1 << bit) - (2 * SIZE)) // SIZE), - (((1 << bit) - (1 * SIZE)) // SIZE), - ((1 << bit) // SIZE), - ) - set_index(buffer, pre, next_value()) - set_index(buffer, idx, next_value()) - set_index(buffer, post, next_value()) - print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=next_value())) - exec(SET_TEMPLATE.format(off=idx, val=next_value())) - exec(SET_TEMPLATE.format(off=post, val=next_value())) + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + globals()["set{}".format((offset + index) // SIZE)](buffer) + print(hex(get_index(buffer, (offset + index) // SIZE))) + for index in range(0, 3 * SIZE, SIZE): + set_index(buffer, (offset + index) // SIZE, next_value()) + print(hex(get_index(buffer, (offset + index) // SIZE))) diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp index 67b114d3358..7a9a5162474 100644 --- a/tests/micropython/viper_ptr32_store_boundary.py.exp +++ b/tests/micropython/viper_ptr32_store_boundary.py.exp @@ -1,20 +1,28 @@ --- 5 -0x1 0x102 0x10203 +0x1 +0x102 +0x10203 +0xa0b0c0d +0xb0c0d0e +0xc0d0e0f +--- 8 0x1020304 0x2030405 0x3040506 ---- 8 -0x4050607 0x5060708 0x6070809 -0x708090a -0x8090a0b -0x90a0b0c ---- 11 -0xa0b0c0d 0xb0c0d0e 0xc0d0e0f 0xd0e0f10 0xe0f1011 0xf101112 +--- 11 +0x4050607 +0x5060708 +0x6070809 +0x10111213 +0x11121314 +0x12131415 --- 12 -0x10111213 0x11121314 0x12131415 +0x708090a +0x8090a0b +0x90a0b0c 0x13141516 0x14151617 0x15161718 diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary.py index d3cba17e163..68b76fd598b 100644 --- a/tests/micropython/viper_ptr8_store_boundary.py +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -6,8 +6,6 @@ def set{off}(dest: ptr8): saved = dest dest[{off}] = {val} assert int(saved) == int(dest) -set{off}(buffer) -print(hex(get_index(buffer, {off}))) """ BIT_THRESHOLDS = (5, 8, 11, 12) @@ -31,6 +29,10 @@ def next_value() -> uint: return output & MASK +def get_index(src: ptr8, i: int): + return src[i] + + @micropython.viper def set_index(dest: ptr8, i: int, val: uint): saved = dest @@ -38,18 +40,24 @@ def set_index(dest: ptr8, i: int, val: uint): assert int(dest) == int(saved) -def get_index(src: ptr8, i: int): - return src[i] +try: + buffer = bytearray((((1 << max(BIT_THRESHOLDS)) // 1024) + 1) * 1024) + + for bit in BIT_THRESHOLDS: + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + exec(SET_TEMPLATE.format(off=(offset + index) // SIZE, val=next_value())) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit -buffer = bytearray(((1 << max(BIT_THRESHOLDS) + 1) // 1024) * 1024) for bit in BIT_THRESHOLDS: print("---", bit) - pre, idx, post = (((1 << bit) - (2 * SIZE)), ((1 << bit) - (1 * SIZE)), (1 << bit)) - set_index(buffer, pre, next_value()) - set_index(buffer, idx, next_value()) - set_index(buffer, post, next_value()) - print(hex(get_index(buffer, pre)), hex(get_index(buffer, idx)), hex(get_index(buffer, post))) - exec(SET_TEMPLATE.format(off=pre, val=next_value())) - exec(SET_TEMPLATE.format(off=idx, val=next_value())) - exec(SET_TEMPLATE.format(off=post, val=next_value())) + offset = (1 << bit) - (2 * SIZE) + for index in range(0, 3 * SIZE, SIZE): + globals()["set{}".format((offset + index) // SIZE)](buffer) + print(hex(get_index(buffer, (offset + index) // SIZE))) + for index in range(0, 3 * SIZE, SIZE): + set_index(buffer, (offset + index) // SIZE, next_value()) + print(hex(get_index(buffer, (offset + index) // SIZE))) diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp index 6b0f7ce13e8..621295d81a8 100644 --- a/tests/micropython/viper_ptr8_store_boundary.py.exp +++ b/tests/micropython/viper_ptr8_store_boundary.py.exp @@ -1,20 +1,28 @@ --- 5 -0x1 0x2 0x3 +0x1 +0x2 +0x3 +0xd +0xe +0xf +--- 8 0x4 0x5 0x6 ---- 8 -0x7 0x8 0x9 -0xa -0xb -0xc ---- 11 -0xd 0xe 0xf 0x10 0x11 0x12 +--- 11 +0x7 +0x8 +0x9 +0x13 +0x14 +0x15 --- 12 -0x13 0x14 0x15 +0xa +0xb +0xc 0x16 0x17 0x18 From 87099b57314f37e19f6551b0d575b620e1d29323 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Jul 2025 15:28:48 +0200 Subject: [PATCH 1141/2098] qemu/Makefile: Allow overriding floating point mode by boards. This commit lets board use a different floating point mode rather than the usual soft-float that was the original default for all QEMU-based boards. The configuration options are the same available in the "stm32" port. Boards can set "MICROPY_FLOAT_IMPL" to either "float", "double", or "none" to indicate which floating point mode they want, and optionally "SUPPORTS_HARDWARE_FP_SINGLE" or "SUPPORTS_HARDWARE_FP_DOUBLE" can be set to 1 to further indicate the hardware capabilities of the hardware floating point unit, if present. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 37 +++++++++++++++++++++++++++++++++---- ports/qemu/mpconfigport.h | 1 - 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index fc1e557974c..7784a266c30 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -36,6 +36,8 @@ MICROPY_HEAP_SIZE ?= 143360 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif +MICROPY_FLOAT_IMPL ?= float + # include py core make definitions include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk @@ -44,6 +46,20 @@ GIT_SUBMODULES += lib/berkeley-db-1.xx CFLAGS += -DMICROPY_HEAP_SIZE=$(MICROPY_HEAP_SIZE) +ifeq ($(MICROPY_FLOAT_IMPL),double) +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE +else ifeq ($(MICROPY_FLOAT_IMPL),float) +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT -fsingle-precision-constant +else +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_NONE +endif + +ifeq ($(SUPPORTS_HARDWARE_FP_SINGLE),1) +CFLAGS += -DMICROPY_HW_FPU=1 +else ifeq ($(SUPPORTS_HARDWARE_FP_DOUBLE),1) +CFLAGS += -DMICROPY_HW_FPU=1 +endif + ################################################################################ # ARM specific settings @@ -156,16 +172,29 @@ SRC_C += \ shared/runtime/stdout_helpers.c \ shared/runtime/sys_stdio_mphal.c \ -LIB_SRC_C += $(SRC_LIB_LIBM_C) -LIB_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C) +ifeq ($(MICROPY_FLOAT_IMPL),double) +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_C) +ifeq ($(SUPPORTS_HARDWARE_FP_DOUBLE),1) +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_SQRT_HW_C) +else +LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_SQRT_SW_C) +endif +else +LIBM_SRC_C += $(SRC_LIB_LIBM_C) +ifeq ($(SUPPORTS_HARDWARE_FP_SINGLE),1) +LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_HW_C) +else +LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C) +endif +endif OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_BOARD_O)) -OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(LIB_SRC_C) +SRC_QSTR += $(SRC_C) $(LIBM_SRC_C) ################################################################################ # Main targets diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 9c879f55dfe..6c6dd68535a 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -51,7 +51,6 @@ #define MICROPY_ENABLE_GC (1) #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_SYS_PLATFORM "qemu" #define MICROPY_PY_SYS_STDIO_BUFFER (0) From 19be404ad899701b655b2da78e57b34d7fb686be Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Jul 2025 15:36:28 +0200 Subject: [PATCH 1142/2098] qemu/arm: Add definition for the MPS2_AN500 machine. This commit introduces a new target for the QEMU port called "MPS2_AN500", an ARMv7-M machine with a Cortex-M7 CPU and single-/double-precision floating point unit. Signed-off-by: Alessandro Gatti --- ports/qemu/README.md | 1 + ports/qemu/boards/MPS2_AN500/mpconfigboard.mk | 15 +++++++++++++++ ports/qemu/mcu/arm/startup.c | 6 ++++++ 3 files changed, 22 insertions(+) create mode 100644 ports/qemu/boards/MPS2_AN500/mpconfigboard.mk diff --git a/ports/qemu/README.md b/ports/qemu/README.md index aab88ab5898..d8ef3ac551e 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -59,6 +59,7 @@ Available boards are: | ----------------- | ------------ | ------------------------ | | `MICROBIT` | `arm` | `microbit` | | `MPS2_AN385` | `arm` | `mps2-an385` | +| `MPS2_AN500` | `arm` | `mps2-an500` | | `NETDUINO2` | `arm` | `netduino2` | | `SABRELITE` | `arm` | `sabrelite` | | `VIRT_RV32` | `riscv32` | `virt` | diff --git a/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk new file mode 100644 index 00000000000..b7b54a80563 --- /dev/null +++ b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk @@ -0,0 +1,15 @@ +QEMU_ARCH = arm +QEMU_MACHINE = mps2-an500 + +CFLAGS += -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 +CFLAGS += -DQEMU_SOC_MPS2 +CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M7"' + +LDSCRIPT = mcu/arm/mps2.ld + +SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o + +MPY_CROSS_FLAGS += -march=armv7emdp + +MICROPY_FLOAT_IMPL = double +SUPPORTS_HARDWARE_FP_DOUBLE = 1 diff --git a/ports/qemu/mcu/arm/startup.c b/ports/qemu/mcu/arm/startup.c index a7e9efb9fba..56d246b1432 100644 --- a/ports/qemu/mcu/arm/startup.c +++ b/ports/qemu/mcu/arm/startup.c @@ -47,6 +47,12 @@ __attribute__((naked)) void Reset_Handler(void) { for (uint32_t *dest = &_sbss; dest < &_ebss;) { *dest++ = 0; } + #if MICROPY_HW_FPU && defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH == 7 + // initialise the FPU (full access to CP10 and CP11, as per B3.2.20) + *((volatile uint32_t *)0xE000ED88) |= 0x00F00000; + __asm volatile ("dsb"); + __asm volatile ("isb"); + #endif // jump to board initialisation void _start(void); _start(); From 64cac4690fad4df240056f5e15900d7ef5274a4c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 23 Jul 2025 13:25:31 +0200 Subject: [PATCH 1143/2098] qemu/mcu/arm/errorhandler: Add ARMv7-M debug registers. This commit extends the QEMU port's CPU error handler for the Arm target by printing out in detail the ARMv7-M debug registers if the firmware is compiled for such a target. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/errorhandler.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ports/qemu/mcu/arm/errorhandler.c b/ports/qemu/mcu/arm/errorhandler.c index a647c0e1536..278bc8d4e5c 100644 --- a/ports/qemu/mcu/arm/errorhandler.c +++ b/ports/qemu/mcu/arm/errorhandler.c @@ -63,6 +63,8 @@ static const char *EXCEPTION_NAMES_TABLE[] = { // R0-R15, PSR, Kind uintptr_t registers_copy[18] = { 0 }; +#define BIT(v, b) (((v) & (1U << (b))) != 0) + __attribute__((naked)) MP_NORETURN void exception_handler(uintptr_t kind) { // Save registers __asm volatile ( @@ -127,13 +129,33 @@ __attribute__((naked)) MP_NORETURN void exception_handler(uintptr_t kind) { } break; } - printf(" exception caught:\n"); + printf(" exception caught:\n\n"); + printf("CPU registers:\n"); printf("R0: %08X R1: %08X R2: %08X R3: %08X\n", registers_copy[0], registers_copy[1], registers_copy[2], registers_copy[3]); printf("R4: %08X R5: %08X R6: %08X R7: %08X\n", registers_copy[4], registers_copy[5], registers_copy[6], registers_copy[7]); printf("R8: %08X R9: %08X R10: %08X R11: %08X\n", registers_copy[8], registers_copy[9], registers_copy[10], registers_copy[11]); printf("R12: %08X R13: %08X R14: %08X R15: %08X\n", registers_copy[12], registers_copy[13], registers_copy[14], registers_copy[15]); printf("xPSR: %08X\n", registers_copy[16]); + #if __ARM_ARCH == 7 + uint32_t cfsr = *(volatile uint32_t *)0xE000ED28; + uint32_t hfsr = *(volatile uint32_t *)0xE000ED2C; + uint32_t dfsr = *(volatile uint32_t *)0xE000ED30; + uint32_t mmfar = *(volatile uint32_t *)0xE000ED34; + uint32_t bfar = *(volatile uint32_t *)0xE000ED38; + + printf("\nDebug registers:\n"); + printf("CFSR: %08lX HFSR: %08lX DFSR: %08lX MMFAR: %08lX BFAR: %08lX\n", cfsr, hfsr, dfsr, mmfar, bfar); + printf("CFSR.IACCVIOL: %d CFSR.DACCVIOL: %d CFSR.MUNSTKERR: %d CFSR.MSTKERR: %d\n", BIT(cfsr, 0), BIT(cfsr, 1), BIT(cfsr, 3), BIT(cfsr, 4)); + printf("CFSR.MLSPERR: %d CFSR.MMARVALID: %d CFSR.IBUSERR: %d CFSR.PRECISERR: %d\n", BIT(cfsr, 5), BIT(cfsr, 7), BIT(cfsr, 8), BIT(cfsr, 9)); + printf("CFSR.IMPRECISERR: %d CFSR.UNSTKERR: %d CFSR.STKERR: %d CFSR.LSPERR: %d\n", BIT(cfsr, 10), BIT(cfsr, 11), BIT(cfsr, 12), BIT(cfsr, 13)); + printf("CFSR.BFARVALID: %d CFSR.UNDEFINSTR: %d CFSR.INVSTATE: %d CFSR.INVPC: %d\n", BIT(cfsr, 15), BIT(cfsr, 16), BIT(cfsr, 17), BIT(cfsr, 18)); + printf("CFSR.NOCP: %d CFSR.UNALIGNED: %d CFSR.DIVBYZERO: %d\n", BIT(cfsr, 19), BIT(cfsr, 24), BIT(cfsr, 25)); + printf("HFSR.VECTTBL: %d HFSR.FORCED: %d HFSR.DEBUGEVT: %d\n", BIT(hfsr, 1), BIT(hfsr, 30), BIT(hfsr, 31)); + printf("DFSR.HALTED: %d DFSR.BKPT: %d DFSR.DWTTRAP: %d DFSR.VCATCH: %d\n", BIT(dfsr, 0), BIT(dfsr, 1), BIT(dfsr, 2), BIT(dfsr, 3)); + printf("DFSR.EXTERNAL: %d\n", BIT(dfsr, 4)); + #endif + for (;;) {} } From 6650506e06787cb742d11aee3f07ffeb5bbe0e82 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Jul 2025 14:21:17 +0200 Subject: [PATCH 1144/2098] tools/ci.sh: Extend Arm testing to include hardfp targets. This commit lets CI extend the testing scope of the QEMU Arm target, by letting it perform the usual battery of tests (interpreter and natmods) also on hardfp targets. The default board for Arm testing lacks hardware floating point support, so natmods weren't tested in that specific configuration. With the introduction of the "MPS_AN500" QEMU target, now this is made possible as said board emulates a Cortex-M7 machine with a single- and double-precision floating point unit. To reduce the impact on build times, the "ci_qemu_build_arm_thumb" CI step was split in two: "ci_qemu_build_arm_thumb_softfp" and "ci_qemu_build_arm_thumb_hardfp" - so hopefully those can run in parallel whenever possible. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_qemu.yml | 3 ++- tools/ci.sh | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index 85764577662..1284f11bbb3 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -26,7 +26,8 @@ jobs: ci_func: # names are functions in ci.sh - bigendian - sabrelite - - thumb + - thumb_softfp + - thumb_hardfp runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 diff --git a/tools/ci.sh b/tools/ci.sh index e165cb2cf3f..d9d77f69008 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -349,19 +349,30 @@ function ci_qemu_build_arm_sabrelite { make ${MAKEOPTS} -C ports/qemu BOARD=SABRELITE test_full } -function ci_qemu_build_arm_thumb { +function ci_qemu_build_arm_thumb_softfp { ci_qemu_build_arm_prepare - make ${MAKEOPTS} -C ports/qemu test_full + make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu test_full - # Test building native .mpy with all ARM-M architectures. + # Test building native .mpy with ARM-M softfp architectures. ci_native_mpy_modules_build armv6m ci_native_mpy_modules_build armv7m + + # Test running native .mpy with all ARM-M architectures. + make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv6m" + make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv7m" +} + +function ci_qemu_build_arm_thumb_hardfp { + ci_qemu_build_arm_prepare + make BOARD=MPS2_AN500 ${MAKEOPTS} -C ports/qemu test_full + + # Test building native .mpy with all ARM-M hardfp architectures. ci_native_mpy_modules_build armv7emsp ci_native_mpy_modules_build armv7emdp - # Test running native .mpy with armv6m and armv7m architectures. - make ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv6m" - make ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv7m" + # Test running native .mpy with all ARM-M hardfp architectures. + make BOARD=MPS2_AN500 ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv7emsp" + make BOARD=MPS2_AN500 ${MAKEOPTS} -C ports/qemu test_natmod RUN_TESTS_EXTRA="--arch armv7emdp" } function ci_qemu_build_rv32 { From 6c1d1f3ad4f5fb8139e9ba7517502bd441acd687 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 25 Aug 2025 23:18:13 +0200 Subject: [PATCH 1145/2098] qemu/mcu/arm/mps2.ld: Add .ARM.exidx section to the linkerscript. This commit fixes a linking issue on certain Arm toolchains where library code is compiled with exception support. If a library with exception support is included in the MicroPython build, the linker had no place to put the stack unwinding tables necessary to perform exception handling at runtime. This change adds a new section to the linkerscript (and therefore the final ELF file) where that data can be placed into. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/mps2.ld | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ports/qemu/mcu/arm/mps2.ld b/ports/qemu/mcu/arm/mps2.ld index dffefa3b002..1597a7de316 100644 --- a/ports/qemu/mcu/arm/mps2.ld +++ b/ports/qemu/mcu/arm/mps2.ld @@ -19,7 +19,15 @@ SECTIONS *(.rodata*) . = ALIGN(4); _etext = .; - _sidata = _etext; + } > RAM + + .ARM.exidx : AT ( _etext ) { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx*) + *(.gnu.linkonce.armexidx.*) + __exidx_end = .; + _sidata = __exidx_end; } > RAM .data : AT ( _sidata ) From c33a02fe6da766a9405fd14e23389b3c8b85d0b2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 26 Aug 2025 03:12:00 +0200 Subject: [PATCH 1146/2098] tests/run-tests.py: Enable Arm inlineasm FPU tests if possible. This commits lifts the unconditional restriction on inline assembler FPU tests for the Qemu platform, and makes said restriction conditional to the lack of an available floating point unit on the running platform. The Qemu platform supported only emulated machines that could target up to a Cortex-M3, so an ArmV7-M target that had no support for floating point. With the addition of MPS2_AN500 to the list of emulated targets the range was extended to cover up to Cortex-M7, so a floating point unit may possibly be available and thus able to run the FPU inlineasm tests. For that, the test runner was changed to detect the running architecture when checking the target capabilities; if the target reports its MicroPython architecture to be either "armv7emsp" or "armv7emdp" (providing single-precision and double-precision floating point unit support respectively) then the FPU-only inline tests are not put into the blocked tests list. Signed-off-by: Alessandro Gatti --- tests/run-tests.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index aeea0bad5e7..7a004281983 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -177,14 +177,6 @@ def open(self, path, mode): "thread/thread_lock3.py", "thread/thread_shared2.py", ), - "qemu": ( - # Skip tests that require Cortex-M4. - "inlineasm/thumb/asmfpaddsub.py", - "inlineasm/thumb/asmfpcmp.py", - "inlineasm/thumb/asmfpldrstr.py", - "inlineasm/thumb/asmfpmuldiv.py", - "inlineasm/thumb/asmfpsqrt.py", - ), "webassembly": ( "basics/string_format_modulo.py", # can't print nulls to stdout "basics/string_strip.py", # can't print nulls to stdout @@ -757,13 +749,14 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("inlineasm/thumb/asmbitops.py") skip_tests.add("inlineasm/thumb/asmconst.py") skip_tests.add("inlineasm/thumb/asmdiv.py") + skip_tests.add("inlineasm/thumb/asmit.py") + skip_tests.add("inlineasm/thumb/asmspecialregs.py") + if args.arch not in ("armv7emsp", "armv7emdp"): skip_tests.add("inlineasm/thumb/asmfpaddsub.py") skip_tests.add("inlineasm/thumb/asmfpcmp.py") skip_tests.add("inlineasm/thumb/asmfpldrstr.py") skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") skip_tests.add("inlineasm/thumb/asmfpsqrt.py") - skip_tests.add("inlineasm/thumb/asmit.py") - skip_tests.add("inlineasm/thumb/asmspecialregs.py") # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") From 8c47e446bf99393fd20c0416970f5c9a78b294f7 Mon Sep 17 00:00:00 2001 From: David Schneider Date: Tue, 5 Aug 2025 16:37:58 +0200 Subject: [PATCH 1147/2098] py/obj: Fix a comment regarding make_new slot. This was missed as part of the transition to make_new a mp_obj_type_t slot. See also: 94beeabd2ee179d587942046555833e022241f24 Signed-off-by: David Schneider --- py/obj.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/obj.h b/py/obj.h index 69d6e626db3..d756d237abb 100644 --- a/py/obj.h +++ b/py/obj.h @@ -814,7 +814,7 @@ typedef struct _mp_obj_full_type_t { #define MP_DEFINE_CONST_OBJ_TYPE_NARGS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, N, ...) MP_DEFINE_CONST_OBJ_TYPE_NARGS_##N // This macros is used to define a object type in ROM. -// Invoke as MP_DEFINE_CONST_OBJ_TYPE(_typename, _name, _flags, _make_new [, slot, func]*) +// Invoke as MP_DEFINE_CONST_OBJ_TYPE(_typename, _name, _flags, [, slot, func]*) // It uses the number of arguments to select which MP_DEFINE_CONST_OBJ_TYPE_* // macro to use based on the number of arguments. It works by shifting the // numeric values 12, 11, ... 0 by the number of arguments, such that the From ce834b8d661a6fde275ec64083b7e2f4f3efea4d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 26 Aug 2025 20:49:20 +0200 Subject: [PATCH 1148/2098] tools/mpy-tool.py: Allow dumping MPY segments into their own files. This commit lets "tools/mpy-tool.py" extract MPY segments into their own files, one file per segment. A pair of new command line arguments were added, namely "-e"/"--extract" that takes a filename prefix to use as a base for the generated files' name, and "--extract-only" that - combined with "--extract" - allows selecting which kinds of segment should be dumped to the filesystem. So, for example, assuming there's a file called "module.mpy", running "./mpy-tool.py --extract segments module.mpy" would yield a series of files with names like "segments_0_module.py_QSTR_module.py.bin", "segments_1_module.py_META__module_.bin", "segments_2_module.py_QSTR_function.bin", etc. In short the file name format is "____.bin", with being META, QSTR, OBJ, or CODE. Source file names and segment names will only contain characters in the range "a-zA-Z0-9_-." to avoid having output file names with unexpected characters. The "--extract-only" option can accept one or more kinds, separated by commas and treated as case insensitive strings. The supported kinds match what is currently handled by the "MPYSegment" class in "tools/mpy-tool.py": "META", "QSTR", "OBJ", and "CODE". The absence of this command line option implies dumping every segment found. If "--extract" is passed along with "--merge", dumping is performed after the merge process takes place, in order to dump all possible segments that match the requested segment kinds. Signed-off-by: Alessandro Gatti --- tools/mpy-tool.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 69d3a6c06f6..5c63f5be6d4 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1765,6 +1765,44 @@ def copy_section(file, offset, offset2): f.write(merged_mpy) +def extract_segments(compiled_modules, basename, kinds_arg): + import re + + kind_str = ("META", "QSTR", "OBJ", "CODE") + kinds = set() + if kinds_arg is not None: + for kind in kinds_arg.upper().split(","): + if kind in kind_str: + kinds.add(kind) + else: + raise Exception('unknown segment kind "%s"' % (kind,)) + segments = [] + for module in compiled_modules: + for segment in module.mpy_segments: + if not kinds or kind_str[segment.kind] in kinds: + segments.append((module.mpy_source_file, module.source_file.str, segment)) + count_len = len(str(len(segments))) + sanitiser = re.compile("[^a-zA-Z0-9_.-]") + for counter, entry in enumerate(segments): + file_name, source_file, segment = entry + output_name = ( + basename + + "_" + + str(counter).rjust(count_len, "0") + + "_" + + sanitiser.sub("_", source_file) + + "_" + + kind_str[segment.kind] + + "_" + + sanitiser.sub("_", str(segment.name)) + + ".bin" + ) + with open(file_name, "rb") as source: + with open(output_name, "wb") as output: + source.seek(segment.start) + output.write(source.read(segment.end - segment.start)) + + def main(args=None): global global_qstrs @@ -1781,6 +1819,14 @@ def main(args=None): cmd_parser.add_argument( "--merge", action="store_true", help="merge multiple .mpy files into one" ) + cmd_parser.add_argument( + "-e", "--extract", metavar="BASE", type=str, help="write segments into separate files" + ) + cmd_parser.add_argument( + "--extract-only", + metavar="KIND[,...]", + help="extract only segments of the given type (meta, qstr, obj, code)", + ) cmd_parser.add_argument("-q", "--qstr-header", help="qstr header file to freeze against") cmd_parser.add_argument( "-mlongint-impl", @@ -1848,6 +1894,9 @@ def main(args=None): if args.merge: merge_mpy(compiled_modules, args.output) + if args.extract: + extract_segments(compiled_modules, args.extract, args.extract_only) + if __name__ == "__main__": main() From 54787dc37b5c68b12b495deede84bf28530d4f63 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 27 Aug 2025 00:00:59 -0400 Subject: [PATCH 1149/2098] tools/ci.sh: Skip unreliable thread/thread_gc1.py test in Github CI. The `thread/thread_gc1.py` test is a constant source of spurious failures in Github CI. This commit adds it to the list of tests skipped when running on Github CI using either macos, qemu_riscv64, qemu_mips, or qemu_arm, to help reduce the overall false positive rate and improve the predictive value of the test fail indication. Signed-off-by: Anson Mansfield --- tools/ci.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index d9d77f69008..1dfe3d66e86 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -810,7 +810,8 @@ function ci_unix_macos_run_tests { # - float_parse and float_parse_doubleprec parse/print floats out by a few mantissa bits # - ffi_callback crashes for an unknown reason # - thread/stress_heap.py is flaky - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback|thread/stress_heap).py') + # - thread/thread_gc1.py is flaky + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback|thread/stress_heap|thread/thread_gc1).py') } function ci_unix_qemu_mips_setup { @@ -831,8 +832,9 @@ function ci_unix_qemu_mips_run_tests { # Issues with MIPS tests: # - thread/stress_aes.py takes around 50 seconds # - thread/stress_recurse.py is flaky + # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') } function ci_unix_qemu_arm_setup { @@ -854,8 +856,9 @@ function ci_unix_qemu_arm_run_tests { # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) # - thread/stress_aes.py takes around 70 seconds # - thread/stress_recurse.py is flaky + # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py|thread/stress_recurse.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py|thread/stress_recurse.py|thread/thread_gc1.py') } function ci_unix_qemu_riscv64_setup { @@ -876,8 +879,9 @@ function ci_unix_qemu_riscv64_run_tests { # Issues with RISCV-64 tests: # - thread/stress_aes.py takes around 140 seconds # - thread/stress_recurse.py is flaky + # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') } ######################################################################################## From f4f7fbf3dc8164ead907cb418773eef9bbba5644 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 26 Aug 2025 23:12:22 -0400 Subject: [PATCH 1150/2098] py/obj: Remove unused map new/free function declarations. These functions were removed in 6c9fca2 for v1.9.3. This commit removes their declarations as well. See-also: 6c9fca2aa911e31f6c1b48d3b950b4dc058473d4 Signed-off-by: Anson Mansfield --- py/obj.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/py/obj.h b/py/obj.h index d756d237abb..de740bf4ccf 100644 --- a/py/obj.h +++ b/py/obj.h @@ -502,9 +502,7 @@ static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) { void mp_map_init(mp_map_t *map, size_t n); void mp_map_init_fixed_table(mp_map_t *map, size_t n, const mp_obj_t *table); -mp_map_t *mp_map_new(size_t n); void mp_map_deinit(mp_map_t *map); -void mp_map_free(mp_map_t *map); mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind); void mp_map_clear(mp_map_t *map); void mp_map_dump(mp_map_t *map); From 1671977ca4be609679db36abc5594f1d76a71e23 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 15 Aug 2025 08:47:39 -0500 Subject: [PATCH 1151/2098] py/parsenum: Fix parsing LLONG_MIN in longlong configuration. Re-organize `mp_parse_num_integer()` (for longlong) slightly so that the most negative 64-bit integer can be parsed. Fixes issue #17932. Signed-off-by: Jeff Epler --- py/objint.c | 2 +- py/parsenum.c | 9 ++++++--- tests/basics/int_64_basics.py | 8 ++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/py/objint.c b/py/objint.c index 87d8a27852d..fd9ab106f92 100644 --- a/py/objint.c +++ b/py/objint.c @@ -247,7 +247,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co char sign = '\0'; if (num < 0) { - num = -num; + num = -(fmt_uint_t)num; sign = '-'; } diff --git a/py/parsenum.c b/py/parsenum.c index e18002306a2..4239f2dcd4d 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -64,7 +64,7 @@ typedef mp_int_t parsed_int_t; typedef unsigned long long parsed_int_t; #define PARSED_INT_MUL_OVERFLOW mp_mul_ull_overflow -#define PARSED_INT_FITS(I) ((I) <= (unsigned long long)LLONG_MAX) +#define PARSED_INT_FITS(I) ((I) <= (unsigned long long)LLONG_MAX + 1) #endif mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) { @@ -135,8 +135,11 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m have_ret_val: #else // The PARSED_INT_FITS check above ensures parsed_val won't overflow signed long long - long long signed_val = parsed_val; - if (neg) { + long long signed_val = -parsed_val; + if (!neg) { + if (signed_val == LLONG_MIN) { + goto overflow; + } signed_val = -signed_val; } ret_val = mp_obj_new_int_from_ll(signed_val); // Could be large or small int diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py index 2a161dac0ba..ef76793317e 100644 --- a/tests/basics/int_64_basics.py +++ b/tests/basics/int_64_basics.py @@ -151,3 +151,11 @@ print((1 << 48) << -6) except ValueError as e: print(e) + +# Test that the most extreme 64 bit integer values all parse with int() +print(int("-9223372036854775807")) +print(int("-9223372036854775808")) +print(int("9223372036854775807")) + +# Test that the most negative 64 bit integer can be formed via arithmetic +print(-9223372036854775807-1) From b6cd577cf6ea1de28092a7abe58613c66debd412 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 14 Aug 2025 10:22:11 -0500 Subject: [PATCH 1152/2098] py/mkrules.mk: Force ".pp" files to always rebuild. These files are only built on demand for developers, and it is a quick process. Without FORCE, a sequence like this would leave the developer with an outdated `main.pp` to inspect: make build-standard/main.pp touch input.h make build-standard/main.pp # Rebuilds now, wouldn't have before Signed-off-by: Jeff Epler --- py/mkrules.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mkrules.mk b/py/mkrules.mk index 3120066fd4f..6993a13a4f1 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -104,7 +104,7 @@ vpath %.cpp . $(TOP) $(USER_C_MODULES) $(BUILD)/%.o: %.cpp $(call compile_cxx) -$(BUILD)/%.pp: %.c +$(BUILD)/%.pp: %.c FORCE $(ECHO) "PreProcess $<" $(Q)$(CPP) $(CFLAGS) -Wp,-C,-dD,-dI -o $@ $< From 7a8ae72640bf15971e0dc87727a639281ffbcc02 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Aug 2025 11:02:04 +1000 Subject: [PATCH 1153/2098] tests/run-tests.py: Factor code for device shortcuts to a function. So it can be reused by other test runners. Signed-off-by: Damien George --- tests/run-tests.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 7a004281983..2d02e6b23c6 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -246,6 +246,17 @@ def platform_to_port(platform): return platform_to_port_map.get(platform, platform) +def convert_device_shortcut_to_real_device(device): + if device.startswith("a") and device[1:].isdigit(): + return "/dev/ttyACM" + device[1:] + elif device.startswith("u") and device[1:].isdigit(): + return "/dev/ttyUSB" + device[1:] + elif device.startswith("c") and device[1:].isdigit(): + return "COM" + device[1:] + else: + return device + + def get_test_instance(test_instance, baudrate, user, password): if test_instance.startswith("port:"): _, port = test_instance.split(":", 1) @@ -253,15 +264,9 @@ def get_test_instance(test_instance, baudrate, user, password): return None elif test_instance == "webassembly": return PyboardNodeRunner() - elif test_instance.startswith("a") and test_instance[1:].isdigit(): - port = "/dev/ttyACM" + test_instance[1:] - elif test_instance.startswith("u") and test_instance[1:].isdigit(): - port = "/dev/ttyUSB" + test_instance[1:] - elif test_instance.startswith("c") and test_instance[1:].isdigit(): - port = "COM" + test_instance[1:] else: # Assume it's a device path. - port = test_instance + port = convert_device_shortcut_to_real_device(test_instance) global pyboard sys.path.append(base_path("../tools")) From f62c614d3e5f8bdfa6a3e1265cd5af8dca00adb1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Aug 2025 11:02:39 +1000 Subject: [PATCH 1154/2098] tests/run-multitests.py: Change -i argument to -t. Back in commit 8978102f3595ae321484a6be44c1dcf25c8909a9 (see PR #16111) the `run-tests.py` script was changed to use an improved way of selecting the test instance, eg: $ ./run-tests.py -t a0 that would run on /dev/ttyACM0. This has been a very nice improvement, makes it easier to specify the target. This commit updates `run-multitests.py` to use the same scheme. It previously used `-i` but now that's changed to `-t`. Signed-off-by: Damien George --- tests/run-multitests.py | 44 +++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 4412d0fde7f..e5458ffe0d0 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -238,17 +238,8 @@ def wait_finished(self): class PyInstancePyboard(PyInstance): - @staticmethod - def map_device_shortcut(device): - if device[0] == "a" and device[1:].isdigit(): - return "/dev/ttyACM" + device[1:] - elif device[0] == "u" and device[1:].isdigit(): - return "/dev/ttyUSB" + device[1:] - else: - return device - def __init__(self, device): - device = self.map_device_shortcut(device) + device = device self.device = device self.pyb = pyboard.Pyboard(device) self.pyb.enter_raw_repl() @@ -562,16 +553,24 @@ def main(): cmd_parser = argparse.ArgumentParser( description="Run network tests for MicroPython", + epilog=( + run_tests_module.test_instance_epilog + + "Each instance arg can optionally have custom env provided, eg. ,ENV=VAR,ENV=VAR...\n" + ), formatter_class=argparse.RawTextHelpFormatter, ) cmd_parser.add_argument( "-s", "--show-output", action="store_true", help="show test output after running" ) cmd_parser.add_argument( - "-t", "--trace-output", action="store_true", help="trace test output while running" + "-c", "--trace-output", action="store_true", help="trace test output while running" ) cmd_parser.add_argument( - "-i", "--instance", action="append", default=[], help="instance(s) to run the tests on" + "-t", + "--test-instance", + action="append", + default=[], + help="instance(s) to run the tests on", ) cmd_parser.add_argument( "-p", @@ -586,14 +585,6 @@ def main(): default=run_tests_module.base_path("results"), help="directory for test results", ) - cmd_parser.epilog = ( - "Supported instance types:\r\n" - " -i pyb: physical device (eg. pyboard) on provided repl port.\n" - " -i micropython unix micropython instance, path customised with MICROPY_MICROPYTHON env.\n" - " -i cpython desktop python3 instance, path customised with MICROPY_CPYTHON3 env.\n" - " -i exec: custom program run on provided path.\n" - "Each instance arg can optionally have custom env provided, eg. ,ENV=VAR,ENV=VAR...\n" - ) cmd_parser.add_argument("files", nargs="+", help="input test files") cmd_args = cmd_parser.parse_args() @@ -606,22 +597,23 @@ def main(): instances_truth = [PyInstanceSubProcess([PYTHON_TRUTH]) for _ in range(max_instances)] instances_test = [] - for i in cmd_args.instance: + for i in cmd_args.test_instance: # Each instance arg is ,ENV=VAR,ENV=VAR... i = i.split(",") cmd = i[0] env = i[1:] if cmd.startswith("exec:"): instances_test.append(PyInstanceSubProcess([cmd[len("exec:") :]], env)) - elif cmd == "micropython": + elif cmd == "unix": instances_test.append(PyInstanceSubProcess([MICROPYTHON], env)) elif cmd == "cpython": instances_test.append(PyInstanceSubProcess([CPYTHON3], env)) - elif cmd.startswith("pyb:"): - instances_test.append(PyInstancePyboard(cmd[len("pyb:") :])) + elif cmd == "webassembly" or cmd.startswith("execpty:"): + print("unsupported instance string: {}".format(cmd), file=sys.stderr) + sys.exit(2) else: - print("unknown instance string: {}".format(cmd), file=sys.stderr) - sys.exit(1) + device = run_tests_module.convert_device_shortcut_to_real_device(cmd) + instances_test.append(PyInstancePyboard(device)) for _ in range(max_instances - len(instances_test)): instances_test.append(PyInstanceSubProcess([MICROPYTHON])) From bf185c37ed13e120383f2cbdf85e606e1e0a368a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Aug 2025 11:13:08 +1000 Subject: [PATCH 1155/2098] tests/run-natmodtests.py: Change -p/-d arguments to -t. Following the similar change to `run-tests.py` and `run-multitests.py`. What was previously, eg, `-p -d /dev/ttyACM0` is now `-t a0`. Signed-off-by: Damien George --- ports/qemu/Makefile | 2 +- tests/run-natmodtests.py | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 7784a266c30..d213c8fc03c 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -225,7 +225,7 @@ test_natmod: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && \ for natmod in btree deflate framebuf heapq random_basic re; do \ - ./run-natmodtests.py -p -d execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_EXTRA) extmod/$$natmod*.py; \ + ./run-natmodtests.py -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_EXTRA) extmod/$$natmod*.py; \ done $(BUILD)/firmware.elf: $(LDSCRIPT) $(OBJ) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index 55adb6049a3..50e8d127291 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -11,9 +11,6 @@ run_tests_module = __import__("run-tests") -sys.path.append("../tools") -import pyboard - # Paths for host executables CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/build-coverage/micropython") @@ -112,7 +109,7 @@ def run_script(self, script): output = self.pyb.exec_(script) output = output.replace(b"\r\n", b"\n") return output, None - except pyboard.PyboardError as er: + except run_tests_module.pyboard.PyboardError as er: return b"", er @@ -227,14 +224,16 @@ def run_tests(target_truth, target, args, resolved_arch): def main(): cmd_parser = argparse.ArgumentParser( - description="Run dynamic-native-module tests under MicroPython" - ) - cmd_parser.add_argument( - "-p", "--pyboard", action="store_true", help="run tests via pyboard.py" + description="Run dynamic-native-module tests under MicroPython", + epilog=run_tests_module.test_instance_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, ) cmd_parser.add_argument( - "-d", "--device", default="/dev/ttyACM0", help="the device for pyboard.py" + "-t", "--test-instance", default="unix", help="the MicroPython instance to test" ) + cmd_parser.add_argument("--baudrate", default=115200, help="baud rate of the serial device") + cmd_parser.add_argument("--user", default="micro", help="telnet login username") + cmd_parser.add_argument("--password", default="python", help="telnet login password") cmd_parser.add_argument( "-a", "--arch", choices=AVAILABLE_ARCHS, help="override native architecture of the target" ) @@ -256,10 +255,15 @@ def main(): target_truth = TargetSubprocess([CPYTHON3]) - if args.pyboard: - target = TargetPyboard(pyboard.Pyboard(args.device)) - else: + target = run_tests_module.get_test_instance( + args.test_instance, args.baudrate, args.user, args.password + ) + if target is None: + # Use the unix port of MicroPython. target = TargetSubprocess([MICROPYTHON]) + else: + # Use a remote target. + target = TargetPyboard(target) if hasattr(args, "arch") and args.arch is not None: target_arch = args.arch From dbbc7d96672bb44a04f27fef32231dd7340d7952 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Aug 2025 11:27:20 +1000 Subject: [PATCH 1156/2098] tests/run-perfbench.py: Change -p/-d arguments to -t. And the existing "-t" option is changed to "-m" (shorthand for the "--diff-time" option). Signed-off-by: Damien George --- tests/run-perfbench.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index cac2fee58f0..2a2b7b6c9dd 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -12,9 +12,6 @@ run_tests_module = __import__("run-tests") -sys.path.append("../tools") -import pyboard - prepare_script_for_target = run_tests_module.prepare_script_for_target # Paths for host executables @@ -47,12 +44,12 @@ def run_script_on_target(target, script): output = b"" err = None - if isinstance(target, pyboard.Pyboard): + if hasattr(target, "enter_raw_repl"): # Run via pyboard interface try: target.enter_raw_repl() output = target.exec_(script) - except pyboard.PyboardError as er: + except run_tests_module.pyboard.PyboardError as er: err = er else: # Run local executable @@ -125,7 +122,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): f.write(test_script) # Process script through mpy-cross if needed - if isinstance(target, pyboard.Pyboard) or args.via_mpy: + if hasattr(target, "enter_raw_repl") or args.via_mpy: crash, test_script_target = prepare_script_for_target(args, script_text=test_script) if crash: test_results.append((test_file, "fail", "preparation")) @@ -253,17 +250,17 @@ def compute_diff(file1, file2, diff_score): def main(): cmd_parser = argparse.ArgumentParser(description="Run benchmarks for MicroPython") cmd_parser.add_argument( - "-t", "--diff-time", action="store_true", help="diff time outputs from a previous run" + "-m", "--diff-time", action="store_true", help="diff time outputs from a previous run" ) cmd_parser.add_argument( "-s", "--diff-score", action="store_true", help="diff score outputs from a previous run" ) cmd_parser.add_argument( - "-p", "--pyboard", action="store_true", help="run tests via pyboard.py" - ) - cmd_parser.add_argument( - "-d", "--device", default="/dev/ttyACM0", help="the device for pyboard.py" + "-t", "--test-instance", default="unix", help="the MicroPython instance to test" ) + cmd_parser.add_argument("--baudrate", default=115200, help="baud rate of the serial device") + cmd_parser.add_argument("--user", default="micro", help="telnet login username") + cmd_parser.add_argument("--password", default="python", help="telnet login password") cmd_parser.add_argument("-a", "--average", default="8", help="averaging number") cmd_parser.add_argument( "--emit", default="bytecode", help="MicroPython emitter to use (bytecode or native)" @@ -295,15 +292,18 @@ def main(): M = int(args.M[0]) n_average = int(args.average) - if args.pyboard: - if not args.mpy_cross_flags: - args.mpy_cross_flags = "-march=armv7m" - target = pyboard.Pyboard(args.device) - target.enter_raw_repl() - else: + target = run_tests_module.get_test_instance( + args.test_instance, args.baudrate, args.user, args.password + ) + if target is None: + # Use the unix port of MicroPython. target = [MICROPYTHON, "-X", "emit=" + args.emit] if args.heapsize is not None: target.extend(["-X", "heapsize=" + args.heapsize]) + else: + # Use a remote target. + if not args.mpy_cross_flags: + args.mpy_cross_flags = "-march=armv7m" if len(args.files) == 0: tests_skip = ("benchrun.py",) @@ -324,7 +324,7 @@ def main(): test_results = run_benchmarks(args, target, N, M, n_average, tests) res = run_tests_module.create_test_report(args, test_results) - if isinstance(target, pyboard.Pyboard): + if hasattr(target, "exit_raw_repl"): target.exit_raw_repl() target.close() From af745a5982b5be41c8e74d12c70a3b281eb49328 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Aug 2025 13:33:02 +1000 Subject: [PATCH 1157/2098] tests/net_inet/tls_num_errors.py: Make alloc of emg-exc-buf optional. The unix port doesn't have `micropython.alloc_emergency_exception_buf()` but it's still possible to run and pass this test. So make that call optional. Signed-off-by: Damien George --- tests/net_inet/tls_num_errors.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/net_inet/tls_num_errors.py b/tests/net_inet/tls_num_errors.py index 34aa2bb4551..9c12114eab1 100644 --- a/tests/net_inet/tls_num_errors.py +++ b/tests/net_inet/tls_num_errors.py @@ -3,15 +3,21 @@ import socket, ssl, sys try: - from micropython import alloc_emergency_exception_buf, heap_lock, heap_unlock + from micropython import heap_lock, heap_unlock except: print("SKIP") raise SystemExit +try: + from micropython import alloc_emergency_exception_buf + + alloc_emergency_exception_buf(256) +except: + pass + # test with heap locked to see it switch to number-only error message def test(addr): - alloc_emergency_exception_buf(256) s = socket.socket() s.connect(addr) try: From 3274f0355b2df6928bafed8a6dd721c2fdfb65df Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 27 Aug 2025 13:31:18 +1000 Subject: [PATCH 1158/2098] tests/multi_net: Require SSL cert file to be available for test to run. These were missed in 2bba507148e8a751123257fa2b075c70d67e1160 because they didn't use the `os.stat` pattern. Signed-off-by: Damien George --- tests/multi_net/sslcontext_server_client.py | 12 ++++-------- tests/multi_net/sslcontext_server_client_ciphers.py | 12 ++++-------- tests/multi_net/sslcontext_verify_callback.py | 12 ++++-------- tests/multi_net/tls_dtls_server_client.py | 12 ++++-------- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/tests/multi_net/sslcontext_server_client.py b/tests/multi_net/sslcontext_server_client.py index 6516de53f7d..fd330cee78a 100644 --- a/tests/multi_net/sslcontext_server_client.py +++ b/tests/multi_net/sslcontext_server_client.py @@ -14,14 +14,10 @@ certfile = "ec_cert.der" keyfile = "ec_key.der" -try: - with open(certfile, "rb") as cf: - cert = cadata = cf.read() - with open(keyfile, "rb") as kf: - key = kf.read() -except OSError: - print("SKIP") - raise SystemExit +with open(certfile, "rb") as cf: + cert = cadata = cf.read() +with open(keyfile, "rb") as kf: + key = kf.read() # Server diff --git a/tests/multi_net/sslcontext_server_client_ciphers.py b/tests/multi_net/sslcontext_server_client_ciphers.py index 3334d9d9e4c..7017a6f5798 100644 --- a/tests/multi_net/sslcontext_server_client_ciphers.py +++ b/tests/multi_net/sslcontext_server_client_ciphers.py @@ -14,14 +14,10 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - with open(cafile, "rb") as f: - cadata = f.read() - with open(key, "rb") as f: - keydata = f.read() -except OSError: - print("SKIP") - raise SystemExit +with open(cafile, "rb") as f: + cadata = f.read() +with open(key, "rb") as f: + keydata = f.read() # Server diff --git a/tests/multi_net/sslcontext_verify_callback.py b/tests/multi_net/sslcontext_verify_callback.py index 74b97382453..80056a5868c 100644 --- a/tests/multi_net/sslcontext_verify_callback.py +++ b/tests/multi_net/sslcontext_verify_callback.py @@ -15,14 +15,10 @@ cert = cafile = "ec_cert.der" key = "ec_key.der" -try: - with open(cafile, "rb") as f: - cadata = f.read() - with open(key, "rb") as f: - key = f.read() -except OSError: - print("SKIP") - raise SystemExit +with open(cafile, "rb") as f: + cadata = f.read() +with open(key, "rb") as f: + key = f.read() def verify_callback(cert, depth): diff --git a/tests/multi_net/tls_dtls_server_client.py b/tests/multi_net/tls_dtls_server_client.py index a81c4cb2823..ec7d7de55fd 100644 --- a/tests/multi_net/tls_dtls_server_client.py +++ b/tests/multi_net/tls_dtls_server_client.py @@ -13,14 +13,10 @@ certfile = "ec_cert.der" keyfile = "ec_key.der" -try: - with open(certfile, "rb") as cf: - cert = cadata = cf.read() - with open(keyfile, "rb") as kf: - key = kf.read() -except OSError: - print("SKIP") - raise SystemExit +with open(certfile, "rb") as cf: + cert = cadata = cf.read() +with open(keyfile, "rb") as kf: + key = kf.read() # DTLS server. From f0d445a4663f568fedd24518134fcccb84affc1d Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 27 Aug 2025 13:33:22 +1000 Subject: [PATCH 1159/2098] tests/multi_net: Simplify SKIP when imports don't exist. Signed-off-by: Damien George --- tests/multi_net/asyncio_tcp_readinto.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/multi_net/asyncio_tcp_readinto.py b/tests/multi_net/asyncio_tcp_readinto.py index 563b640e13b..972195f96dc 100644 --- a/tests/multi_net/asyncio_tcp_readinto.py +++ b/tests/multi_net/asyncio_tcp_readinto.py @@ -1,13 +1,8 @@ # Test asyncio stream readinto() method using TCP server/client -try: - import asyncio -except ImportError: - print("SKIP") - raise SystemExit - try: import array + import asyncio except ImportError: print("SKIP") raise SystemExit From 5b98126c21f8eaa996e258761a291a7a88624082 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 27 Aug 2025 15:32:39 +1000 Subject: [PATCH 1160/2098] esp32/boards: Reduce flash usage of ESP32-C6 boards. Reduces ESP32_GENERIC_C6 application flash size from 2024432 to 1813216 (206KB smaller). Also has benefit of reducing D/IRAM size, increasing free memory at runtime (167187 to 148584, -18603 bytes). Most of this savings comes from building with -Os instead of -O2, but about 10KB comes from using the SPI flash functions from the ROM. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake | 1 + ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake | 1 + ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake | 1 + ports/esp32/boards/sdkconfig.c6 | 8 ++++++++ ports/esp32/boards/sdkconfig.riscv | 3 +++ 5 files changed, 14 insertions(+) create mode 100644 ports/esp32/boards/sdkconfig.riscv diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake index 3060c1cfd97..48946f70945 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.c6 boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake index 3060c1cfd97..48946f70945 100644 --- a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake +++ b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.c6 boards/sdkconfig.ble ) diff --git a/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake index 024b1299fa3..418bb7ff0f0 100644 --- a/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYC6/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c6) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.c6 boards/sdkconfig.ble boards/UM_TINYC6/sdkconfig.board diff --git a/ports/esp32/boards/sdkconfig.c6 b/ports/esp32/boards/sdkconfig.c6 index 18e8b75eb4e..a90a8f80dfe 100644 --- a/ports/esp32/boards/sdkconfig.c6 +++ b/ports/esp32/boards/sdkconfig.c6 @@ -1,2 +1,10 @@ # Workaround for https://github.com/espressif/esp-idf/issues/14456 CONFIG_ESP_SYSTEM_HW_STACK_GUARD=n + +# 802.15.4 not currently supported in MicroPython, disabling saves +# a little compile time (no difference in binary) +CONFIG_IEEE802154_ENABLED=n + +# Using the SPI flash implementation in ROM saves about 10KB of binary size +# (and some static RAM) +CONFIG_SPI_FLASH_ROM_IMPL=y diff --git a/ports/esp32/boards/sdkconfig.riscv b/ports/esp32/boards/sdkconfig.riscv new file mode 100644 index 00000000000..fff3ea830b7 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.riscv @@ -0,0 +1,3 @@ +# ESP RISC-V binary sizes are generally larger than Xtensa ones, +# so switch to size optimization by default +CONFIG_COMPILER_OPTIMIZATION_SIZE=y From 9a9cc794222e6791d3d615aee4e32a06c5aeb1be Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 27 Aug 2025 16:51:02 +1000 Subject: [PATCH 1161/2098] esp32/boards: Build ESP32-C2 and C3 with -Os instead of -O2. Same optimisation that was applied to C6 in the parent commit, now applied to all RISC-V boards. +------------+------------+------------+------------+ | Size | Before | After | Delta | +------------+------------+------------+------------+ | C2 Binary | 1680384 | 1494224 | -186160 | | C2 D/IRAM | 83710 | 79080 | -4630 | | C3 Binary | 1833328 | 1636624 | -196704 | | C3 D/IRAM | 139608 | 131896 | -7712 | +------------+------------+------------+------------+ This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake | 1 + ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake | 1 + .../boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake | 1 + ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake | 1 + 4 files changed, 4 insertions(+) diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake index 7a8b0e0b3d4..6a59e8a25e2 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.ble boards/sdkconfig.c2 # C2 has unusably low free RAM without these optimisations diff --git a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake index 429366afac9..41b32b84c89 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_C3/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.ble boards/ESP32_GENERIC_C3/sdkconfig.c3usb ) diff --git a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake index 81cfff1d77b..81d7f6b7ca4 100644 --- a/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake +++ b/ports/esp32/boards/GARATRONIC_PYBSTICK26_ESP32C3/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.ble boards/GARATRONIC_PYBSTICK26_ESP32C3/sdkconfig.board ) diff --git a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake index 0ce7f85e5d5..4ecc7195665 100644 --- a/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_C3_MINI/mpconfigboard.cmake @@ -2,6 +2,7 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.riscv boards/sdkconfig.ble boards/LOLIN_C3_MINI/sdkconfig.board ) From 704c70c5bb655afecec4d54537eb1489e689672c Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Wed, 9 Jul 2025 10:07:02 +0200 Subject: [PATCH 1162/2098] esp32/boards: Enable I2S for ESP32_GENERIC_C6 and other C6 boards. I2S works on the ESP32-C6, and it now has enough space to fit. Signed-off-by: Jimisola Laursen Signed-off-by: Damien George --- ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h | 2 -- ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h | 2 -- ports/esp32/boards/UM_TINYC6/mpconfigboard.h | 2 -- 3 files changed, 6 deletions(-) diff --git a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h index d6cc9a6f678..712e1fca157 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_C6/mpconfigboard.h @@ -3,7 +3,5 @@ #define MICROPY_HW_BOARD_NAME "ESP32C6 module" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_PY_MACHINE_I2S (0) - // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h index 656ec0bc6c3..16ddc1e51d3 100644 --- a/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h +++ b/ports/esp32/boards/M5STACK_NANOC6/mpconfigboard.h @@ -1,7 +1,5 @@ #define MICROPY_HW_BOARD_NAME "M5Stack NanoC6" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_PY_MACHINE_I2S (0) - #define MICROPY_HW_I2C0_SCL (1) #define MICROPY_HW_I2C0_SDA (2) diff --git a/ports/esp32/boards/UM_TINYC6/mpconfigboard.h b/ports/esp32/boards/UM_TINYC6/mpconfigboard.h index 09392d55594..131116e9ffb 100644 --- a/ports/esp32/boards/UM_TINYC6/mpconfigboard.h +++ b/ports/esp32/boards/UM_TINYC6/mpconfigboard.h @@ -1,8 +1,6 @@ #define MICROPY_HW_BOARD_NAME "Unexpected Maker TinyC6" #define MICROPY_HW_MCU_NAME "ESP32C6" -#define MICROPY_PY_MACHINE_I2S (0) - #define MICROPY_HW_I2C0_SCL (7) #define MICROPY_HW_I2C0_SDA (6) From 3adf0163765f3de76167705c8dc38bfd0f56e08a Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Aug 2025 13:58:17 +1000 Subject: [PATCH 1163/2098] esp32: Revert "esp32/mpconfigport: Disable I2CTarget on ESP32-C6 to ..". This reverts commit 3c9546ea0911b50d4b85ad4046864c90f84b3fd3. I2CTarget now fits on ESP32-C6 because it's compiled with -Os, see commit 5b98126c21f8eaa996e258761a291a7a88624082. Signed-off-by: Damien George --- ports/esp32/mpconfigport.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 9b12dbd34a7..0e62cd74823 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -137,8 +137,7 @@ #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #ifndef MICROPY_PY_MACHINE_I2C_TARGET // I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs. -// ESP32C6 does not have enough flash space so also disable it on that SoC. -#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32 && !CONFIG_IDF_TARGET_ESP32C6) +#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/esp32/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) #endif From 30c1b6e3d1ab0acd532b6f064e4466d605224460 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 20 Aug 2025 16:16:15 -0500 Subject: [PATCH 1164/2098] unix: Don't crash if heap locked in prompt_write_history. Signed-off-by: Jeff Epler --- ports/unix/input.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/unix/input.c b/ports/unix/input.c index 31926a5a8e1..260e9eac8c9 100644 --- a/ports/unix/input.c +++ b/ports/unix/input.c @@ -104,6 +104,9 @@ void prompt_write_history(void) { #if MICROPY_USE_READLINE == 1 char *home = getenv("HOME"); if (home != NULL) { + if (MP_STATE_THREAD(gc_lock_depth) != 0) { + return; + } vstr_t vstr; vstr_init(&vstr, 50); vstr_printf(&vstr, "%s/.micropython.history", home); From 6ad91f25fb91cfb715210102691c763547261257 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 20 Aug 2025 16:17:19 -0500 Subject: [PATCH 1165/2098] unix: Unlock heap before readline. This is intended to be equivalent to the unlock in shared/runtime/pyexec.c. Signed-off-by: Jeff Epler --- ports/unix/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/unix/main.c b/ports/unix/main.c index 51d99ce5f15..0acd8c9f27a 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -209,6 +209,9 @@ static int do_repl(void) { mp_hal_stdio_mode_raw(); input_restart: + // If the GC is locked at this point there is no way out except a reset, + // so force the GC to be unlocked to help the user debug what went wrong. + MP_STATE_THREAD(gc_lock_depth) = 0; vstr_reset(&line); int ret = readline(&line, mp_repl_get_ps1()); mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; From a791f7f43ccc21f4275e3905240e61cf5bfa1c0c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 20 Aug 2025 17:29:45 -0500 Subject: [PATCH 1166/2098] tests: Add test for heap lock in REPL. Signed-off-by: Jeff Epler --- tests/cmdline/repl_lock.py | 7 +++++++ tests/cmdline/repl_lock.py.exp | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/cmdline/repl_lock.py create mode 100644 tests/cmdline/repl_lock.py.exp diff --git a/tests/cmdline/repl_lock.py b/tests/cmdline/repl_lock.py new file mode 100644 index 00000000000..77e2e932740 --- /dev/null +++ b/tests/cmdline/repl_lock.py @@ -0,0 +1,7 @@ +import micropython +micropython.heap_lock() +1+1 +micropython.heap_lock() +None # Cause the repl's line storage to be enlarged ---------------- +micropython.heap_lock() +raise SystemExit diff --git a/tests/cmdline/repl_lock.py.exp b/tests/cmdline/repl_lock.py.exp new file mode 100644 index 00000000000..7cce24f6c6a --- /dev/null +++ b/tests/cmdline/repl_lock.py.exp @@ -0,0 +1,10 @@ +MicroPython \.\+ version +Use \.\+ +>>> import micropython +>>> micropython.heap_lock() +>>> 1+1 +2 +>>> micropython.heap_lock() +>>> None # Cause the repl's line storage to be enlarged ---------------- +>>> micropython.heap_lock() +>>> raise SystemExit From 4db2cf09e0006cc7cb91654f4699538d7d7f7732 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 1 Sep 2025 21:28:02 -0500 Subject: [PATCH 1167/2098] shared/runtime/pyexec: Unconditionally reset lock depth. Saves code size for the same functionality. Signed-off-by: Jeff Epler --- shared/runtime/pyexec.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index c828c758179..0b253b4ceaa 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -598,9 +598,7 @@ int pyexec_friendly_repl(void) { // If the GC is locked at this point there is no way out except a reset, // so force the GC to be unlocked to help the user debug what went wrong. - if (MP_STATE_THREAD(gc_lock_depth) != 0) { - MP_STATE_THREAD(gc_lock_depth) = 0; - } + MP_STATE_THREAD(gc_lock_depth) = 0; vstr_reset(&line); int ret = readline(&line, mp_repl_get_ps1()); From fc2a851202e214c6f9fa35b57baa9052501a8db5 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sun, 17 Aug 2025 23:49:59 +1000 Subject: [PATCH 1168/2098] stm32: Enable I2CTarget for STM32L4. STM32L4 uses the same I2C controller as STM32WB. Change `defined(STM32WB)` to `defined(STM32L4) || defined(STM32WB) ` in relevant files. Also remove the dummy definition of I2C2_BASE for STM32L432xx. It's now provided by the dummy definition in `i2cslave.h`. Signed-off-by: Chris Mason Signed-off-by: Damien George --- ports/stm32/i2c.c | 4 ---- ports/stm32/i2cslave.c | 2 +- ports/stm32/i2cslave.h | 4 ++-- ports/stm32/mpconfigboard_common.h | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index 4effb23438c..778885eac8c 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -279,10 +279,6 @@ int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) { #elif defined(STM32L4) #define APB1ENR APB1ENR1 #define RCC_APB1ENR_I2C1EN RCC_APB1ENR1_I2C1EN -#if defined(STM32L432xx) -// Not a real peripheral, only needed for i2c_id calculation in i2c_init. -#define I2C2_BASE (APB1PERIPH_BASE + 0x5800UL) -#endif #endif static uint16_t i2c_timeout_ms[MICROPY_HW_MAX_I2C]; diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index 0e4fbf48913..782cca7e4bf 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -76,7 +76,7 @@ void i2c_slave_irq_handler(i2c_slave_t *i2c) { } } -#elif defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#elif defined(STM32F7) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { i2c->CR1 = I2C_CR1_STOPIE | I2C_CR1_ADDRIE | I2C_CR1_RXIE | I2C_CR1_TXIE; diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index edead6cb2c5..0b3317efacd 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -28,7 +28,7 @@ #include STM32_HAL_H -#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) #if !defined(I2C2_BASE) // This MCU doesn't have I2C2_BASE, define it so that the i2c_idx calculation works. @@ -63,7 +63,7 @@ static inline void i2c_slave_init(i2c_slave_t *i2c, int irqn, int irq_pri, int a volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock (void)tmp; } - #elif defined(STM32WB) + #elif defined(STM32L4) || defined(STM32WB) RCC->APB1ENR1 |= 1 << (RCC_APB1ENR1_I2C1EN_Pos + i2c_idx); volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock (void)tmp; diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index a8e50be5c6a..d5cef1fecea 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -640,7 +640,7 @@ || defined(MICROPY_HW_I2C3_SCL) || defined(MICROPY_HW_I2C4_SCL) #define MICROPY_HW_ENABLE_HW_I2C (1) #ifndef MICROPY_HW_ENABLE_HW_I2C_TARGET -#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) #define MICROPY_HW_ENABLE_HW_I2C_TARGET (1) #else #define MICROPY_HW_ENABLE_HW_I2C_TARGET (0) From 9939565d50acfcd68429e86b6276a590197db951 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 26 Aug 2025 09:56:16 +0200 Subject: [PATCH 1169/2098] stm32/boards/ARDUINO_OPTA: Reset ETH PHY on board init. As required by the datasheet. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_OPTA/board_init.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/stm32/boards/ARDUINO_OPTA/board_init.c b/ports/stm32/boards/ARDUINO_OPTA/board_init.c index c0907f5d31d..51b5b944234 100644 --- a/ports/stm32/boards/ARDUINO_OPTA/board_init.c +++ b/ports/stm32/boards/ARDUINO_OPTA/board_init.c @@ -61,6 +61,13 @@ void OPTA_board_early_init(void) { HAL_MPU_ConfigRegion(&MPU_InitStruct); } #endif + + // Reset ETH Phy + mp_hal_pin_config(pyb_pin_ETH_RST, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_UP, 0); + mp_hal_pin_config_speed(pyb_pin_ETH_RST, MP_HAL_PIN_SPEED_LOW); + mp_hal_pin_write(pyb_pin_ETH_RST, 0); + HAL_Delay(100); + mp_hal_pin_write(pyb_pin_ETH_RST, 1); } void OPTA_board_enter_bootloader(void) { From b3c8ed6c33fa237c2061b38b31060a7fd3559810 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 26 Aug 2025 17:28:26 +1000 Subject: [PATCH 1170/2098] tests/ports/rp2: Decrease test lower bound for thread lightsleep time. This makes the rp2-specific lightsleep test more lenient, so it passes. This test was added in PR #16454 / 69993daa5c2eae6055f0b3b2330e95e78d6e3738 and even back then it did not pass reliably on RP2040. The issue is that threads can race each other to enter lightsleep mode, and when one of them actually stops the CPU clock the other thread is stopped as well. Depending on whether the other thread was just entering or just exiting lightsleep itself, the total duration of all sleeps can vary and can be as small as 2*T. Therefore, adjust the minimum to allow 2*T. `machine.lightsleep()` is anyway 1) specified only to wait at most the given time; and 2) not well specified when multiple threads call it at the same time. So it seems OK to make the test more lenient. Work done in collaboration with @projectgus. Signed-off-by: Damien George --- tests/ports/rp2/rp2_lightsleep_thread.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py index 494ead42231..90db6c4a5f8 100644 --- a/tests/ports/rp2/rp2_lightsleep_thread.py +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -9,6 +9,7 @@ SLEEP_MS = 250 IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS +MIN_RUNTIME = 2 * SLEEP_MS MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS MAX_DELTA = 20 @@ -52,14 +53,17 @@ def test_cpu0_also_lightsleep(self): # is unspecified. # # Currently, the other thread will return immediately if one is already - # in lightsleep. Therefore, runtime can be between IDEAL_RUNTIME and - # IDEAL_RUNTIME * 2 depending on how many times the calls to lightsleep() race - # each other. + # in lightsleep doing set up, or waking up. When a thread is actually in the + # sleep section of lightsleep, the CPU clock is stopped and that also stops + # the other thread. It's possible for the total sleep time of this test to + # be less than IDEAL_RUNTIME due to the order in which each thread gets to go + # to sleep first and whether the other thread is paused before or after it + # tries to enter lightsleep itself. # # Note this test case is really only here to ensure that the rp2 hasn't # hung or failed to sleep at all - not to verify any correct behaviour # when there's a race to call lightsleep(). - self.assertGreaterEqual(self.elapsed_ms(), IDEAL_RUNTIME - MAX_DELTA) + self.assertGreaterEqual(self.elapsed_ms(), MIN_RUNTIME - MAX_DELTA) self.assertLessEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2 + MAX_DELTA) From bfda577a3eaa061f8752197b076cf52a426f4fb6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Aug 2025 12:19:03 +1000 Subject: [PATCH 1171/2098] zephyr/CMakeLists.txt: Enable sys.implementation._build. This is, eg, `ZEPHYR_NUCLEO_WB55RG`. Signed-off-by: Damien George --- ports/zephyr/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index a5cc477204e..e7a55d804ae 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -30,6 +30,7 @@ project(micropython) set(MICROPY_PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(MICROPY_DIR ${MICROPY_PORT_DIR}/../..) set(MICROPY_TARGET micropython) +string(TOUPPER ZEPHYR_${BOARD} MICROPY_BOARD) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) From 4a60d230c346041659348d7097363650eff6cde7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Aug 2025 21:27:49 +1000 Subject: [PATCH 1172/2098] tests/run-tests.py: Detect target sys.implementation._build if possible. That's useful to know the exact board that's being tested. Signed-off-by: Damien George --- tests/feature_check/target_info.py | 3 ++- tests/run-tests.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index 962ebf4154b..5367eb6a47d 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -20,6 +20,7 @@ "xtensawin", "rv32imc", ][sys_mpy >> 10] +build = getattr(sys.implementation, "_build", "unknown") thread = getattr(sys.implementation, "_thread", None) # Detect how many bits of precision the floating point implementation has. @@ -33,4 +34,4 @@ except NameError: float_prec = 0 -print(platform, arch, thread, float_prec, len("α") == 1) +print(platform, arch, build, thread, float_prec, len("α") == 1) diff --git a/tests/run-tests.py b/tests/run-tests.py index 2d02e6b23c6..c434cd6fc1b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -291,7 +291,7 @@ def detect_test_platform(pyb, args): output = run_feature_check(pyb, args, "target_info.py") if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) - platform, arch, thread, float_prec, unicode = str(output, "ascii").strip().split() + platform, arch, build, thread, float_prec, unicode = str(output, "ascii").strip().split() if arch == "None": arch = None inlineasm_arch = detect_inline_asm_arch(pyb, args) @@ -305,6 +305,7 @@ def detect_test_platform(pyb, args): if arch and not args.mpy_cross_flags: args.mpy_cross_flags = "-march=" + arch args.inlineasm_arch = inlineasm_arch + args.build = build args.thread = thread args.float_prec = float_prec args.unicode = unicode From 3285e52127017e58125c7aacc361daae48dc6744 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Aug 2025 14:10:13 +1000 Subject: [PATCH 1173/2098] tests/run-tests.py: Add support for board-specific target_wiring config. There are currently a few "hardware" tests that need external board connections to be made for them to work, such as bridging a pair of pins. For example, all tests in `tests/extmod_hardware` need some kind of connection. Along with the physical connections -- which are different for each board -- there needs to be corresponding Python settings, eg which UART instance to use and which pins for TX/RX. These settings are currently hard-coded in each test file. That has a few problems: - settings are repeated, eg all the UART tests have pretty much the same settings code duplicated across them - changing the settings means changing many files - adding a new board means adding a lot of code - the tests get bigger and bigger with each new board that they support, meaning they may not fit on targets with a small amount of RAM (that's already the case with `tests/extmod_hardware/machine_pwm.py`) - if you have a custom board you have to manually edit the test to add your own settings This commit aims to solve all the above problems by splitting the board- specific settings out into separate files, one for each board (or port). They are placed in the `tests/target_wiring/` directory. The `run-tests.py` test runner then loads the appropriate configuration for the target that is being tested, sends it to the board so it's available as `import target_wiring` (without needing a filesystem), and then executes the test. Note tht only tests that need `target_wiring` have it loaded. This commit adds support for this `target_wiring` scheme. Signed-off-by: Damien George --- tests/run-tests.py | 60 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index c434cd6fc1b..feeaa982273 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -216,6 +216,9 @@ def open(self, path, mode): ), } +# Tests that require `import target_wiring` to work. +tests_requiring_target_wiring = () + def rm_f(fname): if os.path.exists(fname): @@ -310,6 +313,7 @@ def detect_test_platform(pyb, args): args.float_prec = float_prec args.unicode = unicode + # Print the detected information about the target. print("platform={}".format(platform), end="") if arch: print(" arch={}".format(arch), end="") @@ -321,7 +325,43 @@ def detect_test_platform(pyb, args): print(" float={}-bit".format(float_prec), end="") if unicode: print(" unicode", end="") - print() + + +def detect_target_wiring_script(pyb, args): + tw_data = b"" + tw_source = None + if args.target_wiring: + # A target_wiring path is explicitly provided, so use that. + tw_source = args.target_wiring + with open(tw_source, "rb") as f: + tw_data = f.read() + elif hasattr(pyb, "exec_raw") and pyb.exec_raw("import target_wiring") == (b"", b""): + # The board already has a target_wiring module available, so use that. + tw_source = "on-device" + else: + port = platform_to_port(args.platform) + build = args.build + tw_board_exact = None + tw_board_partial = None + tw_port = None + for file in os.listdir("target_wiring"): + file_base = file.removesuffix(".py") + if file_base == build: + # A file matching the target's board/build name. + tw_board_exact = file + elif file_base.endswith("x") and build.startswith(file_base.removesuffix("x")): + # A file with a partial match to the target's board/build name. + tw_board_partial = file + elif file_base == port: + # A file matching the target's port. + tw_port = file + tw_source = tw_board_exact or tw_board_partial or tw_port + if tw_source: + with open("target_wiring/" + tw_source, "rb") as f: + tw_data = f.read() + if tw_source: + print(" target_wiring={}".format(tw_source), end="") + pyb.target_wiring_script = tw_data def prepare_script_for_target(args, *, script_text=None, force_plain=False): @@ -382,6 +422,12 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): try: had_crash = False pyb.enter_raw_repl() + if test_file.endswith(tests_requiring_target_wiring) and pyb.target_wiring_script: + pyb.exec_( + "import sys;sys.modules['target_wiring']=__build_class__(lambda:exec(" + + repr(pyb.target_wiring_script) + + "),'target_wiring')" + ) output_mupy = pyb.exec_(script, timeout=TEST_TIMEOUT) except pyboard.PyboardError as e: had_crash = True @@ -1285,6 +1331,11 @@ def main(): default=None, help="prologue python file to execute before module import", ) + cmd_parser.add_argument( + "--target-wiring", + default=None, + help="force the given script to be used as target_wiring.py", + ) args = cmd_parser.parse_args() prologue = "" @@ -1381,6 +1432,13 @@ def main(): # tests explicitly given tests = args.files + # If any tests need it, prepare the target_wiring script for the target. + if pyb and any(test.endswith(tests_requiring_target_wiring) for test in tests): + detect_target_wiring_script(pyb, args) + + # End the target information line. + print() + if not args.keep_path: # Clear search path to make sure tests use only builtin modules, those in # extmod, and a path to unittest in case it's needed. From 5bafb0bf68a65b0b43ef1e3d7e2b174b23b046f3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Aug 2025 14:08:09 +1000 Subject: [PATCH 1174/2098] tests: Convert all machine.UART tests to use target_wiring. All the existing `machine.UART` tests in extmod and extmod_hardware are converted to use the new `target_wiring` scheme, which removes a lot of duplicated board-specific settings. All the existing boards that were supported by these UART tests now have their own `target_wiring` file. Some configurations are board specific (eg NUCLEO_WB55) and others are port specific. Signed-off-by: Damien George --- tests/extmod/machine_uart_irq_txidle.py | 33 +----------- tests/extmod/machine_uart_tx.py | 25 ++------- .../extmod_hardware/machine_uart_irq_break.py | 20 +------- tests/extmod_hardware/machine_uart_irq_rx.py | 51 +++---------------- .../machine_uart_irq_rxidle.py | 51 ++----------------- tests/run-tests.py | 8 ++- tests/target_wiring/EK_RA6M2.py | 8 +++ tests/target_wiring/NUCLEO_WB55.py | 8 +++ tests/target_wiring/PYBx.py | 8 +++ tests/target_wiring/alif.py | 7 +++ tests/target_wiring/esp32.py | 7 +++ tests/target_wiring/mimxrt.py | 7 +++ tests/target_wiring/nrf.py | 7 +++ tests/target_wiring/rp2.py | 7 +++ tests/target_wiring/samd.py | 7 +++ 15 files changed, 91 insertions(+), 163 deletions(-) create mode 100644 tests/target_wiring/EK_RA6M2.py create mode 100644 tests/target_wiring/NUCLEO_WB55.py create mode 100644 tests/target_wiring/PYBx.py create mode 100644 tests/target_wiring/alif.py create mode 100644 tests/target_wiring/esp32.py create mode 100644 tests/target_wiring/mimxrt.py create mode 100644 tests/target_wiring/nrf.py create mode 100644 tests/target_wiring/rp2.py create mode 100644 tests/target_wiring/samd.py diff --git a/tests/extmod/machine_uart_irq_txidle.py b/tests/extmod/machine_uart_irq_txidle.py index 084e9825768..a56f6e3cf85 100644 --- a/tests/extmod/machine_uart_irq_txidle.py +++ b/tests/extmod/machine_uart_irq_txidle.py @@ -10,32 +10,7 @@ raise SystemExit import time, sys - -# Configure pins based on the target. -if "alif" in sys.platform: - uart_id = 1 - tx_pin = None -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" -elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: - uart_id = 3 - tx_pin = "D1" - rx_pin = "D0" -elif "mimxrt" in sys.platform: - uart_id = 1 - tx_pin = None -elif "nrf" in sys.platform: - uart_id = 0 - tx_pin = None -else: - print("Please add support for this test on this platform.") - raise SystemExit +from target_wiring import uart_loopback_args, uart_loopback_kwargs def irq(u): @@ -46,11 +21,7 @@ def irq(u): # 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 = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) uart.irq(irq, uart.IRQ_TXIDLE) # The IRQ_TXIDLE shall trigger after the message has been sent. Thus diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index 85bf7e9fb87..70d57be4647 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -8,50 +8,35 @@ raise SystemExit import time, sys +from target_wiring import uart_loopback_args, uart_loopback_kwargs initial_delay_ms = 0 bit_margin = 0 timing_margin_us = 100 -# Configure pins based on the target. +# Tune test parameters based on the target. if "alif" in sys.platform: - uart_id = 1 - pins = {} bit_margin = 1 elif "esp32" in sys.platform: - uart_id = 1 - pins = {} timing_margin_us = 400 elif "mimxrt" in sys.platform: - uart_id = 1 - pins = {} initial_delay_ms = 20 # UART sends idle frame after init, so wait for that bit_margin = 1 +elif "nrf" in sys.platform: + timing_margin_us = 130 elif "pyboard" in sys.platform: - if "STM32WB" in sys.implementation._machine: - uart_id = "LP1" - else: - uart_id = 4 - pins = {} initial_delay_ms = 50 # UART sends idle frame after init, so wait for that bit_margin = 1 # first start-bit must wait to sync with the UART clock elif "rp2" in sys.platform: - uart_id = 0 - pins = {"tx": "GPIO0", "rx": "GPIO1"} timing_margin_us = 180 elif "samd" in sys.platform: - uart_id = 2 - pins = {"tx": "D1", "rx": "D0"} timing_margin_us = 300 bit_margin = 1 -else: - print("SKIP") - raise SystemExit # Test that write+flush takes the expected amount of time to execute. for bits_per_s in (2400, 9600, 115200): text = "Hello World" - uart = UART(uart_id, bits_per_s, bits=8, parity=None, stop=1, **pins) + uart = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) time.sleep_ms(initial_delay_ms) start_us = time.ticks_us() diff --git a/tests/extmod_hardware/machine_uart_irq_break.py b/tests/extmod_hardware/machine_uart_irq_break.py index 879f9cee676..1832d9883ba 100644 --- a/tests/extmod_hardware/machine_uart_irq_break.py +++ b/tests/extmod_hardware/machine_uart_irq_break.py @@ -12,23 +12,7 @@ raise SystemExit import time, sys - -# Configure pins based on the target. -if "esp32" in sys.platform: - _machine = sys.implementation._machine - if "ESP32S2" in _machine or "ESP32C3" in _machine or "ESP32C6" in _machine: - print("SKIP") - raise SystemExit - uart_id = 1 - tx_pin = 4 - rx_pin = 5 -elif "rp2" in sys.platform: - uart_id = 0 - tx_pin = "GPIO0" - rx_pin = "GPIO1" -else: - print("Please add support for this test on this platform.") - raise SystemExit +from target_wiring import uart_loopback_args, uart_loopback_kwargs def irq(u): @@ -37,7 +21,7 @@ def irq(u): # Test that the IRQ is called for each break received. for bits_per_s in (2400, 9600, 57600): - uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + uart = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) uart.irq(irq, uart.IRQ_BREAK) print("write", bits_per_s) diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py index 3602c260e39..92fe218835d 100644 --- a/tests/extmod_hardware/machine_uart_irq_rx.py +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -13,49 +13,14 @@ import time, sys -byte_by_byte = False -# Configure pins based on the target. -if "alif" in sys.platform: - uart_id = 1 - tx_pin = None - rx_pin = None -elif "esp32" in sys.platform: - uart_id = 1 - tx_pin = 4 - rx_pin = 5 -elif "pyboard" in sys.platform: - if "STM32WB" in sys.implementation._machine: - # LPUART(1) is on PA2/PA3 - uart_id = "LP1" - else: - # UART(4) is on PA0/PA1 - uart_id = 4 - tx_pin = None - rx_pin = None -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: +if "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 + +from target_wiring import uart_loopback_args, uart_loopback_kwargs + +byte_by_byte = "ItsyBitsy M0" in sys.implementation._machine def irq(u): @@ -67,11 +32,7 @@ def irq(u): # 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 = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) uart.irq(irq, uart.IRQ_RX) print("write", bits_per_s) diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py index 3c743c9e0c1..40f781d0788 100644 --- a/tests/extmod_hardware/machine_uart_irq_rxidle.py +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -12,52 +12,10 @@ raise SystemExit import time, sys +from target_wiring import uart_loopback_args, uart_loopback_kwargs # Target tuning options. -tune_wait_initial_rxidle = False - -# Configure pins based on the target. -if "alif" in sys.platform: - uart_id = 1 - tx_pin = None - rx_pin = None -elif "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: - tune_wait_initial_rxidle = True - if "STM32WB" in sys.implementation._machine: - # LPUART(1) is on PA2/PA3 - uart_id = "LP1" - else: - # UART(4) is on PA0/PA1 - uart_id = 4 - 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 "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 +tune_wait_initial_rxidle = sys.platform == "pyboard" def irq(u): @@ -71,10 +29,7 @@ def irq(u): print("========") print("bits_per_s:", bits_per_s) - 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 = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) # Ignore a possible initial RXIDLE condition after creating UART. if tune_wait_initial_rxidle: diff --git a/tests/run-tests.py b/tests/run-tests.py index feeaa982273..c3017542cc9 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -217,7 +217,13 @@ def open(self, path, mode): } # Tests that require `import target_wiring` to work. -tests_requiring_target_wiring = () +tests_requiring_target_wiring = ( + "extmod/machine_uart_irq_txidle.py", + "extmod/machine_uart_tx.py", + "extmod_hardware/machine_uart_irq_break.py", + "extmod_hardware/machine_uart_irq_rx.py", + "extmod_hardware/machine_uart_irq_rxidle.py", +) def rm_f(fname): diff --git a/tests/target_wiring/EK_RA6M2.py b/tests/target_wiring/EK_RA6M2.py new file mode 100644 index 00000000000..7d4a8cbbd64 --- /dev/null +++ b/tests/target_wiring/EK_RA6M2.py @@ -0,0 +1,8 @@ +# Target wiring for EK_RA6M2. +# +# Connect: +# - P601 to P602 + +# UART(9) is on P602/P601. +uart_loopback_args = (9,) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/NUCLEO_WB55.py b/tests/target_wiring/NUCLEO_WB55.py new file mode 100644 index 00000000000..ad7c120d377 --- /dev/null +++ b/tests/target_wiring/NUCLEO_WB55.py @@ -0,0 +1,8 @@ +# Target wiring for NUCLEO_WB55. +# +# Connect: +# - PA2 to PA3 + +# LPUART(1) is on PA2/PA3. +uart_loopback_args = ("LP1",) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/PYBx.py b/tests/target_wiring/PYBx.py new file mode 100644 index 00000000000..10ce520ef0a --- /dev/null +++ b/tests/target_wiring/PYBx.py @@ -0,0 +1,8 @@ +# Target wiring for PYBV10, PYBV11, PYBLITEV10, PYBD_SF2, PYBD_SF3, PYBD_SF6. +# +# Connect: +# - X1 to X2 + +# UART("XA") is on X1/X2 (usually UART(4) on PA0/PA1). +uart_loopback_args = ("XA",) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/alif.py b/tests/target_wiring/alif.py new file mode 100644 index 00000000000..18f3cbe7e5e --- /dev/null +++ b/tests/target_wiring/alif.py @@ -0,0 +1,7 @@ +# Target wiring for general alif board. +# +# Connect: +# - UART1 TX and RX, usually P0_5 and P0_4 + +uart_loopback_args = (1,) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/esp32.py b/tests/target_wiring/esp32.py new file mode 100644 index 00000000000..63d7a81a2dc --- /dev/null +++ b/tests/target_wiring/esp32.py @@ -0,0 +1,7 @@ +# Target wiring for general esp32 board. +# +# Connect: +# - GPIO4 to GPIO5 + +uart_loopback_args = (1,) +uart_loopback_kwargs = {"tx": 4, "rx": 5} diff --git a/tests/target_wiring/mimxrt.py b/tests/target_wiring/mimxrt.py new file mode 100644 index 00000000000..669e9095990 --- /dev/null +++ b/tests/target_wiring/mimxrt.py @@ -0,0 +1,7 @@ +# Target wiring for general mimxrt board. +# +# Connect: +# - UART1 TX and RX, usually D0 and D1 + +uart_loopback_args = (1,) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/nrf.py b/tests/target_wiring/nrf.py new file mode 100644 index 00000000000..6979dd28ee5 --- /dev/null +++ b/tests/target_wiring/nrf.py @@ -0,0 +1,7 @@ +# Target wiring for general nrf board. +# +# Connect: +# - UART0 TX and RX + +uart_loopback_args = (0,) +uart_loopback_kwargs = {} diff --git a/tests/target_wiring/rp2.py b/tests/target_wiring/rp2.py new file mode 100644 index 00000000000..cb0fa0d6263 --- /dev/null +++ b/tests/target_wiring/rp2.py @@ -0,0 +1,7 @@ +# Target wiring for general rp2 board. +# +# Connect: +# - GPIO0 to GPIO1 + +uart_loopback_args = (0,) +uart_loopback_kwargs = {"tx": "GPIO0", "rx": "GPIO1"} diff --git a/tests/target_wiring/samd.py b/tests/target_wiring/samd.py new file mode 100644 index 00000000000..887c43a242f --- /dev/null +++ b/tests/target_wiring/samd.py @@ -0,0 +1,7 @@ +# Target wiring for general samd board. +# +# Connect: +# - D0 to D1 + +uart_loopback_args = () +uart_loopback_kwargs = {"tx": "D1", "rx": "D0"} From 123f66b2598e0feb96993fe19d98568517addc3b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 27 Aug 2025 23:31:19 +1000 Subject: [PATCH 1175/2098] tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py: Add nucleo_wb55rg. Signed-off-by: Damien George --- tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py diff --git a/tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py b/tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py new file mode 100644 index 00000000000..c7ca2ac26f7 --- /dev/null +++ b/tests/target_wiring/ZEPHYR_NUCLEO_WB55RG.py @@ -0,0 +1,7 @@ +# Target wiring for zephyr nucleo_wb55rg. +# +# Connect: +# - TX=PC0 to RX=PC1 + +uart_loopback_args = ("lpuart1",) +uart_loopback_kwargs = {} From c7c0ad222ee017435ba24eeb82ba90a30dc8ae2b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 21 Aug 2025 13:56:36 +1000 Subject: [PATCH 1176/2098] alif/tinyusb_port: Fix setting of USB device addr for fast hosts. A fast host (eg Mac M4) may request the status for the SET_ADDRESS before TinyUSB gets to process it. This is because TinyUSB does not handle events inside the USB ISR, rather it waits for the top-level thread to process them. Fix that by setting the USB device address as soon as TUSB_REQ_SET_ADDRESS comes in. This patch follows the corresponding upstream fix: https://github.com/alifsemi/tinyusb/commit/fc40ea7fc6e6bd49baad6b504c88bb6a37c7a191 Signed-off-by: Damien George --- ports/alif/tinyusb_port/tusb_alif_dcd.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/alif/tinyusb_port/tusb_alif_dcd.c b/ports/alif/tinyusb_port/tusb_alif_dcd.c index 9a990cedbe3..cae0be8d846 100644 --- a/ports/alif/tinyusb_port/tusb_alif_dcd.c +++ b/ports/alif/tinyusb_port/tusb_alif_dcd.c @@ -249,7 +249,11 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { LOG("%010u >%s", DWT->CYCCNT, __func__); - udev->dcfg_b.devaddr = dev_addr; + // Device address is set from the ISR when SETUP packet is received + // By point TinyUSB calls this function, the address has already been + // set and STATUS sent back to the host. Xfer call below is purely for + // internal TinyUSB state to conclude transaction and issue next SETUP req. + dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); } @@ -571,6 +575,9 @@ static void _dcd_handle_depevt(uint8_t ep, uint8_t evt, uint8_t sts, uint16_t pa // XferNotReady NotActive for status stage if ((1 == ep) && (0b0010 == (sts & 0b1011))) { + if (0x00 == _ctrl_buf[0] && TUSB_REQ_SET_ADDRESS == _ctrl_buf[1]) { + udev->dcfg_b.devaddr = _ctrl_buf[2]; + } _dcd_start_xfer(1, NULL, 0, TRBCTL_CTL_STAT2); break; } From 5f3e6b5b5381fd180280b0c852826db079808cce Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 18 Jul 2025 16:36:07 +0200 Subject: [PATCH 1177/2098] shared/tinyusb: Fix build errors with CDC support disabled. This commit makes possible building MicroPython with USB CDC support disabled. The original code does support such a configuration but missed a few spots where build errors would arise. These changes fix the remaining issues, fixing also warnings caused by the changes needed to make the build succeed. Signed-off-by: Alessandro Gatti --- shared/tinyusb/mp_usbd.h | 3 +++ shared/tinyusb/mp_usbd_runtime.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 5c8f2a6095f..866ef8503a5 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -38,13 +38,16 @@ #ifndef NO_QSTR #include "tusb.h" #include "device/dcd.h" +#include "class/cdc/cdc_device.h" #endif // Initialise TinyUSB device. static inline void mp_usbd_init_tud(void) { tusb_init(); + #if MICROPY_HW_USB_CDC tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; tud_cdc_configure_fifo(&cfg); + #endif } // Run the TinyUSB device task diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index a1eebeebd2f..72e011732d4 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -267,9 +267,11 @@ static uint16_t runtime_dev_open(uint8_t rhport, tusb_desc_interface_t const *it } // If TinyUSB built-in drivers are enabled, don't claim any interface in the built-in range + #if USBD_ITF_BUILTIN_MAX > 0 if (mp_usb_device_builtin_enabled(usbd) && itf_desc->bInterfaceNumber < USBD_ITF_BUILTIN_MAX) { return 0; } + #endif // Determine the total descriptor length of the interface(s) we are going to claim uint8_t assoc_itf_count = _runtime_dev_count_itfs(itf_desc); From 1f7952e749d02f74492605ed9a50bd73dd4b4ead Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 25 Jul 2025 11:49:23 +0200 Subject: [PATCH 1178/2098] rp2/mphalport: Fix building with USB CDC disabled. This commit fixes the linking issues in the RP2 port that arise when explicitly disabling USB CDC support. The console input ringbuffer needs to be made available if dupterm support is enabled, and the console character input function would always attempt to read from the input ringbuffer even if CDC support is disabled and the console isn't bound to any UART. Signed-off-by: Alessandro Gatti --- ports/rp2/mphalport.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index b581b3b59f9..4dd510dea4d 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -50,7 +50,7 @@ static uint64_t time_us_64_offset_from_epoch; #endif -#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC +#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC || MICROPY_PY_OS_DUPTERM_NOTIFY #ifndef MICROPY_HW_STDIN_BUFFER_LEN #define MICROPY_HW_STDIN_BUFFER_LEN 512 @@ -83,11 +83,12 @@ int mp_hal_stdin_rx_chr(void) { #if MICROPY_HW_USB_CDC mp_usbd_cdc_poll_interfaces(0); #endif - + #if MICROPY_HW_USB_CDC || MICROPY_HW_ENABLE_UART_REPL || MICROPY_PY_OS_DUPTERM_NOTIFY int c = ringbuf_get(&stdin_ringbuf); if (c != -1) { return c; } + #endif #if MICROPY_PY_OS_DUPTERM int dupterm_c = mp_os_dupterm_rx_chr(); if (dupterm_c >= 0) { From 3e5ecd0c33ddeaf50e0ff48cf1c91f87c1320170 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 25 Jul 2025 12:15:01 +0200 Subject: [PATCH 1179/2098] mimxrt/mphalport: Fix building with USB CDC disabled. This commit fixes the build issues in the MIMXRT port that arise when explicitly disabling USB CDC support. The console code assumed CDC support is always enabled even if it was disabled in mpconfigport.h. These changes make accessing CDC conditional to that support configuration being enabled. Signed-off-by: Alessandro Gatti --- ports/mimxrt/mphalport.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/mimxrt/mphalport.c b/ports/mimxrt/mphalport.c index be5abd95bec..c3802be30f5 100644 --- a/ports/mimxrt/mphalport.c +++ b/ports/mimxrt/mphalport.c @@ -47,7 +47,9 @@ ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; + #if MICROPY_HW_USB_CDC ret |= mp_usbd_cdc_poll_interfaces(poll_flags); + #endif #if MICROPY_PY_OS_DUPTERM ret |= mp_os_dupterm_poll(poll_flags); #endif @@ -56,7 +58,9 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { int mp_hal_stdin_rx_chr(void) { for (;;) { + #if MICROPY_HW_USB_CDC mp_usbd_cdc_poll_interfaces(0); + #endif int c = ringbuf_get(&stdin_ringbuf); if (c != -1) { return c; @@ -74,11 +78,13 @@ int mp_hal_stdin_rx_chr(void) { mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { mp_uint_t ret = len; bool did_write = false; + #if MICROPY_HW_USB_CDC mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len); if (cdc_res > 0) { did_write = true; ret = MIN(cdc_res, ret); } + #endif #if MICROPY_PY_OS_DUPTERM int dupterm_res = mp_os_dupterm_tx_strn(str, len); if (dupterm_res >= 0) { From ef21e98520fb9cdac0ccc340f8be349af8abec29 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 25 Jul 2025 12:42:09 +0200 Subject: [PATCH 1180/2098] samd/mphalport: Fix building with USB CDC disabled. This commit fixes the build issues in the SAMD port that arise when explicitly disabling USB CDC support. The console code assumed CDC support is always enabled even if it was disabled in mpconfigport.h. These changes make accessing CDC conditional to that support configuration being enabled. Signed-off-by: Alessandro Gatti --- ports/samd/mphalport.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/samd/mphalport.c b/ports/samd/mphalport.c index 84d05b9185a..327ab436a71 100644 --- a/ports/samd/mphalport.c +++ b/ports/samd/mphalport.c @@ -120,7 +120,9 @@ uint64_t mp_hal_ticks_us_64(void) { uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; + #if MICROPY_HW_USB_CDC ret |= mp_usbd_cdc_poll_interfaces(poll_flags); + #endif #if MICROPY_PY_OS_DUPTERM ret |= mp_os_dupterm_poll(poll_flags); #endif @@ -129,8 +131,9 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { int mp_hal_stdin_rx_chr(void) { for (;;) { - + #if MICROPY_HW_USB_CDC mp_usbd_cdc_poll_interfaces(0); + #endif int c = ringbuf_get(&stdin_ringbuf); if (c != -1) { return c; @@ -149,11 +152,13 @@ int mp_hal_stdin_rx_chr(void) { mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { mp_uint_t ret = len; bool did_write = false; + #if MICROPY_HW_USB_CDC mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len); if (cdc_res > 0) { did_write = true; ret = MIN(cdc_res, ret); } + #endif #if MICROPY_PY_OS_DUPTERM int dupterm_res = mp_os_dupterm_tx_strn(str, len); if (dupterm_res >= 0) { From d1607598faf4ce2ef8e1a0385f75036ffb50ea9b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 25 Jul 2025 13:54:41 +0200 Subject: [PATCH 1181/2098] esp32/usb: Fix building with USB CDC disabled. This commit fixes the build issues in the ESP32 port that arise when explicitly disabling USB CDC support. The USB support code gated the USB serial number retrieval behind the USB CDC support definition, even though all USB devices must be able to be queried for their serial number. Signed-off-by: Alessandro Gatti --- ports/esp32/usb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c index 3ce3d045810..750dd59ee6f 100644 --- a/ports/esp32/usb.c +++ b/ports/esp32/usb.c @@ -28,16 +28,16 @@ #include "py/mphal.h" #include "usb.h" -#if MICROPY_HW_USB_CDC -#include "esp_rom_gpio.h" +#if MICROPY_HW_ENABLE_USBDEV + #include "esp_mac.h" +#include "esp_rom_gpio.h" #include "esp_private/usb_phy.h" #include "shared/tinyusb/mp_usbd.h" static usb_phy_handle_t phy_hdl; - void usb_init(void) { // ref: https://github.com/espressif/esp-usb/blob/4b6a798d0bed444fff48147c8dcdbbd038e92892/device/esp_tinyusb/tinyusb.c @@ -77,4 +77,4 @@ void mp_usbd_port_get_serial_number(char *serial_buf) { mp_usbd_hex_str(serial_buf, mac, sizeof(mac)); } -#endif // MICROPY_HW_USB_CDC +#endif // MICROPY_HW_ENABLE_USBDEV From 7dce35c7bea5c4c0dff0bb97b4dd4a989fd84fb2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Aug 2025 16:26:27 +1000 Subject: [PATCH 1182/2098] esp32: Update esp_tinyusb component to v1.7.6. Reported to fix issues reported with serial corruption when interacting with MicroPython from macOS hosts. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/main/idf_component.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index f7773f4f4e4..cb14ffde60d 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -4,7 +4,7 @@ dependencies: espressif/esp_tinyusb: rules: - if: "target in [esp32s2, esp32s3]" - version: "~1.0.0" + version: "~1.7.6" espressif/lan867x: version: "~1.0.0" rules: From 1c4455bfe703055155d40197b41a00cc37b04b35 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 3 Sep 2025 15:44:10 +1000 Subject: [PATCH 1183/2098] tools: Add an environment variable MICROPY_MAINTAINER_BUILD. This allows us to have some things which are fatal errors in CI or nightly builds, but warnings in normal developer builds. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/autobuild/autobuild.sh | 3 +++ tools/ci.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tools/autobuild/autobuild.sh b/tools/autobuild/autobuild.sh index 7073152df79..c24c4b3a9af 100755 --- a/tools/autobuild/autobuild.sh +++ b/tools/autobuild/autobuild.sh @@ -48,6 +48,9 @@ git -C ${MICROPY_AUTOBUILD_MICROPYTHON_REPO}/lib/pico-sdk submodule update --ini ######################################## # Build all firmware +# Fail on some things which are warnings otherwise +export MICROPY_MAINTAINER_BUILD=1 + pushd ${MICROPY_AUTOBUILD_MICROPYTHON_REPO} # build cross compiler diff --git a/tools/ci.sh b/tools/ci.sh index 1dfe3d66e86..115442ffd0f 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -9,6 +9,9 @@ fi # Ensure known OPEN_MAX (NO_FILES) limit. ulimit -n 1024 +# Fail on some things which are warnings otherwise +export MICROPY_MAINTAINER_BUILD=1 + ######################################################################################## # general helper functions From 8d5a8892d22388b0d1b0e6769a0f1719a362937b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Aug 2025 16:24:57 +1000 Subject: [PATCH 1184/2098] esp32: Add IDF Component Lockfiles to git repo. This is recommended by Espressif, and it's the only way to ensure everyone builds the same set of component versions. The awkward part is that updating the ESP-IDF version will churn a line in each of these files (and possibly other changes). Adds a build-time check for lock file changes, which is either a warning or a hard error depending on the value of MICROPY_MAINTAINER_BUILD flag (introduced in previous commit). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/.gitignore | 1 - ports/esp32/CMakeLists.txt | 16 ++++++ ports/esp32/README.md | 5 +- ports/esp32/lockfiles/README.md | 12 +++++ ports/esp32/lockfiles/dependencies.lock.esp32 | 35 +++++++++++++ .../esp32/lockfiles/dependencies.lock.esp32c2 | 21 ++++++++ .../esp32/lockfiles/dependencies.lock.esp32c3 | 21 ++++++++ .../esp32/lockfiles/dependencies.lock.esp32c6 | 21 ++++++++ .../esp32/lockfiles/dependencies.lock.esp32s2 | 50 +++++++++++++++++++ .../esp32/lockfiles/dependencies.lock.esp32s3 | 50 +++++++++++++++++++ tools/ci.sh | 7 ++- 11 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 ports/esp32/lockfiles/README.md create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32 create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32c2 create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32c3 create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32c6 create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32s2 create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32s3 diff --git a/ports/esp32/.gitignore b/ports/esp32/.gitignore index a78ecd0191c..9c708bb85c6 100644 --- a/ports/esp32/.gitignore +++ b/ports/esp32/.gitignore @@ -1,2 +1 @@ -dependencies.lock managed_components/ diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index 1db374b40a4..9d6be8010ff 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -61,5 +61,21 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) # Include main IDF cmake file. include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# Generate individual dependencies.lock files based on chip target +idf_build_set_property(DEPENDENCIES_LOCK lockfiles/dependencies.lock.${IDF_TARGET}) + # Define the project. project(micropython) + +# Check for lockfile changes and either warn or error depending on build type +message("Checking lockfile contents...") +execute_process(COMMAND git diff --exit-code lockfiles/ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + RESULT_VARIABLE RES) +if (RES) + # Maintainer builds (CI or autobuild runs) should fail if this has happened + if($ENV{MICROPY_MAINTAINER_BUILD}) + message(FATAL_ERROR "Failing build as lockfiles are dirty (see above). Check that ESP-IDF versions match.") + else() + message(WARNING "Component lockfile contents have changed (see above). This may be due to building with a different ESP-IDF version. Please mention this output if reporting an issue with MicroPython.") + endif() +endif() diff --git a/ports/esp32/README.md b/ports/esp32/README.md index dd4584772cf..2c09ebccd50 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -30,8 +30,9 @@ framework, aka SDK). The ESP-IDF includes the libraries and RTOS needed to manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. -The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.2, v5.2.2, v5.3, v5.4, v5.4.1 and v5.4.2. +The ESP-IDF changes quickly and MicroPython only supports certain versions. The +current recommended version of ESP-IDF for MicroPython is v5.4.2. MicroPython +also supports v5.2, v5.2.2, v5.3, v5.4 and v5.4.1. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/ports/esp32/lockfiles/README.md b/ports/esp32/lockfiles/README.md new file mode 100644 index 00000000000..a957b428f92 --- /dev/null +++ b/ports/esp32/lockfiles/README.md @@ -0,0 +1,12 @@ +# ESP-IDF Component Lockfiles + +This directory contains the exact versions of ESP-IDF components that have been +used to build MicroPython. It is updated by the [component version +solver](https://docs.espressif.com/projects/idf-component-manager/en/latest/guides/version_solver.html). + +Unless you change the `main/idf_component.yml` file for MicroPython ESP32, files +in this directory should only change contents if you build using a different +ESP-IDF version to the last time the file was updated. + +*Please do not commit changes to these files and submit PRs unless the PR needs +to change versions of components or ESP-IDF.* diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 new file mode 100644 index 00000000000..5c2b7827544 --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -0,0 +1,35 @@ +dependencies: + espressif/lan867x: + component_hash: 0ff9dae3affeff53811e7c8283e67c6d36dc0c03e3bc5102c0fba629e08bf6c4 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + targets: + - esp32 + - esp32p4 + version: 1.0.3 + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/lan867x +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 new file mode 100644 index 00000000000..df6ed2c1d89 --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -0,0 +1,21 @@ +dependencies: + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32c2 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 new file mode 100644 index 00000000000..a4c955d4763 --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -0,0 +1,21 @@ +dependencies: + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32c3 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 new file mode 100644 index 00000000000..fa81d8ad78d --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -0,0 +1,21 @@ +dependencies: + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32c6 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 new file mode 100644 index 00000000000..12430428e09 --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -0,0 +1,50 @@ +dependencies: + espressif/esp_tinyusb: + component_hash: 96d232ced7afe1976119b62f7fbf1944a2a78b36228ff6f7b9318394ac1153cc + dependencies: + - name: idf + require: private + version: '>=5.0' + - name: espressif/tinyusb + registry_url: https://components.espressif.com + require: public + version: '>=0.14.2' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.7.6~1 + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + espressif/tinyusb: + component_hash: aa65639878f27a44d349044afd9c3fc134a92bd560874fdac1d836019b5c07ca + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + version: 0.18.0~4 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/esp_tinyusb +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32s2 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 new file mode 100644 index 00000000000..215346dbd44 --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -0,0 +1,50 @@ +dependencies: + espressif/esp_tinyusb: + component_hash: 96d232ced7afe1976119b62f7fbf1944a2a78b36228ff6f7b9318394ac1153cc + dependencies: + - name: idf + require: private + version: '>=5.0' + - name: espressif/tinyusb + registry_url: https://components.espressif.com + require: public + version: '>=0.14.2' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.7.6~1 + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + espressif/tinyusb: + component_hash: aa65639878f27a44d349044afd9c3fc134a92bd560874fdac1d836019b5c07ca + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + version: 0.18.0~4 + idf: + source: + type: idf + version: 5.4.2 +direct_dependencies: +- espressif/esp_tinyusb +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32s3 +version: 2.0.0 diff --git a/tools/ci.sh b/tools/ci.sh index 115442ffd0f..69e59508737 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -171,14 +171,17 @@ function ci_cc3200_build { ######################################################################################## # ports/esp32 -# GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) -IDF_VER=v5.4.2 +# GitHub tag of ESP-IDF to use for CI, extracted from the esp32 dependency lockfile +# This should end up as a tag name like vX.Y.Z +# (note: This hacky parsing can be replaced with 'yq' once Ubuntu >=24.04 is in use) +IDF_VER=v$(grep -A10 "idf:" ports/esp32/lockfiles/dependencies.lock.esp32 | grep "version:" | head -n1 | sed -E 's/ +version: //') PYTHON=$(command -v python3 2> /dev/null) PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) export IDF_CCACHE_ENABLE=1 function ci_esp32_idf_setup { + echo "Using ESP-IDF version $IDF_VER" git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git # doing a treeless clone isn't quite as good as --shallow-submodules, but it # is smaller than full clones and works when the submodule commit isn't a head. From 3ec2e367decd8da968b1b85b65264bbab367bc9c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 27 Aug 2025 15:34:12 +1000 Subject: [PATCH 1185/2098] shared/tinyusb: Fix hang from new tx_overwritabe_if_not_connected flag. This flag is in the main branch of TinyUSB, included in Espressif since their v0.18.0~3 component release (but it's not actually in TinyUSB v0.18.0 release). Setting the flag is needed for the USB device not to block waiting for space in the FIFO if the host is disconnected. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- shared/tinyusb/mp_usbd.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 866ef8503a5..4e3403b3569 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -45,7 +45,18 @@ static inline void mp_usbd_init_tud(void) { tusb_init(); #if MICROPY_HW_USB_CDC - tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, .tx_persistent = 1 }; + tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, + .tx_persistent = 1, + + // This config flag is unreleased in TinyUSB >v0.18.0 + // but included in Espressif's TinyUSB component since v0.18.0~3 + // + // Versioning issue reported as + // https://github.com/espressif/esp-usb/issues/236 + #if TUSB_VERSION_NUMBER > 1800 || defined(ESP_PLATFORM) + .tx_overwritabe_if_not_connected = 1, + #endif + }; tud_cdc_configure_fifo(&cfg); #endif } From a43e38544bf707831350dd53091c7d47eddd41bf Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 10 Sep 2025 09:35:36 +1000 Subject: [PATCH 1186/2098] shared/tinyusb/mp_usbd_cdc: Rewrite USB CDC TX loop. This is related to the previous commit (where due to the new config flag this loop could end up stuck indefinitely if the USB host was disconnected). The previous loop could maybe still get stuck if the low-level USB state and the high-level USB state got out of sync. (Not clearly possible, but hard to say definitely not possible.) To be "belts and braces" careful: - Always run mp_usbd_task() each time around the loop to progress the state. - Always evaluate the timeout if we fail to write anything to the FIFO. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- shared/tinyusb/mp_usbd_cdc.c | 45 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/shared/tinyusb/mp_usbd_cdc.c b/shared/tinyusb/mp_usbd_cdc.c index b4151f685c4..9b380acefc0 100644 --- a/shared/tinyusb/mp_usbd_cdc.c +++ b/shared/tinyusb/mp_usbd_cdc.c @@ -98,32 +98,45 @@ mp_uint_t mp_usbd_cdc_tx_strn(const char *str, mp_uint_t len) { if (!tusb_inited()) { return 0; } + mp_uint_t last_write = mp_hal_ticks_ms(); size_t i = 0; while (i < len) { uint32_t n = len - i; - if (n > CFG_TUD_CDC_EP_BUFSIZE) { - n = CFG_TUD_CDC_EP_BUFSIZE; - } + if (tud_cdc_connected()) { - // If CDC port is connected but the buffer is full, wait for up to USC_CDC_TIMEOUT ms. - mp_uint_t t0 = mp_hal_ticks_ms(); - while (n > tud_cdc_write_available() && (mp_uint_t)(mp_hal_ticks_ms() - t0) < MICROPY_HW_USB_CDC_TX_TIMEOUT) { - mp_event_wait_ms(1); - - // Explicitly run the USB stack as the scheduler may be locked (eg we - // are in an interrupt handler), while there is data pending. - mp_usbd_task(); - } // Limit write to available space in tx buffer when connected. + // + // (If not connected then we write everything to the fifo, expecting + // it to overwrite old data so it will have latest data buffered + // when host connects.) n = MIN(n, tud_cdc_write_available()); - if (n == 0) { - break; - } } - // When not connected we always write to usb fifo, ensuring it has latest data. + uint32_t n2 = tud_cdc_write(str + i, n); tud_cdc_write_flush(); i += n2; + + if (i < len) { + if (n2 > 0) { + // reset the timeout each time we successfully write to the FIFO + last_write = mp_hal_ticks_ms(); + } else { + if ((mp_uint_t)(mp_hal_ticks_ms() - last_write) >= MICROPY_HW_USB_CDC_TX_TIMEOUT) { + break; // Timeout + } + + if (tud_cdc_connected()) { + // If we know we're connected then we can wait for host to make + // more space + mp_event_wait_ms(1); + } + } + + // Always explicitly run the USB stack as the scheduler may be + // locked (eg we are in an interrupt handler), while there is data + // or a state change pending. + mp_usbd_task(); + } } return i; } From 371db85c8e9c29ac66d772e051c7a167782e8407 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Aug 2025 17:26:21 +1000 Subject: [PATCH 1187/2098] esp32/machine_uart: Convert machine.UART objects to static instances. Makes machine.UART objects static instances (similar to other ports), adds machine_uart_deinit_all() function for cleanup on soft reset. - Fixes the case where the OS-level uart_event_task is leaked (along with 2KB of heap) when a UART object is garbage collected (including after a soft reset). - Fixes hard fault if a previous UART irq() fires after the UART object is garbage collected (including after a soft reset), but before any new UART object is instantiated for the same UART number. - Constructing multiple UART objects for the same UART now returns the same instance multiple times, not different instances. - Was also able to streamline deinit/init to only install the driver once, rather than install-with-defaults/uninstall/reinstall. Alternative would be to add a finaliser, but this is more consistent with how most other UART objects are implemented. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_uart.c | 222 ++++++++++++++++++++++--------------- ports/esp32/main.c | 3 + ports/esp32/modmachine.h | 1 + 3 files changed, 136 insertions(+), 90 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index c4dab2cae5c..55987c9b627 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -68,6 +68,9 @@ enum { RXIDLE_ALERT, }; +// RXIDLE irq feature uses this machine.Timer id +#define RXIDLE_TIMER_IDX 0 + typedef struct _machine_uart_obj_t { mp_obj_base_t base; uart_port_t uart_num; @@ -96,6 +99,14 @@ typedef struct _machine_uart_obj_t { static const char *_parity_name[] = {"None", "1", "0"}; +static bool uart_is_repl(uart_port_t uart_num) { + #if MICROPY_HW_ENABLE_UART_REPL + return uart_num == MICROPY_HW_UART_REPL; + #else + return false; + #endif +} + /******************************************************************************/ // MicroPython bindings for UART @@ -245,13 +256,67 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // wait for all data to be transmitted before changing settings uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); + // If UART is being freshly initialised then restore object defaults + if (!uart_is_driver_installed(self->uart_num)) { + self->bits = 8; + self->parity = 0; + self->stop = 1; + self->rts = UART_PIN_NO_CHANGE; + self->cts = UART_PIN_NO_CHANGE; + self->txbuf = 256; + self->rxbuf = 256; // IDF minimum + self->timeout = 0; + self->timeout_char = 0; + self->invert = 0; + self->flowcontrol = 0; + self->uart_event_task = NULL; + self->uart_queue = NULL; + self->rxidle_state = RXIDLE_INACTIVE; + + // Set the MicroPython default UART pins. These may be overwritten with + // caller-provided pins, below + switch (self->uart_num) { + case UART_NUM_0: + self->rx = UART_PIN_NO_CHANGE; // GPIO 3 + self->tx = UART_PIN_NO_CHANGE; // GPIO 1 + break; + case UART_NUM_1: + self->rx = 9; + self->tx = 10; + break; + #if SOC_UART_HP_NUM > 2 + case UART_NUM_2: + self->rx = 16; + self->tx = 17; + break; + #endif + #if SOC_UART_LP_NUM >= 1 + case LP_UART_NUM_0: + self->rx = 4; + self->tx = 5; + break; + #endif + case UART_NUM_MAX: + assert(0); // Range is checked in mp_machine_uart_make_new, value should be unreachable + } + } + + // Default driver parameters, should correspond to values set above + uart_config_t uartcfg = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0, + .source_clk = UART_SOURCE_CLK, + }; + + if ((args[ARG_txbuf].u_int >= 0 && args[ARG_txbuf].u_int != self->txbuf) || (args[ARG_rxbuf].u_int >= 0 && args[ARG_rxbuf].u_int != self->rxbuf)) { - // must reinitialise driver to change the tx/rx buffer size - #if MICROPY_HW_ENABLE_UART_REPL - if (self->uart_num == MICROPY_HW_UART_REPL) { - mp_raise_ValueError(MP_ERROR_TEXT("UART buffer size is fixed")); + if (uart_is_repl(self->uart_num)) { + mp_raise_ValueError(MP_ERROR_TEXT("REPL UART buffer size is fixed")); } - #endif if (args[ARG_txbuf].u_int >= 0) { self->txbuf = args[ARG_txbuf].u_int; @@ -259,18 +324,26 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, if (args[ARG_rxbuf].u_int >= 0) { self->rxbuf = args[ARG_rxbuf].u_int; } - uart_config_t uartcfg = { - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 0, - .source_clk = UART_SOURCE_CLK, - }; - uint32_t baudrate; - check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); - uartcfg.baud_rate = baudrate; - check_esp_err(uart_get_word_length(self->uart_num, &uartcfg.data_bits)); - check_esp_err(uart_get_parity(self->uart_num, &uartcfg.parity)); - check_esp_err(uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits)); - mp_machine_uart_deinit(self); + + // must reinitialise driver to change the tx/rx buffer size + if (uart_is_driver_installed(self->uart_num)) { + // Update uartcfg with the current uart parameters + uint32_t baudrate; + check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); + uartcfg.baud_rate = baudrate; + check_esp_err(uart_get_word_length(self->uart_num, &uartcfg.data_bits)); + check_esp_err(uart_get_parity(self->uart_num, &uartcfg.parity)); + check_esp_err(uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits)); + // De-initialise the driver + mp_machine_uart_deinit(self); + } + } + + if (!uart_is_repl(self->uart_num) && !uart_is_driver_installed(self->uart_num)) { + // install the driver if needed - could be uninstalled for multiple reasons: + // 1. First time initialising this UART + // 2. Has been temporarily de-initialised to change the buffer size + // 3. Python code called .deinit() and now .init() check_esp_err(uart_param_config(self->uart_num, &uartcfg)); check_esp_err(uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); if (self->mp_irq_obj != NULL && self->mp_irq_obj->handler != mp_const_none) { @@ -399,6 +472,9 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } uint8_t uart_fifo_len = UART_HW_FIFO_LEN(self->uart_num); check_esp_err(uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, uart_fifo_len - uart_fifo_len / 4)); + + // discard any input from previous configuration + check_esp_err(uart_flush_input(self->uart_num)); } static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { @@ -409,73 +485,14 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg if (uart_num < 0 || uart_num >= UART_NUM_MAX) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) does not exist"), uart_num); } + _Static_assert(UART_NUM_MAX == SOC_UART_NUM, "SOC and driver constant should match"); - // Defaults - uart_config_t uartcfg = { - .baud_rate = 115200, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 0, - .source_clk = UART_SOURCE_CLK, - }; - - // create instance - machine_uart_obj_t *self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); - self->uart_num = uart_num; - self->bits = 8; - self->parity = 0; - self->stop = 1; - self->rts = UART_PIN_NO_CHANGE; - self->cts = UART_PIN_NO_CHANGE; - self->txbuf = 256; - self->rxbuf = 256; // IDF minimum - self->timeout = 0; - self->timeout_char = 0; - self->invert = 0; - self->flowcontrol = 0; - self->uart_event_task = NULL; - self->uart_queue = NULL; - self->rxidle_state = RXIDLE_INACTIVE; - - switch (uart_num) { - case UART_NUM_0: - self->rx = UART_PIN_NO_CHANGE; // GPIO 3 - self->tx = UART_PIN_NO_CHANGE; // GPIO 1 - break; - case UART_NUM_1: - self->rx = 9; - self->tx = 10; - break; - #if SOC_UART_HP_NUM > 2 - case UART_NUM_2: - self->rx = 16; - self->tx = 17; - break; - #endif - #if SOC_UART_LP_NUM >= 1 - case LP_UART_NUM_0: - self->rx = 4; - self->tx = 5; - #endif - - } - - #if MICROPY_HW_ENABLE_UART_REPL - // Only reset the driver if it's not the REPL UART. - if (uart_num != MICROPY_HW_UART_REPL) - #endif - { - // Remove any existing configuration - check_esp_err(uart_driver_delete(self->uart_num)); - self->uart_queue = NULL; - - // init the peripheral - // Setup - check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - - check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); + machine_uart_obj_t *self = MP_STATE_PORT(machine_uart_objs)[uart_num]; + if (self == NULL) { + // Allocate a new UART object + self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); + self->uart_num = uart_num; + // remaining fields are set from inside mp_machine_uart_init_helper() } mp_map_t kw_args; @@ -485,6 +502,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // Make sure pins are connected. check_esp_err(uart_set_pin(self->uart_num, self->tx, self->rx, self->rts, self->cts)); + MP_STATE_PORT(machine_uart_objs)[uart_num] = self; + return MP_OBJ_FROM_PTR(self); } @@ -493,8 +512,31 @@ static void mp_machine_uart_deinit(machine_uart_obj_t *self) { vTaskDelete(self->uart_event_task); self->uart_event_task = NULL; } - check_esp_err(uart_driver_delete(self->uart_num)); - self->uart_queue = NULL; + if (self->rxidle_timer != NULL) { + machine_timer_disable(self->rxidle_timer); + if (self->rxidle_state > RXIDLE_STANDBY) { + // Currently deinit(),init() sequence resumes any previously + // configured irqs, and we currently also rely on this when changing + // buffer size - so keep rxidle in standby if it was active + self->rxidle_state = RXIDLE_STANDBY; + } + } + // Only stop the ESP-IDF driver entirely if it's not the REPL, + // (if it's the REPL then it shouldn't have any ISR configured anyway.) + if (!uart_is_repl(self->uart_num)) { + check_esp_err(uart_driver_delete(self->uart_num)); + self->uart_queue = NULL; + } +} + +void machine_uart_deinit_all(void) { + for (int i = 0; i < UART_NUM_MAX; i++) { + machine_uart_obj_t *uart = MP_STATE_PORT(machine_uart_objs)[i]; + if (uart) { + mp_machine_uart_deinit(uart); + MP_STATE_PORT(machine_uart_objs)[i] = NULL; + } + } } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { @@ -574,11 +616,9 @@ static const mp_irq_methods_t uart_irq_methods = { }; static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { - #if MICROPY_HW_ENABLE_UART_REPL - if (self->uart_num == MICROPY_HW_UART_REPL) { - mp_raise_ValueError(MP_ERROR_TEXT("UART does not support IRQs")); + if (uart_is_repl(self->uart_num)) { + mp_raise_ValueError(MP_ERROR_TEXT("REPL UART does not support IRQs")); } - #endif if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; @@ -605,7 +645,7 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args } self->mp_irq_obj->ishard = false; self->mp_irq_trigger = trigger; - self->rxidle_timer = machine_timer_create(0); + self->rxidle_timer = machine_timer_create(RXIDLE_TIMER_IDX); uart_irq_configure_timer(self, trigger); // Start a task for handling events @@ -700,3 +740,5 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint } return ret; } + +MP_REGISTER_ROOT_POINTER(void *machine_uart_objs[SOC_UART_NUM]); diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 1523e07f916..bd5775bc663 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -180,6 +180,9 @@ void mp_task(void *pvParameter) { MP_STATE_PORT(espnow_singleton) = NULL; #endif + // Deinit uart before timers, as esp32 uart + // depends on a timer instance + machine_uart_deinit_all(); machine_timer_deinit_all(); #if MICROPY_PY_ESP32_PCNT diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index 9a5e6a566eb..12e0d680024 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -20,6 +20,7 @@ void machine_pins_deinit(void); void machine_pwm_deinit_all(void); // TODO: void machine_rmt_deinit_all(void); void machine_timer_deinit_all(void); +void machine_uart_deinit_all(void); void machine_i2s_init0(); #endif // MICROPY_INCLUDED_ESP32_MODMACHINE_H From 1aaf6eddd82170209ca90586160ac265b9412229 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 27 Aug 2025 17:40:55 +1000 Subject: [PATCH 1188/2098] tools/mpremote: Don't apply Espressif DTR/RTS quirk to TinyUSB CDC dev. The DTR quirk workaround from dea949e86 is needed for the Espressif Serial/JTAG device, but not for TinyUSB - in fact DTR must be set for TinyUSB to correctly determine if the serial port is open (and leads to issues with lost bytes otherwise). See discussion in PR #17999. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/mpremote/mpremote/transport_serial.py | 36 +++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index e2490a7caf8..a26183680ca 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -36,11 +36,37 @@ # as a command line tool and a library for interacting with devices. import ast, io, os, re, struct, sys, time +import serial +import serial.tools.list_ports from errno import EPERM from .console import VT_ENABLED from .transport import TransportError, TransportExecError, Transport VID_ESPRESSIF = 0x303A # Espressif Incorporated +PID_ESPRESSIF_SERIAL_JTAG = 0x1001 # Serial/JTAG peripheral of ESP32-S3,C3,C6 + + +def has_espressif_dtr_quirk(devicename): + """ESP8266 and ESP32 dev boards use the DTR and RTS lines to trigger reset & + reset into bootloader mode. This can causes spurious reset issues on Windows. + + Apply the quirk to any USB/Serial chip on Windows that isn't using the + Microsoft CDC-ACM driver, or to the integrated Espressif Serial/JTAG device. + + Don't apply it to Espressif boards running TinyUSB, as TinyUSB uses DTR + to determine if the CDC port is open (and there's no spurious reset issue). + """ + portinfo = list(serial.tools.list_ports.grep(devicename)) # type: ignore + if not portinfo: + return False + + def port_attr(name): + return getattr(portinfo[0], name, None) + + return (port_attr("vid"), port_attr("pid")) == ( + VID_ESPRESSIF, + PID_ESPRESSIF_SERIAL_JTAG, + ) or port_attr("manufacturer") != "Microsoft" class SerialTransport(Transport): @@ -52,9 +78,6 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None self.device_name = device self.mounted = False - import serial - import serial.tools.list_ports - # Set options, and exclusive if pyserial supports it serial_kwargs = { "baudrate": baudrate, @@ -72,12 +95,7 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None elif os.name == "nt": self.serial = serial.Serial(**serial_kwargs) self.serial.port = device - portinfo = list(serial.tools.list_ports.grep(device)) # type: ignore - if portinfo and ( - getattr(portinfo[0], "vid", 0) == VID_ESPRESSIF - or getattr(portinfo[0], "manufacturer", "") != "Microsoft" - ): - # ESP8266/ESP32 boards use RTS/CTS for flashing and boot mode selection. + if has_espressif_dtr_quirk(device): # DTR False: to avoid using the reset button will hang the MCU in bootloader mode # RTS False: to prevent pulses on rts on serial.close() that would POWERON_RESET an ESPxx self.serial.dtr = False # DTR False = gpio0 High = Normal boot From 9e89c752cb911ec9b4d3dd4d2edd6714e7ad09aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 10 Jul 2025 18:57:55 +0200 Subject: [PATCH 1189/2098] py/makeversionhdr.py: Always abbreviate Git hashes to same length. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Git hash is embedded in the version number. The hash is abbreviated by Git. This commit changes the length of the Git hash abbreviation to a fixed number, so that the length of the version string no longer varies based on external factors (it can still vary, but will now be at least 10 characters). This change is made because builds of the same MicroPython commit on multiple machines were sometimes giving a version string with different lengths, eg due to commits on other local branches having a clashing abbreviated hash. This change may also help the code size report to be more consistent, because it will less often be impacted by random changes in the version string length, at the cost of always being a few bytes longer. Signed-off-by: Daniël van de Giessen --- py/makeversionhdr.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 406a061a094..5c73501b3bb 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -31,7 +31,16 @@ def get_version_info_from_git(repo_path): # Note: git describe doesn't work if no tag is available try: git_tag = subprocess.check_output( - ["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"], + [ + "git", + "describe", + "--tags", + "--dirty", + "--always", + "--match", + "v[1-9].*", + "--abbrev=10", + ], cwd=repo_path, stderr=subprocess.STDOUT, universal_newlines=True, From a21d3f19753b02d452ece551d728944c70d5d572 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 29 Aug 2025 01:27:04 -0400 Subject: [PATCH 1190/2098] py/obj: Update with_finaliser version to match mp_obj_malloc_var. Cleans up 24234937747e6d7fd920d21358fb26b826398e1a, and fixes builds that include LFS but not MICROPY_ENABLE_FINALISER. Signed-off-by: Anson Mansfield --- extmod/vfs_lfsx_file.c | 4 ++-- py/obj.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extmod/vfs_lfsx_file.c b/extmod/vfs_lfsx_file.c index ab5cce50088..56daa53e068 100644 --- a/extmod/vfs_lfsx_file.c +++ b/extmod/vfs_lfsx_file.c @@ -90,9 +90,9 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod } #if LFS_BUILD_VERSION == 1 - MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->prog_size, type); + MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->prog_size, type); #else - MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->cache_size, type); + MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->cache_size, type); #endif o->vfs = self; #if !MICROPY_GC_CONSERVATIVE_CLEAR diff --git a/py/obj.h b/py/obj.h index de740bf4ccf..72def663d8d 100644 --- a/py/obj.h +++ b/py/obj.h @@ -947,11 +947,11 @@ void *mp_obj_malloc_helper(size_t num_bytes, const mp_obj_type_t *type); // Object allocation macros for allocating objects that have a finaliser. #if MICROPY_ENABLE_FINALISER #define mp_obj_malloc_with_finaliser(struct_type, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(sizeof(struct_type), obj_type)) -#define mp_obj_malloc_var_with_finaliser(struct_type, var_type, var_num, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(sizeof(struct_type) + sizeof(var_type) * (var_num), obj_type)) +#define mp_obj_malloc_var_with_finaliser(struct_type, var_field, var_type, var_num, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(offsetof(struct_type, var_field) + sizeof(var_type) * (var_num), obj_type)) void *mp_obj_malloc_with_finaliser_helper(size_t num_bytes, const mp_obj_type_t *type); #else #define mp_obj_malloc_with_finaliser(struct_type, obj_type) mp_obj_malloc(struct_type, obj_type) -#define mp_obj_malloc_var_with_finaliser(struct_type, var_type, var_num, obj_type) mp_obj_malloc_var(struct_type, var_type, var_num, obj_type) +#define mp_obj_malloc_var_with_finaliser(struct_type, var_field, var_type, var_num, obj_type) mp_obj_malloc_var(struct_type, var_field, var_type, var_num, obj_type) #endif // These macros are derived from more primitive ones and are used to From 1d90577b7e1fa5c4f452471378dd887152a4825c Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sat, 30 Aug 2025 14:37:35 -0400 Subject: [PATCH 1191/2098] py/gc: Clean up usage of GC_ALLOC_FLAG_HAS_FINALISER flag. The calls signature for gc_malloc was changed in 5ed578e5b48730606536ded9a711223ae9a70262, without cleaning up existing code on the rationale that the previous bool is automatically converted to an int with the same meaning. This commit goes back and cleans up existing invocations to make their behavior more readable in a modern context. Signed-off-by: Anson Mansfield --- ports/esp8266/posix_helpers.c | 2 +- ports/unix/coverage.c | 2 +- py/malloc.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/esp8266/posix_helpers.c b/ports/esp8266/posix_helpers.c index b72c4ff9d66..11c9dc36dbd 100644 --- a/ports/esp8266/posix_helpers.c +++ b/ports/esp8266/posix_helpers.c @@ -33,7 +33,7 @@ // Functions for external libs like axTLS, BerkeleyDB, etc. void *malloc(size_t size) { - void *p = gc_alloc(size, false); + void *p = gc_alloc(size, 0); if (p == NULL) { // POSIX requires ENOMEM to be set in case of error errno = ENOMEM; diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index b7c3d2c25ef..e47558689d1 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -270,7 +270,7 @@ static mp_obj_t extra_coverage(void) { gc_unlock(); // using gc_realloc to resize to 0, which means free the memory - void *p = gc_alloc(4, false); + void *p = gc_alloc(4, 0); mp_printf(&mp_plat_print, "%p\n", gc_realloc(p, 0, false)); // calling gc_nbytes with a non-heap pointer diff --git a/py/malloc.c b/py/malloc.c index 05daeb35d09..5135dc26bd3 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -56,8 +56,8 @@ #undef malloc #undef free #undef realloc -#define malloc(b) gc_alloc((b), false) -#define malloc_with_finaliser(b) gc_alloc((b), true) +#define malloc(b) gc_alloc((b), 0) +#define malloc_with_finaliser(b) gc_alloc((b), GC_ALLOC_FLAG_HAS_FINALISER) #define free gc_free #define realloc(ptr, n) gc_realloc(ptr, n, true) #define realloc_ext(ptr, n, mv) gc_realloc(ptr, n, mv) From 9728538c3ae81c3e9932126e77ba469e36b30863 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 30 Aug 2025 10:51:53 -0500 Subject: [PATCH 1192/2098] py/compile: Throw SyntaxError instead of asserting. This condition corresponds to invalid asm code like ``` @micropython.asm_rv32 def l(): a=di(a2, a2, -1) ``` and possibly other forms where nodes[0] is not a STRUCT. Signed-off-by: Jeff Epler --- py/compile.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/py/compile.c b/py/compile.c index 7a1151bcd66..945ee2b2dee 100644 --- a/py/compile.c +++ b/py/compile.c @@ -3277,7 +3277,9 @@ static void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind } // check structure of parse node - assert(MP_PARSE_NODE_IS_STRUCT(pns2->nodes[0])); + if (!MP_PARSE_NODE_IS_STRUCT(pns2->nodes[0])) { + goto not_an_instruction; + } if (!MP_PARSE_NODE_IS_NULL(pns2->nodes[1])) { goto not_an_instruction; } From 7630ef0240a543883095635f5b43a81a11f79e20 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 10 Sep 2025 07:45:50 -0500 Subject: [PATCH 1193/2098] tests: Add a test for invalid syntax in @micropython.asm. Signed-off-by: Jeff Epler --- tests/inlineasm/thumb/asmerrors.py | 4 ++++ tests/inlineasm/thumb/asmerrors.py.exp | 1 + 2 files changed, 5 insertions(+) create mode 100644 tests/inlineasm/thumb/asmerrors.py create mode 100644 tests/inlineasm/thumb/asmerrors.py.exp diff --git a/tests/inlineasm/thumb/asmerrors.py b/tests/inlineasm/thumb/asmerrors.py new file mode 100644 index 00000000000..a26f9322540 --- /dev/null +++ b/tests/inlineasm/thumb/asmerrors.py @@ -0,0 +1,4 @@ +try: + exec("@micropython.asm_thumb\ndef l():\n a = di(a2, a2, -1)") +except SyntaxError as e: + print(e) diff --git a/tests/inlineasm/thumb/asmerrors.py.exp b/tests/inlineasm/thumb/asmerrors.py.exp new file mode 100644 index 00000000000..7590f171e51 --- /dev/null +++ b/tests/inlineasm/thumb/asmerrors.py.exp @@ -0,0 +1 @@ +expecting an assembler instruction From 8e6cd4b58e338dd5c3aab7503d6cfe6260d02da5 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 11:29:29 -0400 Subject: [PATCH 1194/2098] tests/cpydiff: Document lack of OSError errno subtype mapping. Signed-off-by: Anson Mansfield --- tests/cpydiff/types_oserror_errnomap.py | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/cpydiff/types_oserror_errnomap.py diff --git a/tests/cpydiff/types_oserror_errnomap.py b/tests/cpydiff/types_oserror_errnomap.py new file mode 100644 index 00000000000..6627bd2af4a --- /dev/null +++ b/tests/cpydiff/types_oserror_errnomap.py @@ -0,0 +1,48 @@ +""" +categories: Types,OSError +description: OSError constructor returns a plain OSError for all errno values, rather than a relevant subtype. +cause: MicroPython does not include the CPython-standard OSError subclasses. +workaround: Catch OSError and use its errno attribute to discriminate the cause. +""" + +import errno + +errno_list = [ # i.e. the set implemented by micropython + errno.EPERM, + errno.ENOENT, + errno.EIO, + errno.EBADF, + errno.EAGAIN, + errno.ENOMEM, + errno.EACCES, + errno.EEXIST, + errno.ENODEV, + errno.EISDIR, + errno.EINVAL, + errno.EOPNOTSUPP, + errno.EADDRINUSE, + errno.ECONNABORTED, + errno.ECONNRESET, + errno.ENOBUFS, + errno.ENOTCONN, + errno.ETIMEDOUT, + errno.ECONNREFUSED, + errno.EHOSTUNREACH, + errno.EALREADY, + errno.EINPROGRESS, +] + + +def errno_output_type(n): + try: + raise OSError(n, "") + except OSError as e: + return f"{type(e).__name__}" + except Exception as e: + return f"non-OSError {type(e).__name__}" + else: + return "no error" + + +for n in errno_list: + print(errno.errorcode[n], "=", errno_output_type(n)) From 52d25928d57ed37d8ab675a5092fbe13bdae69a4 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 11:11:58 -0400 Subject: [PATCH 1195/2098] tests/cpydiff: Document ENOTSUP vs EOPNOTSUPP. Signed-off-by: Anson Mansfield --- tests/cpydiff/modules_errno_enotsup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/cpydiff/modules_errno_enotsup.py diff --git a/tests/cpydiff/modules_errno_enotsup.py b/tests/cpydiff/modules_errno_enotsup.py new file mode 100644 index 00000000000..80e5ad9d032 --- /dev/null +++ b/tests/cpydiff/modules_errno_enotsup.py @@ -0,0 +1,10 @@ +""" +categories: Modules,errno +description: MicroPython does not include ``ENOTSUP`` as a name for errno 95. +cause: MicroPython does not implement the ``ENOTSUP`` canonical alias for ``EOPNOTSUPP`` added in CPython 3.2. +workaround: Use ``errno.EOPNOTSUPP`` in place of ``errno.ENOTSUP``. +""" + +import errno + +print(f"{errno.errorcode[errno.EOPNOTSUPP]=!s}") From 2327972947d6f5119ec13a95139fff090351c132 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 19 Feb 2025 12:10:29 -0500 Subject: [PATCH 1196/2098] tests/cpydiff: Test for PEP487 __init_subclass__. This commit documents and verifies the current absence of `__init_subclass__` functionality, in anticipation of a possible future PEP487 'metaclasses lite' patch. The main `core_class_initsubclass.py` test verifies if the method is automatically called, while the other three verify other orthogonal properties it should have: if the method is an implicit classmethod; if MicroPython supplies the base case for the usual recursive function body the PEP encourages; and if kwargs inheritance parameters work correctly. Signed-off-by: Anson Mansfield --- tests/cpydiff/core_class_initsubclass.py | 21 +++++++++++++ ...core_class_initsubclass_autoclassmethod.py | 31 +++++++++++++++++++ .../cpydiff/core_class_initsubclass_kwargs.py | 22 +++++++++++++ .../cpydiff/core_class_initsubclass_super.py | 22 +++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 tests/cpydiff/core_class_initsubclass.py create mode 100644 tests/cpydiff/core_class_initsubclass_autoclassmethod.py create mode 100644 tests/cpydiff/core_class_initsubclass_kwargs.py create mode 100644 tests/cpydiff/core_class_initsubclass_super.py diff --git a/tests/cpydiff/core_class_initsubclass.py b/tests/cpydiff/core_class_initsubclass.py new file mode 100644 index 00000000000..8683271dcb0 --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass.py @@ -0,0 +1,21 @@ +""" +categories: Core,Classes +description: ``__init_subclass__`` isn't automatically called. +cause: MicroPython does not currently implement PEP 487. +workaround: Manually call ``__init_subclass__`` after class creation if needed. e.g.:: + + class A(Base): + pass + A.__init_subclass__() + +""" + + +class Base: + @classmethod + def __init_subclass__(cls): + print(f"Base.__init_subclass__({cls.__name__})") + + +class A(Base): + pass diff --git a/tests/cpydiff/core_class_initsubclass_autoclassmethod.py b/tests/cpydiff/core_class_initsubclass_autoclassmethod.py new file mode 100644 index 00000000000..b2f7628976c --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_autoclassmethod.py @@ -0,0 +1,31 @@ +""" +categories: Core,Classes +description: ``__init_subclass__`` isn't an implicit classmethod. +cause: MicroPython does not currently implement PEP 487. ``__init_subclass__`` is not currently in the list of special-cased class/static methods. +workaround: Decorate declarations of ``__init_subclass__`` with ``@classmethod``. +""" + + +def regularize_spelling(text, prefix="bound_"): + # for regularizing across the CPython "method" vs MicroPython "bound_method" spelling for the type of a bound classmethod + if text.startswith(prefix): + return text[len(prefix) :] + return text + + +class A: + def __init_subclass__(cls): + pass + + @classmethod + def manual_decorated(cls): + pass + + +a = type(A.__init_subclass__).__name__ +b = type(A.manual_decorated).__name__ + +print(regularize_spelling(a)) +print(regularize_spelling(b)) +if a != b: + print("FAIL") diff --git a/tests/cpydiff/core_class_initsubclass_kwargs.py b/tests/cpydiff/core_class_initsubclass_kwargs.py new file mode 100644 index 00000000000..ed5157afeae --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_kwargs.py @@ -0,0 +1,22 @@ +""" +categories: Core,Classes +description: MicroPython doesn't support parameterized ``__init_subclass__`` class customization. +cause: MicroPython does not currently implement PEP 487. The MicroPython syntax tree does not include a kwargs node after the class inheritance list. +workaround: Use class variables or another mechanism to specify base-class customizations. +""" + + +class Base: + @classmethod + def __init_subclass__(cls, arg=None, **kwargs): + cls.init_subclass_was_called = True + print(f"Base.__init_subclass__({cls.__name__}, {arg=!r}, {kwargs=!r})") + + +class A(Base, arg="arg"): + pass + + +# Regularize across MicroPython not automatically calling __init_subclass__ either. +if not getattr(A, "init_subclass_was_called", False): + A.__init_subclass__() diff --git a/tests/cpydiff/core_class_initsubclass_super.py b/tests/cpydiff/core_class_initsubclass_super.py new file mode 100644 index 00000000000..d18671a74bf --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_super.py @@ -0,0 +1,22 @@ +""" +categories: Core,Classes +description: ``__init_subclass__`` can't be defined a cooperatively-recursive way. +cause: MicroPython does not currently implement PEP 487. The base object type does not have an ``__init_subclass__`` implementation. +workaround: Omit the recursive ``__init_subclass__`` call unless it's known that the grandparent also defines it. +""" + + +class Base: + @classmethod + def __init_subclass__(cls, **kwargs): + cls.init_subclass_was_called = True + super().__init_subclass__(**kwargs) + + +class A(Base): + pass + + +# Regularize across MicroPython not automatically calling __init_subclass__ either. +if not getattr(A, "init_subclass_was_called", False): + A.__init_subclass__() From c224e976f733657058b2c10270470edb9c0657dc Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 12 Jun 2024 07:58:02 +0200 Subject: [PATCH 1197/2098] extmod/modplatform: Expose CPU features/extensions. This adds the ability to expose CPU-specific features/extensions to scripts when the `platform` module is compiled in, by implementing `platform.processor()`. Right now this is only available on bare-metal RV32 and RV64. Signed-off-by: Alessandro Gatti --- docs/library/platform.rst | 8 ++++++++ extmod/modplatform.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/docs/library/platform.rst b/docs/library/platform.rst index c091477d84c..c19ef0f5df5 100644 --- a/docs/library/platform.rst +++ b/docs/library/platform.rst @@ -36,3 +36,11 @@ Functions Returns a tuple of strings *(lib, version)*, where *lib* is the name of the libc that MicroPython is linked to, and *version* the corresponding version of this libc. + +.. function:: processor() + + Returns a string with a detailed name of the processor, if one is available. + If no name for the processor is known, it will return an empty string + instead. + + This is currently available only on RISC-V targets (both 32 and 64 bits). diff --git a/extmod/modplatform.c b/extmod/modplatform.c index c6d4d31b97e..e1f5476c3b3 100644 --- a/extmod/modplatform.c +++ b/extmod/modplatform.c @@ -61,11 +61,49 @@ static mp_obj_t platform_libc_ver(size_t n_args, const mp_obj_t *pos_args, mp_ma } static MP_DEFINE_CONST_FUN_OBJ_KW(platform_libc_ver_obj, 0, platform_libc_ver); +#ifdef __riscv +static mp_obj_t platform_processor(void) { + #if (__riscv_xlen <= 64) && !defined(__linux__) + uintptr_t misa_csr = 0; + + // Load the MISA CSR directly. + __asm volatile ( + "csrr %0, misa \n" + : "+r" (misa_csr) + : + : + ); + + char processor_buffer[31] = { // "RV32"/"RV64" + up to 26 chars. + #if (__riscv_xlen < 64) + "RV32" + #else + "RV64" + #endif + }; + mp_uint_t offset = 4; + for (mp_uint_t bit = 0; bit < 26; bit++) { + if (misa_csr & (1U << bit)) { + processor_buffer[offset++] = 'A' + bit; + } + } + + return mp_obj_new_str(processor_buffer, offset); + #else + return MP_OBJ_NEW_QSTR(MP_QSTR_); + #endif +} +static MP_DEFINE_CONST_FUN_OBJ_0(platform_processor_obj, platform_processor); +#endif + static const mp_rom_map_elem_t modplatform_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_platform) }, { MP_ROM_QSTR(MP_QSTR_platform), MP_ROM_PTR(&platform_platform_obj) }, { MP_ROM_QSTR(MP_QSTR_python_compiler), MP_ROM_PTR(&platform_python_compiler_obj) }, { MP_ROM_QSTR(MP_QSTR_libc_ver), MP_ROM_PTR(&platform_libc_ver_obj) }, + #ifdef __riscv + { MP_ROM_QSTR(MP_QSTR_processor), MP_ROM_PTR(&platform_processor_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(modplatform_globals, modplatform_globals_table); From 55ecaf14a0154e449fbbbaa19f26f55d81d5ee12 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 15 Aug 2025 08:22:49 +0200 Subject: [PATCH 1198/2098] extmod/machine_uart: Return from read()/write() at the first timeout. Do not try to read/write again after timeout happened once. Fixes issue #17611. Signed-off-by: robert-hh --- extmod/machine_uart.c | 4 ++-- ports/cc3200/mods/pybuart.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extmod/machine_uart.c b/extmod/machine_uart.c index b62f5a49c46..77d5ec01f4a 100644 --- a/extmod/machine_uart.c +++ b/extmod/machine_uart.c @@ -140,10 +140,10 @@ static const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_uart_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read1_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write1_obj) }, { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, { MP_ROM_QSTR(MP_QSTR_txdone), MP_ROM_PTR(&machine_uart_txdone_obj) }, diff --git a/ports/cc3200/mods/pybuart.c b/ports/cc3200/mods/pybuart.c index eb2b3754f74..5b71f02e29f 100644 --- a/ports/cc3200/mods/pybuart.c +++ b/ports/cc3200/mods/pybuart.c @@ -579,13 +579,13 @@ static const mp_rom_map_elem_t pyb_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_txdone), MP_ROM_PTR(&machine_uart_txdone_obj) }, /// \method read([nbytes]) - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read1_obj) }, /// \method readline() { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, /// \method readinto(buf[, nbytes]) { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, /// \method write(buf) - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write1_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, // class constants From c50f9cbb4244a4aef2a77098939af3b7eb7410b3 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 16 Aug 2025 21:26:06 +0200 Subject: [PATCH 1199/2098] esp32/machine_uart: Handle properly the timeout_char argument. Before, it was ignored. Tested with ESP32, ESP32S3, ESP32C6. Signed-off-by: robert-hh --- ports/esp32/machine_uart.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 55987c9b627..36b5bcb0ff9 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -668,19 +668,18 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t return 0; } - TickType_t time_to_wait; - if (self->timeout == 0) { - time_to_wait = 0; - } else { - time_to_wait = pdMS_TO_TICKS(self->timeout); - } + TickType_t time_to_wait = self->timeout > 0 ? pdMS_TO_TICKS(self->timeout) : 0; - bool release_gil = time_to_wait > 0; + bool release_gil = (self->timeout + self->timeout_char) > 0; if (release_gil) { MP_THREAD_GIL_EXIT(); } - int bytes_read = uart_read_bytes(self->uart_num, buf_in, size, time_to_wait); + int bytes_read = uart_read_bytes(self->uart_num, buf_in, 1, time_to_wait); + if (size > 1 && bytes_read != 0) { + time_to_wait = self->timeout_char > 0 ? pdMS_TO_TICKS(self->timeout_char) : 0; + bytes_read += uart_read_bytes(self->uart_num, buf_in + 1, size - 1, time_to_wait); + } if (release_gil) { MP_THREAD_GIL_ENTER(); From 66fb82e44e0beecb64225189ec8fb788b2076fb4 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 16 Aug 2025 14:43:37 +0200 Subject: [PATCH 1200/2098] py/stream: Add a stream.readinto1() method for machine.UART. Avoiding the double timeout when used with the UART class. `stream.readinto1()` returns after the first timeout. Fixes issue #17611. Signed-off-by: robert-hh --- extmod/machine_uart.c | 2 +- ports/cc3200/mods/pybuart.c | 2 +- py/stream.c | 13 +++++++++++-- py/stream.h | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/extmod/machine_uart.c b/extmod/machine_uart.c index 77d5ec01f4a..c7e761ccdb6 100644 --- a/extmod/machine_uart.c +++ b/extmod/machine_uart.c @@ -142,7 +142,7 @@ static const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read1_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, - { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto1_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write1_obj) }, { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, diff --git a/ports/cc3200/mods/pybuart.c b/ports/cc3200/mods/pybuart.c index 5b71f02e29f..273b945830a 100644 --- a/ports/cc3200/mods/pybuart.c +++ b/ports/cc3200/mods/pybuart.c @@ -583,7 +583,7 @@ static const mp_rom_map_elem_t pyb_uart_locals_dict_table[] = { /// \method readline() { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, /// \method readinto(buf[, nbytes]) - { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto1_obj) }, /// \method write(buf) { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write1_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, diff --git a/py/stream.c b/py/stream.c index d7a8881e1a3..e41fbdd5da9 100644 --- a/py/stream.c +++ b/py/stream.c @@ -287,7 +287,7 @@ static mp_obj_t stream_write1_method(mp_obj_t self_in, mp_obj_t arg) { } MP_DEFINE_CONST_FUN_OBJ_2(mp_stream_write1_obj, stream_write1_method); -static mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { +static mp_obj_t stream_readinto_generic(size_t n_args, const mp_obj_t *args, byte flags) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); @@ -303,7 +303,7 @@ static mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { } int error; - mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error); + mp_uint_t out_sz = mp_stream_rw(args[0], bufinfo.buf, len, &error, flags); if (error != 0) { if (mp_is_nonblocking_error(error)) { return mp_const_none; @@ -313,8 +313,17 @@ static mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(out_sz); } } + +static mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { + return stream_readinto_generic(n_args, args, MP_STREAM_RW_READ); +} MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto_obj, 2, 3, stream_readinto); +static mp_obj_t stream_readinto1(size_t n_args, const mp_obj_t *args) { + return stream_readinto_generic(n_args, args, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto1_obj, 2, 3, stream_readinto1); + static mp_obj_t stream_readall(mp_obj_t self_in) { const mp_stream_p_t *stream_p = mp_get_stream(self_in); diff --git a/py/stream.h b/py/stream.h index 7c4d38afa9b..c3606e8eced 100644 --- a/py/stream.h +++ b/py/stream.h @@ -79,6 +79,7 @@ typedef struct _mp_stream_p_t { MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read1_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto1_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_unbuffered_readline_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_stream_unbuffered_readlines_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj); From e453d9d9f14ec5eabd1077d32ed8f293faeefb13 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 8 Sep 2025 17:09:12 +0200 Subject: [PATCH 1201/2098] py/stream: Support additional arguments for mp_stream_write1_obj. As suggested by @dpgeorge adding a `stream_write_generic()` function. Tested the different write timeouts with a RP2 using flow control. Tested support of the additional arguments of `uart.write()`. Signed-off-by: robert-hh --- py/stream.c | 16 +++++++++------- py/stream.h | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/py/stream.c b/py/stream.c index e41fbdd5da9..008c9684527 100644 --- a/py/stream.c +++ b/py/stream.c @@ -261,7 +261,7 @@ void mp_stream_write_adaptor(void *self, const char *buf, size_t len) { mp_stream_write(MP_OBJ_FROM_PTR(self), buf, len, MP_STREAM_RW_WRITE); } -static mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) { +static mp_obj_t stream_write_generic(size_t n_args, const mp_obj_t *args, byte flags) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); size_t max_len = (size_t)-1; @@ -276,16 +276,18 @@ static mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) { } } bufinfo.len -= off; - return mp_stream_write(args[0], (byte *)bufinfo.buf + off, MIN(bufinfo.len, max_len), MP_STREAM_RW_WRITE); + return mp_stream_write(args[0], (byte *)bufinfo.buf + off, MIN(bufinfo.len, max_len), flags); +} + +static mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) { + return stream_write_generic(n_args, args, MP_STREAM_RW_WRITE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj, 2, 4, stream_write_method); -static mp_obj_t stream_write1_method(mp_obj_t self_in, mp_obj_t arg) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); - return mp_stream_write(self_in, bufinfo.buf, bufinfo.len, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE); +static mp_obj_t stream_write1_method(size_t n_args, const mp_obj_t *args) { + return stream_write_generic(n_args, args, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE); } -MP_DEFINE_CONST_FUN_OBJ_2(mp_stream_write1_obj, stream_write1_method); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write1_obj, 2, 4, stream_write1_method); static mp_obj_t stream_readinto_generic(size_t n_args, const mp_obj_t *args, byte flags) { mp_buffer_info_t bufinfo; diff --git a/py/stream.h b/py/stream.h index c3606e8eced..5a3462d5a3d 100644 --- a/py/stream.h +++ b/py/stream.h @@ -83,7 +83,7 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto1_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_unbuffered_readline_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_stream_unbuffered_readlines_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj); -MP_DECLARE_CONST_FUN_OBJ_2(mp_stream_write1_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write1_obj); MP_DECLARE_CONST_FUN_OBJ_1(mp_stream_close_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream___exit___obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj); From d441788975a6dfb277ad9a3d54d651ae48817d16 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Jul 2025 23:09:49 +1000 Subject: [PATCH 1202/2098] esp8266/main: Use new cstack API and add a stack margin of 64 bytes. The margin of 64 bytes is needed to get `micropython/extreme_exc.py` to pass when run via-mpy. Signed-off-by: Damien George --- ports/esp8266/main.c | 4 +--- ports/esp8266/mpconfigport.h | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/esp8266/main.c b/ports/esp8266/main.c index da712fce9be..fb6cda37984 100644 --- a/ports/esp8266/main.c +++ b/ports/esp8266/main.c @@ -31,7 +31,6 @@ #include "py/builtin.h" #include "py/compile.h" #include "py/runtime.h" -#include "py/stackctrl.h" #include "py/mperrno.h" #include "py/mphal.h" #include "py/gc.h" @@ -108,8 +107,7 @@ static void print_reset_info(void) { #endif static void mp_reset(void) { - mp_stack_set_top((void *)0x40000000); - mp_stack_set_limit(8192); + mp_cstack_init_with_top((void *)0x40000000, 8192); mp_hal_init(); gc_init(heap, heap + sizeof(heap)); mp_init(); diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 0321de45d7f..17c3ff44176 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -52,6 +52,7 @@ #define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) #define MICROPY_DEBUG_PRINTER (&mp_debug_print) #define MICROPY_ENABLE_GC (1) +#define MICROPY_STACK_CHECK_MARGIN (64) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_REPL_EVENT_DRIVEN (0) #define MICROPY_USE_INTERNAL_ERRNO (1) From f0c6f16b9eeedc11f3b042704109fddb9aa3532f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Aug 2025 10:09:13 -0500 Subject: [PATCH 1203/2098] all: Remove Python 2.7 support. Python 2.7 has been EOL since January 2020. Ubuntu oldoldlts (Focal Fossa, 20.04) has Python 3.8. Debian oldoldstable (Buster, from 2019) has Python 3.7. RHEL 8 (from 2019) has Python 3.6. It's easier than ever to install a modern Python using uv. Given this, it seems like a fine idea to drop Python 2.7 support. Even though the build is not tested on Python as old as 3.3, I left comments stating that "3.3+" is the baseline Python version. However, it might make sense to bump this to e.g., 3.10, the oldest Python 3 version used during CI. Or, using uv or another method actually test on the oldest Python interpreter that is desirable to support (uv goes back to Python 3.7 easily; in October 2025, the oldest supported Python interpreter version will be 3.10) Signed-off-by: Jeff Epler --- .github/workflows/mpy_format.yml | 2 +- .github/workflows/ports_unix.yml | 4 ++-- README.md | 5 ++--- docs/develop/gettingstarted.rst | 2 +- ports/stm32/boards/pllvalues.py | 5 ++--- ports/stm32/make-stmconst.py | 24 +++++--------------- py/makeqstrdata.py | 11 ++------- py/makeqstrdefs.py | 2 +- py/makeversionhdr.py | 14 +----------- tools/ci.sh | 11 +++------ tools/mpy-tool.py | 38 ++++++++------------------------ tools/pydfu.py | 6 +---- 12 files changed, 31 insertions(+), 93 deletions(-) diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index 4043b63288a..9d3cd139e68 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -15,7 +15,7 @@ concurrency: jobs: test: - runs-on: ubuntu-22.04 # use 22.04 to get python2 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Install packages diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 8fd8e1aec23..20a0e879694 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -121,7 +121,7 @@ jobs: run: tests/run-tests.py --print-failures nanbox: - runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 + runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - uses: actions/checkout@v5 - name: Install packages @@ -135,7 +135,7 @@ jobs: run: tests/run-tests.py --print-failures longlong: - runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 + runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - uses: actions/checkout@v5 - name: Install packages diff --git a/README.md b/README.md index c78a2384604..9025da64444 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,8 @@ This repository contains the following components: - [examples/](examples/) -- a few example Python scripts. "make" is used to build the components, or "gmake" on BSD-based systems. -You will also need bash, gcc, and Python 3.3+ available as the command `python3` -(if your system only has Python 2.7 then invoke make with the additional option -`PYTHON=python2`). Some ports (rp2 and esp32) additionally use CMake. +You will also need bash, gcc, and Python 3.3+ available as the command `python3`. +Some ports (rp2 and esp32) additionally use CMake. Supported platforms & architectures ----------------------------------- diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index fed632ea1ac..329d218a879 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -106,7 +106,7 @@ See the `ARM GCC toolchain `_ for the latest details. -Python is also required. Python 2 is supported for now, but we recommend using Python 3. +Python 3 is also required. Check that you have Python available on your system: .. code-block:: bash diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py index ae042d999ce..987b784a608 100644 --- a/ports/stm32/boards/pllvalues.py +++ b/ports/stm32/boards/pllvalues.py @@ -105,7 +105,7 @@ def compute_pll2(hse, sys, relax_pll48): # VCO_OUT must be between 192MHz and 432MHz if sys * P not in mcu.range_vco_out: continue - NbyM = float(sys * P) / hse # float for Python 2 + NbyM = sys * P / hse # scan M M_min = mcu.range_n[0] // int(round(NbyM)) # starting value while mcu.range_vco_in[-1] * M_min < hse: @@ -121,7 +121,7 @@ def compute_pll2(hse, sys, relax_pll48): # N is restricted if N not in mcu.range_n: continue - Q = float(sys * P) / 48 # float for Python 2 + Q = sys * P / 48 # Q must be an integer in a set range if close_int(Q) and round(Q) in mcu.range_q: # found valid values @@ -142,7 +142,6 @@ def compute_pll2(hse, sys, relax_pll48): def compute_derived(hse, pll): - hse = float(hse) # float for Python 2 M, N, P, Q = pll vco_in = hse / M vco_out = hse * N / M diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index 4ef6143bda5..ff84977ad02 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -9,25 +9,13 @@ import argparse import re -# Python 2/3 compatibility -import platform -if platform.python_version_tuple()[0] == "2": - - def convert_bytes_to_str(b): - return b - -elif platform.python_version_tuple()[0] == "3": - - def convert_bytes_to_str(b): - try: - return str(b, "utf8") - except ValueError: - # some files have invalid utf8 bytes, so filter them out - return "".join(chr(l) for l in b if l <= 126) - - -# end compatibility code +def convert_bytes_to_str(b): + try: + return str(b, "utf8") + except ValueError: + # some files have invalid utf8 bytes, so filter them out + return "".join(chr(l) for l in b if l <= 126) # given a list of (name,regex) pairs, find the first one that matches the given line diff --git a/py/makeqstrdata.py b/py/makeqstrdata.py index 3a9c7aff525..8cd9bdc704a 100644 --- a/py/makeqstrdata.py +++ b/py/makeqstrdata.py @@ -1,7 +1,7 @@ """ Process raw qstr file and output qstr data with length, hash and data bytes. -This script works with Python 2.6, 2.7, 3.3 and 3.4. +This script works with Python 3.3+. """ from __future__ import print_function @@ -9,13 +9,7 @@ import re import sys -# Python 2/3/MicroPython compatibility: -# - iterating through bytes is different -# - codepoint2name from html.entities is hard-coded -if sys.version_info[0] == 2: - bytes_cons = lambda val, enc=None: bytearray(val) -elif sys.version_info[0] == 3: # Also handles MicroPython - bytes_cons = bytes +bytes_cons = bytes # fmt: off codepoint2name = { @@ -57,7 +51,6 @@ 253: "yacute", 165: "yen", 255: "yuml", 950: "zeta", 8205: "zwj", 8204: "zwnj" } # fmt: on -# end compatibility code codepoint2name[ord("-")] = "hyphen" diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 8e930ef5061..3f53e1ff31d 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -2,7 +2,7 @@ This script processes the output from the C preprocessor and extracts all qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'. -This script works with Python 2.6, 2.7, 3.3 and 3.4. +This script works with Python 3.3+. """ from __future__ import print_function diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 5c73501b3bb..aec95f08ec4 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -1,7 +1,7 @@ """ Generate header file with macros defining MicroPython version info. -This script works with Python 2.6, 2.7, 3.3 and 3.4. +This script works with Python 3.3+. """ from __future__ import print_function @@ -22,12 +22,6 @@ # "vX.Y.Z-preview.N.gHASH.dirty" -- building at any subsequent commit in the cycle # with local changes def get_version_info_from_git(repo_path): - # Python 2.6 doesn't have check_output, so check for that - try: - subprocess.check_output - except AttributeError: - return None - # Note: git describe doesn't work if no tag is available try: git_tag = subprocess.check_output( @@ -57,12 +51,6 @@ def get_version_info_from_git(repo_path): def get_hash_from_git(repo_path): - # Python 2.6 doesn't have check_output, so check for that. - try: - subprocess.check_output - except AttributeError: - return None - try: return subprocess.check_output( ["git", "rev-parse", "--short", "HEAD"], diff --git a/tools/ci.sh b/tools/ci.sh index 69e59508737..901059991ef 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -130,15 +130,12 @@ function ci_code_size_build { function ci_mpy_format_setup { sudo apt-get update - sudo apt-get install python2.7 sudo pip3 install pyelftools - python2.7 --version python3 --version } function ci_mpy_format_test { # Test mpy-tool.py dump feature on bytecode - python2.7 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy # Build MicroPython @@ -683,12 +680,11 @@ function ci_unix_coverage_run_native_mpy_tests { function ci_unix_32bit_setup { sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 python2.7 + sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 sudo pip3 install setuptools sudo pip3 install pyelftools sudo pip3 install ar gcc --version - python2.7 --version python3 --version } @@ -706,13 +702,12 @@ function ci_unix_coverage_32bit_run_native_mpy_tests { } function ci_unix_nanbox_build { - # Use Python 2 to check that it can run the build scripts - ci_unix_build_helper PYTHON=python2.7 VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" + ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" ci_unix_build_ffi_lib_helper gcc -m32 } function ci_unix_nanbox_run_tests { - ci_unix_run_tests_full_no_native_helper nanbox PYTHON=python2.7 + ci_unix_run_tests_full_no_native_helper nanbox } function ci_unix_longlong_build { diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 5c63f5be6d4..c87c656ff9b 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -24,41 +24,21 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# Python 2/3/MicroPython compatibility code -from __future__ import print_function +import struct import sys +from binascii import hexlify -if sys.version_info[0] == 2: - from binascii import hexlify as hexlify_py2 - - str_cons = lambda val, enc=None: str(val) - bytes_cons = lambda val, enc=None: bytearray(val) - is_str_type = lambda o: isinstance(o, str) - is_bytes_type = lambda o: type(o) is bytearray - is_int_type = lambda o: isinstance(o, int) or isinstance(o, long) # noqa: F821 - - def hexlify_to_str(b): - x = hexlify_py2(b) - return ":".join(x[i : i + 2] for i in range(0, len(x), 2)) - -elif sys.version_info[0] == 3: # Also handles MicroPython - from binascii import hexlify +str_cons = str +bytes_cons = bytes +is_str_type = lambda o: isinstance(o, str) +is_bytes_type = lambda o: isinstance(o, bytes) +is_int_type = lambda o: isinstance(o, int) - str_cons = str - bytes_cons = bytes - is_str_type = lambda o: isinstance(o, str) - is_bytes_type = lambda o: isinstance(o, bytes) - is_int_type = lambda o: isinstance(o, int) - def hexlify_to_str(b): - return str(hexlify(b, ":"), "ascii") +def hexlify_to_str(b): + return str(hexlify(b, ":"), "ascii") -# end compatibility code - -import sys -import struct - sys.path.append(sys.path[0] + "/../py") import makeqstrdata as qstrutil diff --git a/tools/pydfu.py b/tools/pydfu.py index cd7354818cd..8822b07bead 100755 --- a/tools/pydfu.py +++ b/tools/pydfu.py @@ -75,11 +75,7 @@ # USB DFU interface __DFU_INTERFACE = 0 -# Python 3 deprecated getargspec in favour of getfullargspec, but -# Python 2 doesn't have the latter, so detect which one to use -getargspec = getattr(inspect, "getfullargspec", getattr(inspect, "getargspec", None)) - -if "length" in getargspec(usb.util.get_string).args: +if "length" in inspect.getfullargspec(usb.util.get_string).args: # PyUSB 1.0.0.b1 has the length argument def get_string(dev, index): return usb.util.get_string(dev, 255, index) From 3c60e0cf216994ea8a502744b2f915a2a48788f4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 10 Sep 2025 07:37:23 -0500 Subject: [PATCH 1204/2098] tools: Remove unneeded future imports. Signed-off-by: Jeff Epler --- tools/file2h.py | 2 -- tools/insert-usb-ids.py | 2 -- tools/makemanifest.py | 1 - tools/manifestfile.py | 1 - tools/pydfu.py | 2 -- 5 files changed, 8 deletions(-) diff --git a/tools/file2h.py b/tools/file2h.py index 4607ab9279d..293f18a9c4b 100644 --- a/tools/file2h.py +++ b/tools/file2h.py @@ -5,8 +5,6 @@ # ; # This script simply prints the escaped string straight to stdout -from __future__ import print_function - import sys # Can either be set explicitly, or left blank to auto-detect diff --git a/tools/insert-usb-ids.py b/tools/insert-usb-ids.py index 177a68c4a45..bee285ad077 100644 --- a/tools/insert-usb-ids.py +++ b/tools/insert-usb-ids.py @@ -2,8 +2,6 @@ # inserts those values into the template file specified by sys.argv[2], # printing the result to stdout -from __future__ import print_function - import sys import re import string diff --git a/tools/makemanifest.py b/tools/makemanifest.py index e076a03e0be..860935397af 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -24,7 +24,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function import sys import os import subprocess diff --git a/tools/manifestfile.py b/tools/manifestfile.py index beaa36d0f5f..9c7a6e140f9 100644 --- a/tools/manifestfile.py +++ b/tools/manifestfile.py @@ -25,7 +25,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function import contextlib import os import sys diff --git a/tools/pydfu.py b/tools/pydfu.py index 8822b07bead..376c697cbd5 100755 --- a/tools/pydfu.py +++ b/tools/pydfu.py @@ -11,8 +11,6 @@ See document UM0391 for a description of the DFuse file. """ -from __future__ import print_function - import argparse import collections import inspect From d9d9d1ab0d0f12aea4d1e15457af8041e00e3453 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 10 Sep 2025 07:37:46 -0500 Subject: [PATCH 1205/2098] py: Remove unneeded future imports. Signed-off-by: Jeff Epler --- py/make_root_pointers.py | 2 -- py/makecompresseddata.py | 2 -- py/makemoduledefs.py | 2 -- py/makeqstrdata.py | 2 -- py/makeqstrdefs.py | 2 -- py/makeversionhdr.py | 2 -- 6 files changed, 12 deletions(-) diff --git a/py/make_root_pointers.py b/py/make_root_pointers.py index efe398b8227..5da343c1270 100644 --- a/py/make_root_pointers.py +++ b/py/make_root_pointers.py @@ -6,8 +6,6 @@ "struct _mp_state_vm_t" in py/mpstate.h """ -from __future__ import print_function - import argparse import io import re diff --git a/py/makecompresseddata.py b/py/makecompresseddata.py index 1bce3e8e837..a6fa7db00b2 100644 --- a/py/makecompresseddata.py +++ b/py/makecompresseddata.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import collections import re import sys diff --git a/py/makemoduledefs.py b/py/makemoduledefs.py index 29162ab387c..1488db5c90e 100644 --- a/py/makemoduledefs.py +++ b/py/makemoduledefs.py @@ -14,8 +14,6 @@ the built-in version. """ -from __future__ import print_function - import sys import re import io diff --git a/py/makeqstrdata.py b/py/makeqstrdata.py index 8cd9bdc704a..0280a4aa683 100644 --- a/py/makeqstrdata.py +++ b/py/makeqstrdata.py @@ -4,8 +4,6 @@ This script works with Python 3.3+. """ -from __future__ import print_function - import re import sys diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 3f53e1ff31d..dd514c7033d 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -5,8 +5,6 @@ This script works with Python 3.3+. """ -from __future__ import print_function - import io import os import re diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index aec95f08ec4..249ec1830c1 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -4,8 +4,6 @@ This script works with Python 3.3+. """ -from __future__ import print_function - import argparse import sys import os From 4f9bc4b71c55e12805f644a7733bfc21c45742e0 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 10 Sep 2025 07:39:13 -0500 Subject: [PATCH 1206/2098] mpy-cross: Remove unneeded future imports. Signed-off-by: Jeff Epler --- mpy-cross/mpy_cross/__init__.py | 1 - mpy-cross/mpy_cross/__main__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/mpy-cross/mpy_cross/__init__.py b/mpy-cross/mpy_cross/__init__.py index 91cd6f99335..9399aa92f39 100644 --- a/mpy-cross/mpy_cross/__init__.py +++ b/mpy-cross/mpy_cross/__init__.py @@ -25,7 +25,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function import os import re import stat diff --git a/mpy-cross/mpy_cross/__main__.py b/mpy-cross/mpy_cross/__main__.py index 2b6b81c3333..fe78a9e077e 100644 --- a/mpy-cross/mpy_cross/__main__.py +++ b/mpy-cross/mpy_cross/__main__.py @@ -25,7 +25,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function import argparse import sys From 36ab1c26400a68c9f8c8c24cce03f487e403c312 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 10 Sep 2025 07:39:27 -0500 Subject: [PATCH 1207/2098] ports: Remove unneeded future imports. Signed-off-by: Jeff Epler --- ports/cc3200/boards/make-pins.py | 2 -- ports/nrf/boards/make-pins.py | 2 -- ports/stm32/boards/pllvalues.py | 1 - ports/stm32/make-stmconst.py | 2 -- shared/memzip/make-memzip.py | 2 -- 5 files changed, 9 deletions(-) diff --git a/ports/cc3200/boards/make-pins.py b/ports/cc3200/boards/make-pins.py index e30c02ce94a..0a06805f777 100644 --- a/ports/cc3200/boards/make-pins.py +++ b/ports/cc3200/boards/make-pins.py @@ -4,8 +4,6 @@ # Do not use this as a reference for new ports. See tools/boardgen.py and e.g. # ports/stm32/boards/make-pins.py. -from __future__ import print_function - import argparse import sys import csv diff --git a/ports/nrf/boards/make-pins.py b/ports/nrf/boards/make-pins.py index 30da035589d..20e89936f6a 100644 --- a/ports/nrf/boards/make-pins.py +++ b/ports/nrf/boards/make-pins.py @@ -4,8 +4,6 @@ # Do not use this as a reference for new ports. See tools/boardgen.py and e.g. # ports/stm32/boards/make-pins.py. -from __future__ import print_function - import argparse import sys import csv diff --git a/ports/stm32/boards/pllvalues.py b/ports/stm32/boards/pllvalues.py index 987b784a608..5558d04c5b9 100644 --- a/ports/stm32/boards/pllvalues.py +++ b/ports/stm32/boards/pllvalues.py @@ -4,7 +4,6 @@ for the machine.freq() function. """ -from __future__ import print_function import re diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index ff84977ad02..770033ef59d 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -4,8 +4,6 @@ for the stm module. """ -from __future__ import print_function - import argparse import re diff --git a/shared/memzip/make-memzip.py b/shared/memzip/make-memzip.py index 92a5d6168bb..e406c55a43c 100755 --- a/shared/memzip/make-memzip.py +++ b/shared/memzip/make-memzip.py @@ -7,8 +7,6 @@ # This is somewhat like frozen modules in python, but allows arbitrary files # to be used. -from __future__ import print_function - import argparse import os import subprocess From 6d09d3e76e1c4837c9e54957ffaa3d6161803630 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 31 Aug 2025 13:20:13 -0400 Subject: [PATCH 1208/2098] tests/internal_bench/class_instance: Benchmark instantiation. This commit adds tests to benchmark the performance of class instantiation. Signed-off-by: Anson Mansfield --- tests/internal_bench/class_instance-0-object.py | 11 +++++++++++ .../class_instance-0.1-object-gc.py | 13 +++++++++++++ tests/internal_bench/class_instance-1-empty.py | 13 +++++++++++++ .../class_instance-1.1-classattr.py | 13 +++++++++++++ tests/internal_bench/class_instance-1.2-func.py | 14 ++++++++++++++ .../class_instance-1.3-empty-gc.py | 15 +++++++++++++++ tests/internal_bench/class_instance-2-init.py | 14 ++++++++++++++ .../class_instance-2.1-init_super.py | 14 ++++++++++++++ tests/internal_bench/class_instance-2.2-new.py | 14 ++++++++++++++ tests/internal_bench/class_instance-3-del.py | 14 ++++++++++++++ .../internal_bench/class_instance-3.1-del-gc.py | 16 ++++++++++++++++ tests/internal_bench/class_instance-4-slots.py | 13 +++++++++++++ .../internal_bench/class_instance-4.1-slots5.py | 13 +++++++++++++ 13 files changed, 177 insertions(+) create mode 100644 tests/internal_bench/class_instance-0-object.py create mode 100644 tests/internal_bench/class_instance-0.1-object-gc.py create mode 100644 tests/internal_bench/class_instance-1-empty.py create mode 100644 tests/internal_bench/class_instance-1.1-classattr.py create mode 100644 tests/internal_bench/class_instance-1.2-func.py create mode 100644 tests/internal_bench/class_instance-1.3-empty-gc.py create mode 100644 tests/internal_bench/class_instance-2-init.py create mode 100644 tests/internal_bench/class_instance-2.1-init_super.py create mode 100644 tests/internal_bench/class_instance-2.2-new.py create mode 100644 tests/internal_bench/class_instance-3-del.py create mode 100644 tests/internal_bench/class_instance-3.1-del-gc.py create mode 100644 tests/internal_bench/class_instance-4-slots.py create mode 100644 tests/internal_bench/class_instance-4.1-slots5.py diff --git a/tests/internal_bench/class_instance-0-object.py b/tests/internal_bench/class_instance-0-object.py new file mode 100644 index 00000000000..401c8ea7e3c --- /dev/null +++ b/tests/internal_bench/class_instance-0-object.py @@ -0,0 +1,11 @@ +import bench + +X = object + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-0.1-object-gc.py b/tests/internal_bench/class_instance-0.1-object-gc.py new file mode 100644 index 00000000000..7c475963a8e --- /dev/null +++ b/tests/internal_bench/class_instance-0.1-object-gc.py @@ -0,0 +1,13 @@ +import bench +import gc + +X = object + + +def test(num): + for i in range(num // 5): + x = X() + gc.collect() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-1-empty.py b/tests/internal_bench/class_instance-1-empty.py new file mode 100644 index 00000000000..617d47a86e9 --- /dev/null +++ b/tests/internal_bench/class_instance-1-empty.py @@ -0,0 +1,13 @@ +import bench + + +class X: + pass + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-1.1-classattr.py b/tests/internal_bench/class_instance-1.1-classattr.py new file mode 100644 index 00000000000..4e667533d4a --- /dev/null +++ b/tests/internal_bench/class_instance-1.1-classattr.py @@ -0,0 +1,13 @@ +import bench + + +class X: + x = 0 + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-1.2-func.py b/tests/internal_bench/class_instance-1.2-func.py new file mode 100644 index 00000000000..21bf7a1ac48 --- /dev/null +++ b/tests/internal_bench/class_instance-1.2-func.py @@ -0,0 +1,14 @@ +import bench + + +class X: + def f(self): + pass + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-1.3-empty-gc.py b/tests/internal_bench/class_instance-1.3-empty-gc.py new file mode 100644 index 00000000000..a5108ef8e81 --- /dev/null +++ b/tests/internal_bench/class_instance-1.3-empty-gc.py @@ -0,0 +1,15 @@ +import bench +import gc + + +class X: + pass + + +def test(num): + for i in range(num // 5): + x = X() + gc.collect() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-2-init.py b/tests/internal_bench/class_instance-2-init.py new file mode 100644 index 00000000000..86619d31548 --- /dev/null +++ b/tests/internal_bench/class_instance-2-init.py @@ -0,0 +1,14 @@ +import bench + + +class X: + def __init__(self): + pass + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-2.1-init_super.py b/tests/internal_bench/class_instance-2.1-init_super.py new file mode 100644 index 00000000000..38bca5fef87 --- /dev/null +++ b/tests/internal_bench/class_instance-2.1-init_super.py @@ -0,0 +1,14 @@ +import bench + + +class X: + def __init__(self): + return super().__init__() + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-2.2-new.py b/tests/internal_bench/class_instance-2.2-new.py new file mode 100644 index 00000000000..dc5e78ea5ef --- /dev/null +++ b/tests/internal_bench/class_instance-2.2-new.py @@ -0,0 +1,14 @@ +import bench + + +class X: + def __new__(cls): + return super().__new__(cls) + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-3-del.py b/tests/internal_bench/class_instance-3-del.py new file mode 100644 index 00000000000..af700f72a94 --- /dev/null +++ b/tests/internal_bench/class_instance-3-del.py @@ -0,0 +1,14 @@ +import bench + + +class X: + def __del__(self): + pass + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-3.1-del-gc.py b/tests/internal_bench/class_instance-3.1-del-gc.py new file mode 100644 index 00000000000..311c71c3571 --- /dev/null +++ b/tests/internal_bench/class_instance-3.1-del-gc.py @@ -0,0 +1,16 @@ +import bench +import gc + + +class X: + def __del__(self): + pass + + +def test(num): + for i in range(num // 5): + x = X() + gc.collect() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-4-slots.py b/tests/internal_bench/class_instance-4-slots.py new file mode 100644 index 00000000000..51b067fedf0 --- /dev/null +++ b/tests/internal_bench/class_instance-4-slots.py @@ -0,0 +1,13 @@ +import bench + + +class X: + __slots__ = ["x"] + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) diff --git a/tests/internal_bench/class_instance-4.1-slots5.py b/tests/internal_bench/class_instance-4.1-slots5.py new file mode 100644 index 00000000000..8f5c2ecb456 --- /dev/null +++ b/tests/internal_bench/class_instance-4.1-slots5.py @@ -0,0 +1,13 @@ +import bench + + +class X: + __slots__ = ["a", "b", "c", "d", "x"] + + +def test(num): + for i in range(num // 5): + x = X() + + +bench.run(test) From d28844d11b1414832458cfd91660a815255ca737 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 31 Aug 2025 13:21:08 -0400 Subject: [PATCH 1209/2098] tests/internal_bench/var: Benchmark checked attribute access. This commit also includes a fix to the `var-6.2-instance-speciallookup.py` test originally added by 82db5c8 / #16806, as the `__getattr__` method actually does not trigger a class's special lookups flag as originally believed. Signed-off-by: Anson Mansfield --- .../var-6.2-instance-speciallookup.py | 2 +- tests/internal_bench/var-9-getattr.py | 16 +++++++++++++ .../internal_bench/var-9.1-getattr_default.py | 15 ++++++++++++ .../var-9.2-getattr_default_special.py | 16 +++++++++++++ tests/internal_bench/var-9.3-except_ok.py | 23 +++++++++++++++++++ .../var-9.4-except_selfinduced.py | 22 ++++++++++++++++++ tests/internal_bench/var-9.5-except_error.py | 22 ++++++++++++++++++ .../var-9.6-except_error_special.py | 23 +++++++++++++++++++ 8 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/internal_bench/var-9-getattr.py create mode 100644 tests/internal_bench/var-9.1-getattr_default.py create mode 100644 tests/internal_bench/var-9.2-getattr_default_special.py create mode 100644 tests/internal_bench/var-9.3-except_ok.py create mode 100644 tests/internal_bench/var-9.4-except_selfinduced.py create mode 100644 tests/internal_bench/var-9.5-except_error.py create mode 100644 tests/internal_bench/var-9.6-except_error_special.py diff --git a/tests/internal_bench/var-6.2-instance-speciallookup.py b/tests/internal_bench/var-6.2-instance-speciallookup.py index 71845f3aaa2..fee12b2f930 100644 --- a/tests/internal_bench/var-6.2-instance-speciallookup.py +++ b/tests/internal_bench/var-6.2-instance-speciallookup.py @@ -5,7 +5,7 @@ class Foo: def __init__(self): self.num = 20000000 - def __getattr__(self, name): # just trigger the 'special lookups' flag on the class + def __delattr__(self, name): # just trigger the 'special lookups' flag on the class pass diff --git a/tests/internal_bench/var-9-getattr.py b/tests/internal_bench/var-9-getattr.py new file mode 100644 index 00000000000..69d2bfed2e0 --- /dev/null +++ b/tests/internal_bench/var-9-getattr.py @@ -0,0 +1,16 @@ +import bench + + +class Foo: + pass + + +def test(num): + o = Foo() + o.num = num + i = 0 + while i < getattr(o, "num", num): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.1-getattr_default.py b/tests/internal_bench/var-9.1-getattr_default.py new file mode 100644 index 00000000000..e803d39b326 --- /dev/null +++ b/tests/internal_bench/var-9.1-getattr_default.py @@ -0,0 +1,15 @@ +import bench + + +class Foo: + pass + + +def test(num): + o = Foo() + i = 0 + while i < getattr(o, "num", num): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.2-getattr_default_special.py b/tests/internal_bench/var-9.2-getattr_default_special.py new file mode 100644 index 00000000000..c48ec0742cf --- /dev/null +++ b/tests/internal_bench/var-9.2-getattr_default_special.py @@ -0,0 +1,16 @@ +import bench + + +class Foo: + def __delattr__(self, name): # just trigger the 'special lookups' flag on the class + pass + + +def test(num): + o = Foo() + i = 0 + while i < getattr(o, "num", num): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.3-except_ok.py b/tests/internal_bench/var-9.3-except_ok.py new file mode 100644 index 00000000000..efc1a8f858c --- /dev/null +++ b/tests/internal_bench/var-9.3-except_ok.py @@ -0,0 +1,23 @@ +import bench + + +class Foo: + pass + + +def test(num): + o = Foo() + o.num = num + + def get(): + try: + return o.num + except AttributeError: + return num + + i = 0 + while i < get(): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.4-except_selfinduced.py b/tests/internal_bench/var-9.4-except_selfinduced.py new file mode 100644 index 00000000000..544609ca4b6 --- /dev/null +++ b/tests/internal_bench/var-9.4-except_selfinduced.py @@ -0,0 +1,22 @@ +import bench + + +class Foo: + pass + + +def test(num): + o = Foo() + + def get(): + try: + raise AttributeError + except AttributeError: + return num + + i = 0 + while i < get(): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.5-except_error.py b/tests/internal_bench/var-9.5-except_error.py new file mode 100644 index 00000000000..caf83fa46ab --- /dev/null +++ b/tests/internal_bench/var-9.5-except_error.py @@ -0,0 +1,22 @@ +import bench + + +class Foo: + pass + + +def test(num): + o = Foo() + + def get(): + try: + return o.num + except AttributeError: + return num + + i = 0 + while i < get(): + i += 1 + + +bench.run(test) diff --git a/tests/internal_bench/var-9.6-except_error_special.py b/tests/internal_bench/var-9.6-except_error_special.py new file mode 100644 index 00000000000..8bc395b4d7e --- /dev/null +++ b/tests/internal_bench/var-9.6-except_error_special.py @@ -0,0 +1,23 @@ +import bench + + +class Foo: + def __delattr__(self, name): # just trigger the 'special lookups' flag on the class + pass + + +def test(num): + o = Foo() + + def get(): + try: + return o.num + except AttributeError: + return num + + i = 0 + while i < get(): + i += 1 + + +bench.run(test) From 2707ae032f8b211ae37446c1606e6a9ce411c2d9 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 3 Sep 2025 15:41:04 -0400 Subject: [PATCH 1210/2098] tests/internal_bench/var: Benchmark ordered map accesses. This adds an internal_bench benchmark that benchmarks ordered map linear scan cache performance. Signed-off-by: Anson Mansfield --- .../var-6.6-instance-builtin_ordered.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/internal_bench/var-6.6-instance-builtin_ordered.py diff --git a/tests/internal_bench/var-6.6-instance-builtin_ordered.py b/tests/internal_bench/var-6.6-instance-builtin_ordered.py new file mode 100644 index 00000000000..02d75252301 --- /dev/null +++ b/tests/internal_bench/var-6.6-instance-builtin_ordered.py @@ -0,0 +1,12 @@ +import bench + + +def test(num): + i = 0 + o = set() # object with largest rom-frozen ordered locals_dict + n = "__contains__" # last element in that dict for longest lookup + while i < num: + i += hasattr(o, n) # True, converts to 1 + + +bench.run(test) From c3040463849eb9b3e5f85706bb223d648e60510d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 4 Sep 2025 11:39:24 -0500 Subject: [PATCH 1211/2098] docs: Always use sphinx_rtd_theme. Without this, the wy-alert block pushed all the content to the right, making the locally built pages nearly illegible. Signed-off-by: Jeff Epler --- docs/conf.py | 16 +++------------- docs/requirements.txt | 1 + 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index eb614875820..603543aa18c 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,6 +55,7 @@ "sphinx.ext.todo", "sphinx.ext.coverage", "sphinxcontrib.jquery", + "sphinx_rtd_theme", ] # Add any paths that contain templates here, relative to this directory. @@ -128,20 +129,9 @@ # -- Options for HTML output ---------------------------------------------- -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get("READTHEDOCS", None) == "True" +import sphinx_rtd_theme -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/requirements.txt b/docs/requirements.txt index 824e9799c77..4d9b9558575 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx~=7.2.6 sphinxcontrib.jquery==4.1 +sphinx-rtd-theme==3.0.2 From 6d640a15ab5258140f402d62fcd69e73650c08d6 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Thu, 11 Sep 2025 17:52:10 -0400 Subject: [PATCH 1212/2098] py/obj: Document undocumented MP_TYPE_FLAG values. This adds comments documentign the meaning of the type flag bits `MP_TYPE_FLAG_IS_SUBCLASSED` and `MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS`, for consistency with the comments added later on documenting the other flags. These flags were were originally made part of the public API in c3450effd4c3a402eeccf44a84a05ef4b36d69a0. This block of doc comments was only added later on when `MP_TYPE_FLAG_EQ_NOT_REFLEXIVE`, `MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE`, and `MP_TYPE_FLAG_EQ_HAS_NEQ_TEST` were added in 9ec1caf42e7733b5141b7aecf1b6e58834a94bf7. Signed-off-by: Anson Mansfield --- py/obj.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/obj.h b/py/obj.h index 72def663d8d..9a5d273cdd2 100644 --- a/py/obj.h +++ b/py/obj.h @@ -539,6 +539,10 @@ typedef mp_obj_t (*mp_fun_var_t)(size_t n, const mp_obj_t *); typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); // Flags for type behaviour (mp_obj_type_t.flags) +// If MP_TYPE_FLAG_IS_SUBCLASSED is set, then subclasses of this class have been created. +// Mutations to this class that would require updating all subclasses must be rejected. +// If MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS is set, then attribute lookups involving this +// class need to additionally check for special accessor methods, such as from descriptors. // If MP_TYPE_FLAG_EQ_NOT_REFLEXIVE is clear then __eq__ is reflexive (A==A returns True). // If MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE is clear then the type can't be equal to an // instance of any different class that also clears this flag. If this flag is set From e9da4c9c98c329c85eb7273437ff9e567d6cd006 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 3 Aug 2025 10:11:49 -0500 Subject: [PATCH 1213/2098] unix/modsocket: Use type-checking mp_obj_get_int. MP_OBJ_SMALL_INT_VALUE would give erroneous results, such as assertion failures in the coverage build and other oddities like: >>> s = socket.socket() >>> s.recv(3.14) MemoryError: memory allocation failed, allocating 4235896656 bytes Signed-off-by: Jeff Epler --- ports/unix/modsocket.c | 33 ++++++++++++--------------- tests/extmod/socket_badconstructor.py | 22 ++++++++++++++++++ 2 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 tests/extmod/socket_badconstructor.py diff --git a/ports/unix/modsocket.c b/ports/unix/modsocket.c index 2aaa21183a9..14f321d34fb 100644 --- a/ports/unix/modsocket.c +++ b/ports/unix/modsocket.c @@ -280,11 +280,11 @@ static MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); // these would be thrown as exceptions. static mp_obj_t socket_recv(size_t n_args, const mp_obj_t *args) { mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); + int sz = mp_obj_get_int(args[1]); int flags = 0; if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + flags = mp_obj_get_int(args[2]); } byte *buf = m_new(byte, sz); @@ -298,11 +298,11 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recv_obj, 2, 3, socket_recv); static mp_obj_t socket_recvfrom(size_t n_args, const mp_obj_t *args) { mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); + int sz = mp_obj_get_int(args[1]); int flags = 0; if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + flags = mp_obj_get_int(args[2]); } struct sockaddr_storage addr; @@ -331,7 +331,7 @@ static mp_obj_t socket_send(size_t n_args, const mp_obj_t *args) { int flags = 0; if (n_args > 2) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + flags = mp_obj_get_int(args[2]); } mp_buffer_info_t bufinfo; @@ -349,7 +349,7 @@ static mp_obj_t socket_sendto(size_t n_args, const mp_obj_t *args) { mp_obj_t dst_addr = args[2]; if (n_args > 3) { - flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + flags = mp_obj_get_int(args[2]); dst_addr = args[3]; } @@ -366,7 +366,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_sendto_obj, 3, 4, socket_sendt static mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { (void)n_args; // always 4 mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); - int level = MP_OBJ_SMALL_INT_VALUE(args[1]); + int level = mp_obj_get_int(args[1]); int option = mp_obj_get_int(args[2]); const void *optval; @@ -478,14 +478,11 @@ static mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, siz int proto = 0; if (n_args > 0) { - assert(mp_obj_is_small_int(args[0])); - family = MP_OBJ_SMALL_INT_VALUE(args[0]); + family = mp_obj_get_int(args[0]); if (n_args > 1) { - assert(mp_obj_is_small_int(args[1])); - type = MP_OBJ_SMALL_INT_VALUE(args[1]); + type = mp_obj_get_int(args[1]); if (n_args > 2) { - assert(mp_obj_is_small_int(args[2])); - proto = MP_OBJ_SMALL_INT_VALUE(args[2]); + proto = mp_obj_get_int(args[2]); } } } @@ -582,7 +579,7 @@ static mp_obj_t mod_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { // getaddrinfo accepts port in string notation, so however // it may seem stupid, we need to convert int to str if (mp_obj_is_small_int(args[1])) { - unsigned port = (unsigned short)MP_OBJ_SMALL_INT_VALUE(args[1]); + unsigned port = (unsigned short)mp_obj_get_int(args[1]); snprintf(buf, sizeof(buf), "%u", port); serv = buf; hints.ai_flags = AI_NUMERICSERV; @@ -605,13 +602,13 @@ static mp_obj_t mod_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { } if (n_args > 2) { - hints.ai_family = MP_OBJ_SMALL_INT_VALUE(args[2]); + hints.ai_family = mp_obj_get_int(args[2]); if (n_args > 3) { - hints.ai_socktype = MP_OBJ_SMALL_INT_VALUE(args[3]); + hints.ai_socktype = mp_obj_get_int(args[3]); if (n_args > 4) { - hints.ai_protocol = MP_OBJ_SMALL_INT_VALUE(args[4]); + hints.ai_protocol = mp_obj_get_int(args[4]); if (n_args > 5) { - hints.ai_flags = MP_OBJ_SMALL_INT_VALUE(args[5]); + hints.ai_flags = mp_obj_get_int(args[5]); } } } diff --git a/tests/extmod/socket_badconstructor.py b/tests/extmod/socket_badconstructor.py new file mode 100644 index 00000000000..4a9d2668c7f --- /dev/null +++ b/tests/extmod/socket_badconstructor.py @@ -0,0 +1,22 @@ +# Test passing in bad values to socket.socket constructor. + +try: + import socket +except: + print("SKIP") + raise SystemExit + +try: + s = socket.socket(None) +except TypeError: + print("TypeError") + +try: + s = socket.socket(socket.AF_INET, None) +except TypeError: + print("TypeError") + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, None) +except TypeError: + print("TypeError") From a69425b533932bbcac0ef463f9e27f79ff2150e3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 14 Aug 2025 10:04:33 -0500 Subject: [PATCH 1214/2098] unix/Makefile: Add additional testing targets. These are convenience targets for running specific tests as a developer. They are more useful that invoking run-tests directly as they take account of the VARIANT= specified on the make command-line. For instance, you can run all tests matching the regular expression "int" with `make VARIANT=... test//int`. The new targets are all documented in README.md. Signed-off-by: Jeff Epler --- ports/unix/Makefile | 20 +++++++++++++++++++- ports/unix/README.md | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 4e9a3736aad..e4638299f15 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -254,12 +254,30 @@ endif include $(TOP)/py/mkrules.mk -.PHONY: test test_full_no_native test_full +.PHONY: test test_full_no_native test_full test//% test/% test-failures print-failures clean-failures test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py +test//%: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -i "$*" + +test-failures: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --run-failures + +print-failures: + cd $(TOP)/tests && ./run-tests.py --print-failures + +clean-failures: + cd $(TOP)/tests && ./run-tests.py --clean-failures + +test/%: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d "$*" + test_full_no_native: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py diff --git a/ports/unix/README.md b/ports/unix/README.md index 656d4303d38..ee983a882cc 100644 --- a/ports/unix/README.md +++ b/ports/unix/README.md @@ -72,6 +72,14 @@ To run the complete testsuite, use: $ make test +There are other make targets to interact with the testsuite: + + $ make test//int # Run all tests matching the pattern "int" + $ make test/ports/unix # Run all tests in ports/unix + $ make test-failures # Re-run only the failed tests + $ make print-failures # print the differences for failed tests + $ make clean-failures # delete the .exp and .out files from failed tests + The Unix port comes with a built-in package manager called `mip`, e.g.: $ ./build-standard/micropython -m mip install hmac From 2dc6873bbc32dcae78a061b394a2dbfd53e70650 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Mon, 15 Sep 2025 14:18:41 +0530 Subject: [PATCH 1215/2098] py/py.cmake: Add nlraarch64. - Required for aarch64 zephyr port targets to build. - Tested with PocketBeagle 2 [0] A53 cores. [0]: https://docs.zephyrproject.org/latest/boards/beagle/pocketbeagle_2/doc/index.html Signed-off-by: Ayush Singh --- py/py.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/py/py.cmake b/py/py.cmake index 1c81ed4c58f..ec2a5d832d2 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -58,6 +58,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/mpz.c ${MICROPY_PY_DIR}/nativeglue.c ${MICROPY_PY_DIR}/nlr.c + ${MICROPY_PY_DIR}/nlraarch64.c ${MICROPY_PY_DIR}/nlrmips.c ${MICROPY_PY_DIR}/nlrpowerpc.c ${MICROPY_PY_DIR}/nlrrv32.c From 29246ba81784e9e1b31a070082b2729f1b4b1d72 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 14 Sep 2025 19:22:06 +0900 Subject: [PATCH 1216/2098] tests/run-perfbench.py: Fix issues when -s/-m is used with failed tests. The option '-s' (--diff-score) or '-m' (--diff-time) fails when the specified result contains tests that was skipped or failed. This patch ignores "skipped: " or "failed: " message. Signed-off-by: Yuuki NAGAO --- tests/run-perfbench.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 2a2b7b6c9dd..039d11a3611 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -194,7 +194,13 @@ def parse_output(filename): m = int(m.split("=")[1]) data = [] for l in f: - if ": " in l and ": SKIP" not in l and "CRASH: " not in l: + if ( + ": " in l + and ": SKIP" not in l + and "CRASH: " not in l + and "skipped: " not in l + and "failed: " not in l + ): name, values = l.strip().split(": ") values = tuple(float(v) for v in values.split()) data.append((name,) + values) From c76e6014423f2690106a815d1deb643fb6f3627f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:06:30 +0000 Subject: [PATCH 1217/2098] github/workflows: Bump actions/github-script from 7 to 8. Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_size_comment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_size_comment.yml b/.github/workflows/code_size_comment.yml index 521afad709d..2eed0b06b8e 100644 --- a/.github/workflows/code_size_comment.yml +++ b/.github/workflows/code_size_comment.yml @@ -15,7 +15,7 @@ jobs: steps: - name: 'Download artifact' id: download-artifact - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: result-encoding: string script: | @@ -56,7 +56,7 @@ jobs: run: unzip code-size-report.zip - name: Post comment to pull request if: steps.download-artifact.outputs.result == 'ok' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | From 6681530fc0f2b426e77642d887b548c0bae1fc9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:06:36 +0000 Subject: [PATCH 1218/2098] github/workflows: Bump actions/setup-python from 5 to 6. Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_formatting.yml | 2 +- .github/workflows/commit_formatting.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/mpremote.yml | 2 +- .github/workflows/ports_unix.yml | 10 +++++----- .github/workflows/ports_windows.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 5669779946e..2a7da52e26d 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Install packages run: source tools/ci.sh && ci_c_code_formatting_setup - name: Run code formatting diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml index 2e1def95c36..58ffb745b34 100644 --- a/.github/workflows/commit_formatting.yml +++ b/.github/workflows/commit_formatting.yml @@ -13,6 +13,6 @@ jobs: - uses: actions/checkout@v5 with: fetch-depth: 100 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Check commit message formatting run: source tools/ci.sh && ci_commit_formatting_run diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f9d61125b50..6a277a4a4f0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Install Python packages run: pip install -r docs/requirements.txt - name: Build unix port diff --git a/.github/workflows/mpremote.yml b/.github/workflows/mpremote.yml index 359d8882864..36904764c99 100644 --- a/.github/workflows/mpremote.yml +++ b/.github/workflows/mpremote.yml @@ -16,7 +16,7 @@ jobs: # Setting this to zero means fetch all history and tags, # which hatch-vcs can use to discover the version tag. fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Install build tools run: pip install build - name: Build mpremote wheel diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 20a0e879694..77d0b29ca96 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -71,7 +71,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. with: @@ -204,7 +204,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. with: @@ -221,7 +221,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.8' - name: Build @@ -281,7 +281,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. with: @@ -306,7 +306,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. with: diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 6b492640a1f..84a93a50433 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -109,7 +109,7 @@ jobs: run: shell: msys2 {0} steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 # note: can go back to installing mingw-w64-${{ matrix.env }}-python after # MSYS2 updates to Python >3.12 (due to settrace compatibility issue) with: From be1a1eb7ac44c45d7d5280e2ceabc29bef6e107c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 3 Sep 2025 08:52:13 -0500 Subject: [PATCH 1219/2098] tools/ci.sh: Make this script runnable as command. This makes it easier to run a sequence of ci steps locally. A help message is also provided. Signed-off-by: Jeff Epler --- docs/develop/gettingstarted.rst | 24 ++++++++++++++ tools/ci.sh | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index 329d218a879..b625a72c98a 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -282,6 +282,30 @@ To run a selection of tests on a board/device connected over USB use: See also :ref:`writingtests`. +Using ci.sh locally +------------------- + +MicroPython uses GitHub Actions for continuous integration. +To reduce dependence on any specific CI system, the actual build steps for Unix-based builds are in the file ``tools/ci.sh``. +This can also be used as a script on developer desktops, with caveats: + +* For most steps, An Ubuntu/Debian system similar to the one used during CI is assumed. +* Some specific steps assume specific Ubuntu versions. +* The setup steps may invoke the system package manager to install packages, + download and install software from the internet, etc. + +To get a usage message including the list of commands, run: + +.. code-block:: bash + + $ tools/ci.sh --help + +As an example, you can build and test the unix minimal port with: + +.. code-block:: bash + + $ tools/ci.sh unix_minimal_build unix_minimal_run_tests + Folder structure ---------------- diff --git a/tools/ci.sh b/tools/ci.sh index 901059991ef..6b974f98304 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -970,3 +970,60 @@ function ci_alif_ae3_build { make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_DUAL make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL } + +function _ci_help { + # Note: these lines must be indented with tab characters (required by bash <<-EOF) + cat <<-EOF + ci.sh: Script fragments used during CI + + When invoked as a script, runs a sequence of ci steps, + stopping after the first error. + + Usage: + ${BASH_SOURCE} step1 step2... + + Steps: + EOF + if type -path column > /dev/null 2>&1; then + grep '^function ci_' $0 | awk '{print $2}' | sed 's/^ci_//' | column + else + grep '^function ci_' $0 | awk '{print $2}' | sed 's/^ci_//' + fi + exit +} + +function _ci_main { + case "$1" in + (-h|-?|--help) + _ci_help + ;; + (*) + cd $(dirname "$0")/.. + while [ $# -ne 0 ]; do + ci_$1 + shift + done + ;; + esac +} + +# https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced +sourced=0 +if [ -n "$ZSH_VERSION" ]; then + case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac +elif [ -n "$KSH_VERSION" ]; then + [ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1 +elif [ -n "$BASH_VERSION" ]; then + (return 0 2>/dev/null) && sourced=1 +else # All other shells: examine $0 for known shell binary filenames. + # Detects `sh` and `dash`; add additional shell filenames as needed. + case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac +fi + +if [ $sourced -eq 0 ]; then + # invoked as a command + if [ "$#" -eq 0 ]; then + set -- --help + fi + _ci_main "$@" +fi From 06a90e0b4b38543741c5749b5710f2796c61f0d8 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 14 Sep 2025 20:11:49 +0900 Subject: [PATCH 1220/2098] stm32/boards/NUCLEO_F401RE: Change flash latency for NUCLEO-F401RE. For NUCLEO-F401RE, FLASH_LATENCY_2 can be specified for flash latency because it runs at 84MHz. Signed-off-by: Yuuki NAGAO --- ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h index 8dda34f4a06..6150765e18e 100644 --- a/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h @@ -21,6 +21,9 @@ #define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV4) #define MICROPY_HW_CLK_PLLQ (7) +// 84 MHz needs 2 wait states +#define MICROPY_HW_FLASH_LATENCY (FLASH_LATENCY_2) + // UART config #define MICROPY_HW_UART2_TX (pin_A2) #define MICROPY_HW_UART2_RX (pin_A3) From 03e3a06de641cca6299b1a7bb8f9379014e61b8e Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Mon, 15 Sep 2025 09:39:28 +0900 Subject: [PATCH 1221/2098] stm32/boards/NUCLEO_G474RE: Change flash latency for NUCLEO-G474RE. For NUCLEO-G474RE, FLASH_LATENCY_4 can be specified instead of FLASH_LATENCY_8 because it runs at 170MHz. Signed-off-by: Yuuki NAGAO --- ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h | 2 +- ports/stm32/system_stm32.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h index 62d104a5462..e807a54780d 100644 --- a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h @@ -29,7 +29,7 @@ #define MICROPY_HW_CLK_USE_HSI48 (1) // for RNG // 4 wait states -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_8 +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_4 // UART config #define MICROPY_HW_LPUART1_TX (pin_A2) // A2 (to STLINK), B11, C1 diff --git a/ports/stm32/system_stm32.c b/ports/stm32/system_stm32.c index b76a41e97bf..5a9fef1c2a5 100644 --- a/ports/stm32/system_stm32.c +++ b/ports/stm32/system_stm32.c @@ -494,7 +494,7 @@ MP_WEAK void SystemClock_Config(void) { #endif #if defined(STM32G4) - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_8) != HAL_OK) { + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, MICROPY_HW_FLASH_LATENCY) != HAL_OK) { MICROPY_BOARD_FATAL_ERROR("HAL_RCC_ClockConfig"); } PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC | RCC_PERIPHCLK_LPUART1 From a6efc670647193cab3fd2b84253960f5304e0bdd Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Mon, 15 Sep 2025 08:09:01 +0900 Subject: [PATCH 1222/2098] stm32/i2c: Add hardware I2C implementation for STM32G4. For STM32G4, hardware I2C can implement same as STM32L4 for machine.I2C. This commit makes to be able to use of hardware I2C in machine.I2C. Tested on NUCLEO-G474RE. Signed-off-by: Yuuki NAGAO --- ports/stm32/i2c.c | 4 ++-- ports/stm32/machine_i2c.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index 778885eac8c..2460a36cb15 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -271,12 +271,12 @@ int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len) { return num_acks; } -#elif defined(STM32F0) || defined(STM32F7) || defined(STM32H7) || defined(STM32L4) +#elif defined(STM32F0) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32L4) #if defined(STM32H7) #define APB1ENR APB1LENR #define RCC_APB1ENR_I2C1EN RCC_APB1LENR_I2C1EN -#elif defined(STM32L4) +#elif defined(STM32G4) || defined(STM32L4) #define APB1ENR APB1ENR1 #define RCC_APB1ENR_I2C1EN RCC_APB1ENR1_I2C1EN #endif diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index ca2170e32d3..0fb9cf83c90 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -34,7 +34,7 @@ #define I2C_POLL_DEFAULT_TIMEOUT_US (50000) // 50ms -#if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32L1) || defined(STM32L4) +#if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32L1) || defined(STM32L4) typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; From 7cbe083b2c8d58ae3e0128b28f001934d45cff35 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 14 Sep 2025 12:23:24 +0200 Subject: [PATCH 1223/2098] stm32/uart: Enable UART FIFO for STM32N6. Signed-off-by: iabdalkader --- ports/stm32/Makefile | 2 +- ports/stm32/uart.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index abca3a05f8c..3af1d892a03 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -445,7 +445,7 @@ HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ ) endif -ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),h7)) +ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),h7 n6)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_,\ hal_uart_ex.c \ ) diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 9354af4a291..d0c5819b20d 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -665,7 +665,7 @@ bool uart_init(machine_uart_obj_t *uart_obj, huart.Init.HwFlowCtl = flow; huart.Init.OverSampling = UART_OVERSAMPLING_16; - #if defined(STM32G4) || defined(STM32H7) // WB also has a fifo.. + #if defined(STM32G4) || defined(STM32H7) || defined(STM32N6) // WB also has a fifo.. huart.FifoMode = UART_FIFOMODE_ENABLE; #endif @@ -717,7 +717,7 @@ bool uart_init(machine_uart_obj_t *uart_obj, uart_obj->char_width = CHAR_WIDTH_8BIT; } - #if defined(STM32H7) + #if defined(STM32H7) || defined(STM32N6) HAL_UARTEx_SetTxFifoThreshold(&huart, UART_TXFIFO_THRESHOLD_1_8); HAL_UARTEx_SetRxFifoThreshold(&huart, UART_RXFIFO_THRESHOLD_1_8); HAL_UARTEx_EnableFifoMode(&huart); @@ -1186,8 +1186,8 @@ size_t uart_tx_data(machine_uart_obj_t *self, const void *src_in, size_t num_cha // timeout_char by FIFO size + 1. // STM32G4 has 8 words FIFO. timeout = (8 + 1) * self->timeout_char; - #elif defined(STM32H7) - // STM32H7 has 16 words FIFO. + #elif defined(STM32H7) || defined(STM32N6) + // STM32H7 and STM32N6 have 16 words FIFO. timeout = (16 + 1) * self->timeout_char; #else // The timeout specified here is for waiting for the TX data register to From c4d99f34ae4bbec075550acff680f31364708580 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Mon, 15 Sep 2025 14:04:37 +0900 Subject: [PATCH 1224/2098] stm32/uart: Fix LPUART init failure with low baudrate. UART prescaler value should set up correctly to initialize LPUART with low baudrate for STM32G0/G4/H5. This can be implemented similarly to STM32H7/N6/WB. Signed-off-by: Yuuki NAGAO --- ports/stm32/uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index d0c5819b20d..63de8cc6fa5 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -673,7 +673,7 @@ bool uart_init(machine_uart_obj_t *uart_obj, huart.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; #endif - #if defined(STM32H7) || defined(STM32N6) || defined(STM32WB) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32N6) || defined(STM32WB) // Compute the smallest prescaler that will allow the given baudrate. uint32_t presc = UART_PRESCALER_DIV1; if (uart_obj->uart_id == PYB_LPUART_1) { From 8d04f5da7df99f105523c32ceaeb72113e09a18c Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Mon, 15 Sep 2025 09:09:49 +0900 Subject: [PATCH 1225/2098] stm32/boards: Set RCC_HSE_BYPASS for relevant NUCLEO boards. For NUCLEO boards that does not have HSE crystal, HSEState should set RCC_HSE_BYPASS to use HSE clock from ST-LINK 8MHz. Signed-off-by: Yuuki NAGAO --- ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h | 4 +++- ports/stm32/boards/NUCLEO_F446RE/mpconfigboard.h | 2 ++ ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h | 2 ++ ports/stm32/boards/NUCLEO_L152RE/mpconfigboard.h | 4 +++- ports/stm32/powerctrlboot.c | 2 ++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h index 6150765e18e..21ce91fd939 100644 --- a/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F401RE/mpconfigboard.h @@ -9,12 +9,14 @@ // HSE is 8MHz, HSI is 16MHz CPU freq set to 84MHz // Default source for the clock is HSI. // For revisions of the board greater than C-01, HSE can be used as a -// clock source by removing the #define MICROPY_HW_CLK_USE_HSE line +// clock source by removing the #define MICROPY_HW_CLK_USE_HSI line #define MICROPY_HW_CLK_USE_HSI (1) #if MICROPY_HW_CLK_USE_HSI #define MICROPY_HW_CLK_PLLM (16) #else +// HSE comes from ST-LINK 8MHz, not crystal. +#define MICROPY_HW_CLK_USE_BYPASS (1) #define MICROPY_HW_CLK_PLLM (8) #endif #define MICROPY_HW_CLK_PLLN (336) diff --git a/ports/stm32/boards/NUCLEO_F446RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F446RE/mpconfigboard.h index a0a6dbf8c51..62fd5abe6dd 100644 --- a/ports/stm32/boards/NUCLEO_F446RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F446RE/mpconfigboard.h @@ -14,6 +14,8 @@ #define MICROPY_HW_CLK_PLLN (336) #define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2) #define MICROPY_HW_CLK_PLLQ (7) +// HSE comes from ST-LINK 8MHz, not crystal. +#define MICROPY_HW_CLK_USE_BYPASS (1) // UART config #define MICROPY_HW_UART1_TX (pin_B6) // Arduino D10, pin 17 on CN10 diff --git a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h index 4ed9f41832d..e607d5f9355 100644 --- a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h @@ -21,6 +21,8 @@ void NUCLEO_F767ZI_board_early_init(void); #define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2) #define MICROPY_HW_CLK_PLLQ (9) #define MICROPY_HW_FLASH_LATENCY (FLASH_LATENCY_7) // 210-216 MHz needs 7 wait states +// HSE comes from ST-LINK 8MHz, not crystal. +#define MICROPY_HW_CLK_USE_BYPASS (1) // UART config #define MICROPY_HW_UART2_TX (pin_D5) diff --git a/ports/stm32/boards/NUCLEO_L152RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_L152RE/mpconfigboard.h index 5b83a980ce1..4eef1db8fdc 100644 --- a/ports/stm32/boards/NUCLEO_L152RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_L152RE/mpconfigboard.h @@ -12,7 +12,7 @@ // HSE is 8MHz, HSI is 16MHz CPU freq set to 32MHz // Default source for the clock is HSI. // For revisions of the board greater than C-01, HSE can be used as a -// clock source by removing the #define MICROPY_HW_CLK_USE_HSE line +// clock source by removing the #define MICROPY_HW_CLK_USE_HSI line #define MICROPY_HW_CLK_USE_HSI (1) #if MICROPY_HW_CLK_USE_HSI @@ -21,6 +21,8 @@ #else #define MICROPY_HW_CLK_PLLMUL (RCC_CFGR_PLLMUL12) #define MICROPY_HW_CLK_PLLDIV (RCC_CFGR_PLLDIV3) +// HSE comes from ST-LINK 8MHz, not crystal. +#define MICROPY_HW_CLK_USE_BYPASS (1) #endif // UART config diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 059d2a45da9..2354deb4da1 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -385,7 +385,9 @@ void SystemClock_Config(void) { RCC->CFGR = RCC_CFGR_PLLSRC_HSI; #else // Enable the 8MHz external oscillator + #if MICROPY_HW_CLK_USE_BYPASS RCC->CR |= RCC_CR_HSEBYP; + #endif RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)) { } From ec1bfcfbb7944bb7bbf5048de0152867cf71817b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 17 Sep 2025 11:41:59 +1000 Subject: [PATCH 1226/2098] stm32/main: Use defined constants to enable N6 clocks during low power. Use constants from the HAL instead of literal numbers to select which peripheral clocks are enabled during low power mode. Signed-off-by: Damien George --- ports/stm32/main.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index af4d7f8bbb6..8bce6b477a5 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -400,19 +400,21 @@ void stm32_main(uint32_t reset_mode) { #if defined(STM32N6) // SRAM, XSPI needs to remain awake during sleep, eg so DMA from flash works. - LL_MEM_EnableClockLowPower(0xffffffff); + LL_MEM_EnableClockLowPower(LL_MEM_AXISRAM1 | LL_MEM_AXISRAM2 | LL_MEM_AXISRAM3 + | LL_MEM_AXISRAM4 | LL_MEM_AXISRAM5 | LL_MEM_AXISRAM6 | LL_MEM_AHBSRAM1 | LL_MEM_AHBSRAM2 + | LL_MEM_BKPSRAM | LL_MEM_FLEXRAM | LL_MEM_CACHEAXIRAM | LL_MEM_VENCRAM | LL_MEM_BOOTROM); LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_XSPI2 | LL_AHB5_GRP1_PERIPH_XSPIM); LL_APB4_GRP1_EnableClock(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); LL_APB4_GRP1_EnableClockLowPower(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); // Enable some AHB peripherals during sleep. - LL_AHB1_GRP1_EnableClockLowPower(0xffffffff); // GPDMA1, ADC12 - LL_AHB4_GRP1_EnableClockLowPower(0xffffffff); // GPIOA-Q, PWR, CRC + LL_AHB1_GRP1_EnableClockLowPower(LL_AHB1_GRP1_PERIPH_ALL); // GPDMA1, ADC12 + LL_AHB4_GRP1_EnableClockLowPower(LL_AHB4_GRP1_PERIPH_ALL); // GPIOA-Q, PWR, CRC // Enable some APB peripherals during sleep. - LL_APB1_GRP1_EnableClockLowPower(0xffffffff); // I2C, I3C, LPTIM, SPI, TIM, UART, WWDG - LL_APB2_GRP1_EnableClockLowPower(0xffffffff); // SAI, SPI, TIM, UART - LL_APB4_GRP1_EnableClockLowPower(0xffffffff); // I2C, LPTIM, LPUART, RTC, SPI + LL_APB1_GRP1_EnableClockLowPower(LL_APB1_GRP1_PERIPH_ALL); // I2C, I3C, LPTIM, SPI, TIM, UART, WWDG + LL_APB2_GRP1_EnableClockLowPower(LL_APB2_GRP1_PERIPH_ALL); // SAI, SPI, TIM, UART + LL_APB4_GRP1_EnableClockLowPower(LL_APB4_GRP1_PERIPH_ALL); // I2C, LPTIM, LPUART, RTC, SPI #endif mpu_init(); From 1b92bda5b8e5e2db8e57c4b7134930e52bc5207a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 29 Sep 2024 17:40:17 +0200 Subject: [PATCH 1227/2098] mpy-cross: Add RISC-V RV64IMC support in MPY files. MPY files can now hold data to be run on RV64IMC. This can be accomplished by passing the `-march=rv64imc` flag to mpy-cross. Signed-off-by: Alessandro Gatti --- docs/reference/mpyfiles.rst | 2 +- mpy-cross/main.c | 9 ++++++++- mpy-cross/mpy_cross/__init__.py | 1 + py/persistentcode.h | 1 + tests/feature_check/target_info.py | 1 + tools/mpy-tool.py | 6 ++++-- 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/reference/mpyfiles.rst b/docs/reference/mpyfiles.rst index be0114701ae..cd4db623ffe 100644 --- a/docs/reference/mpyfiles.rst +++ b/docs/reference/mpyfiles.rst @@ -58,7 +58,7 @@ If importing an .mpy file fails then try the following: sys_mpy = sys.implementation._mpy arch = [None, 'x86', 'x64', 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', - 'xtensa', 'xtensawin', 'rv32imc'][sys_mpy >> 10] + 'xtensa', 'xtensawin', 'rv32imc', 'rv64imc'][sys_mpy >> 10] print('mpy version:', sys_mpy & 0xff) print('mpy sub-version:', sys_mpy >> 8 & 3) print('mpy flags:', end='') diff --git a/mpy-cross/main.c b/mpy-cross/main.c index b7771ce6e79..63a74d0844b 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -130,7 +130,8 @@ static int usage(char **argv) { "Target specific options:\n" "-msmall-int-bits=number : set the maximum bits used to encode a small-int\n" "-march= : set architecture for native emitter;\n" - " x86, x64, armv6, armv6m, armv7m, armv7em, armv7emsp, armv7emdp, xtensa, xtensawin, rv32imc, host, debug\n" + " x86, x64, armv6, armv6m, armv7m, armv7em, armv7emsp,\n" + " armv7emdp, xtensa, xtensawin, rv32imc, rv64imc, host, debug\n" "\n" "Implementation specific options:\n", argv[0] ); @@ -316,6 +317,9 @@ MP_NOINLINE int main_(int argc, char **argv) { } else if (strcmp(arch, "rv32imc") == 0) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_RV32IMC; mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_RV32I; + } else if (strcmp(arch, "rv64imc") == 0) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_RV64IMC; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_RV64I; } else if (strcmp(arch, "debug") == 0) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_DEBUG; mp_dynamic_compiler.nlr_buf_num_regs = 0; @@ -329,6 +333,9 @@ MP_NOINLINE int main_(int argc, char **argv) { #elif defined(__arm__) && !defined(__thumb2__) mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV6; mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + #elif defined(__riscv) && (__riscv_xlen == 64) + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_RV64IMC; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_RV64I; #else mp_printf(&mp_stderr_print, "unable to determine host architecture for -march=host\n"); exit(1); diff --git a/mpy-cross/mpy_cross/__init__.py b/mpy-cross/mpy_cross/__init__.py index 9399aa92f39..6d7002a3b89 100644 --- a/mpy-cross/mpy_cross/__init__.py +++ b/mpy-cross/mpy_cross/__init__.py @@ -43,6 +43,7 @@ "NATIVE_ARCH_XTENSA": "xtensa", "NATIVE_ARCH_XTENSAWIN": "xtensawin", "NATIVE_ARCH_RV32IMC": "rv32imc", + "NATIVE_ARCH_RV64IMC": "rv64imc", } globals().update(NATIVE_ARCHS) diff --git a/py/persistentcode.h b/py/persistentcode.h index cf257a7ab1f..a45d5fd182e 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -98,6 +98,7 @@ enum { MP_NATIVE_ARCH_XTENSA, MP_NATIVE_ARCH_XTENSAWIN, MP_NATIVE_ARCH_RV32IMC, + MP_NATIVE_ARCH_RV64IMC, MP_NATIVE_ARCH_DEBUG, // this entry should always be last }; diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index 5367eb6a47d..35a995a153d 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -19,6 +19,7 @@ "xtensa", "xtensawin", "rv32imc", + "rv64imc", ][sys_mpy >> 10] build = getattr(sys.implementation, "_build", "unknown") thread = getattr(sys.implementation, "_thread", None) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index c87c656ff9b..df918e15398 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -94,6 +94,7 @@ class Config: MP_NATIVE_ARCH_XTENSA = 9 MP_NATIVE_ARCH_XTENSAWIN = 10 MP_NATIVE_ARCH_RV32IMC = 11 +MP_NATIVE_ARCH_RV64IMC = 12 MP_PERSISTENT_OBJ_FUN_TABLE = 0 MP_PERSISTENT_OBJ_NONE = 1 @@ -1061,6 +1062,7 @@ def __init__( MP_NATIVE_ARCH_XTENSA, MP_NATIVE_ARCH_XTENSAWIN, MP_NATIVE_ARCH_RV32IMC, + MP_NATIVE_ARCH_RV64IMC, ): self.fun_data_attributes = '__attribute__((section(".text,\\"ax\\",@progbits # ")))' else: @@ -1078,8 +1080,8 @@ def __init__( self.fun_data_attributes += " __attribute__ ((aligned (4)))" elif ( MP_NATIVE_ARCH_ARMV6M <= config.native_arch <= MP_NATIVE_ARCH_ARMV7EMDP - ) or config.native_arch == MP_NATIVE_ARCH_RV32IMC: - # ARMVxxM or RV32IMC -- two byte align. + ) or MP_NATIVE_ARCH_RV32IMC <= config.native_arch <= MP_NATIVE_ARCH_RV64IMC: + # ARMVxxM or RV{32,64}IMC -- two byte align. self.fun_data_attributes += " __attribute__ ((aligned (2)))" def disassemble(self): From 44986b1f0401f086254f7dabc65f1816db35829a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 29 Sep 2024 17:49:24 +0200 Subject: [PATCH 1228/2098] qemu/riscv64: Add new QEMU RV64 port. This adds a QEMU-based bare metal RISC-V 64 bits port. For the time being only QEMU's "virt" 64 bits board is supported, using the lp64 ABI and the RV64IMC architecture. The port's README is also updated to keep track of these changes. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_qemu.yml | 12 + ports/qemu/Dockerfile.riscv | 39 +++ ports/qemu/Makefile | 37 +++ ports/qemu/README.md | 54 +++- ports/qemu/boards/VIRT_RV64/mpconfigboard.mk | 11 + ports/qemu/mcu/rv64/entrypoint.s | 70 +++++ ports/qemu/mcu/rv64/interrupts.c | 294 +++++++++++++++++++ ports/qemu/mcu/rv64/startup.c | 86 ++++++ ports/qemu/mcu/rv64/virt.ld | 98 +++++++ ports/qemu/mpconfigport.h | 12 +- tools/ci.sh | 13 + 11 files changed, 717 insertions(+), 9 deletions(-) create mode 100644 ports/qemu/Dockerfile.riscv create mode 100644 ports/qemu/boards/VIRT_RV64/mpconfigboard.mk create mode 100644 ports/qemu/mcu/rv64/entrypoint.s create mode 100644 ports/qemu/mcu/rv64/interrupts.c create mode 100644 ports/qemu/mcu/rv64/startup.c create mode 100644 ports/qemu/mcu/rv64/virt.ld diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index 1284f11bbb3..e743f29c996 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -50,3 +50,15 @@ jobs: - name: Print failures if: failure() run: tests/run-tests.py --print-failures + + build_and_test_rv64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_qemu_setup_rv64 + - name: Build and run test suite + run: source tools/ci.sh && ci_qemu_build_rv64 + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures diff --git a/ports/qemu/Dockerfile.riscv b/ports/qemu/Dockerfile.riscv new file mode 100644 index 00000000000..9521872fe88 --- /dev/null +++ b/ports/qemu/Dockerfile.riscv @@ -0,0 +1,39 @@ +# This file is part of the MicroPython project, https://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2025 Alessandro Gatti +# +# 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. + +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y \ + gcc-riscv64-unknown-elf \ + picolibc-riscv64-unknown-elf \ + python3 \ + python3-pip \ + python3-platformdirs \ + python3-pyelftools \ + python3-serial \ + qemu-system && \ + pip3 install ar --break-system-packages + +WORKDIR /micropython diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index d213c8fc03c..685bb3536c4 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -35,6 +35,10 @@ ifeq ($(QEMU_ARCH),riscv32) MICROPY_HEAP_SIZE ?= 143360 FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_asm_rv32.py', 'frozen_const.py', 'frozen_viper.py', 'native_frozen_align.py'))" endif +ifeq ($(QEMU_ARCH),riscv64) +MICROPY_HEAP_SIZE ?= 204800 +FROZEN_MANIFEST ?= "require('unittest'); freeze('test-frzmpy', ('frozen_const.py',))" +endif MICROPY_FLOAT_IMPL ?= float @@ -110,6 +114,39 @@ SRC_BOARD_O += mcu/rv32/entrypoint.o endif +################################################################################ +# RISC-V 64-bit specific settings + +ifeq ($(QEMU_ARCH),riscv64) + +CROSS_COMPILE ?= riscv64-unknown-elf- + +GCC_VERSION = $(word 1, $(subst ., , $(shell $(CC) -dumpversion))) + +RV64_ABI = lp64 + +QEMU_ARGS += -bios none + +# GCC 10 and lower do not recognise the Zicsr extension in the architecture name. +ifeq ($(shell test $(GCC_VERSION) -le 10; echo $$?),0) +RV64_ARCH ?= rv64imac +else +# Recent GCC versions explicitly require to declare extensions. +RV64_ARCH ?= rv64imac_zicsr +endif + +AFLAGS += -mabi=$(RV64_ABI) -march=$(RV64_ARCH) +CFLAGS += $(AFLAGS) -mcmodel=medany +LDFLAGS += -mabi=$(RV64_ABI) -march=$(RV64_ARCH) -Wl,-EL -mcmodel=medany + +SRC_C += \ + mcu/rv64/interrupts.c \ + mcu/rv64/startup.c \ + +SRC_BOARD_O += mcu/rv64/entrypoint.o + +endif + ################################################################################ # Project specific settings and compiler/linker flags diff --git a/ports/qemu/README.md b/ports/qemu/README.md index d8ef3ac551e..bf5788961db 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -1,8 +1,8 @@ MicroPython port to qemu ======================== -This is experimental, community-supported port for Cortex-M and RISC-V RV32IMC -emulation as provided by QEMU (http://qemu.org). +This is experimental, community-supported port for Cortex-M and RISC-V +RV32IMC/RV64IMC emulation as provided by QEMU (http://qemu.org). The purposes of this port are to enable: @@ -26,9 +26,9 @@ Dependencies For ARM-based boards the build requires a bare-metal ARM toolchain, such as `arm-none-eabi-gcc`. -### RISC-V +### RISC-V 32 -For RISC-V-based boards the build requires a bare metal RISC-V toolchain with GCC 10 +For RV32-based boards the build requires a bare metal RISC-V toolchain with GCC 10 or later, either with multilib support or 32 bits specific (M, C, and Zicsr extensions must be supported, along with ilp32 ABI). Both newlib and picolibc are supported, with the latter having precedence if found. @@ -37,6 +37,47 @@ Most pre-built toolchains should work out of the box, either coming from your Linux distribution's package manager, or independently packaged ones like [xPack](https://xpack.github.io/dev-tools/riscv-none-elf-gcc/). +### RISC-V 64 + +For RV64-based boards the build requires a bare metal RISC-V toolchain with GCC 10 +or later, either with multilib support or 64 bits specific (M, C, and Zicsr +extensions must be supported, along with lp64 ABI). In other words, if your +compiler can build a binary that's compliant to any ratified RVA profiles, +you *might* be good to go: if you run your cross-compiler `gcc` binary with `-v` as +its only argument and you don't see mentions of `-mcmodel=medany`, your +toolchain is probably not going to work for this situation (more later). Both newlib +and picolibc are supported, with the latter having precedence if found. + +RISC-V 64 toolchains are a bit more temperamental. Some toolchains have their `libc`, +`libm`, and `libgcc` libraries built in a way that they cannot be used with code +placed at an offset beyond 0x7FFFF7FF. QEMU's `VIRT_RV64` board code address space +starts at 0x80000000, making those toolchains not viable if code from those libraries +is brought in by the build process. A lot of readily available RISC-V toolchains are +unfortunately broken from this point of view. + +However, to work around this, a Docker container file is provided to be able to run +tests using a known working compiler (the same one running MicroPython's RV32 and RV64 +CI jobs). + +To build the provided toolchain image you can use the following commands: + +```bash +cd $MICROPYTHON_SOURCE_ROOT/ports/qemu +docker build -t micropython/mpy-qemu-riscv -f Dockerfile.riscv . +``` + +and then tests can be run with: + +```bash +cd $MICROPYTHON_SOURCE_ROOT +docker container run -v .:/micropython --rm -it micropython/mpy-qemu-riscv make -C ports/qemu -- BOARD=VIRT_RV64 test +``` + +(If you use Podman, you can replace `docker` with `podman` in the commands above). + +Also, if you replace `VIRT_RV64` with `VIRT_RV32` you should be able to run +32-bits tests as well! + Build instructions ------------------ @@ -63,6 +104,7 @@ Available boards are: | `NETDUINO2` | `arm` | `netduino2` | | `SABRELITE` | `arm` | `sabrelite` | | `VIRT_RV32` | `riscv32` | `virt` | +| `VIRT_RV64` | `riscv64` | `virt` | Running ------- @@ -100,8 +142,8 @@ tests against the serial device, for example: $ ./run-tests.py -t /dev/pts/1 Selected native modules that come as examples with the MicroPython source tree -can also be tested with this command (this is currently supported only for the -`VIRT_RV32` board): +can also be tested with this command (this is currently not supported for the +`VIRT_RV64` board): $ make test_natmod diff --git a/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk b/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk new file mode 100644 index 00000000000..dae4ca3e036 --- /dev/null +++ b/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk @@ -0,0 +1,11 @@ +QEMU_ARCH = riscv64 +QEMU_MACHINE = virt + +CFLAGS += -DQEMU_SOC_VIRT +CFLAGS += -DMICROPY_HW_MCU_NAME='"$(RV64_ARCH)"' + +LDSCRIPT = mcu/rv64/virt.ld + +SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv64i.o + +MPY_CROSS_FLAGS += -march=rv64imc diff --git a/ports/qemu/mcu/rv64/entrypoint.s b/ports/qemu/mcu/rv64/entrypoint.s new file mode 100644 index 00000000000..7bf7c24c62b --- /dev/null +++ b/ports/qemu/mcu/rv64/entrypoint.s @@ -0,0 +1,70 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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. + */ + + .section .start + .option norvc /* Do not emit compressed instructions. */ + .type start, @function + .global start + +start: + .cfi_startproc + + .option push + .option norelax + la gp, _global_pointer /* Load global pointer register. */ + .option pop + + csrw satp, zero /* Disable supervisor mode. */ + + /* Fill stack with a canary value. */ + + li t0, 0xBBBBBBBBBBBBBBBB /* Load canary value. */ + la t1, _sstack /* Load stack area start address. */ + la t2, _estack /* Load stack area end address. */ +1: + sd t0, (t1) /* Write canary. */ + addi t1, t1, 8 /* Next word. */ + bltu t1, t2, 1b /* Loop until stack is filled. */ + + la sp, _estack /* Load stack pointer. */ + + /* Clear BSS area. */ + + la t1, _sbss /* Load BSS area start address. */ + la t2, _ebss /* Load BSS area end address. */ +1: + sd zero, (t1) /* Clear word. */ + addi t1, t1, 8 /* Next word. */ + bltu t1, t2, 1b /* Loop until BSS is cleared. */ + + /* Set program counter. */ + + la t0, _entry_point /* Load program counter address. */ + csrw mepc, t0 /* Store it into machine exception PC. */ + tail _entry_point /* Jump to entry point. */ + + .cfi_endproc + .end diff --git a/ports/qemu/mcu/rv64/interrupts.c b/ports/qemu/mcu/rv64/interrupts.c new file mode 100644 index 00000000000..e616e82d279 --- /dev/null +++ b/ports/qemu/mcu/rv64/interrupts.c @@ -0,0 +1,294 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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 +#include + +// Vector table + +void mtvec_table(void) __attribute__((naked, section(".text.mtvec"), aligned(256))); + +// Default interrupts + +#define ASSIGN_EMPTY_MACHINE_INTERRUPT(interrupt_name) \ + void interrupt_name(void) __attribute__((alias("mtvec_nop"))) + +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_ssi); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_msi); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_sti); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_mti); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_sei); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_mei); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq0); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq1); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq2); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq3); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq4); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq5); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq6); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq7); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq8); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq9); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq10); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq11); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq12); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq13); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq14); +ASSIGN_EMPTY_MACHINE_INTERRUPT(mtvec_plat_irq15); + +void mtvec_table(void) { + __asm volatile ( + ".org mtvec_table + 0 \n" + "jal zero, mtvec_exception \n" // Exception Handler + ".org mtvec_table + 4 \n" + "jal zero, mtvec_ssi \n" // Supervisor Software Interrupt + ".org mtvec_table + 12 \n" + "jal zero, mtvec_msi \n" // Machine Software Interrupt + ".org mtvec_table + 20 \n" + "jal zero, mtvec_sti \n" // Supervisor Timer Interrupt + ".org mtvec_table + 28 \n" + "jal zero, mtvec_mti \n" // Machine Timer Interrupt + ".org mtvec_table + 36 \n" + "jal zero, mtvec_sei \n" // Supervisor External Interrupt + ".org mtvec_table + 44 \n" + "jal zero, mtvec_mei \n" // Machine External Interrupt + // Not sure how many platform interrupts QEMU handles... + ".org mtvec_table + 48 \n" + "jal mtvec_plat_irq0 \n" // Platform Interrupt #0 + "jal mtvec_plat_irq1 \n" // Platform Interrupt #1 + "jal mtvec_plat_irq2 \n" // Platform Interrupt #2 + "jal mtvec_plat_irq3 \n" // Platform Interrupt #3 + "jal mtvec_plat_irq4 \n" // Platform Interrupt #4 + "jal mtvec_plat_irq5 \n" // Platform Interrupt #5 + "jal mtvec_plat_irq6 \n" // Platform Interrupt #6 + "jal mtvec_plat_irq7 \n" // Platform Interrupt #7 + "jal mtvec_plat_irq8 \n" // Platform Interrupt #8 + "jal mtvec_plat_irq9 \n" // Platform Interrupt #9 + "jal mtvec_plat_irq10 \n" // Platform Interrupt #10 + "jal mtvec_plat_irq11 \n" // Platform Interrupt #11 + "jal mtvec_plat_irq12 \n" // Platform Interrupt #12 + "jal mtvec_plat_irq13 \n" // Platform Interrupt #13 + "jal mtvec_plat_irq14 \n" // Platform Interrupt #14 + "jal mtvec_plat_irq15 \n" // Platform Interrupt #15 + : + : + : "memory" + ); +} + +static volatile uintptr_t registers_copy[35] = { 0 }; + +static const char *exception_causes[] = { + "Reserved", // 0 + "Supervisor software interrupt", // 1 + "Machine software interrupt", // 2 + "Supervisor timer interrupt", // 3 + "Machine timer interrupt", // 4 + "Supervisor external interrupt", // 5 + "Machine external interrupt", // 6 + "Designated for platform use", // 7 + "Instruction address misaligned", // 8 + "Instruction address fault", // 9 + "Illegal instruction", // 10 + "Breakpoint", // 11 + "Load address misaligned", // 12 + "Load address fault", // 13 + "Store/AMO address misaligned", // 14 + "Store/AMO access fault", // 15 + "Environment call from U-mode", // 16 + "Environment call from S-mode", // 17 + "Environment call from M-mode", // 18 + "Instruction page fault", // 19 + "Load page fault", // 20 + "Store/AMO page fault", // 21 + "Designated for custom use" // 22 +}; + +#define MSB64 (1ULL << 63) + +static const char *lookup_cause(uintptr_t mcause) { + if (mcause & MSB64) { + switch (mcause & (MSB64 - 1)) { + case 1: + return exception_causes[1]; + case 3: + return exception_causes[2]; + case 5: + return exception_causes[3]; + case 7: + return exception_causes[4]; + case 9: + return exception_causes[5]; + case 11: + return exception_causes[6]; + default: + return ((mcause & (MSB64 - 1)) >= 16) ? + exception_causes[7] : + exception_causes[0]; + } + } + + switch (mcause) { + case 0: + return exception_causes[8]; + case 1: + return exception_causes[9]; + case 2: + return exception_causes[10]; + case 3: + return exception_causes[11]; + case 4: + return exception_causes[12]; + case 5: + return exception_causes[13]; + case 6: + return exception_causes[14]; + case 7: + return exception_causes[15]; + case 8: + return exception_causes[16]; + case 9: + return exception_causes[17]; + case 11: + return exception_causes[18]; + case 12: + return exception_causes[19]; + case 13: + return exception_causes[20]; + case 15: + return exception_causes[21]; + default: { + if ((mcause >= 24 && mcause <= 31) || + (mcause >= 48 && mcause <= 63)) { + return exception_causes[22]; + } + + return exception_causes[0]; + } + } +} + +#pragma GCC push_options +#pragma GCC optimize ("align-functions=8") + +__attribute__((interrupt("machine"), weak)) void mtvec_nop(void) { +} + +__attribute__((interrupt("machine"), weak)) void mtvec_exception(void) { + __asm volatile ( + "csrrw x31, mscratch, x31 \n" // Save X31 + "la x31, registers_copy \n" // Load target address + "sd x1, 0(x31) \n" // Save X1 + "sd x2, 8(x31) \n" // Save X2 + "sd x3, 16(x31) \n" // Save X3 + "sd x4, 24(x31) \n" // Save X4 + "sd x5, 32(x31) \n" // Save X5 + "sd x6, 40(x31) \n" // Save X6 + "sd x7, 48(x31) \n" // Save X7 + "sd x8, 56(x31) \n" // Save X8 + "sd x9, 64(x31) \n" // Save X9 + "sd x10, 72(x31) \n" // Save X10 + "sd x11, 80(x31) \n" // Save X11 + "sd x12, 88(x31) \n" // Save X12 + "sd x13, 96(x31) \n" // Save X13 + "sd x14, 104(x31) \n" // Save X14 + "sd x15, 112(x31) \n" // Save X15 + "sd x16, 120(x31) \n" // Save X16 + "sd x17, 128(x31) \n" // Save X17 + "sd x18, 136(x31) \n" // Save X18 + "sd x19, 144(x31) \n" // Save X19 + "sd x20, 152(x31) \n" // Save X20 + "sd x21, 160(x31) \n" // Save X21 + "sd x22, 168(x31) \n" // Save X22 + "sd x23, 176(x31) \n" // Save X23 + "sd x24, 184(x31) \n" // Save X24 + "sd x25, 192(x31) \n" // Save X25 + "sd x26, 200(x31) \n" // Save X26 + "sd x27, 208(x31) \n" // Save X27 + "sd x28, 216(x31) \n" // Save X28 + "sd x29, 224(x31) \n" // Save X29 + "sd x30, 232(x31) \n" // Save X30 + "csrr x30, mscratch \n" // Restore X31 + "sd x30, 240(x31) \n" // Save X31 + "csrr x30, mepc \n" // Load MEPC + "sd x30, 248(x31) \n" // Save MEPC + "csrr x30, mcause \n" // Load MCAUSE + "sd x30, 256(x31) \n" // Save MCAUSE + "csrr x30, mtval \n" // Load MTVAL + "sd x30, 264(x31) \n" // Save MTVAL + "csrr x30, mstatus \n" // Load MSTATUS + "sd x30, 272(x31) \n" // Save MSTATUS + "ld x30, 232(x31) \n" // Restore X30 + "ld x31, 240(x31) \n" // Restore X31 + : + : + : "memory" + ); + + printf("\nMACHINE EXCEPTION CAUGHT:\n\n"); + + printf(" RA=%016lX SP=%016lX GP=%016lX TP=%016lX T0=%016lX T1=%016lX\n", + registers_copy[0], registers_copy[1], registers_copy[2], + registers_copy[3], registers_copy[4], registers_copy[5]); + printf(" T2=%016lX S0=%016lX S1=%016lX A0=%016lX A1=%016lX A2=%016lX\n", + registers_copy[6], registers_copy[7], registers_copy[8], + registers_copy[9], registers_copy[10], registers_copy[11]); + printf(" A3=%016lX A4=%016lX A5=%016lX A6=%016lX A7=%016lX S2=%016lX\n", + registers_copy[12], registers_copy[13], registers_copy[14], + registers_copy[15], registers_copy[16], registers_copy[17]); + printf(" S3=%016lX S4=%016lX S5=%016lX S6=%016lX S7=%016lX S8=%016lX\n", + registers_copy[18], registers_copy[19], registers_copy[20], + registers_copy[21], registers_copy[22], registers_copy[23]); + printf(" S9=%016lX S10=%016lX S11=%016lX T3=%016lX T4=%016lX T5=%016lX\n", + registers_copy[24], registers_copy[25], registers_copy[26], + registers_copy[27], registers_copy[28], registers_copy[29]); + printf(" T6=%016lX\n\n", registers_copy[30]); + + printf(" MEPC=%016lX MTVAL=%016lX MSTATUS=%016lx MCAUSE=%016lx (%s)\n", + registers_copy[31], registers_copy[33], registers_copy[34], + registers_copy[32], lookup_cause(registers_copy[32])); + + exit(-1); +} + +#pragma GCC pop_options + +void set_interrupt_table(void) { + __asm volatile ( + "csrrci s0, mstatus, 8 \n" // S0 = MSTATUS & ~MIE + "csrw mstatus, s0 \n" // Global machine interrupts are disabled + "csrw mie, zero \n" // Disable machine interrupts + "csrw mip, zero \n" // Clear pending machine interrupts + "addi s0, %0, 1 \n" // Vectored machine interrupts enabled + "csrw mtvec, s0 \n" // Set new machine vector table + "csrrsi s0, mstatus, 8 \n" // S0 = MSTATUS | MIE + "csrw mstatus, s0 \n" // Global machine interrupts are enabled + : + : "r" (mtvec_table) + : "memory" + ); +} diff --git a/ports/qemu/mcu/rv64/startup.c b/ports/qemu/mcu/rv64/startup.c new file mode 100644 index 00000000000..8c7c5b7312a --- /dev/null +++ b/ports/qemu/mcu/rv64/startup.c @@ -0,0 +1,86 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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 +#include + +#include "uart.h" + +extern void set_interrupt_table(void); +extern int main(int argc, char **argv); + +void _entry_point(void) { + // Set interrupt table + set_interrupt_table(); + // Enable UART + uart_init(); + // Now that we have a basic system up and running we can call main + main(0, 0); + // Finished + exit(0); +} + +void exit(int status) { + uintptr_t semihosting_arguments[2] = { 0 }; + + // Exit via QEMU's RISC-V semihosting support. + __asm volatile ( + ".option push \n" // Transient options + ".option norvc \n" // Do not emit compressed instructions + ".align 4 \n" // 16 bytes alignment + "mv a1, %0 \n" // Load buffer + "li t0, 0x20026 \n" // ADP_Stopped_ApplicationExit + "sd t0, 0(a1) \n" // ADP_Stopped_ApplicationExit + "sd %1, 8(a1) \n" // Exit code + "addi a0, zero, 0x20 \n" // TARGET_SYS_EXIT_EXTENDED + "slli zero, zero, 0x1F \n" // Entry NOP + "ebreak \n" // Give control to the debugger + "srai zero, zero, 7 \n" // Semihosting call + ".option pop \n" // Restore previous options set + : + : "r" (semihosting_arguments), "r" (status) + : "memory" + ); + + // Should never reach here. + for (;;) { + } +} + +#ifndef NDEBUG +void __assert_func(const char *file, int line, const char *func, const char *expr) { + (void)func; + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + exit(1); +} +#endif + +// Picolibc requires `stdout` to be explicitly defined. + +#ifdef _PICOLIBC__ +FILE *const stdout; +#endif diff --git a/ports/qemu/mcu/rv64/virt.ld b/ports/qemu/mcu/rv64/virt.ld new file mode 100644 index 00000000000..7727839e0d5 --- /dev/null +++ b/ports/qemu/mcu/rv64/virt.ld @@ -0,0 +1,98 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Alessandro Gatti + * + * 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. + */ + +/* Output format is 64 bits little endian. */ +OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv"); + +/* + * Memory layout: + * + * 0x0000_0000_8000_0000: .text + * .........: .rodata + * 0x0000_0000_8040_0000: .data + * .........: _global_pointer + * .........: .bss + * 0x0000_0000_8060_0000: .stack + * 0x0000_0000_8060_0000: _sstack + * 0x0000_0000_8061_FFFF: _estack + */ +MEMORY +{ + ROM (xr) : ORIGIN = 0x0000000080000000, LENGTH = 4M + RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M + STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 128K +} + +SECTIONS +{ + /* Code + Read-Only data segment */ + + .text : ALIGN (4K) + { + *(.start) + *(.text) + . = ALIGN (4K); + *(.rodata) + _sirodata = .; + } > ROM + + .rodata : AT (_sirodata) ALIGN (4K) + { + *(.rodata) + } > ROM + + /* Data + BSS segment */ + + .data : ALIGN (4K) + { + *(.data) + _sibss = .; + } > RAM + + .bss : AT (_sibss) ALIGN (4K) + { + /* Mark global pointer address. */ + _global_pointer = .; + + /* Mark BSS start. */ + . = . + 4; + _sbss = .; + *(.bss) + /* Mark BSS end. */ + _ebss = .; + } > RAM + + /* Isolated stack segment. */ + + .stack : ALIGN(4K) + { + /* Mark stack start. */ + _sstack = .; + . = LENGTH(STACK); + /* Mark stack end. */ + _estack = .; + } > STACK +} diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 6c6dd68535a..1649d8a01ad 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -39,7 +39,7 @@ #define MICROPY_EMIT_INLINE_THUMB (1) #endif #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) -#elif defined(__riscv) +#elif defined(__riscv) && (__riscv_xlen == 32) #define MICROPY_EMIT_RV32 (1) #define MICROPY_EMIT_INLINE_RV32 (1) #endif @@ -67,14 +67,20 @@ // type definitions for the specific machine +#if defined(__riscv) && (__riscv_xlen == 64) +#define MP_SSIZE_MAX (0x7fffffffffffffff) +typedef int64_t mp_int_t; // must be pointer size +typedef uint64_t mp_uint_t; // must be pointer size +#else #define MP_SSIZE_MAX (0x7fffffff) +typedef int32_t mp_int_t; // must be pointer size +typedef uint32_t mp_uint_t; // must be pointer size +#endif #define UINT_FMT "%lu" #define INT_FMT "%ld" #define HEX_FMT "%lx" -typedef int32_t mp_int_t; // must be pointer size -typedef uint32_t mp_uint_t; // must be pointer size typedef long mp_off_t; // We need to provide a declaration/definition of alloca() diff --git a/tools/ci.sh b/tools/ci.sh index 6b974f98304..26def3cc4de 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -337,6 +337,13 @@ function ci_qemu_setup_rv32 { qemu-system-riscv32 --version } +function ci_qemu_setup_rv64 { + ci_gcc_riscv_setup + sudo apt-get update + sudo apt-get install qemu-system + qemu-system-riscv64 --version +} + function ci_qemu_build_arm_prepare { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu submodules @@ -388,6 +395,12 @@ function ci_qemu_build_rv32 { make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV32 test_natmod } +function ci_qemu_build_rv64 { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 submodules + make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 test +} + ######################################################################################## # ports/renesas-ra From 42ea7bc32af49f305ece4f4e4601accea9df0534 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Jul 2025 19:39:06 +0200 Subject: [PATCH 1229/2098] py/mpstate: Make it possible for mpy-cross to set emitter options. This commit introduces a way for mpy-cross to pass a set of options to the chosen emitter. This is achieved by adding an opaque pointer to the dynamic compiler state structure that is only accessed by emitters that have a need to receive options from mpy-cross when generating code. That's a way to make this feature possible without breaking any existing code or emitter, and without re-engineering the compiler entry point function (together with passing the options struct downstream until it's time to emit code). The main use case for this is letting the RV32 emitter know which optional extensions it can generate code with, to be able to emit better suited code for the platform in use. Signed-off-by: Alessandro Gatti --- py/mpstate.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/mpstate.h b/py/mpstate.h index 138c5617300..325c1221752 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -60,6 +60,12 @@ enum { // This structure contains dynamic configuration for the compiler. #if MICROPY_DYNAMIC_COMPILER typedef struct mp_dynamic_compiler_t { + // This is used to let mpy-cross pass options to the emitter chosen with + // `native_arch`. The main use case for the time being is to give the + // RV32 emitter extended information about which extensions can be + // optionally used, in order to generate code that's better suited for the + // hardware platform the code will run on. + void *backend_options; uint8_t small_int_bits; // must be <= host small_int_bits uint8_t native_arch; uint8_t nlr_buf_num_regs; From cb7ca6f1bc9c77969d465025f63afdd96892577a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 19 Sep 2025 14:50:05 +0200 Subject: [PATCH 1230/2098] py/asmrv32: Use RV32 Zba opcodes if possible. This commit adds optional support for selected Zba opcodes (address generation) to speed up Viper and native code generation on MCUs where those opcodes are supported (namely RP2350). Right now support for these opcodes is opt-in, as extension detection granularity on the RISC-V platform is still a bit in flux. Relying on the 'B' bit in the MISA register may yield both false positives and false negatives depending on the RISC-V implementation the check runs on. As a side-effect of Zba support, regular non-byte load/stores have been made shorter by two bytes. Whilst this makes code using Zba take up the same space as non-Zba code, the former will still be faster as it will have to process just one instruction instead of two, without stalling registers between the shift and the addition needed to compute the final offset. Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 42 +++++++++++++++++++++++++++++++++ py/asmrv32.h | 65 ++++++++++++++++++++++++--------------------------- py/mpconfig.h | 5 ++++ 3 files changed, 78 insertions(+), 34 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 723d32cdd7c..9313e61ccb7 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -36,6 +36,8 @@ #if MICROPY_EMIT_RV32 #include "py/asmrv32.h" +#include "py/mpstate.h" +#include "py/persistentcode.h" #if MICROPY_DEBUG_VERBOSE #define DEBUG_PRINT (1) @@ -555,6 +557,46 @@ void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) asm_rv32_opcode_xor(state, rd, rd, rs); } +static bool asm_rv32_allow_zba_opcodes(void) { + return asm_rv32_allowed_extensions() & RV32_EXT_ZBA; +} + +static void asm_rv32_fix_up_scaled_reg_reg_reg(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { + assert(operation_size <= 2 && "Operation size value out of range."); + + if (operation_size > 0 && asm_rv32_allow_zba_opcodes()) { + // sh{1,2}add rs1, rs2, rs1 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 1 << operation_size, 0x10, rs1, rs2, rs1)); + } else { + if (operation_size > 0) { + asm_rv32_opcode_cslli(state, rs2, operation_size); + } + asm_rv32_opcode_cadd(state, rs1, rs2); + } +} + +void asm_rv32_emit_load_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { + asm_rv32_fix_up_scaled_reg_reg_reg(state, rs1, rs2, operation_size); + if (operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs1)) { + // c.lw rd', offset(rs') + asm_rv32_opcode_clw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs1), 0); + } else { + // lbu|lhu|lw rd, offset(rs) + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rs1, 0)); + } +} + +void asm_rv32_emit_store_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { + asm_rv32_fix_up_scaled_reg_reg_reg(state, rs1, rs2, operation_size); + if (operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs1)) { + // c.sw rd', offset(rs') + asm_rv32_opcode_csw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs1), 0); + } else { + // sb|sh|sw rd, offset(rs) + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_S(0x23, operation_size, rs1, rd, 0)); + } +} + void asm_rv32_meta_comparison_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd) { // c.li rd, 1 ; // beq rs1, rs2, 6 ; PC + 0 diff --git a/py/asmrv32.h b/py/asmrv32.h index dac9c028b07..f320d3ee0dc 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -122,6 +122,16 @@ typedef struct _asm_rv32_t { mp_uint_t locals_stack_offset; } asm_rv32_t; +enum { + RV32_EXT_NONE = 0, + RV32_EXT_ZBA = 1 << 0, +}; + +typedef struct _asm_rv32_backend_options_t { + // This is a bitmask holding a combination of RV32_EXT_* entries. + uint8_t allowed_extensions; +} asm_rv32_backend_options_t; + void asm_rv32_entry(asm_rv32_t *state, mp_uint_t locals); void asm_rv32_exit(asm_rv32_t *state); void asm_rv32_end_pass(asm_rv32_t *state); @@ -679,6 +689,19 @@ static inline void asm_rv32_opcode_xori(asm_rv32_t *state, mp_uint_t rd, mp_uint asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x13, 0x04, rd, rs, immediate)); } +static inline uint8_t asm_rv32_allowed_extensions(void) { + uint8_t extensions = 0; + #if MICROPY_EMIT_RV32_ZBA + extensions |= RV32_EXT_ZBA; + #endif + #if MICROPY_DYNAMIC_COMPILER + if (mp_dynamic_compiler.backend_options != NULL) { + extensions |= ((asm_rv32_backend_options_t *)mp_dynamic_compiler.backend_options)->allowed_extensions; + } + #endif + return extensions; +} + #define ASM_WORD_SIZE (4) #define ASM_HALFWORD_SIZE (2) @@ -702,6 +725,8 @@ void asm_rv32_meta_comparison_lt(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison); void asm_rv32_emit_optimised_load_immediate(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); +void asm_rv32_emit_load_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size); +void asm_rv32_emit_store_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size); #ifdef GENERIC_ASM_API @@ -760,40 +785,12 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) -#define ASM_LOAD8_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_lbu(state, rd, rs1, 0); \ - } while (0) -#define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_slli(state, rs2, rs2, 1); \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_lhu(state, rd, rs1, 0); \ - } while (0) -#define ASM_LOAD32_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_slli(state, rs2, rs2, 2); \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_lw(state, rd, rs1, 0); \ - } while (0) -#define ASM_STORE8_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_sb(state, rd, rs1, 0); \ - } while (0) -#define ASM_STORE16_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_slli(state, rs2, rs2, 1); \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_sh(state, rd, rs1, 0); \ - } while (0) -#define ASM_STORE32_REG_REG_REG(state, rd, rs1, rs2) \ - do { \ - asm_rv32_opcode_slli(state, rs2, rs2, 2); \ - asm_rv32_opcode_cadd(state, rs1, rs2); \ - asm_rv32_opcode_sw(state, rd, rs1, 0); \ - } while (0) +#define ASM_LOAD8_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 0) +#define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 1) +#define ASM_LOAD32_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 2) +#define ASM_STORE8_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_store_reg_reg_reg(state, rd, rs1, rs2, 0) +#define ASM_STORE16_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_store_reg_reg_reg(state, rd, rs1, rs2, 1) +#define ASM_STORE32_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_store_reg_reg_reg(state, rd, rs1, rs2, 2) #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index 303eb08f90b..ae44b461d37 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -421,6 +421,11 @@ #define MICROPY_EMIT_RV32 (0) #endif +// Whether to emit RISC-V RV32 Zba opcodes in native code +#ifndef MICROPY_EMIT_RV32_ZBA +#define MICROPY_EMIT_RV32_ZBA (0) +#endif + // Whether to enable the RISC-V RV32 inline assembler #ifndef MICROPY_EMIT_INLINE_RV32 #define MICROPY_EMIT_INLINE_RV32 (0) From 965c77ade9512532529379e26b37eaa7ed4a6e02 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Jul 2025 22:30:34 +0200 Subject: [PATCH 1231/2098] mpy-cross/main: Add support for RV32 Zba opcodes. This commit adds a new command line switch to inform the RV32 emitter to use Zba opcodes in its output. A new implementation-specific option was introduced, called "-march-flags", that will contain a list of additional architecture-specific flags to pass to the chosen native emitter implementation. At the moment only the RV32 emitter can make use of this command line facility: if the architecture flags string equals to "zba" (case-sensitive), then the native emitter will emit Zba opcodes if it has a chance to do so. At the moment there is no check on whether additional architecture flags using to build a MPY file are compatible with the target the output code is run on, so use this with caution. Signed-off-by: Alessandro Gatti --- mpy-cross/main.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/mpy-cross/main.c b/mpy-cross/main.c index 63a74d0844b..9dcad3c5d8c 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -40,6 +40,12 @@ #include "ports/windows/fmode.h" #endif +#if MICROPY_EMIT_NATIVE && MICROPY_EMIT_RV32 +#include "py/asmrv32.h" + +static asm_rv32_backend_options_t rv32_options = { 0 }; +#endif + // Command line options, with their defaults static uint emit_opt = MP_EMIT_OPT_NONE; mp_uint_t mp_verbose_flag = 0; @@ -132,6 +138,8 @@ static int usage(char **argv) { "-march= : set architecture for native emitter;\n" " x86, x64, armv6, armv6m, armv7m, armv7em, armv7emsp,\n" " armv7emdp, xtensa, xtensawin, rv32imc, rv64imc, host, debug\n" + "-march-flags= : set architecture-specific flags (OUTPUT FILE MAY NOT WORK ON ALL TARGETS!)\n" + " supported flags for rv32imc: zba\n" "\n" "Implementation specific options:\n", argv[0] ); @@ -237,11 +245,13 @@ MP_NOINLINE int main_(int argc, char **argv) { // don't support native emitter unless -march is specified mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; mp_dynamic_compiler.nlr_buf_num_regs = 0; + mp_dynamic_compiler.backend_options = NULL; const char *input_file = NULL; const char *output_file = NULL; const char *source_file = NULL; bool option_parsing_active = true; + const char *arch_flags = NULL; // parse main options for (int a = 1; a < argc; a++) { @@ -343,6 +353,8 @@ MP_NOINLINE int main_(int argc, char **argv) { } else { return usage(argv); } + } else if (strncmp(argv[a], "-march-flags=", sizeof("-march-flags=") - 1) == 0) { + arch_flags = argv[a] + sizeof("-march-flags=") - 1; } else if (strcmp(argv[a], "--") == 0) { option_parsing_active = false; } else { @@ -357,6 +369,27 @@ MP_NOINLINE int main_(int argc, char **argv) { } } + if (arch_flags && mp_dynamic_compiler.native_arch != MP_NATIVE_ARCH_NONE) { + bool processed = false; + #if MICROPY_EMIT_NATIVE && MICROPY_EMIT_RV32 + if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_RV32IMC) { + mp_dynamic_compiler.backend_options = (void *)&rv32_options; + if (strncmp(arch_flags, "zba", sizeof("zba") - 1) == 0) { + rv32_options.allowed_extensions |= RV32_EXT_ZBA; + processed = true; + } + } + #endif + if (!processed) { + mp_printf(&mp_stderr_print, "unrecognised arch flags\n"); + exit(1); + } + mp_printf(&mp_stderr_print, + "WARNING: Using architecture-specific flags may create a MPY file whose code won't run on all targets!\n" + " Currently there are no checks in the module file loader for whether the chosen flags used to\n" + " build the MPY file are compatible with the running target.\n\n"); + } + #if MICROPY_EMIT_NATIVE if ((MP_STATE_VM(default_emit_opt) == MP_EMIT_OPT_NATIVE_PYTHON || MP_STATE_VM(default_emit_opt) == MP_EMIT_OPT_VIPER) From 40dbf774151140839c74e0bf226bb7d254129eb9 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Jul 2025 22:09:25 +0200 Subject: [PATCH 1232/2098] py/emitinlinerv32: Add Zba opcodes to the inline assembler. This commit adds support for Zba opcodes to the RV32 inline assembler. Three new opcodes were added, SH1ADD, SH2ADD, and SH3ADD, which performs a scaled addition (by 1, 2, or 3 bits respectively). At the moment only qemu's VIRT_RV32 and rp2's RPI_PICO2/RPI_PICO2_W ports support these opcodes (the latter only when using the RISCV variant). Signed-off-by: Alessandro Gatti --- py/asmrv32.h | 18 ++ py/emitinlinerv32.c | 177 +++++++++--------- tests/feature_check/inlineasm_rv32_zba.py | 10 + tests/feature_check/inlineasm_rv32_zba.py.exp | 1 + tests/inlineasm/rv32/asmzba.py | 18 ++ tests/inlineasm/rv32/asmzba.py.exp | 3 + tests/run-tests.py | 6 + 7 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 tests/feature_check/inlineasm_rv32_zba.py create mode 100644 tests/feature_check/inlineasm_rv32_zba.py.exp create mode 100644 tests/inlineasm/rv32/asmzba.py create mode 100644 tests/inlineasm/rv32/asmzba.py.exp diff --git a/py/asmrv32.h b/py/asmrv32.h index f320d3ee0dc..27a08cf9fde 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -593,6 +593,24 @@ static inline void asm_rv32_opcode_remu(asm_rv32_t *state, mp_uint_t rd, mp_uint asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x07, 0x01, rd, rs1, rs2)); } +// SH1ADD RD, RS1, RS2 +static inline void asm_rv32_opcode_sh1add(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0010000 ..... ..... 010 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x02, 0x10, rd, rs1, rs2)); +} + +// SH2ADD RD, RS1, RS2 +static inline void asm_rv32_opcode_sh2add(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0010000 ..... ..... 100 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x04, 0x10, rd, rs1, rs2)); +} + +// SH3ADD RD, RS1, RS2 +static inline void asm_rv32_opcode_sh3add(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { + // R: 0010000 ..... ..... 110 ..... 0110011 + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_R(0x33, 0x06, 0x10, rd, rs1, rs2)); +} + // SLL RD, RS1, RS2 static inline void asm_rv32_opcode_sll(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2) { // R: 0000000 ..... ..... 001 ..... 0110011 diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index a539242b84d..8693b3bd1c0 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -234,7 +234,8 @@ typedef struct _opcode_t { uint32_t argument2_shift : 4; uint32_t argument3_kind : 4; uint32_t argument3_shift : 4; - // 4 bits available here + uint32_t required_extensions : 1; + // 3 bits available here void *emitter; } opcode_t; @@ -297,88 +298,91 @@ static const uint32_t OPCODE_MASKS[] = { }; static const opcode_t OPCODES[] = { - { MP_QSTR_add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_add }, - { MP_QSTR_addi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_addi }, - { MP_QSTR_and_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_and }, - { MP_QSTR_andi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_andi }, - { MP_QSTR_auipc, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, asm_rv32_opcode_auipc }, - { MP_QSTR_beq, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_beq }, - { MP_QSTR_bge, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bge }, - { MP_QSTR_bgeu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bgeu }, - { MP_QSTR_blt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_blt }, - { MP_QSTR_bltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bltu }, - { MP_QSTR_bne, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, asm_rv32_opcode_bne }, - { MP_QSTR_csrrc, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrc }, - { MP_QSTR_csrrs, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrs }, - { MP_QSTR_csrrw, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_csrrw }, - { MP_QSTR_csrrci, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrci }, - { MP_QSTR_csrrsi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrsi }, - { MP_QSTR_csrrwi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, asm_rv32_opcode_csrrwi }, - { MP_QSTR_c_add, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cadd }, - { MP_QSTR_c_addi, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, IZ, 0, N, 0, asm_rv32_opcode_caddi }, - { MP_QSTR_c_addi4spn, MASK_0000FF00, MASK_000003FC, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 0, N, 0, asm_rv32_opcode_caddi4spn }, - { MP_QSTR_c_and, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cand }, - { MP_QSTR_c_andi, MASK_0000FF00, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, RC, 0, I, 0, N, 0, asm_rv32_opcode_candi }, - { MP_QSTR_c_beqz, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, asm_rv32_opcode_cbeqz }, - { MP_QSTR_c_bnez, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, asm_rv32_opcode_cbnez }, - { MP_QSTR_c_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_cebreak }, - { MP_QSTR_c_j, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, asm_rv32_opcode_cj }, - { MP_QSTR_c_jal, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, asm_rv32_opcode_cjal }, - { MP_QSTR_c_jalr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, asm_rv32_opcode_cjalr }, - { MP_QSTR_c_jr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, asm_rv32_opcode_cjr }, - { MP_QSTR_c_li, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_cli }, - { MP_QSTR_c_lui, MASK_FFFFFFFA, MASK_0001F800, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 12, N, 0, asm_rv32_opcode_clui }, - { MP_QSTR_c_lw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, asm_rv32_opcode_clw }, - { MP_QSTR_c_lwsp, MASK_FFFFFFFE, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_clwsp }, - { MP_QSTR_c_mv, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cmv }, - { MP_QSTR_c_nop, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_cnop }, - { MP_QSTR_c_or, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cor }, - { MP_QSTR_c_slli, MASK_FFFFFFFE, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, R, 0, IU, 0, N, 0, asm_rv32_opcode_cslli }, - { MP_QSTR_c_srai, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, asm_rv32_opcode_csrai }, - { MP_QSTR_c_srli, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, asm_rv32_opcode_csrli }, - { MP_QSTR_c_sub, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_csub }, - { MP_QSTR_c_sw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, asm_rv32_opcode_csw }, - { MP_QSTR_c_swsp, MASK_FFFFFFFF, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, asm_rv32_opcode_cswsp }, - { MP_QSTR_c_xor, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, asm_rv32_opcode_cxor }, - { MP_QSTR_div, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_div }, - { MP_QSTR_divu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_divu }, - { MP_QSTR_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_ebreak }, - { MP_QSTR_ecall, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, asm_rv32_opcode_ecall }, - { MP_QSTR_jal, MASK_FFFFFFFF, MASK_001FFFFE, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, asm_rv32_opcode_jal }, - { MP_QSTR_jalr, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_jalr }, - { MP_QSTR_la, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, opcode_la }, - { MP_QSTR_lb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lb }, - { MP_QSTR_lbu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lbu }, - { MP_QSTR_lh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lh }, - { MP_QSTR_lhu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lhu }, - { MP_QSTR_li, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, opcode_li }, - { MP_QSTR_lui, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, asm_rv32_opcode_lui }, - { MP_QSTR_lw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_lw }, - { MP_QSTR_mv, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, asm_rv32_opcode_cmv }, - { MP_QSTR_mul, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mul }, - { MP_QSTR_mulh, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulh }, - { MP_QSTR_mulhsu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulhsu }, - { MP_QSTR_mulhu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_mulhu }, - { MP_QSTR_or_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_or }, - { MP_QSTR_ori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_ori }, - { MP_QSTR_rem, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_rem }, - { MP_QSTR_remu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_remu }, - { MP_QSTR_sb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sb }, - { MP_QSTR_sh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sh }, - { MP_QSTR_sll, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sll }, - { MP_QSTR_slli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_slli }, - { MP_QSTR_slt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_slt }, - { MP_QSTR_slti, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_slti }, - { MP_QSTR_sltiu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_sltiu }, - { MP_QSTR_sltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sltu }, - { MP_QSTR_sra, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sra }, - { MP_QSTR_srai, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_srai }, - { MP_QSTR_srl, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_srl }, - { MP_QSTR_srli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, asm_rv32_opcode_srli }, - { MP_QSTR_sub, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_sub }, - { MP_QSTR_sw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, asm_rv32_opcode_sw }, - { MP_QSTR_xor, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, asm_rv32_opcode_xor }, - { MP_QSTR_xori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, asm_rv32_opcode_xori }, + { MP_QSTR_add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_add }, + { MP_QSTR_addi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_addi }, + { MP_QSTR_and_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_and }, + { MP_QSTR_andi, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_andi }, + { MP_QSTR_auipc, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, RV32_EXT_NONE, asm_rv32_opcode_auipc }, + { MP_QSTR_beq, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_beq }, + { MP_QSTR_bge, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_bge }, + { MP_QSTR_bgeu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_bgeu }, + { MP_QSTR_blt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_blt }, + { MP_QSTR_bltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_bltu }, + { MP_QSTR_bne, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00001FFE, 3, CALL_RRL, R, 0, R, 0, L, 0, RV32_EXT_NONE, asm_rv32_opcode_bne }, + { MP_QSTR_csrrc, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrc }, + { MP_QSTR_csrrs, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrs }, + { MP_QSTR_csrrw, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrw }, + { MP_QSTR_csrrci, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrci }, + { MP_QSTR_csrrsi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrsi }, + { MP_QSTR_csrrwi, MASK_FFFFFFFF, MASK_00000FFF, MASK_0000001F, 3, CALL_RII, R, 0, IU, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_csrrwi }, + { MP_QSTR_c_add, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cadd }, + { MP_QSTR_c_addi, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, IZ, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_caddi }, + { MP_QSTR_c_addi4spn, MASK_0000FF00, MASK_000003FC, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_caddi4spn }, + { MP_QSTR_c_and, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cand }, + { MP_QSTR_c_andi, MASK_0000FF00, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, RC, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_candi }, + { MP_QSTR_c_beqz, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cbeqz }, + { MP_QSTR_c_bnez, MASK_0000FF00, MASK_000001FE, MASK_NOT_USED, 2, CALL_RL, RC, 0, L, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cbnez }, + { MP_QSTR_c_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cebreak }, + { MP_QSTR_c_j, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cj }, + { MP_QSTR_c_jal, MASK_00000FFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_L, L, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cjal }, + { MP_QSTR_c_jalr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cjalr }, + { MP_QSTR_c_jr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cjr }, + { MP_QSTR_c_li, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cli }, + { MP_QSTR_c_lui, MASK_FFFFFFFA, MASK_0001F800, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 12, N, 0, RV32_EXT_NONE, asm_rv32_opcode_clui }, + { MP_QSTR_c_lw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_clw }, + { MP_QSTR_c_lwsp, MASK_FFFFFFFE, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_clwsp }, + { MP_QSTR_c_mv, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cmv }, + { MP_QSTR_c_nop, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cnop }, + { MP_QSTR_c_or, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cor }, + { MP_QSTR_c_slli, MASK_FFFFFFFE, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, R, 0, IU, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cslli }, + { MP_QSTR_c_srai, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csrai }, + { MP_QSTR_c_srli, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csrli }, + { MP_QSTR_c_sub, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csub }, + { MP_QSTR_c_sw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_csw }, + { MP_QSTR_c_swsp, MASK_FFFFFFFF, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cswsp }, + { MP_QSTR_c_xor, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cxor }, + { MP_QSTR_div, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_div }, + { MP_QSTR_divu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_divu }, + { MP_QSTR_ebreak, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_ebreak }, + { MP_QSTR_ecall, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_ecall }, + { MP_QSTR_jal, MASK_FFFFFFFF, MASK_001FFFFE, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_jal }, + { MP_QSTR_jalr, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_jalr }, + { MP_QSTR_la, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, RV32_EXT_NONE, opcode_la }, + { MP_QSTR_lb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lb }, + { MP_QSTR_lbu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lbu }, + { MP_QSTR_lh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lh }, + { MP_QSTR_lhu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lhu }, + { MP_QSTR_li, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, opcode_li }, + { MP_QSTR_lui, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, RV32_EXT_NONE, asm_rv32_opcode_lui }, + { MP_QSTR_lw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lw }, + { MP_QSTR_mv, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cmv }, + { MP_QSTR_mul, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mul }, + { MP_QSTR_mulh, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mulh }, + { MP_QSTR_mulhsu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mulhsu }, + { MP_QSTR_mulhu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mulhu }, + { MP_QSTR_or_, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_or }, + { MP_QSTR_ori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_ori }, + { MP_QSTR_rem, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_rem }, + { MP_QSTR_remu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_remu }, + { MP_QSTR_sb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sb }, + { MP_QSTR_sh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sh }, + { MP_QSTR_sh1add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh1add }, + { MP_QSTR_sh2add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh2add }, + { MP_QSTR_sh3add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh3add }, + { MP_QSTR_sll, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sll }, + { MP_QSTR_slli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_slli }, + { MP_QSTR_slt, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_slt }, + { MP_QSTR_slti, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_slti }, + { MP_QSTR_sltiu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_sltiu }, + { MP_QSTR_sltu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sltu }, + { MP_QSTR_sra, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sra }, + { MP_QSTR_srai, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_srai }, + { MP_QSTR_srl, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_srl }, + { MP_QSTR_srli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_srli }, + { MP_QSTR_sub, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sub }, + { MP_QSTR_sw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sw }, + { MP_QSTR_xor, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_xor }, + { MP_QSTR_xori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_xori }, }; #undef RC @@ -785,10 +789,9 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin } } - if (!opcode_data) { - emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - MP_ERROR_TEXT("unknown RV32 instruction '%q'"), opcode)); + if (!opcode_data || (asm_rv32_allowed_extensions() & opcode_data->required_extensions) != opcode_data->required_extensions) { + emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("invalid RV32 instruction '%q'"), opcode)); return; } diff --git a/tests/feature_check/inlineasm_rv32_zba.py b/tests/feature_check/inlineasm_rv32_zba.py new file mode 100644 index 00000000000..81228819042 --- /dev/null +++ b/tests/feature_check/inlineasm_rv32_zba.py @@ -0,0 +1,10 @@ +# check if RISC-V 32 inline asm supported Zba opcodes + + +@micropython.asm_rv32 +def f(): + sh1add(a0, a0, a0) + + +f() +print("rv32_zba") diff --git a/tests/feature_check/inlineasm_rv32_zba.py.exp b/tests/feature_check/inlineasm_rv32_zba.py.exp new file mode 100644 index 00000000000..fde22f5f400 --- /dev/null +++ b/tests/feature_check/inlineasm_rv32_zba.py.exp @@ -0,0 +1 @@ +rv32_zba diff --git a/tests/inlineasm/rv32/asmzba.py b/tests/inlineasm/rv32/asmzba.py new file mode 100644 index 00000000000..75f3573c864 --- /dev/null +++ b/tests/inlineasm/rv32/asmzba.py @@ -0,0 +1,18 @@ +@micropython.asm_rv32 +def test_sh1add(a0, a1): + sh1add(a0, a0, a1) + + +@micropython.asm_rv32 +def test_sh2add(a0, a1): + sh2add(a0, a0, a1) + + +@micropython.asm_rv32 +def test_sh3add(a0, a1): + sh3add(a0, a0, a1) + + +print(hex(test_sh1add(10, 20))) +print(hex(test_sh2add(10, 20))) +print(hex(test_sh3add(10, 20))) diff --git a/tests/inlineasm/rv32/asmzba.py.exp b/tests/inlineasm/rv32/asmzba.py.exp new file mode 100644 index 00000000000..5f56bd95642 --- /dev/null +++ b/tests/inlineasm/rv32/asmzba.py.exp @@ -0,0 +1,3 @@ +0x28 +0x3c +0x64 diff --git a/tests/run-tests.py b/tests/run-tests.py index c3017542cc9..d6eb9a3fbc2 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -816,6 +816,12 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") skip_tests.add("inlineasm/thumb/asmfpsqrt.py") + if args.inlineasm_arch == "rv32": + # Check if @micropython.asm_rv32 supports Zba instructions, and skip such tests if it doesn't + output = run_feature_check(pyb, args, "inlineasm_rv32_zba.py") + if output != b"rv32_zba\n": + skip_tests.add("inlineasm/rv32/asmzba.py") + # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") if "True" not in str(t, "ascii"): From 1fea1e20db7f677db5fb10b1e0becd7713755bb6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 11 Sep 2025 21:59:31 +0200 Subject: [PATCH 1233/2098] qemu: Enable Zba opcodes for the VIRT_RV32 board. This commit enables the usage of the Zba address generation opcodes in the QEMU port when targeting the "VIRT_RV32" board, which emulates a generic RV32 target. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 2 +- ports/qemu/boards/VIRT_RV32/mpconfigboard.mk | 2 +- ports/qemu/mpconfigport.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 685bb3536c4..84711cc45ae 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -193,7 +193,7 @@ CFLAGS += $(SPECS_FRAGMENT) LDFLAGS += $(SPECS_FRAGMENT) endif -RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../ports/qemu/$<" $(RUN_TESTS_ARGS) +RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../ports/qemu/$<" --mpy-cross-flags='$(MPY_CROSS_FLAGS)' $(RUN_TESTS_ARGS) ################################################################################ # Source files and libraries diff --git a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk index ce12720928e..6a3b94a98bc 100644 --- a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk +++ b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk @@ -8,4 +8,4 @@ LDSCRIPT = mcu/rv32/virt.ld SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o -MPY_CROSS_FLAGS += -march=rv32imc +MPY_CROSS_FLAGS += -march=rv32imc -march-flags=zba diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 1649d8a01ad..3d2466a9195 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -41,6 +41,7 @@ #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #elif defined(__riscv) && (__riscv_xlen == 32) #define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_RV32_ZBA (1) #define MICROPY_EMIT_INLINE_RV32 (1) #endif From 8757eb715ef9da9e1da7704d552210d5bc1b76e2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 7 Jul 2025 22:29:24 +0200 Subject: [PATCH 1234/2098] rp2/mpconfigport: Enable Zba opcodes in RISC-V mode. This commit enables generation of Zba opcodes by the native emitter for the Pico2, as its RISC-V implementation supports both of those extensions (see section 3.8 of the RP2350 datasheet). Signed-off-by: Alessandro Gatti --- ports/rp2/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 0c226538cda..e6e8aa076fa 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -109,6 +109,7 @@ #endif #elif PICO_RISCV #define MICROPY_EMIT_RV32 (1) +#define MICROPY_EMIT_RV32_ZBA (1) #define MICROPY_EMIT_INLINE_RV32 (1) #endif From 353814df59171e435708813243f47cfbcf7c1646 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 19 Sep 2025 10:04:52 -0500 Subject: [PATCH 1235/2098] tools/ci.sh: Fix missing `set -e` when run as a script. This is needed to correctly stop after an error in a CI step when running `ci.sh` as a script. Signed-off-by: Jeff Epler --- tools/ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ci.sh b/tools/ci.sh index 26def3cc4de..3e34bd6fa86 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -1011,6 +1011,7 @@ function _ci_main { _ci_help ;; (*) + set -e cd $(dirname "$0")/.. while [ $# -ne 0 ]; do ci_$1 From a277fe4a36f09558e57c89b7e365e34af14b1cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:06:53 +0000 Subject: [PATCH 1236/2098] github/workflows: Bump actions/checkout from 4 to 5. Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ports_qemu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index e743f29c996..cdecfae6c9b 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -54,7 +54,7 @@ jobs: build_and_test_rv64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: source tools/ci.sh && ci_qemu_setup_rv64 - name: Build and run test suite From cf097932a2a7ffd03958a8a14d9c87bd477ac5c3 Mon Sep 17 00:00:00 2001 From: Mike Wang Date: Sun, 21 Sep 2025 01:44:59 +0800 Subject: [PATCH 1237/2098] unix/modsocket: Set file descriptor to -1 on close. After s.close(), s.fileno() now returns -1, matching CPython behavior. Some code relies on this compatibility, as it allows checking whether a socket is closed by testing its fileno() value. This change ensures better interoperability with existing Python code and libraries. Signed-off-by: Mike Wang --- ports/unix/modsocket.c | 9 ++++++--- tests/extmod/socket_fileno.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/extmod/socket_fileno.py diff --git a/ports/unix/modsocket.c b/ports/unix/modsocket.c index 14f321d34fb..e7f25cdd43d 100644 --- a/ports/unix/modsocket.c +++ b/ports/unix/modsocket.c @@ -140,9 +140,12 @@ static mp_uint_t socket_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, i // The rationale MicroPython follows is that close() just releases // file descriptor. If you're interested to catch I/O errors before // closing fd, fsync() it. - MP_THREAD_GIL_EXIT(); - close(self->fd); - MP_THREAD_GIL_ENTER(); + if (self->fd >= 0) { + MP_THREAD_GIL_EXIT(); + close(self->fd); + MP_THREAD_GIL_ENTER(); + } + self->fd = -1; return 0; case MP_STREAM_GET_FILENO: diff --git a/tests/extmod/socket_fileno.py b/tests/extmod/socket_fileno.py new file mode 100644 index 00000000000..da15825e3d5 --- /dev/null +++ b/tests/extmod/socket_fileno.py @@ -0,0 +1,17 @@ +# Test socket.fileno() functionality + +try: + import socket +except ImportError: + print("SKIP") + raise SystemExit + +if not hasattr(socket.socket, "fileno"): + print("SKIP") + raise SystemExit + +s = socket.socket() +print(s.fileno() >= 0) + +s.close() +print(s.fileno()) # should print -1 From 8c389db6ea552a7a5bfcb2de644bc3e8849ef521 Mon Sep 17 00:00:00 2001 From: Mike Wang Date: Sun, 21 Sep 2025 00:09:17 +0800 Subject: [PATCH 1238/2098] unix/main: Ensure atexit function is called with -m . Previously, when running `micropython -m ` and the module called sys.exit(), the registered atexit function was not executed. This was due to sys.exit() raising a SystemExit exception, which bypassed the atexit handler. This change fixes the issue so that the atexit function is properly invoked when exiting via sys.exit(). Additionally, following the pattern in execute_from_lexer(), mp_hal_set_interrupt_char() and mp_handle_pending() handling were added to ensure that the atexit function is also executed when the user exits via Ctrl-C. Signed-off-by: Mike Wang --- ports/unix/main.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 0acd8c9f27a..db50e12f598 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -669,12 +669,18 @@ MP_NOINLINE int main_(int argc, char **argv) { subpkg_tried = false; reimport: + mp_hal_set_interrupt_char(CHAR_CTRL_C); if (nlr_push(&nlr) == 0) { mod = mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args); + mp_hal_set_interrupt_char(-1); + mp_handle_pending(true); nlr_pop(); } else { // uncaught exception - return handle_uncaught_exception(nlr.ret_val) & 0xff; + mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + ret = handle_uncaught_exception(nlr.ret_val) & 0xff; + break; } // If this module is a package, see if it has a `__main__.py`. From bf08f601fa67f005d5f21095b8fd455f7c7e7fab Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 22 Sep 2025 11:41:36 +1000 Subject: [PATCH 1239/2098] tests/cmdline: Add tests for using -m combined with sys.atexit. Signed-off-by: Damien George --- tests/cmdline/cmd_module_atexit.py | 16 ++++++++++++++++ tests/cmdline/cmd_module_atexit.py.exp | 3 +++ tests/cmdline/cmd_module_atexit_exc.py | 19 +++++++++++++++++++ tests/cmdline/cmd_module_atexit_exc.py.exp | 3 +++ 4 files changed, 41 insertions(+) create mode 100644 tests/cmdline/cmd_module_atexit.py create mode 100644 tests/cmdline/cmd_module_atexit.py.exp create mode 100644 tests/cmdline/cmd_module_atexit_exc.py create mode 100644 tests/cmdline/cmd_module_atexit_exc.py.exp diff --git a/tests/cmdline/cmd_module_atexit.py b/tests/cmdline/cmd_module_atexit.py new file mode 100644 index 00000000000..100bc112777 --- /dev/null +++ b/tests/cmdline/cmd_module_atexit.py @@ -0,0 +1,16 @@ +# cmdline: -m cmdline.cmd_module_atexit +# +# Test running as a module and using sys.atexit. + +import sys + +if not hasattr(sys, "atexit"): + print("SKIP") + raise SystemExit + +# Verify we ran as a module. +print(sys.argv) + +sys.atexit(lambda: print("done")) + +print("start") diff --git a/tests/cmdline/cmd_module_atexit.py.exp b/tests/cmdline/cmd_module_atexit.py.exp new file mode 100644 index 00000000000..2a0f756b1e7 --- /dev/null +++ b/tests/cmdline/cmd_module_atexit.py.exp @@ -0,0 +1,3 @@ +['cmdline.cmd_module_atexit', 'cmdline/cmd_module_atexit.py'] +start +done diff --git a/tests/cmdline/cmd_module_atexit_exc.py b/tests/cmdline/cmd_module_atexit_exc.py new file mode 100644 index 00000000000..88940a7741f --- /dev/null +++ b/tests/cmdline/cmd_module_atexit_exc.py @@ -0,0 +1,19 @@ +# cmdline: -m cmdline.cmd_module_atexit_exc +# +# Test running as a module and using sys.atexit, with script completion via sys.exit. + +import sys + +if not hasattr(sys, "atexit"): + print("SKIP") + raise SystemExit + +# Verify we ran as a module. +print(sys.argv) + +sys.atexit(lambda: print("done")) + +print("start") + +# This will raise SystemExit to finish the script, and atexit should still be run. +sys.exit(0) diff --git a/tests/cmdline/cmd_module_atexit_exc.py.exp b/tests/cmdline/cmd_module_atexit_exc.py.exp new file mode 100644 index 00000000000..6320d9d2d30 --- /dev/null +++ b/tests/cmdline/cmd_module_atexit_exc.py.exp @@ -0,0 +1,3 @@ +['cmdline.cmd_module_atexit_exc', 'cmdline/cmd_module_atexit_exc.py'] +start +done From 592affce3f7118bbfa814fe92f127e9a37194d79 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 15:01:19 +0900 Subject: [PATCH 1240/2098] stm32/make-stmconst.py: Fix missing peripheral consts in stm module. Lines like the following were not handled by `make-stmconst.py`: #define APBPERIPH_BASE (PERIPH_BASE) This leads to missing definitions on stm module. For example, `stm.RTC` is not defined if `RTC_BASE` is defined as #define RTC_BASE (APBPERIPH_BASE + 0x00002800UL) because `APBPERIPH_BASE` is not handled as a valid id. This patch modifies the RegExp so it can handle the above. Signed-off-by: Yuuki NAGAO --- ports/stm32/make-stmconst.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index 770033ef59d..5601eb0af42 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -41,7 +41,10 @@ class Lexer: r"#define +(?P[A-Z0-9_]+) +\(?(\(uint32_t\))?(?P0x[0-9A-F]+)U?L?\)?($| */\*)" ), ), - ("#define X", re.compile(r"#define +(?P[A-Z0-9_]+) +(?P[A-Z0-9_]+)($| +/\*)")), + ( + "#define X", + re.compile(r"#define +(?P[A-Z0-9_]+) +\(?(?P[A-Z0-9_]+)\)?($| +/\*)"), + ), ( "#define X+hex", re.compile( From 65b2dc255b2415e7afd6960587a1857297f6913a Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 10:04:48 +0900 Subject: [PATCH 1241/2098] stm32/powerctrlboot: Refactor clock settings for STM32G0. Clock parameter definitions should be in `mpconfigboard.h` and use them at clock initialization. Signed-off-by: Yuuki NAGAO --- ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h | 14 +++++++++----- ports/stm32/powerctrlboot.c | 15 +++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h index ead36ed6c76..e62243ca330 100644 --- a/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h @@ -18,13 +18,17 @@ #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 #if MICROPY_HW_CLK_USE_HSI -#define MICROPY_HW_CLK_PLLM (16) +#define MICROPY_HW_CLK_PLLM (1) +#define MICROPY_HW_CLK_PLLN (8) #else -#define MICROPY_HW_CLK_PLLM (8) +// HSE comes from ST-LINK 8MHz, not crystal. +#define MICROPY_HW_CLK_USE_BYPASS (1) +#define MICROPY_HW_CLK_PLLM (1) +#define MICROPY_HW_CLK_PLLN (16) #endif -#define MICROPY_HW_CLK_PLLN (336) -#define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV4) -#define MICROPY_HW_CLK_PLLQ (7) +#define MICROPY_HW_CLK_PLLP (2) +#define MICROPY_HW_CLK_PLLQ (2) +#define MICROPY_HW_CLK_PLLR (2) // USART1 config #define MICROPY_HW_UART1_TX (pin_A9) diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 2354deb4da1..a8ef8c34af8 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -145,17 +145,12 @@ void SystemClock_Config(void) { } // Use the PLL to get a 64MHz SYSCLK - #define PLLM (HSI_VALUE / 16000000) // input is 8MHz - #define PLLN (8) // 8*16MHz = 128MHz - #define PLLP (2) // f_P = 64MHz - #define PLLQ (2) // f_Q = 64MHz - #define PLLR (2) // f_R = 64MHz RCC->PLLCFGR = - (PLLP - 1) << RCC_PLLCFGR_PLLP_Pos | RCC_PLLCFGR_PLLPEN - | (PLLQ - 1) << RCC_PLLCFGR_PLLQ_Pos | RCC_PLLCFGR_PLLQEN - | (PLLR - 1) << RCC_PLLCFGR_PLLR_Pos | RCC_PLLCFGR_PLLREN - | PLLN << RCC_PLLCFGR_PLLN_Pos - | (PLLM - 1) << RCC_PLLCFGR_PLLM_Pos + (MICROPY_HW_CLK_PLLP - 1) << RCC_PLLCFGR_PLLP_Pos | RCC_PLLCFGR_PLLPEN + | (MICROPY_HW_CLK_PLLQ - 1) << RCC_PLLCFGR_PLLQ_Pos | RCC_PLLCFGR_PLLQEN + | (MICROPY_HW_CLK_PLLR - 1) << RCC_PLLCFGR_PLLR_Pos | RCC_PLLCFGR_PLLREN + | MICROPY_HW_CLK_PLLN << RCC_PLLCFGR_PLLN_Pos + | (MICROPY_HW_CLK_PLLM - 1) << RCC_PLLCFGR_PLLM_Pos | RCC_PLLCFGR_PLLSRC_HSI; #else From 7ba82219f3d40689479704edfcc97fcf5b914b2c Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 10:07:02 +0900 Subject: [PATCH 1242/2098] stm32/adc: Fix pyb.ADC issue for STM32G0. Fixes are: - The internal sensors of STM32G0 are connected channel 12, 13, 14. - Update adc_refcor before reading vbat and tempsensor because reference values are at VDDA=3.0V. - The method of initialize of sampling time is different with other MCUs: STM32G0 should initialize SamplingTimeCommon1/2 first. When reading value from ADC, ADC_ChannelConfTypeDef.SamplingTime should be specified SamplingTimeCommon1 or SamplingTimeCommon2. In this patch, SamplingTimeCommon2 is used for internal sensors and SamplingTimeCommon1 is used for external ADC channels. Signed-off-by: Yuuki NAGAO --- ports/stm32/adc.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index f47e9eaad7b..07368f4733d 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -212,6 +212,14 @@ static inline uint32_t adc_get_internal_channel(uint32_t channel) { if (channel == 16) { channel = ADC_CHANNEL_TEMPSENSOR; } + #elif defined(STM32G0) + if (channel == 12) { + channel = ADC_CHANNEL_TEMPSENSOR; + } else if (channel == 13) { + channel = ADC_CHANNEL_VREFINT; + } else if (channel == 14) { + channel = ADC_CHANNEL_VBAT; + } #elif defined(STM32G4) if (channel == 16) { channel = ADC_CHANNEL_TEMPSENSOR_ADC1; @@ -344,6 +352,10 @@ static void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { adch->Init.DataAlign = ADC_DATAALIGN_RIGHT; adch->Init.DMAContinuousRequests = DISABLE; #elif defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) + #if defined(STM32G0) + adch->Init.SamplingTimeCommon1 = ADC_SAMPLETIME_12CYCLES_5; + adch->Init.SamplingTimeCommon2 = ADC_SAMPLETIME_160CYCLES_5; + #endif #if defined(STM32G4) || defined(STM32H5) adch->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV16; #else @@ -432,9 +444,9 @@ static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) } #elif defined(STM32G0) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { - sConfig.SamplingTime = ADC_SAMPLETIME_160CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_2; } else { - sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1; } #elif defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { @@ -939,9 +951,9 @@ float adc_read_core_temp_float(ADC_HandleTypeDef *adcHandle) { return 0.0f; // TODO #else - #if defined(STM32G4) || defined(STM32L1) || defined(STM32L4) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32L1) || defined(STM32L4) // Update the reference correction factor before reading tempsensor - // because TS_CAL1 and TS_CAL2 of STM32G4,L1/L4 are at VDDA=3.0V + // because TS_CAL1 and TS_CAL2 of STM32G0,G4,L1,L4 are at VDDA=3.0V adc_read_core_vref(adcHandle); #endif @@ -966,9 +978,9 @@ float adc_read_core_temp_float(ADC_HandleTypeDef *adcHandle) { } float adc_read_core_vbat(ADC_HandleTypeDef *adcHandle) { - #if defined(STM32G4) || defined(STM32L4) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32L4) // Update the reference correction factor before reading tempsensor - // because VREFINT of STM32G4,L4 is at VDDA=3.0V + // because VREFINT of STM32G0,G4,L4 is at VDDA=3.0V adc_read_core_vref(adcHandle); #endif From ddf1a383ca110fa6787ea71ff92e3e110378bf2e Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 10:07:53 +0900 Subject: [PATCH 1243/2098] stm32/machine_adc: Fix machine.ADC so it works on STM32G0. Fixes are: - Enable VREG before initializing ADC. - The channel value of internal sensor should convert with __LL_ADC_CHANNEL_TO_DECIMAL_NB(). Signed-off-by: Yuuki NAGAO --- ports/stm32/machine_adc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index 63cd4e089dd..20f392516cc 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -244,12 +244,12 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { } #endif - #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) + #if defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) || defined(STM32WL) if (!(adc->CR & ADC_CR_ADVREGEN)) { adc->CR = ADC_CR_ADVREGEN; // enable VREG #if defined(STM32H7) mp_hal_delay_us(10); // T_ADCVREG_STUP - #elif defined(STM32G4) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32G0) || defined(STM32G4) || defined(STM32L4) || defined(STM32WB) mp_hal_delay_us(20); // T_ADCVREG_STUP #endif } @@ -379,6 +379,12 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp #else adc->SMPR = sample_time << ADC_SMPR_SMP_Pos; // select sample time #endif + + #if defined(STM32G0) + if (__LL_ADC_IS_CHANNEL_INTERNAL(channel)) { + channel = __LL_ADC_CHANNEL_TO_DECIMAL_NB(channel); + } + #endif adc->CHSELR = 1 << channel; // select channel for conversion #elif defined(STM32F4) || defined(STM32F7) From 86323519835114a9761cd65f073a4bdaafcfec1c Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 11:51:09 +0900 Subject: [PATCH 1244/2098] stm32/timer: Fix Timer(4) issue for STM32G0. The definition of TIM_ENTRY of TIM4 is incorrect. Signed-off-by: Yuuki NAGAO --- ports/stm32/timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 8aa0b3a2dda..cb196210ba0 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -865,7 +865,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #if defined(TIM4) #if defined(STM32G0B1xx) || defined(STM32G0C1xx) - TIM_ENTRY(3, TIM3_TIM4_IRQn), + TIM_ENTRY(4, TIM3_TIM4_IRQn), #else TIM_ENTRY(4, TIM4_IRQn), #endif From 9a13688c13b6e0c2fe50f7a90f165b05e5b21991 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 12:37:40 +0900 Subject: [PATCH 1245/2098] stm32/timer: Enable RTCAPB_CLK for STM32G0. To get worked pyb.RTC and machine.RTC on STM32G0. Signed-off-by: Yuuki NAGAO --- ports/stm32/rtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index b90d17149bd..fa28fda6a89 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -108,7 +108,7 @@ void rtc_init_start(bool force_init) { // Enable the RTC APB bus clock, to communicate with the RTC. #if defined(STM32H5) __HAL_RCC_RTC_CLK_ENABLE(); - #elif defined(STM32WL) + #elif defined(STM32G0) || defined(STM32WL) __HAL_RCC_RTCAPB_CLK_ENABLE(); #endif From 531261497403b0932e524ea3ffb0ce56e071ba97 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 21 Sep 2025 16:07:17 +0900 Subject: [PATCH 1246/2098] stm32/rtc: Fix RTC.wakeup issue for STM32G0. To clear wakeup interrupt flag, set CWUTF on RTC_SCR instead of RTC_MISR. Signed-off-by: Yuuki NAGAO --- ports/stm32/stm32_it.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 9eda3cb2397..052550a8fa8 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -541,7 +541,7 @@ void RTC_IRQHandler(void) { #if defined(STM32G0) void RTC_TAMP_IRQHandler(void) { IRQ_ENTER(RTC_TAMP_IRQn); - RTC->MISR &= ~RTC_MISR_WUTMF; // clear wakeup interrupt flag + RTC->SCR |= RTC_SCR_CWUTF; // clear wakeup interrupt flag Handle_EXTI_Irq(EXTI_RTC_WAKEUP); // clear EXTI flag and execute optional callback Handle_EXTI_Irq(EXTI_RTC_TIMESTAMP); // clear EXTI flag and execute optional callback IRQ_EXIT(RTC_TAMP_IRQn); From 616f35f7f8783c133f67e2eedc6adbf2d26a7e47 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Sep 2025 16:30:57 +1000 Subject: [PATCH 1247/2098] tests/README: Update docs for run-perfbench.py now that it uses -t. Follow up to dbbc7d96672bb44a04f27fef32231dd7340d7952 Signed-off-by: Damien George --- tests/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/README.md b/tests/README.md index 21e14eee5e1..6a6395c39cb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -67,16 +67,14 @@ for a full list of command line options. ### Benchmarking a target -To run tests on a firmware target using `pyboard.py`, run the command line like +To run tests on a firmware target using a serial port, run the command line like this: ``` -./run-perfbench.py -p -d /dev/ttyACM0 168 100 +./run-perfbench.py -t /dev/ttyACM0 168 100 ``` -* `-p` indicates running on a remote target via pyboard.py, not the host. -* `-d PORTNAME` is the serial port, `/dev/ttyACM0` is the default if not - provided. +* `-t PORTNAME` is the serial port to use (and it supports shorthand like `a0`). * `168` is value `N`, the approximate CPU frequency in MHz (in this case Pyboard V1.1 is 168MHz). It's possible to choose other values as well: lower values like `10` will run much the tests much quicker, higher values like `1000` will @@ -136,11 +134,11 @@ Usually you want to know if something is faster or slower than a reference. To do this, copy the output of each `run-perfbench.py` run to a text file. This can be done multiple ways, but one way on Linux/macOS is with the `tee` -utility: `./run-perfbench.py -p 168 100 | tee pyb-run1.txt` +utility: `./run-perfbench.py -t a0 168 100 | tee pyb-run1.txt` Once you have two files with output from two different runs (maybe with different code or configuration), compare the runtimes with `./run-perfbench.py --t pybv-run1.txt pybv-run2.txt` or compare scores with `./run-perfbench.py -s +-m pybv-run1.txt pybv-run2.txt` or compare scores with `./run-perfbench.py -s pybv-run1.txt pybv-run2.txt`: ``` From 0d9aafd9f508c89dc13bbf06583f24cbee3fedfa Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Aug 2025 13:46:36 +1000 Subject: [PATCH 1248/2098] tools/metrics.py: Compute mpy-cross size as part of size metrics. Add support to `tools/metrics.py` to compute the size delta of mpy-cross, alongside the sizes of port firmware. This is an easy and cheap addition because mpy-cross is usually built before the ports are. Although the size of mpy-cross is not critical, it's still a nice indication of how changes affect code size, and helps to eliminate any unwanted increases in mpy-cross. Signed-off-by: Damien George --- tools/metrics.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/metrics.py b/tools/metrics.py index f6189e65abb..150b40bff10 100755 --- a/tools/metrics.py +++ b/tools/metrics.py @@ -57,6 +57,8 @@ def __init__(self, name, dir, output, make_flags=None): self.needs_mpy_cross = dir not in ("bare-arm", "minimal") +mpy_cross_output = "mpy-cross/build/mpy-cross" + port_data = { "b": PortData("bare-arm", "bare-arm", "build/firmware.elf"), "m": PortData("minimal x86", "minimal", "build/firmware.elf"), @@ -142,6 +144,8 @@ def do_diff(args): max_delta = None for key, value1 in data1.items(): value2 = data2[key] + if key == mpy_cross_output: + name = "mpy-cross" for port in port_data.values(): if key == "ports/{}/{}".format(port.dir, port.output): name = port.name @@ -207,6 +211,10 @@ def do_sizes(args): ports = parse_port_list(args) print("COMPUTING SIZES") + + if any(port.needs_mpy_cross for port in ports): + syscmd("size", mpy_cross_output) + for port in ports: syscmd("size", "ports/{}/{}".format(port.dir, port.output)) From 91abffb4d66abd22ba4cb29beb3cde4ba7a7eda6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 9 Sep 2025 12:34:30 +1000 Subject: [PATCH 1249/2098] github/workflows: Retry Windows VC2017 & 2019 install steps. The Windows 8.1 sdksetup.exe in particular seems seems to fail intermittently pretty often, so retry each step up to four times before failing outright. Delete the Chocolatey temp directory between each run, as it seems like the root cause is a corrupt download. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_windows.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 84a93a50433..16396f0fdd4 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -29,8 +29,10 @@ jobs: include: - visualstudio: '2017' vs_version: '[15, 16)' + custom_vs_install: true - visualstudio: '2019' vs_version: '[16, 17)' + custom_vs_install: true - visualstudio: '2022' vs_version: '[17, 18)' # trim down the number of jobs in the matrix @@ -43,18 +45,17 @@ jobs: env: CI_BUILD_CONFIGURATION: ${{ matrix.configuration }} steps: - - name: Install Visual Studio 2017 - if: matrix.visualstudio == '2017' + - name: Install Visual Studio ${{ matrix.visualstudio }} + if: matrix.custom_vs_install + shell: bash + # Shell functions in this block are to retry intermittent corrupt + # downloads (with a clean download dir) before failing the job outright run: | - choco install visualstudio2017buildtools - choco install visualstudio2017-workload-vctools - choco install windows-sdk-8.1 - - name: Install Visual Studio 2019 - if: matrix.visualstudio == '2019' - run: | - choco install visualstudio2019buildtools - choco install visualstudio2019-workload-vctools - choco install windows-sdk-8.1 + try () { ($@) || ($@) || ($@) || ($@) } + clean_install () ( rm -rf $TEMP/chocolatey; choco install $1 ) + try clean_install visualstudio${{ matrix.visualstudio }}buildtools + try clean_install visualstudio${{ matrix.visualstudio }}-workload-vctools + try clean_install windows-sdk-8.1 - uses: microsoft/setup-msbuild@v2 with: vs-version: ${{ matrix.vs_version }} From 046013a1ffbeccb971b6067ff389ebd0350b9e9c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 24 Sep 2025 14:58:08 +1000 Subject: [PATCH 1250/2098] github/workflows: Run esp32&zephyr daily to keep mstr branch caches hot. Currently it seems if master branch doesn't build for 1-2 days then the cached ESP-IDF install (1.6GB) and Zephyr workspace (3.1GB) caches expire. Then each PR branch has to create its own redundant cache instead of falling back to the default branch cache, which is expensive and quickly blows our 10GB cache limit. Currently this is mitigated (and possibly happens more frequently) due to GitHub's relatively soft enforcement of the limit (at time of writing we're using 33GB of 10GB), but apparently they're going to start enforcing it more aggressively in October. (We may find we need to do this twice a day...) This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_esp32.yml | 4 ++++ .github/workflows/ports_zephyr.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index b86c6a76f82..c87552ade31 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -12,6 +12,10 @@ on: - 'lib/**' - 'drivers/**' - 'ports/esp32/**' + schedule: + # Scheduled run exists to keep master branch ESP-IDF cache entry hot + # and prevent creating many redundant per-branch cache entries instead. + - cron: "20 0 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 9ce70343986..09ffd616b40 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -12,6 +12,10 @@ on: - 'lib/**' - 'ports/zephyr/**' - 'tests/**' + schedule: + # Scheduled run exists to keep master branch Zephyr cache entry hot + # and prevent creating many redundant per-branch cache entries instead. + - cron: "40 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} From d0d9d978eb57e2c9ce996d8a7cbab3e8dd1c37d5 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 22 Sep 2025 18:31:28 +0200 Subject: [PATCH 1251/2098] esp32/machine_uart: Call uart_wait_tx_done() only with driver installed. Otherwise an error message will pop up at the first instatiation of the UART object. Addresses #18122 / #18123. Signed-off-by: robert-hh --- ports/esp32/machine_uart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 36b5bcb0ff9..ea403f42218 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -253,9 +253,6 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - // wait for all data to be transmitted before changing settings - uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - // If UART is being freshly initialised then restore object defaults if (!uart_is_driver_installed(self->uart_num)) { self->bits = 8; @@ -299,6 +296,9 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, case UART_NUM_MAX: assert(0); // Range is checked in mp_machine_uart_make_new, value should be unreachable } + } else { + // wait for all data to be transmitted before changing settings + uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); } // Default driver parameters, should correspond to values set above From 6c9940b1f6fb75749ba2ac7949faaf76ace802da Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Aug 2025 20:12:40 +1000 Subject: [PATCH 1252/2098] stm32/adc: Get ADC working on STM32N6 MCUs. Changes made here for N6 are: - set RIF security attributes for ADC12 - clock ADC12 at 50MHz (maximum) so it runs at spec (max 5Msamp/sec) - increase sampling time for standard channels to 46.5 cycles - calibrate ADC in `adc.c` - correctly clear ADC_CFGR1_RES bits in `machine_adc.c` - set preselection register in `machine_adc.c` Signed-off-by: Damien George --- ports/stm32/adc.c | 9 +++++++-- ports/stm32/machine_adc.c | 25 ++++++++++++++++--------- ports/stm32/main.c | 1 + 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 07368f4733d..c912d9df3a7 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -314,6 +314,11 @@ static void adcx_clock_enable(ADC_HandleTypeDef *adch) { static void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { adcx_clock_enable(adch); + // Set ADC clock prescaler, if it's not done by HAL_ADC_Init() below. + #if defined(STM32N6) + LL_RCC_SetADCPrescaler(4 - 1); // 200MHz / 4 = 50MHz + #endif + adch->Init.Resolution = resolution; adch->Init.ContinuousConvMode = DISABLE; adch->Init.DiscontinuousConvMode = DISABLE; @@ -387,7 +392,7 @@ static void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { #endif #if defined(STM32G0) HAL_ADCEx_Calibration_Start(adch); - #elif defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32WB) + #elif defined(STM32G4) || defined(STM32H5) || defined(STM32L4) || defined(STM32N6) || defined(STM32WB) HAL_ADCEx_Calibration_Start(adch, ADC_SINGLE_ENDED); #endif } @@ -461,7 +466,7 @@ static void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { sConfig.SamplingTime = ADC_SAMPLETIME_246CYCLES_5; } else { - sConfig.SamplingTime = ADC_SAMPLETIME_11CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLETIME_46CYCLES_5; } sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index 20f392516cc..14384429c44 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -86,7 +86,7 @@ #define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 #define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_247CYCLES_5 #elif defined(STM32N6) -#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_11CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_46CYCLES_5 #define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_246CYCLES_5 #endif @@ -232,6 +232,8 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { ADC1_COMMON->CCR = 0; // ADCPR=PCLK/2 #elif defined(STM32L1) ADC1_COMMON->CCR = 1 << ADC_CCR_ADCPRE_Pos; // ADCPRE=2 + #elif defined(STM32N6) + LL_RCC_SetADCPrescaler(4 - 1); // 200MHz / 4 = 50MHz #elif defined(STM32WB) ADC1_COMMON->CCR = 0 << ADC_CCR_PRESC_Pos | 0 << ADC_CCR_CKMODE_Pos; // PRESC=1, MODE=ASYNC #elif defined(STM32WL) @@ -324,7 +326,7 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { #elif defined(STM32N6) - uint32_t cfgr1_clr = ADC_CFGR1_CONT | ADC_CFGR1_EXTEN; + uint32_t cfgr1_clr = ADC_CFGR1_CONT | ADC_CFGR1_EXTEN | ADC_CFGR1_RES; uint32_t cfgr1 = res << ADC_CFGR1_RES_Pos; adc->CFGR1 = (adc->CFGR1 & ~cfgr1_clr) | cfgr1; @@ -439,13 +441,6 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp #if defined(STM32G4) || defined(STM32H5) || defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || defined(STM32H7B3xx) || defined(STM32H7B3xxQ) || defined(STM32N6) ADC_Common_TypeDef *adc_common = ADC12_COMMON; #elif defined(STM32H7) - #if defined(ADC_VER_V5_V90) - if (adc != ADC3) { - adc->PCSEL_RES0 |= 1 << channel; - } - #else - adc->PCSEL |= 1 << channel; - #endif ADC_Common_TypeDef *adc_common = adc == ADC3 ? ADC3_COMMON : ADC12_COMMON; #elif defined(STM32L4) ADC_Common_TypeDef *adc_common = ADCx_COMMON; @@ -475,6 +470,7 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp adc->OR |= ADC_OR_OP0; // Enable Vddcore channel on ADC2 #endif } + #if defined(STM32G4) || defined(STM32H5) || defined(STM32N6) || defined(STM32WB) // MCU uses encoded literals for internal channels -> extract ADC channel for following code if (__LL_ADC_IS_CHANNEL_INTERNAL(channel)) { @@ -482,6 +478,17 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp } adc->DIFSEL &= ~(1 << channel); // Set channel to Single-ended. #endif + + #if defined(STM32H7) || defined(STM32N6) + #if defined(ADC_VER_V5_V90) + if (adc != ADC3) { + adc->PCSEL_RES0 |= 1 << channel; + } + #else + adc->PCSEL |= 1 << channel; + #endif + #endif + adc->SQR1 = (channel & 0x1f) << ADC_SQR1_SQ1_Pos | (1 - 1) << ADC_SQR1_L_Pos; __IO uint32_t *smpr; if (channel <= 9) { diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 8bce6b477a5..51c29fe7537 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -316,6 +316,7 @@ static void risaf_init(void) { rimc_master.MasterCID = RIF_CID_1; rimc_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV; + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_ADC12, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC1, &rimc_master); HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC1, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC2, &rimc_master); From 706cc8d833ebd2685b324cab48f1a7d8e0c8bca2 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 13:56:49 +0100 Subject: [PATCH 1253/2098] shared/runtime/mpirq: Factor out mp_irq_dispatch() and use it. Separate out a routine to call an arbitrary function with arbitrary argument either directly as a hard-IRQ handler or scheduled as a soft-IRQ handler, adjusting mp_irq_handler() to wrap this. This can then be used to implement other hard/soft callbacks, such as for machine.Timer. Signed-off-by: Chris Webb --- shared/runtime/mpirq.c | 25 +++++++++++++++++-------- shared/runtime/mpirq.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/shared/runtime/mpirq.c b/shared/runtime/mpirq.c index 6111b9b10cf..0b3489c5141 100644 --- a/shared/runtime/mpirq.c +++ b/shared/runtime/mpirq.c @@ -65,9 +65,10 @@ void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t p self->ishard = false; } -void mp_irq_handler(mp_irq_obj_t *self) { - if (self->handler != mp_const_none) { - if (self->ishard) { +int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard) { + int result = 0; + if (handler != mp_const_none) { + if (ishard) { // When executing code within a handler we must lock the scheduler to // prevent any scheduled callbacks from running, and lock the GC to // prevent any memory allocations. @@ -75,22 +76,30 @@ void mp_irq_handler(mp_irq_obj_t *self) { gc_lock(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - mp_call_function_1(self->handler, self->parent); + mp_call_function_1(handler, parent); nlr_pop(); } else { - // Uncaught exception; disable the callback so that it doesn't run again - self->methods->trigger(self->parent, 0); - self->handler = mp_const_none; mp_printf(MICROPY_ERROR_PRINTER, "Uncaught exception in IRQ callback handler\n"); mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + result = -1; } gc_unlock(); mp_sched_unlock(); } else { // Schedule call to user function - mp_sched_schedule(self->handler, self->parent); + mp_sched_schedule(handler, parent); } } + return result; +} + + +void mp_irq_handler(mp_irq_obj_t *self) { + if (mp_irq_dispatch(self->handler, self->parent, self->ishard) < 0) { + // Uncaught exception; disable the callback so that it doesn't run again + self->methods->trigger(self->parent, 0); + self->handler = mp_const_none; + } } /******************************************************************************/ diff --git a/shared/runtime/mpirq.h b/shared/runtime/mpirq.h index dd423c01011..c65741e0e49 100644 --- a/shared/runtime/mpirq.h +++ b/shared/runtime/mpirq.h @@ -77,6 +77,7 @@ extern const mp_obj_type_t mp_irq_type; mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent); void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent); +int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard); void mp_irq_handler(mp_irq_obj_t *self); #endif // MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H From ad11df7f7ac73b1d9d0c69274a5e56140c621401 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 13:59:53 +0100 Subject: [PATCH 1254/2098] shared/runtime/softtimer: Add support for hard callbacks. Add a flag SOFT_TIMER_HARD_CALLBACK to request that a soft timer's python callback is run directly from the IRQ handler with the scheduler and heap locked, instead of being scheduled via mp_sched_schedule(). Signed-off-by: Chris Webb --- shared/runtime/softtimer.c | 7 ++++++- shared/runtime/softtimer.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/runtime/softtimer.c b/shared/runtime/softtimer.c index 92a0d6d5111..b154cd9d7b5 100644 --- a/shared/runtime/softtimer.c +++ b/shared/runtime/softtimer.c @@ -28,6 +28,7 @@ #include "py/gc.h" #include "py/mphal.h" #include "py/runtime.h" +#include "shared/runtime/mpirq.h" #include "softtimer.h" #ifdef MICROPY_SOFT_TIMER_TICKS_MS @@ -88,7 +89,11 @@ void soft_timer_handler(void) { soft_timer_entry_t *entry = heap; heap = (soft_timer_entry_t *)mp_pairheap_pop(soft_timer_lt, &heap->pairheap); if (entry->flags & SOFT_TIMER_FLAG_PY_CALLBACK) { - mp_sched_schedule(entry->py_callback, MP_OBJ_FROM_PTR(entry)); + if (mp_irq_dispatch(entry->py_callback, MP_OBJ_FROM_PTR(entry), + entry->flags & SOFT_TIMER_FLAG_HARD_CALLBACK)) { + // Uncaught exception; disable the callback so it doesn't run again. + entry->mode = SOFT_TIMER_MODE_ONE_SHOT; + } } else if (entry->c_callback) { entry->c_callback(entry); } diff --git a/shared/runtime/softtimer.h b/shared/runtime/softtimer.h index 6921c9f47d6..464d2067ede 100644 --- a/shared/runtime/softtimer.h +++ b/shared/runtime/softtimer.h @@ -30,6 +30,7 @@ #define SOFT_TIMER_FLAG_PY_CALLBACK (1) #define SOFT_TIMER_FLAG_GC_ALLOCATED (2) +#define SOFT_TIMER_FLAG_HARD_CALLBACK (4) #define SOFT_TIMER_MODE_ONE_SHOT (1) #define SOFT_TIMER_MODE_PERIODIC (2) From 955b6a907cdaf15135470d2c71bcede8620eaa6f Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:02:32 +0100 Subject: [PATCH 1255/2098] extmod/machine_timer: Support hard IRQ soft timer callbacks. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. As on the rp2 port, add support to the generic software timer for a hard= argument to explicitly choose between these, setting the default to False to match the existing behaviour. This enables hard timer callbacks for the alif, mimxrt and samd ports. Signed-off-by: Chris Webb --- extmod/machine_timer.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/extmod/machine_timer.c b/extmod/machine_timer.c index 665be82ce12..7029f7ddef0 100644 --- a/extmod/machine_timer.c +++ b/extmod/machine_timer.c @@ -42,13 +42,14 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr } static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, }; + enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SOFT_TIMER_MODE_PERIODIC} }, { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; // Parse args @@ -81,6 +82,12 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_ar self->py_callback = args[ARG_callback].u_obj; } + if (args[ARG_hard].u_bool) { + self->flags |= SOFT_TIMER_FLAG_HARD_CALLBACK; + } else { + self->flags &= ~SOFT_TIMER_FLAG_HARD_CALLBACK; + } + if (self->py_callback != mp_const_none) { soft_timer_insert(self, self->delta_ms); } From b7bf24e4812d2d27c61c8cb0dbcbbfc1a28a271c Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:04:45 +0100 Subject: [PATCH 1256/2098] rp2/machine_timer: Use mp_irq_dispatch() to reduce duplication. Now that mp_irq_dispatch() is available to dispatch arbitary hard/soft callbacks, take advantage of this for rp2 machine.Timer. This should slightly reduce binary size. Signed-off-by: Chris Webb --- ports/rp2/machine_timer.c | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/ports/rp2/machine_timer.c b/ports/rp2/machine_timer.c index ffb4c702430..276fa5e788b 100644 --- a/ports/rp2/machine_timer.c +++ b/ports/rp2/machine_timer.c @@ -29,6 +29,7 @@ #include "py/mphal.h" #include "py/gc.h" #include "pico/time.h" +#include "shared/runtime/mpirq.h" #define ALARM_ID_INVALID (-1) #define TIMER_MODE_ONE_SHOT (0) @@ -49,29 +50,10 @@ const mp_obj_type_t machine_timer_type; static int64_t alarm_callback(alarm_id_t id, void *user_data) { machine_timer_obj_t *self = user_data; - if (self->ishard) { - // When executing code within a handler we must lock the scheduler to - // prevent any scheduled callbacks from running, and lock the GC to - // prevent any memory allocations. - mp_sched_lock(); - gc_lock(); - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_call_function_1(self->callback, MP_OBJ_FROM_PTR(self)); - nlr_pop(); - } else { - // Uncaught exception; disable the callback so it doesn't run again. - self->mode = TIMER_MODE_ONE_SHOT; - mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in timer callback\n"); - mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); - } - gc_unlock(); - mp_sched_unlock(); - } else { - mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); - } - - if (self->mode == TIMER_MODE_ONE_SHOT) { + if (mp_irq_dispatch(self->callback, MP_OBJ_FROM_PTR(self), self->ishard) < 0) { + // Uncaught exception; don't run the callback again. + return 0; + } else if (self->mode == TIMER_MODE_ONE_SHOT) { return 0; } else { return -self->delta_us; From a69b08b756eae5b59006b93524a8d9c4d78847bc Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:18:03 +0100 Subject: [PATCH 1257/2098] renesas-ra/timer: Support soft IRQ timer callbacks. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. As on the rp2 port, add support to the renesas-ra port for a hard= argument to explicitly choose between these, setting the default to True to match the existing behaviour. Signed-off-by: Chris Webb --- ports/renesas-ra/timer.c | 44 +++++++++++++--------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/ports/renesas-ra/timer.c b/ports/renesas-ra/timer.c index d264ab06129..286bc4d0fb5 100644 --- a/ports/renesas-ra/timer.c +++ b/ports/renesas-ra/timer.c @@ -30,6 +30,7 @@ #include "py/runtime.h" #include "py/gc.h" +#include "shared/runtime/mpirq.h" #include "timer.h" #include "pin.h" #include "irq.h" @@ -53,6 +54,7 @@ typedef struct _pyb_timer_obj_t { mp_obj_base_t base; uint8_t tim_id; mp_obj_t callback; + bool ishard; #if defined(TIMER_CHANNEL) pyb_timer_channel_obj_t *channel; #endif @@ -91,7 +93,7 @@ static void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ mp_printf(print, "Timer(%u)", self->tim_id); } -/// \method init(*, freq, prescaler, period) +/// \method init(*, freq, hard) /// Initialise the timer. Initialisation must be either by frequency (in Hz) /// or by prescaler and period: /// @@ -102,16 +104,17 @@ static void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ /// - `freq` - specifies the periodic frequency of the timer. You might also /// view this as the frequency with which the timer goes through /// one complete cycle. -////// /// - `callback` - as per Timer.callback() -////// -/// You must either specify freq. +/// - `hard` - whether the callback should be called in hard-IRQ context +/// +/// You must specify freq; hard defaults to True if not set. static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { // enum { ARG_freq, ARG_prescaler, ARG_period, ARG_tick_hz, ARG_mode, ARG_div, ARG_callback, ARG_deadtime }; - enum { ARG_freq, ARG_callback }; + enum { ARG_freq, ARG_callback, ARG_hard }; static const mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -128,11 +131,12 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons freq_args[1] = args[ARG_freq].u_obj; pyb_timer_freq(2, (const mp_obj_t *)&freq_args); } else { - mp_raise_TypeError(MP_ERROR_TEXT("must specify either freq, period, or prescaler and period")); + mp_raise_TypeError(MP_ERROR_TEXT("must specify freq")); } // Enable ARPE so that the auto-reload register is buffered. // This allows to smoothly change the frequency of the timer. // Start the timer running + self->ishard = args[ARG_hard].u_bool; if (args[ARG_callback].u_obj == mp_const_none) { // do nothing } else { @@ -160,6 +164,7 @@ static mp_obj_t pyb_timer_make_new(const mp_obj_type_t *type, size_t n_args, siz tim->base.type = &pyb_timer_type; tim->tim_id = tim_id; tim->callback = mp_const_none; + tim->ishard = true; MP_STATE_PORT(pyb_timer_obj_all)[tim_id - 1] = tim; } else { // reference existing Timer object @@ -513,30 +518,9 @@ static MP_DEFINE_CONST_OBJ_TYPE( #endif static void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_obj_t callback) { - - // execute callback if it's set - if (callback != mp_const_none) { - mp_sched_lock(); - // When executing code within a handler we must lock the GC to prevent - // any memory allocations. We must also catch any exceptions. - gc_lock(); - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_call_function_1(callback, MP_OBJ_FROM_PTR(tim)); - nlr_pop(); - } else { - // Uncaught exception; disable the callback so it doesn't run again. - tim->callback = mp_const_none; - // __HAL_TIM_DISABLE_IT(&tim->tim, irq_mask); - if (channel == 0) { - mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) interrupt handler\n", tim->tim_id); - } else { - mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) channel %u interrupt handler\n", tim->tim_id, channel); - } - mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); - } - gc_unlock(); - mp_sched_unlock(); + if (mp_irq_dispatch(callback, MP_OBJ_FROM_PTR(tim), tim->ishard) < 0) { + // Uncaught exception; disable the callback so it doesn't run again. + tim->callback = mp_const_none; } } From 49f2a1f0ed1ef140b2ae5a2bb1870b605d99c2f6 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:19:52 +0100 Subject: [PATCH 1258/2098] stm32/timer: Support soft IRQ timer callbacks. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. As on the rp2 port, add support to the stm32 port for a hard= argument to explicitly choose between these, setting the default to True to match the existing behaviour. Signed-off-by: Chris Webb --- ports/stm32/timer.c | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index cb196210ba0..9c4a5f8894e 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -29,6 +29,7 @@ #include "py/runtime.h" #include "py/gc.h" +#include "shared/runtime/mpirq.h" #include "timer.h" #include "servo.h" #include "pin.h" @@ -131,6 +132,7 @@ typedef struct _pyb_timer_obj_t { uint8_t tim_id; uint8_t is_32bit; mp_obj_t callback; + bool ishard; TIM_HandleTypeDef tim; IRQn_Type irqn; pyb_timer_channel_obj_t *channel; @@ -632,10 +634,14 @@ static void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ /// BRK_IN input is triggered. It can be set to `BRK_OFF`, `BRK_LOW` /// and `BRK_HIGH`. /// +/// - `hard` - specifies if the timer and channel callbacks should be run +/// in hard-IRQ context (the default on stm32) or scheduled as a soft +/// handler. +/// /// /// You must either specify freq or both of period and prescaler. static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_prescaler, ARG_period, ARG_tick_hz, ARG_mode, ARG_div, ARG_callback, ARG_deadtime, ARG_brk }; + enum { ARG_freq, ARG_prescaler, ARG_period, ARG_tick_hz, ARG_mode, ARG_div, ARG_callback, ARG_deadtime, ARG_brk, ARG_hard }; static const mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_prescaler, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, @@ -646,6 +652,7 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_deadtime, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_brk, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = BRK_OFF} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, }; // parse args @@ -828,6 +835,7 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons self->tim.Instance->CR1 |= TIM_CR1_ARPE; // Start the timer running + self->ishard = args[ARG_hard].u_bool; if (args[ARG_callback].u_obj == mp_const_none) { HAL_TIM_Base_Start(&self->tim); } else { @@ -1708,29 +1716,10 @@ static void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_o // clear the interrupt __HAL_TIM_CLEAR_IT(&tim->tim, irq_mask); - // execute callback if it's set - if (callback != mp_const_none) { - mp_sched_lock(); - // When executing code within a handler we must lock the GC to prevent - // any memory allocations. We must also catch any exceptions. - gc_lock(); - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_call_function_1(callback, MP_OBJ_FROM_PTR(tim)); - nlr_pop(); - } else { - // Uncaught exception; disable the callback so it doesn't run again. - tim->callback = mp_const_none; - __HAL_TIM_DISABLE_IT(&tim->tim, irq_mask); - if (channel == 0) { - mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) interrupt handler\n", tim->tim_id); - } else { - mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) channel %u interrupt handler\n", tim->tim_id, channel); - } - mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); - } - gc_unlock(); - mp_sched_unlock(); + if (mp_irq_dispatch(callback, MP_OBJ_FROM_PTR(tim), tim->ishard) < 0) { + // Uncaught exception; disable the callback so it doesn't run again. + tim->callback = mp_const_none; + __HAL_TIM_DISABLE_IT(&tim->tim, irq_mask); } } } From c617ed159a632c59838000e21d2dde9ce1feaaec Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:20:34 +0100 Subject: [PATCH 1259/2098] zephyr/machine_timer: Support hard IRQ timer callbacks. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. As on the rp2 port, add support to the zephyr port for a hard= argument to explicitly choose between these, setting the default to False to match the existing behaviour. Adjust the stack-limit check to use the ISR stack while the callback is dispatched so that hard IRQ callbacks work, as with machine_pin.c and machine_i2c_target.c IRQ callbacks. Signed-off-by: Chris Webb --- ports/zephyr/machine_timer.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/machine_timer.c b/ports/zephyr/machine_timer.c index 1bb743c2eb9..410e86762bd 100644 --- a/ports/zephyr/machine_timer.c +++ b/ports/zephyr/machine_timer.c @@ -27,9 +27,11 @@ #include #include +#include "py/gc.h" #include "py/mperrno.h" #include "py/obj.h" #include "py/runtime.h" +#include "shared/runtime/mpirq.h" #include "modmachine.h" #include #include @@ -51,6 +53,7 @@ typedef struct _machine_timer_obj_t { uint32_t period_ms; mp_obj_t callback; + bool ishard; struct _machine_timer_obj_t *next; } machine_timer_obj_t; @@ -62,12 +65,31 @@ static mp_obj_t machine_timer_deinit(mp_obj_t self_in); static void machine_timer_callback(struct k_timer *timer) { machine_timer_obj_t *self = (machine_timer_obj_t *)k_timer_user_data_get(timer); + + #if MICROPY_STACK_CHECK + // This callback executes in an ISR context so the stack-limit check must + // be changed to use the ISR stack for the duration of this function (so + // that hard IRQ callbacks work). + char *orig_stack_top = MP_STATE_THREAD(stack_top); + size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); + MP_STATE_THREAD(stack_top) = (void *)&self; + MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; + #endif + + if (mp_irq_dispatch(self->callback, MP_OBJ_FROM_PTR(self), self->ishard) < 0) { + // Uncaught exception; disable the callback so it doesn't run again. + self->mode = TIMER_MODE_ONE_SHOT; + } + if (self->mode == TIMER_MODE_ONE_SHOT) { machine_timer_deinit(self); } - if (self->callback != mp_const_none) { - mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); - } + + #if MICROPY_STACK_CHECK + // Restore original stack-limit checking values. + MP_STATE_THREAD(stack_top) = orig_stack_top; + MP_STATE_THREAD(stack_limit) = orig_stack_limit; + #endif } static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -109,6 +131,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n ARG_callback, ARG_period, ARG_freq, + ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIMER_MODE_PERIODIC} }, @@ -119,6 +142,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n #else { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, #endif + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -139,6 +163,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n self->mode = args[ARG_mode].u_int; self->callback = args[ARG_callback].u_obj; + self->ishard = args[ARG_hard].u_bool; k_timer_init(&self->my_timer, machine_timer_callback, NULL); k_timer_user_data_set(&self->my_timer, self); From e7aa2a523c518fc17b6e73a06c2ef15e1b4cb9c8 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:21:48 +0100 Subject: [PATCH 1260/2098] esp8266/modmachine: Support hard IRQ timer callbacks. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. As on the rp2 port, add support to the esp8266 port for a hard= argument to explicitly choose between these, setting the default to False to match the existing behaviour. Open-code this as we don't link against mpirq.c so can't use mp_irq_dispatch(). Signed-off-by: Chris Webb --- ports/esp8266/modmachine.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index d43fe382457..ffc20784514 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -34,6 +34,7 @@ #include "osapi.h" #include "etshal.h" #include "user_interface.h" +#include "py/gc.h" // #define MACHINE_WAKE_IDLE (0x01) // #define MACHINE_WAKE_SLEEP (0x02) @@ -169,6 +170,7 @@ typedef struct _esp_timer_obj_t { uint32_t remain_ms; // if non-zero, remaining time to handle large periods uint32_t period_ms; // if non-zero, periodic timer with a large period mp_obj_t callback; + bool ishard; } esp_timer_obj_t; static void esp_timer_arm_ms(esp_timer_obj_t *self, uint32_t ms, bool repeat) { @@ -225,7 +227,27 @@ static void esp_timer_cb(void *arg) { self->remain_ms -= next_period_ms; os_timer_arm(&self->timer, next_period_ms, false); } else { - mp_sched_schedule(self->callback, self); + if (self->ishard) { + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_1(self->callback, MP_OBJ_FROM_PTR(self)); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so it doesn't run again. + self->period_ms = 0; + mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in timer callback\n"); + mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + gc_unlock(); + mp_sched_unlock(); + } else { + mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + } if (self->period_ms != 0) { // A periodic timer with a larger period: reschedule it esp_timer_arm_ms(self, self->period_ms, true); @@ -240,6 +262,7 @@ static mp_obj_t esp_timer_init_helper(esp_timer_obj_t *self, size_t n_args, cons ARG_period, ARG_tick_hz, ARG_freq, + ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, @@ -251,6 +274,7 @@ static mp_obj_t esp_timer_init_helper(esp_timer_obj_t *self, size_t n_args, cons #else { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, #endif + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; // parse args @@ -258,6 +282,7 @@ static mp_obj_t esp_timer_init_helper(esp_timer_obj_t *self, size_t n_args, cons mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); self->callback = args[ARG_callback].u_obj; + self->ishard = args[ARG_hard].u_bool; // Be sure to disarm timer before making any changes os_timer_disarm(&self->timer); os_timer_setfn(&self->timer, esp_timer_cb, self); From 64fd2f5f360bfae4a231fc3db053139a4621e878 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:32:33 +0100 Subject: [PATCH 1261/2098] esp32/machine_timer: Warn that hard timers are not implemented. machine.Timer() has inconsistent behaviour between ports: some run callbacks in hard IRQ context whereas others schedule them like soft IRQs. Most ports now support a hard= argument to the machine.Timer constructor or initialiser to explicitly choose between these behaviours. However, esp32 does not support hardware interrupts because they are not delivered to the main thread, so the interrupt handler would need to acquire the GIL. Raise a ValueError if hard=True is requested for esp32 machine.Timer. Signed-off-by: Chris Webb --- ports/esp32/machine_timer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index b0292a0379f..ea9ce5469b0 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -227,6 +227,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n ARG_period, ARG_tick_hz, ARG_freq, + ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, @@ -238,6 +239,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n #else { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, #endif + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; machine_timer_disable(self); @@ -245,6 +247,10 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if (args[ARG_hard].u_bool) { + mp_raise_ValueError(MP_ERROR_TEXT("hard Timers are not implemented")); + } + #if MICROPY_PY_BUILTINS_FLOAT if (args[ARG_freq].u_obj != mp_const_none) { self->period = (uint64_t)(machine_timer_freq_hz() / mp_obj_get_float(args[ARG_freq].u_obj)); From ccc954256f890b6626558349b513a6d840da24d9 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 14:49:17 +0100 Subject: [PATCH 1262/2098] tests: Generalise rp2 timer test into a cross-port test. Now all ports with machine.Timer except nrf support both hard and soft callbacks, generalise tests/ports/rp2_machine_timer.py into tests/extmod/machine_timer.py. There is an existing machine_soft_timer.py which varies period= and covers the nrf port but skips esp32/esp8266 because they don't support software timers. In our new test, we try varying freq= instead of period=, and cover esp32/esp8266 (with a fixed choice of hardware timer) but skip nrf because it doesn't support hard= or freq=. Add a check that the heap is locked (so allocation fails) in hard callbacks and it is unlocked (so allocation succeeds) in soft callbacks, to ensure we're getting the right kind of callback, not falling back to the default. Signed-off-by: Chris Webb --- tests/extmod/machine_timer.py | 48 ++++++++++++++++++++++++ tests/extmod/machine_timer.py.exp | 16 ++++++++ tests/ports/rp2/rp2_machine_timer.py | 20 ---------- tests/ports/rp2/rp2_machine_timer.py.exp | 16 -------- 4 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 tests/extmod/machine_timer.py create mode 100644 tests/extmod/machine_timer.py.exp delete mode 100644 tests/ports/rp2/rp2_machine_timer.py delete mode 100644 tests/ports/rp2/rp2_machine_timer.py.exp diff --git a/tests/extmod/machine_timer.py b/tests/extmod/machine_timer.py new file mode 100644 index 00000000000..ef97ea4e949 --- /dev/null +++ b/tests/extmod/machine_timer.py @@ -0,0 +1,48 @@ +import sys + +try: + from machine import Timer + from time import sleep_ms +except: + print("SKIP") + raise SystemExit + +if sys.platform in ("esp32", "esp8266", "nrf"): + # Software timers aren't implemented on the esp32 and esp8266 ports. + # The nrf port doesn't support selection of hard and soft callbacks, + # and only allows Timer(period=N), not Timer(freq=N). + print("SKIP") + raise SystemExit +else: + timer_id = -1 + +# Test both hard and soft IRQ handlers and both one-shot and periodic +# timers. We adjust period in tests/extmod/machine_soft_timer.py, so try +# adjusting freq here instead. The heap should be locked in hard callbacks +# and unlocked in soft callbacks. + + +def callback(t): + print("callback", mode[1], kind[1], freq, end=" ") + try: + allocate = bytearray(1) + print("unlocked") + except MemoryError: + print("locked") + + +modes = [(Timer.ONE_SHOT, "one-shot"), (Timer.PERIODIC, "periodic")] +kinds = [(False, "soft"), (True, "hard")] + +for mode in modes: + for kind in kinds: + for freq in 50, 25: + timer = Timer( + timer_id, + mode=mode[0], + freq=freq, + hard=kind[0], + callback=callback, + ) + sleep_ms(90) + timer.deinit() diff --git a/tests/extmod/machine_timer.py.exp b/tests/extmod/machine_timer.py.exp new file mode 100644 index 00000000000..26cdc644fdd --- /dev/null +++ b/tests/extmod/machine_timer.py.exp @@ -0,0 +1,16 @@ +callback one-shot soft 50 unlocked +callback one-shot soft 25 unlocked +callback one-shot hard 50 locked +callback one-shot hard 25 locked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 25 unlocked +callback periodic soft 25 unlocked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 25 locked +callback periodic hard 25 locked diff --git a/tests/ports/rp2/rp2_machine_timer.py b/tests/ports/rp2/rp2_machine_timer.py deleted file mode 100644 index ac4efcf7f38..00000000000 --- a/tests/ports/rp2/rp2_machine_timer.py +++ /dev/null @@ -1,20 +0,0 @@ -from machine import Timer -from time import sleep_ms - -# Test the rp2-specific adjustable tick_hz and hard/soft IRQ handlers -# for both one-shot and periodic timers. - -modes = {Timer.ONE_SHOT: "one-shot", Timer.PERIODIC: "periodic"} -kinds = {False: "soft", True: "hard"} - -for mode in modes: - for hard in kinds: - for period in 2, 4: - timer = Timer( - mode=mode, - period=period, - hard=hard, - callback=lambda t: print("callback", modes[mode], kinds[hard], period), - ) - sleep_ms(9) - timer.deinit() diff --git a/tests/ports/rp2/rp2_machine_timer.py.exp b/tests/ports/rp2/rp2_machine_timer.py.exp deleted file mode 100644 index b3dd93dfabf..00000000000 --- a/tests/ports/rp2/rp2_machine_timer.py.exp +++ /dev/null @@ -1,16 +0,0 @@ -callback one-shot soft 2 -callback one-shot soft 4 -callback one-shot hard 2 -callback one-shot hard 4 -callback periodic soft 2 -callback periodic soft 2 -callback periodic soft 2 -callback periodic soft 2 -callback periodic soft 4 -callback periodic soft 4 -callback periodic hard 2 -callback periodic hard 2 -callback periodic hard 2 -callback periodic hard 2 -callback periodic hard 4 -callback periodic hard 4 From ec6cdf3718ce49026940c5e2f65fb07f3fc23309 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 15:25:21 +0100 Subject: [PATCH 1263/2098] tests: Test hardware timers as well as software timers. On platforms where hardware timers are available, test these in each combination of hard/soft and one-shot/periodic in the same way as for software timers. Where a platform supports both software (id = -1) and hardware (id >= 0) timers, the behaviour of both is now checked. For now, esp8266 is the only platform that supports hardware timers and both hard and soft callbacks. Signed-off-by: Chris Webb --- tests/extmod/machine_hard_timer.py | 45 ++++++++++++++++++++++++++ tests/extmod/machine_hard_timer.py.exp | 16 +++++++++ 2 files changed, 61 insertions(+) create mode 100644 tests/extmod/machine_hard_timer.py create mode 100644 tests/extmod/machine_hard_timer.py.exp diff --git a/tests/extmod/machine_hard_timer.py b/tests/extmod/machine_hard_timer.py new file mode 100644 index 00000000000..8fe42ea8508 --- /dev/null +++ b/tests/extmod/machine_hard_timer.py @@ -0,0 +1,45 @@ +import sys + +try: + from machine import Timer + from time import sleep_ms +except: + print("SKIP") + raise SystemExit + +if sys.platform == "esp8266": + timer = Timer(0) +else: + # Hardware timers are not implemented. + print("SKIP") + raise SystemExit + +# Test both hard and soft IRQ handlers and both one-shot and periodic +# timers. We adjust period in tests/extmod/machine_soft_timer.py, so try +# adjusting freq here instead. The heap should be locked in hard callbacks +# and unlocked in soft callbacks. + + +def callback(t): + print("callback", mode[1], kind[1], freq, end=" ") + try: + allocate = bytearray(1) + print("unlocked") + except MemoryError: + print("locked") + + +modes = [(Timer.ONE_SHOT, "one-shot"), (Timer.PERIODIC, "periodic")] +kinds = [(False, "soft"), (True, "hard")] + +for mode in modes: + for kind in kinds: + for freq in 50, 25: + timer.init( + mode=mode[0], + freq=freq, + hard=kind[0], + callback=callback, + ) + sleep_ms(90) + timer.deinit() diff --git a/tests/extmod/machine_hard_timer.py.exp b/tests/extmod/machine_hard_timer.py.exp new file mode 100644 index 00000000000..26cdc644fdd --- /dev/null +++ b/tests/extmod/machine_hard_timer.py.exp @@ -0,0 +1,16 @@ +callback one-shot soft 50 unlocked +callback one-shot soft 25 unlocked +callback one-shot hard 50 locked +callback one-shot hard 25 locked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 50 unlocked +callback periodic soft 25 unlocked +callback periodic soft 25 unlocked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 50 locked +callback periodic hard 25 locked +callback periodic hard 25 locked From b9523fd58c02dba74239015bf6b2a1983342a06b Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Mon, 25 Aug 2025 15:29:40 +0100 Subject: [PATCH 1264/2098] docs: Document the cross-port Timer hard= option. Update the main machine.Timer specification, and any references to hard/soft interrupts in port-specific documentation. There is a separate copy of the machine.Timer documentation for the pyboard, so update that too to keep everything consistent. Signed-off-by: Chris Webb --- docs/esp32/quickref.rst | 4 ++++ docs/esp8266/quickref.rst | 3 +++ docs/library/machine.Timer.rst | 20 +++++++++++++++++--- docs/library/pyb.Timer.rst | 14 +++++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 2c667a0f014..25e52ec32db 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -287,6 +287,10 @@ with a timer ID of 0, 0 and 1, or from 0 to 3 (inclusive):: The period is in milliseconds. When using UART.IRQ_RXIDLE, timer 0 is needed for the IRQ_RXIDLE mechanism and must not be used otherwise. +Timer callbacks are scheduled as soft interrupts on this port; hard +callbacks are not implemented. Specifying ``hard=True`` will raise +a ValueError. + Virtual timers are not currently supported on this port. .. _Pins_and_GPIO: diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index e17b60f6761..95e1fcf9768 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -108,6 +108,9 @@ with timer ID of -1:: The period is in milliseconds. +By default, timer callbacks are scheduled as soft interrupts on this port. +Specify ``hard=True`` to run them in hard interrupt context instead. + Pins and GPIO ------------- diff --git a/docs/library/machine.Timer.rst b/docs/library/machine.Timer.rst index 44e65940805..5d228ea7b67 100644 --- a/docs/library/machine.Timer.rst +++ b/docs/library/machine.Timer.rst @@ -18,8 +18,9 @@ Timer callbacks. Memory can't be allocated inside irq handlers (an interrupt) and so exceptions raised within a handler don't give much information. See - :func:`micropython.alloc_emergency_exception_buf` for how to get around this - limitation. + :func:`micropython.alloc_emergency_exception_buf` for how to get around + this limitation, which applies to all callbacks of Timers created with + ``hard=True``. If you are using a WiPy board please refer to :ref:`machine.TimerWiPy ` instead of this class. @@ -38,7 +39,7 @@ Constructors Methods ------- -.. method:: Timer.init(*, mode=Timer.PERIODIC, freq=-1, period=-1, callback=None) +.. method:: Timer.init(*, mode=Timer.PERIODIC, freq=-1, period=-1, callback=None, hard=True) Initialise the timer. Example:: @@ -76,6 +77,19 @@ Methods will occur upon timer expiration: ``TypeError: 'NoneType' object isn't callable`` + - ``hard`` can be one of: + + - ``True`` - The callback will be executed in hard interrupt + context, which minimises delay and jitter but is subject to the + limitations described in :ref:`isr_rules` including being unable + to allocate on the heap. + - ``False`` - The callback will be scheduled as a soft interrupt, + allowing it to allocate but possibly also introducing + garbage-collection delays and jitter. + + The default value of this option is port-specific for historical + reasons. + .. method:: Timer.deinit() Deinitialises the timer. Stops the timer, and disables the timer peripheral. diff --git a/docs/library/pyb.Timer.rst b/docs/library/pyb.Timer.rst index c644e38d786..0e9e49c0eab 100644 --- a/docs/library/pyb.Timer.rst +++ b/docs/library/pyb.Timer.rst @@ -62,7 +62,7 @@ Constructors Methods ------- -.. method:: Timer.init(*, freq, prescaler, period, mode=Timer.UP, div=1, callback=None, deadtime=0, brk=Timer.BRK_OFF) +.. method:: Timer.init(*, freq, prescaler, period, mode=Timer.UP, div=1, callback=None, deadtime=0, brk=Timer.BRK_OFF, hard=True) Initialise the timer. Initialisation must be either by frequency (in Hz) or by prescaler and period:: @@ -115,6 +115,18 @@ Methods ``mode=Pin.ALT, alt=Pin.AFn_TIMx``. The pin's GPIO input features are available in alt mode - ``pull=`` , ``value()`` and ``irq()``. + - ``hard`` can be one of: + + - ``True`` - The callback will be executed in hard interrupt + context, which minimises delay and jitter but is subject to the + limitations described in :ref:`isr_rules` including being unable + to allocate on the heap. + - ``False`` - The callback will be scheduled as a soft interrupt, + allowing it to allocate but possibly also introducing + garbage-collection delays and jitter. + + The default value of this option is True. + You must either specify freq or both of period and prescaler. .. method:: Timer.deinit() From 81985d20c98e1550c355263f1d6c7bd6150ee0b4 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Tue, 9 Sep 2025 15:40:51 +0100 Subject: [PATCH 1265/2098] shared/runtime/mpirq: Check separate hard IRQ stack correctly. On the zephyr port, hard IRQ handlers run with a separate stack on a different thread, so each call to mp_irq_dispatch() and mp_irq_handler() has to be wrapped with adjustments to the stack-limit checker. Move these adjustments into the shared mp_irq_dispatch(), introducing MICROPY_STACK_SIZE_HARD_IRQ which a port can define to non-zero if it uses a separate stack for hard IRQ handlers. We only need wrap the hard dispatch case. This should reduce binary size on zephyr without affecting other ports. Signed-off-by: Chris Webb --- ports/zephyr/machine_i2c_target.c | 7 ------- ports/zephyr/machine_pin.c | 15 --------------- ports/zephyr/machine_timer.c | 16 ---------------- ports/zephyr/mpconfigport.h | 2 ++ py/mpconfig.h | 7 +++++++ shared/runtime/mpirq.c | 15 +++++++++++++++ 6 files changed, 24 insertions(+), 38 deletions(-) diff --git a/ports/zephyr/machine_i2c_target.c b/ports/zephyr/machine_i2c_target.c index 236f1334883..c7786e7df92 100644 --- a/ports/zephyr/machine_i2c_target.c +++ b/ports/zephyr/machine_i2c_target.c @@ -114,14 +114,7 @@ static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *s } static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { - char dummy; - void *orig_top = MP_STATE_THREAD(stack_top); - mp_uint_t orig_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = &dummy; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; mp_irq_handler(&irq->base); - MP_STATE_THREAD(stack_top) = orig_top; - MP_STATE_THREAD(stack_limit) = orig_limit; } static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index e0718588d65..1ae262419f5 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -64,22 +64,7 @@ void machine_pin_deinit(void) { static void gpio_callback_handler(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { machine_pin_irq_obj_t *irq = CONTAINER_OF(cb, machine_pin_irq_obj_t, callback); - #if MICROPY_STACK_CHECK - // This callback executes in an ISR context so the stack-limit check must be changed to - // use the ISR stack for the duration of this function (so that hard IRQ callbacks work). - char *orig_stack_top = MP_STATE_THREAD(stack_top); - size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = (void *)&irq; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; - #endif - mp_irq_handler(&irq->base); - - #if MICROPY_STACK_CHECK - // Restore original stack-limit checking values. - MP_STATE_THREAD(stack_top) = orig_stack_top; - MP_STATE_THREAD(stack_limit) = orig_stack_limit; - #endif } static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { diff --git a/ports/zephyr/machine_timer.c b/ports/zephyr/machine_timer.c index 410e86762bd..4b660849f67 100644 --- a/ports/zephyr/machine_timer.c +++ b/ports/zephyr/machine_timer.c @@ -66,16 +66,6 @@ static mp_obj_t machine_timer_deinit(mp_obj_t self_in); static void machine_timer_callback(struct k_timer *timer) { machine_timer_obj_t *self = (machine_timer_obj_t *)k_timer_user_data_get(timer); - #if MICROPY_STACK_CHECK - // This callback executes in an ISR context so the stack-limit check must - // be changed to use the ISR stack for the duration of this function (so - // that hard IRQ callbacks work). - char *orig_stack_top = MP_STATE_THREAD(stack_top); - size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = (void *)&self; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; - #endif - if (mp_irq_dispatch(self->callback, MP_OBJ_FROM_PTR(self), self->ishard) < 0) { // Uncaught exception; disable the callback so it doesn't run again. self->mode = TIMER_MODE_ONE_SHOT; @@ -84,12 +74,6 @@ static void machine_timer_callback(struct k_timer *timer) { if (self->mode == TIMER_MODE_ONE_SHOT) { machine_timer_deinit(self); } - - #if MICROPY_STACK_CHECK - // Restore original stack-limit checking values. - MP_STATE_THREAD(stack_top) = orig_stack_top; - MP_STATE_THREAD(stack_limit) = orig_stack_limit; - #endif } static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index fbf8dbcc7a7..f6b78e64f0c 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -47,6 +47,8 @@ #define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_ENABLE_SOURCE_LINE (1) #define MICROPY_STACK_CHECK (1) +#define MICROPY_STACK_CHECK_MARGIN (512) +#define MICROPY_STACK_SIZE_HARD_IRQ (CONFIG_ISR_STACK_SIZE) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_FINALISER (MICROPY_VFS) #define MICROPY_HELPER_REPL (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index ae44b461d37..f4b7c105836 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -717,6 +717,13 @@ #define MICROPY_STACK_CHECK_MARGIN (0) #endif +// The size of a separate stack used for hard IRQ handlers, which should be +// checked instead of the main stack when running a hard callback. 0 implies +// there is no separate ISR stack to check. +#ifndef MICROPY_STACK_SIZE_HARD_IRQ +#define MICROPY_STACK_SIZE_HARD_IRQ (0) +#endif + // Whether to have an emergency exception buffer #ifndef MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0) diff --git a/shared/runtime/mpirq.c b/shared/runtime/mpirq.c index 0b3489c5141..4d848ae7e91 100644 --- a/shared/runtime/mpirq.c +++ b/shared/runtime/mpirq.c @@ -69,6 +69,15 @@ int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard) { int result = 0; if (handler != mp_const_none) { if (ishard) { + #if MICROPY_STACK_CHECK && MICROPY_STACK_SIZE_HARD_IRQ > 0 + // This callback executes in an ISR context so the stack-limit + // check must be changed to use the ISR stack for the duration + // of this function. + char *orig_stack_top = MP_STATE_THREAD(stack_top); + size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); + mp_cstack_init_with_sp_here(MICROPY_STACK_SIZE_HARD_IRQ); + #endif + // When executing code within a handler we must lock the scheduler to // prevent any scheduled callbacks from running, and lock the GC to // prevent any memory allocations. @@ -85,6 +94,12 @@ int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard) { } gc_unlock(); mp_sched_unlock(); + + #if MICROPY_STACK_CHECK && MICROPY_STACK_SIZE_HARD_IRQ > 0 + // Restore original stack-limit checking values. + MP_STATE_THREAD(stack_top) = orig_stack_top; + MP_STATE_THREAD(stack_limit) = orig_stack_limit; + #endif } else { // Schedule call to user function mp_sched_schedule(handler, parent); From 2a03ade02cbeee55f275eda30b809812a8c83a7a Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sat, 20 Sep 2025 17:29:48 +0200 Subject: [PATCH 1266/2098] docs/library/os: Document dupterm_notify function. Closes issue #17799. Signed-off-by: Jos Verlinde --- docs/library/os.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/library/os.rst b/docs/library/os.rst index 710875e34c3..bd552e36f01 100644 --- a/docs/library/os.rst +++ b/docs/library/os.rst @@ -133,6 +133,37 @@ Terminal redirection and duplication The function returns the previous stream-like object in the given slot. +.. function:: dupterm_notify(obj_in, /) + + Notify the MicroPython REPL that input is available on a stream-like object + previously registered via `os.dupterm()`. + + This function should be called by custom stream implementations (e.g., UART, + Bluetooth, or other non-USB REPL streams) to inform the REPL that input is + ready to be read. Proper use ensures that special characters such as + Ctrl+C (used to trigger KeyboardInterrupt) are processed promptly by the + REPL, enabling expected interruption behavior for user code. + + The *obj_in* parameter is ignored by `os.dupterm_notify()`, but is required to allow calling + dupterm_notify from an interrupt handler such as `UART.irq()`. + + Example: + + .. code-block:: python + + from machine import UART + import os + uart = UART(0) + os.dupterm(uart, 0) + uart.irq(os.dupterm_notify, machine.UART.IRQ_RX) + + .. note:: + If the ``dupterm_notify()`` function is not called, input from the custom stream + may not be detected or processed until the next REPL poll, potentially delaying + KeyboardInterrupts or other control signals. + This is especially important for UART, Bluetooth and other + non-standard REPL connections, where automatic notification is not guaranteed. + Filesystem mounting ------------------- From 74270df9acbb619ce58830eb6940b3366d372526 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 25 Sep 2025 12:15:16 +0200 Subject: [PATCH 1267/2098] docs/README: Update doc build dependency. The docs were changed in c3040463849eb9b3e5f85706bb223d648e60510d to require sphinx_rtd_theme. Signed-off-by: Jos Verlinde --- docs/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 892726ba17f..11ad32ddb5d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,8 +13,7 @@ Building the documentation locally If you're making changes to the documentation, you may want to build the documentation locally so that you can preview your changes. -Install Sphinx, and optionally (for the RTD-styling), sphinx_rtd_theme, -preferably in a virtualenv: +Install Sphinx and sphinx_rtd_theme, preferably in a virtualenv: pip install sphinx pip install sphinx_rtd_theme From 10b3d96b607404e5d73cb43c9d587ddb1f5665fc Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 25 Sep 2025 12:16:51 +0200 Subject: [PATCH 1268/2098] docs/README: Document sphinx-autobuild for local preview. Signed-off-by: Jos Verlinde --- docs/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/README.md b/docs/README.md index 11ad32ddb5d..9b3b036e063 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,21 @@ In `micropython/docs`, build the docs: You'll find the index page at `micropython/docs/build/html/index.html`. +Documentation autobuild +----------------------- + +For a more convenient development experience, you can use `sphinx-autobuild` +to automatically rebuild and serve the documentation when you make changes: + + pip install sphinx-autobuild + +Then run from the `micropython/docs` directory: + + sphinx-autobuild . build/html + +This will start a local web server (typically at `http://127.0.0.1:8000`) +and automatically rebuild the documentation whenever you save changes to the source files. + Having readthedocs.org build the documentation ---------------------------------------------- From 2373340aa342dbd30e1482cf431895f808d863b2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 13 Sep 2025 19:33:34 +1000 Subject: [PATCH 1269/2098] py/stream: Reuse write implementation for readinto. This commit refactors some common code in the core stream implementation, to reduce code size while retaining the same functionality. With the factoring, `readinto`/`readinto1` could now support an additional 4th argument (like write) but it's best not to introduce even more CPython incompatibility, so they are left as having a maximum of 3 args. Signed-off-by: Damien George --- py/stream.c | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/py/stream.c b/py/stream.c index 008c9684527..93bdac42c8f 100644 --- a/py/stream.c +++ b/py/stream.c @@ -261,9 +261,14 @@ void mp_stream_write_adaptor(void *self, const char *buf, size_t len) { mp_stream_write(MP_OBJ_FROM_PTR(self), buf, len, MP_STREAM_RW_WRITE); } -static mp_obj_t stream_write_generic(size_t n_args, const mp_obj_t *args, byte flags) { +static mp_obj_t stream_readinto_write_generic(size_t n_args, const mp_obj_t *args, byte flags) { mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); + mp_get_buffer_raise(args[1], &bufinfo, (flags & MP_STREAM_RW_WRITE) ? MP_BUFFER_READ : MP_BUFFER_WRITE); + + // CPython extension, allow optional maximum length and offset: + // - stream.operation(buf, max_len) + // - stream.operation(buf, off, max_len) + // Similar to https://docs.python.org/3/library/socket.html#socket.socket.recv_into size_t max_len = (size_t)-1; size_t off = 0; if (n_args == 3) { @@ -276,53 +281,28 @@ static mp_obj_t stream_write_generic(size_t n_args, const mp_obj_t *args, byte f } } bufinfo.len -= off; + + // Perform the readinto or write operation. return mp_stream_write(args[0], (byte *)bufinfo.buf + off, MIN(bufinfo.len, max_len), flags); } static mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) { - return stream_write_generic(n_args, args, MP_STREAM_RW_WRITE); + return stream_readinto_write_generic(n_args, args, MP_STREAM_RW_WRITE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj, 2, 4, stream_write_method); static mp_obj_t stream_write1_method(size_t n_args, const mp_obj_t *args) { - return stream_write_generic(n_args, args, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE); + return stream_readinto_write_generic(n_args, args, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write1_obj, 2, 4, stream_write1_method); -static mp_obj_t stream_readinto_generic(size_t n_args, const mp_obj_t *args, byte flags) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); - - // CPython extension: if 2nd arg is provided, that's max len to read, - // instead of full buffer. Similar to - // https://docs.python.org/3/library/socket.html#socket.socket.recv_into - mp_uint_t len = bufinfo.len; - if (n_args > 2) { - len = mp_obj_get_int(args[2]); - if (len > bufinfo.len) { - len = bufinfo.len; - } - } - - int error; - mp_uint_t out_sz = mp_stream_rw(args[0], bufinfo.buf, len, &error, flags); - if (error != 0) { - if (mp_is_nonblocking_error(error)) { - return mp_const_none; - } - mp_raise_OSError(error); - } else { - return MP_OBJ_NEW_SMALL_INT(out_sz); - } -} - static mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) { - return stream_readinto_generic(n_args, args, MP_STREAM_RW_READ); + return stream_readinto_write_generic(n_args, args, MP_STREAM_RW_READ); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto_obj, 2, 3, stream_readinto); static mp_obj_t stream_readinto1(size_t n_args, const mp_obj_t *args) { - return stream_readinto_generic(n_args, args, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); + return stream_readinto_write_generic(n_args, args, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto1_obj, 2, 3, stream_readinto1); From b44c4de4fde4a520f0af044d9495187abc9e29fa Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 19 Sep 2025 13:09:25 +1000 Subject: [PATCH 1270/2098] tests/ports/unix: Add coverage test for readinto1 stream method. And expand the test for `readinto()` to test the difference between trying to read the requested amount by doing multiple underlying IO calls, and only doing one call. Signed-off-by: Damien George --- ports/unix/coverage.c | 1 + tests/ports/unix/extra_coverage.py | 23 +++++++++++++++++++++++ tests/ports/unix/extra_coverage.py.exp | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index e47558689d1..1dd009ed093 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -97,6 +97,7 @@ static const mp_rom_map_elem_t rawfile_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write1), MP_ROM_PTR(&mp_stream_write1_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto1), MP_ROM_PTR(&mp_stream_readinto1_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&mp_stream_ioctl_obj) }, }; diff --git a/tests/ports/unix/extra_coverage.py b/tests/ports/unix/extra_coverage.py index 72f5fe56b3a..493d02cde98 100644 --- a/tests/ports/unix/extra_coverage.py +++ b/tests/ports/unix/extra_coverage.py @@ -33,6 +33,7 @@ print(stream.read(1)) # read 1 byte encounters non-blocking error print(stream.readline()) # readline encounters non-blocking error print(stream.readinto(bytearray(10))) # readinto encounters non-blocking error +print(stream.readinto1(bytearray(10))) # readinto1 encounters non-blocking error print(stream.write(b"1")) # write encounters non-blocking error print(stream.write1(b"1")) # write1 encounters non-blocking error stream.set_buf(b"123") @@ -48,6 +49,28 @@ stream.set_error(0) print(stream.ioctl(0, bytearray(10))) # successful ioctl call +print("# stream.readinto") + +# stream.readinto will read 3 bytes then try to read more to fill the buffer, +# but on the second attempt will encounter EIO and should raise that error. +stream.set_error(errno.EIO) +stream.set_buf(b"123") +buf = bytearray(4) +try: + stream.readinto(buf) +except OSError as er: + print("OSError", er.errno == errno.EIO) +print(buf) + +# stream.readinto1 will read 3 bytes then should return them immediately, and +# not encounter the EIO. +stream.set_error(errno.EIO) +stream.set_buf(b"123") +buf = bytearray(4) +print(stream.readinto1(buf), buf) + +print("# stream textio") + stream2 = data[3] # is textio print(stream2.read(1)) # read 1 byte encounters non-blocking error with textio stream diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 85e0e18cbfd..5b6e1f3f0f0 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -214,11 +214,17 @@ None None None None +None b'123' b'123' b'123' OSError 0 +# stream.readinto +OSError True +bytearray(b'123\x00') +3 bytearray(b'123\x00') +# stream textio None None cpp None From 7be82f8b7c9ee83696cb08f8f2936f0ed93cd52c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 16:05:42 +0100 Subject: [PATCH 1271/2098] tests: Test extremes of range() and document differences to CPython. This avoids remaining buggy cases where C signed overflow occurs. Signed-off-by: Jeff Epler --- tests/basics/builtin_range_maxsize.py | 38 +++++++++++++++++++++++++++ tests/cpydiff/types_range_limits.py | 26 ++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tests/basics/builtin_range_maxsize.py create mode 100644 tests/cpydiff/types_range_limits.py diff --git a/tests/basics/builtin_range_maxsize.py b/tests/basics/builtin_range_maxsize.py new file mode 100644 index 00000000000..b0f3a5e5129 --- /dev/null +++ b/tests/basics/builtin_range_maxsize.py @@ -0,0 +1,38 @@ +try: + from sys import maxsize +except ImportError: + print("SKIP") + raise SystemExit + +# Test the range builtin at extreme values. (https://github.com/micropython/micropython/issues/17684) +# +# This is written using asserts instead of prints because the value of `maxsize` differs. +# +# Numbers & counts right up against the max of mp_int_t also cause overflows, such as +# objrange.c:115:14: runtime error: signed integer overflow: 9223372036854775807 + 1 cannot be represented in type 'long int' +# so just avoid them for the purposes of this test. + +r = range(-maxsize + 1, -1) +assert r.start == -maxsize + 1 +assert r.stop == -1 +assert r[0] == -maxsize + 1 +assert r[1] == -maxsize + 2 +assert r[-1] == -2 +assert r[-2] == -3 + +ir = iter(r) +assert next(ir) == -maxsize + 1 +assert next(ir) == -maxsize + 2 + +r = range(0, maxsize - 1) +assert len(r) == maxsize - 1 +assert r.stop == maxsize - 1 + +r = range(maxsize, 0, -1) +assert len(r) == maxsize +assert r.start == maxsize +assert r[0] == maxsize +assert r[1] == maxsize - 1 +ir = iter(r) +assert next(ir) == maxsize +assert next(ir) == maxsize - 1 diff --git a/tests/cpydiff/types_range_limits.py b/tests/cpydiff/types_range_limits.py new file mode 100644 index 00000000000..e53d5fd4088 --- /dev/null +++ b/tests/cpydiff/types_range_limits.py @@ -0,0 +1,26 @@ +""" +categories: Types,range +description: Range objects with large start or stop arguments misbehave. +cause: Intermediate calculations overflow the C mp_int_t type +workaround: Avoid using such ranges +""" + +from sys import maxsize + +# A range including `maxsize-1` cannot be created +try: + print(range(-maxsize - 1, 0)) +except OverflowError: + print("OverflowError") + +# A range with `stop-start` exceeding sys.maxsize has incorrect len(), while CPython cannot calculate len(). +try: + print(len(range(-maxsize, maxsize))) +except OverflowError: + print("OverflowError") + +# A range with `stop-start` exceeding sys.maxsize has incorrect len() +try: + print(len(range(-maxsize, maxsize, maxsize))) +except OverflowError: + print("OverflowError") From 72867bce1d2cf39abe164bebf4d239c6974c9122 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 16:09:19 +0100 Subject: [PATCH 1272/2098] py/objrange: Allow return of non-small ints. The magnitude of `range()` arguments is not restricted to "small" ints, but includes "machine ints" which fit inside a register but can only be represented as "long integer" objects in Python. Signed-off-by: Jeff Epler --- py/objrange.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/objrange.c b/py/objrange.c index 1cc575f33e7..e19d6c4dfcd 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -41,9 +41,9 @@ typedef struct _mp_obj_range_it_t { static mp_obj_t range_it_iternext(mp_obj_t o_in) { mp_obj_range_it_t *o = MP_OBJ_TO_PTR(o_in); if ((o->step > 0 && o->cur < o->stop) || (o->step < 0 && o->cur > o->stop)) { - mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(o->cur); + mp_int_t cur = o->cur; o->cur += o->step; - return o_out; + return mp_obj_new_int(cur); } else { return MP_OBJ_STOP_ITERATION; } @@ -132,7 +132,7 @@ static mp_obj_t range_unary_op(mp_unary_op_t op, mp_obj_t self_in) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(len > 0); case MP_UNARY_OP_LEN: - return MP_OBJ_NEW_SMALL_INT(len); + return mp_obj_new_int(len); default: return MP_OBJ_NULL; // op not supported } @@ -173,7 +173,7 @@ static mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } #endif size_t index_val = mp_get_index(self->base.type, len, index, false); - return MP_OBJ_NEW_SMALL_INT(self->start + index_val * self->step); + return mp_obj_new_int(self->start + index_val * self->step); } else { return MP_OBJ_NULL; // op not supported } From 03b4c60fc1cf85e10b88dad3d7b04046b6393a15 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 10 Aug 2025 22:35:31 +1000 Subject: [PATCH 1273/2098] nrf/modules/machine/spi: Print SPI baudrate, polarity and phase. On most ports, printing an instance of machine.SPI gives something like: >>> machine.SPI(1) SPI(1, baudrate=328125, polarity=0, phase=0, bits=8) This commit makes the nrf port do the same. The reason for this change is: - make nrf consistent with other ports - allow the `tests/extmod/machine_spi_rate.py` to run on the nrf port (this tests parses the output of str(spi) to get the actual baudrate) Signed-off-by: Damien George --- ports/nrf/modules/machine/spi.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ports/nrf/modules/machine/spi.c b/ports/nrf/modules/machine/spi.c index b00a5706c66..f474c88715c 100644 --- a/ports/nrf/modules/machine/spi.c +++ b/ports/nrf/modules/machine/spi.c @@ -187,11 +187,33 @@ enum { ARG_INIT_firstbit }; +static inline uint32_t machine_hard_spi_get_baudrate(const machine_hard_spi_obj_t *self) { + MP_STATIC_ASSERT(NRF_SPI_FREQ_125K == (2 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_250K == (4 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_500K == (8 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_1M == (16 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_2M == (32 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_4M == (64 << 24)); + MP_STATIC_ASSERT(NRF_SPI_FREQ_8M == (128 << 24)); + #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED + if (self->p_config->frequency == NRF_SPIM_FREQ_16M) { + return 16000000; + } + if (self->p_config->frequency == NRF_SPIM_FREQ_32M) { + return 32000000; + } + #endif + return 125000 * (self->p_config->frequency >> 25); +} + static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_arg_val_t *args); static void machine_hard_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hard_spi_obj_t *self = self_in; - mp_printf(print, "SPI(%u)", self->p_spi->drv_inst_idx); + unsigned int polarity = self->p_config->mode == NRF_SPI_MODE_0 || self->p_config->mode == NRF_SPI_MODE_1 ? 0 : 1; + unsigned int phase = self->p_config->mode == NRF_SPI_MODE_0 || self->p_config->mode == NRF_SPI_MODE_2 ? 0 : 1; + mp_printf(print, "SPI(%u, baudrate=%u, polarity=%u, phase=%u)", + self->p_spi->drv_inst_idx, machine_hard_spi_get_baudrate(self), polarity, phase); } static mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { From f1b00d67e969ae97d0dc362df7ee0355c1a12a30 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 17 Sep 2025 11:16:05 +1000 Subject: [PATCH 1274/2098] cc3200/mpconfigport: Use MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES. This commit makes it explicit that the port uses the MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES feature level, and removes config options that are the default at this level. This change is a no-op for the firmware. Signed-off-by: Damien George --- ports/cc3200/mpconfigport.h | 39 +++++++------------------------------ 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 3e8498e380b..ba7e5830ffd 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -32,31 +32,27 @@ #include "semphr.h" #endif +// Set the rom feature level. +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) + // options to control how MicroPython is built #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_PERSISTENT_CODE_LOAD (1) -#define MICROPY_EMIT_THUMB (0) -#define MICROPY_EMIT_INLINE_THUMB (0) #define MICROPY_COMP_CONST_LITERAL (0) #define MICROPY_COMP_MODULE_CONST (1) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_FINALISER (1) -#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0) -#define MICROPY_STACK_CHECK (0) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_KBD_EXCEPTION (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_ENABLE_SOURCE_LINE (1) -#define MICROPY_ENABLE_DOC_STRING (0) #define MICROPY_REPL_AUTO_INDENT (1) #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) -#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_NONE) -#define MICROPY_OPT_COMPUTED_GOTO (0) #define MICROPY_READER_VFS (1) -#ifndef DEBUG // we need ram on the launchxl while debugging -#define MICROPY_CPYTHON_COMPAT (1) -#else +#ifdef DEBUG // we need ram on the launchxl while debugging #define MICROPY_CPYTHON_COMPAT (0) #endif @@ -74,7 +70,7 @@ #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_VFS (1) #define MICROPY_VFS_FAT (1) -#define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_PY_ASYNC_AWAIT (0) #define MICROPY_PY_ALL_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) #define MICROPY_PY_BUILTINS_INPUT (1) @@ -88,32 +84,16 @@ #define MICROPY_PY_BUILTINS_EXECFILE (1) #define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) -#else -#define MICROPY_PY_BUILTINS_STR_UNICODE (0) -#define MICROPY_PY_BUILTINS_STR_SPLITLINES (0) -#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) -#define MICROPY_PY_BUILTINS_FROZENSET (0) -#define MICROPY_PY_BUILTINS_EXECFILE (0) -#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) -#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (0) #endif -#define MICROPY_PY_MICROPYTHON_MEM_INFO (0) #define MICROPY_PY_SYS_MAXSIZE (1) -#define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_STDFILES (1) -#define MICROPY_PY_CMATH (0) -#define MICROPY_PY_IO (1) #define MICROPY_PY_ERRNO (1) #define MICROPY_PY_ERRNO_ERRORCODE (0) #define MICROPY_PY_THREAD (1) #define MICROPY_PY_THREAD_GIL (1) #define MICROPY_PY_BINASCII (1) -#define MICROPY_PY_UCTYPES (0) -#define MICROPY_PY_DEFLATE (0) #define MICROPY_PY_JSON (1) #define MICROPY_PY_RE (1) -#define MICROPY_PY_HEAPQ (0) -#define MICROPY_PY_HASHLIB (0) #define MICROPY_PY_OS (1) #define MICROPY_PY_OS_INCLUDEFILE "ports/cc3200/mods/modos.c" #define MICROPY_PY_OS_DUPTERM (1) @@ -124,7 +104,6 @@ #define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/cc3200/mods/modtime.c" -#define MICROPY_PY_VFS (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/cc3200/mods/modmachine.c" #define MICROPY_PY_MACHINE_RESET (1) @@ -133,10 +112,6 @@ #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/cc3200/mods/machine_wdt.c" -#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) -#define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) -#define MICROPY_KBD_EXCEPTION (1) - // We define our own list of errno constants to include in errno module #define MICROPY_PY_ERRNO_LIST \ X(EPERM) \ From 24f395b5dbb2149bf505873e32f7899c6dd57558 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Sep 2025 15:24:02 +1000 Subject: [PATCH 1275/2098] tests/ports/webassembly: Move JsProxy identity test to separate file. This test is not a PyProxy test, rather it's a JsProxy test. Signed-off-by: Damien George --- tests/ports/webassembly/js_proxy_identity.mjs | 18 ++++++++++++++++++ .../webassembly/js_proxy_identity.mjs.exp | 8 ++++++++ tests/ports/webassembly/py_proxy_identity.mjs | 9 --------- .../webassembly/py_proxy_identity.mjs.exp | 7 ------- 4 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 tests/ports/webassembly/js_proxy_identity.mjs create mode 100644 tests/ports/webassembly/js_proxy_identity.mjs.exp diff --git a/tests/ports/webassembly/js_proxy_identity.mjs b/tests/ports/webassembly/js_proxy_identity.mjs new file mode 100644 index 00000000000..e279d219d21 --- /dev/null +++ b/tests/ports/webassembly/js_proxy_identity.mjs @@ -0,0 +1,18 @@ +// Test identity of JsProxy when they are the same JavaScript object. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +mp.runPython(` +import js + +print(js.Object) + +print("Object equality") +print(js.Object == js.Object) +print(js.Object.assign == js.Object.assign) + +print("Array equality") +print(js.Array == js.Array) +print(js.Array.prototype == js.Array.prototype) +print(js.Array.prototype.push == js.Array.prototype.push) +`); diff --git a/tests/ports/webassembly/js_proxy_identity.mjs.exp b/tests/ports/webassembly/js_proxy_identity.mjs.exp new file mode 100644 index 00000000000..5791d911baf --- /dev/null +++ b/tests/ports/webassembly/js_proxy_identity.mjs.exp @@ -0,0 +1,8 @@ + +Object equality +True +True +Array equality +True +True +True diff --git a/tests/ports/webassembly/py_proxy_identity.mjs b/tests/ports/webassembly/py_proxy_identity.mjs index 97dab2e7836..d4a720b738a 100644 --- a/tests/ports/webassembly/py_proxy_identity.mjs +++ b/tests/ports/webassembly/py_proxy_identity.mjs @@ -23,13 +23,4 @@ js.eventTarget.addEventListener("event", callback) js.eventTarget.dispatchEvent(js.event) js.eventTarget.removeEventListener("event", callback) js.eventTarget.dispatchEvent(js.event) - -print("Object equality") -print(js.Object == js.Object) -print(js.Object.assign == js.Object.assign) - -print("Array equality") -print(js.Array == js.Array) -print(js.Array.prototype == js.Array.prototype) -print(js.Array.prototype.push == js.Array.prototype.push) `); diff --git a/tests/ports/webassembly/py_proxy_identity.mjs.exp b/tests/ports/webassembly/py_proxy_identity.mjs.exp index 344a0a20236..b5b8b210bb6 100644 --- a/tests/ports/webassembly/py_proxy_identity.mjs.exp +++ b/tests/ports/webassembly/py_proxy_identity.mjs.exp @@ -1,10 +1,3 @@ PyProxy { _ref: 3 } PyProxy { _ref: 3 } true callback -Object equality -True -True -Array equality -True -True -True From 9eddbb32f338a51b475b56891b334084633fbd0e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Sep 2025 14:48:17 +1000 Subject: [PATCH 1276/2098] webassembly: Do GC in return path rather than entry path. Doing GC calls in the entry path (when JavaScript calls into MicroPython at the top/outer level) can lead to freeing of objects which are still in use. This is because the (JavaScript) objects are referenced in the input arguments to the C functions and they are not yet converted to full proxy objects and not yet tracked properly by the live-object tracker. Signed-off-by: Damien George --- ports/webassembly/main.c | 16 +++++++--------- ports/webassembly/proxy_c.c | 17 +++++++---------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c index e0e2d59ae6b..a7df7969c31 100644 --- a/ports/webassembly/main.c +++ b/ports/webassembly/main.c @@ -58,15 +58,15 @@ static void gc_collect_top_level(void); void external_call_depth_inc(void) { ++external_call_depth; - #if MICROPY_GC_SPLIT_HEAP_AUTO - if (external_call_depth == 1) { - gc_collect_top_level(); - } - #endif } void external_call_depth_dec(void) { --external_call_depth; + #if MICROPY_GC_SPLIT_HEAP_AUTO + if (external_call_depth == 0) { + gc_collect_top_level(); + } + #endif } void mp_js_init(int pystack_size, int heap_size) { @@ -133,13 +133,12 @@ void mp_js_do_import(const char *name, uint32_t *out) { } } nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(ret, out); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); } + external_call_depth_dec(); } void mp_js_do_exec(const char *src, size_t len, uint32_t *out) { @@ -153,13 +152,12 @@ void mp_js_do_exec(const char *src, size_t len, uint32_t *out) { mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); mp_obj_t ret = mp_call_function_0(module_fun); nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(ret, out); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); } + external_call_depth_dec(); } void mp_js_do_exec_async(const char *src, size_t len, uint32_t *out) { diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c index b7bc87b7634..459f6a6dad8 100644 --- a/ports/webassembly/proxy_c.c +++ b/ports/webassembly/proxy_c.c @@ -257,13 +257,12 @@ void proxy_c_to_js_call(uint32_t c_ref, uint32_t n_args, uint32_t *args_value, u mp_obj_t obj = proxy_c_get_obj(c_ref); mp_obj_t member = mp_call_function_n_kw(obj, n_args, 0, args); nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(member, out); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); } + external_call_depth_dec(); } void proxy_c_to_js_dir(uint32_t c_ref, uint32_t *out) { @@ -285,13 +284,12 @@ void proxy_c_to_js_dir(uint32_t c_ref, uint32_t *out) { dir = mp_builtin_dir_obj.fun.var(1, args); } nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(dir, out); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); } + external_call_depth_dec(); } bool proxy_c_to_js_has_attr(uint32_t c_ref, const char *attr_in) { @@ -333,13 +331,12 @@ void proxy_c_to_js_lookup_attr(uint32_t c_ref, const char *attr_in, uint32_t *ou member = mp_load_attr(obj, attr); } nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(member, out); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); } + external_call_depth_dec(); } static bool proxy_c_to_js_store_helper(uint32_t c_ref, const char *attr_in, uint32_t *value_in) { @@ -450,16 +447,17 @@ bool proxy_c_to_js_iternext(uint32_t c_ref, uint32_t *out) { return false; } nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(iter, out); + external_call_depth_dec(); return true; } else { - external_call_depth_dec(); if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + external_call_depth_dec(); return false; } else { // uncaught exception proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out); + external_call_depth_dec(); return true; } } @@ -605,11 +603,10 @@ void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) { mp_obj_t reject = proxy_convert_js_to_mp_obj_cside(args + 2 * 3); mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, mp_const_none, resolve, reject); nlr_pop(); - external_call_depth_dec(); proxy_convert_mp_to_js_obj_cside(ret, args); } else { // uncaught exception - external_call_depth_dec(); proxy_convert_mp_to_js_exc_cside(nlr.ret_val, args); } + external_call_depth_dec(); } From a319ddefa60750ce1b71b1fc7296fc2c69adc66a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Sep 2025 13:56:58 +1000 Subject: [PATCH 1277/2098] webassembly: Improve identity and fix bug with lost JsProxy refs. Commit ffa98cb0143c43af9f4c61142784a08a19f660c5 improved equality for `JsProxy` objects so that, eg, `js.Object == js.Object` is true. As mentioned in #17758, a further optimisation is to make identity work in that case, eg `js.Object is js.Object` should be true (on the Python side). This commit implements that, by keeping track of all `JsProxy` Python objects and reusing them where possible: where the underlying JS ref is equal, ie they point to the same JS object. That reduces memory churn and gives better identity behaviour of JS objects proxied over to Python. As part of this, a bug is fixed where JS objects can be freed while there's still a `JsProxy` referring to that JS object. A test is added for that exact scenario, and the test now passes. Signed-off-by: Damien George --- ports/webassembly/objjsproxy.c | 31 ++++++++++++ ports/webassembly/proxy_c.c | 10 +++- ports/webassembly/proxy_c.h | 1 + ports/webassembly/proxy_js.js | 50 +++++++++---------- tests/ports/webassembly/heap_expand.mjs.exp | 48 +++++++++--------- tests/ports/webassembly/js_proxy_identity.mjs | 7 +++ .../webassembly/js_proxy_identity.mjs.exp | 5 ++ .../ports/webassembly/js_proxy_reuse_free.mjs | 48 ++++++++++++++++++ .../webassembly/js_proxy_reuse_free.mjs.exp | 7 +++ 9 files changed, 155 insertions(+), 52 deletions(-) create mode 100644 tests/ports/webassembly/js_proxy_reuse_free.mjs create mode 100644 tests/ports/webassembly/js_proxy_reuse_free.mjs.exp diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index a8b21a74456..2d46702ff8e 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -32,6 +32,9 @@ #include "py/runtime.h" #include "proxy_c.h" +static mp_obj_t *jsproxy_table = NULL; +static size_t jsproxy_table_len = 0; + EM_JS(bool, has_attr, (int jsref, const char *str), { const base = proxy_js_ref[jsref]; const attr = UTF8ToString(str); @@ -295,6 +298,7 @@ EM_JS(void, proxy_js_free_obj, (int js_ref), { static mp_obj_t jsproxy___del__(mp_obj_t self_in) { mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in); + jsproxy_table[self->ref] = MP_OBJ_NULL; proxy_js_free_obj(self->ref); return mp_const_none; } @@ -590,13 +594,40 @@ MP_DEFINE_CONST_OBJ_TYPE( iter, jsproxy_getiter ); +void mp_obj_jsproxy_init(void) { + jsproxy_table = NULL; + jsproxy_table_len = 0; + MP_STATE_PORT(jsproxy_global_this) = mp_obj_new_jsproxy(MP_OBJ_JSPROXY_REF_GLOBAL_THIS); +} + +MP_REGISTER_ROOT_POINTER(mp_obj_t jsproxy_global_this); + mp_obj_t mp_obj_new_jsproxy(int ref) { + // The proxy for this ref should not exist. + assert(ref >= jsproxy_table_len || jsproxy_table[ref] == MP_OBJ_NULL); + mp_obj_jsproxy_t *o = mp_obj_malloc_with_finaliser(mp_obj_jsproxy_t, &mp_type_jsproxy); o->ref = ref; o->bind_to_self = false; + if (ref >= jsproxy_table_len) { + size_t new_len = MAX(16, ref * 2); + jsproxy_table = realloc(jsproxy_table, new_len * sizeof(mp_obj_t)); + for (size_t i = jsproxy_table_len; i < new_len; ++i) { + jsproxy_table[i] = MP_OBJ_NULL; + } + jsproxy_table_len = new_len; + } + jsproxy_table[ref] = MP_OBJ_FROM_PTR(o); return MP_OBJ_FROM_PTR(o); } +mp_obj_t mp_obj_get_jsproxy(int ref) { + // The proxy for this ref should exist. + assert(ref < jsproxy_table_len && jsproxy_table[ref] != MP_OBJ_NULL); + + return jsproxy_table[ref]; +} + // Load/delete/store an attribute from/to the JavaScript globalThis entity. void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c index 459f6a6dad8..790ad90ef85 100644 --- a/ports/webassembly/proxy_c.c +++ b/ports/webassembly/proxy_c.c @@ -59,8 +59,9 @@ enum { PROXY_KIND_JS_INTEGER = 3, PROXY_KIND_JS_DOUBLE = 4, PROXY_KIND_JS_STRING = 5, - PROXY_KIND_JS_OBJECT = 6, - PROXY_KIND_JS_PYPROXY = 7, + PROXY_KIND_JS_OBJECT_EXISTING = 6, + PROXY_KIND_JS_OBJECT = 7, + PROXY_KIND_JS_PYPROXY = 8, }; MP_DEFINE_CONST_OBJ_TYPE( @@ -83,6 +84,9 @@ void proxy_c_init(void) { MP_STATE_PORT(proxy_c_dict) = mp_obj_new_dict(0); mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), MP_OBJ_NULL); proxy_c_ref_next = PROXY_C_REF_NUM_STATIC; + + void mp_obj_jsproxy_init(void); + mp_obj_jsproxy_init(); } MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref); @@ -172,6 +176,8 @@ mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value) { return s; } else if (value[0] == PROXY_KIND_JS_PYPROXY) { return proxy_c_get_obj(value[1]); + } else if (value[0] == PROXY_KIND_JS_OBJECT_EXISTING) { + return mp_obj_get_jsproxy(value[1]); } else { // PROXY_KIND_JS_OBJECT return mp_obj_new_jsproxy(value[1]); diff --git a/ports/webassembly/proxy_c.h b/ports/webassembly/proxy_c.h index bac0a90bd68..2f37aedbc5a 100644 --- a/ports/webassembly/proxy_c.h +++ b/ports/webassembly/proxy_c.h @@ -53,6 +53,7 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out); void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out); mp_obj_t mp_obj_new_jsproxy(int ref); +mp_obj_t mp_obj_get_jsproxy(int ref); void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest); static inline bool mp_obj_is_jsproxy(mp_obj_t o) { diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js index cbd6e5b0088..60b832d81fd 100644 --- a/ports/webassembly/proxy_js.js +++ b/ports/webassembly/proxy_js.js @@ -48,8 +48,9 @@ const PROXY_KIND_JS_BOOLEAN = 2; const PROXY_KIND_JS_INTEGER = 3; const PROXY_KIND_JS_DOUBLE = 4; const PROXY_KIND_JS_STRING = 5; -const PROXY_KIND_JS_OBJECT = 6; -const PROXY_KIND_JS_PYPROXY = 7; +const PROXY_KIND_JS_OBJECT_EXISTING = 6; +const PROXY_KIND_JS_OBJECT = 7; +const PROXY_KIND_JS_PYPROXY = 8; class PythonError extends Error { constructor(exc_type, exc_details) { @@ -63,6 +64,7 @@ function proxy_js_init() { globalThis.proxy_js_ref = [globalThis, undefined]; globalThis.proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC; globalThis.proxy_js_ref_map = new Map(); + globalThis.proxy_js_ref_map.set(globalThis, 0); globalThis.proxy_js_map = new Map(); globalThis.proxy_js_existing = [undefined]; globalThis.pyProxyFinalizationRegistry = new FinalizationRegistry( @@ -99,12 +101,6 @@ function proxy_js_check_existing(c_ref) { // The `js_obj` argument cannot be `undefined`. // Returns an integer reference to the given `js_obj`. function proxy_js_add_obj(js_obj) { - // See if there is an existing JsProxy reference, and use that if there is. - const existing_ref = proxy_js_ref_map.get(js_obj); - if (existing_ref !== undefined) { - return existing_ref; - } - // Search for the first free slot in proxy_js_ref. while (proxy_js_ref_next < proxy_js_ref.length) { if (proxy_js_ref[proxy_js_ref_next] === undefined) { @@ -175,7 +171,7 @@ function proxy_call_python(target, argumentsList) { return ret; } -function proxy_convert_js_to_mp_obj_jsside(js_obj, out) { +function proxy_convert_js_to_mp_obj_jsside_helper(js_obj, out, allow_pyproxy) { let kind; if (js_obj === undefined) { kind = PROXY_KIND_JS_UNDEFINED; @@ -206,33 +202,35 @@ function proxy_convert_js_to_mp_obj_jsside(js_obj, out) { Module.setValue(out + 4, len, "i32"); Module.setValue(out + 8, buf, "i32"); } else if ( - js_obj instanceof PyProxy || - (typeof js_obj === "function" && "_ref" in js_obj) || - js_obj instanceof PyProxyThenable + allow_pyproxy && + (js_obj instanceof PyProxy || + (typeof js_obj === "function" && "_ref" in js_obj) || + js_obj instanceof PyProxyThenable) ) { kind = PROXY_KIND_JS_PYPROXY; Module.setValue(out + 4, js_obj._ref, "i32"); } else { - kind = PROXY_KIND_JS_OBJECT; - const id = proxy_js_add_obj(js_obj); + let id; + // See if there is an existing JsProxy reference, and use that if there is. + const existing_ref = proxy_js_ref_map.get(js_obj); + if (existing_ref !== undefined) { + kind = PROXY_KIND_JS_OBJECT_EXISTING; + id = existing_ref; + } else { + kind = PROXY_KIND_JS_OBJECT; + id = proxy_js_add_obj(js_obj); + } Module.setValue(out + 4, id, "i32"); } Module.setValue(out + 0, kind, "i32"); } +function proxy_convert_js_to_mp_obj_jsside(js_obj, out) { + proxy_convert_js_to_mp_obj_jsside_helper(js_obj, out, true); +} + function proxy_convert_js_to_mp_obj_jsside_force_double_proxy(js_obj, out) { - if ( - js_obj instanceof PyProxy || - (typeof js_obj === "function" && "_ref" in js_obj) || - js_obj instanceof PyProxyThenable - ) { - const kind = PROXY_KIND_JS_OBJECT; - const id = proxy_js_add_obj(js_obj); - Module.setValue(out + 4, id, "i32"); - Module.setValue(out + 0, kind, "i32"); - } else { - proxy_convert_js_to_mp_obj_jsside(js_obj, out); - } + proxy_convert_js_to_mp_obj_jsside_helper(js_obj, out, false); } function proxy_convert_mp_to_js_obj_jsside(value) { diff --git a/tests/ports/webassembly/heap_expand.mjs.exp b/tests/ports/webassembly/heap_expand.mjs.exp index 56341351492..67ebe98e7fe 100644 --- a/tests/ports/webassembly/heap_expand.mjs.exp +++ b/tests/ports/webassembly/heap_expand.mjs.exp @@ -1,27 +1,27 @@ -135241328 -135241296 -135241264 -135241232 -135241184 -135241136 -135241056 -135240912 -135240608 -135240080 -135239040 -135236976 -135232864 -135224656 -135208256 -135175472 -135109856 -134978768 -134716608 -135216752 -136217120 -138217808 -142219264 -150222192 +135241312 +135241280 +135241248 +135241216 +135241168 +135241120 +135241040 +135240896 +135240592 +135240064 +135239024 +135236960 +135232848 +135224640 +135208240 +135175456 +135109840 +134978752 +134716592 +135216800 +136217168 +138217984 +142219568 +150222816 1 2 4 diff --git a/tests/ports/webassembly/js_proxy_identity.mjs b/tests/ports/webassembly/js_proxy_identity.mjs index e279d219d21..ca2f3980af4 100644 --- a/tests/ports/webassembly/js_proxy_identity.mjs +++ b/tests/ports/webassembly/js_proxy_identity.mjs @@ -11,8 +11,15 @@ print("Object equality") print(js.Object == js.Object) print(js.Object.assign == js.Object.assign) +print("Object identity") +print(js.Object is js.Object) + print("Array equality") print(js.Array == js.Array) print(js.Array.prototype == js.Array.prototype) print(js.Array.prototype.push == js.Array.prototype.push) + +print("Array identity") +print(js.Array is js.Array) +print(js.Array.prototype is js.Array.prototype) `); diff --git a/tests/ports/webassembly/js_proxy_identity.mjs.exp b/tests/ports/webassembly/js_proxy_identity.mjs.exp index 5791d911baf..d8f1ae89139 100644 --- a/tests/ports/webassembly/js_proxy_identity.mjs.exp +++ b/tests/ports/webassembly/js_proxy_identity.mjs.exp @@ -2,7 +2,12 @@ Object equality True True +Object identity +True Array equality True True True +Array identity +True +True diff --git a/tests/ports/webassembly/js_proxy_reuse_free.mjs b/tests/ports/webassembly/js_proxy_reuse_free.mjs new file mode 100644 index 00000000000..ebca86f0bad --- /dev/null +++ b/tests/ports/webassembly/js_proxy_reuse_free.mjs @@ -0,0 +1,48 @@ +// Test reuse of JsProxy references and freeing of JsProxy objects. +// This ensures that a Python-side JsProxy that refers to a JavaScript object retains +// the correct JavaScript object in the case that another JsProxy that refers to the +// same JavaScript object is freed. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +globalThis.obj = [1, 2]; +globalThis.obj2 = [3, 4]; + +console.log("JS obj:", globalThis.obj); + +mp.runPython(` +import gc +import js + +# Create 2 proxies of the same JS object. +# They should refer to the same underlying JS-side reference. +obj = js.obj +obj_copy = js.obj +print(obj, obj_copy, obj == obj_copy) + +# Print out the object. +js.console.log("Py obj:", obj) + +# Forget obj_copy and trigger a GC when the Python code finishes. +obj_copy = None +gc.collect() +`); + +console.log("JS obj:", globalThis.obj); + +mp.runPython(` +# Create a new proxy of a different object. +# It should not clobber the existing obj proxy reference. +obj2 = js.obj2 + +# Create a copy of the existing obj proxy. +obj_copy = js.obj + +# Print the JS proxy, it should be the same reference as before. +print(obj, obj_copy, obj == obj_copy) + +# Print out the object. +js.console.log("Py obj:", obj) +`); + +console.log("JS obj:", globalThis.obj); diff --git a/tests/ports/webassembly/js_proxy_reuse_free.mjs.exp b/tests/ports/webassembly/js_proxy_reuse_free.mjs.exp new file mode 100644 index 00000000000..c74e4f49ee4 --- /dev/null +++ b/tests/ports/webassembly/js_proxy_reuse_free.mjs.exp @@ -0,0 +1,7 @@ +JS obj: [ 1, 2 ] + True +Py obj: [ 1, 2 ] +JS obj: [ 1, 2 ] + True +Py obj: [ 1, 2 ] +JS obj: [ 1, 2 ] From 293bb0e575052c29aeeae0ea53a41eb1c1a88db8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Sep 2025 16:12:54 +1000 Subject: [PATCH 1278/2098] webassembly: Remove MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK. With the parent commit implementing proper identities, this equality check option is no longer needed. Signed-off-by: Damien George --- ports/webassembly/mpconfigport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h index 0783aacbbcd..b13da2991e6 100644 --- a/ports/webassembly/mpconfigport.h +++ b/ports/webassembly/mpconfigport.h @@ -53,7 +53,6 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_USE_INTERNAL_PRINTF (0) -#define MICROPY_PY_BOUND_METHOD_FULL_EQUALITY_CHECK (1) #define MICROPY_EPOCH_IS_1970 (1) #define MICROPY_PY_ASYNCIO_TASK_QUEUE_PUSH_CALLBACK (1) From 70c4ffa77081385df6401be80d5b2e640a2ca976 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 21 Sep 2025 09:50:02 -0500 Subject: [PATCH 1279/2098] tools/ci.sh: Add `--bash-completion` option. Signed-off-by: Jeff Epler --- docs/develop/gettingstarted.rst | 16 ++++++++++++++++ tools/ci.sh | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index b625a72c98a..4cc818fda1b 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -306,6 +306,22 @@ As an example, you can build and test the unix minimal port with: $ tools/ci.sh unix_minimal_build unix_minimal_run_tests +If you use the bash shell, you can add a ``ci`` command with tab completion: + +.. code-block:: bash + + $ eval `tools/ci.sh --bash-completion` + $ ci unix_cov + +This will complete the ci step name to ``unix_coverage_``. +Pressing tab a second time will show the list of matching steps: + +.. code-block:: bash + + $ ci unix_coverage_ + unix_coverage_32bit_build + unix_coverage_32bit_run_native_mpy_tests… + Folder structure ---------------- diff --git a/tools/ci.sh b/tools/ci.sh index 3e34bd6fa86..6ecf8cc67cc 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -1005,11 +1005,22 @@ function _ci_help { exit } +function _ci_bash_completion { + echo "alias ci=\"$(readlink -f "$0")\"; complete -W '$(grep '^function ci_' $0 | awk '{print $2}' | sed 's/^ci_//')' ci" +} + function _ci_main { case "$1" in (-h|-?|--help) _ci_help ;; + (--bash-completion) + _ci_bash_completion + ;; + (-*) + echo "Unknown option: $1" 1>&2 + exit 1 + ;; (*) set -e cd $(dirname "$0")/.. From 7e5c55dc2f36022100fda3dc09063c4704251a12 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 21 Sep 2025 08:47:09 -0500 Subject: [PATCH 1280/2098] py/mkrules.mk: Add %.sz rule to print size of an object file. It's frequently the case that a developer will want to compare the object code size of various alternatives. When this can be done at the single object code level, the turnaround is faster. Provide a rule `$(BUILD)/%.sz` to print the size of a given object. Because it is a normal Makefile target that depends on an object file, it rebuilds the object file if needed. Signed-off-by: Jeff Epler --- py/mkrules.mk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/mkrules.mk b/py/mkrules.mk index 6993a13a4f1..4968ed58b03 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -108,6 +108,10 @@ $(BUILD)/%.pp: %.c FORCE $(ECHO) "PreProcess $<" $(Q)$(CPP) $(CFLAGS) -Wp,-C,-dD,-dI -o $@ $< +.PHONY: $(BUILD)/%.sz +$(BUILD)/%.sz: $(BUILD)/%.o + $(Q)$(SIZE) $< + # Special case for compiling auto-generated source files. $(BUILD)/%.o: $(BUILD)/%.c $(call compile_c) From 1921d22b35cb241eff5aa923325c53efbedf1dc2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 21 Sep 2025 09:51:01 -0500 Subject: [PATCH 1281/2098] docs/develop/gettingstarted: Document the additional unix test targets. The docs in the port README are nice but don't appear at docs.micropython.org. Signed-off-by: Jeff Epler --- docs/develop/gettingstarted.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index 4cc818fda1b..039be881e05 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -282,6 +282,19 @@ To run a selection of tests on a board/device connected over USB use: See also :ref:`writingtests`. +Additional make targets for developers +-------------------------------------- + +In ``ports/unix`` there are additional targets related to running tests: + +.. code-block:: bash + + $ make test//int # Run all tests matching the pattern "int" + $ make test/ports/unix # Run all tests in ports/unix + $ make test-failures # Re-run only the failed tests + $ make print-failures # print the differences for failed tests + $ make clean-failures # delete the .exp and .out files from failed tests + Using ci.sh locally ------------------- From adf63198849caa8c660249782caa368589ef7916 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 21 Sep 2025 09:53:27 -0500 Subject: [PATCH 1282/2098] docs/develop/gettingstarted: Document %.sz and %.pp targets. Signed-off-by: Jeff Epler --- docs/develop/gettingstarted.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index 039be881e05..cb479458e11 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -285,6 +285,21 @@ See also :ref:`writingtests`. Additional make targets for developers -------------------------------------- +In all ``make``-based ports, there is a target to print the size of a specific object file. +When a change is confined to a single file, this is useful when testing variations to find smaller alternatives. + +For instance, to print the size of ``objstr.o`` in the ``py/`` directory when making a unix standard build: + +.. code-block:: bash + + $ make build-standard/py/objstr.sz + +Similarly, there is a target to save the preprocessed version of a file: + +.. code-block:: bash + + $ make build-standard/py/objstr.pp + In ``ports/unix`` there are additional targets related to running tests: .. code-block:: bash From 4b013ec3ff688b8dc3af0cc9159a5fc4c27d8d20 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Aug 2025 08:53:14 -0500 Subject: [PATCH 1283/2098] extmod/vfs_reader: Check that open() resulted in a file-like object. That is, an object whose type defines the protocol slot. Note that due to protocol confusion, a variant of the original crasher that returned e.g., a machine.Pin instance could still lead to a crash (#17852). Fixes issue #17841. Signed-off-by: Jeff Epler Signed-off-by: Jeff Epler --- extmod/vfs_reader.c | 2 +- tests/micropython/builtin_execfile.py | 21 +++++++++++++++++++++ tests/micropython/builtin_execfile.py.exp | 3 +++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/extmod/vfs_reader.c b/extmod/vfs_reader.c index de5c4e03d3c..6c36770295b 100644 --- a/extmod/vfs_reader.c +++ b/extmod/vfs_reader.c @@ -83,7 +83,7 @@ void mp_reader_new_file(mp_reader_t *reader, qstr filename) { }; mp_obj_t file = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map); - const mp_stream_p_t *stream_p = mp_get_stream(file); + const mp_stream_p_t *stream_p = mp_get_stream_raise(file, MP_STREAM_OP_READ); int errcode = 0; #if MICROPY_VFS_ROM diff --git a/tests/micropython/builtin_execfile.py b/tests/micropython/builtin_execfile.py index 75a867bb940..4fd4d66d4ee 100644 --- a/tests/micropython/builtin_execfile.py +++ b/tests/micropython/builtin_execfile.py @@ -75,3 +75,24 @@ def open(self, file, mode): # Unmount the VFS object. vfs.umount(fs) + + +class EvilFilesystem: + def mount(self, readonly, mkfs): + print("mount", readonly, mkfs) + + def umount(self): + print("umount") + + def open(self, file, mode): + return None + + +fs = EvilFilesystem() +vfs.mount(fs, "/test_mnt") +try: + execfile("/test_mnt/test.py") + print("ExecFile succeeded") +except OSError: + print("OSError") +vfs.umount(fs) diff --git a/tests/micropython/builtin_execfile.py.exp b/tests/micropython/builtin_execfile.py.exp index 49703d57076..d93dee547b6 100644 --- a/tests/micropython/builtin_execfile.py.exp +++ b/tests/micropython/builtin_execfile.py.exp @@ -5,3 +5,6 @@ open /test.py rb 123 TypeError umount +mount False False +OSError +umount From 0a41838becec8af2d80b90388072090ef2d0a4ad Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 26 Sep 2025 09:17:47 -0500 Subject: [PATCH 1284/2098] extmod/modopenamp: Check that mp_vfs_open actually returned a stream. Signed-off-by: Jeff Epler --- extmod/modopenamp_remoteproc_store.c | 1 + 1 file changed, 1 insertion(+) diff --git a/extmod/modopenamp_remoteproc_store.c b/extmod/modopenamp_remoteproc_store.c index 03022ebc835..7c2cee1099c 100644 --- a/extmod/modopenamp_remoteproc_store.c +++ b/extmod/modopenamp_remoteproc_store.c @@ -76,6 +76,7 @@ static int openamp_remoteproc_store_open(void *store, const char *path, const vo openamp_remoteproc_filestore_t *fstore = store; fstore->file = mp_vfs_open(MP_ARRAY_SIZE(args), args, (mp_map_t *)&mp_const_empty_map); + (void)mp_get_stream_raise(fstore->file, MP_STREAM_OP_READ); int error = 0; mp_uint_t bytes = mp_stream_read_exactly(fstore->file, fstore->buf, RPROC_FILE_STORE_BUF_SIZE, &error); From 2d08f2f6311739c26ed3e15d10d9c03d7cabfe9f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 20 Sep 2025 11:48:23 -0500 Subject: [PATCH 1285/2098] extmod/moductypes: Error if small ints are not big enough. Signed-off-by: Jeff Epler --- extmod/moductypes.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index eb72f441bbb..ed865f4263f 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -28,6 +28,7 @@ #include #include +#include "py/smallint.h" #include "py/runtime.h" #include "py/objtuple.h" #include "py/binary.h" @@ -94,6 +95,17 @@ static MP_NORETURN void syntax_error(void) { } static mp_obj_t uctypes_struct_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // Because mpy-cross turns an expression like `uctypes.INT8` into a single + // constant integer load, the uctypes constant values must be consistent, no + // matter the OBJ_REPR and mp_int_t type. + // + // However, these constants are 31 bits (counting the sign bit) while + // OBJ_REPR_B with 32-bit mp_int_t provides only 30 bits of small integer, so + // this combination is unsupported. + // + // For more information, see https://github.com/micropython/micropython/issues/18105 + MP_STATIC_ASSERT(MP_SMALL_INT_BITS >= 31); + mp_arg_check_num(n_args, n_kw, 2, 3, false); mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, type); o->addr = (void *)(uintptr_t)mp_obj_get_int_truncated(args[0]); From f6dcf35d5c685ff0594025b992679bebb8949a2e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 18:28:35 +0100 Subject: [PATCH 1286/2098] tests/basics/int_big_to_small.py: Bifurcate test using small-int-max. Different results are needed for different integer sizes. Signed-off-by: Jeff Epler --- tests/basics/int_big_to_small.py | 11 ++++++++++ tests/basics/int_big_to_small_int29.py | 22 +++++++++++++++++++ tests/basics/int_big_to_small_int29.py.exp | 25 ++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/basics/int_big_to_small_int29.py create mode 100644 tests/basics/int_big_to_small_int29.py.exp diff --git a/tests/basics/int_big_to_small.py b/tests/basics/int_big_to_small.py index 64280d0c635..c92d263fc28 100644 --- a/tests/basics/int_big_to_small.py +++ b/tests/basics/int_big_to_small.py @@ -5,6 +5,17 @@ print("SKIP") raise SystemExit +# Skip this test on "REPR B" where (1<<29 + 1) is not a small int. +k = 1 << 29 +micropython.heap_lock() +try: + k = k + 1 +except MemoryError: + print("SKIP") + raise SystemExit +finally: + micropython.heap_unlock() + # All less than small int max. for d in (0, 27, 1<<29, -1861, -(1<<29)): i = 1<<70 diff --git a/tests/basics/int_big_to_small_int29.py b/tests/basics/int_big_to_small_int29.py new file mode 100644 index 00000000000..438a74d2bc6 --- /dev/null +++ b/tests/basics/int_big_to_small_int29.py @@ -0,0 +1,22 @@ +try: + import micropython + micropython.heap_lock +except: + print("SKIP") + raise SystemExit + +# All less than small int max. +for d in (0, 27, 1<<28, -1861, -(1<<28)): + i = 1<<70 + print(i) + j = (1<<70) + d + print(j) + # k should now be a small int. + k = j - i + print(k) + + # Now verify that working with k doesn't allocate (i.e. it's a small int). + micropython.heap_lock() + print(k + 20) + print(k // 20) + micropython.heap_unlock() diff --git a/tests/basics/int_big_to_small_int29.py.exp b/tests/basics/int_big_to_small_int29.py.exp new file mode 100644 index 00000000000..0920101924a --- /dev/null +++ b/tests/basics/int_big_to_small_int29.py.exp @@ -0,0 +1,25 @@ +1180591620717411303424 +1180591620717411303424 +0 +20 +0 +1180591620717411303424 +1180591620717411303451 +27 +47 +1 +1180591620717411303424 +1180591620717679738880 +268435456 +268435476 +13421772 +1180591620717411303424 +1180591620717411301563 +-1861 +-1841 +-94 +1180591620717411303424 +1180591620717142867968 +-268435456 +-268435436 +-13421773 From fca5bbf664220599c7ed82f276f10996249fe8e5 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 18:28:55 +0100 Subject: [PATCH 1287/2098] tests: Use OBJ_REPR_B compatible mpy file headers. So these tests can run and pass using OBJ_REPR_B. Signed-off-by: Jeff Epler --- tests/extmod/vfs_rom.py | 2 +- tests/micropython/import_mpy_native.py | 5 +++-- tests/perf_bench/core_import_mpy_multi.py | 4 ++-- tests/perf_bench/core_import_mpy_single.py | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index cd14542ea6b..18ae1f5cf96 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -25,7 +25,7 @@ # An mpy file with four constant objects: str, bytes, long-int, float. test_mpy = ( # header - b"M\x06\x00\x1f" # mpy file header + b"M\x06\x00\x1e" # mpy file header, -msmall-int-bits=30 b"\x06" # n_qstr b"\x05" # n_obj # qstrs diff --git a/tests/micropython/import_mpy_native.py b/tests/micropython/import_mpy_native.py index ac5e724e8d4..7a9bb8eddb9 100644 --- a/tests/micropython/import_mpy_native.py +++ b/tests/micropython/import_mpy_native.py @@ -53,11 +53,12 @@ def open(self, path, mode): # these are the test .mpy files -valid_header = bytes([77, 6, mpy_arch, 31]) +small_int_bits = 30 +valid_header = bytes([77, 6, mpy_arch, small_int_bits]) # fmt: off user_files = { # bad architecture (mpy_arch needed for sub-version) - '/mod0.mpy': bytes([77, 6, 0xfc | mpy_arch, 31]), + '/mod0.mpy': bytes([77, 6, 0xfc | mpy_arch, small_int_bits]), # test loading of viper and asm '/mod1.mpy': valid_header + ( diff --git a/tests/perf_bench/core_import_mpy_multi.py b/tests/perf_bench/core_import_mpy_multi.py index 8affa157fa0..67deec05088 100644 --- a/tests/perf_bench/core_import_mpy_multi.py +++ b/tests/perf_bench/core_import_mpy_multi.py @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -# This is the test.py file that is compiled to test.mpy below. +# This is the test.py file that is compiled to test.mpy below. mpy-cross must be invoked with `-msmall-int-bits=30`. """ class A: def __init__(self, arg): @@ -23,7 +23,7 @@ def f(): x = ("const tuple", None, False, True, 1, 2, 3) result = 123 """ -file_data = b'M\x06\x00\x1f\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00\x0cresult\x00/-5#\x82I\x81{\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x06arg\x00\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\n\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x04Qc\x02\x81d\x00\x08\x02(DD\x11\x05\x16\x06\x10\x02\x16\x072\x00\x16\x082\x01\x16\t2\x02\x16\nQc\x03`\x1a\x08\x08\x12\x13@\xb1\xb0\x18\x13Qc@\t\x08\t\x12` Qc@\t\x08\n\x12``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0b\x12\x0c\x12\r\x12\x0e*\x04Y\x12\x0f\x12\x10\x12\x11*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' +file_data = b'M\x06\x00\x1e\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00\x0cresult\x00/-5#\x82I\x81{\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x06arg\x00\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\n\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x04Qc\x02\x81d\x00\x08\x02(DD\x11\x05\x16\x06\x10\x02\x16\x072\x00\x16\x082\x01\x16\t2\x02\x16\nQc\x03`\x1a\x08\x08\x12\x13@\xb1\xb0\x18\x13Qc@\t\x08\t\x12` Qc@\t\x08\n\x12``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0b\x12\x0c\x12\r\x12\x0e*\x04Y\x12\x0f\x12\x10\x12\x11*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' class File(io.IOBase): diff --git a/tests/perf_bench/core_import_mpy_single.py b/tests/perf_bench/core_import_mpy_single.py index 4d9aa67bf2f..f472bb64762 100644 --- a/tests/perf_bench/core_import_mpy_single.py +++ b/tests/perf_bench/core_import_mpy_single.py @@ -8,7 +8,7 @@ print("SKIP") raise SystemExit -# This is the test.py file that is compiled to test.mpy below. +# This is the test.py file that is compiled to test.mpy below. mpy-cross must be invoked with `-msmall-int-bits=30`. # Many known and unknown names/strings are included to test the linking process. """ class A0: @@ -78,7 +78,7 @@ def f1(): x = ("const tuple 9", None, False, True, 1, 2, 3) result = 123 """ -file_data = b"M\x06\x00\x1f\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x0cresult\x00/-5\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\n\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\x06Qc\x04\x82\x0c\x00\n\x02($$$\x11\x07\x16\x08\x10\x02\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04@\t\x08\n\x81\x0b Qc@\t\x08\x0b\x81\x0b@Qc@\t\x08\x0c\x81\x0b`QcH\t\n\r\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x07\x16\x08\x10\x03\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04H\t\n\n\x81\x0b``QcH\t\n\x0b\x81\x0b\x80\x07QcH\t\n\x0c\x81\x0b\x80\x08QcH\t\n\r\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x08\x12\x07*\x08Y\x12\x1b\x12\x1c\x12\t\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" +file_data = b"M\x06\x00\x1e\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x0cresult\x00/-5\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\n\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\x06Qc\x04\x82\x0c\x00\n\x02($$$\x11\x07\x16\x08\x10\x02\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04@\t\x08\n\x81\x0b Qc@\t\x08\x0b\x81\x0b@Qc@\t\x08\x0c\x81\x0b`QcH\t\n\r\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x07\x16\x08\x10\x03\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04H\t\n\n\x81\x0b``QcH\t\n\x0b\x81\x0b\x80\x07QcH\t\n\x0c\x81\x0b\x80\x08QcH\t\n\r\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x08\x12\x07*\x08Y\x12\x1b\x12\x1c\x12\t\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" class File(io.IOBase): From e35ac67421f71c430ce7a0b26532b048aa4528cd Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 18:43:04 +0100 Subject: [PATCH 1288/2098] tests/stress/fun_call_limit.py: Allow to run with OBJ_REPR_B. Signed-off-by: Jeff Epler --- tests/stress/fun_call_limit.py | 10 +++++++--- tests/stress/fun_call_limit.py.exp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/stress/fun_call_limit.py b/tests/stress/fun_call_limit.py index b802aadd558..69f8aa5aec4 100644 --- a/tests/stress/fun_call_limit.py +++ b/tests/stress/fun_call_limit.py @@ -16,14 +16,16 @@ def test(n): # If the port has at least 32-bits then this test should pass. -print(test(29)) +print(test(28)) # This test should fail on all ports (overflows a small int). print(test(70)) -# Check that there is a correct transition to the limit of too many args before *args. +# 28 is the biggest number that will pass on a 32-bit port using object +# representation B, which has 1<<28 still fitting in a positive small int. reached_limit = False -for i in range(30, 70): +any_test_succeeded = False +for i in range(28, 70): result = test(i) if reached_limit: if result != "SyntaxError": @@ -34,3 +36,5 @@ def test(n): else: if result != i + 4: print("FAIL") + any_test_succeeded = True +assert any_test_succeeded # At least one iteration must have passed diff --git a/tests/stress/fun_call_limit.py.exp b/tests/stress/fun_call_limit.py.exp index 53d2b280430..491abbfa8be 100644 --- a/tests/stress/fun_call_limit.py.exp +++ b/tests/stress/fun_call_limit.py.exp @@ -1,2 +1,2 @@ -33 +32 SyntaxError From f658b0d862cb4eafe8df5bd22cb1302d83da4938 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 18:49:51 +0100 Subject: [PATCH 1289/2098] py/objint: Fix converting float to int with OBJ_REPR_B. The check for 'fits in a small int' is specific to the 31-bit int of other object representations. Signed-off-by: Jeff Epler --- py/objint.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/objint.c b/py/objint.c index fd9ab106f92..901264aca65 100644 --- a/py/objint.c +++ b/py/objint.c @@ -117,9 +117,15 @@ static mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { } // 8 * sizeof(uintptr_t) counts the number of bits for a small int // TODO provide a way to configure this properly + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B + if (e <= ((8 * sizeof(uintptr_t) + MP_FLOAT_EXP_BIAS - 4) << MP_FLOAT_EXP_SHIFT_I32)) { + return MP_FP_CLASS_FIT_SMALLINT; + } + #else if (e <= ((8 * sizeof(uintptr_t) + MP_FLOAT_EXP_BIAS - 3) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_SMALLINT; } + #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG if (e <= (((sizeof(long long) * MP_BITS_PER_BYTE) + MP_FLOAT_EXP_BIAS - 2) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_LONGINT; From e3ef68215605938c906196ae37120950d0eb6105 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 15 Jul 2025 18:51:25 +0100 Subject: [PATCH 1290/2098] github/workflows: Add 32-bit OBJ_REPR_B CI job. This showed up some interesting errors (hopefully all fixed now). Signed-off-by: Jeff Epler --- .github/workflows/ports_unix.yml | 14 ++++++++++++++ tools/ci.sh | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 77d0b29ca96..eeae895f6a2 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -217,6 +217,20 @@ jobs: if: failure() run: tests/run-tests.py --print-failures + repr_b: + runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 + steps: + - uses: actions/checkout@v5 + - name: Install packages + run: source tools/ci.sh && ci_unix_32bit_setup + - name: Build + run: source tools/ci.sh && ci_unix_repr_b_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_repr_b_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures + macos: runs-on: macos-latest steps: diff --git a/tools/ci.sh b/tools/ci.sh index 6ecf8cc67cc..f55b004c45c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -556,6 +556,14 @@ CI_UNIX_OPTS_SANITIZE_UNDEFINED=( LDFLAGS_EXTRA="-fsanitize=undefined -fno-sanitize=nonnull-attribute" ) +CI_UNIX_OPTS_REPR_B=( + VARIANT=standard + CFLAGS_EXTRA="-DMICROPY_OBJ_REPR=MICROPY_OBJ_REPR_B -DMICROPY_PY_UCTYPES=0 -Dmp_int_t=int32_t -Dmp_uint_t=uint32_t" + MICROPY_FORCE_32BIT=1 + RUN_TESTS_MPY_CROSS_FLAGS="--mpy-cross-flags=\"-march=x86 -msmall-int-bits=30\"" + +) + function ci_unix_build_helper { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix "$@" submodules @@ -898,6 +906,17 @@ function ci_unix_qemu_riscv64_run_tests { (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') } +function ci_unix_repr_b_build { + ci_unix_build_helper "${CI_UNIX_OPTS_REPR_B[@]}" + ci_unix_build_ffi_lib_helper gcc -m32 +} + +function ci_unix_repr_b_run_tests { + # ci_unix_run_tests_full_no_native_helper is not used due to + # https://github.com/micropython/micropython/issues/18105 + ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" +} + ######################################################################################## # ports/windows From beb4c3188f5c26ede14134db1d34f817793b7e46 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 18 Sep 2025 23:55:36 +1000 Subject: [PATCH 1291/2098] py/runtime: Support importing a method from an instance. This change follows CPython behaviour, allowing use of: from instance import method to import a bound method from a class instance, eg registered via setting `sys.modules["instance"] = instance`. Admittedly this is probably a very rarely used pattern in Python, but it resolves a long standing comment about whether or not this is actually possible (it turns out it is possible!). A test is added to show how it works. The main reason for this change is to fix a problem with imports in the webassembly port: prior to this fix, it was not possible to do `from js_module import function`, where `js_module` is a JavaScript object registered to be visible to Python through the webassembly API function `registerJsModule(js_module)`. But now with this fix that is possible. Signed-off-by: Damien George --- py/runtime.c | 14 +++++----- tests/basics/import_instance_method.py | 38 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 tests/basics/import_instance_method.py diff --git a/py/runtime.c b/py/runtime.c index 0ab0626ef94..61aeb83f8d4 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1562,14 +1562,11 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { mp_obj_t dest[2]; mp_load_method_maybe(module, name, dest); - if (dest[1] != MP_OBJ_NULL) { - // Hopefully we can't import bound method from an object - import_error: - mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name); - } - - if (dest[0] != MP_OBJ_NULL) { + // Importing a bound method from a class instance. + return mp_obj_new_bound_meth(dest[0], dest[1]); + } else if (dest[0] != MP_OBJ_NULL) { + // Importing a function or attribute. return dest[0]; } @@ -1602,6 +1599,9 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { goto import_error; #endif + +import_error: + mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name); } void mp_import_all(mp_obj_t module) { diff --git a/tests/basics/import_instance_method.py b/tests/basics/import_instance_method.py new file mode 100644 index 00000000000..d25b70ac5f0 --- /dev/null +++ b/tests/basics/import_instance_method.py @@ -0,0 +1,38 @@ +# Test importing a method from a class instance. +# This is not a common thing to do, but ensures MicroPython has the same semantics as CPython. + +import sys + +if not hasattr(sys, "modules"): + print("SKIP") + raise SystemExit + + +class A: + def __init__(self, value): + self.value = value + + def meth(self): + return self.value + + def meth_with_arg(self, a): + return [self.value, a] + + +# Register a class instance as the module "mod". +sys.modules["mod"] = A(1) + +# Try importing it as a module. +import mod + +print(mod.meth()) +print(mod.meth_with_arg(2)) + +# Change the module. +sys.modules["mod"] = A(3) + +# Try importing it using "from ... import". +from mod import meth, meth_with_arg + +print(meth()) +print(meth_with_arg(4)) From 68ca22bbc882cda735d10f921769beff8aa60d77 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 19 Sep 2025 00:02:54 +1000 Subject: [PATCH 1292/2098] tests/ports/webassembly: Expand test for registerJsModule. This tests `from mod import foo` where `mod` is a module registered using the webassembly API `registerJsModule(mod)`, and where `foo` is a JavaScript function. Prior to the parent commit, this would fail. Signed-off-by: Damien George --- tests/ports/webassembly/register_js_module.js | 39 +++++++++++++++++++ .../webassembly/register_js_module.js.exp | 10 +++++ 2 files changed, 49 insertions(+) diff --git a/tests/ports/webassembly/register_js_module.js b/tests/ports/webassembly/register_js_module.js index b512f2c0dd4..f58d12e50d3 100644 --- a/tests/ports/webassembly/register_js_module.js +++ b/tests/ports/webassembly/register_js_module.js @@ -1,6 +1,45 @@ +// Test the registerJsModule() public API method. + import(process.argv[2]).then((mp) => { mp.loadMicroPython().then((py) => { + // Simple module. py.registerJsModule("js_module", { y: 2 }); py.runPython("import js_module; print(js_module); print(js_module.y)"); + + // Module with functions. + // In particular test how "this" behaves. + py.registerJsModule("js_module2", { + yes: true, + add1(x) { + return x + 1; + }, + getThis() { + return this; + }, + }); + + console.log("===="); + + // Test using simple import. + py.runPython(` +import js_module2 + +print(js_module2.yes) +print(js_module2.add1(1)) +print(js_module2.getThis()) +print(js_module2.getThis().yes) +`); + + console.log("===="); + + // Test using "from ... import". + py.runPython(` +from js_module2 import yes, add1, getThis + +print(yes) +print(add1(2)) +print(getThis()) +print(getThis().yes) +`); }); }); diff --git a/tests/ports/webassembly/register_js_module.js.exp b/tests/ports/webassembly/register_js_module.js.exp index 6e2bad3ce56..34bfd345d17 100644 --- a/tests/ports/webassembly/register_js_module.js.exp +++ b/tests/ports/webassembly/register_js_module.js.exp @@ -1,2 +1,12 @@ 2 +==== +True +2 + +True +==== +True +3 + +True From 094000d418e9f7393b3d34814c3e43e6386145a7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 28 Sep 2025 22:16:36 +1000 Subject: [PATCH 1293/2098] webassembly/objjsproxy: Fix logic that determines if asyncio is active. `cur_task` can never be `None` in the webassembly port, so test it for the top-level task to see if an asyncio Task is active or not. This fixes a bug where await'ing on a JavaScript awaitable that ends up raising an error would not be caught on the Python side. The fix here makes sure it is caught by Python, as tested by the new test. Signed-off-by: Damien George --- ports/webassembly/asyncio/core.py | 3 +- ports/webassembly/objjsproxy.c | 3 +- .../webassembly/asyncio_top_level_await.mjs | 32 +++++++++++++++++++ .../asyncio_top_level_await.mjs.exp | 6 ++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/ports/webassembly/asyncio/core.py b/ports/webassembly/asyncio/core.py index 47846fc25a3..1d0124a6a21 100644 --- a/ports/webassembly/asyncio/core.py +++ b/ports/webassembly/asyncio/core.py @@ -243,8 +243,7 @@ def get_event_loop(): def current_task(): - if cur_task is None: - raise RuntimeError("no running event loop") + assert cur_task is not None return cur_task diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c index 2d46702ff8e..93c40d8356b 100644 --- a/ports/webassembly/objjsproxy.c +++ b/ports/webassembly/objjsproxy.c @@ -566,7 +566,8 @@ static mp_obj_t jsproxy_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { // decouples the task from the thenable and allows cancelling the task. if (mp_asyncio_context != MP_OBJ_NULL) { mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task)); - if (cur_task != mp_const_none) { + mp_obj_t top_level_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__top_level_task)); + if (cur_task != top_level_task) { mp_obj_t thenable_event_class = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_ThenableEvent)); mp_obj_t thenable_event = mp_call_function_1(thenable_event_class, self_in); mp_obj_t dest[2]; diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs b/tests/ports/webassembly/asyncio_top_level_await.mjs index 234b7a6ce6f..449985b493c 100644 --- a/tests/ports/webassembly/asyncio_top_level_await.mjs +++ b/tests/ports/webassembly/asyncio_top_level_await.mjs @@ -88,3 +88,35 @@ print("top-level end") `); console.log("finished"); + +/**********************************************************/ +// Top-level await's on a JavaScript function that throws. + +console.log("= TEST 4 =========="); + +globalThis.jsFail = async () => { + console.log("jsFail"); + throw new Error("jsFail"); +}; + +await mp.runPythonAsync(` +import asyncio +import js + +# Test top-level catching from a failed JS await. +try: + await js.jsFail() +except Exception as er: + print("caught exception:", type(er), type(er.args[0]), er.args[1:]) + +async def main(): + try: + await js.jsFail() + except Exception as er: + print("caught exception:", type(er), type(er.args[0]), er.args[1:]) + +# Test top-level waiting on a coro that catches. +await main() +`); + +console.log("finished"); diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp index 66fefd2dcef..90c817f3df7 100644 --- a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp +++ b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp @@ -17,3 +17,9 @@ top-level wait task task end top-level end finished += TEST 4 ========== +jsFail +caught exception: ('Error', 'jsFail') +jsFail +caught exception: ('Error', 'jsFail') +finished From a563592b111686dde9b46785bb783d45ec0ba78e Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 28 Sep 2025 22:28:27 +1000 Subject: [PATCH 1294/2098] webassembly/asyncio: Fix ThenableEvent to handle rejected thenables. Prior to this fix, if a JavaScript thenable/Promise that was part of an asyncio chain was rejected it would be ignored because the Python-side `ThenableEvent` did not register a handler for the rejection. That's fixed by this commit, and a corresponding test added. Signed-off-by: Damien George --- ports/webassembly/asyncio/core.py | 16 +++++++++++++--- .../webassembly/asyncio_top_level_await.mjs | 4 ++++ .../webassembly/asyncio_top_level_await.mjs.exp | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ports/webassembly/asyncio/core.py b/ports/webassembly/asyncio/core.py index 1d0124a6a21..9d07bd7b858 100644 --- a/ports/webassembly/asyncio/core.py +++ b/ports/webassembly/asyncio/core.py @@ -79,9 +79,8 @@ def send(value): class ThenableEvent: def __init__(self, thenable): - self.result = None # Result of the thenable self.waiting = None # Task waiting on completion of this thenable - thenable.then(self.set) + thenable.then(self.set, self.cancel) def set(self, value=None): # Thenable/Promise is fulfilled, set result and schedule any waiting task. @@ -90,6 +89,15 @@ def set(self, value=None): _task_queue.push(self.waiting) self.waiting = None + def cancel(self, value=None): + # Thenable/Promise is rejected, set error and schedule any waiting task. + self.error = jsffi.JsException( + value, getattr(value, "name", None), getattr(value, "message", None) + ) + if self.waiting: + _task_queue.push(self.waiting) + self.waiting = None + def remove(self, task): self.waiting = None @@ -101,7 +109,9 @@ def wait(self): cur_task.data = self # Wait for the thenable to fulfill. yield - # Return the result of the thenable. + # Raise the error, or return the result, of the thenable. + if hasattr(self, "error"): + raise self.error return self.result diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs b/tests/ports/webassembly/asyncio_top_level_await.mjs index 449985b493c..fedf10d9c5d 100644 --- a/tests/ports/webassembly/asyncio_top_level_await.mjs +++ b/tests/ports/webassembly/asyncio_top_level_await.mjs @@ -117,6 +117,10 @@ async def main(): # Test top-level waiting on a coro that catches. await main() + +# Test top-level waiting on a task that catches. +t = asyncio.create_task(main()) +await t `); console.log("finished"); diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp index 90c817f3df7..f6720b5aba7 100644 --- a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp +++ b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp @@ -22,4 +22,6 @@ jsFail caught exception: ('Error', 'jsFail') jsFail caught exception: ('Error', 'jsFail') +jsFail +caught exception: ('Error', 'jsFail') finished From 3dd8073c290c077f17ffdee17a019763ad82604d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 29 Aug 2025 08:06:27 +1000 Subject: [PATCH 1295/2098] tools/mpremote: Workaround ESP DTR/RTS reset quirk at disconnect time. The problem with ESP board spurious reset happens at disconnect time on Windows (clearing DTR before RTS triggers a reset). Previous workarounds tried to detect possible ESP boards and apply the correct DTR and RTS settings when opening the port. Instead, we can manually clear RTS before closing the port and thereby avoid the reset issue. Opening the port can keep the default behaviour (RTS & DTR both set). close() is called from a finally block in the mpremote main module (via do_disconnect()) - so this should always happen provided the Python process isn't terminated by the OS. One additional workaround is needed to prevent a spurious reset first time a Silicon Labs CP210x-based ESP board is opened by mpremote after enumeration. Signed-off-by: Angus Gratton --- tools/mpremote/mpremote/transport_serial.py | 60 +++++++++------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index a26183680ca..eccdd4c1ea4 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -42,31 +42,8 @@ from .console import VT_ENABLED from .transport import TransportError, TransportExecError, Transport -VID_ESPRESSIF = 0x303A # Espressif Incorporated -PID_ESPRESSIF_SERIAL_JTAG = 0x1001 # Serial/JTAG peripheral of ESP32-S3,C3,C6 - -def has_espressif_dtr_quirk(devicename): - """ESP8266 and ESP32 dev boards use the DTR and RTS lines to trigger reset & - reset into bootloader mode. This can causes spurious reset issues on Windows. - - Apply the quirk to any USB/Serial chip on Windows that isn't using the - Microsoft CDC-ACM driver, or to the integrated Espressif Serial/JTAG device. - - Don't apply it to Espressif boards running TinyUSB, as TinyUSB uses DTR - to determine if the CDC port is open (and there's no spurious reset issue). - """ - portinfo = list(serial.tools.list_ports.grep(devicename)) # type: ignore - if not portinfo: - return False - - def port_attr(name): - return getattr(portinfo[0], name, None) - - return (port_attr("vid"), port_attr("pid")) == ( - VID_ESPRESSIF, - PID_ESPRESSIF_SERIAL_JTAG, - ) or port_attr("manufacturer") != "Microsoft" +VID_SILICON_LABS = 0x10C4 class SerialTransport(Transport): @@ -90,19 +67,27 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None delayed = False for attempt in range(wait + 1): try: - if device.startswith("rfc2217://"): - self.serial = serial.serial_for_url(device, **serial_kwargs) - elif os.name == "nt": - self.serial = serial.Serial(**serial_kwargs) - self.serial.port = device - if has_espressif_dtr_quirk(device): - # DTR False: to avoid using the reset button will hang the MCU in bootloader mode - # RTS False: to prevent pulses on rts on serial.close() that would POWERON_RESET an ESPxx - self.serial.dtr = False # DTR False = gpio0 High = Normal boot - self.serial.rts = False # RTS False = EN High = MCU enabled + self.serial = serial.serial_for_url(device, do_not_open=True, **serial_kwargs) + if os.name == "nt": + portinfo = list(serial.tools.list_ports.grep(device)) # type: ignore + if portinfo and getattr(portinfo[0], "vid", None) == VID_SILICON_LABS: + # Silicon Labs CP210x driver on Windows has a quirk + # where after a power on reset it will set DTR and RTS + # at different times when the port is opened (it doesn't + # happen on subsequent openings). + # + # To avoid issues with spurious reset on Espressif boards we clear DTR and RTS, + # open the port, and then set them in an order which prevents triggering a reset. + self.serial.dtr = False + self.serial.rts = False + self.serial.open() + self.serial.dtr = True + self.serial.rts = True + + # On all other host/driver combinations we keep the default + # behaviour (pyserial will set DTR and RTS automatically on open) + if not self.serial.isOpen(): self.serial.open() - else: - self.serial = serial.Serial(device, **serial_kwargs) break except OSError: if wait == 0: @@ -121,6 +106,9 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None print("") def close(self): + # ESP Windows quirk: Prevent target from resetting when Windows clears DTR before RTS + self.serial.rts = False + self.serial.dtr = False self.serial.close() def read_until( From a80913292153a14424b29bdb9ca8847e8d35cf73 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 23 Jul 2025 16:14:22 -0500 Subject: [PATCH 1296/2098] py: Add MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC. Most MCUs apart from Cortex-M0 with Thumb 1 have an instruction for computing the "high part" of a multiplication (e.g., the upper 32 bits of a 32x32 multiply). When they do, gcc uses this to implement a small and fast overflow check using the __builtin_mul_overflow intrinsic, which is preferable to the guard division method previously used in smallint.c. However, in contrast to the previous mp_small_int_mul_overflow routine, which checks that the result fits not only within mp_int_t but is SMALL_INT_FITS(), __builtin_mul_overflow only checks for overflow of the C type. As a result, a slight change in the code flow is needed for MP_BINARY_OP_MULTIPLY. Other sites using mp_small_int_mul_overflow already had the result value flow through to a SMALL_INT_FITS check so they didn't need any additional changes. Do similarly for the _ll and _ull multiply overflows checks. Signed-off-by: Jeff Epler --- py/misc.h | 49 ++++++++++++++----------------------- py/mpconfig.h | 19 +++++++++++++++ py/parsenum.c | 3 ++- py/runtime.c | 27 +++++---------------- py/runtime_utils.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ py/smallint.c | 29 ---------------------- py/smallint.h | 4 ---- 7 files changed, 105 insertions(+), 86 deletions(-) diff --git a/py/misc.h b/py/misc.h index 081163cadf9..ac5e8fb0edf 100644 --- a/py/misc.h +++ b/py/misc.h @@ -35,7 +35,11 @@ #include #include #include +#if __cplusplus // Required on at least one compiler to get ULLONG_MAX +#include +#else #include +#endif typedef unsigned char byte; typedef unsigned int uint; @@ -454,7 +458,7 @@ static inline uint32_t mp_clz_mpi(mp_int_t x) { #endif } -// Overflow-checked operations for long long +// Overflow-checked operations // Integer overflow builtins were added to GCC 5, but __has_builtin only in GCC 10 // @@ -462,45 +466,28 @@ static inline uint32_t mp_clz_mpi(mp_int_t x) { // functions below don't update the result if an overflow would occur (to avoid UB). #define MP_GCC_HAS_BUILTIN_OVERFLOW (__GNUC__ >= 5) -#if __has_builtin(__builtin_umulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW +#if MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC + #define mp_mul_ull_overflow __builtin_umulll_overflow +#define mp_mul_ll_overflow __builtin_smulll_overflow +static inline bool mp_mul_mp_int_t_overflow(mp_int_t x, mp_int_t y, mp_int_t *res) { + // __builtin_mul_overflow is a type-generic function, this inline ensures the argument + // types are checked to match mp_int_t. + return __builtin_mul_overflow(x, y, res); +} + #else -inline static bool mp_mul_ull_overflow(unsigned long long int x, unsigned long long int y, unsigned long long int *res) { + +bool mp_mul_ll_overflow(long long int x, long long int y, long long int *res); +bool mp_mul_mp_int_t_overflow(mp_int_t x, mp_int_t y, mp_int_t *res); +static inline bool mp_mul_ull_overflow(unsigned long long int x, unsigned long long int y, unsigned long long int *res) { if (y > 0 && x > (ULLONG_MAX / y)) { return true; // overflow } *res = x * y; return false; } -#endif - -#if __has_builtin(__builtin_smulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW -#define mp_mul_ll_overflow __builtin_smulll_overflow -#else -inline static bool mp_mul_ll_overflow(long long int x, long long int y, long long int *res) { - bool overflow; - // Check for multiply overflow; see CERT INT32-C - if (x > 0) { // x is positive - if (y > 0) { // x and y are positive - overflow = (x > (LLONG_MAX / y)); - } else { // x positive, y nonpositive - overflow = (y < (LLONG_MIN / x)); - } // x positive, y nonpositive - } else { // x is nonpositive - if (y > 0) { // x is nonpositive, y is positive - overflow = (x < (LLONG_MIN / y)); - } else { // x and y are nonpositive - overflow = (x != 0 && y < (LLONG_MAX / x)); - } // End if x and y are nonpositive - } // End if x is nonpositive - - if (!overflow) { - *res = x * y; - } - - return overflow; -} #endif #if __has_builtin(__builtin_saddll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW diff --git a/py/mpconfig.h b/py/mpconfig.h index f4b7c105836..45178034ac9 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2336,4 +2336,23 @@ typedef time_t mp_timestamp_t; #define MP_WARN_CAT(x) (NULL) #endif +// If true, use __builtin_mul_overflow (a gcc intrinsic supported by clang) for +// overflow checking when multiplying two small ints. Otherwise, use a portable +// algorithm. +// +// Most MCUs have a 32x32->64 bit multiply instruction, in which case the +// intrinsic is likely to be faster and generate smaller code. The main exception is +// cortex-m0 with __ARM_ARCH_ISA_THUMB == 1. +// +// The intrinsic is in GCC starting with version 5. +#ifndef MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC +#if defined(__ARM_ARCH_ISA_THUMB) && (__GNUC__ >= 5) +#define MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC (__ARM_ARCH_ISA_THUMB >= 2) +#elif (__GNUC__ >= 5) +#define MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC (1) +#else +#define MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC (0) +#endif +#endif + #endif // MICROPY_INCLUDED_PY_MPCONFIG_H diff --git a/py/parsenum.c b/py/parsenum.c index 4239f2dcd4d..05bcc0f0a70 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -28,6 +28,7 @@ #include #include "py/runtime.h" +#include "py/misc.h" #include "py/parsenumbase.h" #include "py/parsenum.h" #include "py/smallint.h" @@ -52,7 +53,7 @@ static MP_NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { // to bigint parsing if supported) typedef mp_int_t parsed_int_t; -#define PARSED_INT_MUL_OVERFLOW mp_small_int_mul_overflow +#define PARSED_INT_MUL_OVERFLOW mp_mul_mp_int_t_overflow #define PARSED_INT_FITS MP_SMALL_INT_FITS #else // In the special case where bigint support is long long, we save code size by diff --git a/py/runtime.c b/py/runtime.c index 61aeb83f8d4..58d5732be14 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -430,7 +430,7 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs // Operations that can overflow: // + result always fits in mp_int_t, then handled by SMALL_INT check // - result always fits in mp_int_t, then handled by SMALL_INT check - // * checked explicitly + // * checked explicitly for fit in mp_int_t, then handled by SMALL_INT check // / if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check // % if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check // << checked explicitly @@ -489,31 +489,16 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs break; case MP_BINARY_OP_MULTIPLY: case MP_BINARY_OP_INPLACE_MULTIPLY: { - - // If long long type exists and is larger than mp_int_t, then - // we can use the following code to perform overflow-checked multiplication. - // Otherwise (eg in x64 case) we must use mp_small_int_mul_overflow. - #if 0 - // compute result using long long precision - long long res = (long long)lhs_val * (long long)rhs_val; - if (res > MP_SMALL_INT_MAX || res < MP_SMALL_INT_MIN) { - // result overflowed SMALL_INT, so return higher precision integer - return mp_obj_new_int_from_ll(res); - } else { - // use standard precision - lhs_val = (mp_int_t)res; - } - #endif - mp_int_t int_res; - if (mp_small_int_mul_overflow(lhs_val, rhs_val, &int_res)) { + if (mp_mul_mp_int_t_overflow(lhs_val, rhs_val, &int_res)) { // use higher precision lhs = mp_obj_new_int_from_ll(lhs_val); goto generic_binary_op; } else { // use standard precision - return MP_OBJ_NEW_SMALL_INT(int_res); + lhs_val = int_res; } + break; } case MP_BINARY_OP_FLOOR_DIVIDE: case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: @@ -553,7 +538,7 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs mp_int_t ans = 1; while (rhs_val > 0) { if (rhs_val & 1) { - if (mp_small_int_mul_overflow(ans, lhs_val, &ans)) { + if (mp_mul_mp_int_t_overflow(ans, lhs_val, &ans)) { goto power_overflow; } } @@ -562,7 +547,7 @@ mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs } rhs_val /= 2; mp_int_t int_res; - if (mp_small_int_mul_overflow(lhs_val, lhs_val, &int_res)) { + if (mp_mul_mp_int_t_overflow(lhs_val, lhs_val, &int_res)) { goto power_overflow; } lhs_val = int_res; diff --git a/py/runtime_utils.c b/py/runtime_utils.c index b92c6bd767c..50526b210fa 100644 --- a/py/runtime_utils.c +++ b/py/runtime_utils.c @@ -50,3 +50,63 @@ mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2 return MP_OBJ_NULL; } } + +#if !MICROPY_USE_GCC_MUL_OVERFLOW_INTRINSIC +bool mp_mul_ll_overflow(long long int x, long long int y, long long int *res) { + bool overflow; + + // Check for multiply overflow; see CERT INT32-C + if (x > 0) { // x is positive + if (y > 0) { // x and y are positive + overflow = (x > (LLONG_MAX / y)); + } else { // x positive, y nonpositive + overflow = (y < (LLONG_MIN / x)); + } // x positive, y nonpositive + } else { // x is nonpositive + if (y > 0) { // x is nonpositive, y is positive + overflow = (x < (LLONG_MIN / y)); + } else { // x and y are nonpositive + overflow = (x != 0 && y < (LLONG_MAX / x)); + } // End if x and y are nonpositive + } // End if x is nonpositive + + if (!overflow) { + *res = x * y; + } + + return overflow; +} + +#define MP_UINT_MAX (~(mp_uint_t)0) +#define MP_INT_MAX ((mp_int_t)(MP_UINT_MAX >> 1)) +#define MP_INT_MIN (-MP_INT_MAX - 1) + +bool mp_mul_mp_int_t_overflow(mp_int_t x, mp_int_t y, mp_int_t *res) { + // Check for multiply overflow; see CERT INT32-C + if (x > 0) { // x is positive + if (y > 0) { // x and y are positive + if (x > (MP_INT_MAX / y)) { + return true; + } + } else { // x positive, y nonpositive + if (y < (MP_INT_MIN / x)) { + return true; + } + } // x positive, y nonpositive + } else { // x is nonpositive + if (y > 0) { // x is nonpositive, y is positive + if (x < (MP_INT_MIN / y)) { + return true; + } + } else { // x and y are nonpositive + if (x != 0 && y < (MP_INT_MAX / x)) { + return true; + } + } // End if x and y are nonpositive + } // End if x is nonpositive + + // Result doesn't overflow + *res = x * y; + return false; +} +#endif diff --git a/py/smallint.c b/py/smallint.c index a494093d61a..eb99b58667a 100644 --- a/py/smallint.c +++ b/py/smallint.c @@ -26,35 +26,6 @@ #include "py/smallint.h" -bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y, mp_int_t *res) { - // Check for multiply overflow; see CERT INT32-C - if (x > 0) { // x is positive - if (y > 0) { // x and y are positive - if (x > (MP_SMALL_INT_MAX / y)) { - return true; - } - } else { // x positive, y nonpositive - if (y < (MP_SMALL_INT_MIN / x)) { - return true; - } - } // x positive, y nonpositive - } else { // x is nonpositive - if (y > 0) { // x is nonpositive, y is positive - if (x < (MP_SMALL_INT_MIN / y)) { - return true; - } - } else { // x and y are nonpositive - if (x != 0 && y < (MP_SMALL_INT_MAX / x)) { - return true; - } - } // End if x and y are nonpositive - } // End if x is nonpositive - - // Result doesn't overflow - *res = x * y; - return false; -} - mp_int_t mp_small_int_modulo(mp_int_t dividend, mp_int_t divisor) { // Python specs require that mod has same sign as second operand dividend %= divisor; diff --git a/py/smallint.h b/py/smallint.h index e50f98651e6..ec5b0af3b28 100644 --- a/py/smallint.h +++ b/py/smallint.h @@ -68,10 +68,6 @@ // The number of bits in a MP_SMALL_INT including the sign bit. #define MP_SMALL_INT_BITS (MP_IMAX_BITS(MP_SMALL_INT_MAX) + 1) -// Multiply two small ints. -// If returns false, the correct result is stored in 'res' -// If returns true, the multiplication would have overflowed. 'res' is unchanged. -bool mp_small_int_mul_overflow(mp_int_t x, mp_int_t y, mp_int_t *res); mp_int_t mp_small_int_modulo(mp_int_t dividend, mp_int_t divisor); mp_int_t mp_small_int_floor_divide(mp_int_t num, mp_int_t denom); From ea63e43faf34a8c60eeae69f139ae4b0602a728f Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 28 Sep 2025 20:44:05 -0700 Subject: [PATCH 1297/2098] alif/machine_spi: Fix init() to only change requested settings. Signed-off-by: Kwabena W. Agyeman --- ports/alif/machine_spi.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ports/alif/machine_spi.c b/ports/alif/machine_spi.c index b2c14745cfc..362ffa9b214 100644 --- a/ports/alif/machine_spi.c +++ b/ports/alif/machine_spi.c @@ -79,8 +79,8 @@ static inline uint32_t spi_get_clk(machine_spi_obj_t *spi) { return spi->is_lp ? GetSystemCoreClock() : GetSystemAHBClock(); } -static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, - uint32_t polarity, uint32_t phase, uint32_t bits, uint32_t firstbit) { +static void spi_init(machine_spi_obj_t *spi, int32_t baudrate, + int32_t polarity, int32_t phase, int32_t bits, int32_t firstbit) { const machine_pin_obj_t *pins[4] = { NULL, NULL, NULL, NULL }; switch (spi->id) { #if defined(MICROPY_HW_SPI0_SCK) @@ -161,7 +161,9 @@ static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, spi_mask_interrupts(spi->inst); // Configure baudrate clock - spi_set_bus_speed(spi->inst, baudrate, spi_get_clk(spi)); + if (baudrate > 0) { + spi_set_bus_speed(spi->inst, baudrate, spi_get_clk(spi)); + } // Configure FIFOs spi_set_tx_threshold(spi->inst, 0); @@ -172,6 +174,21 @@ static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, } // Configure SPI bus mode. + if (!spi->is_lp) { + if (polarity < 0) { + polarity = (spi->inst->SPI_CTRLR0 & SPI_CTRLR0_SCPOL_HIGH) ? 1 : 0; + } + if (phase < 0) { + phase = (spi->inst->SPI_CTRLR0 & SPI_CTRLR0_SCPH_HIGH) ? 1 : 0; + } + } else { + if (polarity < 0) { + polarity = (spi->inst->SPI_CTRLR0 & LPSPI_CTRLR0_SCPOL_HIGH) ? 1 : 0; + } + if (phase < 0) { + phase = (spi->inst->SPI_CTRLR0 & LPSPI_CTRLR0_SCPH_HIGH) ? 1 : 0; + } + } uint32_t spi_mode = (polarity << 1) | phase; if (!spi->is_lp) { spi_set_mode(spi->inst, spi_mode); @@ -193,10 +210,12 @@ static void spi_init(machine_spi_obj_t *spi, uint32_t baudrate, } // Configure frame size. - if (!spi->is_lp) { - spi_set_dfs(spi->inst, bits); - } else { - lpspi_set_dfs(spi->inst, bits); + if (bits > 0) { + if (!spi->is_lp) { + spi_set_dfs(spi->inst, bits); + } else { + lpspi_set_dfs(spi->inst, bits); + } } // Configure slave select pin From 39e4413f46e616b70499865d1c22e523365d6f55 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 28 Sep 2025 20:49:29 -0700 Subject: [PATCH 1298/2098] alif/boards/OPENMV_AE3: Fix switch name to match OpenMV RT1062 and N6. Signed-off-by: Kwabena W. Agyeman --- ports/alif/boards/OPENMV_AE3/pins.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/alif/boards/OPENMV_AE3/pins.csv b/ports/alif/boards/OPENMV_AE3/pins.csv index ab4f1a34f7e..360b27af813 100644 --- a/ports/alif/boards/OPENMV_AE3/pins.csv +++ b/ports/alif/boards/OPENMV_AE3/pins.csv @@ -8,7 +8,7 @@ LED_GREEN,P6_3 LED_BLUE,P6_0 # User switch -USR_SW,P15_7 +SW,P15_7 # Flash on OSPI0 FLASH_RESET,P3_3 From 58ff28b505cb311e4ffb024a81d81e1b094976bc Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Mon, 22 Sep 2025 20:24:18 +0900 Subject: [PATCH 1299/2098] stm32/rtc: Fix passing invalid argument to RTC callback. NameError occurred when trying to access the argument of RTC.wakeup() callback because the callback argument is not initialized. To fix this issue, this commit passes EXTI_RTC_WAKEUP to callback argument. This is equivalent behavior with ExtInt callback. Signed-off-by: Yuuki NAGAO --- ports/stm32/extint.c | 2 +- ports/stm32/extint.h | 1 + ports/stm32/rtc.c | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/stm32/extint.c b/ports/stm32/extint.c index df0ed6e23e1..b1e5c8a8f22 100644 --- a/ports/stm32/extint.c +++ b/ports/stm32/extint.c @@ -127,7 +127,7 @@ static uint8_t pyb_extint_mode[EXTI_NUM_VECTORS]; static bool pyb_extint_hard_irq[EXTI_NUM_VECTORS]; // The callback arg is a small-int or a ROM Pin object, so no need to scan by GC -static mp_obj_t pyb_extint_callback_arg[EXTI_NUM_VECTORS]; +mp_obj_t pyb_extint_callback_arg[EXTI_NUM_VECTORS]; #if !defined(ETH) #define ETH_WKUP_IRQn 62 // Some MCUs don't have ETH, but we want a value to put in our table diff --git a/ports/stm32/extint.h b/ports/stm32/extint.h index 801dcf62b54..cdebf6eb86a 100644 --- a/ports/stm32/extint.h +++ b/ports/stm32/extint.h @@ -64,6 +64,7 @@ #endif #define EXTI_NUM_VECTORS (PYB_EXTI_NUM_VECTORS) +extern mp_obj_t pyb_extint_callback_arg[]; void extint_init0(void); diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index fa28fda6a89..fb106e179be 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -771,6 +771,7 @@ mp_obj_t pyb_rtc_wakeup(size_t n_args, const mp_obj_t *args) { // set the callback MP_STATE_PORT(pyb_extint_callback)[EXTI_RTC_WAKEUP] = callback; + pyb_extint_callback_arg[EXTI_RTC_WAKEUP] = MP_OBJ_NEW_SMALL_INT(EXTI_RTC_WAKEUP); // disable register write protection RTC->WPR = 0xca; From 64c7045213514d7d57abeee8705f6b4e70222785 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Wed, 24 Sep 2025 21:32:30 -0700 Subject: [PATCH 1300/2098] stm32/boards/OPENMV_N6: Define all OpenMV N6 pins. Signed-off-by: Kwabena W. Agyeman --- ports/stm32/boards/OPENMV_N6/pins.csv | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/OPENMV_N6/pins.csv b/ports/stm32/boards/OPENMV_N6/pins.csv index b05b8b57f9e..84b4160faf6 100644 --- a/ports/stm32/boards/OPENMV_N6/pins.csv +++ b/ports/stm32/boards/OPENMV_N6/pins.csv @@ -1,9 +1,9 @@ ,PA0 ,PA1 -,PA2 +ONOFF,PA2 ,PA3 ,PA4 -,PA5 +P6_ADC,PA5 ,PA6 ,PA7 ,PA8 @@ -21,11 +21,13 @@ P2,PA12 ,PB0 ,PB1 ,PB2 -,PB3 +CHG,PB3 ,PB4 ,PB5 SPI4_MISO,PB6 SPI4_MOSI,PB7 +P17,PB6 +P18,PB7 ,PB8 ,PB9 I2C2_SCL,PB10 @@ -63,8 +65,8 @@ P11,PC13 P10,PD6 SPI2_MOSI,PD7 P0,PD7 -,PD8 -,PD9 +ST,PD8 +PG,PD9 ,PD10 SPI2_MISO,PD11 P1,PD11 @@ -81,19 +83,23 @@ P8,PD13 ,PE6 UART7_RX,PE7 UART7_TX,PE8 +P13,PE7 +P14,PE8 ,PE9 ,PE10 SPI4_CS,PE11 SPI4_SCK,PE12 +P15,PE11 +P16,PE12 I2C4_SCL,PE13 I2C4_SDA,PE14 ,PE15 P6,PG0 P9,PG12 P7,PG13 -,PG15 +BAT_ADC,PG15 -BUTTON,PF4 +SW,PF4 LED_RED,PG10 LED_GREEN,PA7 LED_BLUE,PB1 From ed46ff68207edfb0d4d3273cee1f3e5f1199dab4 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 27 Sep 2025 16:01:37 +0200 Subject: [PATCH 1301/2098] stm32/boards: Add PG13 AF7 (USART3_RTS) to N6 AF file. Signed-off-by: iabdalkader --- ports/stm32/boards/stm32n657_af.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv index 35e305a3767..9bdb196f881 100644 --- a/ports/stm32/boards/stm32n657_af.csv +++ b/ports/stm32/boards/stm32n657_af.csv @@ -37,6 +37,6 @@ PortG,PG2 , , , , PortG,PG5 , , , , , , , ,USART2_CTS , , , , , , , , , PortG,PG8 , , , , , , , , , , , ,SDMMC2_D1 , , , , , PortG,PG12, ,TIM17_CH1 , , , , , , , , , , , , , , , -PortG,PG13, , ,TIM4_CH1 , , , , , , , , , , , , , , +PortG,PG13, , ,TIM4_CH1 , , , , ,USART3_RTS , , , , , , , , , PortG,PG15, , , , , , , , , , , , , , , , ,ADC12_INP7/ADC12_INN3 PortC,PH2 , , , , , , , , , , ,SDMMC1_CMD , , , , , , From 6d65882b05381522ff946ba1f41fd0122edb344f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 27 Sep 2025 16:03:35 +0200 Subject: [PATCH 1302/2098] stm32/boards/OPENMV_N6: Define RTS pin for UART3. Signed-off-by: iabdalkader --- ports/stm32/boards/OPENMV_N6/mpconfigboard.h | 1 + ports/stm32/boards/OPENMV_N6/pins.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h index ed7bb548a1d..5e4f8109991 100644 --- a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h +++ b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h @@ -53,6 +53,7 @@ #define MICROPY_HW_UART2_CTS (pyb_pin_BT_CTS) #define MICROPY_HW_UART3_TX (pyb_pin_UART3_TX) #define MICROPY_HW_UART3_RX (pyb_pin_UART3_RX) +#define MICROPY_HW_UART3_RTS (pyb_pin_UART3_RTS) #define MICROPY_HW_UART4_TX (pyb_pin_UART4_TX) #define MICROPY_HW_UART4_RX (pyb_pin_UART4_RX) #define MICROPY_HW_UART7_TX (pyb_pin_UART7_TX) diff --git a/ports/stm32/boards/OPENMV_N6/pins.csv b/ports/stm32/boards/OPENMV_N6/pins.csv index 84b4160faf6..52f6c92496d 100644 --- a/ports/stm32/boards/OPENMV_N6/pins.csv +++ b/ports/stm32/boards/OPENMV_N6/pins.csv @@ -34,6 +34,7 @@ I2C2_SCL,PB10 I2C2_SDA,PB11 UART3_TX,PB10 UART3_RX,PB11 +UART3_RTS,PG13 P4,PB10 P5,PB11 ,PB12 From 7681c683920055a7b0285b2b40ce94656293d32e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 16 Sep 2025 13:15:10 +1000 Subject: [PATCH 1303/2098] extmod/vfs_blockdev: Check return type is an integer. This catches cases where the return type is not a small int, eg it could be a string, or even a big integer. Signed-off-by: Damien George --- extmod/vfs_blockdev.c | 2 +- tests/extmod/vfs_blockdev_invalid.py | 8 ++++---- tests/extmod/vfs_blockdev_invalid.py.exp | 24 ++++++++++++------------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index d43c96b08f3..5c7d248ac5a 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -69,7 +69,7 @@ static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t bloc // and negative integer on errors. Check for positive integer // results as some callers (i.e. littlefs) will produce corrupt // results from these. - int i = MP_OBJ_SMALL_INT_VALUE(ret); + int i = mp_obj_get_int(ret); return i > 0 ? (-MP_EINVAL) : i; } } diff --git a/tests/extmod/vfs_blockdev_invalid.py b/tests/extmod/vfs_blockdev_invalid.py index 4d00f4b0027..29d6bd6b2f9 100644 --- a/tests/extmod/vfs_blockdev_invalid.py +++ b/tests/extmod/vfs_blockdev_invalid.py @@ -70,8 +70,8 @@ def test(vfs_class): try: with fs.open("test", "r") as f: print("opened") - except OSError as e: - print("OSError", e) + except Exception as e: + print(type(e), e) # This variant should succeed on open, may fail on read # unless the filesystem cached the contents already @@ -81,8 +81,8 @@ def test(vfs_class): bdev.read_res = res print("read 1", f.read(1)) print("read rest", f.read()) - except OSError as e: - print("OSError", e) + except Exception as e: + print(type(e), e) test(vfs.VfsLfs2) diff --git a/tests/extmod/vfs_blockdev_invalid.py.exp b/tests/extmod/vfs_blockdev_invalid.py.exp index 13695e0d889..0ea0353501d 100644 --- a/tests/extmod/vfs_blockdev_invalid.py.exp +++ b/tests/extmod/vfs_blockdev_invalid.py.exp @@ -2,27 +2,27 @@ opened read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OSError [Errno 5] EIO + [Errno 5] EIO read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OSError [Errno 22] EINVAL + [Errno 22] EINVAL read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OSError [Errno 22] EINVAL + [Errno 22] EINVAL read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OSError [Errno 22] EINVAL + can't convert str to int read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa opened read 1 a read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO -OSError [Errno 5] EIO + [Errno 5] EIO + [Errno 5] EIO + [Errno 5] EIO + [Errno 5] EIO + [Errno 5] EIO + [Errno 5] EIO + can't convert str to int + can't convert str to int From 4d905ef55235ed6f2f2d02799a3089ae8eb0c6e3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 16 Sep 2025 12:55:59 +1000 Subject: [PATCH 1304/2098] tests/run-tests.py: Skip certain tests when using --via-mpy. These tests cannot pass when using `--via-mpy`. Signed-off-by: Damien George --- tests/run-tests.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index d6eb9a3fbc2..1aec3ad85e9 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -107,6 +107,19 @@ def open(self, path, mode): platform_to_port_map = {"pyboard": "stm32", "WiPy": "cc3200"} platform_to_port_map.update({p: "unix" for p in PC_PLATFORMS}) +# Tests to skip for values of the `--via-mpy` argument. +via_mpy_tests_to_skip = { + # Skip the following when mpy is enabled. + True: ( + # These print out the filename and that's expected to match the .py name. + "import/import_file.py", + "io/argv.py", + "misc/sys_settrace_features.py", + "misc/sys_settrace_generator.py", + "misc/sys_settrace_loop.py", + ), +} + # Tests to skip for specific emitters. emitter_tests_to_skip = { # Some tests are known to fail with native emitter. @@ -925,6 +938,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("unicode/file2.py") # requires local file access skip_tests.add("unicode/file_invalid.py") # requires local file access + # Skip certain tests when going via a .mpy file. + skip_tests.update(via_mpy_tests_to_skip.get(args.via_mpy, ())) + # Skip emitter-specific tests. skip_tests.update(emitter_tests_to_skip.get(args.emit, ())) From e031ead6b2043c412abb38804c1a1e350237e642 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 16 Sep 2025 12:56:30 +1000 Subject: [PATCH 1305/2098] unix/Makefile: Run all possible tests when using --via-mpy. Currently testing on the unix port with `--via-mpy` only runs tests in the `basics`, `float` and `micropython` test directories. This commit removes that restriction and now runs `--via-mpy` tests using all possible test directories. This improves test coverage. Signed-off-by: Damien George --- ports/unix/Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index e4638299f15..7df4c6f79b5 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -278,16 +278,15 @@ test/%: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d "$*" -test_full_no_native: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py +test_full_no_native: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py test $(eval DIRNAME=ports/$(notdir $(CURDIR))) - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) cat $(TOP)/tests/basics/0prelim.py | ./$(BUILD)/$(PROG) | grep -q 'abc' test_full: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py test_full_no_native $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --emit native - cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native test_gcov: test_full gcov -o $(BUILD)/py $(TOP)/py/*.c From 4c9ce826cbfbd99cff10cc933064f87a13bee4ac Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 16 Sep 2025 15:13:10 +1000 Subject: [PATCH 1306/2098] tests: Remove .py.exp files that match with CPython 3.8.2 or newer. Running the tests now requires CPython 3.8.2 or newer, which was released February 2020 and should be widely available. A few examples of features that were previously not supported by CPython, but which are now: - %-formatting for bytes and bytearray (PEP 461), CPython 3.5 - annotated variables (PEP 526), CPython 3.6 - assignment expressions (PEP 572), CPython 3.8 Note that `basics/fun_code_full.py.exp` is added here because that requires CPython 3.10 or newer. Signed-off-by: Damien George --- tests/README.md | 3 +- tests/basics/annotate_var.py.exp | 5 --- tests/basics/assign_expr.py.exp | 16 -------- tests/basics/assign_expr_scope.py.exp | 23 ----------- tests/basics/async_await.py.exp | 32 --------------- tests/basics/async_await2.py.exp | 5 --- tests/basics/async_def.py.exp | 3 -- tests/basics/async_for.py.exp | 51 ------------------------ tests/basics/async_for2.py.exp | 32 --------------- tests/basics/async_syntaxerror.py.exp | 2 - tests/basics/async_with.py.exp | 11 ----- tests/basics/async_with2.py.exp | 17 -------- tests/basics/async_with_break.py.exp | 15 ------- tests/basics/async_with_return.py.exp | 15 ------- tests/basics/bytes_format_modulo.py.exp | 5 --- tests/basics/class_inplace_op2.py.exp | 12 ------ tests/basics/class_ordereddict.py.exp | 1 - tests/basics/fun_code_full.py.exp | 9 +++++ tests/basics/python36.py.exp | 5 --- tests/basics/special_methods2.py.exp | 19 --------- tests/basics/string_fstring_debug.py.exp | 9 ----- tests/basics/try_finally_continue.py.exp | 9 ----- tests/extmod/asyncio_basic.py.exp | 6 --- tests/extmod/asyncio_lock.py.exp | 41 ------------------- tests/extmod/asyncio_wait_task.py.exp | 12 ------ tests/extmod/json_loads_bytes.py.exp | 2 - tests/extmod/re_sub_unmatched.py.exp | 1 - 27 files changed, 11 insertions(+), 350 deletions(-) delete mode 100644 tests/basics/annotate_var.py.exp delete mode 100644 tests/basics/assign_expr.py.exp delete mode 100644 tests/basics/assign_expr_scope.py.exp delete mode 100644 tests/basics/async_await.py.exp delete mode 100644 tests/basics/async_await2.py.exp delete mode 100644 tests/basics/async_def.py.exp delete mode 100644 tests/basics/async_for.py.exp delete mode 100644 tests/basics/async_for2.py.exp delete mode 100644 tests/basics/async_syntaxerror.py.exp delete mode 100644 tests/basics/async_with.py.exp delete mode 100644 tests/basics/async_with2.py.exp delete mode 100644 tests/basics/async_with_break.py.exp delete mode 100644 tests/basics/async_with_return.py.exp delete mode 100644 tests/basics/bytes_format_modulo.py.exp delete mode 100644 tests/basics/class_inplace_op2.py.exp delete mode 100644 tests/basics/class_ordereddict.py.exp create mode 100644 tests/basics/fun_code_full.py.exp delete mode 100644 tests/basics/python36.py.exp delete mode 100644 tests/basics/special_methods2.py.exp delete mode 100644 tests/basics/string_fstring_debug.py.exp delete mode 100644 tests/basics/try_finally_continue.py.exp delete mode 100644 tests/extmod/asyncio_basic.py.exp delete mode 100644 tests/extmod/asyncio_lock.py.exp delete mode 100644 tests/extmod/asyncio_wait_task.py.exp delete mode 100644 tests/extmod/json_loads_bytes.py.exp delete mode 100644 tests/extmod/re_sub_unmatched.py.exp diff --git a/tests/README.md b/tests/README.md index 6a6395c39cb..676e9d83205 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,7 @@ # MicroPython Test Suite -This directory contains tests for most parts of MicroPython. +This directory contains tests for most parts of MicroPython. To run it you will need +CPython 3.8.2 or newer, which is used to validate MicroPython's behaviour. To run all stable tests, run the "run-tests.py" script in this directory. By default that will run the test suite against the unix port of MicroPython. diff --git a/tests/basics/annotate_var.py.exp b/tests/basics/annotate_var.py.exp deleted file mode 100644 index 9b6536e966b..00000000000 --- a/tests/basics/annotate_var.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -False -1 -(1, 2) -NameError -1 diff --git a/tests/basics/assign_expr.py.exp b/tests/basics/assign_expr.py.exp deleted file mode 100644 index 47da56b80d4..00000000000 --- a/tests/basics/assign_expr.py.exp +++ /dev/null @@ -1,16 +0,0 @@ -4 -True -2 -4 5 -5 -1 5 5 -5 -2 1 -1 0 -any True -8 -123 -any True -8 -[(1, 0), (2, 2), (3, 6), (4, 12)] -4 diff --git a/tests/basics/assign_expr_scope.py.exp b/tests/basics/assign_expr_scope.py.exp deleted file mode 100644 index 5c780b38221..00000000000 --- a/tests/basics/assign_expr_scope.py.exp +++ /dev/null @@ -1,23 +0,0 @@ -scope0 -1 -None -scope1 -[1] -1 -None -scope2 -[0, 1] -1 -1 -scope3 -[0, 1] -None -None -scope4 -[0, 1] -1 -1 -scope5 -[0, 1] -1 -None diff --git a/tests/basics/async_await.py.exp b/tests/basics/async_await.py.exp deleted file mode 100644 index b51c388a933..00000000000 --- a/tests/basics/async_await.py.exp +++ /dev/null @@ -1,32 +0,0 @@ -4 -3 -2 -1 -0 -0 -1 -0 -0 -2 -1 -0 -0 -1 -0 -0 -3 -2 -1 -0 -0 -1 -0 -0 -2 -1 -0 -0 -1 -0 -0 -finished diff --git a/tests/basics/async_await2.py.exp b/tests/basics/async_await2.py.exp deleted file mode 100644 index fc9ff0aa535..00000000000 --- a/tests/basics/async_await2.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -wait value: 1 -return from send: message from wait(1) -wait got back: message from main -x = 100 -got StopIteration diff --git a/tests/basics/async_def.py.exp b/tests/basics/async_def.py.exp deleted file mode 100644 index f555ace99ab..00000000000 --- a/tests/basics/async_def.py.exp +++ /dev/null @@ -1,3 +0,0 @@ -decorator -foo -StopIteration diff --git a/tests/basics/async_for.py.exp b/tests/basics/async_for.py.exp deleted file mode 100644 index 6f59979c065..00000000000 --- a/tests/basics/async_for.py.exp +++ /dev/null @@ -1,51 +0,0 @@ -== start == -init -aiter -init -anext -a -anext -b -anext -c -anext -== finish == -== start == -init -aiter -init -anext -d -anext -e -anext -f -anext -AsyncIteratorWrapper-def -== finish == -init -== start == -aiter -init -anext -g -anext -h -anext -i -anext -AsyncIteratorWrapper-ghi -== finish == -init -== start == -aiter -init -anext -j -anext -k -anext -l -anext -AsyncIteratorWrapper-jkl -== finish == diff --git a/tests/basics/async_for2.py.exp b/tests/basics/async_for2.py.exp deleted file mode 100644 index 52bbe90c853..00000000000 --- a/tests/basics/async_for2.py.exp +++ /dev/null @@ -1,32 +0,0 @@ -init -aiter -anext -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -x 0 -anext -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -x 1 -anext -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -x 2 -anext -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -x 3 -anext -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -finished diff --git a/tests/basics/async_syntaxerror.py.exp b/tests/basics/async_syntaxerror.py.exp deleted file mode 100644 index 5275689b413..00000000000 --- a/tests/basics/async_syntaxerror.py.exp +++ /dev/null @@ -1,2 +0,0 @@ -SyntaxError -SyntaxError diff --git a/tests/basics/async_with.py.exp b/tests/basics/async_with.py.exp deleted file mode 100644 index 6bbf84cb4b5..00000000000 --- a/tests/basics/async_with.py.exp +++ /dev/null @@ -1,11 +0,0 @@ -enter -body -exit None None -finished -enter -1 -exit error -ValueError -enter -exit -BaseException diff --git a/tests/basics/async_with2.py.exp b/tests/basics/async_with2.py.exp deleted file mode 100644 index 76b173b4c24..00000000000 --- a/tests/basics/async_with2.py.exp +++ /dev/null @@ -1,17 +0,0 @@ -enter -f start: 10 -coro yielded: 11 -coro yielded: 12 -f returned: 13 -body start -f start: 30 -coro yielded: 31 -coro yielded: 32 -body f returned: 33 -body end -exit None None -f start: 20 -coro yielded: 21 -coro yielded: 22 -f returned: 23 -finished diff --git a/tests/basics/async_with_break.py.exp b/tests/basics/async_with_break.py.exp deleted file mode 100644 index d077a88fad0..00000000000 --- a/tests/basics/async_with_break.py.exp +++ /dev/null @@ -1,15 +0,0 @@ -enter -body -exit None None -finished -enter -body -exit None None -finally -finished -enter -body -exit None None -finally inner -finally outer -finished diff --git a/tests/basics/async_with_return.py.exp b/tests/basics/async_with_return.py.exp deleted file mode 100644 index d077a88fad0..00000000000 --- a/tests/basics/async_with_return.py.exp +++ /dev/null @@ -1,15 +0,0 @@ -enter -body -exit None None -finished -enter -body -exit None None -finally -finished -enter -body -exit None None -finally inner -finally outer -finished diff --git a/tests/basics/bytes_format_modulo.py.exp b/tests/basics/bytes_format_modulo.py.exp deleted file mode 100644 index 782b7f91fcb..00000000000 --- a/tests/basics/bytes_format_modulo.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -b'%' -b'=1=' -b'=1=2=' -b'=str=' -b"=b'str'=" diff --git a/tests/basics/class_inplace_op2.py.exp b/tests/basics/class_inplace_op2.py.exp deleted file mode 100644 index 8c323b5178e..00000000000 --- a/tests/basics/class_inplace_op2.py.exp +++ /dev/null @@ -1,12 +0,0 @@ -__imul__ -__imatmul__ -__ifloordiv__ -__itruediv__ -__imod__ -__ipow__ -__ior__ -__ixor__ -__iand__ -__ilshift__ -__irshift__ -TypeError diff --git a/tests/basics/class_ordereddict.py.exp b/tests/basics/class_ordereddict.py.exp deleted file mode 100644 index b723e327515..00000000000 --- a/tests/basics/class_ordereddict.py.exp +++ /dev/null @@ -1 +0,0 @@ -['a', 'b', 'c', 'd', 'e'] diff --git a/tests/basics/fun_code_full.py.exp b/tests/basics/fun_code_full.py.exp new file mode 100644 index 00000000000..830effadfc6 --- /dev/null +++ b/tests/basics/fun_code_full.py.exp @@ -0,0 +1,9 @@ + +(None,) +fun_code_full.py + +f +True +0 +0 +non-contiguous diff --git a/tests/basics/python36.py.exp b/tests/basics/python36.py.exp deleted file mode 100644 index 4b65daafa18..00000000000 --- a/tests/basics/python36.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -100000 -165 -65535 -123 -83 diff --git a/tests/basics/special_methods2.py.exp b/tests/basics/special_methods2.py.exp deleted file mode 100644 index a9ae75be55c..00000000000 --- a/tests/basics/special_methods2.py.exp +++ /dev/null @@ -1,19 +0,0 @@ -__pos__ called -__pos__ called -__neg__ called -__invert__ called -__mul__ called -__matmul__ called -__truediv__ called -__floordiv__ called -__iadd__ called -__isub__ called -__mod__ called -__pow__ called -__or__ called -__and__ called -__xor__ called -__lshift__ called -__rshift__ called -['a', 'b', 'c'] -False diff --git a/tests/basics/string_fstring_debug.py.exp b/tests/basics/string_fstring_debug.py.exp deleted file mode 100644 index f0309e1c98a..00000000000 --- a/tests/basics/string_fstring_debug.py.exp +++ /dev/null @@ -1,9 +0,0 @@ -x=1 -x=00000001 -a x=1 b 2 c -a x=00000001 b 2 c -a f() + g("foo") + h()=15 b -a f() + g("foo") + h()=0000000f b -a 1,=(1,) b -a x,y,=(1, 2) b -a x,1=(1, 1) b diff --git a/tests/basics/try_finally_continue.py.exp b/tests/basics/try_finally_continue.py.exp deleted file mode 100644 index 2901997b1aa..00000000000 --- a/tests/basics/try_finally_continue.py.exp +++ /dev/null @@ -1,9 +0,0 @@ -4 0 -continue -4 1 -continue -4 2 -continue -4 3 -continue -None diff --git a/tests/extmod/asyncio_basic.py.exp b/tests/extmod/asyncio_basic.py.exp deleted file mode 100644 index 478e22abc8f..00000000000 --- a/tests/extmod/asyncio_basic.py.exp +++ /dev/null @@ -1,6 +0,0 @@ -start -after sleep -short -long -negative -took 200 400 0 diff --git a/tests/extmod/asyncio_lock.py.exp b/tests/extmod/asyncio_lock.py.exp deleted file mode 100644 index a37dfcbd2e5..00000000000 --- a/tests/extmod/asyncio_lock.py.exp +++ /dev/null @@ -1,41 +0,0 @@ -False -True -False -have lock ----- -task start 1 -task start 2 -task start 3 -task have 1 0 -task have 2 0 -task have 3 0 -task have 1 1 -task have 2 1 -task have 3 1 -task have 1 2 -task end 1 -task have 2 2 -task end 2 -task have 3 2 -task end 3 ----- -task have True -task release False -task have True -task release False -task have again -task have again ----- -task got 0 -task release 0 -task cancel 1 -task got 2 -task release 2 -False ----- -task got 0 -task cancel 1 -task release 0 -task got 2 -task cancel 2 -False diff --git a/tests/extmod/asyncio_wait_task.py.exp b/tests/extmod/asyncio_wait_task.py.exp deleted file mode 100644 index 514a4342233..00000000000 --- a/tests/extmod/asyncio_wait_task.py.exp +++ /dev/null @@ -1,12 +0,0 @@ -start -task 1 -task 2 ----- -start -hello -world -took 200 200 -task_raise -ValueError -task_raise -ValueError diff --git a/tests/extmod/json_loads_bytes.py.exp b/tests/extmod/json_loads_bytes.py.exp deleted file mode 100644 index c2735a99052..00000000000 --- a/tests/extmod/json_loads_bytes.py.exp +++ /dev/null @@ -1,2 +0,0 @@ -[1, 2] -[None] diff --git a/tests/extmod/re_sub_unmatched.py.exp b/tests/extmod/re_sub_unmatched.py.exp deleted file mode 100644 index 1e5f0fda055..00000000000 --- a/tests/extmod/re_sub_unmatched.py.exp +++ /dev/null @@ -1 +0,0 @@ -1-a2 From da568a3a3019685b217955fe312c04dbbd893ada Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 07:58:48 +1000 Subject: [PATCH 1307/2098] tests/stress: Improve qstr_limit test to run on minimal targets. Changes here are: - Split out string format module sub-test to a separate test file, so it can be skipped on targets that don't have str% capabilities. - Print the whole type instead of `.__name__` to support targets that don't have `.__name__` enabled (this still tests the same thing). - Print `RuntimeError` exception message to be sure the correct exception is being raised. This test now runs on unix and zephyr minimal configurations. Signed-off-by: Damien George --- tests/stress/qstr_limit.py | 42 +++++++---------- tests/stress/qstr_limit.py.exp | 55 +++++++++++------------ tests/stress/qstr_limit_str_modulo.py | 21 +++++++++ tests/stress/qstr_limit_str_modulo.py.exp | 5 +++ 4 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 tests/stress/qstr_limit_str_modulo.py create mode 100644 tests/stress/qstr_limit_str_modulo.py.exp diff --git a/tests/stress/qstr_limit.py b/tests/stress/qstr_limit.py index 08b10a039f5..c7bd437f3ad 100644 --- a/tests/stress/qstr_limit.py +++ b/tests/stress/qstr_limit.py @@ -12,8 +12,8 @@ def make_id(n, base="a"): var = make_id(l) try: exec(var + "=1", g) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) continue print(var in g) @@ -26,16 +26,16 @@ def f(**k): for l in range(254, 259): try: exec("f({}=1)".format(make_id(l))) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) # type construction for l in range(254, 259): id = make_id(l) try: - print(type(id, (), {}).__name__) - except RuntimeError: - print("RuntimeError", l) + print(type(id, (), {})) + except RuntimeError as er: + print("RuntimeError", er, l) # hasattr, setattr, getattr @@ -48,28 +48,20 @@ class A: a = A() try: setattr(a, id, 123) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) try: print(hasattr(a, id), getattr(a, id)) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) # format with keys for l in range(254, 259): id = make_id(l) try: print(("{" + id + "}").format(**{id: l})) - except RuntimeError: - print("RuntimeError", l) - -# modulo format with keys -for l in range(254, 259): - id = make_id(l) - try: - print(("%(" + id + ")d") % {id: l}) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) # import module # (different OS's have different results so only run those that are consistent) @@ -78,8 +70,8 @@ class A: __import__(make_id(l)) except ImportError: print("ok", l) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) # import package for l in (100, 101, 102, 128, 129): @@ -87,5 +79,5 @@ class A: exec("import " + make_id(l) + "." + make_id(l, "A")) except ImportError: print("ok", l) - except RuntimeError: - print("RuntimeError", l) + except RuntimeError as er: + print("RuntimeError", er, l) diff --git a/tests/stress/qstr_limit.py.exp b/tests/stress/qstr_limit.py.exp index 455761bc71e..2349adf220f 100644 --- a/tests/stress/qstr_limit.py.exp +++ b/tests/stress/qstr_limit.py.exp @@ -1,43 +1,38 @@ True True -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 {'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst': 1} {'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu': 1} -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 -abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst -abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 + + +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 True 123 True 123 -RuntimeError 256 -RuntimeError 256 -RuntimeError 257 -RuntimeError 257 -RuntimeError 258 -RuntimeError 258 +RuntimeError name too long 256 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 257 +RuntimeError name too long 258 +RuntimeError name too long 258 254 255 -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 -254 -255 -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 ok 100 ok 101 -RuntimeError 256 -RuntimeError 257 -RuntimeError 258 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 ok 100 ok 101 ok 102 -RuntimeError 128 -RuntimeError 129 +RuntimeError name too long 128 +RuntimeError name too long 129 diff --git a/tests/stress/qstr_limit_str_modulo.py b/tests/stress/qstr_limit_str_modulo.py new file mode 100644 index 00000000000..90b9f4364ec --- /dev/null +++ b/tests/stress/qstr_limit_str_modulo.py @@ -0,0 +1,21 @@ +# Test interning qstrs that go over the qstr length limit (255 bytes in default configuration). +# The tests here are specifically for str formatting with %. + +try: + "" % () +except TypeError: + print("SKIP") + raise SystemExit + + +def make_id(n, base="a"): + return "".join(chr(ord(base) + i % 26) for i in range(n)) + + +# modulo format with keys +for l in range(254, 259): + id = make_id(l) + try: + print(("%(" + id + ")d") % {id: l}) + except RuntimeError as er: + print("RuntimeError", er, l) diff --git a/tests/stress/qstr_limit_str_modulo.py.exp b/tests/stress/qstr_limit_str_modulo.py.exp new file mode 100644 index 00000000000..3632c85bffe --- /dev/null +++ b/tests/stress/qstr_limit_str_modulo.py.exp @@ -0,0 +1,5 @@ +254 +255 +RuntimeError name too long 256 +RuntimeError name too long 257 +RuntimeError name too long 258 From d37ce8c369cdc7e094c7b981a69fb3abddd6d2a6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 08:30:28 +1000 Subject: [PATCH 1308/2098] tests/misc/sys_exc_info.py: Don't rely on slicing. Signed-off-by: Damien George --- tests/misc/sys_exc_info.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/misc/sys_exc_info.py b/tests/misc/sys_exc_info.py index d7e8a2d943b..c076dd572b0 100644 --- a/tests/misc/sys_exc_info.py +++ b/tests/misc/sys_exc_info.py @@ -8,13 +8,14 @@ def f(): - print(sys.exc_info()[0:2]) + e = sys.exc_info() + print(e[0], e[1]) try: raise ValueError("value", 123) except: - print(sys.exc_info()[0:2]) + print(sys.exc_info()[0], sys.exc_info()[1]) f() # Outside except block, sys.exc_info() should be back to None's From 72be75f477328b16aba7eae281ef4de281073825 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 08:30:50 +1000 Subject: [PATCH 1309/2098] tests/run-tests.py: Improve skip detection for tests using slice. This skips some additional tests that use slice, and no longer skips `basics/string_format_modulo.py` which doesn't actually use slice (but `float/string_format_modulo.py` does). Signed-off-by: Damien George --- tests/run-tests.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 1aec3ad85e9..aeffa1b796a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -229,6 +229,26 @@ def open(self, path, mode): ), } +# These tests don't test slice explicitly but rather use it to perform the test. +tests_requiring_slice = ( + "basics/builtin_range.py", + "basics/bytearray1.py", + "basics/class_super.py", + "basics/containment.py", + "basics/errno1.py", + "basics/fun_str.py", + "basics/generator1.py", + "basics/globals_del.py", + "basics/memoryview1.py", + "basics/memoryview_gc.py", + "basics/object1.py", + "basics/python34.py", + "basics/struct_endian.py", + "float/string_format_modulo.py", + "misc/non_compliant.py", + "misc/rge_sm.py", +) + # Tests that require `import target_wiring` to work. tests_requiring_target_wiring = ( "extmod/machine_uart_irq_txidle.py", @@ -855,24 +875,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_inlineasm = args.inlineasm_arch is None - # 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", - "fun_str", - "generator1", - "globals_del", - "memoryview1", - "memoryview_gc", - "object1", - "python34", - "string_format_modulo", - "struct_endian", - ) - # Some tests shouldn't be run on GitHub Actions if os.getenv("GITHUB_ACTIONS") == "true": skip_tests.add("thread/stress_schedule.py") # has reliability issues @@ -909,6 +911,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if not args.unicode: skip_tests.add("extmod/json_loads.py") # tests loading a utf-8 character + if skip_slice: + skip_tests.update(tests_requiring_slice) + if not has_complex: skip_tests.add("float/complex1.py") skip_tests.add("float/complex1_intbig.py") @@ -982,7 +987,7 @@ def run_one_test(test_file): is_int_64 = test_name.startswith("int_64") or test_name.endswith("_int64") is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") is_set_type = test_name.startswith(("set_", "frozenset")) or test_name.endswith("_set") - is_slice = test_name.find("slice") != -1 or test_name in misc_slice_tests + is_slice = test_name.find("slice") != -1 is_async = test_name.startswith(("async_", "asyncio_")) or test_name.endswith("_async") is_const = test_name.startswith("const") is_fstring = test_name.startswith("string_fstring") From c176fe8702cd806cbd0981234a4d7328e37b1673 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 11:49:38 +1000 Subject: [PATCH 1310/2098] tests/run-tests.py: Skip more tests that need slice. Signed-off-by: Damien George --- tests/run-tests.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index aeffa1b796a..0672be771e9 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -244,7 +244,30 @@ def open(self, path, mode): "basics/object1.py", "basics/python34.py", "basics/struct_endian.py", + "extmod/btree1.py", + "extmod/deflate_decompress.py", + "extmod/framebuf16.py", + "extmod/framebuf4.py", + "extmod/machine1.py", + "extmod/time_mktime.py", + "extmod/time_res.py", + "extmod/tls_sslcontext_ciphers.py", + "extmod/vfs_fat_fileio1.py", + "extmod/vfs_fat_finaliser.py", + "extmod/vfs_fat_more.py", + "extmod/vfs_fat_ramdisk.py", + "extmod/vfs_fat_ramdisklarge.py", + "extmod/vfs_lfs.py", + "extmod/vfs_rom.py", "float/string_format_modulo.py", + "micropython/builtin_execfile.py", + "micropython/extreme_exc.py", + "micropython/heapalloc_fail_bytearray.py", + "micropython/heapalloc_fail_list.py", + "micropython/heapalloc_fail_memoryview.py", + "micropython/import_mpy_invalid.py", + "micropython/import_mpy_native.py", + "micropython/import_mpy_native_gc.py", "misc/non_compliant.py", "misc/rge_sm.py", ) From a3a2b9e9738332eb9033bc1c227c9a69e9476e97 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 11:50:29 +1000 Subject: [PATCH 1311/2098] tests/extmod: Improve skip detection of extmod tests. Signed-off-by: Damien George --- tests/extmod/machine_soft_timer.py | 12 +++++------- tests/extmod/select_poll_custom.py | 4 ++-- tests/extmod/tls_threads.py | 3 ++- tests/extmod/vfs_fat_ilistdir_del.py | 3 +-- tests/extmod/vfs_lfs_ilistdir_del.py | 3 +-- tests/extmod/vfs_posix_ilistdir_del.py | 3 +-- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/extmod/machine_soft_timer.py b/tests/extmod/machine_soft_timer.py index 1e9c66aa225..aa19becd8eb 100644 --- a/tests/extmod/machine_soft_timer.py +++ b/tests/extmod/machine_soft_timer.py @@ -1,20 +1,18 @@ # test "soft" machine.Timer (no hardware ID) import sys - -if sys.platform in ("esp32", "esp8266"): - print("SKIP") # TODO: Implement soft timers for esp32/esp8266 ports - raise SystemExit - - try: - import time, machine as machine + import time, machine machine.Timer except: print("SKIP") raise SystemExit +if sys.platform in ("esp32", "esp8266"): + print("SKIP") # TODO: Implement soft timers for esp32/esp8266 ports + raise SystemExit + # create and deinit t = machine.Timer(freq=1) t.deinit() diff --git a/tests/extmod/select_poll_custom.py b/tests/extmod/select_poll_custom.py index b854a8a14da..0d2ab07b232 100644 --- a/tests/extmod/select_poll_custom.py +++ b/tests/extmod/select_poll_custom.py @@ -1,13 +1,13 @@ # Test custom pollable objects implemented in Python. -from micropython import const - try: import socket, select, io except ImportError: print("SKIP") raise SystemExit +from micropython import const + _MP_STREAM_POLL = const(3) _MP_STREAM_GET_FILENO = const(10) diff --git a/tests/extmod/tls_threads.py b/tests/extmod/tls_threads.py index 4564abd3d80..1e0c3d23d2f 100644 --- a/tests/extmod/tls_threads.py +++ b/tests/extmod/tls_threads.py @@ -1,6 +1,5 @@ # Ensure that SSL sockets can be allocated from multiple # threads without thread safety issues -import unittest try: import _thread @@ -11,6 +10,8 @@ print("SKIP") raise SystemExit +import unittest + class TestSocket(io.IOBase): def write(self, buf): diff --git a/tests/extmod/vfs_fat_ilistdir_del.py b/tests/extmod/vfs_fat_ilistdir_del.py index a6e24ec92f3..964e6b12868 100644 --- a/tests/extmod/vfs_fat_ilistdir_del.py +++ b/tests/extmod/vfs_fat_ilistdir_del.py @@ -1,8 +1,7 @@ # Test ilistdir __del__ for VfsFat using a RAM device. -import gc try: - import os, vfs + import gc, os, vfs vfs.VfsFat except (ImportError, AttributeError): diff --git a/tests/extmod/vfs_lfs_ilistdir_del.py b/tests/extmod/vfs_lfs_ilistdir_del.py index f463b84b224..828c85a2588 100644 --- a/tests/extmod/vfs_lfs_ilistdir_del.py +++ b/tests/extmod/vfs_lfs_ilistdir_del.py @@ -1,8 +1,7 @@ # Test ilistdir __del__ for VfsLittle using a RAM device. -import gc try: - import vfs + import gc, vfs vfs.VfsLfs2 except (ImportError, AttributeError): diff --git a/tests/extmod/vfs_posix_ilistdir_del.py b/tests/extmod/vfs_posix_ilistdir_del.py index 78d7c854c54..8b5984cd81c 100644 --- a/tests/extmod/vfs_posix_ilistdir_del.py +++ b/tests/extmod/vfs_posix_ilistdir_del.py @@ -1,8 +1,7 @@ # Test ilistdir __del__ for VfsPosix. -import gc try: - import os, vfs + import gc, os, vfs vfs.VfsPosix except (ImportError, AttributeError): From 3de5a4c63c4414a35f31ff50e3642129c1eada02 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 11:50:46 +1000 Subject: [PATCH 1312/2098] tests/extmod/vfs_posix_paths.py: Use tuple instead of set. So it can be used on targets without set enabled (or at least not raise a SyntaxError when compiling it). Signed-off-by: Damien George --- tests/extmod/vfs_posix_paths.py | 2 +- tests/extmod/vfs_posix_paths.py.exp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/extmod/vfs_posix_paths.py b/tests/extmod/vfs_posix_paths.py index b4fedc6716f..c06318748a3 100644 --- a/tests/extmod/vfs_posix_paths.py +++ b/tests/extmod/vfs_posix_paths.py @@ -31,7 +31,7 @@ fs.mkdir("subdir/one") print('listdir("/"):', sorted(i[0] for i in fs.ilistdir("/"))) print('listdir("."):', sorted(i[0] for i in fs.ilistdir("."))) -print('getcwd() in {"", "/"}:', fs.getcwd() in {"", "/"}) +print('getcwd() in ("", "/"):', fs.getcwd() in ("", "/")) print('chdir("subdir"):', fs.chdir("subdir")) print("getcwd():", fs.getcwd()) print('mkdir("two"):', fs.mkdir("two")) diff --git a/tests/extmod/vfs_posix_paths.py.exp b/tests/extmod/vfs_posix_paths.py.exp index ecc13222aaa..d6a0960efd2 100644 --- a/tests/extmod/vfs_posix_paths.py.exp +++ b/tests/extmod/vfs_posix_paths.py.exp @@ -1,6 +1,6 @@ listdir("/"): ['subdir'] listdir("."): ['subdir'] -getcwd() in {"", "/"}: True +getcwd() in ("", "/"): True chdir("subdir"): None getcwd(): /subdir mkdir("two"): None From 381cd730c834a1d071e7e90787ef7354b8532142 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 11:51:36 +1000 Subject: [PATCH 1313/2098] tests/micropython: Improve skipping of tests using micropython module. Signed-off-by: Damien George --- tests/micropython/emg_exc.py | 3 +-- tests/micropython/emg_exc.py.exp | 2 +- tests/micropython/extreme_exc.py | 6 +++++- tests/micropython/heap_lock.py | 6 +++++- tests/micropython/heap_locked.py | 6 ++++-- tests/micropython/heapalloc.py | 6 +++++- tests/micropython/heapalloc_exc_compressed.py | 8 +++++++- tests/micropython/heapalloc_exc_compressed_emg_exc.py | 8 +++++++- tests/micropython/heapalloc_exc_raise.py | 8 +++++++- tests/micropython/heapalloc_fail_bytearray.py | 8 +++++++- tests/micropython/heapalloc_fail_dict.py | 8 +++++++- tests/micropython/heapalloc_fail_list.py | 8 +++++++- tests/micropython/heapalloc_fail_memoryview.py | 8 +++++++- tests/micropython/heapalloc_fail_set.py | 8 +++++++- tests/micropython/heapalloc_fail_tuple.py | 8 +++++++- tests/micropython/heapalloc_inst_call.py | 9 ++++++++- tests/micropython/heapalloc_int_from_bytes.py | 9 ++++++++- tests/micropython/heapalloc_str.py | 9 ++++++++- tests/micropython/heapalloc_super.py | 9 ++++++++- tests/micropython/heapalloc_traceback.py | 3 +-- tests/micropython/heapalloc_traceback.py.exp | 2 +- tests/micropython/heapalloc_yield_from.py | 8 +++++++- tests/micropython/kbd_intr.py | 6 +++--- tests/micropython/meminfo.py | 7 ++++--- tests/micropython/memstats.py | 7 ++++--- tests/micropython/opt_level.py | 8 +++++++- tests/micropython/opt_level_lineno.py | 8 +++++++- tests/micropython/ringio.py | 6 +++--- tests/micropython/ringio_async.py | 4 +--- tests/micropython/ringio_big.py | 6 +++--- tests/micropython/schedule.py | 6 +++--- tests/micropython/stack_use.py | 7 +++++-- 32 files changed, 165 insertions(+), 50 deletions(-) diff --git a/tests/micropython/emg_exc.py b/tests/micropython/emg_exc.py index 9a09956c861..42b709375ce 100644 --- a/tests/micropython/emg_exc.py +++ b/tests/micropython/emg_exc.py @@ -1,10 +1,9 @@ # test that emergency exceptions work -import micropython import sys try: - import io + import io, micropython except ImportError: print("SKIP") raise SystemExit diff --git a/tests/micropython/emg_exc.py.exp b/tests/micropython/emg_exc.py.exp index 0d4b80ce2bc..d18012d691a 100644 --- a/tests/micropython/emg_exc.py.exp +++ b/tests/micropython/emg_exc.py.exp @@ -1,4 +1,4 @@ Traceback (most recent call last): -, line 22, in f +, line 21, in f ValueError: 1 diff --git a/tests/micropython/extreme_exc.py b/tests/micropython/extreme_exc.py index ad819e408fd..17323b566d3 100644 --- a/tests/micropython/extreme_exc.py +++ b/tests/micropython/extreme_exc.py @@ -1,6 +1,10 @@ # test some extreme cases of allocating exceptions and tracebacks -import micropython +try: + import micropython +except ImportError: + print("SKIP") + raise SystemExit # Check for stackless build, which can't call functions without # allocating a frame on the heap. diff --git a/tests/micropython/heap_lock.py b/tests/micropython/heap_lock.py index f2892a6dc58..209a3143461 100644 --- a/tests/micropython/heap_lock.py +++ b/tests/micropython/heap_lock.py @@ -1,6 +1,10 @@ # check that heap_lock/heap_unlock work as expected -import micropython +try: + import micropython +except ImportError: + print("SKIP") + raise SystemExit l = [] l2 = list(range(100)) diff --git a/tests/micropython/heap_locked.py b/tests/micropython/heap_locked.py index d9e5b5d4090..d9d99493dd6 100644 --- a/tests/micropython/heap_locked.py +++ b/tests/micropython/heap_locked.py @@ -1,8 +1,10 @@ # test micropython.heap_locked() -import micropython +try: + import micropython -if not hasattr(micropython, "heap_locked"): + micropython.heap_locked +except (AttributeError, ImportError): print("SKIP") raise SystemExit diff --git a/tests/micropython/heapalloc.py b/tests/micropython/heapalloc.py index e19f8d0255d..010bf878a07 100644 --- a/tests/micropython/heapalloc.py +++ b/tests/micropython/heapalloc.py @@ -1,6 +1,10 @@ # check that we can do certain things without allocating heap memory -import micropython +try: + import micropython +except ImportError: + print("SKIP") + raise SystemExit # Check for stackless build, which can't call functions without # allocating a frame on heap. diff --git a/tests/micropython/heapalloc_exc_compressed.py b/tests/micropython/heapalloc_exc_compressed.py index aa071d641c2..96fe3ca4f7e 100644 --- a/tests/micropython/heapalloc_exc_compressed.py +++ b/tests/micropython/heapalloc_exc_compressed.py @@ -1,4 +1,10 @@ -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # Tests both code paths for built-in exception raising. # mp_obj_new_exception_msg_varg (exception requires decompression at raise-time to format) diff --git a/tests/micropython/heapalloc_exc_compressed_emg_exc.py b/tests/micropython/heapalloc_exc_compressed_emg_exc.py index 48ce9dd69e6..31d937b8f93 100644 --- a/tests/micropython/heapalloc_exc_compressed_emg_exc.py +++ b/tests/micropython/heapalloc_exc_compressed_emg_exc.py @@ -1,4 +1,10 @@ -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # Does the full test from heapalloc_exc_compressed.py but while the heap is # locked (this can only work when the emergency exception buf is enabled). diff --git a/tests/micropython/heapalloc_exc_raise.py b/tests/micropython/heapalloc_exc_raise.py index 99810e00750..917ccf42f1e 100644 --- a/tests/micropython/heapalloc_exc_raise.py +++ b/tests/micropython/heapalloc_exc_raise.py @@ -1,6 +1,12 @@ # Test that we can raise and catch (preallocated) exception # without memory allocation. -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit e = ValueError("error") diff --git a/tests/micropython/heapalloc_fail_bytearray.py b/tests/micropython/heapalloc_fail_bytearray.py index 1bf7ddd600b..9ee73d1c583 100644 --- a/tests/micropython/heapalloc_fail_bytearray.py +++ b/tests/micropython/heapalloc_fail_bytearray.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with bytearray -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit class GetSlice: diff --git a/tests/micropython/heapalloc_fail_dict.py b/tests/micropython/heapalloc_fail_dict.py index ce2d158bd09..04c79183578 100644 --- a/tests/micropython/heapalloc_fail_dict.py +++ b/tests/micropython/heapalloc_fail_dict.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with dict -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # create dict x = 1 diff --git a/tests/micropython/heapalloc_fail_list.py b/tests/micropython/heapalloc_fail_list.py index 9a2e9a555f3..7afb6dc1019 100644 --- a/tests/micropython/heapalloc_fail_list.py +++ b/tests/micropython/heapalloc_fail_list.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with list -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit class GetSlice: diff --git a/tests/micropython/heapalloc_fail_memoryview.py b/tests/micropython/heapalloc_fail_memoryview.py index da2d1abffa6..17e3e126247 100644 --- a/tests/micropython/heapalloc_fail_memoryview.py +++ b/tests/micropython/heapalloc_fail_memoryview.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with memoryview -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit class GetSlice: diff --git a/tests/micropython/heapalloc_fail_set.py b/tests/micropython/heapalloc_fail_set.py index 3c347660ad7..0c4d85eef62 100644 --- a/tests/micropython/heapalloc_fail_set.py +++ b/tests/micropython/heapalloc_fail_set.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with set -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # create set x = 1 diff --git a/tests/micropython/heapalloc_fail_tuple.py b/tests/micropython/heapalloc_fail_tuple.py index de79385e3e3..11718a8107b 100644 --- a/tests/micropython/heapalloc_fail_tuple.py +++ b/tests/micropython/heapalloc_fail_tuple.py @@ -1,6 +1,12 @@ # test handling of failed heap allocation with tuple -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # create tuple x = 1 diff --git a/tests/micropython/heapalloc_inst_call.py b/tests/micropython/heapalloc_inst_call.py index 14d8826bf06..f78aa3cf877 100644 --- a/tests/micropython/heapalloc_inst_call.py +++ b/tests/micropython/heapalloc_inst_call.py @@ -1,6 +1,13 @@ # Test that calling clazz.__call__() with up to at least 3 arguments # doesn't require heap allocation. -import micropython + +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit class Foo0: diff --git a/tests/micropython/heapalloc_int_from_bytes.py b/tests/micropython/heapalloc_int_from_bytes.py index 5fe50443ae3..3310ea95d14 100644 --- a/tests/micropython/heapalloc_int_from_bytes.py +++ b/tests/micropython/heapalloc_int_from_bytes.py @@ -1,6 +1,13 @@ # Test that int.from_bytes() for small number of bytes generates # small int. -import micropython + +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit micropython.heap_lock() print(int.from_bytes(b"1", "little")) diff --git a/tests/micropython/heapalloc_str.py b/tests/micropython/heapalloc_str.py index 39aa56ccd78..6372df5d37b 100644 --- a/tests/micropython/heapalloc_str.py +++ b/tests/micropython/heapalloc_str.py @@ -1,5 +1,12 @@ # String operations which don't require allocation -import micropython + +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit micropython.heap_lock() diff --git a/tests/micropython/heapalloc_super.py b/tests/micropython/heapalloc_super.py index 51afae3d83d..6839eee330a 100644 --- a/tests/micropython/heapalloc_super.py +++ b/tests/micropython/heapalloc_super.py @@ -1,5 +1,12 @@ # test super() operations which don't require allocation -import micropython + +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # Check for stackless build, which can't call functions without # allocating a frame on heap. diff --git a/tests/micropython/heapalloc_traceback.py b/tests/micropython/heapalloc_traceback.py index 4c5f99afeeb..5ded8c5f0c6 100644 --- a/tests/micropython/heapalloc_traceback.py +++ b/tests/micropython/heapalloc_traceback.py @@ -1,10 +1,9 @@ # test that we can generate a traceback without allocating -import micropython import sys try: - import io + import io, micropython except ImportError: print("SKIP") raise SystemExit diff --git a/tests/micropython/heapalloc_traceback.py.exp b/tests/micropython/heapalloc_traceback.py.exp index 71929db93d5..e0799aa2b35 100644 --- a/tests/micropython/heapalloc_traceback.py.exp +++ b/tests/micropython/heapalloc_traceback.py.exp @@ -1,5 +1,5 @@ StopIteration Traceback (most recent call last): - File , line 25, in test + File , line 24, in test StopIteration: diff --git a/tests/micropython/heapalloc_yield_from.py b/tests/micropython/heapalloc_yield_from.py index 95071718908..9d4f8c6940f 100644 --- a/tests/micropython/heapalloc_yield_from.py +++ b/tests/micropython/heapalloc_yield_from.py @@ -1,6 +1,12 @@ # Check that yield-from can work without heap allocation -import micropython +try: + import micropython + + micropython.heap_lock +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit # Yielding from a function generator diff --git a/tests/micropython/kbd_intr.py b/tests/micropython/kbd_intr.py index 81977aaa52f..e1ed7185ef0 100644 --- a/tests/micropython/kbd_intr.py +++ b/tests/micropython/kbd_intr.py @@ -1,10 +1,10 @@ # test the micropython.kbd_intr() function -import micropython - try: + import micropython + micropython.kbd_intr -except AttributeError: +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/meminfo.py b/tests/micropython/meminfo.py index 957f061f153..f4dd8fdb604 100644 --- a/tests/micropython/meminfo.py +++ b/tests/micropython/meminfo.py @@ -1,9 +1,10 @@ # tests meminfo functions in micropython module -import micropython +try: + import micropython -# these functions are not always available -if not hasattr(micropython, "mem_info"): + micropython.mem_info +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/memstats.py b/tests/micropython/memstats.py index 33204d908c6..0e2e7b1c0b3 100644 --- a/tests/micropython/memstats.py +++ b/tests/micropython/memstats.py @@ -1,9 +1,10 @@ # tests meminfo functions in micropython module -import micropython +try: + import micropython -# these functions are not always available -if not hasattr(micropython, "mem_total"): + micropython.mem_total +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/opt_level.py b/tests/micropython/opt_level.py index dd5493a7a3c..789197d8825 100644 --- a/tests/micropython/opt_level.py +++ b/tests/micropython/opt_level.py @@ -1,4 +1,10 @@ -import micropython as micropython +# test micropython.opt_level() + +try: + import micropython +except ImportError: + print("SKIP") + raise SystemExit # check we can get and set the level micropython.opt_level(0) diff --git a/tests/micropython/opt_level_lineno.py b/tests/micropython/opt_level_lineno.py index dda9092d868..fcfbe8643da 100644 --- a/tests/micropython/opt_level_lineno.py +++ b/tests/micropython/opt_level_lineno.py @@ -1,4 +1,10 @@ -import micropython as micropython +# test micropython.opt_level() and line numbers + +try: + import micropython +except ImportError: + print("SKIP") + raise SystemExit # check that level 3 doesn't store line numbers # the expected output is that any line is printed as "line 1" diff --git a/tests/micropython/ringio.py b/tests/micropython/ringio.py index 41092887984..d82c04565bd 100644 --- a/tests/micropython/ringio.py +++ b/tests/micropython/ringio.py @@ -1,10 +1,10 @@ # Check that micropython.RingIO works correctly. -import micropython - try: + import micropython + micropython.RingIO -except AttributeError: +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/ringio_async.py b/tests/micropython/ringio_async.py index 2a4befc3527..0db146451c9 100644 --- a/tests/micropython/ringio_async.py +++ b/tests/micropython/ringio_async.py @@ -1,9 +1,7 @@ # Check that micropython.RingIO works correctly with asyncio.Stream. -import micropython - try: - import asyncio + import asyncio, micropython asyncio.StreamWriter micropython.RingIO diff --git a/tests/micropython/ringio_big.py b/tests/micropython/ringio_big.py index d55c4c00b7c..ddbbae12a63 100644 --- a/tests/micropython/ringio_big.py +++ b/tests/micropython/ringio_big.py @@ -1,10 +1,10 @@ # Check that micropython.RingIO works correctly. -import micropython - try: + import micropython + micropython.RingIO -except AttributeError: +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/schedule.py b/tests/micropython/schedule.py index 6a91459ea3d..f3dd3266126 100644 --- a/tests/micropython/schedule.py +++ b/tests/micropython/schedule.py @@ -1,10 +1,10 @@ # test micropython.schedule() function -import micropython - try: + import micropython + micropython.schedule -except AttributeError: +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/micropython/stack_use.py b/tests/micropython/stack_use.py index 640bb8b2f38..5d36fdca2fe 100644 --- a/tests/micropython/stack_use.py +++ b/tests/micropython/stack_use.py @@ -1,7 +1,10 @@ # tests stack_use function in micropython module -import micropython -if not hasattr(micropython, "stack_use"): +try: + import micropython + + micropython.stack_use +except (ImportError, AttributeError): print("SKIP") raise SystemExit From dcbda765d1dc654cdcba06e0a01649256330794e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 1 Oct 2025 12:21:27 +1000 Subject: [PATCH 1314/2098] py/modmath: Make MICROPY_PY_MATH_POW_FIX_NAN also fix pow(x, NaN) cases. This is needed by the zephyr port. Combining it with the existing `MICROPY_PY_MATH_POW_FIX_NAN` option should be safe, and eliminates the need for a separate option. Signed-off-by: Damien George --- py/modmath.c | 6 +++++- py/mpconfig.h | 1 + py/objfloat.c | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/py/modmath.c b/py/modmath.c index 919a8ccd9d3..045c842150e 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -99,12 +99,16 @@ mp_float_t MICROPY_FLOAT_C_FUN(log2)(mp_float_t x) { MATH_FUN_1(sqrt, sqrt) // pow(x, y): returns x to the power of y #if MICROPY_PY_MATH_POW_FIX_NAN -mp_float_t pow_func(mp_float_t x, mp_float_t y) { +mp_float_t MICROPY_FLOAT_C_FUN(pow_func)(mp_float_t x, mp_float_t y) { // pow(base, 0) returns 1 for any base, even when base is NaN // pow(+1, exponent) returns 1 for any exponent, even when exponent is NaN if (x == MICROPY_FLOAT_CONST(1.0) || y == MICROPY_FLOAT_CONST(0.0)) { return MICROPY_FLOAT_CONST(1.0); } + // pow(base, NaN) returns NaN for any other value of base + if (isnan(y)) { + return y; + } return MICROPY_FLOAT_C_FUN(pow)(x, y); } MATH_FUN_2(pow, pow_func) diff --git a/py/mpconfig.h b/py/mpconfig.h index 45178034ac9..a6a21dd1c08 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1559,6 +1559,7 @@ typedef time_t mp_timestamp_t; #endif // Whether to provide fix for pow(1, NaN) and pow(NaN, 0), which both should be 1 not NaN. +// Also fixes pow(base, NaN) to return NaN for other values of base. #ifndef MICROPY_PY_MATH_POW_FIX_NAN #define MICROPY_PY_MATH_POW_FIX_NAN (0) #endif diff --git a/py/objfloat.c b/py/objfloat.c index 125b576fb61..b0ad70de4a3 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -301,6 +301,10 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t lhs_val = MICROPY_FLOAT_CONST(1.0); break; } + if (isnan(rhs_val)) { + lhs_val = rhs_val; + break; + } #endif lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break; From 798173dce521e3fe6c25933296a09f8e59478c2d Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 1 Oct 2025 12:22:48 +1000 Subject: [PATCH 1315/2098] zephyr/mpconfigport: Enable MICROPY_PY_MATH_POW_FIX_NAN. This is needed because zephyr incorrectly has `pow(-1, NaN) = 1`. Signed-off-by: Damien George --- ports/zephyr/mpconfigport.h | 1 + ports/zephyr/mpconfigport_minimal.h | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index f6b78e64f0c..6f55b0d7880 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -61,6 +61,7 @@ #define MICROPY_PY_BUILTINS_HELP_TEXT zephyr_help_text #define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_PY_MATH_POW_FIX_NAN (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" #define MICROPY_PY_MACHINE_I2C (1) diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index c802ba52540..27441192044 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -50,6 +50,7 @@ #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_PY_BUILTINS_COMPLEX (0) +#define MICROPY_PY_MATH_POW_FIX_NAN (1) // These features are enabled to get the test suite passing. #define MICROPY_FULL_CHECKS (1) From 957e6b05e69524d1b03f3ccb446b433085a4de8e Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 19:26:19 +1000 Subject: [PATCH 1316/2098] tools/ci.sh: Enable all possible tests on zephyr CI. With the recent improvements to the test suite, and fixes for `pow`, the full test suite can now be run (and appropriate tests will be automatically skipped). Signed-off-by: Damien George --- tools/ci.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index f55b004c45c..b10f3d12dcc 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -983,9 +983,7 @@ function ci_zephyr_build { 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 -t 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) + (cd tests && ./run-tests.py -t execpty:"qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -monitor null -serial pty -kernel ../ports/zephyr/build/zephyr/zephyr.elf") } ######################################################################################## From 7373cbba6b6f32476fc68a40a11fcba1cf1c7d3c Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 19:26:34 +1000 Subject: [PATCH 1317/2098] tests/import: Skip import tests where needed. Signed-off-by: Damien George --- tests/import/builtin_ext.py | 6 ++++++ tests/import/import_broken.py | 6 ++++++ tests/import/import_file.py | 4 ++++ tests/import/import_pkg9.py | 2 +- tests/import/import_star_error.py | 5 +++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/import/builtin_ext.py b/tests/import/builtin_ext.py index 87465f1d593..aecbbb2639a 100644 --- a/tests/import/builtin_ext.py +++ b/tests/import/builtin_ext.py @@ -1,3 +1,9 @@ +try: + import uos, utime +except ImportError: + print("SKIP") + raise SystemExit + # Verify that sys is a builtin. import sys diff --git a/tests/import/import_broken.py b/tests/import/import_broken.py index 3c7cf4a4985..440922157ef 100644 --- a/tests/import/import_broken.py +++ b/tests/import/import_broken.py @@ -1,3 +1,9 @@ +try: + Exception.__class__ +except AttributeError: + print("SKIP") + raise SystemExit + import sys, pkg # Modules we import are usually added to sys.modules. diff --git a/tests/import/import_file.py b/tests/import/import_file.py index 90ec4e41e77..4cf307641c6 100644 --- a/tests/import/import_file.py +++ b/tests/import/import_file.py @@ -1,3 +1,7 @@ +if "__file__" not in globals(): + print("SKIP") + raise SystemExit + import import1b print(import1b.__file__) diff --git a/tests/import/import_pkg9.py b/tests/import/import_pkg9.py index 4de028494f1..c2e0b618b69 100644 --- a/tests/import/import_pkg9.py +++ b/tests/import/import_pkg9.py @@ -13,4 +13,4 @@ import pkg9.mod2 pkg9.mod1() -print(pkg9.mod2.__name__, type(pkg9.mod2).__name__) +print(pkg9.mod2.__name__, type(pkg9.mod2)) diff --git a/tests/import/import_star_error.py b/tests/import/import_star_error.py index 9e1757b6ef5..73d9c863d6f 100644 --- a/tests/import/import_star_error.py +++ b/tests/import/import_star_error.py @@ -1,5 +1,10 @@ # test errors with import * +if not hasattr(object, "__init__"): + # target doesn't have MICROPY_CPYTHON_COMPAT enabled, so doesn't check for "import *" + print("SKIP") + raise SystemExit + # 'import *' is not allowed in function scope try: exec("def foo(): from x import *") From cd6d9387105e5fca9764283e4b3b27b3b83d37ce Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 14 Sep 2025 19:33:26 +1000 Subject: [PATCH 1318/2098] tests/extmod/asyncio_heaplock.py: Improve skip detection of test. Signed-off-by: Damien George --- tests/extmod/asyncio_heaplock.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/extmod/asyncio_heaplock.py b/tests/extmod/asyncio_heaplock.py index 8326443f0e6..9e9908de1cb 100644 --- a/tests/extmod/asyncio_heaplock.py +++ b/tests/extmod/asyncio_heaplock.py @@ -4,7 +4,11 @@ # - StreamWriter.write, stream is blocked and data to write is a bytes object # - StreamWriter.write, when stream is not blocked -import micropython +try: + import asyncio, micropython +except ImportError: + print("SKIP") + raise SystemExit # strict stackless builds can't call functions without allocating a frame on the heap try: @@ -24,12 +28,6 @@ def f(x): print("SKIP") raise SystemExit -try: - import asyncio -except ImportError: - print("SKIP") - raise SystemExit - class TestStream: def __init__(self, blocked): From 110bff9422701fde392e626d1c60f7c933bf7794 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:01:10 +1000 Subject: [PATCH 1319/2098] tests/run-tests.py: Don't include cmdline,io tests for minimal targets. The unix minimal variant cannot run these tests because: - it doesn't support -v -v debug printing - it doesn't have `open()` Signed-off-by: Damien George --- tests/run-tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 0672be771e9..404378b7946 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1469,11 +1469,9 @@ def main(): test_dirs += (port_specific_test_dir,) if args.platform in PC_PLATFORMS: # run PC tests - test_dirs += ( - "import", - "io", - "cmdline", - ) + test_dirs += ("import",) + if args.build != "minimal": + test_dirs += ("cmdline", "io") else: # run tests from these directories test_dirs = args.test_dirs From 1b9a9dde2d9dfc3e6dce7c69a93a54479101963c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:02:18 +1000 Subject: [PATCH 1320/2098] tests/basics/subclass_native_init.py: Skip if __init__ not supported. Signed-off-by: Damien George --- tests/basics/subclass_native_init.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/basics/subclass_native_init.py b/tests/basics/subclass_native_init.py index 64167fa037e..102befd551f 100644 --- a/tests/basics/subclass_native_init.py +++ b/tests/basics/subclass_native_init.py @@ -1,5 +1,9 @@ # test subclassing a native type and overriding __init__ +if not hasattr(object, "__init__"): + print("SKIP") + raise SystemExit + # overriding list.__init__() class L(list): def __init__(self, a, b): From 1a96f72b5b81e15355526fe933bed92863cec77b Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:05:46 +1000 Subject: [PATCH 1321/2098] tests/run-tests.py: Update list of tests that use float. Signed-off-by: Damien George --- tests/run-tests.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 404378b7946..82a6a3d9724 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -229,6 +229,36 @@ def open(self, path, mode): ), } +# These tests don't test float explicitly but rather use it to perform the test. +tests_requiring_float = ( + "extmod/asyncio_basic.py", + "extmod/asyncio_basic2.py", + "extmod/asyncio_cancel_task.py", + "extmod/asyncio_event.py", + "extmod/asyncio_fair.py", + "extmod/asyncio_gather.py", + "extmod/asyncio_gather_notimpl.py", + "extmod/asyncio_get_event_loop.py", + "extmod/asyncio_iterator_event.py", + "extmod/asyncio_lock.py", + "extmod/asyncio_task_done.py", + "extmod/asyncio_wait_for.py", + "extmod/asyncio_wait_for_fwd.py", + "extmod/asyncio_wait_for_linked_task.py", + "extmod/asyncio_wait_task.py", + "extmod/json_dumps_float.py", + "extmod/json_loads_float.py", + "extmod/random_extra_float.py", + "extmod/select_poll_eintr.py", + "extmod/tls_threads.py", + "extmod/uctypes_le_float.py", + "extmod/uctypes_native_float.py", + "extmod/uctypes_sizeof_float.py", + "misc/rge_sm.py", + "ports/unix/ffi_float.py", + "ports/unix/ffi_float2.py", +) + # These tests don't test slice explicitly but rather use it to perform the test. tests_requiring_slice = ( "basics/builtin_range.py", @@ -907,13 +937,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("misc/sys_settrace_features.py") if args.float_prec == 0: - skip_tests.add("extmod/uctypes_le_float.py") - skip_tests.add("extmod/uctypes_native_float.py") - skip_tests.add("extmod/uctypes_sizeof_float.py") - skip_tests.add("extmod/json_dumps_float.py") - skip_tests.add("extmod/json_loads_float.py") - skip_tests.add("extmod/random_extra_float.py") - skip_tests.add("misc/rge_sm.py") + skip_tests.update(tests_requiring_float) if args.float_prec < 32: skip_tests.add( "float/float2int_intbig.py" From 9b5e20dc20b54df432460d81a3a6189f6d601226 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:40:48 +1000 Subject: [PATCH 1322/2098] tests/stress/bytecode_limit.py: Tweak to run on minimal builds. Signed-off-by: Damien George --- tests/stress/bytecode_limit.py | 18 ++++++++++++++---- tests/stress/bytecode_limit.py.exp | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/stress/bytecode_limit.py b/tests/stress/bytecode_limit.py index 948d7668da5..0a72b66fa05 100644 --- a/tests/stress/bytecode_limit.py +++ b/tests/stress/bytecode_limit.py @@ -1,19 +1,29 @@ # Test the limits of bytecode generation. +import sys + +# Tune the test parameters based on the target's bytecode generator. +if hasattr(sys.implementation, "_mpy"): + # Target can load .mpy files so generated bytecode uses 1 byte per qstr. + number_of_body_copies = (433, 432, 431, 399) +else: + # Target can't load .mpy files so generated bytecode uses 2 bytes per qstr. + number_of_body_copies = (401, 400, 399, 398) + body = " with f()()() as a:\n try:\n f()()()\n except Exception:\n pass\n" # Test overflow of jump offset. # Print results at the end in case an intermediate value of n fails with MemoryError. results = [] -for n in (433, 432, 431, 430): +for n in number_of_body_copies: try: exec("cond = 0\nif cond:\n" + body * n + "else:\n print('cond false')\n") - results.append((n, "ok")) + results.append("ok") except MemoryError: print("SKIP") raise SystemExit - except RuntimeError: - results.append((n, "RuntimeError")) + except RuntimeError as er: + results.append(repr(er)) print(results) # Test changing size of code info (source line/bytecode mapping) due to changing diff --git a/tests/stress/bytecode_limit.py.exp b/tests/stress/bytecode_limit.py.exp index cda52b1b973..50511665f00 100644 --- a/tests/stress/bytecode_limit.py.exp +++ b/tests/stress/bytecode_limit.py.exp @@ -1,4 +1,4 @@ cond false cond false -[(433, 'RuntimeError'), (432, 'RuntimeError'), (431, 'ok'), (430, 'ok')] +["RuntimeError('bytecode overflow',)", "RuntimeError('bytecode overflow',)", 'ok', 'ok'] [123] From 9a37e2feb93079dc3154db3f891d21e4fd80b0fa Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:41:10 +1000 Subject: [PATCH 1323/2098] tests/ports/unix: Improve skip detection for os.getenv and time module. Signed-off-by: Damien George --- tests/ports/unix/mod_os.py | 3 +++ tests/ports/unix/time_mktime_localtime.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ports/unix/mod_os.py b/tests/ports/unix/mod_os.py index f69fa45b2b2..468f6badd1e 100644 --- a/tests/ports/unix/mod_os.py +++ b/tests/ports/unix/mod_os.py @@ -1,6 +1,9 @@ # This module is not entirely compatible with CPython import os +if not hasattr(os, "getenv"): + print("SKIP") + raise SystemExit os.putenv("TEST_VARIABLE", "TEST_VALUE") diff --git a/tests/ports/unix/time_mktime_localtime.py b/tests/ports/unix/time_mktime_localtime.py index d1c03c103d7..df5d6cda690 100644 --- a/tests/ports/unix/time_mktime_localtime.py +++ b/tests/ports/unix/time_mktime_localtime.py @@ -1,4 +1,8 @@ -import time +try: + import time +except ImportError: + print("SKIP") + raise SystemExit DAYS_PER_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] From 7db50ccf5fd0932bacd40436c44d3283e1441d61 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 1 Oct 2025 15:17:13 +1000 Subject: [PATCH 1324/2098] tests/basics: Skip exception_chain and self_type_check on unix minimal. These two tests can't run on the unix minimal build because it doesn't have the relevant build options enabled. So skip them. Signed-off-by: Damien George --- tests/basics/exception_chain.py | 7 +++++++ tests/basics/self_type_check.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/tests/basics/exception_chain.py b/tests/basics/exception_chain.py index 14dd6dfbade..cf19f049500 100644 --- a/tests/basics/exception_chain.py +++ b/tests/basics/exception_chain.py @@ -1,6 +1,13 @@ # Exception chaining is not supported, but check that basic # exception works as expected. +import sys + +# The unix minimal build doesn't enable MICROPY_WARNINGS (required for this test). +if getattr(sys.implementation, "_build", None) == "minimal": + print("SKIP") + raise SystemExit + try: raise Exception from None except Exception: diff --git a/tests/basics/self_type_check.py b/tests/basics/self_type_check.py index 947e362cdbd..c182afd783d 100644 --- a/tests/basics/self_type_check.py +++ b/tests/basics/self_type_check.py @@ -1,5 +1,13 @@ # make sure type of first arg (self) to a builtin method is checked +import sys + +# Minimal builds usually don't enable MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG, +# which is required for this test. +if getattr(sys.implementation, "_build", None) == "minimal": + print("SKIP") + raise SystemExit + list.append try: From 688016c6a2e75e062c4b55fe1f50e40db9252ba6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:41:35 +1000 Subject: [PATCH 1325/2098] unix/variants/minimal: Enable C stack checking. This unix minimal variant already has a few other things enabled beyond the default minimal configuration, and C-stack checking is arguably more important than those, so enable it. This helps to get some of the stress tests passing on this variant. Signed-off-by: Damien George --- ports/unix/variants/minimal/mpconfigvariant.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/unix/variants/minimal/mpconfigvariant.h b/ports/unix/variants/minimal/mpconfigvariant.h index 2edac41c7fd..0e280a8f730 100644 --- a/ports/unix/variants/minimal/mpconfigvariant.h +++ b/ports/unix/variants/minimal/mpconfigvariant.h @@ -49,6 +49,7 @@ #define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) #define MICROPY_ENABLE_COMPILER (1) #define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_STACK_CHECK (1) #define MICROPY_FULL_CHECKS (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_KBD_EXCEPTION (1) From b2871e0d0aec0c3783fe4bad215fbd8a4d88e5fe Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Sep 2025 13:43:18 +1000 Subject: [PATCH 1326/2098] tools/ci.sh: Enable the full test suite for unix minimal variant. With all the preceeding improvements to the test suite, it's now possible to just run `make VARIANT=minimal test` -- which is equivalent to just `./run-tests.py` -- on the unix minimal variant. Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index b10f3d12dcc..a5555647bd7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -631,7 +631,7 @@ function ci_unix_minimal_build { } function ci_unix_minimal_run_tests { - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/build-minimal/micropython ./run-tests.py -e exception_chain -e self_type_check -e subclass_native_init -d basics) + make -C ports/unix VARIANT=minimal test } function ci_unix_standard_build { From 7ef47ef98d8b98f9c2786e8d58a0307f59dceebb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 25 Sep 2024 17:46:59 +1000 Subject: [PATCH 1327/2098] tests/serial_test.py: Add test for serial throughput. This is a test script used to test USB CDC (or USB UART) serial reliability and throughput. Run against any MicroPython remote target with: $ python serial_test.py -t Signed-off-by: Damien George --- tests/README.md | 12 ++ tests/serial_test.py | 263 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100755 tests/serial_test.py diff --git a/tests/README.md b/tests/README.md index 676e9d83205..534e7e0a059 100644 --- a/tests/README.md +++ b/tests/README.md @@ -203,6 +203,18 @@ internal_bench/bytebuf: 1 tests performed (3 individual testcases) ``` +## Serial reliability and performance test + +Serial port reliability and performance can be tested using the `serial_test.py` script. +Pass the name of the port to test against, for example: + + $ ./serial_test.py -t /dev/ttyACM0 + +If no port is specified then `/dev/ttyACM0` is used as the default. + +The test will send data out to the target, and receive data from the target, in various +chunk sizes. The throughput of the serial connection will be reported for each sub-test. + ## Test key/certificates SSL/TLS tests in `multi_net` and `net_inet` use self-signed key/cert pairs diff --git a/tests/serial_test.py b/tests/serial_test.py new file mode 100755 index 00000000000..1baa2282a0a --- /dev/null +++ b/tests/serial_test.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# +# Performance and reliability test for serial port communication. +# +# Basic usage: +# serial_test.py [-t serial-device] +# +# The `serial-device` will default to /dev/ttyACM0. + +import argparse +import serial +import sys +import time + +run_tests_module = __import__("run-tests") + +read_test_script = """ +bin = True +try: + wr=__import__("pyb").USB_VCP(0).send +except: + import sys + if hasattr(sys.stdout,'buffer'): + wr=sys.stdout.buffer.write + else: + wr=sys.stdout.write + bin = False +b=bytearray(%u) +if bin: + wr('BIN') + for i in range(len(b)): + b[i] = i & 0xff +else: + wr('TXT') + for i in range(len(b)): + b[i] = 0x20 + (i & 0x3f) +for _ in range(%d): + wr(b) +""" + + +write_test_script_verified = """ +import sys +try: + rd=__import__("pyb").USB_VCP(0).recv +except: + rd=sys.stdin.readinto +b=bytearray(%u) +for _ in range(%u): + n = rd(b) + fail = 0 + for i in range(n): + if b[i] != 32 + (i & 0x3f): + fail += 1 + if fail: + sys.stdout.write(b'ER%%05u' %% fail) + else: + sys.stdout.write(b'OK%%05u' %% n) +""" + +write_test_script_unverified = """ +import sys +try: + rd=__import__("pyb").USB_VCP(0).recv +except: + rd=sys.stdin.readinto +b=bytearray(%u) +for _ in range(%u): + n = rd(b) + if n != len(b): + sys.stdout.write(b'ER%%05u' %% n) + else: + sys.stdout.write(b'OK%%05u' %% n) +""" + + +class TestError(Exception): + pass + + +def drain_input(ser): + time.sleep(0.1) + while ser.inWaiting() > 0: + data = ser.read(ser.inWaiting()) + time.sleep(0.1) + + +def send_script(ser, script): + chunk_size = 32 + for i in range(0, len(script), chunk_size): + ser.write(script[i : i + chunk_size]) + time.sleep(0.01) + ser.write(b"\x04") # eof + ser.flush() + response = ser.read(2) + if response != b"OK": + response += ser.read(ser.inWaiting()) + raise TestError("could not send script", response) + + +def read_test(ser_repl, ser_data, bufsize, nbuf): + global test_passed + + assert bufsize % 256 == 0 # for verify to work + + # Load and run the read_test_script. + ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot + drain_input(ser_repl) + script = bytes(read_test_script % (bufsize, nbuf), "ascii") + send_script(ser_repl, script) + + # Read from the device the type of data that it will send (BIN or TXT). + data_type = ser_data.read(3) + + # Read data from the device, check it is correct, and measure throughput. + n = 0 + last_byte = None + t_start = time.time() + remain = nbuf * bufsize + total_data = bytearray(remain) + while remain: + t0 = time.monotonic_ns() + while ser_data.inWaiting() == 0: + if time.monotonic_ns() - t0 > 1e9: + # timeout waiting for data from device + break + time.sleep(0.0001) + if not ser_data.inWaiting(): + test_passed = False + print("ERROR: timeout waiting for data") + print(total_data[:n]) + return 0 + to_read = min(ser_data.inWaiting(), remain) + data = ser_data.read(to_read) + remain -= len(data) + print(f"{n} / {nbuf * bufsize}", end="\r") + total_data[n : n + len(data)] = data + n += len(data) + t_end = time.time() + for i in range(0, len(total_data)): + if data_type == b"BIN": + wanted = i & 0xFF + else: + wanted = 0x20 + (i & 0x3F) + if total_data[i] != wanted: + test_passed = False + print("ERROR: data mismatch:", i, wanted, total_data[i]) + ser_repl.write(b"\x03") # break + t = t_end - t_start + + # Print results. + print( + "DATA IN: bufsize=%u, read %u bytes in %.2f msec = %.2f kibytes/sec = %.2f MBits/sec" + % (bufsize, n, t * 1000, n / 1024 / t, n * 8 / 1000000 / t) + ) + + return n / t + + +def write_test(ser_repl, ser_data, bufsize, nbuf, verified): + global test_passed + + # Load and run the write_test_script. + ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot + drain_input(ser_repl) + if verified: + script = write_test_script_verified + else: + script = write_test_script_unverified + script = bytes(script % (bufsize, nbuf), "ascii") + send_script(ser_repl, script) + drain_input(ser_repl) + + # Write data to the device, check it is correct, and measure throughput. + n = 0 + t_start = time.time() + buf = bytearray(bufsize) + for i in range(len(buf)): + buf[i] = 32 + (i & 0x3F) # don't want to send ctrl chars! + for i in range(nbuf): + ser_data.write(buf) + n += len(buf) + print(f"{n} / {nbuf * bufsize}", end="\r") + response = ser_repl.read(7) + if response != b"OK%05u" % bufsize: + test_passed = False + print("ERROR: bad response, expecting OK%05u, got %r" % (bufsize, response)) + t_end = time.time() + ser_repl.write(b"\x03") # break + t = t_end - t_start + + # Print results. + print( + "DATA OUT: verify=%d, bufsize=%u, wrote %u bytes in %.2f msec = %.2f kibytes/sec = %.2f MBits/sec" + % (verified, bufsize, n, t * 1000, n / 1024 / t, n * 8 / 1000000 / t) + ) + + return n / t + + +def do_test(dev_repl, dev_data=None, time_per_subtest=1): + if dev_data is None: + print("REPL and data on", dev_repl) + ser_repl = serial.Serial(dev_repl, baudrate=115200, timeout=1) + ser_data = ser_repl + else: + print("REPL on", dev_repl) + print("data on", dev_data) + ser_repl = serial.Serial(dev_repl, baudrate=115200, timeout=1) + ser_data = serial.Serial(dev_data, baudrate=115200, timeout=1) + + for test_func, test_args, bufsize in ( + (read_test, (), 256), + (write_test, (True,), 128), + (write_test, (False,), 128), + ): + nbuf = 128 + while bufsize <= 16384: + rate = test_func(ser_repl, ser_data, bufsize, nbuf, *test_args) + bufsize *= 2 + if rate: + # Adjust the amount of data based on the rate, to keep each subtest + # at around time_per_subtest seconds long. + nbuf = max(min(128, int(rate * time_per_subtest / bufsize)), 1) + + ser_repl.close() + ser_data.close() + + +def main(): + global test_passed + + cmd_parser = argparse.ArgumentParser( + description="Test performance and reliability of serial port communication.", + epilog=run_tests_module.test_instance_epilog, + formatter_class=argparse.RawTextHelpFormatter, + ) + cmd_parser.add_argument( + "-t", + "--test-instance", + default="a0", + help="MicroPython instance to test", + ) + cmd_parser.add_argument( + "--time-per-subtest", default="1", help="approximate time to take per subtest (in seconds)" + ) + args = cmd_parser.parse_args() + + dev_repl = run_tests_module.convert_device_shortcut_to_real_device(args.test_instance) + + test_passed = True + try: + do_test(dev_repl, None, float(args.time_per_subtest)) + except TestError as er: + test_passed = False + print("ERROR:", er) + + if not test_passed: + sys.exit(1) + + +if __name__ == "__main__": + main() From 3546f23143ed7fee66cf873dd224082d0cc424cb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 3 Sep 2025 09:54:07 +0200 Subject: [PATCH 1328/2098] esp32/machine_i2c: Update to support both v1 and v2 of the IDF driver. Support both the legacy ESP-IDF I2C driver (driver/i2c.h) and the new driver/i2c_master.h API for all ESP32-series SoCs. This is controlled by the new config option MICROPY_HW_ESP_NEW_I2C_DRIVER, which is disabled by default. There is a small change to the legacy variant, which shows now as well freq and timeout_us in the print() method. Open issues with the new driver: - No control of the stop=True|False option. stop is always assumed to be True. An issue is raised at the esp-idf repository for it. - Separate call to probe the address at fixed 100kHz. The need is caused by the fact, that NACK on an address is considered as an hard error. According to espressif's issue tracker this is already changed for v6.x and will be backported to earlier versions. So we may see it in a version after V5.5.1 or in a version 5.4.3. There is code in comments to support zero-length writes. Original patch for the v2 driver by Vincent1-python . Signed-off-by: robert-hh --- ports/esp32/machine_i2c.c | 245 ++++++++++++++++++++++++++++++++----- ports/esp32/mpconfigport.h | 3 + 2 files changed, 216 insertions(+), 32 deletions(-) diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index a9e5f0d3b32..e8d913def93 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George + * Copyright (c) 2025 Vincent1-python * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,11 +31,196 @@ #include "extmod/modmachine.h" #include "machine_i2c.h" +#if MICROPY_HW_ESP_NEW_I2C_DRIVER +#include "driver/i2c_master.h" +#else #include "driver/i2c.h" #include "hal/i2c_ll.h" +#endif #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C +#define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms + +// CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is set if the related sdkconfig +// option is set. + +#if MICROPY_HW_ESP_NEW_I2C_DRIVER + +typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_master_bus_handle_t bus_handle; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_master_dev_handle_t dev_handle; + #endif + uint8_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; +} machine_hw_i2c_obj_t; + +static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; + +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + if (!first_init && self->dev_handle) { + i2c_master_bus_rm_device(self->dev_handle); + self->dev_handle = NULL; + } + #endif + + if (!first_init && self->bus_handle) { + i2c_del_master_bus(self->bus_handle); + self->bus_handle = NULL; + } + + i2c_master_bus_config_t bus_cfg = { + .i2c_port = self->port, + .scl_io_num = self->scl, + .sda_io_num = self->sda, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &self->bus_handle)); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = 0, // Will be replaced + .scl_speed_hz = self->freq, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &self->dev_handle)); + #endif +} + +static uint8_t *create_transfer_buffer(size_t n, mp_machine_i2c_buf_t *bufs, size_t *len_ptr) { + size_t len = 0; + uint8_t *buf; + if (n == 1) { + // Use given single buffer + len = bufs[0].len; + buf = bufs[0].buf; + } else { + // Allocate a buffer that can hold the data from all buffers + len = 0; + for (size_t i = 0; i < n; ++i) { + len += bufs[i].len; + } + buf = m_new(uint8_t, len); + } + *len_ptr = len; + return buf; +} + +int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Probe the address to see if any device responds. + // This test uses a fixed scl freq of 100_000. + esp_err_t err = i2c_master_probe(self->bus_handle, addr, self->timeout_us / 1000); + if (err != ESP_OK) { + return -MP_ENODEV; // No device at address, return immediately + } + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Using ".device_address = I2C_DEVICE_ADDRESS_NOT_USED," below + // allows to write the address separately using the + // i2c_master_execute_defined_operations() API. + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = self->freq, + }; + i2c_master_dev_handle_t dev_handle; + err = i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &dev_handle); + #else + #define dev_handle self->dev_handle + err = i2c_master_device_change_address(dev_handle, addr, self->timeout_us / 1000); + #endif + if (err != ESP_OK) { + return -MP_ENODEV; + } + + size_t len = 0; + uint8_t *buf; + + // Assume that with MP_MACHINE_I2C_FLAG_WRITE1 set the first + // buffer has to be written and all others have to be read. + // extmod/read_mem() uses only a single buffer for reading, but + // the other implementation(s) support multiple buffers. + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + // create a large buffer if needed + buf = create_transfer_buffer(n - 1, bufs + 1, &len); + // Do a write then read + err = i2c_master_transmit_receive(dev_handle, bufs[0].buf, bufs[0].len, buf, len, self->timeout_us / 1000); + // Copy the data back if needed starting with the second buffer. + if (n > 2) { + len = 0; + for (size_t i = 1; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; + } + m_del(uint8_t, buf, len); + } + len += bufs[0].len; + } else if (bufs->len > 0) { + buf = create_transfer_buffer(n, bufs, &len); + // Transfer data and copy it from/to the buffers as needed. + if (flags & MP_MACHINE_I2C_FLAG_READ) { + err = i2c_master_receive(dev_handle, buf, len, self->timeout_us / 1000); + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; + } + } + } else { + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(buf + len, bufs[i].buf, bufs[i].len); + len += bufs[i].len; + } + } + err = i2c_master_transmit(dev_handle, buf, len, self->timeout_us / 1000); + // Use i2c_master_execute_defined_operations() instead of + // i2c_master_transmit(), allowing for len == 0. + // That will be needed for scan() when dropping i2c_master_probe() is possible, + // after https://github.com/espressif/esp-idf/issues/17543 backported to supported versions + // i2c_operation_job_t i2c_ops[] = { + // { .command = I2C_MASTER_CMD_START }, + // { .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = true, .data = buf, .total_bytes = len } }, + // { .command = I2C_MASTER_CMD_STOP }, // Stop is still mandatory + // }; + // err = i2c_master_execute_defined_operations(dev_handle, i2c_ops, 3, self->timeout_us / 1000); + } + if (n > 1) { + m_del(uint8_t, buf, len); + } + } + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Remove the temporary handle. + i2c_master_bus_rm_device(dev_handle); + #endif + + // Map errors + if (err == ESP_FAIL) { + return -MP_ENODEV; + } + if (err == ESP_ERR_TIMEOUT) { + return -MP_ETIMEDOUT; + } + if (err != ESP_OK) { + return -abs(err); + } + return len; +} + +#else + #if SOC_I2C_SUPPORT_XTAL #if CONFIG_XTAL_FREQ > 0 #define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) @@ -47,18 +233,18 @@ #error "unsupported I2C for ESP32 SoC variant" #endif -#define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms - typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; i2c_port_t port : 8; gpio_num_t scl : 8; gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; } machine_hw_i2c_obj_t; static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; -static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { if (!first_init) { i2c_driver_delete(self->port); } @@ -68,10 +254,10 @@ static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint3 .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = self->scl, .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = freq, + .master.clk_speed = self->freq, }; i2c_param_config(self->port, &conf); - int timeout = I2C_SCLK_FREQ / 1000000 * timeout_us; + int timeout = I2C_SCLK_FREQ / 1000000 * self->timeout_us; i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); } @@ -124,15 +310,16 @@ int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_ return data_len; } + +#endif // MICROPY_HW_ESP_NEW_I2C_DRIVER + /******************************************************************************/ // MicroPython bindings for machine API static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - int h, l; - i2c_get_period(self->port, &h, &l); - mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u)", - self->port, self->scl, self->sda, I2C_SCLK_FREQ / (h + l)); + mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u, timeout=%u)", + self->port, self->scl, self->sda, self->freq, self->timeout_us); } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -147,49 +334,43 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ { MP_QSTR_id, MP_ARG_INT, {.u_int = I2C_NUM_0} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT_US} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - // Get I2C bus mp_int_t i2c_id = args[ARG_id].u_int; - - // Check if the I2C bus is valid if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } - // Get static peripheral object - machine_hw_i2c_obj_t *self = (machine_hw_i2c_obj_t *)&machine_hw_i2c_obj[i2c_id]; + machine_hw_i2c_obj_t *self = &machine_hw_i2c_obj[i2c_id]; - bool first_init = false; - if (self->base.type == NULL) { - // Created for the first time, set default pins + bool first_init = (self->base.type == NULL); + if (first_init) { self->base.type = &machine_i2c_type; self->port = i2c_id; - if (self->port == I2C_NUM_0) { - self->scl = MICROPY_HW_I2C0_SCL; - self->sda = MICROPY_HW_I2C0_SDA; - } else { - self->scl = MICROPY_HW_I2C1_SCL; - self->sda = MICROPY_HW_I2C1_SDA; - } - first_init = true; + self->scl = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SCL : MICROPY_HW_I2C1_SCL; + self->sda = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SDA : MICROPY_HW_I2C1_SDA; + self->freq = 400000; + self->timeout_us = I2C_DEFAULT_TIMEOUT_US; } - // Set SCL/SDA pins if given if (args[ARG_scl].u_obj != MP_OBJ_NULL) { self->scl = machine_pin_get_id(args[ARG_scl].u_obj); } if (args[ARG_sda].u_obj != MP_OBJ_NULL) { self->sda = machine_pin_get_id(args[ARG_sda].u_obj); } + if (args[ARG_freq].u_int != -1) { + self->freq = args[ARG_freq].u_int; + } + if (args[ARG_timeout].u_int != -1) { + self->timeout_us = args[ARG_timeout].u_int; + } - // Initialise the I2C peripheral - machine_hw_i2c_init(self, args[ARG_freq].u_int, args[ARG_timeout].u_int, first_init); - + machine_hw_i2c_init(self, first_init); return MP_OBJ_FROM_PTR(self); } @@ -208,4 +389,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &mp_machine_i2c_locals_dict ); -#endif +#endif // MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 0e62cd74823..4614abc3c8d 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -186,6 +186,9 @@ #endif #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (esp_rom_get_cpu_ticks_per_us() * 1000000 / 200) // roughly +#ifndef MICROPY_HW_ESP_NEW_I2C_DRIVER +#define MICROPY_HW_ESP_NEW_I2C_DRIVER (0) +#endif #define MICROPY_PY_SSL (1) #define MICROPY_SSL_MBEDTLS (1) #define MICROPY_PY_WEBSOCKET (1) From 7d50952c2b2d335e9b5528fe1db57fb5f0f4ef0d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 29 Sep 2025 07:57:41 -0500 Subject: [PATCH 1329/2098] all: Use "static inline" consistently in function definitions. Signed-off-by: Jeff Epler --- ports/renesas-ra/ra/ra_gpio.h | 4 ++-- ports/rp2/mutex_extra.h | 4 ++-- ports/stm32/dma.h | 4 ++-- py/cstack.h | 4 ++-- py/misc.h | 4 ++-- shared/tinyusb/mp_usbd.h | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ports/renesas-ra/ra/ra_gpio.h b/ports/renesas-ra/ra/ra_gpio.h index 58df7aafe99..5c0f9eff8db 100644 --- a/ports/renesas-ra/ra/ra_gpio.h +++ b/ports/renesas-ra/ra/ra_gpio.h @@ -171,12 +171,12 @@ uint32_t ra_gpio_get_pull(uint32_t pin); uint32_t ra_gpio_get_af(uint32_t pin); uint32_t ra_gpio_get_drive(uint32_t pin); -inline static void pwpr_unprotect(void) { +static inline void pwpr_unprotect(void) { _PWPR &= (uint8_t) ~0x80; _PWPR |= (uint8_t)0x40; } -inline static void pwpr_protect(void) { +static inline void pwpr_protect(void) { _PWPR &= (uint8_t) ~0x40; _PWPR |= (uint8_t)0x80; } diff --git a/ports/rp2/mutex_extra.h b/ports/rp2/mutex_extra.h index 5f4a2c3643b..9cef2d6b66d 100644 --- a/ports/rp2/mutex_extra.h +++ b/ports/rp2/mutex_extra.h @@ -44,11 +44,11 @@ typedef struct { recursive_mutex_t mutex; } recursive_mutex_nowait_t; -inline static void recursive_mutex_nowait_init(recursive_mutex_nowait_t *mtx) { +static inline void recursive_mutex_nowait_init(recursive_mutex_nowait_t *mtx) { recursive_mutex_init(&mtx->mutex); } -inline static bool recursive_mutex_nowait_try_enter(recursive_mutex_nowait_t *mtx, uint32_t *owner_out) { +static inline bool recursive_mutex_nowait_try_enter(recursive_mutex_nowait_t *mtx, uint32_t *owner_out) { return recursive_mutex_try_enter(&mtx->mutex, owner_out); } diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h index 75e007bc9fa..2ef3e3f266e 100644 --- a/ports/stm32/dma.h +++ b/ports/stm32/dma.h @@ -205,11 +205,11 @@ void dma_unprotect_rx_region(void *dest, size_t len); #else -inline static void dma_protect_rx_region(uint8_t *dest, size_t len) { +static inline void dma_protect_rx_region(uint8_t *dest, size_t len) { // No-ops on targets without D-Cache. } -inline static void dma_unprotect_rx_region(void *dest, size_t len) { +static inline void dma_unprotect_rx_region(void *dest, size_t len) { } diff --git a/py/cstack.h b/py/cstack.h index b12a18e13fc..bcd919d31f9 100644 --- a/py/cstack.h +++ b/py/cstack.h @@ -35,7 +35,7 @@ void mp_cstack_init_with_sp_here(size_t stack_size); -inline static void mp_cstack_init_with_top(void *top, size_t stack_size) { +static inline void mp_cstack_init_with_top(void *top, size_t stack_size) { MP_STATE_THREAD(stack_top) = (char *)top; #if MICROPY_STACK_CHECK @@ -54,7 +54,7 @@ void mp_cstack_check(void); #else -inline static void mp_cstack_check(void) { +static inline void mp_cstack_check(void) { // No-op when stack checking is disabled } diff --git a/py/misc.h b/py/misc.h index ac5e8fb0edf..ee33f84e8f6 100644 --- a/py/misc.h +++ b/py/misc.h @@ -493,7 +493,7 @@ static inline bool mp_mul_ull_overflow(unsigned long long int x, unsigned long l #if __has_builtin(__builtin_saddll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW #define mp_add_ll_overflow __builtin_saddll_overflow #else -inline static bool mp_add_ll_overflow(long long int lhs, long long int rhs, long long int *res) { +static inline bool mp_add_ll_overflow(long long int lhs, long long int rhs, long long int *res) { bool overflow; if (rhs > 0) { @@ -513,7 +513,7 @@ inline static bool mp_add_ll_overflow(long long int lhs, long long int rhs, long #if __has_builtin(__builtin_ssubll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW #define mp_sub_ll_overflow __builtin_ssubll_overflow #else -inline static bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long long int *res) { +static inline bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long long int *res) { bool overflow; if (rhs > 0) { diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 4e3403b3569..176b1ba507c 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -138,7 +138,7 @@ extern const mp_obj_type_t mp_type_usb_device_builtin_default; extern const mp_obj_type_t mp_type_usb_device_builtin_none; // Return true if any built-in driver is enabled -inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) { +static inline bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) { return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); } From effb9503ebbf7552cec1bc788850f27076cbc132 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 28 Sep 2025 09:56:11 -0500 Subject: [PATCH 1330/2098] github/workflows: Use new ci.sh style for calling functions. During CI, invoke `ci.sh` the same way we'd recommend a developer to do so locally. Signed-off-by: Jeff Epler --- .github/workflows/code_formatting.yml | 4 +- .github/workflows/code_size.yml | 4 +- .github/workflows/commit_formatting.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/mpy_format.yml | 4 +- .github/workflows/ports_alif.yml | 4 +- .github/workflows/ports_cc3200.yml | 4 +- .github/workflows/ports_esp32.yml | 4 +- .github/workflows/ports_esp8266.yml | 4 +- .github/workflows/ports_mimxrt.yml | 4 +- .github/workflows/ports_nrf.yml | 4 +- .github/workflows/ports_powerpc.yml | 4 +- .github/workflows/ports_qemu.yml | 12 +-- .github/workflows/ports_renesas-ra.yml | 4 +- .github/workflows/ports_rp2.yml | 4 +- .github/workflows/ports_samd.yml | 4 +- .github/workflows/ports_stm32.yml | 4 +- .github/workflows/ports_unix.yml | 124 ++++++++++++------------ .github/workflows/ports_webassembly.yml | 6 +- .github/workflows/ports_windows.yml | 4 +- .github/workflows/ports_zephyr.yml | 8 +- 21 files changed, 107 insertions(+), 107 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 2a7da52e26d..8a18ca03454 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -13,8 +13,8 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 - name: Install packages - run: source tools/ci.sh && ci_c_code_formatting_setup + run: tools/ci.sh c_code_formatting_setup - name: Run code formatting - run: source tools/ci.sh && ci_c_code_formatting_run + run: tools/ci.sh c_code_formatting_run - name: Check code formatting run: git diff --exit-code diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 8587ef01794..34bdf744cb7 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -29,9 +29,9 @@ jobs: with: fetch-depth: 100 - name: Install packages - run: source tools/ci.sh && ci_code_size_setup + run: tools/ci.sh code_size_setup - name: Build - run: source tools/ci.sh && ci_code_size_build + run: tools/ci.sh code_size_build - name: Compute code size difference run: tools/metrics.py diff ~/size0 ~/size1 | tee diff - name: Save PR number diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml index 58ffb745b34..ca63fc796b0 100644 --- a/.github/workflows/commit_formatting.yml +++ b/.github/workflows/commit_formatting.yml @@ -15,4 +15,4 @@ jobs: fetch-depth: 100 - uses: actions/setup-python@v6 - name: Check commit message formatting - run: source tools/ci.sh && ci_commit_formatting_run + run: tools/ci.sh commit_formatting_run diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6a277a4a4f0..aa05cfb1387 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,6 +22,6 @@ jobs: - name: Install Python packages run: pip install -r docs/requirements.txt - name: Build unix port - run: source tools/ci.sh && ci_unix_build_helper + run: tools/ci.sh unix_build_helper - name: Build docs run: make -C docs/ html diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index 9d3cd139e68..e692a853e7b 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -19,6 +19,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_mpy_format_setup + run: tools/ci.sh mpy_format_setup - name: Test mpy-tool.py - run: source tools/ci.sh && ci_mpy_format_test + run: tools/ci.sh mpy_format_test diff --git a/.github/workflows/ports_alif.yml b/.github/workflows/ports_alif.yml index a06b3f96ffa..eea8f53900e 100644 --- a/.github/workflows/ports_alif.yml +++ b/.github/workflows/ports_alif.yml @@ -28,6 +28,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_alif_setup + run: tools/ci.sh alif_setup - name: Build ci_${{matrix.ci_func }} - run: source tools/ci.sh && ci_${{ matrix.ci_func }} + run: tools/ci.sh ${{ matrix.ci_func }} diff --git a/.github/workflows/ports_cc3200.yml b/.github/workflows/ports_cc3200.yml index b60ff370daf..c57309c23a6 100644 --- a/.github/workflows/ports_cc3200.yml +++ b/.github/workflows/ports_cc3200.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_cc3200_setup + run: tools/ci.sh cc3200_setup - name: Build - run: source tools/ci.sh && ci_cc3200_build + run: tools/ci.sh cc3200_build diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index c87552ade31..8ee3f44d6a2 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -51,7 +51,7 @@ jobs: - name: Install ESP-IDF packages if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: source tools/ci.sh && ci_esp32_idf_setup + run: tools/ci.sh esp32_idf_setup - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -59,4 +59,4 @@ jobs: key: esp32-${{ matrix.ci_func }} - name: Build ci_${{matrix.ci_func }} - run: source tools/ci.sh && ci_${{ matrix.ci_func }} + run: tools/ci.sh ${{ matrix.ci_func }} diff --git a/.github/workflows/ports_esp8266.yml b/.github/workflows/ports_esp8266.yml index 3293abed598..96cf0c5a5cd 100644 --- a/.github/workflows/ports_esp8266.yml +++ b/.github/workflows/ports_esp8266.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_esp8266_setup && ci_esp8266_path >> $GITHUB_PATH + run: tools/ci.sh esp8266_setup && tools/ci.sh esp8266_path >> $GITHUB_PATH - name: Build - run: source tools/ci.sh && ci_esp8266_build + run: tools/ci.sh esp8266_build diff --git a/.github/workflows/ports_mimxrt.yml b/.github/workflows/ports_mimxrt.yml index ae9a80ec580..bcbaf3de066 100644 --- a/.github/workflows/ports_mimxrt.yml +++ b/.github/workflows/ports_mimxrt.yml @@ -28,6 +28,6 @@ jobs: with: path: 'micropython repo' - name: Install packages - run: source tools/ci.sh && ci_mimxrt_setup + run: tools/ci.sh mimxrt_setup - name: Build - run: source tools/ci.sh && ci_mimxrt_build + run: tools/ci.sh mimxrt_build diff --git a/.github/workflows/ports_nrf.yml b/.github/workflows/ports_nrf.yml index ce86617af05..995f65933e0 100644 --- a/.github/workflows/ports_nrf.yml +++ b/.github/workflows/ports_nrf.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_nrf_setup + run: tools/ci.sh nrf_setup - name: Build - run: source tools/ci.sh && ci_nrf_build + run: tools/ci.sh nrf_build diff --git a/.github/workflows/ports_powerpc.yml b/.github/workflows/ports_powerpc.yml index 81f71ca8a96..27417fb3c3c 100644 --- a/.github/workflows/ports_powerpc.yml +++ b/.github/workflows/ports_powerpc.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_powerpc_setup + run: tools/ci.sh powerpc_setup - name: Build - run: source tools/ci.sh && ci_powerpc_build + run: tools/ci.sh powerpc_build diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index cdecfae6c9b..b038b03e71f 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -32,9 +32,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_qemu_setup_arm + run: tools/ci.sh qemu_setup_arm - name: Build and run test suite ci_qemu_build_arm_${{ matrix.ci_func }} - run: source tools/ci.sh && ci_qemu_build_arm_${{ matrix.ci_func }} + run: tools/ci.sh qemu_build_arm_${{ matrix.ci_func }} - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -44,9 +44,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_qemu_setup_rv32 + run: tools/ci.sh qemu_setup_rv32 - name: Build and run test suite - run: source tools/ci.sh && ci_qemu_build_rv32 + run: tools/ci.sh qemu_build_rv32 - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -56,9 +56,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_qemu_setup_rv64 + run: tools/ci.sh qemu_setup_rv64 - name: Build and run test suite - run: source tools/ci.sh && ci_qemu_build_rv64 + run: tools/ci.sh qemu_build_rv64 - name: Print failures if: failure() run: tests/run-tests.py --print-failures diff --git a/.github/workflows/ports_renesas-ra.yml b/.github/workflows/ports_renesas-ra.yml index bf99ed25fed..600b8ea8046 100644 --- a/.github/workflows/ports_renesas-ra.yml +++ b/.github/workflows/ports_renesas-ra.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_renesas_ra_setup + run: tools/ci.sh renesas_ra_setup - name: Build - run: source tools/ci.sh && ci_renesas_ra_board_build + run: tools/ci.sh renesas_ra_board_build diff --git a/.github/workflows/ports_rp2.yml b/.github/workflows/ports_rp2.yml index 22d2a968801..0837c06c97b 100644 --- a/.github/workflows/ports_rp2.yml +++ b/.github/workflows/ports_rp2.yml @@ -28,6 +28,6 @@ jobs: with: path: 'micropython repo' - name: Install packages - run: source tools/ci.sh && ci_rp2_setup + run: tools/ci.sh rp2_setup - name: Build - run: source tools/ci.sh && ci_rp2_build + run: tools/ci.sh rp2_build diff --git a/.github/workflows/ports_samd.yml b/.github/workflows/ports_samd.yml index dbea255c79a..d159fde175d 100644 --- a/.github/workflows/ports_samd.yml +++ b/.github/workflows/ports_samd.yml @@ -23,6 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_samd_setup + run: tools/ci.sh samd_setup - name: Build - run: source tools/ci.sh && ci_samd_build + run: tools/ci.sh samd_build diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 43659a5d5dd..eae3bae871f 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_stm32_setup + run: tools/ci.sh stm32_setup - name: Build ci_${{matrix.ci_func }} - run: source tools/ci.sh && ci_${{ matrix.ci_func }} + run: tools/ci.sh ${{ matrix.ci_func }} diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index eeae895f6a2..d406a912a4a 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -25,9 +25,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: source tools/ci.sh && ci_unix_minimal_build + run: tools/ci.sh unix_minimal_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_minimal_run_tests + run: tools/ci.sh unix_minimal_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -37,7 +37,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build with reproducible date - run: source tools/ci.sh && ci_unix_minimal_build + run: tools/ci.sh unix_minimal_build env: SOURCE_DATE_EPOCH: 1234567890 - name: Check reproducible build date @@ -48,9 +48,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: source tools/ci.sh && ci_unix_standard_build + run: tools/ci.sh unix_standard_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_standard_run_tests + run: tools/ci.sh unix_standard_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -60,9 +60,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: source tools/ci.sh && ci_unix_standard_v2_build + run: tools/ci.sh unix_standard_v2_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_standard_v2_run_tests + run: tools/ci.sh unix_standard_v2_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -77,17 +77,17 @@ jobs: with: python-version: '3.11' - name: Install packages - run: source tools/ci.sh && ci_unix_coverage_setup + run: tools/ci.sh unix_coverage_setup - name: Build - run: source tools/ci.sh && ci_unix_coverage_build + run: tools/ci.sh unix_coverage_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_coverage_run_tests + run: tools/ci.sh unix_coverage_run_tests - name: Test merging .mpy files - run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests + run: tools/ci.sh unix_coverage_run_mpy_merge_tests - name: Build native mpy modules - run: source tools/ci.sh && ci_native_mpy_modules_build + run: tools/ci.sh native_mpy_modules_build - name: Test importing .mpy generated by mpy_ld.py - run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + run: tools/ci.sh unix_coverage_run_native_mpy_tests - name: Run gcov coverage analysis run: | (cd ports/unix && gcov -o build-coverage/py ../../py/*.c || true) @@ -107,15 +107,15 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_32bit_setup + run: tools/ci.sh unix_32bit_setup - name: Build - run: source tools/ci.sh && ci_unix_coverage_32bit_build + run: tools/ci.sh unix_coverage_32bit_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_coverage_32bit_run_tests + run: tools/ci.sh unix_coverage_32bit_run_tests - name: Build native mpy modules - run: source tools/ci.sh && ci_native_mpy_modules_32bit_build + run: tools/ci.sh native_mpy_modules_32bit_build - name: Test importing .mpy generated by mpy_ld.py - run: source tools/ci.sh && ci_unix_coverage_32bit_run_native_mpy_tests + run: tools/ci.sh unix_coverage_32bit_run_native_mpy_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -125,11 +125,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_32bit_setup + run: tools/ci.sh unix_32bit_setup - name: Build - run: source tools/ci.sh && ci_unix_nanbox_build + run: tools/ci.sh unix_nanbox_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_nanbox_run_tests + run: tools/ci.sh unix_nanbox_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -139,11 +139,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_32bit_setup + run: tools/ci.sh unix_32bit_setup - name: Build - run: source tools/ci.sh && ci_unix_longlong_build + run: tools/ci.sh unix_longlong_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_longlong_run_tests + run: tools/ci.sh unix_longlong_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -153,9 +153,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: source tools/ci.sh && ci_unix_float_build + run: tools/ci.sh unix_float_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_float_run_tests + run: tools/ci.sh unix_float_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -165,9 +165,9 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build - run: source tools/ci.sh && ci_unix_gil_enabled_build + run: tools/ci.sh unix_gil_enabled_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_gil_enabled_run_tests + run: tools/ci.sh unix_gil_enabled_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -177,11 +177,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_clang_setup + run: tools/ci.sh unix_clang_setup - name: Build - run: source tools/ci.sh && ci_unix_stackless_clang_build + run: tools/ci.sh unix_stackless_clang_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_stackless_clang_run_tests + run: tools/ci.sh unix_stackless_clang_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -191,11 +191,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_clang_setup + run: tools/ci.sh unix_clang_setup - name: Build - run: source tools/ci.sh && ci_unix_float_clang_build + run: tools/ci.sh unix_float_clang_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_float_clang_run_tests + run: tools/ci.sh unix_float_clang_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -210,9 +210,9 @@ jobs: with: python-version: '3.11' - name: Build - run: source tools/ci.sh && ci_unix_settrace_stackless_build + run: tools/ci.sh unix_settrace_stackless_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_settrace_stackless_run_tests + run: tools/ci.sh unix_settrace_stackless_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -222,11 +222,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_32bit_setup + run: tools/ci.sh unix_32bit_setup - name: Build - run: source tools/ci.sh && ci_unix_repr_b_build + run: tools/ci.sh unix_repr_b_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_repr_b_run_tests + run: tools/ci.sh unix_repr_b_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -239,9 +239,9 @@ jobs: with: python-version: '3.8' - name: Build - run: source tools/ci.sh && ci_unix_macos_build + run: tools/ci.sh unix_macos_build - name: Run tests - run: source tools/ci.sh && ci_unix_macos_run_tests + run: tools/ci.sh unix_macos_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -252,11 +252,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_qemu_mips_setup + run: tools/ci.sh unix_qemu_mips_setup - name: Build - run: source tools/ci.sh && ci_unix_qemu_mips_build + run: tools/ci.sh unix_qemu_mips_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_qemu_mips_run_tests + run: tools/ci.sh unix_qemu_mips_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -267,11 +267,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_qemu_arm_setup + run: tools/ci.sh unix_qemu_arm_setup - name: Build - run: source tools/ci.sh && ci_unix_qemu_arm_build + run: tools/ci.sh unix_qemu_arm_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_qemu_arm_run_tests + run: tools/ci.sh unix_qemu_arm_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -282,11 +282,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_unix_qemu_riscv64_setup + run: tools/ci.sh unix_qemu_riscv64_setup - name: Build - run: source tools/ci.sh && ci_unix_qemu_riscv64_build + run: tools/ci.sh unix_qemu_riscv64_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_qemu_riscv64_run_tests + run: tools/ci.sh unix_qemu_riscv64_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -301,17 +301,17 @@ jobs: with: python-version: '3.11' - name: Install packages - run: source tools/ci.sh && ci_unix_coverage_setup + run: tools/ci.sh unix_coverage_setup - name: Build - run: source tools/ci.sh && ci_unix_sanitize_address_build + run: tools/ci.sh unix_sanitize_address_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_sanitize_address_run_tests + run: tools/ci.sh unix_sanitize_address_run_tests - name: Test merging .mpy files - run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests + run: tools/ci.sh unix_coverage_run_mpy_merge_tests - name: Build native mpy modules - run: source tools/ci.sh && ci_native_mpy_modules_build + run: tools/ci.sh native_mpy_modules_build - name: Test importing .mpy generated by mpy_ld.py - run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + run: tools/ci.sh unix_coverage_run_native_mpy_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures @@ -326,17 +326,17 @@ jobs: with: python-version: '3.11' - name: Install packages - run: source tools/ci.sh && ci_unix_coverage_setup + run: tools/ci.sh unix_coverage_setup - name: Build - run: source tools/ci.sh && ci_unix_sanitize_undefined_build + run: tools/ci.sh unix_sanitize_undefined_build - name: Run main test suite - run: source tools/ci.sh && ci_unix_sanitize_undefined_run_tests + run: tools/ci.sh unix_sanitize_undefined_run_tests - name: Test merging .mpy files - run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests + run: tools/ci.sh unix_coverage_run_mpy_merge_tests - name: Build native mpy modules - run: source tools/ci.sh && ci_native_mpy_modules_build + run: tools/ci.sh native_mpy_modules_build - name: Test importing .mpy generated by mpy_ld.py - run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + run: tools/ci.sh unix_coverage_run_native_mpy_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures diff --git a/.github/workflows/ports_webassembly.yml b/.github/workflows/ports_webassembly.yml index 14399950b56..6bfbb5aec6c 100644 --- a/.github/workflows/ports_webassembly.yml +++ b/.github/workflows/ports_webassembly.yml @@ -23,11 +23,11 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_webassembly_setup + run: tools/ci.sh webassembly_setup - name: Build - run: source tools/ci.sh && ci_webassembly_build + run: tools/ci.sh webassembly_build - name: Run tests - run: source tools/ci.sh && ci_webassembly_run_tests + run: tools/ci.sh webassembly_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 16396f0fdd4..e4e0152d3e9 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -146,6 +146,6 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install packages - run: source tools/ci.sh && ci_windows_setup + run: tools/ci.sh windows_setup - name: Build - run: source tools/ci.sh && ci_windows_build + run: tools/ci.sh windows_build diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 09ffd616b40..3ed164e2f5f 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -52,14 +52,14 @@ jobs: with: key: zephyr - name: Install packages - run: source tools/ci.sh && ci_zephyr_setup + run: tools/ci.sh zephyr_setup - name: Install Zephyr if: steps.cache_workspace.outputs.cache-hit != 'true' - run: source tools/ci.sh && ci_zephyr_install + run: tools/ci.sh zephyr_install - name: Build - run: source tools/ci.sh && ci_zephyr_build + run: tools/ci.sh zephyr_build - name: Run main test suite - run: source tools/ci.sh && ci_zephyr_run_tests + run: tools/ci.sh zephyr_run_tests - name: Print failures if: failure() run: tests/run-tests.py --print-failures From 75fb1addade14a223056d9c233369c29bf098d8d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Oct 2025 11:01:06 +1000 Subject: [PATCH 1331/2098] powerpc/mpconfigport: Remove definitions of _FMT macros. Let the defaults in `py/mpconfig.h` be used instead. Signed-off-by: Damien George --- ports/powerpc/mpconfigport.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/powerpc/mpconfigport.h b/ports/powerpc/mpconfigport.h index fcb66bab1e9..04bf4f37445 100644 --- a/ports/powerpc/mpconfigport.h +++ b/ports/powerpc/mpconfigport.h @@ -93,12 +93,8 @@ #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) // This port is 64-bit -#define UINT_FMT "%lu" -#define INT_FMT "%ld" -#define HEX_FMT "%lx" typedef signed long mp_int_t; // must be pointer size typedef unsigned long mp_uint_t; // must be pointer size - typedef long mp_off_t; // extra built in names to add to the global namespace From bb205f093191c0a4d1b1835f3442dbda40e8eab8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Oct 2025 11:01:37 +1000 Subject: [PATCH 1332/2098] qemu/mpconfigport: Remove definitions of _FMT macros. Let the defaults in `py/mpconfig.h` be used instead. Signed-off-by: Damien George --- ports/qemu/mpconfigport.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 3d2466a9195..65c83a5fea5 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -78,10 +78,6 @@ typedef int32_t mp_int_t; // must be pointer size typedef uint32_t mp_uint_t; // must be pointer size #endif -#define UINT_FMT "%lu" -#define INT_FMT "%ld" -#define HEX_FMT "%lx" - typedef long mp_off_t; // We need to provide a declaration/definition of alloca() From ab0080602578ce789d784aaaf50511313db70bc7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 30 Sep 2025 15:56:35 -0500 Subject: [PATCH 1333/2098] py/mpconfig: Introduce SIZE_FMT macro. Signed-off-by: Jeff Epler --- ports/stm32/mpconfigport_nanbox.h | 1 + ports/unix/variants/nanbox/mpconfigvariant.h | 1 + py/mpconfig.h | 3 +++ 3 files changed, 5 insertions(+) diff --git a/ports/stm32/mpconfigport_nanbox.h b/ports/stm32/mpconfigport_nanbox.h index ffd87ba2f6c..7e9306aab8c 100644 --- a/ports/stm32/mpconfigport_nanbox.h +++ b/ports/stm32/mpconfigport_nanbox.h @@ -37,6 +37,7 @@ #define UINT_FMT "%llu" #define INT_FMT "%lld" #define HEX_FMT "%llx" +#define SIZE_FMT "%lu" typedef int64_t mp_int_t; typedef uint64_t mp_uint_t; diff --git a/ports/unix/variants/nanbox/mpconfigvariant.h b/ports/unix/variants/nanbox/mpconfigvariant.h index 8b23b93a8d3..39969814ae4 100644 --- a/ports/unix/variants/nanbox/mpconfigvariant.h +++ b/ports/unix/variants/nanbox/mpconfigvariant.h @@ -49,3 +49,4 @@ typedef uint64_t mp_uint_t; #define UINT_FMT "%llu" #define INT_FMT "%lld" #define HEX_FMT "%llx" +#define SIZE_FMT "%lu" diff --git a/py/mpconfig.h b/py/mpconfig.h index a6a21dd1c08..c25401b8cad 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2244,15 +2244,18 @@ typedef time_t mp_timestamp_t; #define UINT_FMT "%lu" #define INT_FMT "%ld" #define HEX_FMT "%lx" +#define SIZE_FMT "%lu" #elif defined(_WIN64) #define UINT_FMT "%llu" #define INT_FMT "%lld" #define HEX_FMT "%llx" +#define SIZE_FMT "%llu" #else // Archs where mp_int_t == int #define UINT_FMT "%u" #define INT_FMT "%d" #define HEX_FMT "%x" +#define SIZE_FMT "%u" #endif #endif // INT_FMT From c91e091ad72e3e15ca8981bd953f64714a5afb3e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 15 Aug 2025 08:01:12 -0500 Subject: [PATCH 1334/2098] py/runtime: Fix printing of failed allocation amounts. On LP64 and LLP64 systems, size_t is bigger than unsigned. Printing the failed allocation using the new SIZE_FMT macro allows the correct failed allocation size to be shown. Example where this affects the failed allocation message (on x86_64 coverage build): >>> "a" * (1 << 54) Before, this would print the size as 1. Now it prints it as 18014398509481985 (2**54 + 1). Signed-off-by: Jeff Epler --- py/runtime.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/runtime.c b/py/runtime.c index 58d5732be14..a84e22760be 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1669,14 +1669,14 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i #endif // MICROPY_ENABLE_COMPILER MP_NORETURN void m_malloc_fail(size_t num_bytes) { - DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); + DEBUG_printf("memory allocation failed, allocating " SIZE_FMT " bytes\n", num_bytes); #if MICROPY_ENABLE_GC if (gc_is_locked()) { mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")); } #endif mp_raise_msg_varg(&mp_type_MemoryError, - MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes); + MP_ERROR_TEXT("memory allocation failed, allocating " SIZE_FMT " bytes"), num_bytes); } #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE From c57aebf790c40125b663231ec4307d2a3f3cf193 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 25 Sep 2025 16:28:38 +0200 Subject: [PATCH 1335/2098] py/scheduler: Allow selective handling in mp_handle_pending. Extend mp_handle_pending to support three distinct behaviors via mp_handle_pending_internal(): - MP_HANDLE_PENDING_CALLBACKS_ONLY: process callbacks only - MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS: callbacks + raise exceptions - MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS: callbacks + clear only Original mp_handle_pending(bool) preserved as inline wrapper for backward compatibility. Signed-off-by: iabdalkader --- py/runtime.h | 15 ++++++++++++++- py/scheduler.c | 13 ++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/py/runtime.h b/py/runtime.h index f42039cab90..0ef811c1e40 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -52,6 +52,12 @@ typedef enum { MP_ARG_KW_ONLY = 0x200, } mp_arg_flag_t; +typedef enum { + MP_HANDLE_PENDING_CALLBACKS_ONLY, + MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS, + MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS, +} mp_handle_pending_behaviour_t; + typedef union _mp_arg_val_t { bool u_bool; mp_int_t u_int; @@ -100,7 +106,14 @@ void mp_sched_keyboard_interrupt(void); #if MICROPY_ENABLE_VM_ABORT void mp_sched_vm_abort(void); #endif -void mp_handle_pending(bool raise_exc); + +void mp_handle_pending_internal(mp_handle_pending_behaviour_t behavior); + +static inline void mp_handle_pending(bool raise_exc) { + mp_handle_pending_internal(raise_exc ? + MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS : + MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS); +} #if MICROPY_ENABLE_SCHEDULER void mp_sched_lock(void); diff --git a/py/scheduler.c b/py/scheduler.c index d4cdb59efb6..5355074a7e7 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -213,24 +213,27 @@ MP_REGISTER_ROOT_POINTER(mp_sched_item_t sched_queue[MICROPY_SCHEDULER_DEPTH]); // Called periodically from the VM or from "waiting" code (e.g. sleep) to // process background tasks and pending exceptions (e.g. KeyboardInterrupt). -void mp_handle_pending(bool raise_exc) { +void mp_handle_pending_internal(mp_handle_pending_behaviour_t behavior) { + bool handle_exceptions = (behavior != MP_HANDLE_PENDING_CALLBACKS_ONLY); + bool raise_exceptions = (behavior == MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); + // Handle pending VM abort. #if MICROPY_ENABLE_VM_ABORT - if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) { + if (handle_exceptions && MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) { MP_STATE_VM(vm_abort) = false; - if (raise_exc && nlr_get_abort() != NULL) { + if (raise_exceptions && nlr_get_abort() != NULL) { nlr_jump_abort(); } } #endif // Handle any pending exception. - if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { + if (handle_exceptions && MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); if (obj != MP_OBJ_NULL) { MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; - if (raise_exc) { + if (raise_exceptions) { MICROPY_END_ATOMIC_SECTION(atomic_state); nlr_raise(obj); } From 35d07df44597237a1aff661bd797c7c78d761ddc Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 25 Sep 2025 16:55:47 +0200 Subject: [PATCH 1336/2098] stm32/usbd_hid_interface: Add runtime header. Include runtime.h for mp_handle_pending. Signed-off-by: iabdalkader --- ports/stm32/usbd_hid_interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/usbd_hid_interface.c b/ports/stm32/usbd_hid_interface.c index c167fb38f25..5d00c3a92c3 100644 --- a/ports/stm32/usbd_hid_interface.c +++ b/ports/stm32/usbd_hid_interface.c @@ -26,7 +26,7 @@ #include "usbd_hid_interface.h" -#include "py/mpstate.h" +#include "py/runtime.h" #include "py/mperrno.h" #include "py/mphal.h" #include "usb.h" From 2d14d4ef41c88a63f856b4b633478e6a262c85ee Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 22 Jul 2025 19:38:06 +1000 Subject: [PATCH 1337/2098] extmod/asyncio: Add IPv6 support to start_server(). Ensures that the underlying socket is opened with the correct protocol as parsed by `getaddrinfo()`. Signed-off-by: Andrew Leech --- extmod/asyncio/stream.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/asyncio/stream.py b/extmod/asyncio/stream.py index cb0a804b5ed..10d57ae8a1e 100644 --- a/extmod/asyncio/stream.py +++ b/extmod/asyncio/stream.py @@ -180,11 +180,11 @@ async def start_server(cb, host, port, backlog=5, ssl=None): import socket # Create and bind server socket. - host = socket.getaddrinfo(host, port)[0] # TODO this is blocking! - s = socket.socket() + addr_info = socket.getaddrinfo(host, port)[0] # TODO this is blocking! + s = socket.socket(addr_info[0]) # Use address family from getaddrinfo s.setblocking(False) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(host[-1]) + s.bind(addr_info[-1]) s.listen(backlog) # Create and return server object and task. From a79e1fd501d997051b85083ae4891dc28b2195d4 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Fri, 19 Sep 2025 17:31:05 +0530 Subject: [PATCH 1338/2098] zephyr/boards: Add PocketBeagle 2 rev A1 A53 support. This target runs on all 4 cores. Only GPIO, I2C and UART are supported right now. Signed-off-by: Ayush Singh --- ports/zephyr/boards/pocketbeagle_2_am6254_a53.conf | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ports/zephyr/boards/pocketbeagle_2_am6254_a53.conf diff --git a/ports/zephyr/boards/pocketbeagle_2_am6254_a53.conf b/ports/zephyr/boards/pocketbeagle_2_am6254_a53.conf new file mode 100644 index 00000000000..f4fb7682bbe --- /dev/null +++ b/ports/zephyr/boards/pocketbeagle_2_am6254_a53.conf @@ -0,0 +1,3 @@ +# Hardware features +CONFIG_I2C=y +CONFIG_GPIO=y From fb2b638ba0837b188deb0386270973261eb41fc0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 24 Sep 2025 11:22:25 +1000 Subject: [PATCH 1339/2098] docs: Label table columns as "CPython output" and "MicroPython output". Abbreviating these doesn't really save space in the docs, as the code blocks in the next row are always wider than the column headings. Signed-off-by: Angus Gratton --- tools/gen-cpydiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 3bb928090bb..af965a7bc8c 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -260,7 +260,7 @@ def gen_rst(results): output_cpy = ("::\n\n" if output_cpy != "" else "") + output_cpy output_upy = indent(output.output_upy, TAB).rstrip() output_upy = ("::\n\n" if output_upy != "" else "") + output_upy - table = gen_table([["CPy output:", output_cpy], ["uPy output:", output_upy]]) + table = gen_table([["CPython output:", output_cpy], ["MicroPython output:", output_upy]]) rst.write(table) template = open(INDEXTEMPLATE, "r") From 3ec8b9a77c598104d5caf02e0b6e45d9f5d139dd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 24 Sep 2025 11:17:51 +1000 Subject: [PATCH 1340/2098] all: Replace legacy name with MicroPython and MPy as applicable. With the aim of getting consistency, and removing the need to learn an additional term, replace uses of uPy/uPython with MPy/MicroPython. Rule of thumb was to use "MPy" abbreviation where "CPy" is used nearby, but the full word MicroPython otherwise. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/pyboard/tutorial/lcd_skin.rst | 4 ++-- docs/reference/asm_thumb2_index.rst | 2 +- extmod/asyncio/core.py | 2 +- extmod/modtls_mbedtls.c | 2 +- extmod/vfs.c | 2 +- ports/esp32/machine_hw_spi.c | 8 ++++---- ports/webassembly/asyncio/core.py | 2 +- ports/windows/msvc/paths.props | 2 +- py/gc.c | 2 +- py/mpconfig.h | 2 +- py/objexcept.c | 2 +- py/objstr.c | 2 +- tests/basics/async_await2.py | 2 +- tests/basics/async_for2.py | 2 +- tests/basics/async_with2.py | 2 +- tests/basics/boundmeth1.py | 2 +- tests/basics/builtin_range.py | 2 +- tests/basics/builtin_setattr.py | 2 +- tests/basics/class_bind_self.py | 2 +- tests/basics/del_attr.py | 2 +- tests/basics/del_global.py | 2 +- tests/basics/del_name.py | 2 +- tests/basics/frozenset_set.py | 2 +- tests/basics/fun_name.py | 2 +- tests/basics/gc1.py | 4 ++-- tests/basics/io_iobase.py | 2 +- tests/basics/list_pop.py | 2 +- tests/basics/module2.py | 2 +- tests/basics/parser.py | 2 +- tests/basics/python34.py | 2 +- tests/basics/syntaxerror.py | 4 ++-- tests/basics/sys_tracebacklimit.py | 2 +- tests/basics/tuple1.py | 2 +- tests/cpydiff/modules_struct_fewargs.py | 2 +- tests/cpydiff/modules_struct_manyargs.py | 2 +- .../modules_struct_whitespace_in_format.py | 2 +- .../cpydiff/types_float_implicit_conversion.py | 2 +- tests/extmod/asyncio_set_exception_handler.py | 2 +- tests/extmod/hashlib_md5.py | 3 +-- tests/extmod/hashlib_sha1.py | 3 +-- tests/extmod/hashlib_sha256.py | 3 +-- tests/extmod/json_dump.py | 4 ++-- tests/extmod/json_dump_iobase.py | 2 +- tests/extmod/json_dump_separators.py | 6 +++--- tests/extmod/json_dumps_extra.py | 2 +- tests/extmod/json_dumps_separators.py | 2 +- tests/extmod/re_error.py | 2 +- tests/extmod/re_sub.py | 2 +- tests/float/string_format2.py | 4 ++-- tests/float/string_format_modulo.py | 2 +- tests/frozen/frozentest.mpy | Bin 196 -> 201 bytes tests/frozen/frozentest.py | 2 +- tests/io/builtin_print_file.py | 2 +- tests/io/file_seek.py | 2 +- tests/micropython/decorator_error.py | 2 +- tests/micropython/heapalloc_traceback.py | 2 +- tests/misc/non_compliant.py | 6 +++--- tests/misc/non_compliant_lexer.py | 2 +- tests/misc/print_exception.py | 6 +++--- tests/ports/unix/extra_coverage.py | 2 +- tests/ports/unix/extra_coverage.py.exp | 2 +- 61 files changed, 74 insertions(+), 77 deletions(-) diff --git a/docs/pyboard/tutorial/lcd_skin.rst b/docs/pyboard/tutorial/lcd_skin.rst index 4df0041607c..9d0ff473e2b 100644 --- a/docs/pyboard/tutorial/lcd_skin.rst +++ b/docs/pyboard/tutorial/lcd_skin.rst @@ -29,7 +29,7 @@ Make sure the LCD skin is attached to the pyboard as pictured at the top of this >>> import pyb >>> lcd = pyb.LCD('X') >>> lcd.light(True) - >>> lcd.write('Hello uPy!\n') + >>> lcd.write('Hello MPy!\n') You can make a simple animation using the code:: @@ -38,7 +38,7 @@ You can make a simple animation using the code:: lcd.light(True) for x in range(-80, 128): lcd.fill(0) - lcd.text('Hello uPy!', x, 10, 1) + lcd.text('Hello MPy!', x, 10, 1) lcd.show() pyb.delay(25) diff --git a/docs/reference/asm_thumb2_index.rst b/docs/reference/asm_thumb2_index.rst index ccf0201489a..97b3a8687e5 100644 --- a/docs/reference/asm_thumb2_index.rst +++ b/docs/reference/asm_thumb2_index.rst @@ -63,7 +63,7 @@ References - :ref:`Assembler Tutorial ` - `Wiki hints and tips `__ -- `uPy Inline Assembler source-code, +- `MicroPython Inline Assembler source-code, emitinlinethumb.c `__ - `ARM Thumb2 Instruction Set Quick Reference Card `__ diff --git a/extmod/asyncio/core.py b/extmod/asyncio/core.py index 5d46b4b80e2..7b70b43bce8 100644 --- a/extmod/asyncio/core.py +++ b/extmod/asyncio/core.py @@ -50,7 +50,7 @@ def __next__(self): raise self.exc -# Pause task execution for the given time (integer in milliseconds, uPy extension) +# Pause task execution for the given time (integer in milliseconds, MicroPython extension) # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): assert sgen.state is None diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 58634257328..e2f7254f795 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -304,7 +304,7 @@ static mp_obj_t ssl_context_make_new(const mp_obj_type_t *type_in, size_t n_args psa_crypto_init(); #endif - const byte seed[] = "upy"; + const byte seed[] = "mpy"; int ret = mbedtls_ctr_drbg_seed(&self->ctr_drbg, mbedtls_entropy_func, &self->entropy, seed, sizeof(seed)); if (ret != 0) { mbedtls_raise_error(ret); diff --git a/extmod/vfs.c b/extmod/vfs.c index aebf5ed295b..d2a7af5de00 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -96,7 +96,7 @@ mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out) { return MP_STATE_VM(vfs_cur); } -// Version of mp_vfs_lookup_path that takes and returns uPy string objects. +// Version of mp_vfs_lookup_path that takes and returns MicroPython string objects. static mp_vfs_mount_t *lookup_path(mp_obj_t path_in, mp_obj_t *path_out) { const char *path = mp_obj_str_get_str(path_in); const char *p_out; diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index 6eb83fc09c3..dcf8b3942a7 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -38,10 +38,10 @@ #include "soc/spi_pins.h" // SPI mappings by device, naming used by IDF old/new -// upython | ESP32 | ESP32S2 | ESP32S3 | ESP32C3 | ESP32C6 -// ----------+-----------+-----------+---------+---------+--------- -// SPI(id=1) | HSPI/SPI2 | FSPI/SPI2 | SPI2 | SPI2 | SPI2 -// SPI(id=2) | VSPI/SPI3 | HSPI/SPI3 | SPI3 | err | err +// MicroPython | ESP32 | ESP32S2 | ESP32S3 | ESP32C3 | ESP32C6 +// ------------+-----------+-----------+---------+---------+--------- +// SPI(id=1) | HSPI/SPI2 | FSPI/SPI2 | SPI2 | SPI2 | SPI2 +// SPI(id=2) | VSPI/SPI3 | HSPI/SPI3 | SPI3 | err | err // Number of available hardware SPI peripherals. #if SOC_SPI_PERIPH_NUM > 2 diff --git a/ports/webassembly/asyncio/core.py b/ports/webassembly/asyncio/core.py index 9d07bd7b858..eeffa253bcf 100644 --- a/ports/webassembly/asyncio/core.py +++ b/ports/webassembly/asyncio/core.py @@ -47,7 +47,7 @@ def __next__(self): raise self.exc -# Pause task execution for the given time (integer in milliseconds, uPy extension) +# Pause task execution for the given time (integer in milliseconds, MicroPython extension) # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): assert sgen.state is None diff --git a/ports/windows/msvc/paths.props b/ports/windows/msvc/paths.props index 767772a1aaf..a88182f4c95 100644 --- a/ports/windows/msvc/paths.props +++ b/ports/windows/msvc/paths.props @@ -31,7 +31,7 @@ $(PyWinDir)variants\$(PyVariant)\ $(PyBuildDir) - + $(PyIncDirs);$(PyBaseDir);$(PyWinDir);$(PyBuildDir);$(PyWinDir)msvc;$(PyVariantDir) Only for STM32H7 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) { static const mp_arg_t allowed_args[] = { { MP_QSTR_baudrate, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 9600} }, @@ -152,11 +178,17 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_read_buf_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, // legacy + #if defined(STM32H7) + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + #endif }; // parse args struct { mp_arg_val_t baudrate, bits, parity, stop, flow, timeout, timeout_char, rxbuf, read_buf_len; + #if defined(STM32H7) + mp_arg_val_t invert; + #endif } args; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); @@ -202,11 +234,18 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // flow control uint32_t flow = args.flow.u_int; + // inverted + #if defined(STM32H7) + uint32_t invert = args.invert.u_int; + #else + uint32_t invert = 0; + #endif + // Save attach_to_repl setting because uart_init will disable it. bool attach_to_repl = self->attached_to_repl; // init UART (if it fails, it's because the port doesn't exist) - if (!uart_init(self, baudrate, bits, parity, stop, flow)) { + if (!uart_init(self, baudrate, bits, parity, stop, flow, invert)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), self->uart_id); } diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 9ac2b44905b..1c59b6479fb 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -502,7 +502,7 @@ void stm32_main(uint32_t reset_mode) { pyb_uart_repl_obj.is_static = true; pyb_uart_repl_obj.timeout = 0; pyb_uart_repl_obj.timeout_char = 2; - uart_init(&pyb_uart_repl_obj, MICROPY_HW_UART_REPL_BAUD, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, 0); + uart_init(&pyb_uart_repl_obj, MICROPY_HW_UART_REPL_BAUD, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, 0, 0); uart_set_rxbuf(&pyb_uart_repl_obj, sizeof(pyb_uart_repl_rxbuf), pyb_uart_repl_rxbuf); uart_attach_to_repl(&pyb_uart_repl_obj, true); MP_STATE_PORT(machine_uart_obj_all)[MICROPY_HW_UART_REPL - 1] = &pyb_uart_repl_obj; diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index 3f71146834e..d8be247651d 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -184,7 +184,7 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { MP_STATE_PORT(machine_uart_obj_all)[mp_bluetooth_hci_uart_obj.uart_id - 1] = &mp_bluetooth_hci_uart_obj; // Initialise the UART. - uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); + uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS, 0); uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); // Add IRQ handler for IDLE (i.e. packet finished). diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 73e36881a7c..f1637fd4981 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -265,7 +265,7 @@ bool uart_exists(int uart_id) { // assumes Init parameters have been set up correctly bool uart_init(machine_uart_obj_t *uart_obj, - uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow) { + uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow, uint32_t invert) { USART_TypeDef *UARTx; IRQn_Type irqn; uint8_t uart_fn = AF_FN_UART; @@ -677,6 +677,20 @@ bool uart_init(machine_uart_obj_t *uart_obj, huart.Init.ClockPrescaler = UART_PRESCALER_DIV16; #endif + #if defined(STM32H7) + huart.AdvancedInit.AdvFeatureInit = (UART_ADVFEATURE_TXINVERT_INIT | UART_ADVFEATURE_RXINVERT_INIT); + if (invert & UART_ADVFEATURE_RXINVERT_INIT) { + huart.AdvancedInit.RxPinLevelInvert = UART_ADVFEATURE_RXINV_ENABLE; + } else { + huart.AdvancedInit.RxPinLevelInvert = UART_ADVFEATURE_RXINV_DISABLE; + } + if (invert & UART_ADVFEATURE_TXINVERT_INIT) { + huart.AdvancedInit.TxPinLevelInvert = UART_ADVFEATURE_TXINV_ENABLE; + } else { + huart.AdvancedInit.TxPinLevelInvert = UART_ADVFEATURE_TXINV_DISABLE; + } + #endif + #if defined(STM32G4) || defined(STM32H7) || defined(STM32N6) // WB also has a fifo.. huart.FifoMode = UART_FIFOMODE_ENABLE; #endif diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index d92434c641c..448140f1778 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -87,7 +87,7 @@ void uart_init0(void); void uart_deinit_all(void); bool uart_exists(int uart_id); bool uart_init(machine_uart_obj_t *uart_obj, - uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow); + uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow, uint32_t invert); 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); From 6b0e1c2701490260cb81bbe884f42a9c18bea020 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 9 Aug 2024 19:37:10 +1000 Subject: [PATCH 1445/2098] shared/runtime/pyexec: Remove legacy USB IRQ enable code. It's very STM32 USB stack specific and doesn't generalise well to other ports. So remove it. Signed-off-by: Andrew Leech --- shared/runtime/pyexec.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 0b253b4ceaa..7d2a70c01bc 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -35,10 +35,6 @@ #include "py/gc.h" #include "py/frozenmod.h" #include "py/mphal.h" -#if MICROPY_HW_ENABLE_USB -#include "irq.h" -#include "usb.h" -#endif #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "genhdr/mpversion.h" @@ -582,20 +578,6 @@ int pyexec_friendly_repl(void) { for (;;) { input_restart: - - #if MICROPY_HW_ENABLE_USB - if (usb_vcp_is_enabled()) { - // If the user gets to here and interrupts are disabled then - // they'll never see the prompt, traceback etc. The USB REPL needs - // interrupts to be enabled or no transfers occur. So we try to - // do the user a favor and re-enable interrupts. - if (query_irq() == IRQ_STATE_DISABLED) { - enable_irq(IRQ_STATE_ENABLED); - mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n"); - } - } - #endif - // If the GC is locked at this point there is no way out except a reset, // so force the GC to be unlocked to help the user debug what went wrong. MP_STATE_THREAD(gc_lock_depth) = 0; From 9bb266e31162ba80ec06a6bc9a6b94b9cbafa9b2 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 28 Oct 2025 16:20:31 +1100 Subject: [PATCH 1446/2098] stm32/usb: Add support for using TinyUSB stack. This commit adapts the stm32 port to allow switching from STM USB stack to TinyUSB stack. Using TinyUSB improves consistancy with other MicroPython ports and brings in the ability to use the runtime USB definition support recently added to other TinyUSB based ports. By default the existing STM USB stack is used. TinyUSB can be enabled in a board configuration with: #define MICROPY_HW_TINYUSB_STACK (1) Or, it can be enabled from the command line with: make -C ports/stm32 CFLAGS_EXTRA='-DMICROPY_HW_TINYUSB_STACK=1' Signed-off-by: Andrew Leech --- ports/stm32/Makefile | 22 ++++++++- ports/stm32/main.c | 21 ++++++--- ports/stm32/modmachine.c | 7 ++- ports/stm32/modos.c | 6 +-- ports/stm32/modpyb.c | 2 +- ports/stm32/mpconfigboard_common.h | 71 +++++++++++++++++++++++++----- ports/stm32/mphalport.c | 28 ++++++++++++ ports/stm32/mphalport.h | 8 ++++ ports/stm32/stm32_it.c | 39 +++++++++++++++- ports/stm32/usb.c | 4 +- ports/stm32/usbd.c | 42 ++++++++++++++++++ ports/stm32/usbd_conf.c | 40 +++++++++++------ ports/stm32/usbd_conf.h | 4 ++ tools/ci.sh | 2 +- 14 files changed, 256 insertions(+), 40 deletions(-) create mode 100644 ports/stm32/usbd.c diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 8b35c82a27c..5105b35b162 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -58,7 +58,7 @@ MBOOT_TEXT0_ADDR ?= 0x08000000 include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk -GIT_SUBMODULES += lib/libhydrogen lib/stm32lib +GIT_SUBMODULES += lib/libhydrogen lib/stm32lib lib/tinyusb CROSS_COMPILE ?= arm-none-eabi- LD_DIR=boards @@ -110,6 +110,8 @@ INC += -I$(STM32LIB_CMSIS_ABS)/Include INC += -I$(STM32LIB_HAL_ABS)/Inc INC += -I$(USBDEV_DIR)/core/inc -I$(USBDEV_DIR)/class/inc #INC += -I$(USBHOST_DIR) +INC += -I$(TOP)/lib/tinyusb/src +INC += -I$(TOP)/shared/tinyusb/ INC += -Ilwip_inc CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 -nostdlib $(CFLAGS_EXTRA) @@ -213,6 +215,10 @@ SHARED_SRC_C += $(addprefix shared/,\ runtime/stdout_helpers.c \ runtime/sys_stdio_mphal.c \ timeutils/timeutils.c \ + tinyusb/mp_usbd.c \ + tinyusb/mp_usbd_cdc.c \ + tinyusb/mp_usbd_descriptor.c \ + tinyusb/mp_usbd_runtime.c \ ) ifeq ($(MICROPY_FLOAT_IMPL),double) @@ -242,7 +248,9 @@ SRC_C += \ boardctrl.c \ main.c \ stm32_it.c \ + usbd.c \ usbd_conf.c \ + usb.c \ usbd_desc.c \ usbd_cdc_interface.c \ usbd_hid_interface.c \ @@ -277,7 +285,6 @@ SRC_C += \ can.c \ fdcan.c \ pyb_can.c \ - usb.c \ eth.c \ eth_phy.c \ gccollect.c \ @@ -460,6 +467,16 @@ USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\ class/src/usbd_msc_scsi.c \ ) +# TinyUSB Stack source +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(addprefix lib/tinyusb/, \ + $(TINYUSB_SRC_C) \ + src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c \ + src/portable/synopsys/dwc2/dcd_dwc2.c \ + src/portable/synopsys/dwc2/dwc2_common.c \ + src/portable/synopsys/dwc2/hcd_dwc2.c \ + ) + ifeq ($(MICROPY_SSL_MBEDTLS),1) LIB_SRC_C += mbedtls/mbedtls_port.c endif @@ -499,6 +516,7 @@ OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(HAL_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(USBDEV_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(GEN_PINS_SRC:.c=.o) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 1c59b6479fb..d56b4c4076e 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -88,6 +88,11 @@ #include "pyb_can.h" #include "subghz.h" +#if MICROPY_HW_TINYUSB_STACK +#include "usbd_conf.h" +#include "shared/tinyusb/mp_usbd.h" +#endif + #if MICROPY_PY_THREAD static pyb_thread_t pyb_thread_main; #endif @@ -275,14 +280,12 @@ static bool init_sdcard_fs(void) { } } - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_ENABLE_USB && !MICROPY_HW_TINYUSB_STACK if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { // if no USB MSC medium is selected then use the SD card pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_SDCARD; } - #endif - #if MICROPY_HW_ENABLE_USB // only use SD card as current directory if that's what the USB medium is if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_SDCARD) #endif @@ -606,8 +609,13 @@ void stm32_main(uint32_t reset_mode) { #endif #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_TINYUSB_STACK + pyb_usbd_init(); + mp_usbd_init(); + #else pyb_usb_init0(); #endif + #endif #if MICROPY_PY_MACHINE_I2S machine_i2s_init0(); @@ -631,7 +639,7 @@ void stm32_main(uint32_t reset_mode) { } #endif - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK // if the SD card isn't used as the USB MSC medium then use the internal flash if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_FLASH; @@ -665,7 +673,7 @@ void stm32_main(uint32_t reset_mode) { // or whose initialisation can be safely deferred until after running // boot.py. - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK // init USB device to default setting if it was not already configured if (!(pyb_usb_flags & PYB_USB_FLAG_USB_MODE_CALLED)) { #if MICROPY_HW_USB_MSC @@ -770,6 +778,9 @@ void stm32_main(uint32_t reset_mode) { #else MP_STATE_PORT(pyb_stdio_uart) = NULL; #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE && MICROPY_HW_TINYUSB_STACK + mp_usbd_deinit(); + #endif MICROPY_BOARD_END_SOFT_RESET(&state); diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 8123cd80115..c1ac6e0862d 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -47,6 +47,7 @@ #include "rtc.h" #include "i2c.h" #include "spi.h" +#include "shared/tinyusb/mp_usbd.h" #if defined(STM32G0) // G0 has BOR and POR combined @@ -297,9 +298,13 @@ MP_NORETURN static void mp_machine_reset(void) { // Activate the bootloader without BOOT* pins. MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK pyb_usb_dev_deinit(); #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE && MICROPY_HW_TINYUSB_STACK + mp_usbd_deinit(); + #endif + #if MICROPY_HW_ENABLE_STORAGE storage_flush(); #endif diff --git a/ports/stm32/modos.c b/ports/stm32/modos.c index 7949cf87cde..cd918fe7154 100644 --- a/ports/stm32/modos.c +++ b/ports/stm32/modos.c @@ -52,7 +52,7 @@ bool mp_os_dupterm_is_builtin_stream(mp_const_obj_t stream) { #if MICROPY_PY_MACHINE_UART || type == &machine_uart_type #endif - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK || type == &pyb_usb_vcp_type #endif ; @@ -64,7 +64,7 @@ void mp_os_dupterm_stream_detached_attached(mp_obj_t stream_detached, mp_obj_t s uart_attach_to_repl(MP_OBJ_TO_PTR(stream_detached), false); } #endif - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK if (mp_obj_get_type(stream_detached) == &pyb_usb_vcp_type) { usb_vcp_attach_to_repl(MP_OBJ_TO_PTR(stream_detached), false); } @@ -75,7 +75,7 @@ void mp_os_dupterm_stream_detached_attached(mp_obj_t stream_detached, mp_obj_t s uart_attach_to_repl(MP_OBJ_TO_PTR(stream_attached), true); } #endif - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK if (mp_obj_get_type(stream_attached) == &pyb_usb_vcp_type) { usb_vcp_attach_to_repl(MP_OBJ_TO_PTR(stream_attached), true); } diff --git a/ports/stm32/modpyb.c b/ports/stm32/modpyb.c index 3d8696e3c42..a985fa39da3 100644 --- a/ports/stm32/modpyb.c +++ b/ports/stm32/modpyb.c @@ -167,7 +167,7 @@ static const mp_rom_map_elem_t pyb_module_globals_table[] = { // Deprecated (use network.country instead). { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&mod_network_country_obj) }, - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK { MP_ROM_QSTR(MP_QSTR_usb_mode), MP_ROM_PTR(&pyb_usb_mode_obj) }, #if MICROPY_HW_USB_HID { MP_ROM_QSTR(MP_QSTR_hid_mouse), MP_ROM_PTR(&pyb_usb_hid_mouse_obj) }, diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index f36cdec8ec7..47eb7f036c3 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -250,12 +250,50 @@ #error "Old USBD_xxx configuration option used, renamed to MICROPY_HW_USB_xxx" #endif +// Select whether TinyUSB or legacy STM stack is used to provide USB. +#ifndef MICROPY_HW_TINYUSB_STACK +#define MICROPY_HW_TINYUSB_STACK (0) +#endif + +// Central definition for STM USB stack (when not using TinyUSB) +#define MICROPY_HW_STM_USB_STACK (MICROPY_HW_ENABLE_USB && !MICROPY_HW_TINYUSB_STACK) + +#if MICROPY_HW_TINYUSB_STACK +#ifndef MICROPY_HW_ENABLE_USBDEV +#define MICROPY_HW_ENABLE_USBDEV (1) +#endif + +#ifndef MICROPY_HW_USB_CDC +#define MICROPY_HW_USB_CDC (1) +#endif + +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +#endif +#endif + +// Configure maximum number of CDC VCP interfaces, and whether MSC/HID are supported +#ifndef MICROPY_HW_USB_CDC_NUM +#define MICROPY_HW_USB_CDC_NUM (1) +#endif +#ifndef MICROPY_HW_USB_MSC +#define MICROPY_HW_USB_MSC (MICROPY_HW_STM_USB_STACK) +#endif +#ifndef MICROPY_HW_USB_HID +#define MICROPY_HW_USB_HID (MICROPY_HW_STM_USB_STACK) +#endif + // Default VID and PID values to use for the USB device. If MICROPY_HW_USB_VID // is defined by a board then all needed PID options must also be defined. The // VID and PID can also be set dynamically in pyb.usb_mode(). // Windows needs a different PID to distinguish different device configurations. #ifndef MICROPY_HW_USB_VID #define MICROPY_HW_USB_VID (0xf055) + +// USB PID for TinyUSB Stack. +#define MICROPY_HW_USB_PID (0x9802) + +// USB PID table for STM USB stack. #define MICROPY_HW_USB_PID_CDC_MSC (0x9800) #define MICROPY_HW_USB_PID_CDC_HID (0x9801) #define MICROPY_HW_USB_PID_CDC (0x9802) @@ -369,6 +407,8 @@ #endif #define MICROPY_HW_MAX_LPUART (0) +#define CFG_TUSB_MCU OPT_MCU_STM32F4 + // Configuration for STM32F7 series #elif defined(STM32F7) @@ -384,6 +424,8 @@ #define MICROPY_HW_MAX_UART (8) #define MICROPY_HW_MAX_LPUART (0) +#define CFG_TUSB_MCU OPT_MCU_STM32F7 + // Configuration for STM32G0 series #elif defined(STM32G0) @@ -394,6 +436,8 @@ #define MICROPY_HW_MAX_UART (6) #define MICROPY_HW_MAX_LPUART (2) +#define CFG_TUSB_MCU OPT_MCU_STM32G0 + // Configuration for STM32G4 series #elif defined(STM32G4) @@ -404,6 +448,8 @@ #define MICROPY_HW_MAX_UART (5) // UART1-5 + LPUART1 #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32G4 + // Configuration for STM32H5 series #elif defined(STM32H5) @@ -414,6 +460,8 @@ #define MICROPY_HW_MAX_UART (12) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32H5 + // Configuration for STM32H7A3/B3 series #elif defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || \ defined(STM32H7B3xx) || defined(STM32H7B3xxQ) @@ -425,6 +473,8 @@ #define MICROPY_HW_MAX_UART (10) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32H7 + // Configuration for STM32H7 series #elif defined(STM32H7) @@ -435,6 +485,8 @@ #define MICROPY_HW_MAX_UART (8) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32H7 + #if defined(MICROPY_HW_ANALOG_SWITCH_PA0) \ || defined(MICROPY_HW_ANALOG_SWITCH_PA1) \ || defined(MICROPY_HW_ANALOG_SWITCH_PC2) \ @@ -454,6 +506,8 @@ #define MICROPY_HW_MAX_UART (5) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32L0 + // Configuration for STM32L1 series #elif defined(STM32L1) #define MP_HAL_UNIQUE_ID_ADDRESS (UID_BASE) @@ -464,6 +518,8 @@ #define MICROPY_HW_MAX_UART (5) #define MICROPY_HW_MAX_LPUART (0) +#define CFG_TUSB_MCU OPT_MCU_STM32L1 + // Configuration for STM32L4 series #elif defined(STM32L4) @@ -474,6 +530,8 @@ #define MICROPY_HW_MAX_UART (5) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32L4 + // Configuration for STM32N6 series #elif defined(STM32N6) @@ -508,6 +566,8 @@ #define MICROPY_HW_MAX_UART (1) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32WB + #ifndef MICROPY_HW_STM32WB_FLASH_SYNCRONISATION #define MICROPY_HW_STM32WB_FLASH_SYNCRONISATION (1) #endif @@ -715,17 +775,6 @@ #define MICROPY_HW_USB_IS_MULTI_OTG (1) #endif -// Configure maximum number of CDC VCP interfaces, and whether MSC/HID are supported -#ifndef MICROPY_HW_USB_CDC_NUM -#define MICROPY_HW_USB_CDC_NUM (1) -#endif -#ifndef MICROPY_HW_USB_MSC -#define MICROPY_HW_USB_MSC (MICROPY_HW_ENABLE_USB) -#endif -#ifndef MICROPY_HW_USB_HID -#define MICROPY_HW_USB_HID (MICROPY_HW_ENABLE_USB) -#endif - // Pin definition header file #define MICROPY_PIN_DEFS_PORT_H "pin_defs_stm32.h" diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c index b13ed0ee150..51fb32ae8c6 100644 --- a/ports/stm32/mphalport.c +++ b/ports/stm32/mphalport.c @@ -8,6 +8,17 @@ #include "usb.h" #include "uart.h" +#if MICROPY_HW_TINYUSB_STACK +#include "shared/tinyusb/mp_usbd_cdc.h" + +#ifndef MICROPY_HW_STDIN_BUFFER_LEN +#define MICROPY_HW_STDIN_BUFFER_LEN 512 +#endif + +static uint8_t stdin_ringbuf_array[MICROPY_HW_STDIN_BUFFER_LEN]; +ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0 }; +#endif + // this table converts from HAL_StatusTypeDef to POSIX errno const byte mp_hal_status_to_errno_table[4] = { [HAL_OK] = 0, @@ -26,6 +37,9 @@ MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { MP_WEAK uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; + #if MICROPY_HW_USB_CDC && MICROPY_HW_TINYUSB_STACK + ret |= mp_usbd_cdc_poll_interfaces(poll_flags); + #endif if (MP_STATE_PORT(pyb_stdio_uart) != NULL) { mp_obj_t pyb_stdio_uart = MP_OBJ_FROM_PTR(MP_STATE_PORT(pyb_stdio_uart)); int errcode; @@ -53,6 +67,13 @@ MP_WEAK int mp_hal_stdin_rx_chr(void) { if (dupterm_c >= 0) { return dupterm_c; } + #if MICROPY_HW_USB_CDC && MICROPY_HW_TINYUSB_STACK + mp_usbd_cdc_poll_interfaces(0); + int c = ringbuf_get(&stdin_ringbuf); + if (c != -1) { + return c; + } + #endif MICROPY_EVENT_POLL_HOOK } } @@ -64,6 +85,13 @@ MP_WEAK mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { uart_tx_strn(MP_STATE_PORT(pyb_stdio_uart), str, len); did_write = true; } + #if MICROPY_HW_USB_CDC && MICROPY_HW_TINYUSB_STACK + mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len); + if (cdc_res > 0) { + did_write = true; + ret = MIN(cdc_res, ret); + } + #endif #if 0 && defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD lcd_print_strn(str, len); #endif diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index 4d98ce9f192..098a848fb84 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -1,6 +1,8 @@ // We use the ST Cube HAL library for most hardware peripherals #include STM32_HAL_H #include "pin.h" +#include "py/ringbuf.h" +#include "shared/runtime/interrupt_char.h" extern uint8_t mp_hal_unique_id_address[12]; @@ -41,6 +43,8 @@ static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { return -mp_hal_status_to_errno_table[status]; } +extern ringbuf_t stdin_ringbuf; + MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable @@ -148,3 +152,7 @@ enum { void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); + +static inline void mp_hal_wake_main_task_from_isr(void) { + // Defined for tinyusb support, nothing needs to be done here. +} diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 470df4758f7..19778020d9a 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -80,7 +80,13 @@ #include "uart.h" #include "storage.h" #include "dma.h" + +#if MICROPY_HW_TINYUSB_STACK +#include "tusb.h" +#include "shared/tinyusb/mp_usbd.h" +#else #include "usb.h" +#endif #if defined(MICROPY_HW_USB_FS) extern PCD_HandleTypeDef pcd_fs_handle; @@ -145,7 +151,7 @@ void HardFault_C_Handler(ExceptionRegisters_t *regs) { powerctrl_mcu_reset(); } - #if MICROPY_HW_ENABLE_USB + #if MICROPY_HW_STM_USB_STACK // We need to disable the USB so it doesn't try to write data out on // the VCP and then block indefinitely waiting for the buffer to drain. pyb_usb_flags = 0; @@ -299,7 +305,11 @@ void DebugMon_Handler(void) { #if MICROPY_HW_USB_FS void USB_UCPD1_2_IRQHandler(void) { + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_fs_handle); + #endif } #endif @@ -307,7 +317,11 @@ void USB_UCPD1_2_IRQHandler(void) { #if MICROPY_HW_USB_FS void USB_DRD_FS_IRQHandler(void) { + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_fs_handle); + #endif } #endif @@ -315,7 +329,11 @@ void USB_DRD_FS_IRQHandler(void) { #if MICROPY_HW_USB_FS void USB_IRQHandler(void) { + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_fs_handle); + #endif } #endif @@ -323,7 +341,11 @@ void USB_IRQHandler(void) { #if MICROPY_HW_USB_FS void USB_LP_IRQHandler(void) { + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_fs_handle); + #endif } #endif @@ -337,7 +359,11 @@ void USB_LP_IRQHandler(void) { #if MICROPY_HW_USB_FS void OTG_FS_IRQHandler(void) { IRQ_ENTER(OTG_FS_IRQn); + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_fs_handle); + #endif IRQ_EXIT(OTG_FS_IRQn); } #endif @@ -345,13 +371,21 @@ void OTG_FS_IRQHandler(void) { #if defined(STM32N6) void USB1_OTG_HS_IRQHandler(void) { IRQ_ENTER(USB1_OTG_HS_IRQn); + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_hs_handle); + #endif IRQ_EXIT(USB1_OTG_HS_IRQn); } #else void OTG_HS_IRQHandler(void) { IRQ_ENTER(OTG_HS_IRQn); + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #else HAL_PCD_IRQHandler(&pcd_hs_handle); + #endif IRQ_EXIT(OTG_HS_IRQn); } #endif @@ -414,6 +448,9 @@ static void OTG_CMD_WKUP_Handler(PCD_HandleTypeDef *pcd_handle) { /* ungate PHY clock */ __HAL_PCD_UNGATE_PHYCLOCK(pcd_handle); } + #if MICROPY_HW_TINYUSB_STACK + tud_int_handler(0); + #endif } #endif diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 81025e48bac..15d5637130a 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -45,7 +45,7 @@ #include "sdcard.h" #include "usb.h" -#if MICROPY_HW_ENABLE_USB +#if MICROPY_HW_STM_USB_STACK // Work out which USB device to use as the main one (the one with the REPL) #if !defined(MICROPY_HW_USB_MAIN_DEV) @@ -1156,4 +1156,4 @@ MP_REGISTER_ROOT_POINTER(mp_obj_t pyb_hid_report_desc); #endif MP_REGISTER_ROOT_POINTER(mp_obj_t pyb_usb_vcp_irq[MICROPY_HW_USB_CDC_NUM]); -#endif // MICROPY_HW_ENABLE_USB +#endif // MICROPY_HW_STM_USB_STACK diff --git a/ports/stm32/usbd.c b/ports/stm32/usbd.c new file mode 100644 index 00000000000..35275cd1bd0 --- /dev/null +++ b/ports/stm32/usbd.c @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * 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/mpconfig.h" + +#if MICROPY_HW_ENABLE_USBDEV && MICROPY_HW_TINYUSB_STACK + +#include "mp_usbd.h" +#include "py/mpconfig.h" +#include "string.h" +#include "mphalport.h" + +void mp_usbd_port_get_serial_number(char *serial_buf) { + uint8_t *id = (uint8_t *)MP_HAL_UNIQUE_ID_ADDRESS; + MP_STATIC_ASSERT(12 * 2 <= MICROPY_HW_USB_DESC_STR_MAX); + mp_usbd_hex_str(serial_buf, id, 12); +} + +#endif diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 2cf79e09e07..18f350d4a93 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -37,6 +37,12 @@ #if MICROPY_HW_USB_FS || MICROPY_HW_USB_HS +#if BUILDING_MBOOT +// TinyUSB not used in mboot +#undef MICROPY_HW_TINYUSB_STACK +#endif + +// These handles are also used in Interrupt / Wakeup handlers. #if MICROPY_HW_USB_FS PCD_HandleTypeDef pcd_fs_handle; #endif @@ -56,18 +62,17 @@ PCD_HandleTypeDef pcd_hs_handle; #define OTG_HS_IRQn USB1_OTG_HS_IRQn #endif -/******************************************************************************* - PCD BSP Routines -*******************************************************************************/ - -/** - * @brief Initializes the PCD MSP. - * @param hpcd: PCD handle - * @retval None - */ -void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { +#if MICROPY_HW_TINYUSB_STACK +void pyb_usbd_init(void) +#else +void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) +#endif +{ #if MICROPY_HW_USB_FS - if (hpcd->Instance == USB_OTG_FS) { + #if MICROPY_HW_STM_USB_STACK + if (hpcd->Instance == USB_OTG_FS) + #endif + { // Configure USB GPIO's. #if defined(STM32G0) || defined(STM32G4) @@ -139,7 +144,7 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { #endif // Enable VDDUSB - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32WB) HAL_PWREx_EnableVddUSB(); #elif defined(STM32L4) if (__HAL_RCC_PWR_IS_CLK_DISABLED()) { @@ -172,12 +177,17 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { HAL_NVIC_EnableIRQ(OTG_FS_IRQn); #endif + #if MICROPY_HW_STM_USB_STACK return; + #endif } #endif #if MICROPY_HW_USB_HS - if (hpcd->Instance == USB_OTG_HS) { + #if MICROPY_HW_STM_USB_STACK + if (hpcd->Instance == USB_OTG_HS) + #endif + { #if MICROPY_HW_USB_HS_IN_FS // Configure USB GPIO's. @@ -319,6 +329,8 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { #endif // MICROPY_HW_USB_HS } +#if MICROPY_HW_STM_USB_STACK + /** * @brief DeInitializes the PCD MSP. * @param hpcd: PCD handle @@ -798,6 +810,8 @@ void HAL_PCDEx_SetConnectionState(PCD_HandleTypeDef *hpcd, uint8_t state) { } #endif +#endif // MICROPY_HW_STM_USB_STACK + #endif // MICROPY_HW_USB_FS || MICROPY_HW_USB_HS /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/ports/stm32/usbd_conf.h b/ports/stm32/usbd_conf.h index e61e7ce7efe..cb0457982d5 100644 --- a/ports/stm32/usbd_conf.h +++ b/ports/stm32/usbd_conf.h @@ -64,6 +64,10 @@ #define USBD_HS_NUM_TX_FIFO (9) #define USBD_HS_NUM_FIFO (1 + USBD_HS_NUM_TX_FIFO) +#if MICROPY_HW_TINYUSB_STACK +void pyb_usbd_init(void); +#endif + #endif // MICROPY_INCLUDED_STM32_USBD_CONF_H /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/tools/ci.sh b/tools/ci.sh index 5d401843be3..42d231c4581 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -511,7 +511,7 @@ function ci_stm32_nucleo_build { # Test building various MCU families, some with additional options. make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC - make ${MAKEOPTS} -C ports/stm32 BOARD=STM32H573I_DK + make ${MAKEOPTS} -C ports/stm32 BOARD=STM32H573I_DK CFLAGS_EXTRA='-DMICROPY_HW_TINYUSB_STACK=1' make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI COPT=-O2 CFLAGS_EXTRA='-DMICROPY_PY_THREAD=1' make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L073RZ make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L476RG DEBUG=1 From faae8e73ef654339bd2adfab2b658e6e7f5ef368 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 28 Oct 2025 16:20:31 +1100 Subject: [PATCH 1447/2098] stm32/usb: Add TinyUSB Mass Storage support. Implements USB MSC functionality for STM32 port when using TinyUSB stack, supporting both internal Flash and SD card storage mediums. Signed-off-by: Andrew Leech --- ports/stm32/Makefile | 1 + ports/stm32/main.c | 4 +- ports/stm32/mpconfigboard_common.h | 7 +- ports/stm32/msc_disk.c | 191 ++++++++++++++++++ ports/stm32/storage.c | 10 + ports/stm32/usb.c | 2 + ports/stm32/usb.h | 11 +- .../stm32/usbdev/class/src/usbd_cdc_msc_hid.c | 2 +- 8 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 ports/stm32/msc_disk.c diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 5105b35b162..b43f284b1ab 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -256,6 +256,7 @@ SRC_C += \ usbd_hid_interface.c \ usbd_msc_interface.c \ mphalport.c \ + msc_disk.c \ mpnetworkport.c \ mpthreadport.c \ irq.c \ diff --git a/ports/stm32/main.c b/ports/stm32/main.c index d56b4c4076e..2d97adb1d40 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -280,7 +280,7 @@ static bool init_sdcard_fs(void) { } } - #if MICROPY_HW_ENABLE_USB && !MICROPY_HW_TINYUSB_STACK + #if MICROPY_HW_USB_MSC if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { // if no USB MSC medium is selected then use the SD card pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_SDCARD; @@ -639,7 +639,7 @@ void stm32_main(uint32_t reset_mode) { } #endif - #if MICROPY_HW_STM_USB_STACK + #if MICROPY_HW_USB_MSC // if the SD card isn't used as the USB MSC medium then use the internal flash if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_FLASH; diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 47eb7f036c3..eefd5c05c1b 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -277,7 +277,7 @@ #define MICROPY_HW_USB_CDC_NUM (1) #endif #ifndef MICROPY_HW_USB_MSC -#define MICROPY_HW_USB_MSC (MICROPY_HW_STM_USB_STACK) +#define MICROPY_HW_USB_MSC (MICROPY_HW_ENABLE_USB) #endif #ifndef MICROPY_HW_USB_HID #define MICROPY_HW_USB_HID (MICROPY_HW_STM_USB_STACK) @@ -695,8 +695,13 @@ #define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) spi_bdev_writeblocks(MICROPY_HW_BDEV_SPIFLASH, (src), MICROPY_HW_BDEV_SPIFLASH_OFFSET_BLOCKS + (bl), (n)) #endif +// Define the FATFS maximum sector size. +#ifndef MICROPY_FATFS_MAX_SS #if defined(STM32N6) #define MICROPY_FATFS_MAX_SS (4096) +#else +#define MICROPY_FATFS_MAX_SS (512) +#endif #endif // Whether to enable caching for external SPI flash, to allow block writes that are diff --git a/ports/stm32/msc_disk.c b/ports/stm32/msc_disk.c new file mode 100644 index 00000000000..f32957cdea7 --- /dev/null +++ b/ports/stm32/msc_disk.c @@ -0,0 +1,191 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * Copyright (c) 2025 Andrew Leech + * + * 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/mpconfig.h" + +#if MICROPY_HW_USB_MSC && MICROPY_HW_TINYUSB_STACK + +#include "py/misc.h" +#include "tusb.h" +#include "storage.h" +#include "sdcard.h" +#include "usb.h" + +// Storage medium selection (shared with main.c for compatibility) +pyb_usb_storage_medium_t pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_NONE; + +// Ejection state +static bool msc_ejected = false; + +// Invoked on SCSI_CMD_INQUIRY. +// Fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively. +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + (void)lun; + memcpy(vendor_id, MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_VENDOR_STRING), 8)); + memcpy(product_id, MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_PRODUCT_STRING), 16)); + memcpy(product_rev, MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING, MIN(strlen(MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING), 4)); +} + +// Invoked on Test-Unit-Ready command. +// Return true allowing host to read/write this LUN (e.g., SD card inserted). +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + (void)lun; + + if (msc_ejected || pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + + #if MICROPY_HW_ENABLE_SDCARD + if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_SDCARD) { + if (!sdcard_is_present()) { + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + } + #endif + + return true; +} + +// Invoked on SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size. +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + (void)lun; + + if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_FLASH) { + *block_size = storage_get_block_size(); + *block_count = storage_get_block_count(); + #if MICROPY_HW_ENABLE_SDCARD + } else if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_SDCARD) { + *block_size = SDCARD_BLOCK_SIZE; + *block_count = sdcard_get_capacity_in_bytes() / (uint64_t)SDCARD_BLOCK_SIZE; + #endif + } else { + *block_size = 0; + *block_count = 0; + } +} + +// Invoked on Start-Stop-Unit command: +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + (void)lun; + (void)power_condition; + + if (load_eject) { + if (start) { + // Load disk storage + msc_ejected = false; + } else { + // Unload disk storage + msc_ejected = true; + } + } + return true; +} + +// Callback invoked on READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of read bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + (void)lun; + (void)offset; + + if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_FLASH) { + uint32_t block_count = bufsize / FLASH_BLOCK_SIZE; + int ret = storage_read_blocks(buffer, lba, block_count); + if (ret != 0) { + return -1; + } + return block_count * FLASH_BLOCK_SIZE; + #if MICROPY_HW_ENABLE_SDCARD + } else if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_SDCARD) { + uint32_t block_count = bufsize / SDCARD_BLOCK_SIZE; + if (sdcard_read_blocks(buffer, lba, block_count) != 0) { + return -1; + } + return block_count * SDCARD_BLOCK_SIZE; + #endif + } + + return -1; +} + +// Callback invoked on WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes. +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + (void)lun; + (void)offset; + + if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_FLASH) { + uint32_t block_count = bufsize / FLASH_BLOCK_SIZE; + int ret = storage_write_blocks(buffer, lba, block_count); + if (ret != 0) { + return -1; + } + return block_count * FLASH_BLOCK_SIZE; + #if MICROPY_HW_ENABLE_SDCARD + } else if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_SDCARD) { + uint32_t block_count = bufsize / SDCARD_BLOCK_SIZE; + if (sdcard_write_blocks(buffer, lba, block_count) != 0) { + return -1; + } + return block_count * SDCARD_BLOCK_SIZE; + #endif + } + + return -1; +} + +// Callback invoked on a SCSI command that's not handled by TinyUSB. +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { + (void)lun; + (void)buffer; + (void)bufsize; + + int32_t resplen = 0; + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Sync the logical unit if needed. + #if MICROPY_HW_ENABLE_STORAGE + if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_FLASH) { + storage_flush(); + } + #endif + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + return resplen; +} + +#endif // MICROPY_HW_USB_MSC && MICROPY_HW_TINYUSB_STACK diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index d26ac821e59..066fb48a603 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -171,6 +171,12 @@ bool storage_read_block(uint8_t *dest, uint32_t block) { return true; + } else if (block < FLASH_PART1_START_BLOCK) { + // Blocks between MBR and first partition: return zeros + // (This handles blocks 1 to FLASH_PART1_START_BLOCK-1) + memset(dest, 0, FLASH_BLOCK_SIZE); + return true; + #if defined(MICROPY_HW_BDEV_READBLOCK) } else if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_NUM_BLOCKS, 0)) { return MICROPY_HW_BDEV_READBLOCK(dest, block - FLASH_PART1_START_BLOCK); @@ -184,6 +190,10 @@ bool storage_write_block(const uint8_t *src, uint32_t block) { if (block == 0) { // can't write MBR, but pretend we did return true; + } else if (block < FLASH_PART1_START_BLOCK) { + // Blocks between MBR and first partition: ignore writes + // (This handles blocks 1 to FLASH_PART1_START_BLOCK-1) + return true; #if defined(MICROPY_HW_BDEV_WRITEBLOCK) } else if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_NUM_BLOCKS, 0)) { return MICROPY_HW_BDEV_WRITEBLOCK(src, block - FLASH_PART1_START_BLOCK); diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 15d5637130a..c17d049124f 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -92,7 +92,9 @@ typedef struct _usb_device_t { } usb_device_t; usb_device_t usb_device = {0}; +#if MICROPY_HW_USB_MSC pyb_usb_storage_medium_t pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_NONE; +#endif #if !MICROPY_HW_USB_IS_MULTI_OTG diff --git a/ports/stm32/usb.h b/ports/stm32/usb.h index 3c382887adc..70e3ee64c7b 100644 --- a/ports/stm32/usb.h +++ b/ports/stm32/usb.h @@ -26,7 +26,9 @@ #ifndef MICROPY_INCLUDED_STM32_USB_H #define MICROPY_INCLUDED_STM32_USB_H +#if MICROPY_HW_STM_USB_STACK #include "usbd_cdc_msc_hid0.h" +#endif #define PYB_USB_FLAG_USB_MODE_CALLED (0x0002) @@ -41,10 +43,16 @@ typedef enum { USB_PHY_HS_ID = 1, } USB_PHY_ID; +// Storage medium variable used by both USB stacks when MSC is enabled +#if MICROPY_HW_USB_MSC +extern pyb_usb_storage_medium_t pyb_usb_storage_medium; +#endif + +#if MICROPY_HW_STM_USB_STACK + typedef struct _pyb_usb_vcp_obj_t pyb_usb_vcp_obj_t; extern mp_uint_t pyb_usb_flags; -extern pyb_usb_storage_medium_t pyb_usb_storage_medium; extern const struct _mp_rom_obj_tuple_t pyb_usb_hid_mouse_obj; extern const struct _mp_rom_obj_tuple_t pyb_usb_hid_keyboard_obj; extern const mp_obj_type_t pyb_usb_vcp_type; @@ -66,5 +74,6 @@ void usb_vcp_attach_to_repl(const pyb_usb_vcp_obj_t *self, bool attached); void pyb_usb_host_init(void); void pyb_usb_host_process(void); uint pyb_usb_host_get_keyboard(void); +#endif #endif // MICROPY_INCLUDED_STM32_USB_H diff --git a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c index 65d2fd70e5c..e541c321343 100644 --- a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c +++ b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c @@ -28,7 +28,7 @@ #include "usbd_ioreq.h" #include "usbd_cdc_msc_hid.h" -#if MICROPY_HW_ENABLE_USB +#if MICROPY_HW_STM_USB_STACK #define HEAD_DESC_SIZE (9) #define MSC_CLASS_DESC_SIZE (9 + 7 + 7) From 27b7bf36a33f7c51a976c41b8682d7acfb4201cb Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 23 Oct 2025 09:48:00 +1100 Subject: [PATCH 1448/2098] stm32/usb: Add VBUS sensing configuration for TinyUSB on F4/F7. TinyUSB's `dwc2_phy_init()` only sets the PWRDWN bit in the GCCFG register but doesn't configure VBUS sensing, which is required for the DWC2 USB controller to detect host connection on STM32F4 and STM32F7 series. Without VBUS sensing configured, USB devices fail to enumerate on these platforms when using the TinyUSB stack, while the legacy STM32 USB stack works because it includes this configuration. This commit adds VBUS sensing configuration in `pyb_usbd_init()` for TinyUSB mode on STM32F4/F7: - When VBUS detect pin is configured (typically PA9): Enable "B device" VBUS sensing (USB_OTG_GCCFG_VBUSBSEN) - When no VBUS pin: Force VBUS valid (USB_OTG_GCCFG_NOVBUSSENS) Tested on: - NUCLEO-F429ZI: USB now enumerates successfully (CDC + MSC) - NUCLEO-H563ZI: No regression (STM32H5 uses different register layout) Signed-off-by: Andrew Leech --- ports/stm32/usbd_conf.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 18f350d4a93..76142789d41 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -177,6 +177,22 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) HAL_NVIC_EnableIRQ(OTG_FS_IRQn); #endif + #if MICROPY_HW_TINYUSB_STACK + // Configure VBUS sensing for TinyUSB on STM32F4/F7 which have separate GCCFG register + // The DWC2 PHY init only sets PWRDWN bit, but doesn't configure VBUS sensing + #if defined(STM32F4) || defined(STM32F7) + #if defined(MICROPY_HW_USB_VBUS_DETECT_PIN) + // Enable VBUS sensing in "B device" mode (using PA9) + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; + #else + // Force VBUS valid (no VBUS detect pin configured) + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG &= ~(USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_VBUSASEN); + #endif + #endif + #endif + #if MICROPY_HW_STM_USB_STACK return; #endif From 3da529554d013cce2eec7736dea3f2c561563a52 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 25 Oct 2025 18:39:08 +0200 Subject: [PATCH 1449/2098] py/emitinlinerv32: Refactor opcode arguments validation. This commit simplifies the way arguments are validated when processing RV32 inline assembler opcodes. Opcode arguments were handled in two separate passes, one that performed a pure validation (with an early rejection in case of errors), and another that converted the parse node into a serialised value but without any error checking. Considering that the validation pass effectively performed the parse node conversion and then discarded its result once validated, it is preferable to hold onto the serialised result to reuse it later at opcode generation time. With these changes, those two passes are merged into one single operation when applicable (basically any opcode that doesn't use an integer offset), removing a fair amount of duplicate code. The size savings should be around half a kilobyte, with no other changes in the assembler's behaviour. Signed-off-by: Alessandro Gatti --- py/emitinlinerv32.c | 149 +++++++++++++++----------------------------- 1 file changed, 49 insertions(+), 100 deletions(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index 8693b3bd1c0..948b693179f 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -196,7 +196,6 @@ typedef enum { CALL_R, // Opcode Register CALL_RL, // Opcode Register, Label CALL_N, // Opcode - CALL_I, // Opcode Immediate CALL_RII, // Opcode Register, Register, Immediate CALL_RIR, // Opcode Register, Immediate(Register) CALL_COUNT @@ -210,7 +209,6 @@ typedef enum { #define U (1 << 2) // Unsigned immediate #define Z (1 << 3) // Non-zero -typedef void (*call_l_t)(asm_rv32_t *state, mp_uint_t label_index); typedef void (*call_ri_t)(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); typedef void (*call_rri_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_int_t immediate); typedef void (*call_rii_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t immediate1, mp_int_t immediate2); @@ -439,8 +437,7 @@ static bool validate_integer(mp_uint_t value, mp_uint_t mask, mp_uint_t flags) { #define ET_WRONG_ARGUMENTS_COUNT MP_ERROR_TEXT("opcode '%q': expecting %d arguments") #define ET_OUT_OF_RANGE MP_ERROR_TEXT("opcode '%q' argument %d: out of range") -static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, - const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index) { +static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index, mp_uint_t *serialised) { assert((node_index < 3) && "Invalid argument node number."); uint32_t kind = 0; @@ -470,17 +467,19 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, break; } + mp_uint_t serialised_value = 0; + switch (kind & 0x03) { case N: assert(mask == OPCODE_MASKS[MASK_NOT_USED] && "Invalid mask index for missing operand."); - return true; + break; case R: { mp_uint_t register_index; if (!parse_register_node(node, ®ister_index, false)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_register)); + ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_register)); return false; } @@ -488,11 +487,11 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("opcode '%q' argument %d: unknown register"), - opcode_qstr, node_index + 1)); + opcode->qstring, node_index + 1)); return false; } - return true; + serialised_value = (kind & C) ? RV32_MAP_IN_C_REGISTER_WINDOW(register_index) : register_index; } break; @@ -501,7 +500,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (!mp_parse_node_get_int_maybe(node, &object)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_integer)); + ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_integer)); return false; } @@ -520,7 +519,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, goto zero_immediate; } - return true; + serialised_value = immediate; } break; @@ -528,7 +527,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, if (!MP_PARSE_NODE_IS_ID(node)) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_label)); + ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_label)); return false; } @@ -538,7 +537,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("opcode '%q' argument %d: undefined label '%q'"), - opcode_qstr, node_index + 1, qstring)); + opcode->qstring, node_index + 1, qstring)); return false; } @@ -552,27 +551,33 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, goto out_of_range; } } - return true; + + serialised_value = displacement; } break; default: assert(!"Unknown argument kind"); + MP_UNREACHABLE; break; } - return false; + if (serialised != NULL) { + *serialised = serialised_value; + } + + return true; out_of_range: emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_qstr, node_index + 1)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode->qstring, node_index + 1)); return false; zero_immediate: emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("opcode '%q' argument %d: must not be zero"), - opcode_qstr, node_index + 1)); + opcode->qstring, node_index + 1)); return false; } @@ -618,14 +623,14 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr return false; } } else { - if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 1)) { + if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 1, NULL)) { return false; } } *offset_node = node_struct->nodes[0]; node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; - if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 2)) { + if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 2, NULL)) { return false; } *register_node = node_struct->nodes[0]; @@ -638,115 +643,58 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr return false; } -static void handle_opcode(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *arguments) { - mp_uint_t rd = 0; - mp_uint_t rs1 = 0; - mp_uint_t rs2 = 0; - +static void handle_opcode(emit_inline_asm_t *emit, const opcode_t *opcode_data, mp_uint_t *arguments) { switch (opcode_data->calling_convention) { - case CALL_RRR: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); - parse_register_node(arguments[2], &rs2, opcode_data->argument3_kind & C); - ((call_rrr_t)opcode_data->emitter)(&emit->as, rd, rs1, rs2); + case CALL_RRR: + ((call_rrr_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]); break; - } - case CALL_RR: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); - ((call_rr_t)opcode_data->emitter)(&emit->as, rd, rs1); + case CALL_RR: + ((call_rr_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1]); break; - } - case CALL_RRI: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); - mp_obj_t object; - mp_parse_node_get_int_maybe(arguments[2], &object); - mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift; - ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, immediate); + case CALL_RRI: + ((call_rri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]); break; - } - case CALL_RI: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - mp_obj_t object; - mp_parse_node_get_int_maybe(arguments[1], &object); - mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; - ((call_ri_t)opcode_data->emitter)(&emit->as, rd, immediate); + case CALL_RI: + ((call_ri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1]); break; - } - case CALL_R: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - ((call_r_t)opcode_data->emitter)(&emit->as, rd); + case CALL_R: + ((call_r_t)opcode_data->emitter)(&emit->as, arguments[0]); break; - } - case CALL_RRL: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C); - qstr qstring; - mp_uint_t label_index = lookup_label(emit, arguments[2], &qstring); - ptrdiff_t displacement = label_code_offset(emit, label_index); - ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, displacement); + case CALL_RRL: + ((call_rri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], (ptrdiff_t)arguments[2]); break; - } - case CALL_RL: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - qstr qstring; - mp_uint_t label_index = lookup_label(emit, arguments[1], &qstring); - ptrdiff_t displacement = label_code_offset(emit, label_index); - ((call_ri_t)opcode_data->emitter)(&emit->as, rd, displacement); + case CALL_RL: + ((call_ri_t)opcode_data->emitter)(&emit->as, arguments[0], (ptrdiff_t)arguments[1]); break; - } - case CALL_L: { - qstr qstring; - mp_uint_t label_index = lookup_label(emit, arguments[0], &qstring); - ptrdiff_t displacement = label_code_offset(emit, label_index); - ((call_i_t)opcode_data->emitter)(&emit->as, displacement); + case CALL_L: + ((call_i_t)opcode_data->emitter)(&emit->as, (ptrdiff_t)arguments[0]); break; - } case CALL_N: ((call_n_t)opcode_data->emitter)(&emit->as); break; - case CALL_I: { - mp_obj_t object; - mp_parse_node_get_int_maybe(arguments[0], &object); - mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument1_shift; - ((call_i_t)opcode_data->emitter)(&emit->as, immediate); - break; - } - - case CALL_RII: { - parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); - mp_obj_t object; - mp_parse_node_get_int_maybe(arguments[1], &object); - mp_uint_t immediate1 = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; - mp_parse_node_get_int_maybe(arguments[2], &object); - mp_uint_t immediate2 = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift; - ((call_rii_t)opcode_data->emitter)(&emit->as, rd, immediate1, immediate2); - break; - } - - case CALL_RIR: - assert(!"Should not get here."); + case CALL_RII: + ((call_rii_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]); break; default: assert(!"Unhandled call convention."); + MP_UNREACHABLE; break; } } static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *argument_nodes) { mp_parse_node_t nodes[3] = {0}; - if (!validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { + if (!serialise_argument(emit, opcode_data, argument_nodes[0], 0, NULL)) { return false; } nodes[0] = argument_nodes[0]; @@ -806,16 +754,17 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->arguments_count)); return; } - if (opcode_data->arguments_count >= 1 && !validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { + mp_uint_t serialised_arguments[3] = { 0 }; + if (opcode_data->arguments_count >= 1 && !serialise_argument(emit, opcode_data, argument_nodes[0], 0, &serialised_arguments[0])) { return; } - if (opcode_data->arguments_count >= 2 && !validate_argument(emit, opcode, opcode_data, argument_nodes[1], 1)) { + if (opcode_data->arguments_count >= 2 && !serialise_argument(emit, opcode_data, argument_nodes[1], 1, &serialised_arguments[1])) { return; } - if (opcode_data->arguments_count >= 3 && !validate_argument(emit, opcode, opcode_data, argument_nodes[2], 2)) { + if (opcode_data->arguments_count >= 3 && !serialise_argument(emit, opcode_data, argument_nodes[2], 2, &serialised_arguments[2])) { return; } - handle_opcode(emit, opcode, opcode_data, argument_nodes); + handle_opcode(emit, opcode_data, serialised_arguments); return; } From 385e4f3d0cb726a33beaca17bf403f1850bb3cbf Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 26 Oct 2025 20:21:15 +0100 Subject: [PATCH 1450/2098] py/emitinlinerv32: Refactor load/store opcodes validation. This commit minimises the amount of code required to perform validation of load/store opcodes, streamlining their validation and serialisation. Load/store opcodes used to be handled as a special case due to how its peculiar syntax yields parse node arguments that cannot be handled by the regular validation and serialisation functions. The changes in this commit attempt to reduce the amount of special code needed for those opcodes to its bare minimum, by removing the special opcode handling step, merging the validation and serialisation pass for the combined offset + base register operand, and integrate said changes in the existing argument handling structure. That allowed to rework the special operand parsing function to make it smaller, and remove the code that performed the special case validation and emitted the opcode. Signed-off-by: Alessandro Gatti --- py/emitinlinerv32.c | 140 ++++++++++++++++---------------------------- 1 file changed, 52 insertions(+), 88 deletions(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index 948b693179f..a5f4c49c294 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -223,7 +223,7 @@ typedef struct _opcode_t { uint16_t argument1_mask : 4; uint16_t argument2_mask : 4; uint16_t argument3_mask : 4; - uint16_t arguments_count : 2; + uint16_t parse_nodes : 2; // 2 bits available here uint32_t calling_convention : 4; uint32_t argument1_kind : 4; @@ -327,7 +327,7 @@ static const opcode_t OPCODES[] = { { MP_QSTR_c_jr, MASK_FFFFFFFE, MASK_NOT_USED, MASK_NOT_USED, 1, CALL_R, R, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cjr }, { MP_QSTR_c_li, MASK_FFFFFFFE, MASK_0000003F, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cli }, { MP_QSTR_c_lui, MASK_FFFFFFFA, MASK_0001F800, MASK_NOT_USED, 2, CALL_RI, R, 0, IUZ, 12, N, 0, RV32_EXT_NONE, asm_rv32_opcode_clui }, - { MP_QSTR_c_lw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_clw }, + { MP_QSTR_c_lw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 2, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_clw }, { MP_QSTR_c_lwsp, MASK_FFFFFFFE, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_clwsp }, { MP_QSTR_c_mv, MASK_FFFFFFFE, MASK_FFFFFFFE, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cmv }, { MP_QSTR_c_nop, MASK_NOT_USED, MASK_NOT_USED, MASK_NOT_USED, 0, CALL_N, N, 0, N, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cnop }, @@ -336,7 +336,7 @@ static const opcode_t OPCODES[] = { { MP_QSTR_c_srai, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csrai }, { MP_QSTR_c_srli, MASK_0000FF00, MASK_0000001F, MASK_NOT_USED, 2, CALL_RI, RC, 0, IU, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csrli }, { MP_QSTR_c_sub, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_csub }, - { MP_QSTR_c_sw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 3, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_csw }, + { MP_QSTR_c_sw, MASK_0000FF00, MASK_0000007C, MASK_0000FF00, 2, CALL_RIR, RC, 0, I, 0, RC, 0, RV32_EXT_NONE, asm_rv32_opcode_csw }, { MP_QSTR_c_swsp, MASK_FFFFFFFF, MASK_000000FC, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cswsp }, { MP_QSTR_c_xor, MASK_0000FF00, MASK_0000FF00, MASK_NOT_USED, 2, CALL_RR, RC, 0, RC, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cxor }, { MP_QSTR_div, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_div }, @@ -346,13 +346,13 @@ static const opcode_t OPCODES[] = { { MP_QSTR_jal, MASK_FFFFFFFF, MASK_001FFFFE, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_jal }, { MP_QSTR_jalr, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_jalr }, { MP_QSTR_la, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RL, R, 0, L, 0, N, 0, RV32_EXT_NONE, opcode_la }, - { MP_QSTR_lb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lb }, - { MP_QSTR_lbu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lbu }, - { MP_QSTR_lh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lh }, - { MP_QSTR_lhu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lhu }, + { MP_QSTR_lb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lb }, + { MP_QSTR_lbu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lbu }, + { MP_QSTR_lh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lh }, + { MP_QSTR_lhu, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lhu }, { MP_QSTR_li, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 0, N, 0, RV32_EXT_NONE, opcode_li }, { MP_QSTR_lui, MASK_FFFFFFFF, MASK_FFFFF000, MASK_NOT_USED, 2, CALL_RI, R, 0, I, 12, N, 0, RV32_EXT_NONE, asm_rv32_opcode_lui }, - { MP_QSTR_lw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lw }, + { MP_QSTR_lw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_lw }, { MP_QSTR_mv, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_NOT_USED, 2, CALL_RR, R, 0, R, 0, N, 0, RV32_EXT_NONE, asm_rv32_opcode_cmv }, { MP_QSTR_mul, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mul }, { MP_QSTR_mulh, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_mulh }, @@ -362,8 +362,8 @@ static const opcode_t OPCODES[] = { { MP_QSTR_ori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_ori }, { MP_QSTR_rem, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_rem }, { MP_QSTR_remu, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_remu }, - { MP_QSTR_sb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sb }, - { MP_QSTR_sh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sh }, + { MP_QSTR_sb, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sb }, + { MP_QSTR_sh, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sh }, { MP_QSTR_sh1add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh1add }, { MP_QSTR_sh2add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh2add }, { MP_QSTR_sh3add, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_ZBA, asm_rv32_opcode_sh3add }, @@ -378,7 +378,7 @@ static const opcode_t OPCODES[] = { { MP_QSTR_srl, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_srl }, { MP_QSTR_srli, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_0000001F, 3, CALL_RRI, R, 0, R, 0, IU, 0, RV32_EXT_NONE, asm_rv32_opcode_srli }, { MP_QSTR_sub, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sub }, - { MP_QSTR_sw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 3, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sw }, + { MP_QSTR_sw, MASK_FFFFFFFF, MASK_00000FFF, MASK_FFFFFFFF, 2, CALL_RIR, R, 0, I, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_sw }, { MP_QSTR_xor, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_FFFFFFFF, 3, CALL_RRR, R, 0, R, 0, R, 0, RV32_EXT_NONE, asm_rv32_opcode_xor }, { MP_QSTR_xori, MASK_FFFFFFFF, MASK_FFFFFFFF, MASK_00000FFF, 3, CALL_RRI, R, 0, R, 0, I, 0, RV32_EXT_NONE, asm_rv32_opcode_xori }, }; @@ -439,6 +439,7 @@ static bool validate_integer(mp_uint_t value, mp_uint_t mask, mp_uint_t flags) { static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index, mp_uint_t *serialised) { assert((node_index < 3) && "Invalid argument node number."); + assert(serialised && "Serialised value pointer is NULL."); uint32_t kind = 0; uint32_t shift = 0; @@ -562,10 +563,7 @@ static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, break; } - if (serialised != NULL) { - *serialised = serialised_value; - } - + *serialised = serialised_value; return true; out_of_range: @@ -581,19 +579,18 @@ static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, return false; } -static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr, const opcode_t *opcode_data, mp_parse_node_t node, mp_uint_t node_index, mp_parse_node_t *register_node, mp_parse_node_t *offset_node, bool *negative) { - assert(register_node != NULL && "Register node pointer is NULL."); - assert(offset_node != NULL && "Offset node pointer is NULL."); - assert(negative != NULL && "Negative pointer is NULL."); +static bool serialise_register_offset_node(emit_inline_asm_t *emit, const opcode_t *opcode_data, mp_parse_node_t node, mp_uint_t node_index, mp_uint_t *offset, mp_uint_t *base) { + assert(offset && "Attempting to store the offset value into NULL."); + assert(base && "Attempting to store the base register into NULL."); if (!MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_atom_expr_normal) && !MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_factor_2)) { goto invalid_structure; } mp_parse_node_struct_t *node_struct = (mp_parse_node_struct_t *)node; - *negative = false; + bool negative = false; if (MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_factor_2)) { if (MP_PARSE_NODE_IS_TOKEN_KIND(node_struct->nodes[0], MP_TOKEN_OP_MINUS)) { - *negative = true; + negative = true; } else { if (!MP_PARSE_NODE_IS_TOKEN_KIND(node_struct->nodes[0], MP_TOKEN_OP_PLUS)) { goto invalid_structure; @@ -605,41 +602,40 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; } - if (*negative) { + if (negative) { // If the value is negative, RULE_atom_expr_normal's first token will be the // offset stripped of its negative marker; range check will then fail if the // default method is used, so a custom check is used instead. mp_obj_t object; if (!mp_parse_node_get_int_maybe(node_struct->nodes[0], &object)) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_WRONG_ARGUMENT_KIND, opcode_qstr, 2, MP_QSTR_integer)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_WRONG_ARGUMENT_KIND, opcode_data->qstring, 2, MP_QSTR_integer)); return false; } mp_uint_t value = mp_obj_get_int_truncated(object); value = (~value + 1) & (mp_uint_t)-1; if (!validate_integer(value << opcode_data->argument2_shift, OPCODE_MASKS[opcode_data->argument2_mask], opcode_data->argument2_kind)) { emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_qstr, 2)); + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_data->qstring, 2)); return false; } + *offset = value; } else { - if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 1, NULL)) { + if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 1, offset)) { return false; } } - *offset_node = node_struct->nodes[0]; node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; - if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 2, NULL)) { + if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 2, base)) { return false; } - *register_node = node_struct->nodes[0]; return true; invalid_structure: emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_offset)); + ET_WRONG_ARGUMENT_KIND, opcode_data->qstring, node_index + 1, MP_QSTR_offset)); return false; } @@ -685,6 +681,11 @@ static void handle_opcode(emit_inline_asm_t *emit, const opcode_t *opcode_data, ((call_rii_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]); break; + case CALL_RIR: + // The last two arguments indices are swapped on purpose. + ((call_rri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[2], arguments[1]); + break; + default: assert(!"Unhandled call convention."); MP_UNREACHABLE; @@ -692,42 +693,6 @@ static void handle_opcode(emit_inline_asm_t *emit, const opcode_t *opcode_data, } } -static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *argument_nodes) { - mp_parse_node_t nodes[3] = {0}; - if (!serialise_argument(emit, opcode_data, argument_nodes[0], 0, NULL)) { - return false; - } - nodes[0] = argument_nodes[0]; - bool negative = false; - if (!parse_register_offset_node(emit, opcode, opcode_data, argument_nodes[1], 1, &nodes[1], &nodes[2], &negative)) { - return false; - } - - mp_uint_t rd = 0; - mp_uint_t rs1 = 0; - if (!parse_register_node(nodes[0], &rd, opcode_data->argument1_kind & C)) { - return false; - } - if (!parse_register_node(nodes[1], &rs1, opcode_data->argument3_kind & C)) { - return false; - } - - mp_obj_t object; - mp_parse_node_get_int_maybe(nodes[2], &object); - mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift; - if (negative) { - immediate = (~immediate + 1) & (mp_uint_t)-1; - } - if (!is_in_signed_mask(OPCODE_MASKS[opcode_data->argument2_mask], immediate)) { - emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode, 2)); - return false; - } - - ((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, immediate); - return true; -} - static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uint_t arguments_count, mp_parse_node_t *argument_nodes) { const opcode_t *opcode_data = NULL; for (mp_uint_t index = 0; index < MP_ARRAY_SIZE(OPCODES); index++) { @@ -747,37 +712,36 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin assert((opcode_data->argument2_mask < MP_ARRAY_SIZE(OPCODE_MASKS)) && "Argument #2 opcode mask index out of bounds."); assert((opcode_data->argument3_mask < MP_ARRAY_SIZE(OPCODE_MASKS)) && "Argument #3 opcode mask index out of bounds."); assert((opcode_data->calling_convention < CALL_COUNT) && "Calling convention index out of bounds."); - if (opcode_data->calling_convention != CALL_RIR) { - if (opcode_data->arguments_count != arguments_count) { - emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, - ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->arguments_count)); - return; - } - mp_uint_t serialised_arguments[3] = { 0 }; - if (opcode_data->arguments_count >= 1 && !serialise_argument(emit, opcode_data, argument_nodes[0], 0, &serialised_arguments[0])) { + mp_uint_t serialised_arguments[3] = { 0 }; + if (arguments_count != opcode_data->parse_nodes) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->parse_nodes)); + return; + } + + if (opcode_data->parse_nodes >= 1 && !serialise_argument(emit, opcode_data, argument_nodes[0], 0, &serialised_arguments[0])) { + return; + } + if (opcode_data->calling_convention == CALL_RIR) { + // "register, offset(base)" calls require some preprocessing to + // split the offset and base nodes - not to mention that if the offset + // is negative, the parser won't see the offset as a single node but as + // a sequence of the minus sign token followed by the number itself. + + if (!serialise_register_offset_node(emit, opcode_data, argument_nodes[1], 1, &serialised_arguments[1], &serialised_arguments[2])) { return; } - if (opcode_data->arguments_count >= 2 && !serialise_argument(emit, opcode_data, argument_nodes[1], 1, &serialised_arguments[1])) { + } else { + if (opcode_data->parse_nodes >= 2 && !serialise_argument(emit, opcode_data, argument_nodes[1], 1, &serialised_arguments[1])) { return; } - if (opcode_data->arguments_count >= 3 && !serialise_argument(emit, opcode_data, argument_nodes[2], 2, &serialised_arguments[2])) { + if (opcode_data->parse_nodes >= 3 && !serialise_argument(emit, opcode_data, argument_nodes[2], 2, &serialised_arguments[2])) { return; } - handle_opcode(emit, opcode_data, serialised_arguments); - return; - } - - assert((opcode_data->argument2_kind & U) == 0 && "Offset must not be unsigned."); - assert((opcode_data->argument2_kind & Z) == 0 && "Offset can be zero."); - - if (arguments_count != 2) { - emit_inline_rv32_error_exc(emit, - mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_WRONG_ARGUMENTS_COUNT, opcode, 2)); - return; } - handle_load_store_opcode_with_offset(emit, opcode, opcode_data, argument_nodes); + handle_opcode(emit, opcode_data, serialised_arguments); } #undef N From a1684ad2c142e804cffce8f4f14b7293c1ac44b4 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 28 Oct 2025 15:38:38 +0100 Subject: [PATCH 1451/2098] py/asmrv32: Refactor register-indexed load/store emitters. This commit shortens register-indexed load/store emitter functions, by reusing integer-indexed equivalent operations as part of the sequence generation process. Before these changes, register-indexed load/store emitters would follow two steps to generate the sequence: generate opcodes to fix up the register offset to make it point to the exact position in memory where the operation should take place, and then perform the load/store operation itself using 0 as an offset from the recalculated address register. Since there is already a generic optimised emitter for integer-indexed load/stores, that bit of code can be reused rather than having an ad-hoc implementation that is tailored to operate on an offset of 0. Removing the custom emitter code in favour of calling the general integer-indexed emitter saves around 150 bytes without any changes in the emitter behaviour (generating the same opcode sequence and making use of future improvement in that emitter too). Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 9313e61ccb7..9e4fefec0d4 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -577,24 +577,12 @@ static void asm_rv32_fix_up_scaled_reg_reg_reg(asm_rv32_t *state, mp_uint_t rs1, void asm_rv32_emit_load_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { asm_rv32_fix_up_scaled_reg_reg_reg(state, rs1, rs2, operation_size); - if (operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs1)) { - // c.lw rd', offset(rs') - asm_rv32_opcode_clw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs1), 0); - } else { - // lbu|lhu|lw rd, offset(rs) - asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rs1, 0)); - } + asm_rv32_emit_load_reg_reg_offset(state, rd, rs1, 0, operation_size); } void asm_rv32_emit_store_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { asm_rv32_fix_up_scaled_reg_reg_reg(state, rs1, rs2, operation_size); - if (operation_size == 2 && RV32_IS_IN_C_REGISTER_WINDOW(rd) && RV32_IS_IN_C_REGISTER_WINDOW(rs1)) { - // c.sw rd', offset(rs') - asm_rv32_opcode_csw(state, RV32_MAP_IN_C_REGISTER_WINDOW(rd), RV32_MAP_IN_C_REGISTER_WINDOW(rs1), 0); - } else { - // sb|sh|sw rd, offset(rs) - asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_S(0x23, operation_size, rs1, rd, 0)); - } + asm_rv32_emit_store_reg_reg_offset(state, rd, rs1, 0, operation_size); } void asm_rv32_meta_comparison_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd) { From 3cd95dda649b1b3c855a577cb8f97c874c7047d6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 30 Oct 2025 22:47:23 +0100 Subject: [PATCH 1452/2098] py/asmrv32: Generate better comparison sequences. This commit changes the sequences generated for not-equal and less-than-or-equal comparisons, in favour of better replacements. The new not-equal comparison generates a sequence of equal size but without the burden of a jump to set the output value, this also had the effect of reducing the size of the code generator as only two opcodes need to be generated instead of three. The less-than-or-equal sequence, on the other hand, is actually two bytes shorter and does not contain any jumps. If Zcb opcodes can be used for performing the final XOR operation then two more bytes could be saved on each comparison. The same remarks about having a shorter generator due to two opcodes being generated instead of three still applies here. Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 9e4fefec0d4..1d0cea6c026 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -586,13 +586,10 @@ void asm_rv32_emit_store_reg_reg_reg(asm_rv32_t *state, mp_uint_t rd, mp_uint_t } void asm_rv32_meta_comparison_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd) { - // c.li rd, 1 ; - // beq rs1, rs2, 6 ; PC + 0 - // c.li rd, 0 ; PC + 4 - // ... ; PC + 6 - asm_rv32_opcode_cli(state, rd, 1); - asm_rv32_opcode_beq(state, rs1, rs2, 6); - asm_rv32_opcode_cli(state, rd, 0); + // sub rd, rs1, rs2 + // sltiu rd, rd, 1 + asm_rv32_opcode_sub(state, rd, rs1, rs2); + asm_rv32_opcode_sltiu(state, rd, rd, 1); } void asm_rv32_meta_comparison_ne(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd) { @@ -608,13 +605,10 @@ void asm_rv32_meta_comparison_lt(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2 } void asm_rv32_meta_comparison_le(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd, bool unsigned_comparison) { - // c.li rd, 1 ; - // beq rs1, rs2, 8 ; PC + 0 - // slt(u) rd, rs1, rs2 ; PC + 4 - // ... ; PC + 8 - asm_rv32_opcode_cli(state, rd, 1); - asm_rv32_opcode_beq(state, rs1, rs2, 8); - asm_rv32_meta_comparison_lt(state, rs1, rs2, rd, unsigned_comparison); + // slt[u] rd, rs2, rs1 + // xori rd, rd, 1 + asm_rv32_meta_comparison_lt(state, rs2, rs1, rd, unsigned_comparison); + asm_rv32_opcode_xori(state, rd, rd, 1); } #endif // MICROPY_EMIT_RV32 From e0a9b7023b28ac920b9e8e82f9bb4e8fda49ab3e Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 2 Nov 2025 11:41:43 -0500 Subject: [PATCH 1453/2098] py/objcode: Remove `mp_obj_code_t.lnotab` field from v2 preview. This field exists to cache the lnotab field removed from v2 in #17639 by ddf2c3afb17c0ea3dd678d02d9c2f01bed5a3020, and is now unused. Signed-off-by: Anson Mansfield --- py/objcode.c | 2 ++ py/objcode.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/py/objcode.c b/py/objcode.c index 1ee33936c5a..09904f10f36 100644 --- a/py/objcode.c +++ b/py/objcode.c @@ -237,7 +237,9 @@ mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t o->context = context; o->rc = rc; o->dict_locals = mp_locals_get(); // this is a wrong! how to do this properly? + #if !MICROPY_PREVIEW_VERSION_2 o->lnotab = MP_OBJ_NULL; + #endif return MP_OBJ_FROM_PTR(o); } diff --git a/py/objcode.h b/py/objcode.h index 8f26bd9dbd9..7be15e23f52 100644 --- a/py/objcode.h +++ b/py/objcode.h @@ -75,12 +75,13 @@ static inline const void *mp_code_get_proto_fun(mp_obj_code_t *self) { #define MP_CODE_QSTR_MAP(context, idx) ((qstr)(context->constants.qstr_table[idx])) typedef struct _mp_obj_code_t { - // TODO this was 4 words mp_obj_base_t base; const mp_module_context_t *context; const mp_raw_code_t *rc; mp_obj_dict_t *dict_locals; + #if !MICROPY_PREVIEW_VERSION_2 mp_obj_t lnotab; + #endif } mp_obj_code_t; mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc, bool result_required); From 2b5669d193f2a2e41d7a57cedd86ec3130d2dab3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 4 Nov 2025 01:56:33 +0100 Subject: [PATCH 1454/2098] py/emitnative: Generate shorter RV32 code for exception handling. This commit lets the native emitter generate shorter code when clearing exception objects on RV32. Since there are no direct generic ASM functions to set a specific immediate to a local variable, the native emitter usually generates an immediate assignment to a temporary register and then a store of that register into the chosen local variable. This pattern is also followed when clearing certain local variables related to exception handling, using MP_OBJ_NULL as the immediate value to set. Given that at the moment MP_OBJ_NULL is defined to be 0 (with some other spots in the native emitter that leverage that fact when checking the state of the variables mentioned earlier), and that the RV32 CPU has a dedicated register that is hardwired to read 0, a new method to set local variables to MP_OBJ_NULL is introduced. When generating RV32 code, the new macro will skip the intermediate register assignment and directly uses the X0/ZERO register to set the chosen local variable to MP_OBJ_NULL. Other platforms will still generate the same code sequence as before this change. This is a followup to 40585eaa8f1b603f0094b73764e8ce5623812ecf. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index 3aacf69a817..a33ec01ec0f 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -287,12 +287,27 @@ struct _emit_t { #define ASM_CLR_REG(state, rd) ASM_XOR_REG_REG(state, rd, rd) #endif +#if N_RV32 +#define ASM_MOV_LOCAL_MP_OBJ_NULL(as, local_num, reg_temp) \ + ASM_MOV_LOCAL_REG(as, local_num, REG_ZERO) +#else +#define ASM_MOV_LOCAL_MP_OBJ_NULL(as, local_num, reg_temp) \ + ASM_MOV_REG_IMM(as, reg_temp, (mp_uint_t)MP_OBJ_NULL); \ + ASM_MOV_LOCAL_REG(as, local_num, reg_temp) +#endif + static void emit_load_reg_with_object(emit_t *emit, int reg, mp_obj_t obj); static void emit_native_global_exc_entry(emit_t *emit); static void emit_native_global_exc_exit(emit_t *emit); static void emit_native_load_const_obj(emit_t *emit, mp_obj_t obj); emit_t *EXPORT_FUN(new)(mp_emit_common_t * emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels) { + // Generated code performing exception handling assumes that MP_OBJ_NULL + // equals to 0 to simplify some checks, leveraging dedicated opcodes for + // comparisons against 0. If this assumption does not hold true anymore + // then generated code won't work correctly. + MP_STATIC_ASSERT(MP_OBJ_NULL == 0); + emit_t *emit = m_new0(emit_t, 1); emit->emit_common = emit_common; emit->error_slot = error_slot; @@ -1291,8 +1306,7 @@ static void emit_native_global_exc_entry(emit_t *emit) { // Check LOCAL_IDX_THROW_VAL for any injected value ASM_MOV_REG_LOCAL(emit->as, REG_ARG_1, LOCAL_IDX_THROW_VAL(emit)); - ASM_MOV_REG_IMM(emit->as, REG_ARG_2, (mp_uint_t)MP_OBJ_NULL); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_THROW_VAL(emit), REG_ARG_2); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_THROW_VAL(emit), REG_ARG_2); emit_call(emit, MP_F_NATIVE_RAISE); } } @@ -2208,8 +2222,7 @@ static void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) { // Replace exception with MP_OBJ_NULL. emit_native_label_assign(emit, *emit->label_slot); - ASM_MOV_REG_IMM(emit->as, REG_TEMP0, (mp_uint_t)MP_OBJ_NULL); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); // end of with cleanup nlr_catch block emit_native_label_assign(emit, *emit->label_slot + 1); @@ -2316,8 +2329,7 @@ static void emit_native_for_iter_end(emit_t *emit) { static void emit_native_pop_except_jump(emit_t *emit, mp_uint_t label, bool within_exc_handler) { if (within_exc_handler) { // Cancel any active exception so subsequent handlers don't see it - ASM_MOV_REG_IMM(emit->as, REG_TEMP0, (mp_uint_t)MP_OBJ_NULL); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0); } else { emit_native_leave_exc_stack(emit, false); } @@ -3002,8 +3014,7 @@ static void emit_native_yield(emit_t *emit, int kind) { if (kind == MP_EMIT_YIELD_VALUE) { // Check LOCAL_IDX_THROW_VAL for any injected value ASM_MOV_REG_LOCAL(emit->as, REG_ARG_1, LOCAL_IDX_THROW_VAL(emit)); - ASM_MOV_REG_IMM(emit->as, REG_ARG_2, (mp_uint_t)MP_OBJ_NULL); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_THROW_VAL(emit), REG_ARG_2); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_THROW_VAL(emit), REG_ARG_2); emit_call(emit, MP_F_NATIVE_RAISE); } else { // Label loop entry From 4f2f520dc282f3b666e29aa7f316932cc9af8016 Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Sat, 18 Oct 2025 14:44:25 +0800 Subject: [PATCH 1455/2098] unix/modtime: Add type casting for mktime return value. This adds type casting to avoid build errors on certain systems. Signed-off-by: Yanfeng Liu --- ports/unix/modtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 41b7c89df42..4f0550dbea7 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -189,7 +189,7 @@ static mp_obj_t mod_time_mktime(mp_obj_t tuple) { time.tm_isdst = -1; // auto-detect } time_t ret = mktime(&time); - if (ret == -1) { + if (ret == (time_t)-1) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("invalid mktime usage")); } return timeutils_obj_from_timestamp(ret); From 1e5da2642cbb0b4c808323173b7a703c3a3cf24f Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 29 Aug 2024 10:29:35 +0200 Subject: [PATCH 1456/2098] shared/runtime: Set exit code according to the SystemExit exception. Add abort setup code `nlr_set_abort` to the standard runtime executor. This makes the standard runtime respond to abort signal without any further modifications. - When aborted, the program exits with 137 exit code (configurable, same as posix sig abort), to differentiate from a normal shutdown. - When exited by exception/crash, the program will exit with exit code 1 (configurable). - When exited by exception KeyboardInterrupt, the program will exit with exit code 130 (configurable, same as posix sig int). - When exited with a exit code (from Python environment), this code is propagated. When a different object is passed, exit code is set to 1 and the value printed, to be consistent with Python docs: https://python.readthedocs.io/en/latest/library/exceptions.html#SystemExit Signed-off-by: John Smith --- py/mpconfig.h | 10 ++++++++++ shared/runtime/pyexec.c | 42 +++++++++++++++++++++++++++++++++++------ shared/runtime/pyexec.h | 11 +++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 6ad27c51fab..97c40389b32 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1180,6 +1180,16 @@ typedef time_t mp_timestamp_t; #define MICROPY_ENABLE_VM_ABORT (0) #endif +// Whether to handle abort behavior in pyexec code +#ifndef MICROPY_PYEXEC_ENABLE_VM_ABORT +#define MICROPY_PYEXEC_ENABLE_VM_ABORT (0) +#endif + +// Whether to set exit codes according to the exit reason (keyboard interrupt, crash, normal exit, ...) +#ifndef MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING +#define MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (0) +#endif + // Support for internal scheduler #ifndef MICROPY_ENABLE_SCHEDULER #define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 7d2a70c01bc..cf17bd0dc35 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -72,6 +72,10 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input nlr_buf_t nlr; nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { + #if MICROPY_PYEXEC_ENABLE_VM_ABORT + nlr_set_abort(&nlr); + #endif + mp_obj_t module_fun; #if MICROPY_MODULE_FROZEN_MPY if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { @@ -116,7 +120,7 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_hal_set_interrupt_char(-1); // disable interrupt mp_handle_pending(true); // handle any pending exceptions (and any callbacks) nlr_pop(); - ret = 1; + ret = PYEXEC_NORMAL_EXIT; if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); } @@ -135,15 +139,41 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_hal_stdout_tx_strn("\x04", 1); } - // check for SystemExit - if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { - // at the moment, the value of SystemExit is unused + #if MICROPY_PYEXEC_ENABLE_VM_ABORT + if (nlr.ret_val == NULL) { // abort + ret = PYEXEC_ABORT; + } else + #endif + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // system exit + #if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING + mp_obj_t val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val)); + if (val != mp_const_none) { + if (mp_obj_is_int(val)) { + ret = (int)mp_obj_int_get_truncated(val); + } else { + mp_obj_print_helper(MICROPY_ERROR_PRINTER, val, PRINT_STR); + mp_print_str(MICROPY_ERROR_PRINTER, "\n"); + ret = PYEXEC_UNHANDLED_EXCEPTION; + } + } else { + ret = PYEXEC_NORMAL_EXIT; + } + #else ret = PYEXEC_FORCED_EXIT; - } else { + #endif + } else { // other exception mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); - ret = 0; + ret = PYEXEC_UNHANDLED_EXCEPTION; + #if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_KeyboardInterrupt))) { // keyboard interrupt + ret = PYEXEC_KEYBOARD_INTERRUPT; + } + #endif } } + #if MICROPY_PYEXEC_ENABLE_VM_ABORT + nlr_set_abort(NULL); + #endif #if MICROPY_REPL_INFO // display debugging info if wanted diff --git a/shared/runtime/pyexec.h b/shared/runtime/pyexec.h index 95f44816266..fb52c09b1ce 100644 --- a/shared/runtime/pyexec.h +++ b/shared/runtime/pyexec.h @@ -37,6 +37,17 @@ extern pyexec_mode_kind_t pyexec_mode_kind; #define PYEXEC_FORCED_EXIT (0x100) +#if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING +#define PYEXEC_NORMAL_EXIT (0) +#define PYEXEC_UNHANDLED_EXCEPTION (1) +#define PYEXEC_KEYBOARD_INTERRUPT (128 + 2) // same as SIG INT exit code +#define PYEXEC_ABORT (128 + 9) // same as SIG KILL exit code +#else +#define PYEXEC_NORMAL_EXIT (1) +#define PYEXEC_UNHANDLED_EXCEPTION (0) +#define PYEXEC_ABORT PYEXEC_FORCED_EXIT +#endif + int pyexec_raw_repl(void); int pyexec_friendly_repl(void); int pyexec_file(const char *filename); From 509207c9d03c6116899350d7e3ae06962c0b4df2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 25 Feb 2025 13:41:32 +0100 Subject: [PATCH 1457/2098] tools/cc1: Apply ruff formatting to cc1 script. Running `ruff format tools/cc1` picks up `tools/cc1` which is a Python file that does not have a .py file extension. Signed-off-by: Christian Clauss Signed-off-by: Damien George --- tools/cc1 | 88 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/tools/cc1 b/tools/cc1 index 827d5886a04..7a047e9ac18 100755 --- a/tools/cc1 +++ b/tools/cc1 @@ -23,8 +23,8 @@ import re # TODO somehow make them externally configurable # this is the path to the true C compiler -cc1_path = '/usr/lib/gcc/x86_64-unknown-linux-gnu/5.3.0/cc1' -#cc1_path = '/usr/lib/gcc/arm-none-eabi/5.3.0/cc1' +cc1_path = "/usr/lib/gcc/x86_64-unknown-linux-gnu/5.3.0/cc1" +# cc1_path = '/usr/lib/gcc/arm-none-eabi/5.3.0/cc1' # this must be the same as MICROPY_QSTR_BYTES_IN_HASH bytes_in_qstr_hash = 2 @@ -41,11 +41,16 @@ print_debug = False ################################################################################ # precompile regexs -re_preproc_line = re.compile(r'# [0-9]+ ') -re_map_entry = re.compile(r'\{.+?\(MP_QSTR_([A-Za-z0-9_]+)\).+\},') -re_mp_obj_dict_t = re.compile(r'(?P(static )?const mp_obj_dict_t (?P[a-z0-9_]+) = \{ \.base = \{&mp_type_dict\}, \.map = \{ \.all_keys_are_qstrs = 1, \.is_fixed = 1, \.is_ordered = )1(?P, \.used = .+ };)$') -re_mp_map_t = re.compile(r'(?P(static )?const mp_map_t (?P[a-z0-9_]+) = \{ \.all_keys_are_qstrs = 1, \.is_fixed = 1, \.is_ordered = )1(?P, \.used = .+ };)$') -re_mp_rom_map_elem_t = re.compile(r'static const mp_rom_map_elem_t [a-z_0-9]+\[\] = {$') +re_preproc_line = re.compile(r"# [0-9]+ ") +re_map_entry = re.compile(r"\{.+?\(MP_QSTR_([A-Za-z0-9_]+)\).+\},") +re_mp_obj_dict_t = re.compile( + r"(?P(static )?const mp_obj_dict_t (?P[a-z0-9_]+) = \{ \.base = \{&mp_type_dict\}, \.map = \{ \.all_keys_are_qstrs = 1, \.is_fixed = 1, \.is_ordered = )1(?P, \.used = .+ };)$" +) +re_mp_map_t = re.compile( + r"(?P(static )?const mp_map_t (?P[a-z0-9_]+) = \{ \.all_keys_are_qstrs = 1, \.is_fixed = 1, \.is_ordered = )1(?P, \.used = .+ };)$" +) +re_mp_rom_map_elem_t = re.compile(r"static const mp_rom_map_elem_t [a-z_0-9]+\[\] = {$") + # this must match the equivalent function in qstr.c def compute_hash(qstr): @@ -55,18 +60,19 @@ def compute_hash(qstr): # Make sure that valid hash is never zero, zero means "hash not computed" return (hash & ((1 << (8 * bytes_in_qstr_hash)) - 1)) or 1 + # this algo must match the equivalent in map.c def hash_insert(map, key, value): hash = compute_hash(key) pos = hash % len(map) start_pos = pos if print_debug: - print(' insert %s: start at %u/%u -- ' % (key, pos, len(map)), end='') + print(" insert %s: start at %u/%u -- " % (key, pos, len(map)), end="") while True: if map[pos] is None: # found empty slot, so key is not in table if print_debug: - print('put at %u' % pos) + print("put at %u" % pos) map[pos] = (key, value) return else: @@ -76,6 +82,7 @@ def hash_insert(map, key, value): pos = (pos + 1) % len(map) assert pos != start_pos + def hash_find(map, key): hash = compute_hash(key) pos = hash % len(map) @@ -92,6 +99,7 @@ def hash_find(map, key): if pos == start_pos: return attempts, None + def process_map_table(file, line, output): output.append(line) @@ -101,7 +109,7 @@ def process_map_table(file, line, output): while True: line = file.readline() if len(line) == 0: - print('unexpected end of input') + print("unexpected end of input") sys.exit(1) line = line.strip() if len(line) == 0: @@ -110,38 +118,38 @@ def process_map_table(file, line, output): if re_preproc_line.match(line): # preprocessor line number comment continue - if line == '};': + if line == "};": # end of table (we assume it appears on a single line) break table_contents.append(line) # make combined string of entries - entries_str = ''.join(table_contents) + entries_str = "".join(table_contents) # split into individual entries entries = [] while entries_str: # look for single entry, by matching nested braces match = None - if entries_str[0] == '{': + if entries_str[0] == "{": nested_braces = 0 for i in range(len(entries_str)): - if entries_str[i] == '{': + if entries_str[i] == "{": nested_braces += 1 - elif entries_str[i] == '}': + elif entries_str[i] == "}": nested_braces -= 1 if nested_braces == 0: - match = re_map_entry.match(entries_str[:i + 2]) + match = re_map_entry.match(entries_str[: i + 2]) break if not match: - print('unknown line in table:', entries_str) + print("unknown line in table:", entries_str) sys.exit(1) # extract single entry line = match.group(0) qstr = match.group(1) - entries_str = entries_str[len(line):].lstrip() + entries_str = entries_str[len(line) :].lstrip() # add the qstr and the whole line to list of all entries entries.append((qstr, line)) @@ -164,28 +172,28 @@ def process_map_table(file, line, output): attempts, line = hash_find(map, qstr) assert line is not None if print_debug: - print(' %s lookup took %u attempts' % (qstr, attempts)) + print(" %s lookup took %u attempts" % (qstr, attempts)) total_attempts += attempts if len(entries): stats = len(map), len(entries) / len(map), total_attempts / len(entries) else: stats = 0, 0, 0 if print_debug: - print(' table stats: size=%d, load=%.2f, avg_lookups=%.1f' % stats) + print(" table stats: size=%d, load=%.2f, avg_lookups=%.1f" % stats) # output hash table for row in map: if row is None: - output.append('{ 0, 0 },\n') + output.append("{ 0, 0 },\n") else: - output.append(row[1] + '\n') - output.append('};\n') + output.append(row[1] + "\n") + output.append("};\n") # skip to next non-blank line while True: line = file.readline() if len(line) == 0: - print('unexpected end of input') + print("unexpected end of input") sys.exit(1) line = line.strip() if len(line) == 0: @@ -197,19 +205,20 @@ def process_map_table(file, line, output): if match is None: match = re_mp_map_t.match(line) if match is None: - print('expecting mp_obj_dict_t or mp_map_t definition') + print("expecting mp_obj_dict_t or mp_map_t definition") print(output[0]) print(line) sys.exit(1) - line = match.group('head') + '0' + match.group('tail') + '\n' + line = match.group("head") + "0" + match.group("tail") + "\n" output.append(line) - return (match.group('id'),) + stats + return (match.group("id"),) + stats + def process_file(filename): output = [] file_changed = False - with open(filename, 'rt') as f: + with open(filename, "rt") as f: while True: line = f.readline() if not line: @@ -218,39 +227,41 @@ def process_file(filename): file_changed = True stats = process_map_table(f, line, output) if print_stats: - print(' [%s: size=%d, load=%.2f, avg_lookups=%.1f]' % stats) + print(" [%s: size=%d, load=%.2f, avg_lookups=%.1f]" % stats) else: output.append(line) if file_changed: if print_debug: - print(' modifying static maps in', output[0].strip()) - with open(filename, 'wt') as f: + print(" modifying static maps in", output[0].strip()) + with open(filename, "wt") as f: for line in output: f.write(line) + def main(): # run actual C compiler # need to quote args that have special characters in them def quote(s): - if s.find('<') != -1 or s.find('>') != -1: + if s.find("<") != -1 or s.find(">") != -1: return "'" + s + "'" else: return s - ret = os.system(cc1_path + ' ' + ' '.join(quote(s) for s in sys.argv[1:])) + + ret = os.system(cc1_path + " " + " ".join(quote(s) for s in sys.argv[1:])) if ret != 0: - ret = (ret & 0x7f) or 127 # make it in range 0-127, but non-zero + ret = (ret & 0x7F) or 127 # make it in range 0-127, but non-zero sys.exit(ret) - if sys.argv[1] == '-E': + if sys.argv[1] == "-E": # CPP has been run, now do our processing stage for i, arg in enumerate(sys.argv): - if arg == '-o': + if arg == "-o": return process_file(sys.argv[i + 1]) print('%s: could not find "-o" option' % (sys.argv[0],)) sys.exit(1) - elif sys.argv[1] == '-fpreprocessed': + elif sys.argv[1] == "-fpreprocessed": # compiler has been run, nothing more to do return else: @@ -258,5 +269,6 @@ def main(): print('%s: unknown first option "%s"' % (sys.argv[0], sys.argv[1])) sys.exit(1) -if __name__ == '__main__': + +if __name__ == "__main__": main() From 2a5c5093ec69baf1460d1dadb617b7f5371b5b60 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 5 Nov 2025 12:46:29 +1100 Subject: [PATCH 1458/2098] tools/cc1: Fix ruff lint check with list length. Signed-off-by: Damien George --- tools/cc1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cc1 b/tools/cc1 index 7a047e9ac18..aa2534f01e7 100755 --- a/tools/cc1 +++ b/tools/cc1 @@ -174,7 +174,7 @@ def process_map_table(file, line, output): if print_debug: print(" %s lookup took %u attempts" % (qstr, attempts)) total_attempts += attempts - if len(entries): + if entries: stats = len(map), len(entries) / len(map), total_attempts / len(entries) else: stats = 0, 0, 0 From 10b7dfd9f20f58987da511d87684662d3a38ef01 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 4 Nov 2025 16:58:02 +1100 Subject: [PATCH 1459/2098] top: Include tools/cc1 in ruff search path. So that pre-commit and CI will check formatting and linting. Signed-off-by: Damien George --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8c14c2bffa8..041dd74191f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,8 @@ ACKNOWLEDGEMENTS,\ [tool.ruff] # Exclude third-party code from linting and formatting extend-exclude = ["lib"] +# Include Python source files that don't end with .py +extend-include = ["tools/cc1"] line-length = 99 target-version = "py37" From 2762fe680a03706d3c21efe51db9b1f8d193d2d0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Nov 2025 15:41:35 +1100 Subject: [PATCH 1460/2098] tests/serial_test.py: Allow up to 2 seconds between bytes. Only a problem when UART TX is also enabled and goes first (i.e. esp32 port) as sending 16384 bytes in one go triggers the timeout. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/serial_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/serial_test.py b/tests/serial_test.py index 1baa2282a0a..455d2277f64 100755 --- a/tests/serial_test.py +++ b/tests/serial_test.py @@ -103,6 +103,11 @@ def read_test(ser_repl, ser_data, bufsize, nbuf): assert bufsize % 256 == 0 # for verify to work + # how long to wait for data from device + # (if UART TX is also enabled then it can take 1.4s to send + # out a 16KB butter at 115200bps) + READ_TIMEOUT_S = 2 + # Load and run the read_test_script. ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot drain_input(ser_repl) @@ -121,7 +126,7 @@ def read_test(ser_repl, ser_data, bufsize, nbuf): while remain: t0 = time.monotonic_ns() while ser_data.inWaiting() == 0: - if time.monotonic_ns() - t0 > 1e9: + if time.monotonic_ns() - t0 > READ_TIMEOUT_S * 1e9: # timeout waiting for data from device break time.sleep(0.0001) From b4ab3a893c55858c9bf5d644c56abc0d9f7e7f44 Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Mon, 16 Jun 2025 07:57:00 +0300 Subject: [PATCH 1461/2098] esp32/modesp32: Add esp32.wake_on_gpio() function. Some boards support waking up via GPIO pins, but this is not currently supported by MicroPython. This commit adds support for waking with GPIO in a similar interface to waking with ext0, ext1, touch and ulp. This commit adds documentation for this new function as well. Signed-off-by: Meir Armon --- docs/library/esp32.rst | 9 +++++++ ports/esp32/machine_rtc.c | 3 ++- ports/esp32/machine_rtc.h | 2 ++ ports/esp32/modesp32.c | 51 +++++++++++++++++++++++++++++++++++++-- ports/esp32/modmachine.c | 41 +++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index e5f39c7f59a..84f33f46e72 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -43,6 +43,15 @@ Functions .. note:: This is only available for boards that have ext1 support. +.. function:: wake_on_gpio(pins, level) + + Configure how GPIO wakes the device from sleep. *pins* can be ``None`` + or a tuple/list of valid Pin objects. *level* should be ``esp32.WAKEUP_ALL_LOW`` + or ``esp32.WAKEUP_ANY_HIGH``. + + .. note:: Some boards don't support waking on GPIO from deep sleep, + on those boards, the pins set here can only be used to wake from light sleep. + .. function:: gpio_deep_sleep_hold(enable) Configure whether non-RTC GPIO pin configuration is retained during diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index ee6b1ad5cb5..d11ee22b510 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -86,8 +86,9 @@ machine_rtc_config_t machine_rtc_config = { .ext1_pins = 0, #endif #if SOC_PM_SUPPORT_EXT0_WAKEUP - .ext0_pin = -1 + .ext0_pin = -1, #endif + .gpio_pins = 0, }; 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) { diff --git a/ports/esp32/machine_rtc.h b/ports/esp32/machine_rtc.h index 72717c5936d..e40a17fb3db 100644 --- a/ports/esp32/machine_rtc.h +++ b/ports/esp32/machine_rtc.h @@ -37,6 +37,7 @@ typedef struct { #if SOC_PM_SUPPORT_EXT0_WAKEUP int8_t ext0_pin; // just the pin#, -1 == None #endif + uint64_t gpio_pins; // set bit == pin# #if SOC_TOUCH_SENSOR_SUPPORTED bool wake_on_touch : 1; #endif @@ -50,6 +51,7 @@ typedef struct { #if SOC_PM_SUPPORT_EXT1_WAKEUP bool ext1_level : 1; #endif + bool gpio_level : 1; } machine_rtc_config_t; extern machine_rtc_config_t machine_rtc_config; diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 858be2ed05f..1d002fc84bb 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -46,12 +46,13 @@ #define MULTI_HEAP_FREERTOS #include "../multi_heap_platform.h" #include "../heap_private.h" +#include "driver/rtc_io.h" #if SOC_TOUCH_SENSOR_SUPPORTED static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { #if SOC_PM_SUPPORT_EXT0_WAKEUP - if (machine_rtc_config.ext0_pin != -1) { + if (machine_rtc_config.ext0_pin != -1 || machine_rtc_config.gpio_pins != 0) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } #endif @@ -140,7 +141,7 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1) #if SOC_ULP_SUPPORTED static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { #if SOC_PM_SUPPORT_EXT0_WAKEUP - if (machine_rtc_config.ext0_pin != -1) { + if (machine_rtc_config.ext0_pin != -1 || machine_rtc_config.gpio_pins != 0) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } #endif @@ -150,6 +151,51 @@ static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); #endif +static mp_obj_t esp32_wake_on_gpio(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_pins, ARG_level}; + const mp_arg_t allowed_args[] = { + { MP_QSTR_pins, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_level, MP_ARG_BOOL, {.u_bool = machine_rtc_config.gpio_level} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint64_t gpio_pins = machine_rtc_config.gpio_pins; + + #if SOC_ULP_SUPPORTED + if (machine_rtc_config.wake_on_ulp) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + #endif + + #if SOC_TOUCH_SENSOR_SUPPORTED + if (machine_rtc_config.wake_on_touch) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + #endif + + // Check that all pins are allowed + if (args[ARG_pins].u_obj != mp_const_none) { + size_t len = 0; + mp_obj_t *elem; + mp_obj_get_array(args[ARG_pins].u_obj, &len, &elem); + gpio_pins = 0; + + for (int i = 0; i < len; i++) { + // Don't validate the pins at this point, since we can be using + // gpio pins for deepsleep or light sleep. + // Validations happen in the relevant sleep functions. + gpio_num_t pin_id = machine_pin_get_id(elem[i]); + gpio_pins |= (1ll << pin_id); + } + } + + machine_rtc_config.gpio_level = args[ARG_level].u_bool; + machine_rtc_config.gpio_pins = gpio_pins; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_gpio_obj, 0, esp32_wake_on_gpio); + #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP static mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { if (mp_obj_is_true(enable)) { @@ -284,6 +330,7 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { #if SOC_ULP_SUPPORTED { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, #endif + { MP_ROM_QSTR(MP_QSTR_wake_on_gpio), MP_ROM_PTR(&esp32_wake_on_gpio_obj) }, #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, #endif diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 06360e8e899..62f0bf73cf9 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -131,6 +131,12 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } static void machine_sleep_helper(wake_type_t wake_type, size_t n_args, const mp_obj_t *args) { + #if !SOC_DEEP_SLEEP_SUPPORTED + if (MACHINE_WAKE_DEEPSLEEP == wake_type) { + mp_raise_ValueError(MP_ERROR_TEXT("DEEPSLEEP not supported on this chip")); + } + #endif + // First, disable any previously set wake-up source esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); @@ -170,6 +176,41 @@ static void machine_sleep_helper(wake_type_t wake_type, size_t n_args, const mp_ } #endif + if (machine_rtc_config.gpio_pins != 0) { + #if !SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP + if (MACHINE_WAKE_DEEPSLEEP == wake_type) { + mp_raise_ValueError(MP_ERROR_TEXT("DEEPSLEEP with gpio pins not supported on this chip")); + } + #endif + + gpio_int_type_t intr_type = machine_rtc_config.gpio_level ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; + + for (int i = 0; i < GPIO_NUM_MAX; ++i) { + gpio_num_t gpio = (gpio_num_t)i; + uint64_t bm = 1ULL << i; + + if (machine_rtc_config.gpio_pins & bm) { + gpio_sleep_set_direction(gpio, GPIO_MODE_INPUT); + + if (MACHINE_WAKE_SLEEP == wake_type) { + gpio_wakeup_enable(gpio, intr_type); + } + } + } + + if (MACHINE_WAKE_DEEPSLEEP == wake_type) { + #if SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP + if (ESP_OK != esp_deep_sleep_enable_gpio_wakeup( + machine_rtc_config.gpio_pins, + machine_rtc_config.gpio_level ? ESP_GPIO_WAKEUP_GPIO_HIGH : ESP_GPIO_WAKEUP_GPIO_LOW)) { + mp_raise_ValueError(MP_ERROR_TEXT("wake-up pin not supported")); + } + #endif + } else { + esp_sleep_enable_gpio_wakeup(); + } + } + switch (wake_type) { case MACHINE_WAKE_SLEEP: esp_light_sleep_start(); From 27544a2d81da5b0d804a932d98d680f121a22b8f Mon Sep 17 00:00:00 2001 From: Patrick Van Oosterwijck Date: Mon, 4 Aug 2025 17:13:34 -0600 Subject: [PATCH 1462/2098] esp32/boards: Add Silicognition ManT1S board definition. New board planned to be launched on Crowd Supply in late 2025. ESP32-based core module with IEEE 802.3cg 10BASE-T1S Single Pair Ethernet networking and power distribution over data lines. Signed-off-by: Patrick Van Oosterwijck --- ports/esp32/boards/SIL_MANT1S/board.json | 27 +++++ ports/esp32/boards/SIL_MANT1S/manifest.py | 2 + ports/esp32/boards/SIL_MANT1S/modules/ping.py | 114 ++++++++++++++++++ .../boards/SIL_MANT1S/mpconfigboard.cmake | 9 ++ ports/esp32/boards/SIL_MANT1S/mpconfigboard.h | 6 + .../boards/SIL_MANT1S/partitions-8MiB-ota.csv | 9 ++ ports/esp32/boards/SIL_MANT1S/pins.csv | 3 + ports/esp32/boards/SIL_MANT1S/sdkconfig.board | 20 +++ 8 files changed, 190 insertions(+) create mode 100644 ports/esp32/boards/SIL_MANT1S/board.json create mode 100644 ports/esp32/boards/SIL_MANT1S/manifest.py create mode 100644 ports/esp32/boards/SIL_MANT1S/modules/ping.py create mode 100644 ports/esp32/boards/SIL_MANT1S/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SIL_MANT1S/mpconfigboard.h create mode 100644 ports/esp32/boards/SIL_MANT1S/partitions-8MiB-ota.csv create mode 100644 ports/esp32/boards/SIL_MANT1S/pins.csv create mode 100644 ports/esp32/boards/SIL_MANT1S/sdkconfig.board diff --git a/ports/esp32/boards/SIL_MANT1S/board.json b/ports/esp32/boards/SIL_MANT1S/board.json new file mode 100644 index 00000000000..06fd7f38bf4 --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/board.json @@ -0,0 +1,27 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x1000" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "External RAM", + "WiFi" + ], + "features_non_filterable": [ + "T1S", + "10BASE-T1S" + ], + "images": [ + "mant1s-board-top.jpg" + ], + "mcu": "esp32", + "product": "ManT1S", + "thumbnail": "", + "url": "https://mant1s.net/", + "vendor": "Silicognition LLC" +} diff --git a/ports/esp32/boards/SIL_MANT1S/manifest.py b/ports/esp32/boards/SIL_MANT1S/manifest.py new file mode 100644 index 00000000000..7ae2ed15d91 --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/esp32/boards/SIL_MANT1S/modules/ping.py b/ports/esp32/boards/SIL_MANT1S/modules/ping.py new file mode 100644 index 00000000000..ec202f6f9aa --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/modules/ping.py @@ -0,0 +1,114 @@ +# µPing (MicroPing) for MicroPython +# copyright (c) 2018 Shawwwn +# License: MIT + +# Internet Checksum Algorithm +# Author: Olav Morken +# https://github.com/olavmrk/python-ping/blob/master/ping.py +# Adjusted for ruff formatting to include in ManT1S MicroPython + +import utime +import uselect +import uctypes +import usocket +import ustruct +import urandom + + +# @data: bytes +def checksum(data): + if len(data) & 0x1: # Odd number of bytes + data += b"\0" + cs = 0 + for pos in range(0, len(data), 2): + b1 = data[pos] + b2 = data[pos + 1] + cs += (b1 << 8) + b2 + while cs >= 0x10000: + cs = (cs & 0xFFFF) + (cs >> 16) + cs = ~cs & 0xFFFF + return cs + + +def ping(host, count=4, timeout=5000, interval=10, quiet=False, size=64): + # prepare packet + assert size >= 16, "pkt size too small" + pkt = b"Q" * size + pkt_desc = { + "type": uctypes.UINT8 | 0, + "code": uctypes.UINT8 | 1, + "checksum": uctypes.UINT16 | 2, + "id": uctypes.UINT16 | 4, + "seq": uctypes.INT16 | 6, + "timestamp": uctypes.UINT64 | 8, + } # packet header descriptor + h = uctypes.struct(uctypes.addressof(pkt), pkt_desc, uctypes.BIG_ENDIAN) + h.type = 8 # ICMP_ECHO_REQUEST + h.code = 0 + h.checksum = 0 + h.id = urandom.getrandbits(16) + h.seq = 1 + + # init socket + sock = usocket.socket(usocket.AF_INET, usocket.SOCK_RAW, 1) + sock.setblocking(0) + sock.settimeout(timeout / 1000) + addr = usocket.getaddrinfo(host, 1)[0][-1][0] # ip address + sock.connect((addr, 1)) + not quiet and print("PING %s (%s): %u data bytes" % (host, addr, len(pkt))) + + seqs = list(range(1, count + 1)) # [1,2,...,count] + c = 1 + t = 0 + n_trans = 0 + n_recv = 0 + finish = False + while t < timeout: + if t == interval and c <= count: + # send packet + h.checksum = 0 + h.seq = c + h.timestamp = utime.ticks_us() + h.checksum = checksum(pkt) + if sock.send(pkt) == size: + n_trans += 1 + t = 0 # reset timeout + else: + seqs.remove(c) + c += 1 + + # recv packet + while 1: + socks, _, _ = uselect.select([sock], [], [], 0) + if socks: + resp = socks[0].recv(4096) + resp_mv = memoryview(resp) + h2 = uctypes.struct(uctypes.addressof(resp_mv[20:]), pkt_desc, uctypes.BIG_ENDIAN) + # TODO: validate checksum (optional) + seq = h2.seq + if h2.type == 0 and h2.id == h.id and (seq in seqs): # 0: ICMP_ECHO_REPLY + t_elasped = (utime.ticks_us() - h2.timestamp) / 1000 + ttl = ustruct.unpack("!B", resp_mv[8:9])[0] # time-to-live + n_recv += 1 + not quiet and print( + "%u bytes from %s: icmp_seq=%u, ttl=%u, time=%f ms" + % (len(resp), addr, seq, ttl, t_elasped) + ) + seqs.remove(seq) + if len(seqs) == 0: + finish = True + break + else: + break + + if finish: + break + + utime.sleep_ms(1) + t += 1 + + # close + sock.close() + ret = (n_trans, n_recv) + not quiet and print("%u packets transmitted, %u packets received" % ret) + return ret diff --git a/ports/esp32/boards/SIL_MANT1S/mpconfigboard.cmake b/ports/esp32/boards/SIL_MANT1S/mpconfigboard.cmake new file mode 100644 index 00000000000..3d17a289689 --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/mpconfigboard.cmake @@ -0,0 +1,9 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/SIL_MANT1S/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/SIL_MANT1S/mpconfigboard.h b/ports/esp32/boards/SIL_MANT1S/mpconfigboard.h new file mode 100644 index 00000000000..342636c8a1f --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/mpconfigboard.h @@ -0,0 +1,6 @@ +#define MICROPY_HW_BOARD_NAME "Silicognition ManT1S" +#define MICROPY_HW_MCU_NAME "ESP32-PICO-V3-02" +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mant1s" + +#define MICROPY_HW_I2C0_SCL (32) +#define MICROPY_HW_I2C0_SDA (33) diff --git a/ports/esp32/boards/SIL_MANT1S/partitions-8MiB-ota.csv b/ports/esp32/boards/SIL_MANT1S/partitions-8MiB-ota.csv new file mode 100644 index 00000000000..6229f4f75a9 --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/partitions-8MiB-ota.csv @@ -0,0 +1,9 @@ +# Partition table for MicroPython with OTA support using 8MB flash +# Notes: the offset of the partition table itself is set in +# $IDF_PATH/components/partition_table/Kconfig.projbuild. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +ota_0, app, ota_0, 0x10000, 0x200000, +ota_1, app, ota_1, 0x210000, 0x200000, diff --git a/ports/esp32/boards/SIL_MANT1S/pins.csv b/ports/esp32/boards/SIL_MANT1S/pins.csv new file mode 100644 index 00000000000..a3f03778106 --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/pins.csv @@ -0,0 +1,3 @@ +I2C_SCL,GPIO32 +I2C_SDA,GPIO33 + diff --git a/ports/esp32/boards/SIL_MANT1S/sdkconfig.board b/ports/esp32/boards/SIL_MANT1S/sdkconfig.board new file mode 100644 index 00000000000..53a605e2c1c --- /dev/null +++ b/ports/esp32/boards/SIL_MANT1S/sdkconfig.board @@ -0,0 +1,20 @@ +# 8 MB flash + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" + +# Fast flash + +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + +# Partition table + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/SIL_MANT1S/partitions-8MiB-ota.csv" + +# Network name + +CONFIG_LWIP_LOCAL_HOSTNAME="ManT1S" From 2a3b9b0b4bed6ea51f1053e224278e9dbb266c35 Mon Sep 17 00:00:00 2001 From: Elvis Pfutzenreuter Date: Fri, 22 Nov 2024 20:38:58 -0300 Subject: [PATCH 1463/2098] esp32/esp32_rmt: Update RMT module to use the new RMT API. The current `esp32.RMT` class uses a legacy API from ESP-IDF 4.x. The ESP-IDF 5.x offers a new API, which is overall better, and easier to implement the RX side in the future. This commit updates the module and the documentation, preserving the current MicroPython RMT API as much as possible. The bitstream RMT implementation was updated as well, since ESP-IDF does not allow firmware to reference legacy and new APIs at the same time (it resets right after boot with an error message, even if neither module is imported). The documentation is updated accordingly. Signed-off-by: Elvis Pfutzenreuter --- docs/esp32/quickref.rst | 9 +- docs/library/esp32.rst | 108 ++++++--- ports/esp32/boards/sdkconfig.base | 1 - ports/esp32/esp32_common.cmake | 8 + ports/esp32/esp32_rmt.c | 368 +++++++++++++++++++----------- ports/esp32/machine_bitstream.c | 141 ++++++------ ports/esp32/modesp32.h | 6 +- 7 files changed, 396 insertions(+), 245 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 25e52ec32db..0cf7d81a7ea 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -837,9 +837,9 @@ The RMT is ESP32-specific and allows generation of accurate digital pulses with import esp32 from machine import Pin - r = esp32.RMT(0, pin=Pin(18), clock_div=8) - r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) - # The channel resolution is 100ns (1/(source_freq/clock_div)). + r = esp32.RMT(pin=Pin(18), resolution_hz=10000000) + r # RMT(pin=18, source_freq=80000000, resolution_hz=10000000) + # The channel resolution is based on resolution_hz, i.e. 100ns for 10000000 r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns The ESP32-C2 family does not include any RMT peripheral, so this class is @@ -903,8 +903,7 @@ The APA106 driver extends NeoPixel, but internally uses a different colour order ``NeoPixel`` object. For low-level driving of a NeoPixel see `machine.bitstream`. -This low-level driver uses an RMT channel by default. To configure this see -`RMT.bitstream_channel`. +This low-level driver uses an RMT channel by default. APA102 (DotStar) uses a different driver as it has an additional clock pin. diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 84f33f46e72..0f063774113 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -362,29 +362,24 @@ used to transmit or receive many other types of digital signals:: import esp32 from machine import Pin - r = esp32.RMT(0, pin=Pin(18), clock_div=8) - r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8, idle_level=0) + r = esp32.RMT(pin=Pin(18), resolution_hz=10000000) + r # RMT(pin=18, source_freq=80000000, resolution_hz=10000000, idle_level=0) # To apply a carrier frequency to the high output - r = esp32.RMT(0, pin=Pin(18), clock_div=8, tx_carrier=(38000, 50, 1)) + r = esp32.RMT(pin=Pin(18), resolution_hz=10000000, tx_carrier=(38000, 50, 1)) - # The channel resolution is 100ns (1/(source_freq/clock_div)). + # The channel resolution is 100ns (1/resolution_hz) r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns The input to the RMT module is an 80MHz clock (in the future it may be able to -configure the input clock but, for now, it's fixed). ``clock_div`` *divides* -the clock input which determines the resolution of the RMT channel. The -numbers specified in ``write_pulses`` are multiplied by the resolution to +configure the input clock but, for now, it's fixed). ``resolution_hz`` determines +the resolution of the RMT channel. The numbers specified in ``write_pulses`` are +multiplied by the resolution to define the pulses. -``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by -multiplying the resolution by a 15-bit (1-``PULSE_MAX``) number. There are eight -channels (0-7) and each can have a different clock divider. - -So, in the example above, the 80MHz clock is divided by 8. Thus the -resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles -with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns, -100ns, 4000ns]. +So, in the example above, the resolution is resolution is (1/10Mhz) = 100ns. +Since the ``start`` level is 0 and toggles with each number, the bitstream is +``0101`` with durations of [100ns, 2000ns, 100ns, 4000ns]. For more details see Espressif's `ESP-IDF RMT documentation. `_. @@ -395,13 +390,24 @@ For more details see Espressif's `ESP-IDF RMT documentation. *beta feature* and the interface may change in the future. -.. class:: RMT(channel, *, pin=None, clock_div=8, idle_level=False, tx_carrier=None) +.. class:: RMT(channel, *, pin=None, resolution_hz=10000000, clock_div=None, idle_level=False, num_symbols=48|64, tx_carrier=None) This class provides access to one of the eight RMT channels. *channel* is - required and identifies which RMT channel (0-7) will be configured. *pin*, - also required, configures which Pin is bound to the RMT channel. *clock_div* - is an 8-bit clock divider that divides the source clock (80MHz) to the RMT - channel allowing the resolution to be specified. *idle_level* specifies + optional and a dummy parameter for backward compatibility. *pin* is required + and configures which Pin is bound to the RMT channel. + *resolution_hz* defines the resolution/unit of the samples. + For example, 1,000,000 means the unit is microsecond. The pulse widths can + assume values up to *RMT.PULSE_MAX*, so the resolution should be selected + accordingly to the signal to be transmitted. + *clock_div* (deprecated) is equivalent to *resolution_hz*, but expressed as + a clock divider that divides the source clock (80MHz) to the RMT + channel allowing the resolution to be specified. Either *clock_div* and + *resolution_hz* may be supplied, but not both. + *num_symbols* specifies the + RMT buffer allocated for this channel (minimum 48 or 64, depending on chip), from a small pool of + symbols (192 to 512, depending on chip) that are shared by all channels. This buffer does not limit the + size of the pulse train that you can send, but bigger buffers reduce the + CPU load and the potential of glitches/imprecise pulse lengths. *idle_level* specifies what level the output will be when no transmission is in progress and can be any value that converts to a boolean, with ``True`` representing high voltage and ``False`` representing low. @@ -419,21 +425,52 @@ For more details see Espressif's `ESP-IDF RMT documentation. .. method:: RMT.clock_div() Return the clock divider. Note that the channel resolution is - ``1 / (source_freq / clock_div)``. + ``1 / (source_freq / clock_div)``. (Method deprecated. The value may + not be faithful if resolution was supplied as *resolution_hz*.) .. method:: RMT.wait_done(*, timeout=0) Returns ``True`` if the channel is idle or ``False`` if a sequence of pulses started with `RMT.write_pulses` is being transmitted. If the *timeout* keyword argument is given then block for up to this many - milliseconds for transmission to complete. + milliseconds for transmission to complete. Timeout of -1 blocks until + transmission is complete (and blocks forever if loop is enabled). .. method:: RMT.loop(enable_loop) Configure looping on the channel. *enable_loop* is bool, set to ``True`` to enable looping on the *next* call to `RMT.write_pulses`. If called with ``False`` while a looping sequence is currently being transmitted then the - current loop iteration will be completed and then transmission will stop. + transmission will stop. (Method deprecated by `RMT.loop_count`.) + +.. method:: RMT.loop_count(n) + + Configure looping on the channel. *n* is int. Affects the *next* call to + `RMT.write_pulses`. Set to ``0`` to disable looping, ``-1`` to enable + infinite looping, or a positive number to loop for a given number of times. + If *n* is changed, the current transmission is stopped. + + Note: looping for a finite number of times is not supported by all flavors + of ESP32. + +.. method:: RMT.active([boolean]) + + If called without parameters, returns *True* if there is an ongoing transmission. + + If called with parameter *False*, stops the ongoing transmission. + This is useful to stop an infinite transmission loop. + The current loop is finished and transmission stops. + The object is not invalidated, and the RMT channel is again enabled when a new + transmission is started. + + Calling with parameter *True* does not restart transmission. A new transmission + should always be initiated by *write_pulses()*. + +.. method:: RMT.deinit() + + Release all RMT resources and invalidate the object. All subsequent method + calls will raise OSError. Useful to free RMT resources without having to wait + for the object to be garbage-collected. .. method:: RMT.write_pulses(duration, data=True) @@ -463,17 +500,28 @@ For more details see Espressif's `ESP-IDF RMT documentation. new sequence of pulses. Looping sequences longer than 126 pulses is not supported by the hardware. +.. staticmethod:: RMT.bitstream_rmt([value]) + + Configure RMT usage in the `machine.bitstream` implementation. + + If *value* is ``True``, bitstream tries to use RMT if possible. If *value* + is ``False``, bitstream sticks to the bit-banging implementation. + + If no parameter is supplied, it returns the current state. The default state + is ``True``. + .. staticmethod:: RMT.bitstream_channel([value]) - Select which RMT channel is used by the `machine.bitstream` implementation. - *value* can be ``None`` or a valid RMT channel number. The default RMT - channel is the highest numbered one. + *This function is deprecated and will be replaced by `RMT.bitstream_rmt()`.* + + Passing in no argument will return ``1`` if RMT was enabled for the `machine.bitstream` + feature, and ``None`` otherwise. - Passing in ``None`` disables the use of RMT and instead selects a bit-banging - implementation for `machine.bitstream`. + Passing any non-negative integer argument is equivalent to calling ``RMT.bitstream_rmt(True)``. - Passing in no argument will not change the channel. This function returns - the current channel number. + .. note:: In previous versions of MicroPython it was necessary to use this function to assign + a specific RMT channel number for the bitstream, but the channel number is now assigned + dynamically. Constants --------- diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index e1b30b8e520..6c8368ac488 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -124,7 +124,6 @@ CONFIG_UART_ISR_IN_IRAM=y # IDF 5 deprecated CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN=y -CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN=y CONFIG_ETH_USE_SPI_ETHERNET=y diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 79a60adac9f..6922ac5fec0 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -262,6 +262,14 @@ target_compile_options(${MICROPY_TARGET} PUBLIC target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble ) +if (IDF_VERSION VERSION_LESS "5.3") +# Additional include directories needed for private RMT header. +# IDF 5.x versions before 5.3.1 + message(STATUS "Using private rmt headers for ${IDF_VERSION}") + target_include_directories(${MICROPY_TARGET} PRIVATE + ${IDF_PATH}/components/driver/rmt + ) +endif() # Add additional extmod and usermod components. if (MICROPY_PY_BTREE) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index f3bfbecdd11..85a98c2910b 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 "Matt Trentini" + * Copyright (c) 2024 "Elvis Pfützenreuter" * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,13 +27,16 @@ #include "py/mphal.h" #include "py/runtime.h" +#include "py/stream.h" #include "modmachine.h" #include "modesp32.h" #include "esp_task.h" #if SOC_RMT_SUPPORTED -#include "driver/rmt.h" +#include "esp_clk_tree.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_encoder.h" // This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: // @@ -46,105 +50,101 @@ // Originally designed to generate infrared remote control signals, the module is very // flexible and quite easy-to-use. // -// This current MicroPython implementation lacks some major features, notably receive pulses -// and carrier output. - -// Last available RMT channel that can transmit. -#define RMT_LAST_TX_CHANNEL (SOC_RMT_TX_CANDIDATES_PER_GROUP - 1) +// This code exposes the RMT TX feature. // Forward declaration extern const mp_obj_type_t esp32_rmt_type; typedef struct _esp32_rmt_obj_t { mp_obj_base_t base; - uint8_t channel_id; + rmt_channel_handle_t channel; + bool enabled; gpio_num_t pin; - uint8_t clock_div; - mp_uint_t num_items; - rmt_item32_t *items; - bool loop_en; + uint32_t clock_freq; + int resolution_hz; + mp_uint_t cap_items; + rmt_symbol_word_t *items; + int loop_count; + int tx_ongoing; + + rmt_encoder_handle_t encoder; + mp_uint_t idle_level; } esp32_rmt_obj_t; -// Current channel used for machine.bitstream, in the machine_bitstream_high_low_rmt -// implementation. A value of -1 means do not use RMT. -int8_t esp32_rmt_bitstream_channel_id = RMT_LAST_TX_CHANNEL; - -#if MP_TASK_COREID == 0 - -typedef struct _rmt_install_state_t { - SemaphoreHandle_t handle; - uint8_t channel_id; - esp_err_t ret; -} rmt_install_state_t; - -static void rmt_install_task(void *pvParameter) { - rmt_install_state_t *state = pvParameter; - state->ret = rmt_driver_install(state->channel_id, 0, 0); - xSemaphoreGive(state->handle); - vTaskDelete(NULL); - for (;;) { - } -} - -// Call rmt_driver_install on core 1. This ensures that the RMT interrupt handler is -// serviced on core 1, so that WiFi (if active) does not interrupt it and cause glitches. -esp_err_t rmt_driver_install_core1(uint8_t channel_id) { - TaskHandle_t th; - rmt_install_state_t state; - state.handle = xSemaphoreCreateBinary(); - state.channel_id = channel_id; - xTaskCreatePinnedToCore(rmt_install_task, "rmt_install_task", 2048 / sizeof(StackType_t), &state, ESP_TASK_PRIO_MIN + 1, &th, 1); - xSemaphoreTake(state.handle, portMAX_DELAY); - vSemaphoreDelete(state.handle); - return state.ret; -} - -#else +// Decide RMT usage in the machine_bitstream_high_low_rmt implementation. +bool esp32_rmt_bitstream_enabled = true; -// MicroPython runs on core 1, so we can call the RMT installer directly and its -// interrupt handler will also run on core 1. -esp_err_t rmt_driver_install_core1(uint8_t channel_id) { - return rmt_driver_install(channel_id, 0, 0); +static bool IRAM_ATTR esp32_rmt_tx_trans_done(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_ctx) { + esp32_rmt_obj_t *self = user_ctx; + self->tx_ongoing -= 1; + return false; } -#endif // MP_TASK_COREID==0 - static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution + { MP_QSTR_resolution_hz, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_idle_level, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, // low voltage { MP_QSTR_tx_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // no carrier + { MP_QSTR_num_symbols, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SOC_RMT_MEM_WORDS_PER_CHANNEL} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_uint_t channel_id = args[0].u_int; + // RMT channel is an opaque struct in current RMT API and channel_id is a dummy parameter + // mp_uint_t channel_id = args[0].u_int; gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); - mp_uint_t clock_div = args[2].u_int; - mp_uint_t idle_level = args[3].u_bool; - mp_obj_t tx_carrier_obj = args[4].u_obj; - if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) { - mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream")); + uint32_t clock_freq; + check_esp_err(esp_clk_tree_src_get_freq_hz(RMT_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clock_freq)); + + mp_uint_t resolution_hz; + if (args[2].u_obj != mp_const_none && args[3].u_obj != mp_const_none) { + mp_raise_ValueError(MP_ERROR_TEXT("resolution_hz and clock_div are mutually exclusive")); + } else if (args[2].u_obj == mp_const_none && args[3].u_obj == mp_const_none) { + // default value + resolution_hz = 10000000; + } else if (args[2].u_obj != mp_const_none) { + resolution_hz = mp_obj_get_int(args[2].u_obj); + if (resolution_hz <= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("resolution_hz must be positive")); + } + } else if (args[3].u_obj != mp_const_none) { + mp_uint_t clock_div = mp_obj_get_int(args[3].u_obj); + if (clock_div < 1 || clock_div > 255) { + mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); + } + resolution_hz = clock_freq / clock_div; } - if (clock_div < 1 || clock_div > 255) { - mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); + mp_uint_t idle_level = args[4].u_bool; + mp_obj_t tx_carrier_obj = args[5].u_obj; + mp_uint_t num_symbols = args[6].u_int; + + if (num_symbols < SOC_RMT_MEM_WORDS_PER_CHANNEL || ((num_symbols % 2) == 1)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("num_symbols must be even and at least %d"), SOC_RMT_MEM_WORDS_PER_CHANNEL); } esp32_rmt_obj_t *self = mp_obj_malloc_with_finaliser(esp32_rmt_obj_t, &esp32_rmt_type); - self->channel_id = channel_id; + self->channel = NULL; self->pin = pin_id; - self->clock_div = clock_div; - self->loop_en = false; + self->clock_freq = clock_freq; + self->resolution_hz = resolution_hz; + self->loop_count = 0; + self->tx_ongoing = 0; + self->idle_level = idle_level; + self->enabled = false; + + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = self->pin, + .mem_block_symbols = num_symbols, + .resolution_hz = resolution_hz, + .trans_queue_depth = 4, + }; - rmt_config_t config = {0}; - config.rmt_mode = RMT_MODE_TX; - config.channel = (rmt_channel_t)self->channel_id; - config.gpio_num = self->pin; - config.mem_block_num = 1; - config.tx_config.loop_en = 0; + check_esp_err(rmt_new_tx_channel(&tx_chan_config, &self->channel)); if (tx_carrier_obj != mp_const_none) { mp_obj_t *tx_carrier_details = NULL; @@ -160,21 +160,21 @@ static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier duty must be 0..100")); } - config.tx_config.carrier_en = 1; - config.tx_config.carrier_freq_hz = frequency; - config.tx_config.carrier_duty_percent = duty; - config.tx_config.carrier_level = level; - } else { - config.tx_config.carrier_en = 0; + rmt_carrier_config_t tx_carrier_cfg = { + .duty_cycle = ((float)duty) / 100.0, + .frequency_hz = frequency, + .flags.polarity_active_low = !level, + }; + check_esp_err(rmt_apply_carrier(self->channel, &tx_carrier_cfg)); } - config.tx_config.idle_output_en = 1; - config.tx_config.idle_level = idle_level; - - config.clk_div = self->clock_div; + rmt_copy_encoder_config_t copy_encoder_config = {}; + check_esp_err(rmt_new_copy_encoder(©_encoder_config, &self->encoder)); - check_esp_err(rmt_config(&config)); - check_esp_err(rmt_driver_install_core1(config.channel)); + rmt_tx_event_callbacks_t callbacks = { + .on_trans_done = esp32_rmt_tx_trans_done, + }; + check_esp_err(rmt_tx_register_event_callbacks(self->channel, &callbacks, self)); return MP_OBJ_FROM_PTR(self); } @@ -182,33 +182,72 @@ static mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz static void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->pin != -1) { - bool idle_output_en; - rmt_idle_level_t idle_level; - check_esp_err(rmt_get_idle_level(self->channel_id, &idle_output_en, &idle_level)); - mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u, idle_level=%u)", - self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div, idle_level); + mp_printf(print, "RMT(pin=%u, source_freq=%u, resolution_hz=%u, idle_level=%u)", + self->pin, self->clock_freq, self->resolution_hz, self->idle_level); } else { mp_printf(print, "RMT()"); } } +static void esp32_rmt_deactivate(esp32_rmt_obj_t *self) { + if (self->enabled) { + // FIXME: panics in ESP32 if called while TX is ongoing and TX sequence is long (>300ms) + // Does not panic in ESP32-S3, ESP32-C3 and ESP32-C6. + // Tested with ESP-IDF up to 5.5 + // ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17692 + // + // Cause is Interrupt WDT to trigger because ESP-IDF rmt_disable() disables + // interrupts and spinlocks until the ongoing TX sequence is finished. + // + // Workaround is never try to stop RMT sequences longer than 300ms (which are unusual + // anyway). Or apply the patch mentioned at the GitHub issue to ESP-IDF. + rmt_disable(self->channel); + self->enabled = false; + } +} + +static mp_obj_t esp32_rmt_active(size_t n_args, const mp_obj_t *args) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return mp_obj_new_bool(self->enabled && self->tx_ongoing > 0); + } else if (mp_obj_is_true(args[1])) { + mp_raise_ValueError(MP_ERROR_TEXT("activate by calling write_pulses()")); + } + + esp32_rmt_deactivate(self); + + return mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_active_obj, 1, 2, esp32_rmt_active); + static mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { - // fixme: check for valid channel. Return exception if error occurs. esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pin != -1) { // Check if channel has already been deinitialised. - rmt_driver_uninstall(self->channel_id); + esp32_rmt_deactivate(self); + rmt_tx_event_callbacks_t callbacks = { + .on_trans_done = NULL, + }; + rmt_tx_register_event_callbacks(self->channel, &callbacks, self); + rmt_del_encoder(self->encoder); + rmt_del_channel(self->channel); self->pin = -1; // -1 to indicate RMT is unused + self->tx_ongoing = 0; m_free(self->items); } + return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); // Return the source frequency. -// Currently only the APB clock (80MHz) can be used but it is possible other +// Currently only the default clock (80MHz) can be used but it is possible other // clock sources will added in the future. static mp_obj_t esp32_rmt_source_freq() { - return mp_obj_new_int(APB_CLK_FREQ); + uint32_t clock_freq; + check_esp_err(esp_clk_tree_src_get_freq_hz(RMT_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clock_freq)); + return mp_obj_new_int(clock_freq); } static MP_DEFINE_CONST_FUN_OBJ_0(esp32_rmt_source_freq_obj, esp32_rmt_source_freq); static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_source_obj, MP_ROM_PTR(&esp32_rmt_source_freq_obj)); @@ -216,7 +255,11 @@ static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_source_obj, MP_ROM_PTR(&esp32_ // Return the clock divider. static mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int(self->clock_div); + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized")); + } + + return mp_obj_new_int(self->clock_freq / self->resolution_hz); } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); @@ -233,29 +276,86 @@ static mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized")); + } else if (!self->enabled) { + return mp_const_true; + } else if (args[1].u_int == 0 && self->tx_ongoing > 0) { + // shortcut to avoid console spamming with timeout msgs by rmt_tx_wait_all_done() + return mp_const_false; + } - esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int / portTICK_PERIOD_MS); + esp_err_t err = rmt_tx_wait_all_done(self->channel, args[1].u_int); return err == ESP_OK ? mp_const_true : mp_const_false; } static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); +static mp_uint_t esp32_rmt_stream_ioctl( + mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + if (request != MP_STREAM_POLL) { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t ret = 0; + if ((arg & MP_STREAM_POLL_WR) && self->tx_ongoing == 0) { + ret |= MP_STREAM_POLL_WR; + } + return ret; +} + +static const mp_stream_p_t esp32_rmt_stream_p = { + .ioctl = esp32_rmt_stream_ioctl, +}; + +static void esp32_rmt_loop_in(esp32_rmt_obj_t *self, int new_loop_count) { + if (self->enabled && self->tx_ongoing > 0 && self->loop_count != 0 && new_loop_count == 0) { + // Break ongoing loop + esp32_rmt_deactivate(self); + } + self->loop_count = new_loop_count; +} + static mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - self->loop_en = mp_obj_get_int(loop); - if (!self->loop_en) { - bool loop_en; - check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); - if (loop_en) { - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); - check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); - } + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized")); } + + bool loop_en = mp_obj_get_int(loop); + esp32_rmt_loop_in(self, loop_en ? -1 : 0); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); +static mp_obj_t esp32_rmt_loop_count(mp_obj_t self_in, mp_obj_t loop) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized")); + } + + int loop_count = mp_obj_get_int(loop); + if (loop_count < -1) { + mp_raise_ValueError(MP_ERROR_TEXT("arg must be -1, 0 or positive")); + } + esp32_rmt_loop_in(self, loop_count); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_count_obj, esp32_rmt_loop_count); + static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->pin == -1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("already deinitialized")); + } + + if (self->enabled) { + rmt_tx_wait_all_done(self->channel, -1); + } else { + check_esp_err(rmt_enable(self->channel)); + self->enabled = true; + } + mp_obj_t duration_obj = args[1]; mp_obj_t data_obj = n_args > 2 ? args[2] : mp_const_true; @@ -290,14 +390,12 @@ static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { if (num_pulses == 0) { mp_raise_ValueError(MP_ERROR_TEXT("No pulses")); } - if (self->loop_en && num_pulses > 126) { - mp_raise_ValueError(MP_ERROR_TEXT("Too many pulses for loop")); - } mp_uint_t num_items = (num_pulses / 2) + (num_pulses % 2); - if (num_items > self->num_items) { - self->items = (rmt_item32_t *)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); - self->num_items = num_items; + + if (num_items > self->cap_items) { + self->items = (rmt_symbol_word_t *)m_realloc(self->items, num_items * sizeof(rmt_symbol_word_t *)); + self->cap_items = num_items; } for (mp_uint_t item_index = 0, pulse_index = 0; item_index < num_items; item_index++) { @@ -314,63 +412,62 @@ static mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { } } - if (self->loop_en) { - bool loop_en; - check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); - if (loop_en) { - check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); - } - check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY)); - } - - #if !CONFIG_IDF_TARGET_ESP32S3 - check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false)); - #endif - - if (self->loop_en) { - check_esp_err(rmt_set_tx_intr_en(self->channel_id, false)); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true)); - } + rmt_transmit_config_t tx_config = { + .loop_count = self->loop_count, + .flags.eot_level = self->idle_level ? 1 : 0, + }; - #if CONFIG_IDF_TARGET_ESP32S3 - check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false)); - #endif + rmt_encoder_reset(self->encoder); + check_esp_err(rmt_transmit(self->channel, self->encoder, self->items, num_items * sizeof(rmt_symbol_word_t), &tx_config)); + self->tx_ongoing += 1; return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_write_pulses_obj, 2, 3, esp32_rmt_write_pulses); +static mp_obj_t esp32_rmt_bitstream_rmt(size_t n_args, const mp_obj_t *args) { + if (n_args > 0) { + esp32_rmt_bitstream_enabled = mp_obj_is_true(args[0]); + } + return esp32_rmt_bitstream_enabled ? mp_const_true : mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_rmt_fun_obj, 0, 1, esp32_rmt_bitstream_rmt); +static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_rmt_obj, MP_ROM_PTR(&esp32_rmt_bitstream_rmt_fun_obj)); + static mp_obj_t esp32_rmt_bitstream_channel(size_t n_args, const mp_obj_t *args) { if (n_args > 0) { if (args[0] == mp_const_none) { - esp32_rmt_bitstream_channel_id = -1; + esp32_rmt_bitstream_enabled = false; } else { mp_int_t channel_id = mp_obj_get_int(args[0]); - if (channel_id < 0 || channel_id > RMT_LAST_TX_CHANNEL) { + if (channel_id < 0) { mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); } - esp32_rmt_bitstream_channel_id = channel_id; + esp32_rmt_bitstream_enabled = true; } } - if (esp32_rmt_bitstream_channel_id < 0) { + if (!esp32_rmt_bitstream_enabled) { return mp_const_none; } else { - return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id); + return MP_OBJ_NEW_SMALL_INT(1); } } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_channel_fun_obj, 0, 1, esp32_rmt_bitstream_channel); static MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_channel_obj, MP_ROM_PTR(&esp32_rmt_bitstream_channel_fun_obj)); + static const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&esp32_rmt_active_obj) }, { MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) }, { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, + { MP_ROM_QSTR(MP_QSTR_loop_count), MP_ROM_PTR(&esp32_rmt_loop_count_obj) }, { MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) }, // Static methods + { MP_ROM_QSTR(MP_QSTR_bitstream_rmt), MP_ROM_PTR(&esp32_rmt_bitstream_rmt_obj) }, { MP_ROM_QSTR(MP_QSTR_bitstream_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) }, // Class methods @@ -387,7 +484,8 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_TYPE_FLAG_NONE, make_new, esp32_rmt_make_new, print, esp32_rmt_print, - locals_dict, &esp32_rmt_locals_dict + locals_dict, &esp32_rmt_locals_dict, + protocol, &esp32_rmt_stream_p ); #endif // SOC_RMT_SUPPORTED diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index ed7fcc407df..60addcc15b6 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -91,96 +91,95 @@ static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, u } #if SOC_RMT_SUPPORTED + /******************************************************************************/ // RMT implementation -#include "driver/rmt.h" - -// Logical 0 and 1 values (encoded as a rmt_item32_t). -// The duration fields will be set later. -static rmt_item32_t bitstream_high_low_0 = {{{ 0, 1, 0, 0 }}}; -static rmt_item32_t bitstream_high_low_1 = {{{ 0, 1, 0, 0 }}}; - -// See https://github.com/espressif/esp-idf/blob/master/examples/common_components/led_strip/led_strip_rmt_ws2812.c -// This is called automatically by the IDF during rmt_write_sample in order to -// convert the byte stream to rmt_item32_t's. -static void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { - if (src == NULL || dest == NULL) { - *translated_size = 0; - *item_num = 0; - return; - } - - size_t size = 0; - size_t num = 0; - uint8_t *psrc = (uint8_t *)src; - rmt_item32_t *pdest = dest; - while (size < src_size && num < wanted_num) { - for (int i = 0; i < 8; i++) { - // MSB first - if (*psrc & (1 << (7 - i))) { - pdest->val = bitstream_high_low_1.val; - } else { - pdest->val = bitstream_high_low_0.val; - } - num++; - pdest++; - } - size++; - psrc++; - } - - *translated_size = size; - *item_num = num; -} - -// Use the reserved RMT channel to stream high/low data on the specified pin. -static void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len, uint8_t channel_id) { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel_id); +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) +#include "rmt_private.h" +#endif +#include "driver/rmt_tx.h" +#include "driver/rmt_encoder.h" +static bool machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { // Use 40MHz clock (although 2MHz would probably be sufficient). - config.clk_div = 2; - - // Install the driver on this channel & pin. - check_esp_err(rmt_config(&config)); - check_esp_err(rmt_driver_install_core1(config.channel)); + uint32_t clock_div = 2; + rmt_channel_handle_t channel = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = pin, + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .resolution_hz = APB_CLK_FREQ / clock_div, + .trans_queue_depth = 1, + }; + if (rmt_new_tx_channel(&tx_chan_config, &channel) != ESP_OK) { + return false; + } + check_esp_err(rmt_enable(channel)); // Get the tick rate in kHz (this will likely be 40000). - uint32_t counter_clk_khz = 0; - check_esp_err(rmt_get_counter_clock(config.channel, &counter_clk_khz)); - + uint32_t counter_clk_khz = APB_CLK_FREQ / clock_div; counter_clk_khz /= 1000; // Convert nanoseconds to pulse duration. - bitstream_high_low_0.duration0 = (counter_clk_khz * timing_ns[0]) / 1e6; - bitstream_high_low_0.duration1 = (counter_clk_khz * timing_ns[1]) / 1e6; - bitstream_high_low_1.duration0 = (counter_clk_khz * timing_ns[2]) / 1e6; - bitstream_high_low_1.duration1 = (counter_clk_khz * timing_ns[3]) / 1e6; - - // Install the bits->highlow translator. - rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter); - - // Stream the byte data using the translator. - check_esp_err(rmt_write_sample(config.channel, buf, len, true)); - - // Wait 50% longer than we expect (if every bit takes the maximum time). - uint32_t timeout_ms = (3 * len / 2) * (1 + (8 * MAX(timing_ns[0] + timing_ns[1], timing_ns[2] + timing_ns[3])) / 1000); - check_esp_err(rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(timeout_ms))); - - // Uninstall the driver. - check_esp_err(rmt_driver_uninstall(config.channel)); + // Example: 500ns = 40000 * 500 / 1e6 = 20 ticks + // 20 ticks / 40MHz = 500e-9 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = (counter_clk_khz * timing_ns[0]) / 1e6, + .level1 = 0, + .duration1 = (counter_clk_khz * timing_ns[1]) / 1e6, + }, + .bit1 = { + .level0 = 1, + .duration0 = (counter_clk_khz * timing_ns[2]) / 1e6, + .level1 = 0, + .duration1 = (counter_clk_khz * timing_ns[3]) / 1e6, + }, + .flags.msb_first = 1 + }; + + // Install the bits->highlow encoder. + rmt_encoder_handle_t encoder; + check_esp_err(rmt_new_bytes_encoder(&bytes_encoder_config, &encoder)); + + rmt_transmit_config_t tx_config = { + .loop_count = 0, + .flags.eot_level = 0, + }; + + // Stream the byte data using the encoder. + rmt_encoder_reset(encoder); + check_esp_err(rmt_transmit(channel, encoder, buf, len, &tx_config)); + + // Wait until completion. + rmt_tx_wait_all_done(channel, -1); + + // Disable and release channel. + check_esp_err(rmt_del_encoder(encoder)); + rmt_disable(channel); + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) + channel->del(channel); + #else + rmt_del_channel(channel); + #endif // Cancel RMT output to GPIO pin. esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); + + return true; } -#endif + +#endif // SOC_RMT_SUPPORTED + /******************************************************************************/ // Interface to machine.bitstream void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { #if SOC_RMT_SUPPORTED - if (esp32_rmt_bitstream_channel_id >= 0) { - machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); + if (esp32_rmt_bitstream_enabled && machine_bitstream_high_low_rmt(pin, timing_ns, buf, len)) { + // Use of RMT was successful. return; } #endif diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 81ab94dc633..60c386565b8 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -1,6 +1,8 @@ #ifndef MICROPY_INCLUDED_ESP32_MODESP32_H #define MICROPY_INCLUDED_ESP32_MODESP32_H +#include "driver/rmt_tx.h" + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #define RTC_VALID_EXT_PINS \ @@ -59,7 +61,7 @@ #define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) -extern int8_t esp32_rmt_bitstream_channel_id; +extern bool esp32_rmt_bitstream_enabled; extern const mp_obj_type_t esp32_nvs_type; extern const mp_obj_type_t esp32_partition_type; @@ -72,6 +74,4 @@ extern const mp_obj_type_t esp32_pcnt_type; void esp32_pcnt_deinit_all(void); #endif -esp_err_t rmt_driver_install_core1(uint8_t channel_id); - #endif // MICROPY_INCLUDED_ESP32_MODESP32_H From 713f40d20446240fcc340ffc5180802b0a22452a Mon Sep 17 00:00:00 2001 From: yuan_mo <2286087148@qq.com> Date: Thu, 14 Aug 2025 09:15:59 +0800 Subject: [PATCH 1464/2098] esp32: Add support for ESP32-C5 SoCs. Signed-off-by: yuan_mo <2286087148@qq.com> Signed-off-by: Damien George --- .../esp32/lockfiles/dependencies.lock.esp32c5 | 21 +++++++++++++++ ports/esp32/machine_adc.c | 7 +++++ ports/esp32/machine_i2c.c | 11 +++++++- ports/esp32/machine_pin.c | 7 ++++- ports/esp32/machine_pin.h | 26 +++++++++++++++++++ ports/esp32/mpconfigport.h | 2 ++ 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32c5 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c5 b/ports/esp32/lockfiles/dependencies.lock.esp32c5 new file mode 100644 index 00000000000..204f64f68ca --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c5 @@ -0,0 +1,21 @@ +dependencies: + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + idf: + source: + type: idf + version: 5.5.1 +direct_dependencies: +- espressif/mdns +- idf +manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +target: esp32c5 +version: 2.0.0 diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index c5575d45ec7..432df3d3a20 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -100,6 +100,13 @@ static const machine_adc_obj_t madc_obj[] = { {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_5}, + #elif CONFIG_IDF_TARGET_ESP32C5 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_1}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_3}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_4}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_5}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_6}, #elif CONFIG_IDF_TARGET_ESP32C6 {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 74679d01da2..9fb89660f74 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -35,6 +35,7 @@ #include "driver/i2c_master.h" #else #include "driver/i2c.h" +#include "esp_clk_tree.h" #include "hal/i2c_ll.h" #endif @@ -224,6 +225,8 @@ int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_ #if SOC_I2C_SUPPORT_XTAL #if CONFIG_XTAL_FREQ > 0 #define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) +#elif CONFIG_XTAL_FREQ == 0 && CONFIG_IDF_TARGET_ESP32C5 +// The crystal is auto-detected, so the I2C sclk frequency will be computed at runtime. #else #error "I2C uses XTAL but no configured freq" #endif // CONFIG_XTAL_FREQ @@ -257,7 +260,13 @@ static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { .master.clk_speed = self->freq, }; i2c_param_config(self->port, &conf); - int timeout = i2c_ll_calculate_timeout_us_to_reg_val(I2C_SCLK_FREQ, self->timeout_us); + #if CONFIG_IDF_TARGET_ESP32C5 + uint32_t i2c_sclk_freq; + check_esp_err(esp_clk_tree_src_get_freq_hz(I2C_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &i2c_sclk_freq)); + #else + uint32_t i2c_sclk_freq = I2C_SCLK_FREQ; + #endif + int timeout = i2c_ll_calculate_timeout_us_to_reg_val(i2c_sclk_freq, self->timeout_us); i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); } diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 9999223b59d..efe6733194c 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -152,7 +152,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ // reset the pin to digital if this is a mode-setting init (grab it back from ADC) if (args[ARG_mode].u_obj != mp_const_none) { if (rtc_gpio_is_valid_gpio(index)) { - #if !(CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6) + #if !(CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6) rtc_gpio_deinit(index); #endif } @@ -163,6 +163,11 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); } #endif + #if CONFIG_IDF_TARGET_ESP32C5 && !MICROPY_HW_ESP_USB_SERIAL_JTAG + if (index == 13 || index == 14) { + CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); + } + #endif #if CONFIG_IDF_TARGET_ESP32C6 && !MICROPY_HW_ESP_USB_SERIAL_JTAG if (index == 12 || index == 13) { CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); diff --git a/ports/esp32/machine_pin.h b/ports/esp32/machine_pin.h index 6fe9ef0e778..9e247a7367f 100644 --- a/ports/esp32/machine_pin.h +++ b/ports/esp32/machine_pin.h @@ -111,6 +111,32 @@ #define MICROPY_HW_ENABLE_GPIO20 (1) #define MICROPY_HW_ENABLE_GPIO21 (1) +#elif CONFIG_IDF_TARGET_ESP32C5 + +#define MICROPY_HW_ENABLE_GPIO0 (1) +#define MICROPY_HW_ENABLE_GPIO1 (1) +#define MICROPY_HW_ENABLE_GPIO2 (1) +#define MICROPY_HW_ENABLE_GPIO3 (1) +#define MICROPY_HW_ENABLE_GPIO4 (1) +#define MICROPY_HW_ENABLE_GPIO5 (1) +#define MICROPY_HW_ENABLE_GPIO6 (1) +#define MICROPY_HW_ENABLE_GPIO7 (1) +#define MICROPY_HW_ENABLE_GPIO8 (1) +#define MICROPY_HW_ENABLE_GPIO9 (1) +#define MICROPY_HW_ENABLE_GPIO10 (1) +#define MICROPY_HW_ENABLE_GPIO11 (1) +#define MICROPY_HW_ENABLE_GPIO12 (1) +#if !MICROPY_HW_ESP_USB_SERIAL_JTAG +#define MICROPY_HW_ENABLE_GPIO13 (1) +#define MICROPY_HW_ENABLE_GPIO14 (1) +#endif +#define MICROPY_HW_ENABLE_GPIO23 (1) +#define MICROPY_HW_ENABLE_GPIO24 (1) +#define MICROPY_HW_ENABLE_GPIO25 (1) +#define MICROPY_HW_ENABLE_GPIO26 (1) +#define MICROPY_HW_ENABLE_GPIO27 (1) +#define MICROPY_HW_ENABLE_GPIO28 (1) + #elif CONFIG_IDF_TARGET_ESP32C6 #define MICROPY_HW_ENABLE_GPIO0 (1) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 09d75cd1a6d..fafed3961cc 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -172,6 +172,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c2" #elif CONFIG_IDF_TARGET_ESP32C3 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c3" +#elif CONFIG_IDF_TARGET_ESP32C5 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c5" #elif CONFIG_IDF_TARGET_ESP32C6 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c6" #endif From 30c74fc3b49a6a77c9fa8634c3902a895bfefea2 Mon Sep 17 00:00:00 2001 From: yuan_mo <2286087148@qq.com> Date: Thu, 14 Aug 2025 09:15:59 +0800 Subject: [PATCH 1465/2098] esp32/boards: Add ESP32_GENERIC_C5 board definition. Signed-off-by: yuan_mo <2286087148@qq.com> --- .../esp32/boards/ESP32_GENERIC_C5/board.json | 22 +++++++++++++++++++ ports/esp32/boards/ESP32_GENERIC_C5/board.md | 2 ++ .../ESP32_GENERIC_C5/mpconfigboard.cmake | 10 +++++++++ .../boards/ESP32_GENERIC_C5/mpconfigboard.h | 7 ++++++ .../boards/ESP32_GENERIC_C5/sdkconfig.board | 2 ++ 5 files changed, 43 insertions(+) create mode 100644 ports/esp32/boards/ESP32_GENERIC_C5/board.json create mode 100644 ports/esp32/boards/ESP32_GENERIC_C5/board.md create mode 100644 ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.cmake create mode 100644 ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.h create mode 100644 ports/esp32/boards/ESP32_GENERIC_C5/sdkconfig.board diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/board.json b/ports/esp32/boards/ESP32_GENERIC_C5/board.json new file mode 100644 index 00000000000..371da3929c5 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C5/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x2000" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "WiFi" + ], + "images": [ + "esp32c5_devkitmini.jpg" + ], + "mcu": "esp32c5", + "product": "ESP32-C5", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/board.md b/ports/esp32/boards/ESP32_GENERIC_C5/board.md new file mode 100644 index 00000000000..82bac44b96f --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C5/board.md @@ -0,0 +1,2 @@ +The following files are firmware images that should work on most ESP32-C5-based +boards with at least 4MiB of flash and 40MHz/48MHz crystal frequency. diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.cmake new file mode 100644 index 00000000000..79aba7d474f --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.cmake @@ -0,0 +1,10 @@ +set(IDF_TARGET esp32c5) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.riscv + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/sdkconfig.free_ram + boards/ESP32_GENERIC_C5/sdkconfig.board +) diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.h new file mode 100644 index 00000000000..55246849775 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C5/mpconfigboard.h @@ -0,0 +1,7 @@ +// This configuration is for a generic ESP32C5 board with 4MiB (or more) of flash. + +#define MICROPY_HW_BOARD_NAME "ESP32C5 module" +#define MICROPY_HW_MCU_NAME "ESP32C5" + +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/sdkconfig.board b/ports/esp32/boards/ESP32_GENERIC_C5/sdkconfig.board new file mode 100644 index 00000000000..369330682f9 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_C5/sdkconfig.board @@ -0,0 +1,2 @@ +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y From 336d463eb287b8249451b23389517199e3e0fcc8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 15 Nov 2025 18:29:43 +1100 Subject: [PATCH 1466/2098] tools/ci.sh: Build ESP32_GENERIC_C5 as part of esp32 CI. Signed-off-by: Damien George --- .github/workflows/ports_esp32.yml | 2 +- tools/ci.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 8ee3f44d6a2..578bf33af5b 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -29,7 +29,7 @@ jobs: ci_func: # names are functions in ci.sh - esp32_build_cmod_spiram_s2 - esp32_build_s3_c3 - - esp32_build_c2_c6 + - esp32_build_c2_c5_c6 runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 diff --git a/tools/ci.sh b/tools/ci.sh index 42d231c4581..63eed15bea2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -237,10 +237,11 @@ function ci_esp32_build_s3_c3 { make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C3 } -function ci_esp32_build_c2_c6 { +function ci_esp32_build_c2_c5_c6 { ci_esp32_build_common make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C2 + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C5 make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C6 } From 4441fd655c514dd92363ad00aa2e832c9f103b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Mon, 18 Aug 2025 16:00:04 +0200 Subject: [PATCH 1467/2098] lib/littlefs: Update LittleFS to v2.11.2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- lib/littlefs/lfs2.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index abdec19d7cb..6c3c3ece76b 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -93,6 +93,7 @@ static int lfs2_bd_read(lfs2_t *lfs2, // bypass cache? diff = lfs2_aligndown(diff, lfs2->cfg->read_size); int err = lfs2->cfg->read(lfs2->cfg, block, off, data, diff); + LFS2_ASSERT(err <= 0); if (err) { return err; } @@ -739,6 +740,7 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, int err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, sizeof(ntag), dir->pair[0], off, &ntag, sizeof(ntag)); + LFS2_ASSERT(err <= 0); if (err) { return err; } @@ -767,6 +769,7 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, err = lfs2_bd_read(lfs2, NULL, &lfs2->rcache, diff, dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); + LFS2_ASSERT(err <= 0); if (err) { return err; } @@ -828,9 +831,6 @@ static int lfs2_dir_getread(lfs2_t *lfs2, const lfs2_mdir_t *dir, size -= diff; continue; } - - // rcache takes priority - diff = lfs2_min(diff, rcache->off-off); } // load to cache, first condition can no longer fail @@ -1282,6 +1282,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, if (err == LFS2_ERR_CORRUPT) { break; } + return err; } lfs2_fcrc_fromle32(&fcrc); @@ -2267,7 +2268,7 @@ static int lfs2_dir_relocatingcommit(lfs2_t *lfs2, lfs2_mdir_t *dir, } } - if (dir->erased) { + if (dir->erased && dir->count < 0xff) { // try to commit struct lfs2_commit commit = { .block = dir->pair[0], @@ -5225,7 +5226,9 @@ static int lfs2_fs_gc_(lfs2_t *lfs2) { } // try to populate the lookahead buffer, unless it's already full - if (lfs2->lookahead.size < 8*lfs2->cfg->lookahead_size) { + if (lfs2->lookahead.size < lfs2_min( + 8 * lfs2->cfg->lookahead_size, + lfs2->block_count)) { err = lfs2_alloc_scan(lfs2); if (err) { return err; From 8d05a8fc8a78dc6592e87ed5e87616bc01dce37c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 19 Nov 2025 10:15:08 +1100 Subject: [PATCH 1468/2098] shared/tinyusb/mp_usbd: Reorder the mp_usbd_init/deinit functions. Not a functional change, but makes it easier to see that mp_usbd_init() is available regardless of whether MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE is set. Also adds a no-op inline mp_usbd_deinit() implementation, which means both a port's calls to mp_usbd_init()/deinit() can both be guarded on the same MICROPY_HW_ENABLE_USBDEV. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- shared/tinyusb/mp_usbd.h | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 176b1ba507c..b4655553673 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -88,9 +88,22 @@ extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN]; void mp_usbd_task_callback(mp_sched_node_t *node); -#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE -void mp_usbd_deinit(void); +#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +static inline void mp_usbd_init(void) { + // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() + // which is called in the below helper function. + mp_usbd_init_tud(); +} + +static inline void mp_usbd_deinit(void) { + // Called in soft reset path. No-op if no runtime USB devices require cleanup. +} + +#else +// Runtime USB Device support requires more complex init/deinit void mp_usbd_init(void); +void mp_usbd_deinit(void); const char *mp_usbd_runtime_string_cb(uint8_t index); @@ -142,15 +155,7 @@ static inline bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); } -#else // Static USBD drivers only - -static inline void mp_usbd_init(void) { - // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() - // which is called in the below helper function. - mp_usbd_init_tud(); -} - -#endif +#endif // MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE #endif // MICROPY_HW_ENABLE_USBDEV From 4766f5680e25d608bb4200d01ca414dd503b2e3f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 6 Nov 2025 11:05:09 +1100 Subject: [PATCH 1469/2098] esp32: Fix USB deinit/reinit path via soft reset. Fixes problems with USB-CDC state after soft reset if USBDevice has been active with the default USB-CDC driver also enabled. This also brings ESP32 behaviour in line with RP2 and other ports, where boot.py is executed before the runtime USB device is initialised. This allows setting up a custom USB device in boot.py. There is still a bug here, because calling tud_disconnect() doesn't cause any UNPLUG or BUS_RESET events to arrive from TinyUSB - which means the USB device state stays out of sync until we call mp_usbd_init() again... This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton Signed-off-by: Angus Gratton --- ports/esp32/main.c | 10 ++++++++-- ports/esp32/usb.c | 6 +----- ports/esp32/usb.h | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index bd5775bc663..74e31647d76 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -107,7 +107,7 @@ void mp_task(void *pvParameter) { #if MICROPY_HW_ESP_USB_SERIAL_JTAG usb_serial_jtag_init(); #elif MICROPY_HW_ENABLE_USBDEV - usb_init(); + usb_phy_init(); #endif #if MICROPY_HW_ENABLE_UART_REPL uart_stdout_init(); @@ -145,6 +145,11 @@ void mp_task(void *pvParameter) { // run boot-up scripts pyexec_frozen_module("_boot.py", false); int ret = pyexec_file_if_exists("boot.py"); + + #if MICROPY_HW_ENABLE_USBDEV + mp_usbd_init(); + #endif + if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } @@ -193,7 +198,7 @@ void mp_task(void *pvParameter) { mp_thread_deinit(); #endif - #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + #if MICROPY_HW_ENABLE_USBDEV mp_usbd_deinit(); #endif @@ -219,6 +224,7 @@ void mp_task(void *pvParameter) { mp_deinit(); fflush(stdout); + goto soft_reset; } diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c index 750dd59ee6f..b90f53aa49d 100644 --- a/ports/esp32/usb.c +++ b/ports/esp32/usb.c @@ -38,7 +38,7 @@ static usb_phy_handle_t phy_hdl; -void usb_init(void) { +void usb_phy_init(void) { // ref: https://github.com/espressif/esp-usb/blob/4b6a798d0bed444fff48147c8dcdbbd038e92892/device/esp_tinyusb/tinyusb.c // Configure USB PHY @@ -51,10 +51,6 @@ void usb_init(void) { // Init ESP USB Phy usb_new_phy(&phy_conf, &phy_hdl); - - // Init MicroPython / TinyUSB - mp_usbd_init(); - } #if CONFIG_IDF_TARGET_ESP32S3 diff --git a/ports/esp32/usb.h b/ports/esp32/usb.h index 2bfa3d31aff..99437266d90 100644 --- a/ports/esp32/usb.h +++ b/ports/esp32/usb.h @@ -28,7 +28,7 @@ #define MICROPY_HW_USB_CDC_TX_TIMEOUT_MS (500) -void usb_init(void); +void usb_phy_init(void); void usb_usj_mode(void); #endif // MICROPY_INCLUDED_ESP32_USB_H From a435e4ecfd87b8aa7720d0429acbd4043965b7e5 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Nov 2025 15:47:27 +1100 Subject: [PATCH 1470/2098] esp32: Take global dependencies out of mpconfigport.h. This is necessary so the ESP-IDF TinyUSB component can include py/mpconfig.h, but is also a good design goal (less creep of symbols into unrelated parts of the code). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_i2s.c | 2 +- ports/esp32/modnetwork.h | 1 + ports/esp32/mpconfigport.h | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_i2s.c b/ports/esp32/machine_i2s.c index ba6377a7664..27b70f1c8a1 100644 --- a/ports/esp32/machine_i2s.c +++ b/ports/esp32/machine_i2s.c @@ -470,4 +470,4 @@ static void mp_machine_i2s_irq_update(machine_i2s_obj_t *self) { } } -MP_REGISTER_ROOT_POINTER(struct _machine_i2s_obj_t *machine_i2s_obj[I2S_NUM_AUTO]); +MP_REGISTER_ROOT_POINTER(struct _machine_i2s_obj_t *machine_i2s_obj[SOC_I2S_NUM]); diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index ba69d5cc0f9..a68db41a3e3 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -26,6 +26,7 @@ #ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H #define MICROPY_INCLUDED_ESP32_MODNETWORK_H +#include "esp_wifi_types.h" #include "esp_netif.h" // lan867x component requires newer IDF version diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index fafed3961cc..142da4b2309 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -9,8 +9,6 @@ #include "esp_random.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" -#include "driver/i2s_std.h" -#include "esp_wifi_types.h" #ifndef MICROPY_CONFIG_ROM_LEVEL #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) From 4f193132d2de4b464bbed29c9029727440da486a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Nov 2025 15:48:59 +1100 Subject: [PATCH 1471/2098] esp32: Remove dependency on esp_tinyusb. Instead, depend directly on espressif/tinyusb component (which is otherwise transitively included via esp_tinyusb). Turns out esp_tinyusb builds a bunch of source files with symbols that conflict with our tinyusb symbols (i.e. descriptors_control.c). This only works because nothing in MicroPython causes the linker to include the esp_tinyusb.a library, however in order to override the dcd_int_handler (in following commit) this caused the linker to pull this library in and break the build. There's also a problematic header skew - TinyUSB component was building with the tusb_config.h file from esp_tinyusb component, but we have our own tusb_config.h file in shared/tinyusb. The changes in parent commit allow us to build the TinyUSB component with our tusb_config.h header. User-facing impacts are: - Can no longer override USB VID & PID via sdkconfig, have to set MICROPY_HW_USB_VID/PID instead (changes applied in this commit). - esp32 boards will have the same USB serial number as other ports (i.e. based on the hardware MAC address, not hard-coded). Side effects include: - CFG_TUD_DWC2_SLAVE_ENABLE is now set, DMA mode is disabled. Signed-off-by: Angus Gratton --- .../ARDUINO_NANO_ESP32/mpconfigboard.cmake | 1 - .../boards/ARDUINO_NANO_ESP32/mpconfigboard.h | 5 +++++ .../boards/ARDUINO_NANO_ESP32/sdkconfig.board | 7 ------ .../ESP32_GENERIC_S2/mpconfigboard.cmake | 1 - .../ESP32_GENERIC_S3/mpconfigboard.cmake | 1 - .../boards/LOLIN_S2_MINI/mpconfigboard.cmake | 1 - .../boards/LOLIN_S2_PICO/mpconfigboard.cmake | 1 - .../M5STACK_ATOMS3_LITE/mpconfigboard.cmake | 1 - .../boards/UM_FEATHERS2/mpconfigboard.cmake | 1 - .../UM_FEATHERS2NEO/mpconfigboard.cmake | 1 - .../boards/UM_FEATHERS3/mpconfigboard.cmake | 1 - .../esp32/boards/UM_FEATHERS3/mpconfigboard.h | 5 +++++ .../esp32/boards/UM_FEATHERS3/sdkconfig.board | 9 -------- .../UM_FEATHERS3NEO/mpconfigboard.cmake | 1 - .../boards/UM_FEATHERS3NEO/mpconfigboard.h | 5 +++++ .../boards/UM_FEATHERS3NEO/sdkconfig.board | 9 -------- .../boards/UM_NANOS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_NANOS3/mpconfigboard.h | 5 +++++ ports/esp32/boards/UM_NANOS3/sdkconfig.board | 9 -------- .../esp32/boards/UM_OMGS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_OMGS3/mpconfigboard.h | 5 +++++ ports/esp32/boards/UM_OMGS3/sdkconfig.board | 9 -------- .../esp32/boards/UM_PROS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_PROS3/mpconfigboard.h | 5 +++++ ports/esp32/boards/UM_PROS3/sdkconfig.board | 9 -------- .../UM_RGBTOUCH_MINI/mpconfigboard.cmake | 1 - .../boards/UM_RGBTOUCH_MINI/mpconfigboard.h | 5 +++++ .../boards/UM_RGBTOUCH_MINI/sdkconfig.board | 9 -------- .../boards/UM_TINYS2/mpconfigboard.cmake | 1 - .../boards/UM_TINYS3/mpconfigboard.cmake | 1 - ports/esp32/boards/UM_TINYS3/mpconfigboard.h | 5 +++++ ports/esp32/boards/UM_TINYS3/sdkconfig.board | 9 -------- .../boards/UM_TINYWATCHS3/mpconfigboard.cmake | 1 - .../boards/UM_TINYWATCHS3/mpconfigboard.h | 5 +++++ .../boards/UM_TINYWATCHS3/sdkconfig.board | 9 -------- ports/esp32/boards/sdkconfig.usb | 4 ---- ports/esp32/esp32_common.cmake | 8 +++++++ ports/esp32/lockfiles/dependencies.lock.esp32 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c2 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c3 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c5 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c6 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32s2 | 20 +++-------------- .../esp32/lockfiles/dependencies.lock.esp32s3 | 20 +++-------------- ports/esp32/main/idf_component.yml | 4 ++-- ports/esp32/mpconfigport.h | 22 ++++--------------- 46 files changed, 70 insertions(+), 159 deletions(-) delete mode 100644 ports/esp32/boards/sdkconfig.usb diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake index a3888823421..06d3c27a6c7 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.cmake @@ -6,7 +6,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.h b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.h index 31b2a49bf8c..3ca587ae407 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.h +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/mpconfigboard.h @@ -22,6 +22,11 @@ #define MICROPY_HW_USB_CDC_1200BPS_TOUCH (1) #define MICROPY_SCHEDULER_STATIC_NODES (1) +#define MICROPY_HW_USB_VID 0x2341 +#define MICROPY_HW_USB_PID 0x056B +#define MICROPY_HW_USB_MANUFACTURER_STRING "Arduino" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "Nano ESP32" + #define MICROPY_BOARD_STARTUP NANO_ESP32_board_startup void NANO_ESP32_board_startup(void); diff --git a/ports/esp32/boards/ARDUINO_NANO_ESP32/sdkconfig.board b/ports/esp32/boards/ARDUINO_NANO_ESP32/sdkconfig.board index d586dd4ce40..242cff84dde 100644 --- a/ports/esp32/boards/ARDUINO_NANO_ESP32/sdkconfig.board +++ b/ports/esp32/boards/ARDUINO_NANO_ESP32/sdkconfig.board @@ -11,13 +11,6 @@ CONFIG_SPIRAM_IGNORE_NOTFOUND= CONFIG_LWIP_LOCAL_HOSTNAME="nano-esp32" -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x2341 -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x056B -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Arduino" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="Nano ESP32" - # compatibility with Espressif Arduino core CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y CONFIG_ESP_ENABLE_COREDUMP_TO_FLASH=y diff --git a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake index 76d185d90d5..6907adb8192 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_S2/mpconfigboard.cmake @@ -2,6 +2,5 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.spiram_sx ) diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake index 9b0df3b3771..cdcefed84cf 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake +++ b/ports/esp32/boards/ESP32_GENERIC_S3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.spiram_sx boards/ESP32_GENERIC_S3/sdkconfig.board diff --git a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake index dc9abd7478b..8d6d1f3598d 100644 --- a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake @@ -3,7 +3,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.spiram_sx - boards/sdkconfig.usb ) set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake index dc9abd7478b..8d6d1f3598d 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake +++ b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake @@ -3,7 +3,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.spiram_sx - boards/sdkconfig.usb ) set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/M5STACK_ATOMS3_LITE/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_ATOMS3_LITE/mpconfigboard.cmake index 10608fcec7f..9157c6b93e5 100644 --- a/ports/esp32/boards/M5STACK_ATOMS3_LITE/mpconfigboard.cmake +++ b/ports/esp32/boards/M5STACK_ATOMS3_LITE/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/ESP32_GENERIC_S3/sdkconfig.board ) diff --git a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake index 5e570d513bb..2d8f70135e3 100644 --- a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.spiram_sx - boards/sdkconfig.usb boards/UM_FEATHERS2/sdkconfig.board ) diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake index c98ef691769..defb597c9cc 100644 --- a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.spiram_sx - boards/sdkconfig.usb boards/UM_FEATHERS2NEO/sdkconfig.board ) diff --git a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake index 63d1af1da76..5a41be1862e 100644 --- a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.h index f4abfb21bc8..cb6269ff9c6 100644 --- a/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_FEATHERS3/mpconfigboard.h @@ -11,3 +11,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x80D7 +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "FeatherS3" diff --git a/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board index 3092e355982..2962cb44c6e 100644 --- a/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board +++ b/ports/esp32/boards/UM_FEATHERS3/sdkconfig.board @@ -4,12 +4,3 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x80D7 -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="FeatherS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_fs3_" diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake index 1d2b887d2ce..698aa76f048 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.h index c08206f7b52..bfe38bc03bd 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.h +++ b/ports/esp32/boards/UM_FEATHERS3NEO/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x81FC +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "FeatherS3 Neo" diff --git a/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board index 25b8d7689d8..8d44c65c3f2 100644 --- a/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board +++ b/ports/esp32/boards/UM_FEATHERS3NEO/sdkconfig.board @@ -5,12 +5,3 @@ CONFIG_ESPTOOLPY_AFTER_NORESET=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS3Neo" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x81FC -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="FeatherS3 Neo" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_fs3neo_" diff --git a/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake b/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake index 6c7f34009e4..3935a66f8d0 100644 --- a/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_NANOS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_NANOS3/mpconfigboard.h b/ports/esp32/boards/UM_NANOS3/mpconfigboard.h index 44197be2a8f..44434d26e2a 100644 --- a/ports/esp32/boards/UM_NANOS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_NANOS3/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x817A +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "NanoS3" diff --git a/ports/esp32/boards/UM_NANOS3/sdkconfig.board b/ports/esp32/boards/UM_NANOS3/sdkconfig.board index e06f7a4245f..0a1361b9b50 100644 --- a/ports/esp32/boards/UM_NANOS3/sdkconfig.board +++ b/ports/esp32/boards/UM_NANOS3/sdkconfig.board @@ -4,12 +4,3 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMNanoS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x817A -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="NanoS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_ns3_" diff --git a/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake b/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake index aa82111d758..85c3d71e8c7 100644 --- a/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_OMGS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_OMGS3/mpconfigboard.h b/ports/esp32/boards/UM_OMGS3/mpconfigboard.h index e3955a6b807..451225b6fbf 100644 --- a/ports/esp32/boards/UM_OMGS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_OMGS3/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (6) #define MICROPY_HW_SPI1_MISO (5) #define MICROPY_HW_SPI1_SCK (4) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x8225 +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "OMGS3" diff --git a/ports/esp32/boards/UM_OMGS3/sdkconfig.board b/ports/esp32/boards/UM_OMGS3/sdkconfig.board index 8a0bf0b13af..794bc70b111 100644 --- a/ports/esp32/boards/UM_OMGS3/sdkconfig.board +++ b/ports/esp32/boards/UM_OMGS3/sdkconfig.board @@ -5,12 +5,3 @@ CONFIG_ESPTOOLPY_AFTER_NORESET=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMOMGS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x8225 -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="OMGS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_omgs3_" diff --git a/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake b/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake index 41a96f26e34..c8267f72802 100644 --- a/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_PROS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_PROS3/mpconfigboard.h b/ports/esp32/boards/UM_PROS3/mpconfigboard.h index cc0ebbefa30..eea6f6e65db 100644 --- a/ports/esp32/boards/UM_PROS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_PROS3/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x80D4 +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "ProS3" diff --git a/ports/esp32/boards/UM_PROS3/sdkconfig.board b/ports/esp32/boards/UM_PROS3/sdkconfig.board index 75ca58d80b0..1b10ba11cd8 100644 --- a/ports/esp32/boards/UM_PROS3/sdkconfig.board +++ b/ports/esp32/boards/UM_PROS3/sdkconfig.board @@ -4,12 +4,3 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMProS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x80D4 -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="ProS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_ps3_" diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake index 8b29cb344e7..4686fc5884a 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.h b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.h index 11841247993..2d83cba1619 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.h +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x81FF +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "RGBTouchMini" diff --git a/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board b/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board index 7d244fdc163..234aed6b210 100644 --- a/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board +++ b/ports/esp32/boards/UM_RGBTOUCH_MINI/sdkconfig.board @@ -5,12 +5,3 @@ CONFIG_ESPTOOLPY_AFTER_NORESET=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMRGBTouchMini" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x81FF -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="RGBTouchMini" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_rgbtouch_mini_" diff --git a/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake index 70cb4a814ff..4b0c6bfccd7 100644 --- a/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s2) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.spiram_sx - boards/sdkconfig.usb boards/UM_TINYS2/sdkconfig.board ) diff --git a/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake index 6c7f34009e4..3935a66f8d0 100644 --- a/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_TINYS3/mpconfigboard.h b/ports/esp32/boards/UM_TINYS3/mpconfigboard.h index 74c7622cb4b..2cba3f1b0a2 100644 --- a/ports/esp32/boards/UM_TINYS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_TINYS3/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x80D1 +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "TinyS3" diff --git a/ports/esp32/boards/UM_TINYS3/sdkconfig.board b/ports/esp32/boards/UM_TINYS3/sdkconfig.board index 2474c5fb279..b9cb80695f5 100644 --- a/ports/esp32/boards/UM_TINYS3/sdkconfig.board +++ b/ports/esp32/boards/UM_TINYS3/sdkconfig.board @@ -4,12 +4,3 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMTinyS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x80D1 -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="TinyS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_ts3_" diff --git a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake index 089149c44c5..2978dcd6f3c 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake +++ b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.cmake @@ -2,7 +2,6 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base - boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.240mhz boards/sdkconfig.spiram_sx diff --git a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.h b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.h index 6cc13454136..c8fc554a102 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.h +++ b/ports/esp32/boards/UM_TINYWATCHS3/mpconfigboard.h @@ -8,3 +8,8 @@ #define MICROPY_HW_SPI1_MOSI (35) #define MICROPY_HW_SPI1_MISO (37) #define MICROPY_HW_SPI1_SCK (36) + +#define MICROPY_HW_USB_VID 0x303A +#define MICROPY_HW_USB_PID 0x81B1 +#define MICROPY_HW_USB_MANUFACTURER_STRING "Unexpected Maker" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "TinyWATCHS3" diff --git a/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board b/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board index 10121d235d0..da804fe30ad 100644 --- a/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board +++ b/ports/esp32/boards/UM_TINYWATCHS3/sdkconfig.board @@ -4,12 +4,3 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM_MEMTEST= CONFIG_LWIP_LOCAL_HOSTNAME="UMTinyWATCHS3" - -CONFIG_TINYUSB_DESC_CUSTOM_VID=0x303A -CONFIG_TINYUSB_DESC_CUSTOM_PID=0x81B1 -CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 -CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n -CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n -CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Unexpected Maker" -CONFIG_TINYUSB_DESC_PRODUCT_STRING="TinyWATCHS3" -CONFIG_TINYUSB_DESC_SERIAL_STRING="_tws3_" diff --git a/ports/esp32/boards/sdkconfig.usb b/ports/esp32/boards/sdkconfig.usb deleted file mode 100644 index 4090c710e62..00000000000 --- a/ports/esp32/boards/sdkconfig.usb +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG_USB_OTG_SUPPORTED=y -CONFIG_TINYUSB_CDC_ENABLED=y -CONFIG_TINYUSB_CDC_RX_BUFSIZE=256 -CONFIG_TINYUSB_CDC_TX_BUFSIZE=256 diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 6922ac5fec0..807d712830a 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -97,6 +97,14 @@ if(MICROPY_PY_TINYUSB) list(APPEND MICROPY_INC_TINYUSB ${MICROPY_DIR}/shared/tinyusb/ ) + + # Build the Espressif tinyusb component with MicroPython shared/tinyusb/tusb_config.h + idf_component_get_property(tusb_lib espressif__tinyusb COMPONENT_LIB) + target_include_directories(${tusb_lib} PRIVATE + ${MICROPY_DIR}/shared/tinyusb + ${MICROPY_DIR} + ${MICROPY_PORT_DIR} + ${MICROPY_BOARD_DIR}) endif() list(APPEND MICROPY_SOURCE_PORT diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 index 71eccf03201..f48a8bcad62 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -30,6 +30,6 @@ direct_dependencies: - espressif/lan867x - espressif/mdns - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 index 5f46e615641..34311207ee1 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32c2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 index 5fb11084ea2..811a3ab5106 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32c3 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c5 b/ports/esp32/lockfiles/dependencies.lock.esp32c5 index 204f64f68ca..6f24d013470 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c5 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c5 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32c5 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 index 4debf5548ea..74f2cdbd486 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c6 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32c6 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 index bb1443947e9..a672c6d5e0a 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -1,18 +1,4 @@ dependencies: - espressif/esp_tinyusb: - component_hash: 96d232ced7afe1976119b62f7fbf1944a2a78b36228ff6f7b9318394ac1153cc - dependencies: - - name: idf - require: private - version: '>=5.0' - - name: espressif/tinyusb - registry_url: https://components.espressif.com - require: public - version: '>=0.14.2' - source: - registry_url: https://components.espressif.com/ - type: service - version: 1.7.6~1 espressif/mdns: component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed dependencies: @@ -30,7 +16,7 @@ dependencies: require: private version: '>=5.0' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service targets: - esp32s2 @@ -42,9 +28,9 @@ dependencies: type: idf version: 5.5.1 direct_dependencies: -- espressif/esp_tinyusb - espressif/mdns +- espressif/tinyusb - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32s2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 index 38c17f5d625..f0f229c9054 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -1,18 +1,4 @@ dependencies: - espressif/esp_tinyusb: - component_hash: 96d232ced7afe1976119b62f7fbf1944a2a78b36228ff6f7b9318394ac1153cc - dependencies: - - name: idf - require: private - version: '>=5.0' - - name: espressif/tinyusb - registry_url: https://components.espressif.com - require: public - version: '>=0.14.2' - source: - registry_url: https://components.espressif.com/ - type: service - version: 1.7.6~1 espressif/mdns: component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed dependencies: @@ -30,7 +16,7 @@ dependencies: require: private version: '>=5.0' source: - registry_url: https://components.espressif.com + registry_url: https://components.espressif.com/ type: service targets: - esp32s2 @@ -42,9 +28,9 @@ dependencies: type: idf version: 5.5.1 direct_dependencies: -- espressif/esp_tinyusb - espressif/mdns +- espressif/tinyusb - idf -manifest_hash: 3b18b5bbac91c9fe5098d3759a37c84ed0828546d8cbc92e26e4c1698e689c8a +manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 target: esp32s3 version: 2.0.0 diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index cb14ffde60d..1ee8aa9769c 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -1,10 +1,10 @@ ## IDF Component Manager Manifest File dependencies: espressif/mdns: "~1.1.0" - espressif/esp_tinyusb: + espressif/tinyusb: rules: - if: "target in [esp32s2, esp32s3]" - version: "~1.7.6" + version: "~0.18.0" espressif/lan867x: version: "~1.0.0" rules: diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 142da4b2309..7b973ebb969 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -223,19 +223,14 @@ #ifndef MICROPY_HW_USB_VID #define USB_ESPRESSIF_VID 0x303A -#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID #define MICROPY_HW_USB_VID (USB_ESPRESSIF_VID) -#else -#define MICROPY_HW_USB_VID (CONFIG_TINYUSB_DESC_CUSTOM_VID) #endif #ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice #endif -#endif #ifndef MICROPY_HW_USB_PID -#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID #define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) // A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. // Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. @@ -244,25 +239,16 @@ #define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ _PID_MAP(MIDI, 3)) // | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) #define MICROPY_HW_USB_PID (USB_TUSB_PID) -#else -#define MICROPY_HW_USB_PID (CONFIG_TINYUSB_DESC_CUSTOM_PID) -#endif #endif +// These Manufacturer & Product strings are the defaults when using the +// esp_tinyusb component (which MicroPython used in the past). #ifndef MICROPY_HW_USB_MANUFACTURER_STRING -#ifdef CONFIG_TINYUSB_DESC_MANUFACTURER_STRING -#define MICROPY_HW_USB_MANUFACTURER_STRING CONFIG_TINYUSB_DESC_MANUFACTURER_STRING -#else -#define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython" -#endif +#define MICROPY_HW_USB_MANUFACTURER_STRING "Espressif Systems" #endif #ifndef MICROPY_HW_USB_PRODUCT_FS_STRING -#ifdef CONFIG_TINYUSB_DESC_PRODUCT_STRING -#define MICROPY_HW_USB_PRODUCT_FS_STRING CONFIG_TINYUSB_DESC_PRODUCT_STRING -#else -#define MICROPY_HW_USB_PRODUCT_FS_STRING "Board in FS mode" -#endif +#define MICROPY_HW_USB_PRODUCT_FS_STRING "Espressif Device" #endif #endif // MICROPY_HW_ENABLE_USBDEV From 713f9dcd229e9bfaf3a1a4f9e91b1cef6ca08258 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 12 Nov 2025 16:37:25 +1100 Subject: [PATCH 1472/2098] esp32: Fix USB Zero Length Packet issue with patched tinyusb. Temporarily switch from the espressif TinyUSB component to a MicroPython fork where this fix has been cherry-picked: https://github.com/hathach/tinyusb/pull/3293 This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/lockfiles/dependencies.lock.esp32 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c2 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c3 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c6 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32s2 | 13 +++++++------ ports/esp32/lockfiles/dependencies.lock.esp32s3 | 13 +++++++------ ports/esp32/main/idf_component.yml | 5 ++++- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 index f48a8bcad62..4b0b2d9729d 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -30,6 +30,6 @@ direct_dependencies: - espressif/lan867x - espressif/mdns - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 index 34311207ee1..5c5cea0c9a1 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32c2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 index 811a3ab5106..4c4c869c27e 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32c3 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 index 74f2cdbd486..b7435e10767 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c6 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32c6 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 index a672c6d5e0a..a13d9fd401d 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -10,19 +10,20 @@ dependencies: type: service version: 1.1.0 espressif/tinyusb: - component_hash: aa65639878f27a44d349044afd9c3fc134a92bd560874fdac1d836019b5c07ca + component_hash: ee1c962cff61eb975d508258d509974d58031cc27ff0d6c4117a67a613a49594 dependencies: - name: idf - require: private version: '>=5.0' source: - registry_url: https://components.espressif.com/ - type: service + git: https://github.com/micropython/tinyusb-espressif.git + path: . + type: git targets: - esp32s2 - esp32s3 - esp32p4 - version: 0.18.0~4 + - esp32h4 + version: e4c0ec3caab3d9c25374de7047653b9ced8f14ff idf: source: type: idf @@ -31,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32s2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 index f0f229c9054..d5e7045b77a 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -10,19 +10,20 @@ dependencies: type: service version: 1.1.0 espressif/tinyusb: - component_hash: aa65639878f27a44d349044afd9c3fc134a92bd560874fdac1d836019b5c07ca + component_hash: ee1c962cff61eb975d508258d509974d58031cc27ff0d6c4117a67a613a49594 dependencies: - name: idf - require: private version: '>=5.0' source: - registry_url: https://components.espressif.com/ - type: service + git: https://github.com/micropython/tinyusb-espressif.git + path: . + type: git targets: - esp32s2 - esp32s3 - esp32p4 - version: 0.18.0~4 + - esp32h4 + version: e4c0ec3caab3d9c25374de7047653b9ced8f14ff idf: source: type: idf @@ -31,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: ea629d6996152d77801fb1acae35b27596fdc023a933d40287d80f98dc497b55 +manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 target: esp32s3 version: 2.0.0 diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index 1ee8aa9769c..77904865baa 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -4,7 +4,10 @@ dependencies: espressif/tinyusb: rules: - if: "target in [esp32s2, esp32s3]" - version: "~0.18.0" + # Temporary workaround for https://github.com/hathach/tinyusb/issues/3154 + # Can be removed once fix is released in espressif/tinyusb + git: https://github.com/micropython/tinyusb-espressif.git + version: cherrypick/dwc2_zlp_fix espressif/lan867x: version: "~1.0.0" rules: From 787d85e0001089eb92f1c3643f29c14b3273b795 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sun, 9 Nov 2025 00:08:42 +0100 Subject: [PATCH 1473/2098] tests/extmod/machine_i2s_rate.py: Adjust pins when running on ESP32-C3. Changed the pin numbers used for the I2S rate testing as the pin numbers used were incompatible with the limited GPIO pins availalbe on the ESP32-C3, causing the tests to fail. Signed-off-by: Jos Verlinde --- tests/extmod/machine_i2s_rate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/extmod/machine_i2s_rate.py b/tests/extmod/machine_i2s_rate.py index c8fa11514c9..f6de3d8f13c 100644 --- a/tests/extmod/machine_i2s_rate.py +++ b/tests/extmod/machine_i2s_rate.py @@ -26,7 +26,11 @@ (2, Pin("D4"), Pin("D3"), Pin("D2"), None), ) elif "esp32" in sys.platform: - i2s_instances = ((0, Pin(18), Pin(19), Pin(21), Pin(14)),) + try: + i2s_instances = ((0, Pin(18), Pin(19), Pin(21), Pin(14)),) + except ValueError: + # fallback to lower pin number for ESP32-C3 + i2s_instances = ((0, Pin(6), Pin(7), Pin(10), Pin(11)),) # Allow for small additional RTOS overhead MAX_DELTA_MS = 8 From 8b7d9819b72f5c07ece0eefff556bf54bc35a161 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Nov 2025 13:34:38 +1100 Subject: [PATCH 1474/2098] tests/import: Make import_override and pkg7 tests behave under CPython. Signed-off-by: Damien George --- tests/import/import_override.py | 4 ++++ tests/import/pkg7/subpkg1/subpkg2/mod3.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/import/import_override.py b/tests/import/import_override.py index 029ebe54c1f..0144e78cb9f 100644 --- a/tests/import/import_override.py +++ b/tests/import/import_override.py @@ -2,6 +2,10 @@ def custom_import(name, globals, locals, fromlist, level): + # CPython always tries to import _io, so just let that through as-is. + if name == "_io": + return orig_import(name, globals, locals, fromlist, level) + print("import", name, fromlist, level) class M: diff --git a/tests/import/pkg7/subpkg1/subpkg2/mod3.py b/tests/import/pkg7/subpkg1/subpkg2/mod3.py index b0f4279fcf5..f7f4c1e65f4 100644 --- a/tests/import/pkg7/subpkg1/subpkg2/mod3.py +++ b/tests/import/pkg7/subpkg1/subpkg2/mod3.py @@ -5,7 +5,9 @@ print(bar) # attempted relative import beyond top-level package +# On older versions of CPython (eg 3.8) this is a ValueError, but on +# newer CPython (eg 3.11) and MicroPython it's an ImportError. try: from .... import mod1 -except ImportError: +except (ImportError, ValueError): print("ImportError") From f3804c65896fd1471df723977f941f46e7b8bd26 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Nov 2025 13:26:20 +1100 Subject: [PATCH 1475/2098] tests/import: Remove .py.exp files where they match CPython. Signed-off-by: Damien George --- tests/import/import_override.py.exp | 2 -- tests/import/import_pkg7.py.exp | 8 -------- tests/import/module_getattr.py.exp | 1 - 3 files changed, 11 deletions(-) delete mode 100644 tests/import/import_override.py.exp delete mode 100644 tests/import/import_pkg7.py.exp delete mode 100644 tests/import/module_getattr.py.exp diff --git a/tests/import/import_override.py.exp b/tests/import/import_override.py.exp deleted file mode 100644 index 365248da6d8..00000000000 --- a/tests/import/import_override.py.exp +++ /dev/null @@ -1,2 +0,0 @@ -import import1b None 0 -456 diff --git a/tests/import/import_pkg7.py.exp b/tests/import/import_pkg7.py.exp deleted file mode 100644 index 8f21a615f6a..00000000000 --- a/tests/import/import_pkg7.py.exp +++ /dev/null @@ -1,8 +0,0 @@ -pkg __name__: pkg7 -pkg __name__: pkg7.subpkg1 -pkg __name__: pkg7.subpkg1.subpkg2 -mod1 -mod2 -mod1.foo -mod2.bar -ImportError diff --git a/tests/import/module_getattr.py.exp b/tests/import/module_getattr.py.exp deleted file mode 100644 index bc59c12aa16..00000000000 --- a/tests/import/module_getattr.py.exp +++ /dev/null @@ -1 +0,0 @@ -False From d980bbd23717d504b8be8613e119467ca2136b2b Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Wed, 13 Aug 2025 17:13:49 +0300 Subject: [PATCH 1476/2098] tests/extmod_hardware/machine_encoder.py: Fix initial rotation count. ESP32 requires initial high levels of the channels before starting the count, in order to have 4 pulse for the first rotation. Also add tests for encoder phases x2 and x4. Signed-off-by: Ihor Nehrutsa --- tests/extmod_hardware/machine_encoder.py | 102 ++++++++++++++++++----- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/tests/extmod_hardware/machine_encoder.py b/tests/extmod_hardware/machine_encoder.py index 9bd2bb46417..f28eb6de6f3 100644 --- a/tests/extmod_hardware/machine_encoder.py +++ b/tests/extmod_hardware/machine_encoder.py @@ -13,6 +13,9 @@ import sys from machine import Pin +PRINT = False +PIN_INIT_VALUE = 1 + if "esp32" in sys.platform: id = 0 out0_pin = 4 @@ -33,25 +36,70 @@ class TestEncoder(unittest.TestCase): def setUp(self): - out0_pin(0) - out1_pin(0) - self.enc = Encoder(id, in0_pin, in1_pin) + out0_pin(PIN_INIT_VALUE) + out1_pin(PIN_INIT_VALUE) + self.enc = Encoder(id, in0_pin, in1_pin, phases=1) + self.enc2 = Encoder(id + 1, in0_pin, in1_pin, phases=2) + self.enc4 = Encoder(id + 2, in0_pin, in1_pin, phases=4) self.pulses = 0 # track the expected encoder position in software + if PRINT: + print( + "\nout0_pin() out1_pin() enc.value() enc2.value() enc4.value() |", + out0_pin(), + out1_pin(), + "|", + self.enc.value(), + self.enc2.value(), + self.enc4.value(), + ) def tearDown(self): self.enc.deinit() + try: + self.enc2.deinit() + except: + pass + try: + self.enc4.deinit() + except: + pass def rotate(self, pulses): for _ in range(abs(pulses)): self.pulses += 1 if (pulses > 0) else -1 - q = self.pulses % 4 - # Only one pin should change state each "step" so output won't glitch - out0_pin(q in (1, 2)) - out1_pin(q in (2, 3)) + if pulses > 0: + if self.pulses % 2: + out0_pin(not out0_pin()) + else: + out1_pin(not out1_pin()) + else: + if self.pulses % 2: + out1_pin(not out1_pin()) + else: + out0_pin(not out0_pin()) + if PRINT: + print( + "out0_pin() out1_pin() enc.value() enc2.value() enc4.value() pulses self.pulses |", + out0_pin(), + out1_pin(), + "|", + self.enc.value(), + self.enc2.value(), + self.enc4.value(), + "|", + pulses, + self.pulses, + ) - def assertPosition(self, value): + def assertPosition(self, value, value2=None, value4=None): self.assertEqual(self.enc.value(), value) + if not value2 is None: + self.assertEqual(self.enc2.value(), value2) + if not value4 is None: + self.assertEqual(self.enc4.value(), value4) + pass + @unittest.skipIf(sys.platform == "mimxrt", "cannot read back the pin") def test_connections(self): # Test the hardware connections are correct. If this test fails, all tests will fail. for ch, outp, inp in ((0, out0_pin, in0_pin), (1, out1_pin, in1_pin)): @@ -64,7 +112,7 @@ def test_connections(self): def test_basics(self): self.assertPosition(0) self.rotate(100) - self.assertPosition(100 // 4) + self.assertPosition(100 // 4, 100 // 2, 100) self.rotate(-100) self.assertPosition(0) @@ -72,27 +120,39 @@ def test_partial(self): # With phase=1 (default), need 4x pulses to count a rotation self.assertPosition(0) self.rotate(1) - self.assertPosition(0) + self.assertPosition(1, 1, 1) self.rotate(1) - self.assertPosition(0) + self.assertPosition(1, 1, 2) self.rotate(1) - self.assertPosition(1) # only 3 pulses to count first rotation? + self.assertPosition(1, 2, 3) self.rotate(1) - self.assertPosition(1) + self.assertPosition(1, 2, 4) # +4 self.rotate(1) - self.assertPosition(1) + self.assertPosition(2, 3, 5) self.rotate(1) - self.assertPosition(1) + self.assertPosition(2, 3, 6) self.rotate(1) - self.assertPosition(2) # 4 for next rotation + self.assertPosition(2, 4, 7) + self.rotate(1) + self.assertPosition(2, 4, 8) # +4 self.rotate(-1) - self.assertPosition(1) - self.rotate(-4) - self.assertPosition(0) + self.assertPosition(2, 4, 7) + self.rotate(-3) + self.assertPosition(1, 2, 4) # -4 self.rotate(-4) - self.assertPosition(-1) + self.assertPosition(0, 0, 0) # -4 + self.rotate(-1) + self.assertPosition(0, 0, -1) + self.rotate(-1) + self.assertPosition(0, -1, -2) + self.rotate(-1) + self.assertPosition(0, -1, -3) + self.rotate(-1) + self.assertPosition(-1, -2, -4) # -4 + self.rotate(-1) + self.assertPosition(-1, -2, -5) self.rotate(-3) - self.assertPosition(-1) + self.assertPosition(-2, -4, -8) # -4 if __name__ == "__main__": From 094437686fc53727318eea82e5609106c26e882a Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Fri, 19 Sep 2025 13:04:37 +0300 Subject: [PATCH 1477/2098] tests/extmod_hardware/machine_encoder.py: Use target_wiring for encoder. Signed-off-by: Ihor Nehrutsa --- tests/extmod_hardware/machine_encoder.py | 16 +++++----------- tests/run-tests.py | 1 + tests/target_wiring/esp32.py | 5 +++++ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/extmod_hardware/machine_encoder.py b/tests/extmod_hardware/machine_encoder.py index f28eb6de6f3..c218c8bfb64 100644 --- a/tests/extmod_hardware/machine_encoder.py +++ b/tests/extmod_hardware/machine_encoder.py @@ -11,22 +11,16 @@ raise SystemExit import sys +import unittest from machine import Pin +from target_wiring import encoder_loopback_id, encoder_loopback_out_pins, encoder_loopback_in_pins PRINT = False PIN_INIT_VALUE = 1 -if "esp32" in sys.platform: - id = 0 - out0_pin = 4 - in0_pin = 5 - out1_pin = 12 - in1_pin = 13 -else: - print("Please add support for this test on this platform.") - raise SystemExit - -import unittest +id = encoder_loopback_id +out0_pin, out1_pin = encoder_loopback_out_pins +in0_pin, in1_pin = encoder_loopback_in_pins out0_pin = Pin(out0_pin, mode=Pin.OUT) in0_pin = Pin(in0_pin, mode=Pin.IN) diff --git a/tests/run-tests.py b/tests/run-tests.py index d8d2c42ad22..b4654c2e6b5 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -309,6 +309,7 @@ def open(self, path, mode): tests_requiring_target_wiring = ( "extmod/machine_uart_irq_txidle.py", "extmod/machine_uart_tx.py", + "extmod_hardware/machine_encoder.py", "extmod_hardware/machine_uart_irq_break.py", "extmod_hardware/machine_uart_irq_rx.py", "extmod_hardware/machine_uart_irq_rxidle.py", diff --git a/tests/target_wiring/esp32.py b/tests/target_wiring/esp32.py index 63d7a81a2dc..2767cd5acb2 100644 --- a/tests/target_wiring/esp32.py +++ b/tests/target_wiring/esp32.py @@ -2,6 +2,11 @@ # # Connect: # - GPIO4 to GPIO5 +# - GPIO12 to GPIO13 uart_loopback_args = (1,) uart_loopback_kwargs = {"tx": 4, "rx": 5} + +encoder_loopback_id = 0 +encoder_loopback_out_pins = (4, 12) +encoder_loopback_in_pins = (5, 13) From 4e79698a997465b1043fa8cbc4a72cdac889341f Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 5 Nov 2025 10:19:27 +0000 Subject: [PATCH 1478/2098] rp2/pendsv: Fix PendSV_Handler dispatch check when threading enabled. If MICROPY_PY_THREAD is set, PendSV_Handler acquires a mutex and calls the dispatch functions. If pendsv_schedule_dispatch is called by a higher priority interrupt while the mutex is acquired by PendSV_Handler it's possible for the dispatch to not be triggered. Add a check for dispatch calls at the end of PendSV_Handler once the mutex has been released. Fixes issue #18365. Signed-off-by: Peter Harper --- ports/rp2/pendsv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 05b521fde26..a3a855b8d27 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -180,5 +180,7 @@ void PendSV_Handler(void) { #if MICROPY_PY_THREAD mp_thread_recursive_mutex_unlock(&pendsv_mutex); + // Check if a dispatch occurred while the interrupt was being serviced + pendsv_resume_run_dispatch(); #endif } From 3d2eb3b8e907ee74350a69ff92af7a14d7c2f7c7 Mon Sep 17 00:00:00 2001 From: garywill Date: Sat, 15 Nov 2025 11:40:39 +0800 Subject: [PATCH 1479/2098] ports: Guard calls to machine_*_deinit_all() with #if where appropriate. To allow these features to be properly disabled. --- ports/esp32/main.c | 4 ++++ ports/mimxrt/main.c | 4 ++++ ports/rp2/main.c | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 74e31647d76..41eea29b08e 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -187,7 +187,9 @@ void mp_task(void *pvParameter) { // Deinit uart before timers, as esp32 uart // depends on a timer instance + #if MICROPY_PY_MACHINE_UART machine_uart_deinit_all(); + #endif machine_timer_deinit_all(); #if MICROPY_PY_ESP32_PCNT @@ -210,7 +212,9 @@ void mp_task(void *pvParameter) { mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); // deinitialise peripherals + #if MICROPY_PY_MACHINE_PWM machine_pwm_deinit_all(); + #endif // TODO: machine_rmt_deinit_all(); machine_pins_deinit(); #if MICROPY_PY_MACHINE_I2C_TARGET diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 22809916e7a..dcb1ede1670 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -170,8 +170,12 @@ int main(void) { #if MICROPY_PY_NETWORK mod_network_deinit(); #endif + #if MICROPY_PY_MACHINE_UART machine_uart_deinit_all(); + #endif + #if MICROPY_PY_MACHINE_PWM machine_pwm_deinit_all(); + #endif soft_timer_deinit(); gc_sweep_all(); mp_deinit(); diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 1ffcabdfa0d..f01522f2438 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -249,15 +249,21 @@ int main(int argc, char **argv) { #if MICROPY_PY_NETWORK mod_network_deinit(); #endif + #if MICROPY_PY_MACHINE_I2S machine_i2s_deinit_all(); + #endif rp2_dma_deinit(); rp2_pio_deinit(); #if MICROPY_PY_BLUETOOTH mp_bluetooth_deinit(); #endif + #if MICROPY_PY_MACHINE_PWM machine_pwm_deinit_all(); + #endif machine_pin_deinit(); + #if MICROPY_PY_MACHINE_UART machine_uart_deinit_all(); + #endif #if MICROPY_PY_MACHINE_I2C_TARGET mp_machine_i2c_target_deinit_all(); #endif From 2ad1d29747df1f35c638b32477684c6e141d0f81 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 15 Nov 2025 10:42:41 +0100 Subject: [PATCH 1480/2098] alif/tinyusb_port: Add missing license header. The full Alif license texit is available in `lib/alif_ensemble-cmsis-dfp/License.txt` if it's needed. Signed-off-by: iabdalkader --- ports/alif/tinyusb_port/alif_dcd_reg.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ports/alif/tinyusb_port/alif_dcd_reg.h b/ports/alif/tinyusb_port/alif_dcd_reg.h index 84220f6be4c..c5a7076562f 100644 --- a/ports/alif/tinyusb_port/alif_dcd_reg.h +++ b/ports/alif/tinyusb_port/alif_dcd_reg.h @@ -1,11 +1,13 @@ // *FORMAT-OFF* -///------------------------------------------------------------------------------------------------- -/// @file alif_dcd_reg.h -/// @author karol.saja@alifsemi.com -/// @version 0.0.1 -/// @date 2023-09-08 -/// @brief Low Level SPI driver -///------------------------------------------------------------------------------------------------- +/* + * Copyright (C) 2024 Alif Semiconductor - All Rights Reserved. + * Use, distribution and modification of this code is permitted under the + * terms stated in the Alif Semiconductor Software License Agreement + * + * You should have received a copy of the Alif Semiconductor Software + * License Agreement with this file. If not, please write to: + * contact@alifsemi.com, or visit: https://alifsemi.com/license + */ #ifndef __ALIF_DCD_REG_H__ #define __ALIF_DCD_REG_H__ From 5a69d4756751b92e13a6ea9a0c7b755dd3b43f7c Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Jul 2025 16:22:36 +1000 Subject: [PATCH 1481/2098] stm32/eth: Add support for Ethernet on N6 MCUs. This adds working support for 100MBit Ethernet on N6 MCUs. Signed-off-by: Damien George --- ports/stm32/boards/stm32n657_af.csv | 11 + ports/stm32/boards/stm32n6xx_hal_conf_base.h | 1 - ports/stm32/eth.c | 214 ++++++++++++++++--- ports/stm32/main.c | 7 +- ports/stm32/powerctrlboot.c | 5 + 5 files changed, 203 insertions(+), 35 deletions(-) diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv index 220d27b3f88..d7af193992e 100644 --- a/ports/stm32/boards/stm32n657_af.csv +++ b/ports/stm32/boards/stm32n657_af.csv @@ -20,6 +20,7 @@ PortC,PC9 , , , , PortC,PC10, , , , , , , , , , ,SDMMC1_D2 , , , , , , PortC,PC11, , , , , , , , , , ,SDMMC1_D3 , , , , , , PortC,PC12, , , , , , , , , , ,SDMMC1_CK , , , , , , +PortD,PD1 , , , , , , , , , , , ,ETH1_MDC , , , , , PortD,PD2 , , , , , , , , , , , ,SDMMC2_CK , , , , , PortD,PD5 , , , , , , , ,USART2_TX , , , , , , , , , PortD,PD6 , , , , ,TIM15_CH2 , , , , , , , , , , , , @@ -27,6 +28,7 @@ PortD,PD7 , , , , PortD,PD8 , , , , , , , ,USART3_TX , , , , , , , , , PortD,PD9 , , , , , , , ,USART3_RX , , , , , , , , , PortD,PD11, , , , , ,SPI2_MISO , , , , , , , , , , , +PortD,PD12, , , , , , , , , , , ,ETH1_MDIO , , , , , PortD,PD13, , ,TIM4_CH2 , , , , , , , , , , , , , , PortE,PE5 , , , , , , , ,USART1_TX , , , , , , , , , PortE,PE6 , , , , , , , ,USART1_RX , , , , , , , , , @@ -38,12 +40,21 @@ PortE,PE13, , , , PortE,PE14, , , , ,I2C4_SDA , , , , , , , , , , , , PortE,PE15, , , , , ,SPI5_SCK , , , , , , , , , , , PortF,PF3 , , , , , , , ,USART2_RTS , , , , , , , , ,ADC1_INP16 +PortF,PF4 , , , , , , , , , , , ,ETH1_MDIO , , , , , PortF,PF6 , , , , , , , ,USART2_RX , , , , , , , , , +PortF,PF7 , , , , , , , , , , , ,ETH1_RMII_REF_CLK , , , , , +PortF,PF10, , , , , , , , , , , ,ETH1_RMII_CRS_DV , , , , , +PortF,PF11, , , , , , , , , , , ,ETH1_RMII_TX_EN , , , , , +PortF,PF12, , , , , , , , , , , ,ETH1_RMII_TXD0 , , , , , +PortF,PF13, , , , , , , , , , , ,ETH1_RMII_TXD1 , , , , , +PortF,PF14, , , , , , , , , , , ,ETH1_RMII_RXD0 , , , , , +PortF,PF15, , , , , , , , , , , ,ETH1_RMII_RXD1 , , , , , PortG,PG0 , , ,TIM12_CH1 , , , , , , , , , , , , , , PortG,PG1 , , , , , ,SPI5_MISO , , , , , , , , , , , PortG,PG2 , , , , , ,SPI5_MOSI , , , , , , , , , , , PortG,PG5 , , , , , , , ,USART2_CTS , , , , , , , , , PortG,PG8 , , , , , , , , , , , ,SDMMC2_D1 , , , , , +PortG,PG11, , , , , , , , , , , ,ETH1_MDC , , , , , PortG,PG12, ,TIM17_CH1 , , , , , , , , , , , , , , , PortG,PG13, , ,TIM4_CH1 , , , , ,USART3_RTS , , , , , , , , , PortG,PG15, , , , , , , , , , , , , , , , ,ADC12_INP7/ADC12_INN3 diff --git a/ports/stm32/boards/stm32n6xx_hal_conf_base.h b/ports/stm32/boards/stm32n6xx_hal_conf_base.h index 641a003d8bf..87556cf1521 100644 --- a/ports/stm32/boards/stm32n6xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32n6xx_hal_conf_base.h @@ -160,7 +160,6 @@ #include "stm32n6xx_hal_dcmipp.h" #include "stm32n6xx_hal_dma2d.h" #include "stm32n6xx_hal_dts.h" -#include "stm32n6xx_hal_eth.h" #include "stm32n6xx_hal_exti.h" #include "stm32n6xx_hal_fdcan.h" #include "stm32n6xx_hal_gfxmmu.h" diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 9f655306816..0f8af5311bb 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -41,8 +41,19 @@ #include "lwip/dhcp.h" #include "netif/ethernet.h" +// Register and IRQ compatibility for STM32N6. +#if defined(STM32N6) +#define ETH ETH1 +#define ETH_MACMDIOAR_MB (ETH_MACMDIOAR_GB) +#define ETH_MACMDIOAR_MOC_Msk (ETH_MACMDIOAR_GOC_Msk) +#define ETH_MACMDIOAR_MOC_WR (ETH_MACMDIOAR_GOC_0) +#define ETH_MACMDIOAR_MOC_RD (ETH_MACMDIOAR_GOC_1 | ETH_MACMDIOAR_GOC_0) +#define ETH_IRQn ETH1_IRQn +#define ETH_IRQHandler ETH1_IRQHandler +#endif + // ETH DMA RX and TX descriptor definitions -#if defined(STM32H5) +#if defined(STM32H5) || defined(STM32N6) #define RX_DESCR_3_OWN_Pos (31) #define RX_DESCR_3_IOC_Pos (30) #define RX_DESCR_3_BUF1V_Pos (24) @@ -85,6 +96,13 @@ #define TX_DESCR_1_TBS1_Pos (0) #endif +// Static alternate function macro. +#if defined(STM32N6) +#define STATIC_AF_ETH(signal) STATIC_AF_ETH1_##signal +#else +#define STATIC_AF_ETH(signal) STATIC_AF_ETH_##signal +#endif + // Configuration values #define PHY_INIT_TIMEOUT_MS (10000) @@ -95,6 +113,12 @@ #define RX_BUF_NUM (5) #define TX_BUF_NUM (5) +#if defined(STM32N6) +// The N6 has two DMA channels, so use one for RX and one for TX. +#define RX_DMA_CH (0) +#define TX_DMA_CH (1) +#endif + typedef struct _eth_dma_rx_descr_t { volatile uint32_t rdes0, rdes1, rdes2, rdes3; } eth_dma_rx_descr_t; @@ -129,7 +153,7 @@ static void eth_mac_deinit(eth_t *self); static void eth_process_frame(eth_t *self, size_t len, const uint8_t *buf); void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { } uint32_t ar = ETH->MACMDIOAR; @@ -157,7 +181,7 @@ void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val) { } uint32_t eth_phy_read(uint32_t phy_addr, uint32_t reg) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { } uint32_t ar = ETH->MACMDIOAR; @@ -197,15 +221,15 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { } // Configure GPIO - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDC, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDC); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDIO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDIO); - mp_hal_pin_config_alt_static_speed(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_MEDIUM, STATIC_AF_ETH_RMII_REF_CLK); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_CRS_DV, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_CRS_DV); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD0); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD1); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TX_EN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TX_EN); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD0); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD1); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDC, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(MDC)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDIO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(MDIO)); + mp_hal_pin_config_alt_static_speed(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_MEDIUM, STATIC_AF_ETH(RMII_REF_CLK)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_CRS_DV, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_CRS_DV)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_RXD0)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_RXD1)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TX_EN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TX_EN)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TXD0)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TXD1)); // Enable peripheral clock #if defined(STM32H5) @@ -216,6 +240,11 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { __HAL_RCC_ETH1MAC_CLK_ENABLE(); __HAL_RCC_ETH1TX_CLK_ENABLE(); __HAL_RCC_ETH1RX_CLK_ENABLE(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_CLK_ENABLE(); + __HAL_RCC_ETH1MAC_CLK_ENABLE(); + __HAL_RCC_ETH1TX_CLK_ENABLE(); + __HAL_RCC_ETH1RX_CLK_ENABLE(); #else __HAL_RCC_ETH_CLK_ENABLE(); #endif @@ -229,7 +258,7 @@ void eth_set_trace(eth_t *self, uint32_t value) { static int eth_mac_init(eth_t *self) { // Configure MPU uint32_t irq_state = mpu_config_start(); - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) mpu_config_region(MPU_REGION_ETH, (uint32_t)ð_dma, MPU_CONFIG_ETH(16 * 1024)); #else mpu_config_region(MPU_REGION_ETH, (uint32_t)ð_dma, MPU_CONFIG_ETH(MPU_REGION_SIZE_16KB)); @@ -241,16 +270,29 @@ static int eth_mac_init(eth_t *self) { __HAL_RCC_ETH_FORCE_RESET(); #elif defined(STM32H7) __HAL_RCC_ETH1MAC_FORCE_RESET(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_FORCE_RESET(); #else __HAL_RCC_ETHMAC_FORCE_RESET(); #endif + // Select clock sources. + #if defined(STM32N6) + LL_RCC_SetETHREFTXClockSource(LL_RCC_ETH1REFTX_CLKSOURCE_EXT); // max 25MHz + LL_RCC_SetETHREFRXClockSource(LL_RCC_ETH1REFRX_CLKSOURCE_EXT); // max 125MHz + LL_RCC_SetETHClockSource(LL_RCC_ETH1_CLKSOURCE_IC12); // max 125MHz + LL_RCC_SetETH1PTPDivider(LL_RCC_ETH1PTP_DIV_1); + LL_RCC_SetETHPTPClockSource(LL_RCC_ETH1PTP_CLKSOURCE_HCLK); // max 200MHz + #endif + // Select RMII interface #if defined(STM32H5) __HAL_RCC_SBS_CLK_ENABLE(); SBS->PMCR = (SBS->PMCR & ~SBS_PMCR_ETH_SEL_PHY_Msk) | SBS_PMCR_ETH_SEL_PHY_2; #elif defined(STM32H7) SYSCFG->PMCR = (SYSCFG->PMCR & ~SYSCFG_PMCR_EPIS_SEL_Msk) | SYSCFG_PMCR_EPIS_SEL_2; + #elif defined(STM32N6) + LL_RCC_SetETHPHYInterface(LL_RCC_ETH1PHY_IF_RMII); #else __HAL_RCC_SYSCFG_CLK_ENABLE(); SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; @@ -265,6 +307,13 @@ static int eth_mac_init(eth_t *self) { #elif defined(STM32H7) __HAL_RCC_ETH1MAC_RELEASE_RESET(); + __HAL_RCC_ETH1MAC_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETH1TX_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETH1RX_CLK_SLEEP_ENABLE(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_RELEASE_RESET(); + + __HAL_RCC_ETH1_CLK_SLEEP_ENABLE(); __HAL_RCC_ETH1MAC_CLK_SLEEP_ENABLE(); __HAL_RCC_ETH1TX_CLK_SLEEP_ENABLE(); __HAL_RCC_ETH1RX_CLK_SLEEP_ENABLE(); @@ -277,7 +326,7 @@ static int eth_mac_init(eth_t *self) { #endif // Do a soft reset of the MAC core - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) #define ETH_SOFT_RESET(eth) do { eth->DMAMR = ETH_DMAMR_SWR; } while (0) #define ETH_IS_RESET(eth) (eth->DMAMR & ETH_DMAMR_SWR) #else @@ -299,7 +348,7 @@ static int eth_mac_init(eth_t *self) { // Set MII clock range uint32_t hclk = HAL_RCC_GetHCLKFreq(); uint32_t cr_div; - #if defined(STM32H5) + #if defined(STM32H5) || defined(STM32N6) cr_div = ETH->MACMDIOAR & ~ETH_MACMDIOAR_CR; if (hclk < 35000000) { cr_div |= ETH_MACMDIOAR_CR_DIV16; @@ -311,8 +360,17 @@ static int eth_mac_init(eth_t *self) { cr_div |= ETH_MACMDIOAR_CR_DIV62; } else if (hclk < 250000000) { cr_div |= ETH_MACMDIOAR_CR_DIV102; + #if defined(STM32H5) } else { cr_div |= ETH_MACMDIOAR_CR_DIV124; + #else + } else if (hclk < 300000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV124; + } else if (hclk < 500000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV204; + } else { + cr_div |= ETH_MACMDIOAR_CR_DIV324; + #endif } ETH->MACMDIOAR = cr_div; #elif defined(STM32H7) @@ -344,9 +402,17 @@ static int eth_mac_init(eth_t *self) { ETH->MACMIIAR = cr_div; #endif + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) + // Configure the MAC 1-us tick counter register. + WRITE_REG(ETH->MAC1USTCR, HAL_RCC_GetHCLKFreq() / 1000000U - 1U); + #endif + #if defined(STM32H5) || defined(STM32H7) // don't skip 32bit words since our descriptors are continuous in memory ETH->DMACCR &= ~(ETH_DMACCR_DSL_Msk); + #elif defined(STM32N6) + ETH->DMA_CH[RX_DMA_CH].DMACCR &= ~(ETH_DMACxCR_DSL_Msk); + ETH->DMA_CH[TX_DMA_CH].DMACCR &= ~(ETH_DMACxCR_DSL_Msk); #endif // Reset the PHY @@ -397,7 +463,7 @@ static int eth_mac_init(eth_t *self) { uint16_t phy_scsr = self->phy_get_link_status(self->phy_addr); // Burst mode configuration - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) ETH->DMASBMR = ETH->DMASBMR & ~ETH_DMASBMR_AAL & ~ETH_DMASBMR_FB; #else ETH->DMABMR = 0; @@ -410,6 +476,11 @@ static int eth_mac_init(eth_t *self) { | ETH_DMACIER_NIE // enable normal interrupts | ETH_DMACIER_RIE // enable RX interrupt ; + #elif defined(STM32N6) + ETH->DMA_CH[RX_DMA_CH].DMACIER = + ETH_DMACxIER_NIE // enable normal interrupts + | ETH_DMACxIER_RIE // enable RX interrupt + ; #else ETH->DMAIER = ETH_DMAIER_NISE // enable normal interrupts @@ -419,7 +490,7 @@ static int eth_mac_init(eth_t *self) { // Configure RX descriptor lists for (size_t i = 0; i < RX_BUF_NUM; ++i) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) eth_dma.rx_descr[i].rdes3 = 1 << RX_DESCR_3_OWN_Pos | (1 << RX_DESCR_3_BUF1V_Pos) // buf1 address valid @@ -439,6 +510,11 @@ static int eth_mac_init(eth_t *self) { #if defined(STM32H5) || defined(STM32H7) ETH->DMACRDLAR = (uint32_t)ð_dma.rx_descr[0]; + #elif defined(STM32N6) + // Set number of RX descriptors and buffer pointers. + ETH->DMA_CH[RX_DMA_CH].DMACRXRLR = RX_BUF_NUM - 1; + ETH->DMA_CH[RX_DMA_CH].DMACRXDLAR = (uint32_t)ð_dma.rx_descr[0]; + ETH->DMA_CH[RX_DMA_CH].DMACRXDTPR = (uint32_t)ð_dma.rx_descr[RX_BUF_NUM - 1]; #else ETH->DMARDLAR = (uint32_t)ð_dma.rx_descr[0]; #endif @@ -446,7 +522,7 @@ static int eth_mac_init(eth_t *self) { // Configure TX descriptor lists for (size_t i = 0; i < TX_BUF_NUM; ++i) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) eth_dma.tx_descr[i].tdes0 = 0; eth_dma.tx_descr[i].tdes1 = 0; eth_dma.tx_descr[i].tdes2 = TX_BUF_SIZE & TX_DESCR_2_B1L_Msk; @@ -465,6 +541,11 @@ static int eth_mac_init(eth_t *self) { ETH->DMACRDRLR = RX_BUF_NUM - 1; ETH->DMACTDLAR = (uint32_t)ð_dma.tx_descr[0]; + #elif defined(STM32N6) + // Set number of TX descriptors and buffer pointers. + ETH->DMA_CH[TX_DMA_CH].DMACTXRLR = TX_BUF_NUM - 1; + ETH->DMA_CH[TX_DMA_CH].DMACTXDLAR = (uint32_t)ð_dma.tx_descr[0]; + ETH->DMA_CH[TX_DMA_CH].DMACTXDTPR = (uint32_t)ð_dma.tx_descr[0]; #else ETH->DMATDLAR = (uint32_t)ð_dma.tx_descr[0]; #endif @@ -476,6 +557,11 @@ static int eth_mac_init(eth_t *self) { ETH->MTLRQOMR = ETH_MTLRQOMR_RSF; // transmission starts when a full packet resides in the Tx queue ETH->MTLTQOMR = ETH_MTLTQOMR_TSF; + #elif defined(STM32N6) + // read from RX FIFO only after a full frame is written + ETH->MTL_QUEUE[0].MTLRXQOMR = ETH_MTLRXQxOMR_RSF; + // transmission starts when a full packet resides in the Tx queue + ETH->MTL_QUEUE[0].MTLTXQOMR = ETH_MTLTXQxOMR_TSF; #else ETH->DMAOMR = ETH_DMAOMR_RSF // read from RX FIFO after a full frame is written @@ -485,7 +571,7 @@ static int eth_mac_init(eth_t *self) { mp_hal_delay_ms(2); // Select MAC filtering options - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) ETH->MACPFR = ETH_MACPFR_RA; // pass all frames up #else ETH->MACFFR = @@ -501,15 +587,47 @@ static int eth_mac_init(eth_t *self) { ETH->MACA0LR = mac[3] << 24 | mac[2] << 16 | mac[1] << 8 | mac[0]; mp_hal_delay_ms(2); + // Work out the line speed configuration for MACCR. + uint32_t maccr = 0; + if (phy_scsr & PHY_SPEED_100HALF) { + maccr |= ETH_MACCR_FES; + } + if (phy_scsr & PHY_DUPLEX) { + maccr |= ETH_MACCR_DM; + } + + #if defined(STM32N6) + + maccr |= + ETH_MACCR_IPG_96BIT + | ETH_MACCR_SARC_REPADDR0 + | ETH_MACCR_IPC + | ETH_MACCR_PS + | ETH_MACCR_BL_10 + | ETH_MACCR_PRELEN_7; + + ETH->MACCR = maccr; + ETH->MACECR = 0x618U; + ETH->MACWTR = ETH_MACWTR_WTO_2KB; + ETH->MACQ0TXFCR = ETH_MACQ0TXFCR_PLT_MINUS4; + ETH->MACRXFCR = 0; + ETH->MACRXQC0R = ETH_MACRXQC0R_RXQ0EN_GT | ETH_MACRXQC0R_RXQ1EN_NOT; + + ETH->MTLOMR = ETH_MTLOMR_SCHALG_SP | ETH_MTLOMR_RAA_SP; + ETH->MTLRXQDMAMR = ETH_MTLRXQDMAMR_Q0MDMACH_DMACH0 | ETH_MTLRXQDMAMR_Q1MDMACH_DMACH1; + ETH->MTL_QUEUE[0].MTLTXQOMR = ETH_MTLTXQxOMR_TXQEN_EN | ETH_MTLTXQxOMR_TSF | 7 << ETH_MTLTXQxOMR_TQS_Pos; + ETH->MTL_QUEUE[1].MTLTXQOMR = ETH_MTLTXQxOMR_TXQEN_EN | ETH_MTLTXQxOMR_TSF | 7 << ETH_MTLTXQxOMR_TQS_Pos; + ETH->MTL_QUEUE[0].MTLRXQOMR = ETH_MTLRXQxOMR_RSF | 15 << ETH_MTLRXQxOMR_RQS_Pos; + ETH->MTL_QUEUE[1].MTLRXQOMR = ETH_MTLRXQxOMR_RSF | 15 << ETH_MTLRXQxOMR_RQS_Pos; + + #else + // Set main MAC control register - ETH->MACCR = - phy_scsr == PHY_SPEED_10FULL ? ETH_MACCR_DM - : phy_scsr == PHY_SPEED_100HALF ? ETH_MACCR_FES - : phy_scsr == PHY_SPEED_100FULL ? (ETH_MACCR_FES | ETH_MACCR_DM) - : 0 - ; + ETH->MACCR = maccr; mp_hal_delay_ms(2); + #endif + // Start MAC layer ETH->MACCR |= ETH_MACCR_TE // enable TX @@ -521,6 +639,15 @@ static int eth_mac_init(eth_t *self) { #if defined(STM32H5) || defined(STM32H7) ETH->DMACRCR |= ETH_DMACRCR_SR; // start RX ETH->DMACTCR |= ETH_DMACTCR_ST; // start TX + #elif defined(STM32N6) + ETH->MTL_QUEUE[0].MTLTXQOMR |= ETH_MTLTXQxOMR_FTQ; // flush TX FIFO + ETH->MTL_QUEUE[1].MTLTXQOMR |= ETH_MTLTXQxOMR_FTQ; // flush TX FIFO + ETH->DMA_CH[RX_DMA_CH].DMACRXCR = RX_BUF_SIZE << ETH_DMACxRXCR_RBSZ_Pos; + ETH->DMA_CH[RX_DMA_CH].DMACRXCR |= ETH_DMACxRXCR_SR; // start RX + ETH->DMA_CH[TX_DMA_CH].DMACTXCR = 4 << ETH_DMACxTXCR_TXPBL_Pos; + ETH->DMA_CH[TX_DMA_CH].DMACTXCR |= ETH_DMACxTXCR_ST; // start TX + ETH->DMA_CH[RX_DMA_CH].DMACSR |= ETH_DMACxSR_TPS | ETH_DMACxSR_RPS; // clear TX/RX process stopped flags + ETH->DMA_CH[TX_DMA_CH].DMACSR |= ETH_DMACxSR_TPS | ETH_DMACxSR_RPS; // clear TX/RX process stopped flags #else ETH->DMAOMR |= ETH_DMAOMR_ST // start TX @@ -547,6 +674,10 @@ static void eth_mac_deinit(eth_t *self) { __HAL_RCC_ETH1MAC_FORCE_RESET(); __HAL_RCC_ETH1MAC_RELEASE_RESET(); __HAL_RCC_ETH1MAC_CLK_DISABLE(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_FORCE_RESET(); + __HAL_RCC_ETH1_RELEASE_RESET(); + __HAL_RCC_ETH1_CLK_DISABLE(); #else __HAL_RCC_ETHMAC_FORCE_RESET(); __HAL_RCC_ETHMAC_RELEASE_RESET(); @@ -563,7 +694,7 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; uint32_t t0 = mp_hal_ticks_ms(); for (;;) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) if (!(tx_descr->tdes3 & (1 << TX_DESCR_3_OWN_Pos))) { break; } @@ -577,7 +708,7 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { } } - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) // Update TX descriptor with length and buffer pointer *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; tx_descr->tdes2 = len & TX_DESCR_2_B1L_Msk; @@ -599,7 +730,7 @@ static int eth_tx_buf_send(void) { eth_dma.tx_descr_idx = (eth_dma.tx_descr_idx + 1) % TX_BUF_NUM; // Schedule to send next outgoing frame - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) tx_descr->tdes3 = 1 << TX_DESCR_3_OWN_Pos // owned by DMA | 1 << TX_DESCR_3_LD_Pos // last segment @@ -623,6 +754,11 @@ static int eth_tx_buf_send(void) { ETH->DMACSR = ETH_DMACSR_TBU; } ETH->DMACTDTPR = (uint32_t)ð_dma.tx_descr[eth_dma.tx_descr_idx]; + #elif defined(STM32N6) + if (ETH->DMA_CH[TX_DMA_CH].DMACSR & ETH_DMACxSR_TBU) { + ETH->DMA_CH[TX_DMA_CH].DMACSR = ETH_DMACxSR_TBU; + } + ETH->DMA_CH[TX_DMA_CH].DMACTXDTPR = (uint32_t)ð_dma.tx_descr[eth_dma.tx_descr_idx]; #else if (ETH->DMASR & ETH_DMASR_TBUS) { ETH->DMASR = ETH_DMASR_TBUS; @@ -640,7 +776,7 @@ static void eth_dma_rx_free(void) { eth_dma.rx_descr_idx = (eth_dma.rx_descr_idx + 1) % RX_BUF_NUM; // Schedule to get next incoming frame - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) rx_descr->rdes0 = (uint32_t)buf; rx_descr->rdes3 = 1 << RX_DESCR_3_OWN_Pos; // owned by DMA rx_descr->rdes3 |= 1 << RX_DESCR_3_BUF1V_Pos; // buf 1 address valid @@ -659,16 +795,24 @@ static void eth_dma_rx_free(void) { __DMB(); #if defined(STM32H5) || defined(STM32H7) ETH->DMACRDTPR = (uint32_t)&rx_descr[eth_dma.rx_descr_idx]; + #elif defined(STM32N6) + ETH->DMA_CH[RX_DMA_CH].DMACRXDTPR = (uint32_t)&rx_descr[eth_dma.rx_descr_idx]; #else ETH->DMARPDR = 0; #endif } void ETH_IRQHandler(void) { + MP_STATIC_ASSERT(ETH_IRQn > 0); + #if defined(STM32H5) || defined(STM32H7) uint32_t sr = ETH->DMACSR; ETH->DMACSR = ETH_DMACSR_NIS; uint32_t rx_interrupt = sr & ETH_DMACSR_RI; + #elif defined(STM32N6) + uint32_t sr = ETH->DMA_CH[RX_DMA_CH].DMACSR; + ETH->DMA_CH[RX_DMA_CH].DMACSR = ETH_DMACxSR_NIS; + uint32_t rx_interrupt = sr & ETH_DMACxSR_RI; #else uint32_t sr = ETH->DMASR; ETH->DMASR = ETH_DMASR_NIS; @@ -677,11 +821,13 @@ void ETH_IRQHandler(void) { if (rx_interrupt) { #if defined(STM32H5) || defined(STM32H7) ETH->DMACSR = ETH_DMACSR_RI; + #elif defined(STM32N6) + ETH->DMA_CH[RX_DMA_CH].DMACSR = ETH_DMACxSR_RI; #else ETH->DMASR = ETH_DMASR_RS; #endif for (;;) { - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) eth_dma_rx_descr_t *rx_descr_l = ð_dma.rx_descr[eth_dma.rx_descr_idx]; if (rx_descr_l->rdes3 & (1 << RX_DESCR_3_OWN_Pos)) { // No more RX descriptors ready to read @@ -696,13 +842,13 @@ void ETH_IRQHandler(void) { #endif // Get RX buffer containing new frame - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) size_t len = (rx_descr_l->rdes3 & RX_DESCR_3_PL_Msk); #else size_t len = (rx_descr->rdes0 & RX_DESCR_0_FL_Msk) >> RX_DESCR_0_FL_Pos; #endif len -= 4; // discard CRC at end - #if defined(STM32H5) || defined(STM32H7) + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) uint8_t *buf = ð_dma.rx_buf[eth_dma.rx_descr_idx * RX_BUF_SIZE]; #else uint8_t *buf = (uint8_t *)rx_descr->rdes2; @@ -875,6 +1021,8 @@ void eth_low_power_mode(eth_t *self, bool enable) { // Enable eth clock #if defined(STM32H7) __HAL_RCC_ETH1MAC_CLK_ENABLE(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_CLK_ENABLE(); #else __HAL_RCC_ETH_CLK_ENABLE(); #endif @@ -886,6 +1034,8 @@ void eth_low_power_mode(eth_t *self, bool enable) { // Disable eth clock. #if defined(STM32H7) __HAL_RCC_ETH1MAC_CLK_DISABLE(); + #elif defined(STM32N6) + __HAL_RCC_ETH1_CLK_DISABLE(); #else __HAL_RCC_ETH_CLK_DISABLE(); #endif diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 2d97adb1d40..6f7413694b0 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -318,11 +318,14 @@ static void risaf_init(void) { rimc_master.MasterCID = RIF_CID_1; rimc_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV; - HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_ADC12, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC1, &rimc_master); - HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC1, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_SDMMC2, &rimc_master); + HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_ETH1, &rimc_master); + + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_ADC12, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC1, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_SDMMC2, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_ETH1, RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); } #endif diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index a8ef8c34af8..7baaa677320 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -482,6 +482,11 @@ void SystemClock_Config(void) { LL_RCC_IC11_SetDivider(1); LL_RCC_IC11_Enable(); + // Configure IC12 at 100MHz for ETH1CLKSEL. + LL_RCC_IC12_SetSource(LL_RCC_ICCLKSOURCE_PLL1); + LL_RCC_IC12_SetDivider(8); + LL_RCC_IC12_Enable(); + // Configure IC14 at 100MHz for slower peripherals. LL_RCC_IC14_SetSource(LL_RCC_ICCLKSOURCE_PLL1); LL_RCC_IC14_SetDivider(8); From c8a818cf13747252349ffdee9cf1cb9ad6b3408a Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 31 Oct 2025 21:35:18 +1100 Subject: [PATCH 1482/2098] stm32/lwip_inc: Include HAL header to get MCU define. Otherwise the custom memory settings don't take place. Signed-off-by: Damien George --- ports/stm32/lwip_inc/lwipopts.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index ad1143845f7..aa6cf85874e 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -1,6 +1,8 @@ #ifndef MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H #define MICROPY_INCLUDED_STM32_LWIP_LWIPOPTS_H +#include STM32_HAL_H + #define LWIP_NETIF_EXT_STATUS_CALLBACK 1 #define LWIP_LOOPIF_MULTICAST 1 From 6c4b05d8325b518ec620208e3d1fec72a53b86cb Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Nov 2025 17:34:23 +1100 Subject: [PATCH 1483/2098] stm32/lwip_inc: Further increase N6 lwIP memory. This MCU has a lot of memory and gigabit ethernet, so it benefits from improved lwIP performance. Signed-off-by: Damien George --- ports/stm32/lwip_inc/lwipopts.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index aa6cf85874e..5711ba8944e 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -14,11 +14,12 @@ // Increase memory for lwIP to get better performance. #if defined(STM32N6) -#define MEM_SIZE (16 * 1024) +#define MEM_SIZE (64 * 1024) +#define PBUF_POOL_SIZE (32) #define TCP_MSS (1460) -#define TCP_WND (8 * TCP_MSS) -#define TCP_SND_BUF (8 * TCP_MSS) -#define MEMP_NUM_TCP_SEG (32) +#define TCP_WND (16 * TCP_MSS) +#define TCP_SND_BUF (16 * TCP_MSS) +#define MEMP_NUM_TCP_SEG (64) #endif // Include common lwIP configuration. From 15acd6d8f79a081bf9ae8a4b761b556011da26bb Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 29 Sep 2025 12:16:13 +1000 Subject: [PATCH 1484/2098] stm32/boards/NUCLEO_N657X0: Enable Ethernet. Signed-off-by: Damien George --- ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h | 11 +++++++++++ ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk | 3 +++ ports/stm32/boards/NUCLEO_N657X0/pins.csv | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h index ccc3fa051ff..90ea1eae3a8 100644 --- a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.h @@ -74,6 +74,17 @@ #define MICROPY_HW_USB_HS_IN_FS (1) #define MICROPY_HW_USB_MAIN_DEV (USB_PHY_HS_ID) +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_G11) +#define MICROPY_HW_ETH_MDIO (pin_F4) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_F7) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_F10) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_F14) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_F15) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_F11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_F12) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_F13) + /******************************************************************************/ // Bootloader configuration diff --git a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk index fa64cb17065..777f22e61ed 100644 --- a/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_N657X0/mpconfigboard.mk @@ -24,3 +24,6 @@ endif # MicroPython settings MICROPY_FLOAT_IMPL = double +MICROPY_PY_LWIP = 1 +MICROPY_PY_SSL = 1 +MICROPY_SSL_MBEDTLS = 1 diff --git a/ports/stm32/boards/NUCLEO_N657X0/pins.csv b/ports/stm32/boards/NUCLEO_N657X0/pins.csv index 033f0a552e0..a8a3f6c5ab4 100644 --- a/ports/stm32/boards/NUCLEO_N657X0/pins.csv +++ b/ports/stm32/boards/NUCLEO_N657X0/pins.csv @@ -44,6 +44,17 @@ A5,PG15 -SPI5_MISO,PG1 -SPI5_MOSI,PG2 +# ETH1 RMII +,PG11 +,PF4 +,PF7 +,PF10 +,PF11 +,PF12 +,PF13 +,PF14 +,PF15 + -BUTTON,PC13 LED_BLUE,PG8 LED_RED,PG10 From 769f5cf1a96a64454c837d3d201f8c06e44baaad Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 28 Oct 2025 17:19:25 +1100 Subject: [PATCH 1485/2098] stm32/eth_phy: Add support for 1000Mbit PHY. Signed-off-by: Damien George --- ports/stm32/eth_phy.c | 19 +++++++++++++++---- ports/stm32/eth_phy.h | 12 +++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/ports/stm32/eth_phy.c b/ports/stm32/eth_phy.c index 56cddba9c52..2435cc3e038 100644 --- a/ports/stm32/eth_phy.c +++ b/ports/stm32/eth_phy.c @@ -31,8 +31,9 @@ #if defined(MICROPY_HW_ETH_MDC) #define PHY_SCSR_LAN87XX (0x001f) -#define PHY_SCSR_LAN87XX_SPEED_Pos (2) -#define PHY_SCSR_LAN87XX_SPEED_Msk (7) +#define PHY_SCSR_LAN87XX_10M_Msk (0x0004) +#define PHY_SCSR_LAN87XX_100M_Msk (0x0008) +#define PHY_SCSR_LAN87XX_DUPLEX_Msk (0x0010) #define PHY_SCSR_DP838XX (0x0010) #define PHY_RECR_DP838XX (0x0015) @@ -41,8 +42,18 @@ int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr) { // Get the link mode & speed - int16_t scsr = eth_phy_read(phy_addr, PHY_SCSR_LAN87XX); - return (scsr >> PHY_SCSR_LAN87XX_SPEED_Pos) & PHY_SCSR_LAN87XX_SPEED_Msk; + uint16_t scsr = eth_phy_read(phy_addr, PHY_SCSR_LAN87XX); + int16_t status = 0; + if (scsr & PHY_SCSR_LAN87XX_10M_Msk) { + status |= PHY_SPEED_10HALF; + } + if (scsr & PHY_SCSR_LAN87XX_100M_Msk) { + status |= PHY_SPEED_100HALF; + } + if (scsr & PHY_SCSR_LAN87XX_DUPLEX_Msk) { + status |= PHY_DUPLEX; + } + return status; } int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr) { diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h index dccfb7951ad..1f2045082d0 100644 --- a/ports/stm32/eth_phy.h +++ b/ports/stm32/eth_phy.h @@ -50,11 +50,13 @@ #define PHY_ANAR_SPEED_100FULL (0x0100) #define PHY_ANAR_IEEE802_3 (0x0001) -#define PHY_SPEED_10HALF (1) -#define PHY_SPEED_10FULL (5) -#define PHY_SPEED_100HALF (2) -#define PHY_SPEED_100FULL (6) -#define PHY_DUPLEX (4) +#define PHY_SPEED_10HALF (0x01) +#define PHY_SPEED_100HALF (0x02) +#define PHY_SPEED_1000HALF (0x04) +#define PHY_DUPLEX (0x08) +#define PHY_SPEED_10FULL (PHY_DUPLEX | PHY_SPEED_10HALF) +#define PHY_SPEED_100FULL (PHY_DUPLEX | PHY_SPEED_100HALF) +#define PHY_SPEED_1000FULL (PHY_DUPLEX | PHY_SPEED_1000HALF) uint32_t eth_phy_read(uint32_t phy_addr, uint32_t reg); void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val); From 7713cdd8fe9e57832ccd4794313d84f3480438f5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 27 Oct 2025 14:48:33 +1100 Subject: [PATCH 1486/2098] stm32/eth_phy: Add support for RTL8211 ETH PHY. Signed-off-by: Damien George --- ports/stm32/eth.c | 2 ++ ports/stm32/eth.h | 3 ++- ports/stm32/eth_phy.c | 31 +++++++++++++++++++++++++++++++ ports/stm32/eth_phy.h | 1 + 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 0f8af5311bb..5f2478f082b 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -216,6 +216,8 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { self->phy_get_link_status = eth_phy_dp838xx_get_link_status; } else if (phy_type == ETH_PHY_LAN8720 || phy_type == ETH_PHY_LAN8742) { self->phy_get_link_status = eth_phy_lan87xx_get_link_status; + } else if (phy_type == ETH_PHY_RTL8211) { + self->phy_get_link_status = eth_phy_rtl8211_get_link_status; } else { return -1; } diff --git a/ports/stm32/eth.h b/ports/stm32/eth.h index 5647449690c..6556f4a7c10 100644 --- a/ports/stm32/eth.h +++ b/ports/stm32/eth.h @@ -30,7 +30,8 @@ enum { ETH_PHY_LAN8742 = 0, ETH_PHY_LAN8720, ETH_PHY_DP83848, - ETH_PHY_DP83825 + ETH_PHY_DP83825, + ETH_PHY_RTL8211 }; typedef struct _eth_t eth_t; diff --git a/ports/stm32/eth_phy.c b/ports/stm32/eth_phy.c index 2435cc3e038..5b4673cc778 100644 --- a/ports/stm32/eth_phy.c +++ b/ports/stm32/eth_phy.c @@ -40,6 +40,14 @@ #define PHY_SCSR_DP838XX_DUPLEX_Msk (4) #define PHY_SCSR_DP838XX_10M_Msk (2) +#define PHY_RTL8211_DEFAULT_PAGE (0xa42) +#define PHY_RTL8211_PAGSR_ADDR (0x1f) +#define PHY_RTL8211_PHYSR_PAGE (0xa43) +#define PHY_RTL8211_PHYSR_ADDR (0x1a) +#define PHY_RTL8211_PHYSR_SPEED_Pos (4) +#define PHY_RTL8211_PHYSR_SPEED_Msk (3 << PHY_RTL8211_PHYSR_SPEED_Pos) +#define PHY_RTL8211_PHYSR_DUPLEX_Msk (0x0008) + int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr) { // Get the link mode & speed uint16_t scsr = eth_phy_read(phy_addr, PHY_SCSR_LAN87XX); @@ -67,4 +75,27 @@ int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr) { return scsr; } +int16_t eth_phy_rtl8211_get_link_status(uint32_t phy_addr) { + // Get the link mode & speed + eth_phy_write(phy_addr, PHY_RTL8211_PAGSR_ADDR, PHY_RTL8211_PHYSR_PAGE); + int16_t physr = eth_phy_read(phy_addr, PHY_RTL8211_PHYSR_ADDR); + eth_phy_write(phy_addr, PHY_RTL8211_PAGSR_ADDR, PHY_RTL8211_DEFAULT_PAGE); + int16_t status = 0; + switch ((physr & PHY_RTL8211_PHYSR_SPEED_Msk) >> PHY_RTL8211_PHYSR_SPEED_Pos) { + case 0: + status |= PHY_SPEED_10HALF; + break; + case 1: + status |= PHY_SPEED_100HALF; + break; + case 2: + status |= PHY_SPEED_1000HALF; + break; + } + if (physr & PHY_RTL8211_PHYSR_DUPLEX_Msk) { + status |= PHY_DUPLEX; + } + return status; +} + #endif diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h index 1f2045082d0..79f0d7003b7 100644 --- a/ports/stm32/eth_phy.h +++ b/ports/stm32/eth_phy.h @@ -63,6 +63,7 @@ void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val); int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr); int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr); +int16_t eth_phy_rtl8211_get_link_status(uint32_t phy_addr); #endif From 0acac07a16dd900eead199e506d6dc001692e88d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 3 Nov 2025 23:53:45 +1100 Subject: [PATCH 1487/2098] stm32/eth_phy: Move PHY initialization to a dedicated function. Signed-off-by: Damien George --- ports/stm32/eth.c | 7 ++++--- ports/stm32/eth_phy.c | 6 ++++++ ports/stm32/eth_phy.h | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 5f2478f082b..c2155399ebd 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -142,6 +142,7 @@ typedef struct _eth_t { struct netif netif; struct dhcp dhcp_struct; uint32_t phy_addr; + void (*phy_init)(uint32_t phy_addr); int16_t (*phy_get_link_status)(uint32_t phy_addr); } eth_t; @@ -212,6 +213,7 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { mp_hal_get_mac(mac_idx, &self->netif.hwaddr[0]); self->netif.hwaddr_len = 6; self->phy_addr = phy_addr; + self->phy_init = eth_phy_generic_init; if (phy_type == ETH_PHY_DP83825 || phy_type == ETH_PHY_DP83848) { self->phy_get_link_status = eth_phy_dp838xx_get_link_status; } else if (phy_type == ETH_PHY_LAN8720 || phy_type == ETH_PHY_LAN8742) { @@ -417,9 +419,8 @@ static int eth_mac_init(eth_t *self) { ETH->DMA_CH[TX_DMA_CH].DMACCR &= ~(ETH_DMACxCR_DSL_Msk); #endif - // Reset the PHY - eth_phy_write(self->phy_addr, PHY_BCR, PHY_BCR_SOFT_RESET); - mp_hal_delay_ms(50); + // Reset and initialize the PHY. + self->phy_init(self->phy_addr); // Wait for the PHY link to be established int phy_state = 0; diff --git a/ports/stm32/eth_phy.c b/ports/stm32/eth_phy.c index 5b4673cc778..fa4364a9104 100644 --- a/ports/stm32/eth_phy.c +++ b/ports/stm32/eth_phy.c @@ -48,6 +48,12 @@ #define PHY_RTL8211_PHYSR_SPEED_Msk (3 << PHY_RTL8211_PHYSR_SPEED_Pos) #define PHY_RTL8211_PHYSR_DUPLEX_Msk (0x0008) +void eth_phy_generic_init(uint32_t phy_addr) { + // Reset the PHY. + eth_phy_write(phy_addr, PHY_BCR, PHY_BCR_SOFT_RESET); + mp_hal_delay_ms(50); +} + int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr) { // Get the link mode & speed uint16_t scsr = eth_phy_read(phy_addr, PHY_SCSR_LAN87XX); diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h index 79f0d7003b7..c369de1f433 100644 --- a/ports/stm32/eth_phy.h +++ b/ports/stm32/eth_phy.h @@ -61,6 +61,7 @@ uint32_t eth_phy_read(uint32_t phy_addr, uint32_t reg); void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val); +void eth_phy_generic_init(uint32_t phy_addr); int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr); int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr); int16_t eth_phy_rtl8211_get_link_status(uint32_t phy_addr); From d1e993f872293fc74b72d0d189c9610b66c61d21 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 3 Nov 2025 23:54:03 +1100 Subject: [PATCH 1488/2098] stm32/eth_phy: Add support for RTL8211 inititialization. This gets the status indicator working on the RTL8211. Signed-off-by: Damien George --- ports/stm32/eth.c | 1 + ports/stm32/eth_phy.c | 12 ++++++++++++ ports/stm32/eth_phy.h | 1 + 3 files changed, 14 insertions(+) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index c2155399ebd..d1ded64877f 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -219,6 +219,7 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { } else if (phy_type == ETH_PHY_LAN8720 || phy_type == ETH_PHY_LAN8742) { self->phy_get_link_status = eth_phy_lan87xx_get_link_status; } else if (phy_type == ETH_PHY_RTL8211) { + self->phy_init = eth_phy_rtl8211_init; self->phy_get_link_status = eth_phy_rtl8211_get_link_status; } else { return -1; diff --git a/ports/stm32/eth_phy.c b/ports/stm32/eth_phy.c index fa4364a9104..cdc632f26fe 100644 --- a/ports/stm32/eth_phy.c +++ b/ports/stm32/eth_phy.c @@ -47,6 +47,8 @@ #define PHY_RTL8211_PHYSR_SPEED_Pos (4) #define PHY_RTL8211_PHYSR_SPEED_Msk (3 << PHY_RTL8211_PHYSR_SPEED_Pos) #define PHY_RTL8211_PHYSR_DUPLEX_Msk (0x0008) +#define PHY_RTL8211_LCR_PAGE (0xd04) +#define PHY_RTL8211_LCR_ADDR (0x10) void eth_phy_generic_init(uint32_t phy_addr) { // Reset the PHY. @@ -81,6 +83,16 @@ int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr) { return scsr; } +void eth_phy_rtl8211_init(uint32_t phy_addr) { + // Perform generic PHY initialization. + eth_phy_generic_init(phy_addr); + + // Configure LED0 output to show 10/100/1000 link speed, and activity. + eth_phy_write(phy_addr, PHY_RTL8211_PAGSR_ADDR, PHY_RTL8211_LCR_PAGE); + eth_phy_write(phy_addr, PHY_RTL8211_LCR_ADDR, 0x001b); + eth_phy_write(phy_addr, PHY_RTL8211_PAGSR_ADDR, PHY_RTL8211_DEFAULT_PAGE); +} + int16_t eth_phy_rtl8211_get_link_status(uint32_t phy_addr) { // Get the link mode & speed eth_phy_write(phy_addr, PHY_RTL8211_PAGSR_ADDR, PHY_RTL8211_PHYSR_PAGE); diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h index c369de1f433..7d4bf4c4689 100644 --- a/ports/stm32/eth_phy.h +++ b/ports/stm32/eth_phy.h @@ -64,6 +64,7 @@ void eth_phy_write(uint32_t phy_addr, uint32_t reg, uint32_t val); void eth_phy_generic_init(uint32_t phy_addr); int16_t eth_phy_lan87xx_get_link_status(uint32_t phy_addr); int16_t eth_phy_dp838xx_get_link_status(uint32_t phy_addr); +void eth_phy_rtl8211_init(uint32_t phy_addr); int16_t eth_phy_rtl8211_get_link_status(uint32_t phy_addr); #endif From 45956a951651481f9eb062e843f4de6d4dc14d12 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 27 Oct 2025 14:49:15 +1100 Subject: [PATCH 1489/2098] stm32/eth: Add support for gigabit RGMII peripheral interface. Signed-off-by: Damien George --- ports/stm32/boards/stm32n657_af.csv | 20 +++++++++++------- ports/stm32/eth.c | 32 ++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv index d7af193992e..c02c658bd73 100644 --- a/ports/stm32/boards/stm32n657_af.csv +++ b/ports/stm32/boards/stm32n657_af.csv @@ -39,19 +39,25 @@ PortE,PE12, , , , PortE,PE13, , , , ,I2C4_SCL , , , , , , , , , , , , PortE,PE14, , , , ,I2C4_SDA , , , , , , , , , , , , PortE,PE15, , , , , ,SPI5_SCK , , , , , , , , , , , +PortF,PF0 , , , , , , , , , , , , ,ETH1_RGMII_GTX_CLK , , , , +PortF,PF2 , , , , , , , , , , , ,ETH1_RGMII_CLK125 , , , , , PortF,PF3 , , , , , , , ,USART2_RTS , , , , , , , , ,ADC1_INP16 PortF,PF4 , , , , , , , , , , , ,ETH1_MDIO , , , , , PortF,PF6 , , , , , , , ,USART2_RX , , , , , , , , , -PortF,PF7 , , , , , , , , , , , ,ETH1_RMII_REF_CLK , , , , , -PortF,PF10, , , , , , , , , , , ,ETH1_RMII_CRS_DV , , , , , -PortF,PF11, , , , , , , , , , , ,ETH1_RMII_TX_EN , , , , , -PortF,PF12, , , , , , , , , , , ,ETH1_RMII_TXD0 , , , , , -PortF,PF13, , , , , , , , , , , ,ETH1_RMII_TXD1 , , , , , -PortF,PF14, , , , , , , , , , , ,ETH1_RMII_RXD0 , , , , , -PortF,PF15, , , , , , , , , , , ,ETH1_RMII_RXD1 , , , , , +PortF,PF7 , , , , , , , , , , , ,ETH1_RMII_REF_CLK/ETH1_RGMII_RX_CLK , , , , , +PortF,PF8 , , , , , , , , , , , ,ETH1_RGMII_RXD2 , , , , , +PortF,PF9 , , , , , , , , , , , ,ETH1_RGMII_RXD3 , , , , , +PortF,PF10, , , , , , , , , , , ,ETH1_RMII_CRS_DV/ETH1_RGMII_RX_CTL , , , , , +PortF,PF11, , , , , , , , , , , ,ETH1_RMII_TX_EN/ETH1_RGMII_TX_CTL , , , , , +PortF,PF12, , , , , , , , , , , ,ETH1_RMII_TXD0/ETH1_RGMII_TXD0 , , , , , +PortF,PF13, , , , , , , , , , , ,ETH1_RMII_TXD1/ETH1_RGMII_TXD1 , , , , , +PortF,PF14, , , , , , , , , , , ,ETH1_RMII_RXD0/ETH1_RGMII_RXD0 , , , , , +PortF,PF15, , , , , , , , , , , ,ETH1_RMII_RXD1/ETH1_RGMII_RXD1 , , , , , PortG,PG0 , , ,TIM12_CH1 , , , , , , , , , , , , , , PortG,PG1 , , , , , ,SPI5_MISO , , , , , , , , , , , PortG,PG2 , , , , , ,SPI5_MOSI , , , , , , , , , , , +PortG,PG3 , , , , , , , , , , , ,ETH1_RGMII_TXD2 , , , , , +PortG,PG4 , , , , , , , , , , , ,ETH1_RGMII_TXD3 , , , , , PortG,PG5 , , , , , , , ,USART2_CTS , , , , , , , , , PortG,PG8 , , , , , , , , , , , ,SDMMC2_D1 , , , , , PortG,PG11, , , , , , , , , , , ,ETH1_MDC , , , , , diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index d1ded64877f..c4a3460e654 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -225,9 +225,12 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { return -1; } - // Configure GPIO + // Configure GPIO for management data. mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDC, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(MDC)); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDIO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(MDIO)); + + #if defined(MICROPY_HW_ETH_RMII_REF_CLK) + // Configure GPIO for RMII interface. mp_hal_pin_config_alt_static_speed(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_MEDIUM, STATIC_AF_ETH(RMII_REF_CLK)); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_CRS_DV, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_CRS_DV)); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_RXD0)); @@ -235,6 +238,22 @@ int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TX_EN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TX_EN)); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TXD0)); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RMII_TXD1)); + #else + // Configure GPIO for RGMII interface. + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_CLK125, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_CLK125)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_GTX_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_GTX_CLK)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_TXD0)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_TXD1)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_TXD2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_TXD2)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_TXD3, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_TXD3)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_TX_CTL, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_TX_CTL)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RX_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RX_CLK)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RXD0)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RXD1)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RXD2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RXD2)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RXD3, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RXD3)); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RGMII_RX_CTL, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH(RGMII_RX_CTL)); + #endif // Enable peripheral clock #if defined(STM32H5) @@ -290,14 +309,18 @@ static int eth_mac_init(eth_t *self) { LL_RCC_SetETHPTPClockSource(LL_RCC_ETH1PTP_CLKSOURCE_HCLK); // max 200MHz #endif - // Select RMII interface + // Select RMII or RGMII interface #if defined(STM32H5) __HAL_RCC_SBS_CLK_ENABLE(); SBS->PMCR = (SBS->PMCR & ~SBS_PMCR_ETH_SEL_PHY_Msk) | SBS_PMCR_ETH_SEL_PHY_2; #elif defined(STM32H7) SYSCFG->PMCR = (SYSCFG->PMCR & ~SYSCFG_PMCR_EPIS_SEL_Msk) | SYSCFG_PMCR_EPIS_SEL_2; #elif defined(STM32N6) + #if defined(MICROPY_HW_ETH_RGMII_CLK125) + LL_RCC_SetETHPHYInterface(LL_RCC_ETH1PHY_IF_RGMII); + #else LL_RCC_SetETHPHYInterface(LL_RCC_ETH1PHY_IF_RMII); + #endif #else __HAL_RCC_SYSCFG_CLK_ENABLE(); SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; @@ -602,11 +625,14 @@ static int eth_mac_init(eth_t *self) { #if defined(STM32N6) + if (!(phy_scsr & PHY_SPEED_1000HALF)) { + maccr |= ETH_MACCR_PS; + } + maccr |= ETH_MACCR_IPG_96BIT | ETH_MACCR_SARC_REPADDR0 | ETH_MACCR_IPC - | ETH_MACCR_PS | ETH_MACCR_BL_10 | ETH_MACCR_PRELEN_7; From 3202d57cfd9575182345e14d7d364d5e0e1a4967 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 4 Nov 2025 01:04:30 +1100 Subject: [PATCH 1490/2098] stm32/eth: Make TX and RX buffer sizes a multiple of 8. Needed by N6, and doesn't hurt for other MCUs. Signed-off-by: Damien George --- ports/stm32/eth.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index c4a3460e654..0af7d2ae01d 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -107,8 +107,9 @@ #define PHY_INIT_TIMEOUT_MS (10000) -#define RX_BUF_SIZE (1524) // includes 4-byte CRC at end -#define TX_BUF_SIZE (1524) +// These buffer sizes need to be a multiple of 8 (for STM32N6 at least). +#define RX_BUF_SIZE (1528) // includes 4-byte CRC at end +#define TX_BUF_SIZE (1528) #define RX_BUF_NUM (5) #define TX_BUF_NUM (5) @@ -130,11 +131,17 @@ typedef struct _eth_dma_tx_descr_t { typedef struct _eth_dma_t { eth_dma_rx_descr_t rx_descr[RX_BUF_NUM]; eth_dma_tx_descr_t tx_descr[TX_BUF_NUM]; - uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(4))); - uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(4))); + uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(8))); + uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(8))); size_t rx_descr_idx; size_t tx_descr_idx; - uint8_t padding[16384 - 15408]; + // Make sure the size of this struct is 16k, for the MPU. + uint8_t padding[16 * 1024 + - sizeof(eth_dma_rx_descr_t) * RX_BUF_NUM + - sizeof(eth_dma_tx_descr_t) * TX_BUF_NUM + - RX_BUF_NUM * RX_BUF_SIZE + - TX_BUF_NUM * TX_BUF_SIZE + - sizeof(size_t) * 2]; } eth_dma_t; typedef struct _eth_t { @@ -210,6 +217,8 @@ uint32_t eth_phy_read(uint32_t phy_addr, uint32_t reg) { } int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { + MP_STATIC_ASSERT(sizeof(eth_dma_t) == 16 * 1024); + mp_hal_get_mac(mac_idx, &self->netif.hwaddr[0]); self->netif.hwaddr_len = 6; self->phy_addr = phy_addr; From ba5711a011c84f5fab2c04a07424d9788aa36710 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 20 Nov 2025 12:12:57 +1100 Subject: [PATCH 1491/2098] stm32/eth: Move DMA TX/RX index variables to normal RAM. RAM shared with DMA needs caching disabled, which is slower for the MCU to access. So move these index variables (which are not shared with DMA) to normal RAM. Signed-off-by: Damien George --- ports/stm32/eth.c | 49 +++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 0af7d2ae01d..cece15d4572 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -133,15 +133,12 @@ typedef struct _eth_dma_t { eth_dma_tx_descr_t tx_descr[TX_BUF_NUM]; uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(8))); uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(8))); - size_t rx_descr_idx; - size_t tx_descr_idx; // Make sure the size of this struct is 16k, for the MPU. uint8_t padding[16 * 1024 - sizeof(eth_dma_rx_descr_t) * RX_BUF_NUM - sizeof(eth_dma_tx_descr_t) * TX_BUF_NUM - RX_BUF_NUM * RX_BUF_SIZE - - TX_BUF_NUM * TX_BUF_SIZE - - sizeof(size_t) * 2]; + - TX_BUF_NUM * TX_BUF_SIZE]; } eth_dma_t; typedef struct _eth_t { @@ -153,8 +150,14 @@ typedef struct _eth_t { int16_t (*phy_get_link_status)(uint32_t phy_addr); } eth_t; +// This struct contains RX and TX buffers shared with the DMA, and they may need +// to go in a special RAM section, or have MPU settings applied. static eth_dma_t eth_dma MICROPY_HW_ETH_DMA_ATTRIBUTE; +// These variables index the buffers in eth_dma and are not shared with DMA. +static size_t eth_dma_rx_descr_idx; +static size_t eth_dma_tx_descr_idx; + eth_t eth_instance; static void eth_mac_deinit(eth_t *self); @@ -554,7 +557,7 @@ static int eth_mac_init(eth_t *self) { #else ETH->DMARDLAR = (uint32_t)ð_dma.rx_descr[0]; #endif - eth_dma.rx_descr_idx = 0; + eth_dma_rx_descr_idx = 0; // Configure TX descriptor lists for (size_t i = 0; i < TX_BUF_NUM; ++i) { @@ -585,7 +588,7 @@ static int eth_mac_init(eth_t *self) { #else ETH->DMATDLAR = (uint32_t)ð_dma.tx_descr[0]; #endif - eth_dma.tx_descr_idx = 0; + eth_dma_tx_descr_idx = 0; // Configure DMA #if defined(STM32H5) || defined(STM32H7) @@ -730,7 +733,7 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { } // Wait for DMA to release the current TX descriptor (if it has it) - eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma_tx_descr_idx]; uint32_t t0 = mp_hal_ticks_ms(); for (;;) { #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) @@ -749,15 +752,15 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) // Update TX descriptor with length and buffer pointer - *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; + *buf = ð_dma.tx_buf[eth_dma_tx_descr_idx * TX_BUF_SIZE]; tx_descr->tdes2 = len & TX_DESCR_2_B1L_Msk; tx_descr->tdes0 = (uint32_t)*buf; #else // Update TX descriptor with length, buffer pointer and linked list pointer - *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; + *buf = ð_dma.tx_buf[eth_dma_tx_descr_idx * TX_BUF_SIZE]; tx_descr->tdes1 = len << TX_DESCR_1_TBS1_Pos; tx_descr->tdes2 = (uint32_t)*buf; - tx_descr->tdes3 = (uint32_t)ð_dma.tx_descr[(eth_dma.tx_descr_idx + 1) % TX_BUF_NUM]; + tx_descr->tdes3 = (uint32_t)ð_dma.tx_descr[(eth_dma_tx_descr_idx + 1) % TX_BUF_NUM]; #endif return 0; @@ -765,8 +768,8 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { static int eth_tx_buf_send(void) { // Get TX descriptor and move to next one - eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; - eth_dma.tx_descr_idx = (eth_dma.tx_descr_idx + 1) % TX_BUF_NUM; + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma_tx_descr_idx]; + eth_dma_tx_descr_idx = (eth_dma_tx_descr_idx + 1) % TX_BUF_NUM; // Schedule to send next outgoing frame #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) @@ -792,12 +795,12 @@ static int eth_tx_buf_send(void) { if (ETH->DMACSR & ETH_DMACSR_TBU) { ETH->DMACSR = ETH_DMACSR_TBU; } - ETH->DMACTDTPR = (uint32_t)ð_dma.tx_descr[eth_dma.tx_descr_idx]; + ETH->DMACTDTPR = (uint32_t)ð_dma.tx_descr[eth_dma_tx_descr_idx]; #elif defined(STM32N6) if (ETH->DMA_CH[TX_DMA_CH].DMACSR & ETH_DMACxSR_TBU) { ETH->DMA_CH[TX_DMA_CH].DMACSR = ETH_DMACxSR_TBU; } - ETH->DMA_CH[TX_DMA_CH].DMACTXDTPR = (uint32_t)ð_dma.tx_descr[eth_dma.tx_descr_idx]; + ETH->DMA_CH[TX_DMA_CH].DMACTXDTPR = (uint32_t)ð_dma.tx_descr[eth_dma_tx_descr_idx]; #else if (ETH->DMASR & ETH_DMASR_TBUS) { ETH->DMASR = ETH_DMASR_TBUS; @@ -810,9 +813,9 @@ static int eth_tx_buf_send(void) { static void eth_dma_rx_free(void) { // Get RX descriptor, RX buffer and move to next one - eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma.rx_descr_idx]; - uint8_t *buf = ð_dma.rx_buf[eth_dma.rx_descr_idx * RX_BUF_SIZE]; - eth_dma.rx_descr_idx = (eth_dma.rx_descr_idx + 1) % RX_BUF_NUM; + eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma_rx_descr_idx]; + uint8_t *buf = ð_dma.rx_buf[eth_dma_rx_descr_idx * RX_BUF_SIZE]; + eth_dma_rx_descr_idx = (eth_dma_rx_descr_idx + 1) % RX_BUF_NUM; // Schedule to get next incoming frame #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) @@ -826,16 +829,16 @@ static void eth_dma_rx_free(void) { | RX_BUF_SIZE << RX_DESCR_1_RBS1_Pos // maximum buffer length ; rx_descr->rdes2 = (uint32_t)buf; - rx_descr->rdes3 = (uint32_t)ð_dma.rx_descr[eth_dma.rx_descr_idx]; + rx_descr->rdes3 = (uint32_t)ð_dma.rx_descr[eth_dma_rx_descr_idx]; rx_descr->rdes0 = 1 << RX_DESCR_0_OWN_Pos; // owned by DMA #endif // Notify ETH DMA that there is a new RX descriptor available __DMB(); #if defined(STM32H5) || defined(STM32H7) - ETH->DMACRDTPR = (uint32_t)&rx_descr[eth_dma.rx_descr_idx]; + ETH->DMACRDTPR = (uint32_t)&rx_descr[eth_dma_rx_descr_idx]; #elif defined(STM32N6) - ETH->DMA_CH[RX_DMA_CH].DMACRXDTPR = (uint32_t)&rx_descr[eth_dma.rx_descr_idx]; + ETH->DMA_CH[RX_DMA_CH].DMACRXDTPR = (uint32_t)&rx_descr[eth_dma_rx_descr_idx]; #else ETH->DMARPDR = 0; #endif @@ -867,13 +870,13 @@ void ETH_IRQHandler(void) { #endif for (;;) { #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) - eth_dma_rx_descr_t *rx_descr_l = ð_dma.rx_descr[eth_dma.rx_descr_idx]; + eth_dma_rx_descr_t *rx_descr_l = ð_dma.rx_descr[eth_dma_rx_descr_idx]; if (rx_descr_l->rdes3 & (1 << RX_DESCR_3_OWN_Pos)) { // No more RX descriptors ready to read break; } #else - eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma.rx_descr_idx]; + eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma_rx_descr_idx]; if (rx_descr->rdes0 & (1 << RX_DESCR_0_OWN_Pos)) { // No more RX descriptors ready to read break; @@ -888,7 +891,7 @@ void ETH_IRQHandler(void) { #endif len -= 4; // discard CRC at end #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) - uint8_t *buf = ð_dma.rx_buf[eth_dma.rx_descr_idx * RX_BUF_SIZE]; + uint8_t *buf = ð_dma.rx_buf[eth_dma_rx_descr_idx * RX_BUF_SIZE]; #else uint8_t *buf = (uint8_t *)rx_descr->rdes2; #endif From f5a65b39944c85955ecfeca086cce8d27c2c8551 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Nov 2025 17:37:09 +1100 Subject: [PATCH 1492/2098] stm32/eth: Don't pad eth_dma_t struct on H5 and N6 MCUs. It's not necessary (and wastes RAM), the MPU can handle regions of arbitrary size. Signed-off-by: Damien George --- ports/stm32/eth.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index cece15d4572..81faca5cb47 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -133,12 +133,14 @@ typedef struct _eth_dma_t { eth_dma_tx_descr_t tx_descr[TX_BUF_NUM]; uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(8))); uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(8))); + #if !defined(STM32H5) && !defined(STM32N6) // Make sure the size of this struct is 16k, for the MPU. uint8_t padding[16 * 1024 - sizeof(eth_dma_rx_descr_t) * RX_BUF_NUM - sizeof(eth_dma_tx_descr_t) * TX_BUF_NUM - RX_BUF_NUM * RX_BUF_SIZE - TX_BUF_NUM * TX_BUF_SIZE]; + #endif } eth_dma_t; typedef struct _eth_t { @@ -220,8 +222,6 @@ uint32_t eth_phy_read(uint32_t phy_addr, uint32_t reg) { } int eth_init(eth_t *self, int mac_idx, uint32_t phy_addr, int phy_type) { - MP_STATIC_ASSERT(sizeof(eth_dma_t) == 16 * 1024); - mp_hal_get_mac(mac_idx, &self->netif.hwaddr[0]); self->netif.hwaddr_len = 6; self->phy_addr = phy_addr; @@ -295,8 +295,9 @@ static int eth_mac_init(eth_t *self) { // Configure MPU uint32_t irq_state = mpu_config_start(); #if defined(STM32H5) || defined(STM32N6) - mpu_config_region(MPU_REGION_ETH, (uint32_t)ð_dma, MPU_CONFIG_ETH(16 * 1024)); + mpu_config_region(MPU_REGION_ETH, (uint32_t)ð_dma, MPU_CONFIG_ETH(sizeof(eth_dma_t))); #else + MP_STATIC_ASSERT(sizeof(eth_dma_t) == 16 * 1024); mpu_config_region(MPU_REGION_ETH, (uint32_t)ð_dma, MPU_CONFIG_ETH(MPU_REGION_SIZE_16KB)); #endif mpu_config_end(irq_state); From 9f87b7914294ede82c4e0f35621624a26bf9923c Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Nov 2025 17:37:09 +1100 Subject: [PATCH 1493/2098] stm32/eth: Implement zero-copy of lwIP pbufs for TX path. This option (currently only enabled for N6) allows the TX path to hold on to a pbuf reference while the DMA accesses the pbuf's memory directly, instead of copying the entire pbuf data into the internal buffers. This is necessary to achieve gigabit speeds on the N6, although actually achieving that speed requires higher up parts of the stack to be efficient as well. Signed-off-by: Damien George --- ports/stm32/eth.c | 178 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 154 insertions(+), 24 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 81faca5cb47..60f2a23deca 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -63,6 +63,7 @@ #define TX_DESCR_3_FD_Pos (29) #define TX_DESCR_3_LD_Pos (28) #define TX_DESCR_3_CIC_Pos (16) +#define TX_DESCR_2_IOC_Pos (31) #define TX_DESCR_2_B1L_Pos (0) #define TX_DESCR_2_B1L_Msk (0x3fff << TX_DESCR_2_B1L_Pos) #elif defined(STM32H7) @@ -111,8 +112,17 @@ #define RX_BUF_SIZE (1528) // includes 4-byte CRC at end #define TX_BUF_SIZE (1528) +#if defined(MICROPY_HW_ETH_RMII_REF_CLK) +// RMII in use. #define RX_BUF_NUM (5) #define TX_BUF_NUM (5) +#define USE_PBUF_REF_FOR_TX (0) +#else +// RGMII in use, so increase number of buffers and use pbuf zero copy if possible. +#define RX_BUF_NUM (16) +#define TX_BUF_NUM (16) +#define USE_PBUF_REF_FOR_TX (1) +#endif #if defined(STM32N6) // The N6 has two DMA channels, so use one for RX and one for TX. @@ -132,7 +142,9 @@ typedef struct _eth_dma_t { eth_dma_rx_descr_t rx_descr[RX_BUF_NUM]; eth_dma_tx_descr_t tx_descr[TX_BUF_NUM]; uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(8))); + #if !USE_PBUF_REF_FOR_TX uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(8))); + #endif #if !defined(STM32H5) && !defined(STM32N6) // Make sure the size of this struct is 16k, for the MPU. uint8_t padding[16 * 1024 @@ -156,6 +168,11 @@ typedef struct _eth_t { // to go in a special RAM section, or have MPU settings applied. static eth_dma_t eth_dma MICROPY_HW_ETH_DMA_ATTRIBUTE; +#if USE_PBUF_REF_FOR_TX +// This array holds lwIP pbufs that are currently in use by the DMA. +static struct pbuf *eth_dma_pbuf[TX_BUF_NUM]; +#endif + // These variables index the buffers in eth_dma and are not shared with DMA. static size_t eth_dma_rx_descr_idx; static size_t eth_dma_tx_descr_idx; @@ -521,6 +538,16 @@ static int eth_mac_init(eth_t *self) { ETH_DMACxIER_NIE // enable normal interrupts | ETH_DMACxIER_RIE // enable RX interrupt ; + #if USE_PBUF_REF_FOR_TX + #if RX_DMA_CH == TX_DMA_CH + ETH->DMA_CH[TX_DMA_CH].DMACIER |= ETH_DMACxIER_TIE; // enable TX interrupt + #else + ETH->DMA_CH[TX_DMA_CH].DMACIER = + ETH_DMACxIER_NIE // enable normal interrupts + | ETH_DMACxIER_TIE // enable TX interrupt + ; + #endif + #endif #else ETH->DMAIER = ETH_DMAIER_NISE // enable normal interrupts @@ -565,7 +592,7 @@ static int eth_mac_init(eth_t *self) { #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) eth_dma.tx_descr[i].tdes0 = 0; eth_dma.tx_descr[i].tdes1 = 0; - eth_dma.tx_descr[i].tdes2 = TX_BUF_SIZE & TX_DESCR_2_B1L_Msk; + eth_dma.tx_descr[i].tdes2 = 0; eth_dma.tx_descr[i].tdes3 = 0; #else eth_dma.tx_descr[i].tdes0 = 1 << TX_DESCR_0_TCH_Pos; @@ -590,6 +617,11 @@ static int eth_mac_init(eth_t *self) { ETH->DMATDLAR = (uint32_t)ð_dma.tx_descr[0]; #endif eth_dma_tx_descr_idx = 0; + #if USE_PBUF_REF_FOR_TX + for (int i = 0; i < TX_BUF_NUM; ++i) { + eth_dma_pbuf[i] = NULL; + } + #endif // Configure DMA #if defined(STM32H5) || defined(STM32H7) @@ -728,7 +760,9 @@ static void eth_mac_deinit(eth_t *self) { #endif } -static int eth_tx_buf_get(size_t len, uint8_t **buf) { +#if !USE_PBUF_REF_FOR_TX + +int eth_tx_buf_get(size_t len, uint8_t **buf) { if (len > TX_BUF_SIZE) { return -MP_EINVAL; } @@ -767,28 +801,51 @@ static int eth_tx_buf_get(size_t len, uint8_t **buf) { return 0; } -static int eth_tx_buf_send(void) { - // Get TX descriptor and move to next one - eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma_tx_descr_idx]; - eth_dma_tx_descr_idx = (eth_dma_tx_descr_idx + 1) % TX_BUF_NUM; +#else - // Schedule to send next outgoing frame - #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) - tx_descr->tdes3 = - 1 << TX_DESCR_3_OWN_Pos // owned by DMA - | 1 << TX_DESCR_3_LD_Pos // last segment - | 1 << TX_DESCR_3_FD_Pos // first segment - | 3 << TX_DESCR_3_CIC_Pos // enable all checksums inserted by hardware - ; - #else - tx_descr->tdes0 = - 1 << TX_DESCR_0_OWN_Pos // owned by DMA - | 1 << TX_DESCR_0_LS_Pos // last segment - | 1 << TX_DESCR_0_FS_Pos // first segment - | 3 << TX_DESCR_0_CIC_Pos // enable all checksums inserted by hardware - | 1 << TX_DESCR_0_TCH_Pos // TX descriptor is chained - ; - #endif +int eth_tx_buf_get_ref(size_t len, uint8_t *buf, unsigned int idx) { + // Wait for DMA to release the current TX descriptor (if it has it). + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[(eth_dma_tx_descr_idx + idx) % TX_BUF_NUM]; + uint32_t t0 = mp_hal_ticks_ms(); + while (tx_descr->tdes3 & (1 << TX_DESCR_3_OWN_Pos)) { + if (mp_hal_ticks_ms() - t0 > 1000) { + return -MP_ETIMEDOUT; + } + } + + MP_HAL_CLEAN_DCACHE(buf, len); + tx_descr->tdes2 = (len & TX_DESCR_2_B1L_Msk) | (1 << TX_DESCR_2_IOC_Pos); + tx_descr->tdes0 = (uint32_t)buf; + + return 0; +} + +#endif + +static int eth_tx_buf_send(unsigned int num_segments) { + for (unsigned int segment = 0; segment < num_segments; ++segment) { + // Get TX descriptor and move to next one + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma_tx_descr_idx]; + eth_dma_tx_descr_idx = (eth_dma_tx_descr_idx + 1) % TX_BUF_NUM; + + // Schedule to send next outgoing frame + #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) + tx_descr->tdes3 = + 1 << TX_DESCR_3_OWN_Pos // owned by DMA + | (segment == num_segments - 1) << TX_DESCR_3_LD_Pos // last segment + | (segment == 0) << TX_DESCR_3_FD_Pos // first segment + | 3 << TX_DESCR_3_CIC_Pos // enable all checksums inserted by hardware + ; + #else + tx_descr->tdes0 = + 1 << TX_DESCR_0_OWN_Pos // owned by DMA + | (segment == num_segments - 1) << TX_DESCR_0_LS_Pos // last segment + | (segment == 0) << TX_DESCR_0_FS_Pos // first segment + | 3 << TX_DESCR_0_CIC_Pos // enable all checksums inserted by hardware + | 1 << TX_DESCR_0_TCH_Pos // TX descriptor is chained + ; + #endif + } // Notify ETH DMA that there is a new TX descriptor for sending __DMB(); @@ -902,6 +959,28 @@ void ETH_IRQHandler(void) { eth_dma_rx_free(); } } + + #if USE_PBUF_REF_FOR_TX + #if RX_DMA_CH != TX_DMA_CH + sr = ETH->DMA_CH[TX_DMA_CH].DMACSR; + ETH->DMA_CH[TX_DMA_CH].DMACSR = ETH_DMACxSR_NIS; + #endif + uint32_t tx_interrupt = sr & ETH_DMACxSR_TI; + if (tx_interrupt) { + ETH->DMA_CH[TX_DMA_CH].DMACSR = ETH_DMACxSR_TI; + for (int i = 0; i < TX_BUF_NUM; ++i) { + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[i]; + if (!(tx_descr->tdes3 & (1 << TX_DESCR_3_OWN_Pos))) { + // DMA does not own it + if (eth_dma_pbuf[i] != NULL) { + // release pbuf + pbuf_free(eth_dma_pbuf[i]); + eth_dma_pbuf[i] = NULL; + } + } + } + } + #endif } /*******************************************************************************/ @@ -938,13 +1017,64 @@ static err_t eth_netif_output(struct netif *netif, struct pbuf *p) { LINK_STATS_INC(link.xmit); eth_trace(netif->state, (size_t)-1, p, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE); + #if USE_PBUF_REF_FOR_TX + + // Work out how many segments the pbuf has, and if it needs a copy made. + bool made_pbuf_copy = false; + unsigned int num_segments = 0; + for (struct pbuf *pb = p; pb != NULL; pb = pb->next) { + if (PBUF_NEEDS_COPY(pb)) { + // Note: this path is called for large UDP packets that are fragmented, + // because the fragments use PBUF_REF to divide up the original data. + p = pbuf_clone(PBUF_RAW, PBUF_RAM, p); + made_pbuf_copy = true; + num_segments = 1; + break; + } + ++num_segments; + } + + // Allocate TX buffer slots. + unsigned int idx = 0; + for (struct pbuf *pb = p; pb != NULL; pb = pb->next) { + int ret = eth_tx_buf_get_ref(pb->len, pb->payload, idx++); + if (ret != 0) { + if (made_pbuf_copy) { + pbuf_free(p); + } + return ERR_BUF; + } + } + + // Take references to pbufs + idx = 0; + for (struct pbuf *pb = p; pb != NULL; pb = pb->next) { + unsigned int tx_idx = (eth_dma_tx_descr_idx + idx) % TX_BUF_NUM; + if (eth_dma_pbuf[tx_idx] != NULL) { + pbuf_free(eth_dma_pbuf[tx_idx]); + } + if (!made_pbuf_copy) { + pbuf_ref(pb); + } + eth_dma_pbuf[tx_idx] = pb; + ++idx; + } + + // Start the transmission. + int ret = eth_tx_buf_send(num_segments); + + #else + + // Allocate TX slot, copy the pbuf, and start the transmission. uint8_t *buf; int ret = eth_tx_buf_get(p->tot_len, &buf); if (ret == 0) { pbuf_copy_partial(p, buf, p->tot_len, 0); - ret = eth_tx_buf_send(); + ret = eth_tx_buf_send(1); } + #endif + return ret ? ERR_BUF : ERR_OK; } From 5bd684081e5bfa1c449cb14d224e1f9f07097e2d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 27 Oct 2025 14:47:59 +1100 Subject: [PATCH 1494/2098] stm32/network_lan: Allow a board to configure the default LAN PHY. Signed-off-by: Damien George --- ports/stm32/network_lan.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/stm32/network_lan.c b/ports/stm32/network_lan.c index 0ef33e29777..ea03329add4 100644 --- a/ports/stm32/network_lan.c +++ b/ports/stm32/network_lan.c @@ -33,6 +33,11 @@ #include "lwip/netif.h" +// A board can customize the default PHY by defining this setting. +#ifndef NETWORK_LAN_PHY +#define NETWORK_LAN_PHY ETH_PHY_LAN8742 +#endif + typedef struct _network_lan_obj_t { mp_obj_base_t base; eth_t *eth; @@ -57,7 +62,7 @@ static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, s enum { ARG_phy_addr, ARG_phy_type}; static const mp_arg_t allowed_args[] = { { MP_QSTR_phy_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = ETH_PHY_LAN8742} }, + { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NETWORK_LAN_PHY} }, }; // Parse args. mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; From 53189f9b92cfc7aa421af6a66ae3fc61f42b3219 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 22 Oct 2025 00:17:30 +1100 Subject: [PATCH 1495/2098] stm32/boards/OPENMV_N6: Enable RGMII Ethernet. Signed-off-by: Damien George --- ports/stm32/boards/OPENMV_N6/mpconfigboard.h | 18 ++++++++++++++++++ ports/stm32/boards/OPENMV_N6/pins.csv | 13 +++++++++++++ 2 files changed, 31 insertions(+) diff --git a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h index 6e6bb43572f..0d63ff22478 100644 --- a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h +++ b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h @@ -126,6 +126,24 @@ #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_MSC_HID (MICROPY_HW_USB_PID) +// Ethernet via RGMII +#define NETWORK_LAN_PHY (ETH_PHY_RTL8211) +#define MICROPY_HW_ETH_MDC (pin_D1) +#define MICROPY_HW_ETH_MDIO (pin_D12) +#define MICROPY_HW_ETH_RGMII_CLK125 (pin_F2) +#define MICROPY_HW_ETH_RGMII_GTX_CLK (pin_F0) +#define MICROPY_HW_ETH_RGMII_TXD0 (pin_F12) +#define MICROPY_HW_ETH_RGMII_TXD1 (pin_F13) +#define MICROPY_HW_ETH_RGMII_TXD2 (pin_G3) +#define MICROPY_HW_ETH_RGMII_TXD3 (pin_G4) +#define MICROPY_HW_ETH_RGMII_TX_CTL (pin_F11) +#define MICROPY_HW_ETH_RGMII_RX_CLK (pin_F7) +#define MICROPY_HW_ETH_RGMII_RXD0 (pin_F14) +#define MICROPY_HW_ETH_RGMII_RXD1 (pin_F15) +#define MICROPY_HW_ETH_RGMII_RXD2 (pin_F8) +#define MICROPY_HW_ETH_RGMII_RXD3 (pin_F9) +#define MICROPY_HW_ETH_RGMII_RX_CTL (pin_F10) + // Murata 1YN configuration #define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/w43439_sdio_1yn_7_95_59_combined.h" #define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1yn.h" diff --git a/ports/stm32/boards/OPENMV_N6/pins.csv b/ports/stm32/boards/OPENMV_N6/pins.csv index 52f6c92496d..2ca075a154a 100644 --- a/ports/stm32/boards/OPENMV_N6/pins.csv +++ b/ports/stm32/boards/OPENMV_N6/pins.csv @@ -95,7 +95,20 @@ P16,PE12 I2C4_SCL,PE13 I2C4_SDA,PE14 ,PE15 +,PF0 +,PF2 +,PF7 +,PF8 +,PF9 +,PF10 +,PF11 +,PF12 +,PF13 +,PF14 +,PF15 P6,PG0 +,PG3 +,PG4 P9,PG12 P7,PG13 BAT_ADC,PG15 From e74367684e3642441edccca1b41b061c06587cf0 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Sun, 6 Apr 2025 18:31:48 +0200 Subject: [PATCH 1496/2098] zephyr: Introduce Zephyr native filesystem VFS interface. This allows using Zephyr's supported VFS and interface it to MicroPython's VFS. That makes the MicroPython-side filesystem drivers unecessary, eg you can disable LFS2 in MicroPython but still do LFS2 because it's using Zephyr's. An advantage of this is that the filesystem can be shared between Zephyr C code and MicroPython. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 1 + ports/zephyr/README.md | 44 ++ ports/zephyr/modzephyr.c | 3 + ports/zephyr/modzephyr.h | 4 + ports/zephyr/mpconfigport.h | 5 +- ports/zephyr/zephyr_filesystem.c | 782 +++++++++++++++++++++++++++++++ 6 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 ports/zephyr/zephyr_filesystem.c diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index e7a55d804ae..27229d21b9f 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -55,6 +55,7 @@ set(MICROPY_SOURCE_PORT uart_core.c zephyr_device.c zephyr_storage.c + zephyr_filesystem.c mpthreadport.c ) list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index eedcdcb58b3..f3fcd8f70d3 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -208,3 +208,47 @@ run the following after you built an image with the previous command: $ west build -t run +File Systems +------------ + +The Zephyr Micropython port provides 2 options for handling filesystems on the device: +The first is the Micropython filesystem management, which uses Micropython's filesystem code and +relies on zephyr's FlashArea API, this is enabled by default when +`CONFIG_FLASH` and `CONFIG_FLASH_MAP` are turned on. +The second option is using Zephyr's Filesystem management: +This relies on Zephyr's File System features and enables sharing access between zephyr and +micropython code. Several configuration options must be enabled: + + CONFIG_FLASH_MAP=y # Requirement for the file system subsystem + CONFIG_FILE_SYSTEM=y # Enables the file system subsystem + CONFIG_FILE_SYSTEM_LITTLEFS=y # Enables the littlefs support in zephyr + CONFIG_FILE_SYSTEM_MKFS=y # Enables the ability to create new file systems + +Then, a fstab must be added to the dts overlay, for example: + + + fstab { + compatible = "zephyr,fstab"; + lfs2: lfs2 { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<4096>; + cache-size=<4096>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; + +It is then possible to use the FS like a normal Micropython filesystem: + + import vfs, zephyr + zfs = zephyr.FileSystem(zephyr.FileSystem.fstab()[0]) + vfs.mount(zfs, "/zephyr") + +You may disable Micropython's File system code to save space: + + CONFIG_MICROPY_VFS_FAT=n + CONFIG_MICROPY_VFS_LFS1=n + CONFIG_MICROPY_VFS_LFS2=n diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index 08fdf5c5aa4..7726737e16b 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -86,6 +86,9 @@ static const mp_rom_map_elem_t mp_module_time_globals_table[] = { #ifdef CONFIG_FLASH_MAP { MP_ROM_QSTR(MP_QSTR_FlashArea), MP_ROM_PTR(&zephyr_flash_area_type) }, #endif + #ifdef CONFIG_FILE_SYSTEM + { MP_ROM_QSTR(MP_QSTR_FileSystem), MP_ROM_PTR(&zephyr_filesystem_type) }, + #endif }; static MP_DEFINE_CONST_DICT(mp_module_time_globals, mp_module_time_globals_table); diff --git a/ports/zephyr/modzephyr.h b/ports/zephyr/modzephyr.h index f9b7e8eea16..834ec4a31c6 100644 --- a/ports/zephyr/modzephyr.h +++ b/ports/zephyr/modzephyr.h @@ -37,4 +37,8 @@ extern const mp_obj_type_t zephyr_disk_access_type; extern const mp_obj_type_t zephyr_flash_area_type; #endif +#ifdef CONFIG_FILE_SYSTEM +extern const mp_obj_type_t zephyr_filesystem_type; +#endif + #endif // MICROPY_INCLUDED_ZEPHYR_MODZEPHYR_H diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 8d5d60ed29a..7f849734887 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -84,9 +84,10 @@ #endif #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/zephyr/machine_pwm.c" -#ifdef CONFIG_NETWORKING -// If we have networking, we likely want errno comfort +#if defined(CONFIG_NETWORKING) || defined(CONFIG_FILE_SYSTEM) #define MICROPY_PY_ERRNO (1) +#endif +#ifdef CONFIG_NETWORKING #define MICROPY_PY_SOCKET (1) #endif #ifdef CONFIG_BT diff --git a/ports/zephyr/zephyr_filesystem.c b/ports/zephyr/zephyr_filesystem.c new file mode 100644 index 00000000000..2ca72625355 --- /dev/null +++ b/ports/zephyr/zephyr_filesystem.c @@ -0,0 +1,782 @@ +/* +* This file is part of the MicroPython project, http://micropython.org/ +* +* The MIT License (MIT) +* +* Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +* +* 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/mpconfig.h" + +#if CONFIG_FILE_SYSTEM + +#if !MICROPY_VFS +#error "with CONFIG_FILE_SYSTEM enabled, must also enable MICROPY_VFS" +#endif + +#include +#include + +#include +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "shared/timeutils/timeutils.h" + +// Declare each FSTAB entry +#define FOREACH_FS_DEFINE(fs) FS_FSTAB_DECLARE_ENTRY(fs); + +#define FOREACH_FSTAB_DEFINE(n) DT_FOREACH_CHILD(n, FOREACH_FS_DEFINE) + +DT_FOREACH_STATUS_OKAY(zephyr_fstab, FOREACH_FSTAB_DEFINE) + +// Add all FSTAB entries to a table for us to use dynamically +#define FOREACH_FS(fs) FS_FSTAB_ENTRY(fs) + +#define FOREACH_FSTAB(n) DT_FOREACH_CHILD(n, FOREACH_FS) + +static struct fs_mount_t *const zephyr_fs_mounts[] = { + &DT_FOREACH_STATUS_OKAY(zephyr_fstab, FOREACH_FSTAB), +}; + +#define zephyr_fs_mounts_size sizeof(zephyr_fs_mounts) / sizeof(struct fs_mount_t *) + +typedef struct _zephyr_fs_obj_t { + mp_obj_base_t base; + struct fs_mount_t *mount; + vstr_t cur_dir; + vstr_t root_dir; +} zephyr_fs_obj_t; + +const char *zephyr_fs_make_path(zephyr_fs_obj_t *self, mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + if (path[0] != '/') { + size_t l = vstr_len(&self->root_dir); + size_t lc = vstr_len(&self->cur_dir); + vstr_add_str(&self->root_dir, "/"); + vstr_add_strn(&self->root_dir, self->cur_dir.buf, lc); + vstr_add_str(&self->root_dir, path); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + } else { + size_t l = vstr_len(&self->root_dir); + vstr_add_str(&self->root_dir, path); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + } + return path; +} + +typedef struct _zephyr_file_obj_t { + mp_obj_base_t base; + struct fs_file_t fp; +} zephyr_file_obj_t; + +static void file_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_printf(print, "", mp_obj_get_type_str(self_in), MP_OBJ_TO_PTR(self_in)); +} + +static mp_uint_t file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(self_in); + ssize_t err; + + err = fs_read(&self->fp, buf, size); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return err; +} + +static mp_uint_t file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(self_in); + ssize_t err; + + err = fs_write(&self->fp, buf, size); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return err; +} + +static mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(o_in); + int err = -EINVAL; + + if (request == MP_STREAM_SEEK) { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)(uintptr_t)arg; + + MP_STATIC_ASSERT(FS_SEEK_SET == MP_SEEK_SET); + MP_STATIC_ASSERT(FS_SEEK_CUR == MP_SEEK_CUR); + MP_STATIC_ASSERT(FS_SEEK_END == MP_SEEK_END); + + err = fs_seek(&self->fp, s->offset, s->whence); + + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + + off_t tell = fs_tell(&self->fp); + if (tell < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + s->offset = tell; + return 0; + + } else if (request == MP_STREAM_FLUSH) { + err = fs_sync(&self->fp); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return 0; + + } else if (request == MP_STREAM_CLOSE) { + err = fs_close(&self->fp); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return 0; + + } else { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +static const mp_rom_map_elem_t zephyr_fs_rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, + { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj) }, +}; + +static MP_DEFINE_CONST_DICT(zephyr_fs_rawfile_locals_dict, zephyr_fs_rawfile_locals_dict_table); + +static const mp_stream_p_t zephyr_fs_fileio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, + .ioctl = file_obj_ioctl, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_zephyr_fs_fileio, + MP_QSTR_FileIO, + MP_TYPE_FLAG_ITER_IS_STREAM, + print, file_obj_print, + protocol, &zephyr_fs_fileio_stream_p, + locals_dict, &zephyr_fs_rawfile_locals_dict + ); + +static const mp_stream_p_t zephyr_fs_textio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, + .ioctl = file_obj_ioctl, + .is_text = true, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_zephyr_fs_textio, + MP_QSTR_TextIOWrapper, + MP_TYPE_FLAG_ITER_IS_STREAM, + print, file_obj_print, + protocol, &zephyr_fs_textio_stream_p, + locals_dict, &zephyr_fs_rawfile_locals_dict + ); + +// Factory function for I/O stream classes +static mp_obj_t zephyr_fs_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const mp_obj_type_t *type = &mp_type_zephyr_fs_textio; + int flags = 0; + int err; + bool x_mode = false; + const char *mode_str = mp_obj_str_get_str(mode_in); + const char *path = zephyr_fs_make_path(self, path_in); + + for (; *mode_str; ++mode_str) { + int new_flags = 0; + switch (*mode_str) { + case 'r': + new_flags = FS_O_READ; + break; + case 'w': + new_flags = FS_O_WRITE | FS_O_CREATE | FS_O_TRUNC; + break; + case 'x': + new_flags = FS_O_WRITE; + x_mode = true; + break; + case 'a': + new_flags = FS_O_WRITE | FS_O_CREATE | FS_O_APPEND; + break; + case '+': + flags |= FS_O_RDWR; + break; + case 'b': + type = &mp_type_zephyr_fs_fileio; + break; + case 't': + type = &mp_type_zephyr_fs_textio; + break; + } + if (new_flags) { + if (flags) { + mp_raise_ValueError(NULL); + } + flags = new_flags; + } + } + + zephyr_file_obj_t *o = mp_obj_malloc_with_finaliser(zephyr_file_obj_t, type); + + err = fs_open(&o->fp, path, flags); + if (x_mode && err < 0) { + err = fs_open(&o->fp, path, flags | FS_O_CREATE); + } else if (x_mode) { + fs_close(&o->fp); + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("file %s Already exists: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (err < 0) { + m_del_obj(zephyr_file_obj_t, o); + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error opening file %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_open_obj, zephyr_fs_open); + +typedef struct _mp_zephyr_fs_ilistdir_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_fun_1_t finaliser; + bool is_str; + struct fs_dir_t dir; +} mp_zephyr_fs_ilistdir_it_t; + +static mp_obj_t mp_zephyr_fs_ilistdir_it_iternext(mp_obj_t self_in) { + mp_zephyr_fs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + int err; + + for (;;) { + struct fs_dirent stats; + err = fs_readdir(&self->dir, &stats); + char *fn = stats.name; + if (err < 0 || stats.name[0] == 0) { + // stop on error or end of dir + break; + } + + // Note that Zephyr FS also already filters . and .., so we don't need to + + // make 4-tuple with info about this entry + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL)); + if (self->is_str) { + t->items[0] = mp_obj_new_str_from_cstr(fn); + } else { + t->items[0] = mp_obj_new_bytes((const byte *)fn, strlen(fn)); + } + if (stats.type == FS_DIR_ENTRY_DIR) { + // dir + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); + } else { + // file + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG); + } + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number + t->items[3] = mp_obj_new_int_from_uint(stats.size); + + return MP_OBJ_FROM_PTR(t); + } + + // ignore error because we may be closing a second time + fs_closedir(&self->dir); + + return MP_OBJ_STOP_ITERATION; +} + +static mp_obj_t mp_zephyr_fs_ilistdir_it_del(mp_obj_t self_in) { + mp_zephyr_fs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + // ignore result / error because we may be closing a second time. + fs_closedir(&self->dir); + return mp_const_none; +} + +static mp_obj_t zephyr_fs_ilistdir_func(size_t n_args, const mp_obj_t *args) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(args[0]); + bool is_str_type = true; + const char *path; + int err; + + if (n_args == 2) { + if (mp_obj_get_type(args[1]) == &mp_type_bytes) { + is_str_type = false; + } + path = zephyr_fs_make_path(self, args[1]); + } else { + path = ""; + } + + // Create a new iterator object to list the dir + mp_zephyr_fs_ilistdir_it_t *iter = mp_obj_malloc_with_finaliser(mp_zephyr_fs_ilistdir_it_t, &mp_type_polymorph_iter_with_finaliser); + iter->iternext = mp_zephyr_fs_ilistdir_it_iternext; + iter->finaliser = mp_zephyr_fs_ilistdir_it_del; + iter->is_str = is_str_type; + + err = fs_opendir(&iter->dir, path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not open directory %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return MP_OBJ_FROM_PTR(iter); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(zephyr_fs_ilistdir_obj, 1, 2, zephyr_fs_ilistdir_func); + +static mp_obj_t zephyr_fs_mkdir(mp_obj_t vfs_in, mp_obj_t path_o) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_o); + int err; + + err = fs_mkdir(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not mkdir %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_mkdir_obj, zephyr_fs_mkdir); + + +static mp_obj_t zephyr_fs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = mp_obj_str_get_str(path_in); + size_t lc = vstr_len(&self->cur_dir); + size_t l = vstr_len(&self->root_dir); + vstr_t chdir; + struct fs_dirent stats; + int err; + + + vstr_init(&chdir, 32); + vstr_add_strn(&chdir, self->root_dir.buf, l); + if (path[0] != '/') { + vstr_add_byte(&chdir, '/'); + vstr_add_strn(&chdir, self->cur_dir.buf, lc); + } + vstr_add_str(&chdir, path); + vstr_add_byte(&chdir, '/'); + + #define CWD_LEN (vstr_len(&chdir)) + size_t to = 1; + size_t from = 1; + char *cwd = vstr_str(&chdir); + while (from < CWD_LEN) { + for (; from < CWD_LEN && cwd[from] == '/'; ++from) { + // Scan for the start + } + if (from > to) { + // Found excessive slash chars, squeeze them out + vstr_cut_out_bytes(&chdir, to, from - to); + from = to; + } + for (; from < CWD_LEN && cwd[from] != '/'; ++from) { + // Scan for the next / + } + if ((from - to) == 1 && cwd[to] == '.') { + // './', ignore + vstr_cut_out_bytes(&chdir, to, ++from - to); + from = to; + } else if ((from - to) == 2 && cwd[to] == '.' && cwd[to + 1] == '.') { + // '../', skip back + if (to > 1) { + // Only skip back if not at the tip + for (--to; to > 1 && cwd[to - 1] != '/'; --to) { + // Skip back + } + } + vstr_cut_out_bytes(&chdir, to, ++from - to); + from = to; + } else { + // Normal element, keep it and just move the offset + to = ++from; + } + } + + err = fs_stat(vstr_null_terminated_str(&chdir), &stats); + vstr_cut_out_bytes(&chdir, 0, l + 1); + lc = vstr_len(&chdir); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not chdir to %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + vstr_reset(&self->cur_dir); + vstr_add_strn(&self->cur_dir, chdir.buf, lc); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_chdir_obj, zephyr_fs_chdir); + +static mp_obj_t zephyr_fs_getcwd(mp_obj_t vfs_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + char *path; + + if (vstr_len(&self->cur_dir) == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR__slash_); + } else { + size_t l = vstr_len(&self->root_dir); + size_t lc = vstr_len(&self->cur_dir); + vstr_add_str(&self->root_dir, "/"); + vstr_add_strn(&self->root_dir, self->cur_dir.buf, lc); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + return mp_obj_new_str_from_cstr(path + l); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_getcwd_obj, zephyr_fs_getcwd); + +static mp_obj_t zephyr_fs_rmdir(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_in); + struct fs_dirent stats; + int err; + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (stats.type == FS_DIR_ENTRY_FILE) { + mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("not for removing files")); + return mp_const_none; + } + err = fs_unlink(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error removing directory at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_rmdir_obj, zephyr_fs_rmdir); + +static mp_obj_t zephyr_fs_remove(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_in); + struct fs_dirent stats; + int err; + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (stats.type == FS_DIR_ENTRY_DIR) { + mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("not for removing directories")); + return mp_const_none; + } + err = fs_unlink(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error removing file at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_remove_obj, zephyr_fs_remove); + +static mp_obj_t zephyr_fs_rename(mp_obj_t vfs_in, mp_obj_t path_in, mp_obj_t path_out) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *old_path = zephyr_fs_make_path(self, path_in); + const char *new_path = zephyr_fs_make_path(self, path_out); + int err; + + err = fs_rename(old_path, new_path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error renaming file from %s to %s: %q"), old_path, new_path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_rename_obj, zephyr_fs_rename); + +static mp_import_stat_t zephyr_fs_import_stat(void *vfs_in, const char *path) { + zephyr_fs_obj_t *self = vfs_in; + struct fs_dirent stats; + assert(self != NULL); + const char *ppath = zephyr_fs_make_path(self, mp_obj_new_str_from_cstr(path)); + int err; + + err = fs_stat(ppath, &stats); + if (err == 0) { + if (stats.type == FS_DIR_ENTRY_DIR) { + return MP_IMPORT_STAT_DIR; + } else { + return MP_IMPORT_STAT_FILE; + } + } + return MP_IMPORT_STAT_NO_EXIST; +} + +static mp_obj_t zephyr_fs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + int err; + struct fs_dirent stats; + const char *path = zephyr_fs_make_path(self, path_in); + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + mp_int_t mode = 0; + if (stats.type == FS_DIR_ENTRY_DIR) { + mode |= MP_S_IFDIR; + } else { + mode |= MP_S_IFREG; + } + t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev + t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink + t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid + t->items[6] = mp_obj_new_int_from_uint(stats.size); // st_size + t->items[7] = mp_obj_new_int_from_uint(0); // st_atime + t->items[8] = mp_obj_new_int_from_uint(0); // st_mtime + t->items[9] = mp_obj_new_int_from_uint(0); // st_ctime + + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_stat_obj, zephyr_fs_stat); + +static mp_obj_t zephyr_fs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + (void)path_in; + int err; + struct fs_statvfs stats; + unsigned long bsize; + + err = fs_statvfs(self->mount->mnt_point, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + if (stats.f_frsize > stats.f_bsize) { + bsize = stats.f_frsize; + } else { + bsize = stats.f_bsize; + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + + t->items[0] = MP_OBJ_NEW_SMALL_INT(bsize); // f_bsize + t->items[1] = MP_OBJ_NEW_SMALL_INT(stats.f_frsize); // f_frsize + t->items[2] = MP_OBJ_NEW_SMALL_INT(stats.f_blocks); // f_blocks + t->items[3] = MP_OBJ_NEW_SMALL_INT(stats.f_bfree); // f_bfree + t->items[4] = t->items[3]; // f_bavail + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files + t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags + t->items[9] = MP_OBJ_NEW_SMALL_INT(MAX_FILE_NAME); // f_namemax. + + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_statvfs_obj, zephyr_fs_statvfs); + +static mp_obj_t zephyr_fs_umount(mp_obj_t self_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + int err; + + err = fs_unmount(self->mount); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error un-mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_umount_obj, zephyr_fs_umount); + +static mp_obj_t zephyr_fs_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + int err; + (void)readonly; + (void)mkfs; + + err = fs_mount(self->mount); + if (err == -EBUSY) { + err = fs_unmount(self->mount); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error un-mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + err = fs_mount(self->mount); + } + + if (err == -EROFS) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("read only filesystem that requires formatting")); + return mp_const_none; + } else if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_mount_obj, zephyr_fs_mount); + +#if CONFIG_FILE_SYSTEM_MKFS +static mp_obj_t zephyr_fs_mkfs(mp_obj_t self_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Yes, they correspond indirectly + if (self->mount->fs->mkfs((int)self->mount->storage_dev, self->mount->fs_data, self->mount->flags) < 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("error formatting Zephyr File System")); + } + + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_mkfs_obj, zephyr_fs_mkfs); +#endif + +static mp_obj_t zephyr_fs_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, 1, false); + + if (!mp_obj_is_str(args[0])) { + mp_raise_ValueError(MP_ERROR_TEXT("argument must be a zephyr FSTAB mountpoint string")); + return mp_const_none; + } + + const char *_mountpoint = mp_obj_str_get_str(args[0]); + int i = 0; + + for (; i < zephyr_fs_mounts_size; i++) { + if (strcmp(_mountpoint, zephyr_fs_mounts[i]->mnt_point) == 0) { + break; + } + } + + if (i == zephyr_fs_mounts_size) { + mp_raise_ValueError(MP_ERROR_TEXT("couldn't find zephyr mountpoint")); + return mp_const_none; + } + + if (zephyr_fs_mounts[i]->fs == 0) { + if (fs_mount(zephyr_fs_mounts[i]) != 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("FS is invalid, try adding automount to fstab")); + return mp_const_none; + } + } + + // create new object + zephyr_fs_obj_t *fs = mp_obj_malloc(zephyr_fs_obj_t, type); + fs->mount = zephyr_fs_mounts[i]; + vstr_init(&fs->cur_dir, 32); + vstr_init(&fs->root_dir, 128); + vstr_add_str(&fs->root_dir, zephyr_fs_mounts[i]->mnt_point); + + return MP_OBJ_FROM_PTR(fs); +} + +static mp_obj_t zephyr_fs_fstab(void) { + mp_obj_t list = mp_obj_new_list(0, NULL); + + for (int i = 0; i < zephyr_fs_mounts_size; i++) { + mp_obj_list_append(list, mp_obj_new_str_from_cstr(zephyr_fs_mounts[i]->mnt_point)); + } + + return list; +} +static MP_DEFINE_CONST_FUN_OBJ_0(zephyr_fs_fstab_fun_obj, zephyr_fs_fstab); +static MP_DEFINE_CONST_STATICMETHOD_OBJ(zephyr_fs_fstab_obj, MP_ROM_PTR(&zephyr_fs_fstab_fun_obj)); + +static const mp_rom_map_elem_t zephyr_fs_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&zephyr_fs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&zephyr_fs_umount_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&zephyr_fs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&zephyr_fs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&zephyr_fs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&zephyr_fs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&zephyr_fs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&zephyr_fs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&zephyr_fs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&zephyr_fs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&zephyr_fs_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&zephyr_fs_ilistdir_obj) }, + #if CONFIG_FILE_SYSTEM_MKFS + { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&zephyr_fs_mkfs_obj) }, + #endif + // Not part of the VFS API, used to list available File Systems + { MP_ROM_QSTR(MP_QSTR_fstab), MP_ROM_PTR(&zephyr_fs_fstab_obj) }, +}; +static MP_DEFINE_CONST_DICT(zephyr_fs_locals_dict, zephyr_fs_locals_dict_table); + +static const mp_vfs_proto_t zephyr_fs_proto = { + .import_stat = zephyr_fs_import_stat, +}; + +const mp_obj_type_t zephyr_filesystem_type; + +MP_DEFINE_CONST_OBJ_TYPE( + zephyr_filesystem_type, + MP_QSTR_FileSystem, + MP_TYPE_FLAG_NONE, + make_new, zephyr_fs_make_new, + protocol, &zephyr_fs_proto, + locals_dict, &zephyr_fs_locals_dict + ); + +#endif // CONFIG_FILE_SYSTEM From 559eb7cdb36030cd12d502055d7d126520ebc750 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 1 Oct 2025 15:00:18 +0200 Subject: [PATCH 1497/2098] zephyr/CMakeLists.txt: Fix FS config options being ignored. This makes CONFIG_MICROPY_VFS_* actually do things. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 27229d21b9f..9db0a95c663 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -82,14 +82,33 @@ set(MICROPY_QSTRDEFS_PORT ${MICROPY_PORT_DIR}/qstrdefsport.h ) -set(MICROPY_SOURCE_LIB +if (CONFIG_MICROPY_VFS_FAT) + +list(APPEND MICROPY_SOURCE_LIB oofatfs/ff.c oofatfs/ffunicode.c +) + +endif() + +if (CONFIG_MICROPY_VFS_LFS1) + +list(APPEND MICROPY_SOURCE_LIB littlefs/lfs1.c littlefs/lfs1_util.c +) + +endif() + +if (CONFIG_MICROPY_VFS_LFS2) + +list(APPEND MICROPY_SOURCE_LIB littlefs/lfs2.c littlefs/lfs2_util.c ) + +endif() + list(TRANSFORM MICROPY_SOURCE_LIB PREPEND ${MICROPY_DIR}/lib/) set(MICROPY_SOURCE_QSTR From a40aba345545f42ee0ad533ddb735bb597b63ad7 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 1 Oct 2025 18:17:34 +0200 Subject: [PATCH 1498/2098] zephyr/boards: Setup rpi_pico to use Zephyr FS instead of MPY FS. Use new Zephyr FS, and be a demonstration of that configuration. This is still on-disk compatible with existing filesystems on the internal flash. Signed-off-by: Vdragon --- ports/zephyr/boards/rpi_pico.conf | 10 ++++++++++ ports/zephyr/boards/rpi_pico.overlay | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/ports/zephyr/boards/rpi_pico.conf b/ports/zephyr/boards/rpi_pico.conf index 683279ddc2c..6a0be1c3799 100644 --- a/ports/zephyr/boards/rpi_pico.conf +++ b/ports/zephyr/boards/rpi_pico.conf @@ -18,3 +18,13 @@ CONFIG_SPI=y # MicroPython config. CONFIG_MICROPY_HEAP_SIZE=196608 + +# File System Configuration +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_MICROPY_VFS_FAT=n +CONFIG_MICROPY_VFS_LFS1=n +CONFIG_MICROPY_VFS_LFS2=n +# Default heap for littlefs is too small +CONFIG_FS_LITTLEFS_FC_HEAP_SIZE=8192 diff --git a/ports/zephyr/boards/rpi_pico.overlay b/ports/zephyr/boards/rpi_pico.overlay index d63ed73bdf6..436e4210aec 100644 --- a/ports/zephyr/boards/rpi_pico.overlay +++ b/ports/zephyr/boards/rpi_pico.overlay @@ -9,6 +9,20 @@ /* Use USB CDC ACM as the console. */ zephyr,console = &cdc_acm_uart0; }; + + fstab { + compatible = "zephyr,fstab"; + lfs: lfs { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<256>; + cache-size=<1024>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; }; /* Delete defined partitions and make a layout matching the rp2 port RPI_PICO configuration. */ From 0ebaad2ba041aeab8655d03881c15bb062b1bfc2 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Sun, 26 Oct 2025 01:36:14 +0200 Subject: [PATCH 1499/2098] zephyr/modules: Add Zephyr FileSystem support to _boot.py. Signed-off-by: Vdragon --- ports/zephyr/modules/_boot.py | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/modules/_boot.py b/ports/zephyr/modules/_boot.py index d0ba21d3f06..ab3ed9c6340 100644 --- a/ports/zephyr/modules/_boot.py +++ b/ports/zephyr/modules/_boot.py @@ -11,12 +11,39 @@ # DiskAccess depends on CONFIG_DISK_ACCESS DiskAccess = getattr(zephyr, "DiskAccess", None) +# zephyr.FileSystem depends on CONFIG_FILE_SYSTEM and CONFIG_FLASH_MAP +FileSystem = getattr(zephyr, "FileSystem", None) + _FLASH = const("/flash") _FLASH_LIB = const("/flash/lib") _STORAGE_KEY = const("storage") _FLASH_EXISTS = False +def mount_filesystem_flash(): + """Create a filesystem if needed on the FS partition "/flash" + and mount it on /flash. + Return True if successful, False otherwise. + """ + if _FLASH in FileSystem.fstab(): + fs = FileSystem(_FLASH) + retval = True + try: + vfs.mount(fs, _FLASH) + except OSError: + if getattr(fs, "mkfs", None): + try: + fs.mkfs() + vfs.mount(fs, _FLASH) + except OSError: + print("Error formatting flash partition") + retval = False + else: + retval = False + return retval + return False + + def create_flash_partition(): """Create an LFS2 filesystem on the partition labeled storage and mount it on /flash. @@ -54,7 +81,10 @@ def mount_all_disks(): return retval -if FlashArea and create_flash_partition(): +# Prefer FileSystem over FlashArea Access +if FileSystem and mount_filesystem_flash(): + _FLASH_EXISTS = True +elif FlashArea and create_flash_partition(): _FLASH_EXISTS = True # Prefer disks to /flash @@ -67,6 +97,6 @@ def mount_all_disks(): sys.path.append(_FLASH_LIB) # Cleanup globals for boot.py/main.py -del FlashArea, DiskAccess, zephyr +del FlashArea, DiskAccess, FileSystem, zephyr del sys, vfs, os, const -del create_flash_partition, mount_all_disks, _FLASH_EXISTS +del mount_filesystem_flash, create_flash_partition, mount_all_disks, _FLASH_EXISTS From c07fda73f7115d2d2d4e2d4bba00a3e8dfd1367e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 15 Nov 2025 08:58:32 +0100 Subject: [PATCH 1500/2098] docs/mimxrt/pinout: Use Dxx pin identifiers for Teensy boards. The documentation used integers to specify Teensy board pins, which match the board's silkscreen. But in Python code Dxx have to be used, since integers are not accepted as a Pin identifier. This commit changes the Pinout tables accordingly, consistent with the other MIMXRT boards. Signed-off-by: robert-hh --- docs/mimxrt/pinout.rst | 123 +++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/docs/mimxrt/pinout.rst b/docs/mimxrt/pinout.rst index 9aeb85401be..d2b62d56d01 100644 --- a/docs/mimxrt/pinout.rst +++ b/docs/mimxrt/pinout.rst @@ -5,7 +5,10 @@ Pinout for the i.MXRT machine modules .. _mimxrt_uart_pinout: -| +The Teensy 4.0 and 4.1 board show pin numbers **nn** at the board's silkscreen. +These are denoted in the tables below as **Dnn**. E.g. a silkscreen number **1** is +shown as **D1**. Whenever a Pin has to be specified in a script, **Dnn** must +be used, not **nn**. UART pin assignment ------------------- @@ -17,8 +20,8 @@ tables below: ================= =========== =========== =========== =========== Board / Pin UART0 UART1 UART2 UART3 ================= =========== =========== =========== =========== -Teensy 4.0 - 0/1 7/8 14/15 -Teensy 4.1 - 0/1 7/8 14/15 +Teensy 4.0 - D0/D1 D7/D8 D14/D15 +Teensy 4.1 - D0/D1 D7/D8 D14/D15 MIMXRT1010-EVK Debug USB D0/D1 D7/D6 - MIMXRT1015-EVK Debug USB D0/D1 D7/A1 - MIMXRT1020-EVK Debug USB D0/D1 D9/D6 D10/D13 @@ -35,11 +38,11 @@ Makerdiary RT1011 - D9/D10 D13/A0 D11/D12 | -================= =========== =========== ======= ======= ===== -Board / Pin UART4 UART5 UART6 UART7 UART8 -================= =========== =========== ======= ======= ===== -Teensy 4.0 16/17 21/20 25/24 28/29 - -Teensy 4.1 16/17 21/20 25/24 28/29 34/35 +================= =========== =========== ======= ======= ======= +Board / Pin UART4 UART5 UART6 UART7 UART8 +================= =========== =========== ======= ======= ======= +Teensy 4.0 D16/D17 D21/D20 D25/D24 D28/D29 - +Teensy 4.1 D16/D17 D21/D20 D25/D24 D28/D29 D34/D35 MIMXRT1010-EVK - - - - - MIMXRT1015-EVK - - - - - MIMXRT1020-EVK D15/D14 A1/A0 - - - @@ -51,7 +54,7 @@ MIMXRT1170-EVK D15/D14 D25/D26 D33/D34 D35/D36 - Olimex RT1010Py - - - - - Seeed ARCH MIX J4_10/J4_11 J5_08/J5_12 - - - Makerdiary RT1011 A1/A2 - - - - -================= =========== =========== ======= ======= ===== +================= =========== =========== ======= ======= ======= .. _mimxrt_pwm_pinout: @@ -102,46 +105,46 @@ Pins denoted with (*) are by default not wired at the board. ==== ========== ==== ========== Pin Teensy 4.0 Pin Teensy 4.1 ==== ========== ==== ========== -0 F1/1/X 0 F1/1/X -1 F1/0/X 1 F1/0/X -2 F4/2/A 2 F4/2/A -3 F4/2/B 3 F4/2/B -4 F2/0/A 4 F2/0/A -5 F2/1/A 5 F2/1/A -6 F2/2/A 6 F2/2/A -7 F1/3/B 7 F1/3/B -8 F1/3/A 8 F1/3/A -9 F2/2/B 9 F2/2/B -10 Q1/0 10 Q1/0 -11 Q1/2 11 Q1/2 -12 Q1/1 12 Q1/1 -13 Q2/0 13 Q2/0 -14 Q3/2 14 Q3/2 -15 Q3/3 15 Q3/3 -18 Q3/1 18 Q3/1 -19 Q3/0 19 Q3/0 -22 F4/0/A 22 F4/0/A -23 F4/1/A 23 F4/1/A -24 F1/2/X 24 F1/2/X -25 F1/3/X 25 F1/3/X -28 F3/1/B 28 F3/1/B -29 F3/1/A 29 F3/1/A -33 F2/0/B 33 F2/0/B -- - 36 F2/3/A -- - 37 F2/3/B -DAT1 F1/1/B 42 F1/1/B -DAT0 F1/1/A 43 F1/1/A -CLK F1/0/B 44 F1/0/B -CMD F1/0/A 45 F1/0/A -DAT2 F1/2/A 46 F1/2/A -DAT3 F1/2/B 47 F1/2/B -- - 48 F1/0/B -- - 49 F1/2/A -- - 50 F1/2/B -- - 51 F3/3/B -- - 52 F1/1/B -- - 53 F1/1/A -- - 54 F3/0/A +D0 F1/1/X D0 F1/1/X +D1 F1/0/X D1 F1/0/X +D2 F4/2/A D2 F4/2/A +D3 F4/2/B D3 F4/2/B +D4 F2/0/A D4 F2/0/A +D5 F2/1/A D5 F2/1/A +D6 F2/2/A D6 F2/2/A +D7 F1/3/B D7 F1/3/B +D8 F1/3/A D8 F1/3/A +D9 F2/2/B D9 F2/2/B +D10 Q1/0 D10 Q1/0 +D11 Q1/2 D11 Q1/2 +D12 Q1/1 D12 Q1/1 +D13 Q2/0 D13 Q2/0 +D14 Q3/2 D14 Q3/2 +D15 Q3/3 D15 Q3/3 +D18 Q3/1 D18 Q3/1 +D19 Q3/0 D19 Q3/0 +D22 F4/0/A D22 F4/0/A +D23 F4/1/A D23 F4/1/A +D24 F1/2/X D24 F1/2/X +D25 F1/3/X D25 F1/3/X +D28 F3/1/B D28 F3/1/B +D29 F3/1/A D29 F3/1/A +D33 F2/0/B D33 F2/0/B +- - D36 F2/3/A +- - D37 F2/3/B +DAT1 F1/1/B D42 F1/1/B +DAT0 F1/1/A D43 F1/1/A +CLK F1/0/B D44 F1/0/B +CMD F1/0/A D45 F1/0/A +DAT2 F1/2/A D46 F1/2/A +DAT3 F1/2/B D47 F1/2/B +- - D48 F1/0/B +- - D49 F1/2/A +- - D50 F1/2/B +- - D51 F3/3/B +- - D52 F1/1/B +- - D53 F1/1/A +- - D54 F3/0/A ==== ========== ==== ========== | @@ -330,11 +333,11 @@ The SPI signals have fixed assignments to GPIO pins. It depends on the board design, which SPI's signals are exposed to the user, as detailed in the table below. The signal order in the table is: CS0, CS1, MOSI, MISO, CLK. -================= ========================= ======================= =============== +================= ========================= ======================= ================= Board / Pin SPI0 SPI1 SPI2 -================= ========================= ======================= =============== -Teensy 4.0 10/-/11/12/13 0/-/26/1/27 - -Teensy 4.1 10/37/11/12/13 0/-/26/1/27 -/29/50/54/49 +================= ========================= ======================= ================= +Teensy 4.0 D10/-/D11/D12/D13 D0/-/D26/D1/D27 - +Teensy 4.1 D10/D37/D11/D12/D13 D0/-/D26/D1/D27 -/D29/D50/D54/D49 MIXMXRT1010-EVK D10/D7/D11/D12/D13 - - MIXMXRT1015-EVK D10/-/D11/D12/D13 - - MIXMXRT1020-EVK D10/-/D11/D12/D13 A3/D0/A5/A4/A0 - @@ -347,7 +350,7 @@ Adafruit Metro M7 -/-/MOSI/MISO/SCK - - Olimex RT1010Py - CS0/-/SDO/SDI/SCK SDCARD with CS1 Seeed ARCH MIX J4_12/-/J4_14/J4_13/J4_15 J3_09/J3_05/J3_08_J3_11 Makerdiary RT1011 A5/A2/A4/A3/A6 A11/A1/A10/A9/CLK -================= ========================= ======================= =============== +================= ========================= ======================= ================= Pins denoted with (*) are by default not wired at the board. The CS0 and CS1 signals are enabled with the keyword option cs=0 or cs=1 of the SPI object constructor. @@ -367,8 +370,8 @@ detailed in the table below. The signal order in the table is: SDA, SCL. ================= =========== =========== =========== ======= ======= Board / Pin I2C 0 I2C 1 I2C 2 I2C 3 I2C 4 ================= =========== =========== =========== ======= ======= -Teensy 4.0 18/19 17/16 25/24 - - -Teensy 4.1 18/19 17/16 25/24 - - +Teensy 4.0 D18/D19 D17/D16 D25/D24 - - +Teensy 4.1 D18/D19 D17/D16 D25/D24 - - MIXMXRT1010-EVK D14/D15 D0/D1 - - - MIXMXRT1015-EVK D14/D15 - - - - MIXMXRT1020-EVK D14/D15 A4/A5 D0/D1 - - @@ -396,10 +399,10 @@ Pin assignments for a few MIMXRT boards: ================= == ===== ======== ======= ======= ======== ======= ======= Board ID MCK SCK_TX WS_TX SD_TX SCK_RX WS_RX SD_RX ================= == ===== ======== ======= ======= ======== ======= ======= -Teensy 4.0 1 23 26 27 7 21 20 8 -Teensy 4.0 2 33 4 3 2 - - 5 -Teensy 4.1 1 23 26 27 7 21 20 8 -Teensy 4.1 2 33 4 3 2 - - 5 +Teensy 4.0 1 D23 D26 D27 D7 D21 D20 D8 +Teensy 4.0 2 D33 D4 D3 D2 - - D5 +Teensy 4.1 1 D23 D26 D27 D7 D21 D20 D8 +Teensy 4.1 2 D33 D4 D3 D2 - - D5 Seeed Arch MIX 1 J4_09 J4_14 J4_15 J14_13 J4_11 J4_10 J4_10 Adafruit Metro M7 1 D8 D10 D9 D12 D14 D15 D13 Olimex RT1010Py 1 D8 D6 D7 D4 D1 D2 D3 From 48d96b400e73e7fbcca8365d0bebc0c41402dde5 Mon Sep 17 00:00:00 2001 From: Chris Liechti Date: Mon, 30 Nov 2020 16:40:56 +0100 Subject: [PATCH 1501/2098] py/builtinimport: Support relative import in custom __import__ callback. The globals need to be forwarded from the caller's context. Signed-off-by: Damien George --- py/builtinimport.c | 19 ++++++++++++++----- py/runtime.c | 2 +- tests/import/builtin_import.py | 9 +++++++++ tests/import/import_override2.py | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/import/import_override2.py diff --git a/py/builtinimport.c b/py/builtinimport.c index ff894f69d99..2c7d796680f 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -267,7 +267,7 @@ static void do_load(mp_module_context_t *module_obj, vstr_t *file) { // Convert a relative (to the current module) import, going up "level" levels, // into an absolute import. -static void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len) { +static void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len, mp_obj_t globals) { // What we want to do here is to take the name of the current module, // remove trailing components, and concatenate the passed-in // module name. @@ -276,7 +276,7 @@ static void evaluate_relative_import(mp_int_t level, const char **module_name, s // module's position in the package hierarchy." // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name - mp_obj_t current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + mp_obj_t current_module_name_obj = mp_obj_dict_get(globals, MP_OBJ_NEW_QSTR(MP_QSTR___name__)); assert(current_module_name_obj != MP_OBJ_NULL); #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT && MICROPY_CPYTHON_COMPAT @@ -284,12 +284,12 @@ static void evaluate_relative_import(mp_int_t level, const char **module_name, s // This is a module loaded by -m command-line switch (e.g. unix port), // and so its __name__ has been set to "__main__". Get its real name // that we stored during import in the __main__ attribute. - current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + current_module_name_obj = mp_obj_dict_get(globals, MP_OBJ_NEW_QSTR(MP_QSTR___main__)); } #endif // If we have a __path__ in the globals dict, then we're a package. - bool is_pkg = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + bool is_pkg = mp_map_lookup(mp_obj_dict_get_map(globals), MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); #if DEBUG_PRINT DEBUG_printf("Current module/package: "); @@ -569,10 +569,19 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { const char *module_name = mp_obj_str_get_data(module_name_obj, &module_name_len); if (level != 0) { + // This is the dict with all global symbols. + mp_obj_t globals = MP_OBJ_FROM_PTR(mp_globals_get()); + if (n_args >= 2 && args[1] != mp_const_none) { + globals = args[1]; + if (!mp_obj_is_type(globals, &mp_type_dict)) { + mp_raise_TypeError(NULL); + } + } + // Turn "foo.bar" with level=3 into ".foo.bar". // Current module name is extracted from globals().__name__. - evaluate_relative_import(level, &module_name, &module_name_len); // module_name is now an absolute module path. + evaluate_relative_import(level, &module_name, &module_name_len, globals); } if (module_name_len == 0) { diff --git a/py/runtime.c b/py/runtime.c index a84e22760be..ddc1ef7d3ea 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1522,7 +1522,7 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { // build args array mp_obj_t args[5]; args[0] = MP_OBJ_NEW_QSTR(name); - args[1] = mp_const_none; // TODO should be globals + args[1] = MP_OBJ_FROM_PTR(mp_globals_get()); // globals of the current context args[2] = mp_const_none; // TODO should be locals args[3] = fromlist; args[4] = level; diff --git a/tests/import/builtin_import.py b/tests/import/builtin_import.py index 734498d1be4..364b0bae912 100644 --- a/tests/import/builtin_import.py +++ b/tests/import/builtin_import.py @@ -20,3 +20,12 @@ __import__("xyz", None, None, None, -1) except ValueError: print("ValueError") + +# globals is not checked for level=0 +__import__("builtins", "globals") + +# globals must be a dict (or None) for level>0 +try: + __import__("builtins", "globals", None, None, 1) +except TypeError: + print("TypeError") diff --git a/tests/import/import_override2.py b/tests/import/import_override2.py new file mode 100644 index 00000000000..25aac44fe98 --- /dev/null +++ b/tests/import/import_override2.py @@ -0,0 +1,18 @@ +# test overriding __import__ combined with importing from the filesystem + + +def custom_import(name, globals, locals, fromlist, level): + if level > 0: + print("import", name, fromlist, level) + return orig_import(name, globals, locals, fromlist, level) + + +orig_import = __import__ +try: + __import__("builtins").__import__ = custom_import +except AttributeError: + print("SKIP") + raise SystemExit + +# import calls __import__ behind the scenes +import pkg7.subpkg1.subpkg2.mod3 From 45938432c6616fe684e67fdf4c5a9615a7c2fc73 Mon Sep 17 00:00:00 2001 From: Chris Liechti Date: Wed, 2 Dec 2020 00:38:30 +0100 Subject: [PATCH 1502/2098] extmod/asyncio: Pass globals in __import__ call. `globals()` needs to be provided in case `__import__` is a Python function. --- extmod/asyncio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/asyncio/__init__.py b/extmod/asyncio/__init__.py index 1f83750c5aa..801af791414 100644 --- a/extmod/asyncio/__init__.py +++ b/extmod/asyncio/__init__.py @@ -26,6 +26,6 @@ def __getattr__(attr): mod = _attrs.get(attr, None) if mod is None: raise AttributeError(attr) - value = getattr(__import__(mod, None, None, True, 1), attr) + value = getattr(__import__(mod, globals(), None, True, 1), attr) globals()[attr] = value return value From 128420359e9144369df2ed24e99217b3dc54446f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 Nov 2025 14:41:31 +1100 Subject: [PATCH 1503/2098] py/objdict: Implement bool and len unary ops for dict views. Currently, dict views (eg `dict.keys()`, `dict.values()`) do not implement the `bool` or `len` unary operations. That may seem like a reasonable omission for MicroPython to keep code size down, but it actually leads to silently incorrect bool operations, because by default things are true. Eg we currently have: >>> bool(dict().keys()) True which is wrong, it should be `False` because the dict is empty. This commit implements `bool` and `len` unary operations on dict views by simply delegating to the existing dict unary op function. Fixes issue #12385. Signed-off-by: Damien George --- py/objdict.c | 3 ++- tests/basics/dict_views.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/py/objdict.c b/py/objdict.c index 451e8732903..692a7de4275 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -521,7 +521,8 @@ static mp_obj_t dict_view_unary_op(mp_unary_op_t op, mp_obj_t o_in) { if (op == MP_UNARY_OP_HASH && o->kind == MP_DICT_VIEW_VALUES) { return MP_OBJ_NEW_SMALL_INT((mp_uint_t)o_in); } - return MP_OBJ_NULL; + // delegate all other ops to dict unary op handler + return dict_unary_op(op, o->dict); } static mp_obj_t dict_view_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { diff --git a/tests/basics/dict_views.py b/tests/basics/dict_views.py index a82f47b6be2..45f3238f1c6 100644 --- a/tests/basics/dict_views.py +++ b/tests/basics/dict_views.py @@ -6,6 +6,11 @@ # print a view with more than one item print({1:1, 2:1}.values()) +# `bool` and `len` unary ops +for d in ({}, {1: 2}, {1: 2, 3: 4}): + for op in (bool, len): + print(op(d.keys()), op(d.values()), op(d.items())) + # unsupported binary op on a dict values view try: {1:1}.values() + 1 From 7a15c97f069ed11fe0f7e697e240bec7eea4d0fb Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Tue, 18 Nov 2025 21:43:55 -0800 Subject: [PATCH 1504/2098] docs/library/os: Clarify ilistdir tuples size element. The currently documentation for ilistdir tuples says that the size element is included based on the platform. However, this is not the case as it is included based on the filesystem type. This commit makes the according adjustment. Fixes issue #17516. Signed-off-by: Alex Tran --- docs/library/os.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/library/os.rst b/docs/library/os.rst index bd552e36f01..483ef1c0a1e 100644 --- a/docs/library/os.rst +++ b/docs/library/os.rst @@ -55,10 +55,9 @@ Filesystem access directories and 0x8000 for regular files; - *inode* is an integer corresponding to the inode of the file, and may be 0 for filesystems that don't have such a notion. - - Some platforms may return a 4-tuple that includes the entry's *size*. For - file entries, *size* is an integer representing the size of the file - or -1 if unknown. Its meaning is currently undefined for directory - entries. + - *size* is an integer that may be included depending on the filesystem type. + For file entries, *size* represents the size of the file or -1 if unknown. + Its meaning is currently undefined for directory entries. .. function:: listdir([dir]) From 40df95357c7b3270dc60421a0078fd73b122473f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 16 Jul 2025 17:47:29 +0100 Subject: [PATCH 1505/2098] rp2: Fix RP2350 and RP2350B pin alt functions. RP2350 builds were using the incomplete `rp2_af.csv` alt function table (originally made for RP2040) which broke pins > 31 in RP2350B (48-pin QFN-80) builds: >>> machine.Pin(31) Pin(GPIO31, mode=IN, pull=PULL_DOWN) >>> machine.Pin(32) Pin(GPIO32, mode=ALT, pull=PULL_DOWN, alt=31) To fix this, separate alt-functions tables for RP2350 and RP2350B are added in this commit. UART_AUX (alt function 11) was also missing, along with XIP_CS1 and CORESIGHT_TRACE, and they are now added as well. Signed-off-by: Phil Howard Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 21 +++++--- ports/rp2/boards/make-pins.py | 9 ++++ .../rp2/boards/{rp2_af.csv => rp2040_af.csv} | 0 ports/rp2/boards/rp2350_af.csv | 31 ++++++++++++ ports/rp2/boards/rp2350b_af.csv | 49 +++++++++++++++++++ ports/rp2/machine_pin.c | 8 +++ 6 files changed, 112 insertions(+), 6 deletions(-) rename ports/rp2/boards/{rp2_af.csv => rp2040_af.csv} (100%) create mode 100644 ports/rp2/boards/rp2350_af.csv create mode 100644 ports/rp2/boards/rp2350b_af.csv diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 120d07bcce1..c4a081e2efd 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -644,12 +644,6 @@ separate_arguments(MICROPY_CPP_FLAGS_EXTRA) # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) -set(GEN_PINS_AF_CSV "${MICROPY_PORT_DIR}/boards/rp2_af.csv") -set(GEN_PINS_PREFIX "${MICROPY_PORT_DIR}/boards/rp2_prefix.c") -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() @@ -658,6 +652,21 @@ if(NOT PICO_NUM_EXT_GPIOS) set(PICO_NUM_EXT_GPIOS 10) endif() +if(PICO_RP2040) + set(GEN_PINS_AF_CSV "${MICROPY_PORT_DIR}/boards/rp2040_af.csv") +elseif(PICO_RP2350) + if(PICO_NUM_GPIOS EQUAL 48) + set(GEN_PINS_AF_CSV "${MICROPY_PORT_DIR}/boards/rp2350b_af.csv") + else() + set(GEN_PINS_AF_CSV "${MICROPY_PORT_DIR}/boards/rp2350_af.csv") + endif() +endif() + +set(GEN_PINS_PREFIX "${MICROPY_PORT_DIR}/boards/rp2_prefix.c") +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 MICROPY_BOARD_PINS) set(MICROPY_BOARD_PINS "${MICROPY_BOARD_DIR}/pins.csv") endif() diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index 1da2edb7cac..07ce052bba3 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -60,6 +60,15 @@ def add_af(self, af_idx, _af_name, af): m = re.match("([A-Z][A-Z0-9][A-Z]+)(([0-9]+)(_.*)?)?", af) af_fn = m.group(1) af_unit = int(m.group(3)) if m.group(3) is not None else 0 + if af_idx == 10: + # AF11 uses UART_AUX in lieu of UART + af_fn = "UART_AUX" + if af_fn.startswith("QMI"): + # The full func name is GPIO_FUNC_XIP_CS1 + af_fn = "XIP_CS1" + if af_fn.startswith("TRACE"): + # The various TRACE functions all belong under CORESIGHT_TRACE + af_fn = "CORESIGHT_TRACE" if af_fn == "PIO": # Pins can be either PIO unit (unlike, say, I2C where a # pin can only be I2C0 _or_ I2C1, both sharing the same AF diff --git a/ports/rp2/boards/rp2_af.csv b/ports/rp2/boards/rp2040_af.csv similarity index 100% rename from ports/rp2/boards/rp2_af.csv rename to ports/rp2/boards/rp2040_af.csv diff --git a/ports/rp2/boards/rp2350_af.csv b/ports/rp2/boards/rp2350_af.csv new file mode 100644 index 00000000000..cca30d9e74c --- /dev/null +++ b/ports/rp2/boards/rp2350_af.csv @@ -0,0 +1,31 @@ +Pin,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 +GPIO0,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, +GPIO1,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, +GPIO2,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX +GPIO3,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX +GPIO4,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, +GPIO5,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, +GPIO6,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO7,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO8,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, +GPIO9,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO10,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX +GPIO11,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX +GPIO12,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, +GPIO13,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, +GPIO14,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX +GPIO15,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX +GPIO16,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO17,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO18,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO19,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX +GPIO20,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, +GPIO21,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, +GPIO22,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX +GPIO23,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX +GPIO24,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, +GPIO25,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, +GPIO26,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO27,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO28,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO29,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, diff --git a/ports/rp2/boards/rp2350b_af.csv b/ports/rp2/boards/rp2350b_af.csv new file mode 100644 index 00000000000..3b9053f58cd --- /dev/null +++ b/ports/rp2/boards/rp2350b_af.csv @@ -0,0 +1,49 @@ +Pin,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 +GPIO0,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, +GPIO1,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, +GPIO2,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX +GPIO3,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX +GPIO4,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, +GPIO5,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, +GPIO6,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO7,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO8,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, +GPIO9,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO10,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX +GPIO11,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX +GPIO12,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, +GPIO13,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, +GPIO14,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX +GPIO15,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX +GPIO16,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO17,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO18,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO19,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX +GPIO20,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, +GPIO21,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, +GPIO22,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX +GPIO23,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX +GPIO24,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, +GPIO25,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, +GPIO26,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO27,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO28,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO29,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO30,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO31,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_RX +GPIO32,SPI0_RX,UART0_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO33,SPI0_CS,UART0_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO34,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX +GPIO35,SPI0_TX,UART0_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART0_RX +GPIO36,SPI0_RX,UART1_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO37,SPI0_CS,UART1_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO38,SPI0_SCK,UART1_CTS,I2C1_SCL,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO39,SPI0_TX,UART1_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO40,SPI1_RX,UART1_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO41,SPI1_CS,UART1_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO42,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO43,SPI1_TX,UART1_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO44,SPI1_RX,UART0_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO45,SPI1_CS,UART0_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO46,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX +GPIO47,SPI1_TX,UART0_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN,UART0_RX diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 7a2de0c0bc6..e1a184f322c 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -515,8 +515,16 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ALT_SIO), MP_ROM_INT(GPIO_FUNC_SIO) }, { MP_ROM_QSTR(MP_QSTR_ALT_PIO0), MP_ROM_INT(GPIO_FUNC_PIO0) }, { MP_ROM_QSTR(MP_QSTR_ALT_PIO1), MP_ROM_INT(GPIO_FUNC_PIO1) }, + #if NUM_PIOS >= 3 + { MP_ROM_QSTR(MP_QSTR_ALT_PIO2), MP_ROM_INT(GPIO_FUNC_PIO2) }, + #endif { MP_ROM_QSTR(MP_QSTR_ALT_GPCK), MP_ROM_INT(GPIO_FUNC_GPCK) }, { MP_ROM_QSTR(MP_QSTR_ALT_USB), MP_ROM_INT(GPIO_FUNC_USB) }, + #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_ALT_XIP_CS1), MP_ROM_INT(GPIO_FUNC_XIP_CS1) }, + { MP_ROM_QSTR(MP_QSTR_ALT_CORESIGHT_TRACE), MP_ROM_INT(GPIO_FUNC_CORESIGHT_TRACE) }, + { MP_ROM_QSTR(MP_QSTR_ALT_UART_AUX), MP_ROM_INT(GPIO_FUNC_UART_AUX) }, + #endif }; static MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); From 5b3c928f53a0f0e2a1d6178569e594dbe9cf8f92 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 26 Oct 2023 09:02:16 +1100 Subject: [PATCH 1506/2098] unix/main: Use standard pyexec REPL for unix and windows ports. This improves REPL usage consistency across ports, by utilizing the pyexec code for the unix REPL. Only enabled when MICROPY_USE_READLINE == 1 (the default). Signed-off-by: Andrew Leech --- ports/unix/Makefile | 1 + ports/unix/main.c | 90 ++++--------------- ports/windows/Makefile | 1 + ports/windows/micropython.vcxproj | 1 + shared/runtime/pyexec.c | 9 ++ tests/cmdline/cmd_sys_exit_0.py | 5 ++ tests/cmdline/cmd_sys_exit_0.py.exp | 1 + tests/cmdline/cmd_sys_exit_error.py | 5 ++ tests/cmdline/cmd_sys_exit_error.py.exp | 1 + tests/cmdline/cmd_sys_exit_none.py | 5 ++ tests/cmdline/cmd_sys_exit_none.py.exp | 1 + tests/cmdline/repl_autocomplete.py.exp | 2 +- .../repl_autocomplete_underscore.py.exp | 2 +- tests/cmdline/repl_autoindent.py.exp | 2 +- tests/cmdline/repl_basic.py.exp | 2 +- tests/cmdline/repl_cont.py.exp | 2 +- tests/cmdline/repl_emacs_keys.py.exp | 2 +- tests/cmdline/repl_inspect.py.exp | 2 +- tests/cmdline/repl_lock.py.exp | 2 +- tests/cmdline/repl_micropyinspect.py.exp | 2 +- tests/cmdline/repl_paste.py.exp | 2 +- tests/cmdline/repl_sys_ps1_ps2.py.exp | 2 +- tests/cmdline/repl_words_move.py.exp | 2 +- tests/feature_check/repl_emacs_check.py.exp | 2 +- .../repl_words_move_check.py.exp | 2 +- 25 files changed, 59 insertions(+), 89 deletions(-) create mode 100644 tests/cmdline/cmd_sys_exit_0.py create mode 100644 tests/cmdline/cmd_sys_exit_0.py.exp create mode 100644 tests/cmdline/cmd_sys_exit_error.py create mode 100644 tests/cmdline/cmd_sys_exit_error.py.exp create mode 100644 tests/cmdline/cmd_sys_exit_none.py create mode 100644 tests/cmdline/cmd_sys_exit_none.py.exp diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 81751bf0f7b..70e4f339172 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -220,6 +220,7 @@ SRC_C += \ SHARED_SRC_C += $(addprefix shared/,\ runtime/gchelper_generic.c \ + runtime/pyexec.c \ timeutils/timeutils.c \ $(SHARED_SRC_C_EXTRA) \ ) diff --git a/ports/unix/main.c b/ports/unix/main.c index db50e12f598..1c5c2266aa0 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -55,6 +55,7 @@ #include "genhdr/mpversion.h" #include "input.h" #include "stack_size.h" +#include "shared/runtime/pyexec.h" // Command line options, with their defaults static bool compile_only = false; @@ -195,95 +196,34 @@ static char *strjoin(const char *s1, int sep_char, const char *s2) { #endif static int do_repl(void) { - mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); - mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); - mp_hal_stdout_tx_str("\nUse Ctrl-D to exit, Ctrl-E for paste mode\n"); - #if MICROPY_USE_READLINE == 1 - // use MicroPython supplied readline + // use MicroPython supplied readline-based REPL - vstr_t line; - vstr_init(&line, 16); + int ret = 0; + mp_hal_stdio_mode_raw(); for (;;) { - mp_hal_stdio_mode_raw(); - - input_restart: - // If the GC is locked at this point there is no way out except a reset, - // so force the GC to be unlocked to help the user debug what went wrong. - MP_STATE_THREAD(gc_lock_depth) = 0; - vstr_reset(&line); - int ret = readline(&line, mp_repl_get_ps1()); - mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; - - if (ret == CHAR_CTRL_C) { - // cancel input - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // EOF - printf("\n"); - mp_hal_stdio_mode_orig(); - vstr_clear(&line); - return 0; - } else if (ret == CHAR_CTRL_E) { - // paste mode - mp_hal_stdout_tx_str("\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\n=== "); - vstr_reset(&line); - for (;;) { - char c = mp_hal_stdin_rx_chr(); - if (c == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\n"); - goto input_restart; - } else if (c == CHAR_CTRL_D) { - // end of input - mp_hal_stdout_tx_str("\n"); - break; - } else { - // add char to buffer and echo - vstr_add_byte(&line, c); - if (c == '\r') { - mp_hal_stdout_tx_str("\n=== "); - } else { - mp_hal_stdout_tx_strn(&c, 1); - } - } - } - parse_input_kind = MP_PARSE_FILE_INPUT; - } else if (line.len == 0) { - if (ret != 0) { - printf("\n"); + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if ((ret = pyexec_raw_repl()) != 0) { + break; } - goto input_restart; } else { - // got a line with non-zero length, see if it needs continuing - while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { - vstr_add_byte(&line, '\n'); - ret = readline(&line, mp_repl_get_ps2()); - if (ret == CHAR_CTRL_C) { - // cancel everything - printf("\n"); - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // stop entering compound statement - break; - } + if ((ret = pyexec_friendly_repl()) != 0) { + break; } } - - mp_hal_stdio_mode_orig(); - - ret = execute_from_lexer(LEX_SRC_VSTR, &line, parse_input_kind, true); - if (ret & FORCED_EXIT) { - return ret; - } } + mp_hal_stdio_mode_orig(); + return ret; #else // use simple readline + mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); + mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); + mp_hal_stdout_tx_str("\nUse Ctrl-D to exit, Ctrl-E for paste mode\n"); + for (;;) { char *line = prompt((char *)mp_repl_get_ps1()); if (line == NULL) { diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 9eee98cdd45..4129b7fe2cc 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -83,6 +83,7 @@ OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) ifeq ($(MICROPY_USE_READLINE),1) CFLAGS += -DMICROPY_USE_READLINE=1 SRC_C += shared/readline/readline.c +SRC_C += shared/runtime/pyexec.c endif LIB += -lws2_32 diff --git a/ports/windows/micropython.vcxproj b/ports/windows/micropython.vcxproj index 9326f3f4cde..f8bbec82cfd 100644 --- a/ports/windows/micropython.vcxproj +++ b/ports/windows/micropython.vcxproj @@ -89,6 +89,7 @@ + diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index cf17bd0dc35..237a6f9df1c 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -37,6 +37,7 @@ #include "py/mphal.h" #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" +#include "extmod/modplatform.h" #include "genhdr/mpversion.h" pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; @@ -103,6 +104,14 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input // source is a lexer, parse and compile the script qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + #if defined(MICROPY_UNIX_COVERAGE) + // allow to print the parse tree in the coverage build + if (mp_verbose_flag >= 3) { + printf("----------------\n"); + mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); + printf("----------------\n"); + } + #endif module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); #else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); diff --git a/tests/cmdline/cmd_sys_exit_0.py b/tests/cmdline/cmd_sys_exit_0.py new file mode 100644 index 00000000000..1294b739e8f --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_0.py @@ -0,0 +1,5 @@ +# cmdline: +# test sys.exit(0) - success exit code +import sys + +sys.exit(0) diff --git a/tests/cmdline/cmd_sys_exit_0.py.exp b/tests/cmdline/cmd_sys_exit_0.py.exp new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_0.py.exp @@ -0,0 +1 @@ + diff --git a/tests/cmdline/cmd_sys_exit_error.py b/tests/cmdline/cmd_sys_exit_error.py new file mode 100644 index 00000000000..ecac15e94f1 --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_error.py @@ -0,0 +1,5 @@ +# cmdline: +# test sys.exit() functionality and exit codes +import sys + +sys.exit(123) diff --git a/tests/cmdline/cmd_sys_exit_error.py.exp b/tests/cmdline/cmd_sys_exit_error.py.exp new file mode 100644 index 00000000000..3911f71d624 --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_error.py.exp @@ -0,0 +1 @@ +CRASH \ No newline at end of file diff --git a/tests/cmdline/cmd_sys_exit_none.py b/tests/cmdline/cmd_sys_exit_none.py new file mode 100644 index 00000000000..66e19666589 --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_none.py @@ -0,0 +1,5 @@ +# cmdline: +# test sys.exit(None) - should exit with code 0 +import sys + +sys.exit(None) diff --git a/tests/cmdline/cmd_sys_exit_none.py.exp b/tests/cmdline/cmd_sys_exit_none.py.exp new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/cmdline/cmd_sys_exit_none.py.exp @@ -0,0 +1 @@ + diff --git a/tests/cmdline/repl_autocomplete.py.exp b/tests/cmdline/repl_autocomplete.py.exp index 75002985e3c..8cf71bb447d 100644 --- a/tests/cmdline/repl_autocomplete.py.exp +++ b/tests/cmdline/repl_autocomplete.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # tests for autocompletion >>> import sys >>> not_exist. diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp index 35617554f57..f9720ef2331 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py.exp +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use Ctrl-D to exit, Ctrl-E for paste mode +Type "help()" for more information. >>> # Test REPL autocompletion filtering of underscore attributes >>> >>> # Start paste mode diff --git a/tests/cmdline/repl_autoindent.py.exp b/tests/cmdline/repl_autoindent.py.exp index 9127a7d31d9..f45bf840f09 100644 --- a/tests/cmdline/repl_autoindent.py.exp +++ b/tests/cmdline/repl_autoindent.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # tests for autoindent >>> if 1: ... print(1) diff --git a/tests/cmdline/repl_basic.py.exp b/tests/cmdline/repl_basic.py.exp index 2b390ea98bb..a1906847432 100644 --- a/tests/cmdline/repl_basic.py.exp +++ b/tests/cmdline/repl_basic.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # basic REPL tests >>> print(1) 1 diff --git a/tests/cmdline/repl_cont.py.exp b/tests/cmdline/repl_cont.py.exp index 834c18a4d36..d0d20adc496 100644 --- a/tests/cmdline/repl_cont.py.exp +++ b/tests/cmdline/repl_cont.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # check REPL allows to continue input >>> 1 \\\\ ... + 2 diff --git a/tests/cmdline/repl_emacs_keys.py.exp b/tests/cmdline/repl_emacs_keys.py.exp index 6102c19639a..b8b7b794f2d 100644 --- a/tests/cmdline/repl_emacs_keys.py.exp +++ b/tests/cmdline/repl_emacs_keys.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # REPL tests of GNU-ish readline navigation >>> # history buffer navigation >>> 1 diff --git a/tests/cmdline/repl_inspect.py.exp b/tests/cmdline/repl_inspect.py.exp index 051acfd153a..89ae142019b 100644 --- a/tests/cmdline/repl_inspect.py.exp +++ b/tests/cmdline/repl_inspect.py.exp @@ -1,6 +1,6 @@ test MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # cmdline: -i -c print("test") >>> # -c option combined with -i option results in REPL >>> diff --git a/tests/cmdline/repl_lock.py.exp b/tests/cmdline/repl_lock.py.exp index 7cce24f6c6a..d921fd6ae8e 100644 --- a/tests/cmdline/repl_lock.py.exp +++ b/tests/cmdline/repl_lock.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> import micropython >>> micropython.heap_lock() >>> 1+1 diff --git a/tests/cmdline/repl_micropyinspect.py.exp b/tests/cmdline/repl_micropyinspect.py.exp index 93ff43546ea..504bb07d7d4 100644 --- a/tests/cmdline/repl_micropyinspect.py.exp +++ b/tests/cmdline/repl_micropyinspect.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # cmdline: cmdline/repl_micropyinspect >>> # setting MICROPYINSPECT environment variable before program exit triggers REPL >>> diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp index 22d9bd57400..2b837f85cf9 100644 --- a/tests/cmdline/repl_paste.py.exp +++ b/tests/cmdline/repl_paste.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use Ctrl-D to exit, Ctrl-E for paste mode +Type "help()" for more information. >>> # Test REPL paste mode functionality >>> >>> # Basic paste mode with a simple function diff --git a/tests/cmdline/repl_sys_ps1_ps2.py.exp b/tests/cmdline/repl_sys_ps1_ps2.py.exp index 9e82db5e313..6781660bf33 100644 --- a/tests/cmdline/repl_sys_ps1_ps2.py.exp +++ b/tests/cmdline/repl_sys_ps1_ps2.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # test changing ps1/ps2 >>> import sys >>> sys.ps1 = "PS1" diff --git a/tests/cmdline/repl_words_move.py.exp b/tests/cmdline/repl_words_move.py.exp index 86f6b778898..c4d22a0d9a7 100644 --- a/tests/cmdline/repl_words_move.py.exp +++ b/tests/cmdline/repl_words_move.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # word movement >>> # backward-word, start in word >>> \.\+ diff --git a/tests/feature_check/repl_emacs_check.py.exp b/tests/feature_check/repl_emacs_check.py.exp index 82a4e28ee4f..5fe8ba1cd2d 100644 --- a/tests/feature_check/repl_emacs_check.py.exp +++ b/tests/feature_check/repl_emacs_check.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # Check for emacs keys in REPL >>> t = \.\+ >>> t == 2 diff --git a/tests/feature_check/repl_words_move_check.py.exp b/tests/feature_check/repl_words_move_check.py.exp index 82a4e28ee4f..5fe8ba1cd2d 100644 --- a/tests/feature_check/repl_words_move_check.py.exp +++ b/tests/feature_check/repl_words_move_check.py.exp @@ -1,5 +1,5 @@ MicroPython \.\+ version -Use \.\+ +Type "help()" for more information. >>> # Check for emacs keys in REPL >>> t = \.\+ >>> t == 2 From 3b2b8dd53826ef3b7ef755013e1ecdccd368cb8c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 26 May 2025 19:28:40 +1000 Subject: [PATCH 1507/2098] shared/runtime/pyexec: Set __file__ for file input when enabled. When `MICROPY_MODULE___FILE__` is enabled and parsing file input, set the global `__file__` variable to the source filename. This matches the behavior of the unix port and provides the current filename to the executing script. Signed-off-by: Andrew Leech --- shared/runtime/pyexec.c | 5 +++++ tests/cmdline/cmd_file_variable.py | 5 +++++ tests/cmdline/cmd_file_variable.py.exp | 1 + 3 files changed, 11 insertions(+) create mode 100644 tests/cmdline/cmd_file_variable.py create mode 100644 tests/cmdline/cmd_file_variable.py.exp diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 237a6f9df1c..61963678344 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -103,6 +103,11 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input } // source is a lexer, parse and compile the script qstr source_name = lex->source_name; + #if MICROPY_MODULE___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); #if defined(MICROPY_UNIX_COVERAGE) // allow to print the parse tree in the coverage build diff --git a/tests/cmdline/cmd_file_variable.py b/tests/cmdline/cmd_file_variable.py new file mode 100644 index 00000000000..6cac6744d90 --- /dev/null +++ b/tests/cmdline/cmd_file_variable.py @@ -0,0 +1,5 @@ +# Test that __file__ is set correctly for script execution +try: + print("__file__ =", __file__) +except NameError: + print("__file__ not defined") diff --git a/tests/cmdline/cmd_file_variable.py.exp b/tests/cmdline/cmd_file_variable.py.exp new file mode 100644 index 00000000000..6807569f662 --- /dev/null +++ b/tests/cmdline/cmd_file_variable.py.exp @@ -0,0 +1 @@ +__file__ = cmdline/cmd_file_variable.py From 7b3a0fc6b7fe935be008d2518eb480b75a8809d1 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 26 May 2025 19:28:02 +1000 Subject: [PATCH 1508/2098] shared/runtime/pyexec: Provide support for compile-only mode. When `MICROPY_PYEXEC_COMPILE_ONLY` is enabled and the global `mp_compile_only` is True, code is compiled but not executed. Also add comprehensive tests for compile-only functionality covering both successful compilation and syntax error detection. Signed-off-by: Andrew Leech --- pyproject.toml | 8 +++++++- shared/runtime/pyexec.c | 8 +++++++- tests/cmdline/cmd_compile_only.py | 13 +++++++++++++ tests/cmdline/cmd_compile_only.py.exp | 1 + tests/cmdline/cmd_compile_only_error.py | 6 ++++++ tests/cmdline/cmd_compile_only_error.py.exp | 1 + 6 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/cmdline/cmd_compile_only.py create mode 100644 tests/cmdline/cmd_compile_only.py.exp create mode 100644 tests/cmdline/cmd_compile_only_error.py create mode 100644 tests/cmdline/cmd_compile_only_error.py.exp diff --git a/pyproject.toml b/pyproject.toml index 041dd74191f..cdc79bb8ae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ target-version = "py37" [tool.ruff.lint] exclude = [ # Ruff finds Python SyntaxError in these files + "tests/cmdline/cmd_compile_only_error.py", "tests/cmdline/repl_autocomplete.py", "tests/cmdline/repl_autocomplete_underscore.py", "tests/cmdline/repl_autoindent.py", @@ -69,4 +70,9 @@ mccabe.max-complexity = 40 # basics: needs careful attention before applying automatic formatting # repl_: not real python files # viper_args: uses f(*) -exclude = ["tests/basics/*.py", "tests/*/repl_*.py", "tests/micropython/viper_args.py"] +exclude = [ + "tests/basics/*.py", + "tests/*/repl_*.py", + "tests/cmdline/cmd_compile_only_error.py", + "tests/micropython/viper_args.py", +] diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 61963678344..910b39c08c2 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -130,7 +130,12 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input #if MICROPY_REPL_INFO start = mp_hal_ticks_ms(); #endif - mp_call_function_0(module_fun); + #if MICROPY_PYEXEC_COMPILE_ONLY + if (!mp_compile_only) + #endif + { + mp_call_function_0(module_fun); + } mp_hal_set_interrupt_char(-1); // disable interrupt mp_handle_pending(true); // handle any pending exceptions (and any callbacks) nlr_pop(); @@ -706,6 +711,7 @@ int pyexec_file(const char *filename) { return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME); } + int pyexec_file_if_exists(const char *filename) { #if MICROPY_MODULE_FROZEN if (mp_find_frozen_module(filename, NULL, NULL) == MP_IMPORT_STAT_FILE) { diff --git a/tests/cmdline/cmd_compile_only.py b/tests/cmdline/cmd_compile_only.py new file mode 100644 index 00000000000..89964c1b5bd --- /dev/null +++ b/tests/cmdline/cmd_compile_only.py @@ -0,0 +1,13 @@ +# cmdline: -X compile-only +# test compile-only functionality +print("This should not be printed") +x = 1 + 2 + + +def hello(): + return "world" + + +class TestClass: + def __init__(self): + self.value = 42 diff --git a/tests/cmdline/cmd_compile_only.py.exp b/tests/cmdline/cmd_compile_only.py.exp new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/cmdline/cmd_compile_only.py.exp @@ -0,0 +1 @@ + diff --git a/tests/cmdline/cmd_compile_only_error.py b/tests/cmdline/cmd_compile_only_error.py new file mode 100644 index 00000000000..326937a5c07 --- /dev/null +++ b/tests/cmdline/cmd_compile_only_error.py @@ -0,0 +1,6 @@ +# cmdline: -X compile-only +# test compile-only with syntax error +print("This should not be printed") +def broken_syntax( + # Missing closing parenthesis + return "error" diff --git a/tests/cmdline/cmd_compile_only_error.py.exp b/tests/cmdline/cmd_compile_only_error.py.exp new file mode 100644 index 00000000000..3911f71d624 --- /dev/null +++ b/tests/cmdline/cmd_compile_only_error.py.exp @@ -0,0 +1 @@ +CRASH \ No newline at end of file From 7ac8fcf752389e2af37b49994478bd9d93f48434 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 26 May 2025 20:25:01 +1000 Subject: [PATCH 1509/2098] unix: Enable compile-only mode with shared pyexec REPL. Provides support for command line `-X compile-only` option on unix port. Signed-off-by: Andrew Leech --- ports/unix/main.c | 6 +++--- ports/unix/mpconfigport.h | 3 +++ ports/unix/mphalport.h | 4 ++++ ports/windows/mpconfigport.h | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 1c5c2266aa0..934bd3252b5 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -58,7 +58,7 @@ #include "shared/runtime/pyexec.h" // Command line options, with their defaults -static bool compile_only = false; +bool mp_compile_only = false; static uint emit_opt = MP_EMIT_OPT_NONE; #if MICROPY_ENABLE_GC @@ -159,7 +159,7 @@ static int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu mp_obj_t module_fun = mp_compile(&parse_tree, source_name, is_repl); - if (!compile_only) { + if (!mp_compile_only) { // execute it mp_call_function_0(module_fun); } @@ -325,7 +325,7 @@ static void pre_process_options(int argc, char **argv) { } if (0) { } else if (strcmp(argv[a + 1], "compile-only") == 0) { - compile_only = true; + mp_compile_only = true; } else if (strcmp(argv[a + 1], "emit=bytecode") == 0) { emit_opt = MP_EMIT_OPT_BYTECODE; #if MICROPY_EMIT_NATIVE diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index f0743986351..562c93f99bd 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -151,6 +151,9 @@ typedef long mp_off_t; // Enable sys.executable. #define MICROPY_PY_SYS_EXECUTABLE (1) +// Enable support for compile-only mode. +#define MICROPY_PYEXEC_COMPILE_ONLY (1) + #define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) // Bare-metal ports don't have stderr. Printing debug to stderr may give tests diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 0efd6940b30..eb2370c94b5 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -25,6 +25,7 @@ */ #include #include +#include #ifndef CHAR_CTRL_C #define CHAR_CTRL_C (3) @@ -117,3 +118,6 @@ enum { void mp_hal_get_mac(int idx, uint8_t buf[6]); #endif + +// Global variable to control compile-only mode. +extern bool mp_compile_only; diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 68744ba93e7..24c54438e6e 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -161,6 +161,9 @@ #define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr #define MICROPY_MACHINE_MEM_GET_WRITE_ADDR mod_machine_mem_get_addr +// Enable support for compile-only mode. +#define MICROPY_PYEXEC_COMPILE_ONLY (1) + #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) #define MICROPY_ERROR_PRINTER (&mp_stderr_print) #define MICROPY_WARNINGS (1) From e067d96c8b71b3fe258583fd6cddb1094cb4efe5 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 26 May 2025 19:31:30 +1000 Subject: [PATCH 1510/2098] tests/run-tests.py: Add general newline normalization function. Add a general normalize_newlines() function that handles newline variations (\\r\\r\\n, \\r\\n) to \\n while preserving literal \\r characters that are part of test content. This provides a robust solution for cross-platform test compatibility, particularly addressing PTY double-newline issues that can occur with some terminal implementations. The function is applied to all test output before comparison, eliminating platform-specific newline issues. Includes a unit test to verify the normalization behavior. Signed-off-by: Andrew Leech --- .gitattributes | 1 + pyproject.toml | 1 + tests/micropython/test_normalize_newlines.py | 14 ++++++++++++++ .../test_normalize_newlines.py.exp | 8 ++++++++ tests/run-tests.py | 19 ++++++++++++++++++- 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/micropython/test_normalize_newlines.py create mode 100644 tests/micropython/test_normalize_newlines.py.exp diff --git a/.gitattributes b/.gitattributes index 2d8496db504..c14a61b0d97 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,7 @@ # These should also not be modified by git. tests/basics/string_cr_conversion.py -text tests/basics/string_crlf_conversion.py -text +tests/micropython/test_normalize_newlines.py.exp -text ports/stm32/pybcdc.inf_template -text ports/stm32/usbhost/** -text ports/cc3200/hal/aes.c -text diff --git a/pyproject.toml b/pyproject.toml index cdc79bb8ae6..f3693eae1b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,5 +74,6 @@ exclude = [ "tests/basics/*.py", "tests/*/repl_*.py", "tests/cmdline/cmd_compile_only_error.py", + "tests/micropython/test_normalize_newlines.py", "tests/micropython/viper_args.py", ] diff --git a/tests/micropython/test_normalize_newlines.py b/tests/micropython/test_normalize_newlines.py new file mode 100644 index 00000000000..f19aaa69a3f --- /dev/null +++ b/tests/micropython/test_normalize_newlines.py @@ -0,0 +1,14 @@ +# Test for normalize_newlines functionality +# This test verifies that test framework handles various newline combinations correctly + +# Note: This is more of an integration test since normalize_newlines is in the test framework +# The actual testing happens when this test is run through run-tests.py + +print("Testing newline handling") +print("Line 1\r\nLine 2") # Windows-style line ending - should be normalized +print("Line 3") # Normal line +print("Line 4") # Normal line +print("Line 5\nLine 6") # Unix-style line ending - already normalized + +# Test that literal \r in strings is preserved +print(repr("test\rstring")) # Should show 'test\rstring' not 'test\nstring' diff --git a/tests/micropython/test_normalize_newlines.py.exp b/tests/micropython/test_normalize_newlines.py.exp new file mode 100644 index 00000000000..c4395468cf1 --- /dev/null +++ b/tests/micropython/test_normalize_newlines.py.exp @@ -0,0 +1,8 @@ +Testing newline handling +Line 1 +Line 2 +Line 3 +Line 4 +Line 5 +Line 6 +'test\rstring' diff --git a/tests/run-tests.py b/tests/run-tests.py index b4654c2e6b5..fba011fb54c 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -60,6 +60,23 @@ def base_path(*p): # Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale os.environ["PYTHONIOENCODING"] = "utf-8" + +def normalize_newlines(data): + """Normalize newline variations to \\n. + + Only normalizes actual line endings, not literal \\r characters in strings. + Handles \\r\\r\\n and \\r\\n cases to ensure consistent comparison + across different platforms and terminals. + """ + if isinstance(data, bytes): + # Handle PTY double-newline issue first + data = data.replace(b"\r\r\n", b"\n") + # Then handle standard Windows line endings + data = data.replace(b"\r\n", b"\n") + # Don't convert standalone \r as it might be literal content + return data + + # Code to allow a target MicroPython to import an .mpy from RAM # Note: the module is named `__injected_test` but it needs to have `__name__` set to # `__main__` so that the test sees itself as the main module, eg so unittest works. @@ -706,7 +723,7 @@ def send_get(what): ) # canonical form for all ports/platforms is to use \n for end-of-line - output_mupy = output_mupy.replace(b"\r\n", b"\n") + output_mupy = normalize_newlines(output_mupy) # don't try to convert the output if we should skip this test if had_crash or output_mupy in (b"SKIP\n", b"SKIP-TOO-LARGE\n", b"CRASH"): From e06ac9ce089c5eda9cbd9ec035cdf56fe75be0ab Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 22 Jul 2025 15:28:28 +1000 Subject: [PATCH 1511/2098] unix/main: Replace execute_from_lexer with pyexec in do_file and do_str. Consolidates file and string execution to use the standard pyexec interface for consistency with other ports. Simplify execute_from_lexer for remaining usage: Remove unused LEX_SRC_VSTR and LEX_SRC_FILENAME cases, keeping only LEX_SRC_STR for REPL and LEX_SRC_STDIN for stdin execution. Signed-off-by: Andrew Leech --- ports/unix/main.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 934bd3252b5..dec3f90dea5 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -111,8 +111,6 @@ static int handle_uncaught_exception(mp_obj_base_t *exc) { } #define LEX_SRC_STR (1) -#define LEX_SRC_VSTR (2) -#define LEX_SRC_FILENAME (3) #define LEX_SRC_STDIN (4) // Returns standard error codes: 0 for success, 1 for all other errors, @@ -128,12 +126,6 @@ static int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu if (source_kind == LEX_SRC_STR) { const char *line = source; lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, line, strlen(line), false); - } else if (source_kind == LEX_SRC_VSTR) { - const vstr_t *vstr = source; - lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, false); - } else if (source_kind == LEX_SRC_FILENAME) { - const char *filename = (const char *)source; - lex = mp_lexer_new_from_file(qstr_from_str(filename)); } else { // LEX_SRC_STDIN lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false); } @@ -251,12 +243,28 @@ static int do_repl(void) { #endif } +static inline int convert_pyexec_result(int ret) { + // pyexec returns 1 for success, 0 for exception, PYEXEC_FORCED_EXIT for SystemExit + // Convert to unix port's expected codes: 0 for success, 1 for exception, FORCED_EXIT|val for SystemExit + if (ret == 1) { + return 0; // success + } else if (ret & PYEXEC_FORCED_EXIT) { + return ret; // SystemExit with exit value in lower 8 bits + } else { + return 1; // exception + } +} + static int do_file(const char *file) { - return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false); + return convert_pyexec_result(pyexec_file(file)); } static int do_str(const char *str) { - return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); + vstr_t vstr; + vstr.buf = (char *)str; + vstr.len = strlen(str); + int ret = pyexec_vstr(&vstr, true); + return convert_pyexec_result(ret); } static void print_help(char **argv) { From fd1ddc3f12b40e99faaf24dd099fd512fddcb62c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 8 Oct 2025 13:11:23 +1100 Subject: [PATCH 1512/2098] shared/runtime/pyexec: Call mp_hal_stdio_mode_orig/raw as appropriate. This ensures that ctrl-C works on the unix port when executing code at the REPL. Signed-off-by: Damien George --- ports/unix/main.c | 2 -- ports/unix/mphalport.h | 3 +++ shared/runtime/pyexec.c | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index dec3f90dea5..55fd8406ba6 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -193,7 +193,6 @@ static int do_repl(void) { // use MicroPython supplied readline-based REPL int ret = 0; - mp_hal_stdio_mode_raw(); for (;;) { if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { if ((ret = pyexec_raw_repl()) != 0) { @@ -205,7 +204,6 @@ static int do_repl(void) { } } } - mp_hal_stdio_mode_orig(); return ret; #else diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index eb2370c94b5..97c24978a25 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -48,6 +48,9 @@ MP_THREAD_GIL_ENTER(); \ } while (0) +// The port provides `mp_hal_stdio_mode_raw()` and `mp_hal_stdio_mode_orig()`. +#define MICROPY_HAL_HAS_STDIO_MODE_SWITCH (1) + void mp_hal_set_interrupt_char(char c); #define mp_hal_stdio_poll unused // this is not implemented, nor needed diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 910b39c08c2..d2a1403e381 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -536,10 +536,20 @@ MP_REGISTER_ROOT_POINTER(vstr_t * repl_line); #else // MICROPY_REPL_EVENT_DRIVEN +#if !MICROPY_HAL_HAS_STDIO_MODE_SWITCH +// If the port doesn't need any stdio mode switching calls then provide trivial ones. +static inline void mp_hal_stdio_mode_raw(void) { +} +static inline void mp_hal_stdio_mode_orig(void) { +} +#endif + int pyexec_raw_repl(void) { vstr_t line; vstr_init(&line, 32); + mp_hal_stdio_mode_raw(); + raw_repl_reset: mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); @@ -553,6 +563,7 @@ int pyexec_raw_repl(void) { if (vstr_len(&line) == 2 && vstr_str(&line)[0] == CHAR_CTRL_E) { int ret = do_reader_stdin(vstr_str(&line)[1]); if (ret & PYEXEC_FORCED_EXIT) { + mp_hal_stdio_mode_orig(); return ret; } vstr_reset(&line); @@ -565,6 +576,7 @@ int pyexec_raw_repl(void) { mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + mp_hal_stdio_mode_orig(); return 0; } else if (c == CHAR_CTRL_C) { // clear line @@ -585,13 +597,17 @@ int pyexec_raw_repl(void) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); + mp_hal_stdio_mode_orig(); return PYEXEC_FORCED_EXIT; } + // Switch to original terminal mode to execute code, eg to support keyboard interrupt (SIGINT). + mp_hal_stdio_mode_orig(); int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } + mp_hal_stdio_mode_raw(); } } @@ -599,6 +615,8 @@ int pyexec_friendly_repl(void) { vstr_t line; vstr_init(&line, 32); + mp_hal_stdio_mode_raw(); + friendly_repl_reset: mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); @@ -640,6 +658,7 @@ int pyexec_friendly_repl(void) { mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; + mp_hal_stdio_mode_orig(); return 0; } else if (ret == CHAR_CTRL_B) { // reset friendly REPL @@ -653,6 +672,7 @@ int pyexec_friendly_repl(void) { // exit for a soft reset mp_hal_stdout_tx_str("\r\n"); vstr_clear(&line); + mp_hal_stdio_mode_orig(); return PYEXEC_FORCED_EXIT; } else if (ret == CHAR_CTRL_E) { // paste mode @@ -697,10 +717,13 @@ int pyexec_friendly_repl(void) { } } + // Switch to original terminal mode to execute code, eg to support keyboard interrupt (SIGINT). + mp_hal_stdio_mode_orig(); ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); if (ret & PYEXEC_FORCED_EXIT) { return ret; } + mp_hal_stdio_mode_raw(); } } From 5f815b8a2d15a31298386d58eff5ac3c4d0fba92 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 5 Nov 2025 16:35:31 +1100 Subject: [PATCH 1513/2098] unix: Enable exit code handling for sys.exit(). Enable `MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING` to propagate `sys.exit()` exit codes properly. Update `convert_pyexec_result()` to handle return values where pyexec returns the exit code with `PYEXEC_FORCED_EXIT` flag set for `SystemExit`. Extract the exit code from the lower 8 bits when the flag is set, otherwise return as-is (0 for success, 1 for exception). Signed-off-by: Andrew Leech --- ports/unix/main.c | 12 ++++++++++++ ports/unix/mpconfigport.h | 3 +++ ports/windows/mpconfigport.h | 3 +++ shared/runtime/pyexec.c | 1 + 4 files changed, 19 insertions(+) diff --git a/ports/unix/main.c b/ports/unix/main.c index 55fd8406ba6..ecc6924494b 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -242,6 +242,17 @@ static int do_repl(void) { } static inline int convert_pyexec_result(int ret) { + #if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING + // With exit code handling enabled: + // pyexec returns exit code with PYEXEC_FORCED_EXIT flag set for SystemExit + // Unix port expects: 0 for success, non-zero for error/exit + if (ret & PYEXEC_FORCED_EXIT) { + // SystemExit: extract exit code from lower bits + return ret & 0xFF; + } + // Normal execution or exception: return as-is (0 for success, 1 for exception) + return ret; + #else // pyexec returns 1 for success, 0 for exception, PYEXEC_FORCED_EXIT for SystemExit // Convert to unix port's expected codes: 0 for success, 1 for exception, FORCED_EXIT|val for SystemExit if (ret == 1) { @@ -251,6 +262,7 @@ static inline int convert_pyexec_result(int ret) { } else { return 1; // exception } + #endif } static int do_file(const char *file) { diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 562c93f99bd..854da1dbd42 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -154,6 +154,9 @@ typedef long mp_off_t; // Enable support for compile-only mode. #define MICROPY_PYEXEC_COMPILE_ONLY (1) +// Enable handling of sys.exit() exit codes. +#define MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (1) + #define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) // Bare-metal ports don't have stderr. Printing debug to stderr may give tests diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 24c54438e6e..1edcd0eded1 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -164,6 +164,9 @@ // Enable support for compile-only mode. #define MICROPY_PYEXEC_COMPILE_ONLY (1) +// Enable handling of sys.exit() exit codes. +#define MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (1) + #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) #define MICROPY_ERROR_PRINTER (&mp_stderr_print) #define MICROPY_WARNINGS (1) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index d2a1403e381..428cc95b232 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -165,6 +165,7 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input #endif if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // system exit #if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING + // None is an exit value of 0; an int is its value; anything else is 1 mp_obj_t val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val)); if (val != mp_const_none) { if (mp_obj_is_int(val)) { From a6864109db645045790d97b2e2b471d24a9a0560 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 5 Nov 2025 16:35:41 +1100 Subject: [PATCH 1514/2098] shared/runtime/pyexec: Set PYEXEC_FORCED_EXIT flag for SystemExit. When `MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING` is enabled, `SystemExit` now sets the `PYEXEC_FORCED_EXIT` flag in addition to the exit code. This allows the REPL to properly detect and exit when SystemExit is raised, while still preserving the exit code in the lower bits. Fixes `repl_lock.py` test which expects REPL to exit on `SystemExit`. Signed-off-by: Andrew Leech --- shared/runtime/pyexec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 428cc95b232..867bf58ab68 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -178,6 +178,8 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input } else { ret = PYEXEC_NORMAL_EXIT; } + // Set PYEXEC_FORCED_EXIT flag so REPL knows to exit + ret |= PYEXEC_FORCED_EXIT; #else ret = PYEXEC_FORCED_EXIT; #endif From db8273def84510d71cd4d3017b345d83fe8b90bc Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 21 Nov 2025 09:18:20 +0100 Subject: [PATCH 1515/2098] shared/tinyusb: Add macro to override TinyUSB callbacks. Add wrapper macros that by default expand to the callback name. Users can define this macro to add a prefix (e.g., mp_) to callback implementations, to redirect or completely override MicroPython's TinyUSB callbacks. Signed-off-by: iabdalkader --- shared/tinyusb/mp_usbd.c | 2 +- shared/tinyusb/mp_usbd.h | 16 ++++++++++++++++ shared/tinyusb/mp_usbd_cdc.c | 6 +++--- shared/tinyusb/mp_usbd_cdc.h | 4 +++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c index f03f7f7db41..a21e5f027bb 100644 --- a/shared/tinyusb/mp_usbd.c +++ b/shared/tinyusb/mp_usbd.c @@ -44,7 +44,7 @@ void mp_usbd_task_callback(mp_sched_node_t *node) { #endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE // Schedule the TinyUSB task on demand, when there is a new USB device event -TU_ATTR_FAST_FUNC void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { +TU_ATTR_FAST_FUNC void MICROPY_WRAP_TUD_EVENT_HOOK_CB(tud_event_hook_cb)(uint8_t rhport, uint32_t eventid, bool in_isr) { mp_usbd_schedule_task(); mp_hal_wake_main_task_from_isr(); } diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index b4655553673..b482ec4306e 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -29,6 +29,22 @@ #include "py/mpconfig.h" +#ifndef MICROPY_WRAP_TUD_SOF_CB +#define MICROPY_WRAP_TUD_SOF_CB(name) name +#endif + +#ifndef MICROPY_WRAP_TUD_CDC_RX_CB +#define MICROPY_WRAP_TUD_CDC_RX_CB(name) name +#endif + +#ifndef MICROPY_WRAP_TUD_CDC_LINE_STATE_CB +#define MICROPY_WRAP_TUD_CDC_LINE_STATE_CB(name) name +#endif + +#ifndef MICROPY_WRAP_TUD_EVENT_HOOK_CB +#define MICROPY_WRAP_TUD_EVENT_HOOK_CB(name) name +#endif + #if MICROPY_HW_ENABLE_USBDEV #include "py/obj.h" diff --git a/shared/tinyusb/mp_usbd_cdc.c b/shared/tinyusb/mp_usbd_cdc.c index 9b380acefc0..961bdf89896 100644 --- a/shared/tinyusb/mp_usbd_cdc.c +++ b/shared/tinyusb/mp_usbd_cdc.c @@ -68,7 +68,7 @@ uintptr_t mp_usbd_cdc_poll_interfaces(uintptr_t poll_flags) { return ret; } -void tud_cdc_rx_cb(uint8_t itf) { +void MICROPY_WRAP_TUD_CDC_RX_CB(tud_cdc_rx_cb)(uint8_t itf) { // consume pending USB data immediately to free usb buffer and keep the endpoint from stalling. // in case the ringbuffer is full, mark the CDC interface that need attention later on for polling cdc_itf_pending &= ~(1 << itf); @@ -141,7 +141,7 @@ mp_uint_t mp_usbd_cdc_tx_strn(const char *str, mp_uint_t len) { return i; } -void tud_sof_cb(uint32_t frame_count) { +void MICROPY_WRAP_TUD_SOF_CB(tud_sof_cb)(uint32_t frame_count) { if (--cdc_connected_flush_delay < 0) { // Finished on-connection delay, disable SOF interrupt again. tud_sof_cb_enable(false); @@ -172,7 +172,7 @@ static struct { } prev_line_state = {0}; #endif -void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { +void MICROPY_WRAP_TUD_CDC_LINE_STATE_CB(tud_cdc_line_state_cb)(uint8_t itf, bool dtr, bool rts) { #if MICROPY_HW_USB_CDC && !MICROPY_EXCLUDE_SHARED_TINYUSB_USBD_CDC if (dtr) { // A host application has started to open the cdc serial port. diff --git a/shared/tinyusb/mp_usbd_cdc.h b/shared/tinyusb/mp_usbd_cdc.h index 8d37a77315d..6d653a1d09b 100644 --- a/shared/tinyusb/mp_usbd_cdc.h +++ b/shared/tinyusb/mp_usbd_cdc.h @@ -27,6 +27,8 @@ #ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_CDC_H #define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_CDC_H +#include "mp_usbd.h" + #ifndef MICROPY_HW_USB_CDC_TX_TIMEOUT #define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) #endif @@ -38,7 +40,7 @@ #endif uintptr_t mp_usbd_cdc_poll_interfaces(uintptr_t poll_flags); -void tud_cdc_rx_cb(uint8_t itf); +void MICROPY_WRAP_TUD_CDC_RX_CB(tud_cdc_rx_cb)(uint8_t itf); mp_uint_t mp_usbd_cdc_tx_strn(const char *str, mp_uint_t len); #endif // MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_CDC_H From c9af4e1a7e4820bd6702902b415a9a89942d2844 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sun, 9 Nov 2025 13:16:07 -0500 Subject: [PATCH 1516/2098] tools/mpy-tool.py: Add Compiler Explorer JSON output. This commit adds a `--json` option to `mpy-tool.py`, in order to generate Compiler-Explorer-compatible JSON annotation information for the bytecode disassembly. Some of this information might be theoretically possible to parse out from the text itself, but not all of it is, e.g. disambiguating child references with non-unique simple names. Signed-off-by: Anson Mansfield --- tools/mpy-tool.py | 296 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 280 insertions(+), 16 deletions(-) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 8fab1c96975..67e2cbf159c 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -24,6 +24,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import io import struct import sys from binascii import hexlify @@ -302,6 +303,25 @@ class Opcode: MP_BC_POP_JUMP_IF_TRUE, MP_BC_POP_JUMP_IF_FALSE, ) + ALL_OFFSET = ( + MP_BC_UNWIND_JUMP, + MP_BC_JUMP, + MP_BC_POP_JUMP_IF_TRUE, + MP_BC_POP_JUMP_IF_FALSE, + MP_BC_JUMP_IF_TRUE_OR_POP, + MP_BC_JUMP_IF_FALSE_OR_POP, + MP_BC_SETUP_WITH, + MP_BC_SETUP_EXCEPT, + MP_BC_SETUP_FINALLY, + MP_BC_POP_EXCEPT_JUMP, + MP_BC_FOR_ITER, + ) + ALL_WITH_CHILD = ( + MP_BC_MAKE_FUNCTION, + MP_BC_MAKE_FUNCTION_DEFARGS, + MP_BC_MAKE_CLOSURE, + MP_BC_MAKE_CLOSURE_DEFARGS, + ) # Create a dict mapping opcode value to opcode name. mapping = ["unknown" for _ in range(256)] @@ -896,7 +916,7 @@ def __init__(self, parent_name, qstr_table, fun_data, prelude_offset, code_kind) self.escaped_name = unique_escaped_name def disassemble_children(self): - print(" children:", [rc.simple_name.str for rc in self.children]) + self.print_children_annotated() for rc in self.children: rc.disassemble() @@ -985,6 +1005,75 @@ def freeze_raw_code(self, prelude_ptr=None, type_sig=0): raw_code_count += 1 raw_code_content += 4 * 4 + @staticmethod + def decode_lineinfo(line_info: memoryview) -> "tuple[int, int, memoryview]": + c = line_info[0] + if (c & 0x80) == 0: + # 0b0LLBBBBB encoding + return (c & 0x1F), (c >> 5), line_info[1:] + else: + # 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte) + return (c & 0xF), (((c << 4) & 0x700) | line_info[1]), line_info[2:] + + def get_source_annotation(self, ip: int, file=None) -> dict: + bc_offset = ip - self.offset_opcodes + try: + line_info = memoryview(self.fun_data)[self.offset_line_info : self.offset_opcodes] + except AttributeError: + return {"file": file, "line": None} + + source_line = 1 + while line_info: + bc_increment, line_increment, line_info = self.decode_lineinfo(line_info) + if bc_offset >= bc_increment: + bc_offset -= bc_increment + source_line += line_increment + else: + break + + return {"file": file, "line": source_line} + + def get_label(self, ip: "int | None" = None, child_num: "int | None" = None) -> str: + if ip is not None: + assert child_num is None + return "%s.%d" % (self.escaped_name, ip) + elif child_num is not None: + return "%s.child%d" % (self.escaped_name, child_num) + else: + return "%s" % self.escaped_name + + def print_children_annotated(self) -> None: + """ + Equivalent to `print(" children:", [child.simple_name.str for child in self.children])`, + but also includes json markers for the start and end of each one's name in that line. + """ + + labels = ["%s.children" % self.escaped_name] + annotation_labels = [] + output = io.StringIO() + output.write(" children: [") + sep = ", " + for i, child in enumerate(self.children): + if i != 0: + output.write(sep) + start_col = output.tell() + 1 + output.write(child.simple_name.str) + end_col = output.tell() + 1 + labels.append(self.get_label(child_num=i)) + annotation_labels.append( + { + "name": self.get_label(child_num=i), + "target": child.get_label(), + "range": { + "startCol": start_col, + "endCol": end_col, + }, + }, + ) + output.write("]") + + print(output.getvalue(), annotations={"labels": annotation_labels}, labels=labels) + class RawCodeBytecode(RawCode): def __init__(self, parent_name, qstr_table, obj_table, fun_data): @@ -993,9 +1082,58 @@ def __init__(self, parent_name, qstr_table, obj_table, fun_data): parent_name, qstr_table, fun_data, 0, MP_CODE_BYTECODE ) + def get_opcode_annotations_labels( + self, opcode: int, ip: int, arg: int, sz: int, arg_pos: int, arg_len: int + ) -> "tuple[dict, list[str]]": + annotations = { + "source": self.get_source_annotation(ip), + "disassembly": Opcode.mapping[opcode], + } + labels = [self.get_label(ip)] + + if opcode in Opcode.ALL_OFFSET: + annotations["link"] = { + "offset": arg_pos, + "length": arg_len, + "to": ip + arg + sz, + } + annotations["labels"] = [ + { + "name": self.get_label(ip), + "target": self.get_label(ip + arg + sz), + "range": { + "startCol": arg_pos + 1, + "endCol": arg_pos + arg_len + 1, + }, + }, + ] + + elif opcode in Opcode.ALL_WITH_CHILD: + try: + child = self.children[arg] + except IndexError: + # link out-of-range child to the child array itself + target = "%s.children" % self.escaped_name + else: + # link resolvable child to the actual child + target = child.get_label() + + annotations["labels"] = [ + { + "name": self.get_label(ip), + "target": target, + "range": { + "startCol": arg_pos + 1, + "endCol": arg_pos + arg_len + 1, + }, + }, + ] + + return annotations, labels + def disassemble(self): bc = self.fun_data - print("simple_name:", self.simple_name.str) + print("simple_name:", self.simple_name.str, labels=[self.get_label()]) print(" raw bytecode:", len(bc), hexlify_to_str(bc)) print(" prelude:", self.prelude_signature) print(" args:", [self.qstr_table[i].str for i in self.names[1:]]) @@ -1011,9 +1149,22 @@ def disassemble(self): pass else: arg = "" - print( - " %-11s %s %s" % (hexlify_to_str(bc[ip : ip + sz]), Opcode.mapping[bc[ip]], arg) + + pre_arg_part = " %-11s %s" % ( + hexlify_to_str(bc[ip : ip + sz]), + Opcode.mapping[bc[ip]], + ) + arg_part = "%s" % arg + annotations, labels = self.get_opcode_annotations_labels( + opcode=bc[ip], + ip=ip, + arg=arg, + sz=sz, + arg_pos=len(pre_arg_part) + 1, + arg_len=len(arg_part), ) + + print(pre_arg_part, arg_part, annotations=annotations, labels=labels) ip += sz self.disassemble_children() @@ -1114,7 +1265,7 @@ def __init__( def disassemble(self): fun_data = self.fun_data - print("simple_name:", self.simple_name.str) + print("simple_name:", self.simple_name.str, labels=[self.get_label()]) print( " raw data:", len(fun_data), @@ -1833,6 +1984,100 @@ def extract_segments(compiled_modules, basename, kinds_arg): output.write(source.read(segment.end - segment.start)) +class PrintShim: + """Base class for interposing extra functionality onto the global `print` method.""" + + def __init__(self): + self.wrapped_print = None + + def __enter__(self): + global print + + if self.wrapped_print is not None: + raise RecursionError + + self.wrapped_print = print + print = self + + return self + + def __exit__(self, exc_type, exc_value, traceback): + global print + + if self.wrapped_print is None: + return + + print = self.wrapped_print + self.wrapped_print = None + + self.on_exit() + + def on_exit(self): + pass + + def __call__(self, *a, **k): + return self.wrapped_print(*a, **k) + + +class PrintIgnoreExtraArgs(PrintShim): + """Just strip the `annotations` and `labels` kwargs and pass down to the underlying print.""" + + def __call__(self, *a, annotations: dict = {}, labels: "list[str]" = (), **k): + return super().__call__(*a, **k) + + +class PrintJson(PrintShim): + """Output lines as godbolt-compatible JSON with extra annotation info from `annotations` and `labels`, rather than plain text.""" + + def __init__(self, fp=sys.stdout, language_id: str = "mpy"): + super().__init__() + self.fp = fp + self.asm = { + "asm": [], + "labelDefinitions": {}, + "languageId": language_id, + } + self.line_number: int = 0 + self.buf: "io.StringIO | None" = None + + def on_exit(self): + import json + + if self.buf is not None: + # flush last partial line + self.__call__() + + json.dump(self.asm, self.fp) + + def __call__(self, *a, annotations: dict = {}, labels: "list[str]" = (), **k): + # ignore prints directed to an explicit output + if "file" in k: + return super().__call__(*a, **k) + + if self.buf is None: + self.buf = io.StringIO() + + super().__call__(*a, file=sys.stderr, **k) + + if "end" in k: + # buffer partial-line prints to collect into a single AsmResultLine + return super().__call__(*a, file=self.buf, **k) + else: + retval = super().__call__(*a, file=self.buf, end="", **k) + output = self.buf.getvalue() + self.buf = None + + asm_line = {"text": output} + asm_line.update(annotations) + self.asm["asm"].append(asm_line) + + self.line_number += 1 + for label in labels: + self.asm["labelDefinitions"][label] = self.line_number + + return retval + + def main(args=None): global global_qstrs @@ -1846,6 +2091,12 @@ def main(args=None): "-d", "--disassemble", action="store_true", help="output disassembled contents of files" ) cmd_parser.add_argument("-f", "--freeze", action="store_true", help="freeze files") + cmd_parser.add_argument( + "-j", + "--json", + action="store_true", + help="output hexdump, disassembly, and frozen code as JSON with extra metadata", + ) cmd_parser.add_argument( "--merge", action="store_true", help="merge multiple .mpy files into one" ) @@ -1913,20 +2164,33 @@ def main(args=None): print(er, file=sys.stderr) sys.exit(1) - if args.hexdump: - hexdump_mpy(compiled_modules) + if args.json: + if args.freeze: + print_shim = PrintJson(sys.stdout, language_id="c") + elif args.hexdump: + print_shim = PrintJson(sys.stdout, language_id="stderr") + elif args.disassemble: + print_shim = PrintJson(sys.stdout, language_id="mpy") + else: + print_shim = PrintJson(sys.stdout) + else: + print_shim = PrintIgnoreExtraArgs() - if args.disassemble: + with print_shim: if args.hexdump: - print() - disassemble_mpy(compiled_modules) + hexdump_mpy(compiled_modules) - if args.freeze: - try: - freeze_mpy(firmware_qstr_idents, compiled_modules) - except FreezeError as er: - print(er, file=sys.stderr) - sys.exit(1) + if args.disassemble: + if args.hexdump: + print() + disassemble_mpy(compiled_modules) + + if args.freeze: + try: + freeze_mpy(firmware_qstr_idents, compiled_modules) + except FreezeError as er: + print(er, file=sys.stderr) + sys.exit(1) if args.merge: merge_mpy(compiled_modules, args.output) From 25dde8397fc26321bebf24a70cee0ce85de8e3cf Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 12 Nov 2025 16:12:03 +1100 Subject: [PATCH 1517/2098] nrf/drivers/usb: Provide macros for nrf errata. Signed-off-by: Damien George --- ports/nrf/drivers/usb/tusb_config.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/nrf/drivers/usb/tusb_config.h b/ports/nrf/drivers/usb/tusb_config.h index 935d9c22913..231fe2b86c1 100644 --- a/ports/nrf/drivers/usb/tusb_config.h +++ b/ports/nrf/drivers/usb/tusb_config.h @@ -31,4 +31,11 @@ // Device configuration #define CFG_TUSB_MCU OPT_MCU_NRF5X +// TinyUSB uses newer style errata functions, but we currently don't have the +// latest nrfx component. So provide macros mapping the new name to the old. +#include "nrfx_usbd_errata.h" +#define nrf52_errata_166 nrfx_usbd_errata_166 +#define nrf52_errata_171 nrfx_usbd_errata_171 +#define nrf52_errata_187 nrfx_usbd_errata_187 + #endif // MICROPY_INCLUDED_NRF_TUSB_CONFIG_H From 7059e0769e5b53ae838ec4988ed70b246cc2b2fb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 12 Nov 2025 14:22:23 +1100 Subject: [PATCH 1518/2098] lib/tinyusb: Update to version 0.19.0-24. This is 0.19.0 plus the following changes: - remove obsolete dcd_esp32sx - fix for HID stylus descriptor and HID example - typos, docs and generator scripts - MTP fix - DWC2 enumeration when EP0 size=8 - DWC2 fix for EP0 IN - stm32 FSDEV IRQ remapping fix - DWC2 ZLP fix The reason we need the extra 24 commits is due to a bug with TinyUSB's handling of zero-length-packets in the DWC2 (Synopsis) backend, which affects the stm32 port. That's fixed by https://github.com/hathach/tinyusb/pull/3293 Signed-off-by: Damien George --- lib/tinyusb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tinyusb b/lib/tinyusb index 86ad6e56c17..aa0fc2e08f1 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit 86ad6e56c1700e85f1c5678607a762cfe3aa2f47 +Subproject commit aa0fc2e08f1c2dd6f026a431e8989357fbb4c5bf From 207562dfab728211a59503eccec924e68a2b233f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 17 Nov 2025 15:18:07 +1100 Subject: [PATCH 1519/2098] shared/tinyusb: Remove macro guard for tx_overwritabe_if_not_connected. It's now available in the version of TinyUSB used by this repository. Also, update to the newer `tud_cdc_configure_t` struct name and newer `tud_cdc_configure()` function name. Signed-off-by: Damien George --- shared/tinyusb/mp_usbd.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index b482ec4306e..311575b3ad8 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -61,19 +61,12 @@ static inline void mp_usbd_init_tud(void) { tusb_init(); #if MICROPY_HW_USB_CDC - tud_cdc_configure_fifo_t cfg = { .rx_persistent = 0, - .tx_persistent = 1, - - // This config flag is unreleased in TinyUSB >v0.18.0 - // but included in Espressif's TinyUSB component since v0.18.0~3 - // - // Versioning issue reported as - // https://github.com/espressif/esp-usb/issues/236 - #if TUSB_VERSION_NUMBER > 1800 || defined(ESP_PLATFORM) - .tx_overwritabe_if_not_connected = 1, - #endif + tud_cdc_configure_t cfg = { + .rx_persistent = 0, + .tx_persistent = 1, + .tx_overwritabe_if_not_connected = 1, }; - tud_cdc_configure_fifo(&cfg); + tud_cdc_configure(&cfg); #endif } From be8d5fc27b30ade25bdd272368fd8b91e4d4b338 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 29 Jul 2025 10:23:31 +1000 Subject: [PATCH 1520/2098] esp32/README: Update the README details to account for newer chips. Noted while adding C2 support that some of these comments are a bit out of date. Spun out to its own commit, and also mention C5 as well. This change also adds some recommendation on which ESP32 board to pick, as we occasionally see issues or questions that would be non-issues on a board with more RAM (and for small production or personal projects the savings of picking a cheaper ESP32 chip are basically neglible). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/README.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 5a2bb9240d9..55964febeaf 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -5,16 +5,17 @@ This is a port of MicroPython to the Espressif ESP32 series of microcontrollers. It uses the ESP-IDF framework and MicroPython runs as a task under FreeRTOS. -Currently supports ESP32, ESP32-C2 (aka ESP8684), ESP32-C3, ESP32-C6, ESP32-S2 -and ESP32-S3 (ESP8266 is supported by a separate MicroPython port). +Currently supports ESP32, ESP32-C2 (aka ESP8684), ESP32-C3, ESP32-C5, ESP32-C6, +ESP32-S2 and ESP32-S3. ESP8266 is supported by a separate MicroPython port. Supported features include: -- REPL (Python prompt) over UART0. -- 16k stack for the MicroPython task and approximately 100k Python heap. +- REPL (Python prompt) over UART0 and/or the integrated USB peripheral. - Many of MicroPython's features are enabled: unicode, arbitrary-precision integers, single-precision floats, complex numbers, frozen bytecode, as well as many of the internal modules. -- Internal filesystem using the flash (currently 2M in size). +- Internal filesystem using the remaining flash area. +- Threading support via the _thread module (built on native FreeRTOS tasks), + with GIL enabled. - The machine module with GPIO, UART, SPI, software I2C, ADC, DAC, PWM, TouchPad, WDT and Timer. - The network module with WLAN (WiFi) support. @@ -22,6 +23,26 @@ Supported features include: Initial development of this ESP32 port was sponsored in part by Microbric Pty Ltd. +Choosing a suitable chip +------------------------ + +ESP32 chips are not all the same. The different ESP32 families have different +capabilities and resources available. In particular, the ESP32-C2 and ESP32-S2 +(without external SPIRAM) have the least RAM. They can still run MicroPython +well but may run out of RAM if a program uses a lot of resources, eg if it +needs many complex code modules, multiple TLS connections, large memory +buffers for a display, etc. + +ESP32 boards with external "SPIRAM" (also called PSRAM) have megabytes of RAM +available - significantly more than any board without external SPIRAM. SPIRAM is +supported on original ESP32, ESP32-S2 and ESP32-S3 chips. Not every ESP32 board +has SPIRAM included, even if the chip supports it. + +If you don't need particular hardware features but are looking for a board with +the usual Wi-Fi and BLE support, many peripherals and plenty of RAM then +recommend finding a board based on ESP32-S3 with SPIRAM included (usually 2MB, +4MB or 8MB). + Setting up ESP-IDF and the build environment -------------------------------------------- From de2ace72e943c8b38d77b7d68e8fe73e48459f76 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 19 Nov 2025 17:59:25 +1100 Subject: [PATCH 1521/2098] esp32/modespnow: Fix espnow rate setting. Only set the rate on interfaces that are active. It seems ESP-IDF 5.4.x or so added checks that the interface is enabled, whereas previous versions silently did nothing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/modespnow.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ports/esp32/modespnow.c b/ports/esp32/modespnow.c index ab50032ffeb..5d048f6b2c7 100644 --- a/ports/esp32/modespnow.c +++ b/ports/esp32/modespnow.c @@ -187,6 +187,16 @@ static void send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t st static void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len); +// Return the current wifi mode, or raise ValueError +static wifi_mode_t get_wifi_mode(void) { + wifi_mode_t mode; + if (esp_wifi_get_mode(&mode) != ESP_OK || mode == WIFI_MODE_NULL) { + // network.WLAN STA or AP must be set active(True) before ESP-NOW can be used + mp_raise_OSError(MP_ENOENT); + } + return mode; +} + // ESPNow.init(): Initialise the data buffers and ESP-NOW functions. // Initialise the Espressif ESPNOW software stack, register callbacks and // allocate the recv data buffers. @@ -197,7 +207,6 @@ static mp_obj_t espnow_init(mp_obj_t _) { self->recv_buffer = m_new_obj(ringbuf_t); ringbuf_alloc(self->recv_buffer, self->recv_buffer_size); - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c check_esp_err(esp_now_init()); check_esp_err(esp_now_register_recv_cb(recv_cb)); check_esp_err(esp_now_register_send_cb(send_cb)); @@ -260,9 +269,13 @@ static mp_obj_t espnow_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t self->recv_timeout_ms = args[ARG_timeout_ms].u_int; } if (args[ARG_rate].u_int >= 0) { - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, args[ARG_rate].u_int)); - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, args[ARG_rate].u_int)); + wifi_mode_t mode = get_wifi_mode(); + if (mode == WIFI_MODE_STA || mode == WIFI_MODE_APSTA) { + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, args[ARG_rate].u_int)); + } + if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA) { + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, args[ARG_rate].u_int)); + } } if (args[ARG_get].u_obj == MP_OBJ_NULL) { return mp_const_none; From 7e10d22134dabf41e2208aea292774c852d95d13 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 20 Nov 2025 12:52:56 +1100 Subject: [PATCH 1522/2098] esp32,docs: Add constants and documentation for espnow data rates. Changes are: - Add constants for some of the supported ESP-NOW data rates. - Add constants for switching an ESP32 WLAN radio in/out of Long Range mode. - Document the new constants and their usage. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/espnow.rst | 49 ++++++++++++++++++++++++++++++++--- docs/library/network.WLAN.rst | 32 +++++++++++++++++++++++ ports/esp32/modespnow.c | 16 ++++++++++++ ports/esp32/network_wlan.c | 14 ++++++++++ 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index 84e9e9465de..4b4b058f87a 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -164,13 +164,15 @@ Configuration wait forever. The timeout can also be provided as arg to `recv()`/`irecv()`/`recvinto()`. - *rate*: (ESP32 only) Set the transmission speed for - ESPNow packets. Must be set to a number from the allowed numeric values - in `enum wifi_phy_rate_t - `_. This parameter is actually *write-only* due to ESP-IDF not providing any means for querying the radio interface's rate parameter. + See also `espnow-long-range`. .. data:: Returns: @@ -574,6 +576,45 @@ Constants espnow.MAX_TOTAL_PEER_NUM(=20) espnow.MAX_ENCRYPT_PEER_NUM(=6) +The following constants correspond to different transmit data rates on ESP32 +only. Lower data rates are generally more reliable over long distances: + +.. data:: espnow.RATE_LORA_250K + espnow.RATE_LORA_500K + + See `espnow-long-range`. + +.. data:: espnow.RATE_1M + espnow.RATE_2M + espnow.RATE_5M + espnow.RATE_6M + espnow.RATE_11M + espnow.RATE_12M + espnow.RATE_24M + espnow.RATE_54M + +Unless using the two proprietary long range data rates, only the sender must +configure the data rate. + +.. _espnow-long-range: + +Long Range Mode +--------------- + +(ESP32 Only, except ESP32-C2) + +To use the `espnow.RATE_LORA_250K` and `espnow.RATE_LORA_500K` data rates, +first set the `WLAN` interface object to long-range mode, i.e.:: + + import network, espnow + sta = network.WLAN(network.WLAN.IF_STA) + sta.active(True) + sta.config(channel=6, protocol=WLAN.PROTOCOL_LR) # Set on sender & receiver + e = espnow.ESPNow() + e.config(rate=espnow.RATE_LORA_250K) # Needed on sender only + +For more information about the limitations of long-range mode, see `WLAN.PROTOCOL_LR`. + Exceptions ---------- diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index ee0ef490fda..27d6b383a36 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -145,6 +145,7 @@ Methods reconnects Number of reconnect attempts to make (integer, 0=none, -1=unlimited) txpower Maximum transmit power in dBm (integer or float) pm WiFi Power Management setting (see below for allowed values) + protocol (ESP32 Only.) WiFi Low level 802.11 protocol. See `WLAN.PROTOCOL_DEFAULTS`. ============= =========== Constants @@ -161,3 +162,34 @@ Constants * ``PM_POWERSAVE``: enable WiFi power management with additional power savings and reduced WiFi performance * ``PM_NONE``: disable wifi power management + + +ESP32 Protocol Constants +------------------------ + +The following ESP32-only constants relate to the ``WLAN.config(protocol=...)`` +network interface parameter: + +.. data:: WLAN.PROTOCOL_DEFAULTS + + A bitmap representing all of the default 802.11 Wi-Fi modes supported by + the chip. Consult `ESP-IDF Wi-Fi Protocols`_ documentation for details. + +.. data:: WLAN.PROTOCOL_LR + + This value corresponds to the `Espressif proprietary "long-range" mode`_, + which is not compatible with standard Wi-Fi devices. By setting this + protocol it's possible for an ESP32 STA in long-range mode to connect to + an ESP32 AP in long-range mode, or to use `ESP-NOW long range modes + `. + + This mode can be bitwise ORed with some standard 802.11 protocol bits + (including `WLAN.PROTOCOL_DEFAULTS`) in order to support a mix of standard + Wi-Fi modes as well as LR mode, consult the `Espressif long-range + documentation`_ for more details. + + Long range mode is not supported on ESP32-C2. + +.. _ESP-IDF Wi-Fi Protocols: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/wifi.html#wi-fi-protocol-mode +.. _Espressif proprietary "long-range" mode: +.. _Espressif long-range documentation: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/wifi.html#long-range-lr diff --git a/ports/esp32/modespnow.c b/ports/esp32/modespnow.c index 5d048f6b2c7..725374ea77c 100644 --- a/ports/esp32/modespnow.c +++ b/ports/esp32/modespnow.c @@ -817,6 +817,22 @@ static const mp_rom_map_elem_t espnow_globals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_KEY_LEN), MP_ROM_INT(ESP_NOW_KEY_LEN)}, { MP_ROM_QSTR(MP_QSTR_MAX_TOTAL_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_TOTAL_PEER_NUM)}, { MP_ROM_QSTR(MP_QSTR_MAX_ENCRYPT_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_ENCRYPT_PEER_NUM)}, + + #if !CONFIG_IDF_TARGET_ESP32C2 + { MP_ROM_QSTR(MP_QSTR_RATE_LORA_250K), MP_ROM_INT(WIFI_PHY_RATE_LORA_250K)}, + { MP_ROM_QSTR(MP_QSTR_RATE_LORA_500K), MP_ROM_INT(WIFI_PHY_RATE_LORA_500K)}, + #endif + // Note: specifying long preamble versions for the lower bit rates apart + // from the non-802.11b 6Mbit rate, for more robust error correction + { MP_ROM_QSTR(MP_QSTR_RATE_1M), MP_ROM_INT(WIFI_PHY_RATE_1M_L)}, + { MP_ROM_QSTR(MP_QSTR_RATE_2M), MP_ROM_INT(WIFI_PHY_RATE_2M_L)}, + { MP_ROM_QSTR(MP_QSTR_RATE_5M), MP_ROM_INT(WIFI_PHY_RATE_5M_L)}, + { MP_ROM_QSTR(MP_QSTR_RATE_6M), MP_ROM_INT(WIFI_PHY_RATE_6M)}, + { MP_ROM_QSTR(MP_QSTR_RATE_11M), MP_ROM_INT(WIFI_PHY_RATE_11M_L)}, + { MP_ROM_QSTR(MP_QSTR_RATE_12M), MP_ROM_INT(WIFI_PHY_RATE_12M)}, + { MP_ROM_QSTR(MP_QSTR_RATE_24M), MP_ROM_INT(WIFI_PHY_RATE_24M)}, + { MP_ROM_QSTR(MP_QSTR_RATE_54M), MP_ROM_INT(WIFI_PHY_RATE_54M)}, + }; static MP_DEFINE_CONST_DICT(espnow_globals_dict, espnow_globals_dict_table); diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index e20af4806c4..07b16e91cb9 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -79,6 +79,15 @@ static bool mdns_initialised = false; static uint8_t conf_wifi_sta_reconnects = 0; static uint8_t wifi_sta_reconnects; +// The rules for this default are defined in the documentation of esp_wifi_set_protocol() +// rather than in code, so we have to recreate them here. +#if CONFIG_SOC_WIFI_HE_SUPPORT +// Note: No Explicit support for 5GHz here, yet +#define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AX) +#else +#define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N) +#endif + // This function is called by the system-event task and so runs in a different // thread to the main MicroPython task. It must not raise any Python exceptions. static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { @@ -771,6 +780,11 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA_ENT), MP_ROM_INT(WIFI_AUTH_WPA_ENTERPRISE) }, #endif + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_DEFAULT), MP_ROM_INT(WIFI_PROTOCOL_DEFAULT) }, + #if !CONFIG_IDF_TARGET_ESP32C2 + { MP_ROM_QSTR(MP_QSTR_PROTOCOL_LR), MP_ROM_INT(WIFI_PROTOCOL_LR) }, + #endif + { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(WIFI_PS_MAX_MODEM) }, From 938e2c0f2b79c983202cbeccd0bc91e13eff13d2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 20 Nov 2025 13:01:42 +1100 Subject: [PATCH 1523/2098] tests/multi_espnow: Add test case for espnow rate changes. Uses constants added in previous commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/espnow.rst | 2 +- tests/multi_espnow/75_rate.py | 92 +++++++++++++++++++++++++++++++ tests/multi_espnow/75_rate.py.exp | 31 +++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/multi_espnow/75_rate.py create mode 100644 tests/multi_espnow/75_rate.py.exp diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index 4b4b058f87a..14a92c11400 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -172,7 +172,7 @@ Configuration api-reference/network/esp_wifi.html#_CPPv415wifi_phy_rate_t>`_. This parameter is actually *write-only* due to ESP-IDF not providing any means for querying the radio interface's rate parameter. - See also `espnow-long-range`. + See also `espnow-long-range`. This API currently doesn't work on ESP32-C6. .. data:: Returns: diff --git a/tests/multi_espnow/75_rate.py b/tests/multi_espnow/75_rate.py new file mode 100644 index 00000000000..9c05a24bd00 --- /dev/null +++ b/tests/multi_espnow/75_rate.py @@ -0,0 +1,92 @@ +# Test configuring transmit rates for ESP-NOW on ESP32 + +import sys +import time + +try: + import network + import espnow +except ImportError: + print("SKIP") + raise SystemExit + +# ESP8266 doesn't support multiple ESP-NOW data rates +if sys.platform == "esp8266": + print("SKIP") + raise SystemExit + +# Currently the config(rate=...) implementation is not compatible with ESP32-C6 +# (this test passes when C6 is receiver, but not if C6 is sender.) +if "ESP32C6" in sys.implementation._machine: + print("SKIP") + raise SystemExit + +# ESP32-C2 doesn't support Long Range mode. This test is currently written assuming +# LR mode can be enabled. +if "ESP32C2" in sys.implementation._machine: + print("SKIP") + raise SystemExit + + +timeout_ms = 1000 +default_pmk = b"MicroPyth0nRules" +CHANNEL = 9 + + +def init_sta(): + sta = network.WLAN(network.WLAN.IF_STA) + e = espnow.ESPNow() + e.active(True) + sta.active(True) + sta.disconnect() # Force AP disconnect for any saved config, important so the channel doesn't change + sta.config(channel=CHANNEL) + e.set_pmk(default_pmk) + # Enable both default 802.11 modes and Long Range modes + sta.config(protocol=network.WLAN.PROTOCOL_LR | network.WLAN.PROTOCOL_DEFAULT) + return sta, e + + +# Receiver +def instance0(): + sta, e = init_sta() + multitest.globals(PEER=sta.config("mac")) + multitest.next() + while True: + peer, msg = e.recv(timeout_ms) + if peer is None: + print("Timeout") + break + # Note that we don't have any way in Python to tell what data rate this message + # was received with, so we're assuming the rate was correct. + print(msg) + e.active(False) + + +# Sender +def instance1(): + sta, e = init_sta() + multitest.next() + peer = PEER + + e.add_peer(peer) + # Test normal, non-LR rates + for msg, rate in ( + (b"default rate", None), + (b"5Mbit", espnow.RATE_5M), + (b"11Mbit", espnow.RATE_11M), + (b"24Mbit", espnow.RATE_24M), + (b"54Mbit", espnow.RATE_54M), + (b"250K LR", espnow.RATE_LORA_250K), + (b"500K LR", espnow.RATE_LORA_500K), + # switch back to non-LR rates to check it's all OK + (b"1Mbit again", espnow.RATE_1M), + (b"11Mbit again", espnow.RATE_11M), + ): + if rate is not None: + e.config(rate=rate) + for _ in range(3): + e.send(peer, msg) + time.sleep_ms(50) # give messages some time to be received before continuing + e.del_peer(peer) + + e.active(False) diff --git a/tests/multi_espnow/75_rate.py.exp b/tests/multi_espnow/75_rate.py.exp new file mode 100644 index 00000000000..5a275ee6b28 --- /dev/null +++ b/tests/multi_espnow/75_rate.py.exp @@ -0,0 +1,31 @@ +--- instance0 --- +b'default rate' +b'default rate' +b'default rate' +b'5Mbit' +b'5Mbit' +b'5Mbit' +b'11Mbit' +b'11Mbit' +b'11Mbit' +b'24Mbit' +b'24Mbit' +b'24Mbit' +b'54Mbit' +b'54Mbit' +b'54Mbit' +b'250K LR' +b'250K LR' +b'250K LR' +b'500K LR' +b'500K LR' +b'500K LR' +b'1Mbit again' +b'1Mbit again' +b'1Mbit again' +b'11Mbit again' +b'11Mbit again' +b'11Mbit again' +Timeout +--- instance1 --- + From c22ff43753c0679d375db32c9f8752558c8d7511 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:06:26 +0000 Subject: [PATCH 1524/2098] github/workflows: Bump actions/checkout from 5 to 6. Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/biome.yml | 2 +- .github/workflows/code_formatting.yml | 2 +- .github/workflows/code_size.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/commit_formatting.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/examples.yml | 2 +- .github/workflows/mpremote.yml | 2 +- .github/workflows/mpy_format.yml | 2 +- .github/workflows/ports.yml | 2 +- .github/workflows/ports_alif.yml | 2 +- .github/workflows/ports_cc3200.yml | 2 +- .github/workflows/ports_esp32.yml | 2 +- .github/workflows/ports_esp8266.yml | 2 +- .github/workflows/ports_mimxrt.yml | 2 +- .github/workflows/ports_nrf.yml | 2 +- .github/workflows/ports_powerpc.yml | 2 +- .github/workflows/ports_qemu.yml | 6 ++-- .github/workflows/ports_renesas-ra.yml | 2 +- .github/workflows/ports_rp2.yml | 2 +- .github/workflows/ports_samd.yml | 2 +- .github/workflows/ports_stm32.yml | 2 +- .github/workflows/ports_unix.yml | 40 ++++++++++++------------- .github/workflows/ports_webassembly.yml | 2 +- .github/workflows/ports_windows.yml | 6 ++-- .github/workflows/ports_zephyr.yml | 2 +- .github/workflows/ruff.yml | 2 +- 27 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.github/workflows/biome.yml b/.github/workflows/biome.yml index fea9c9c8bd7..0cde10acc91 100644 --- a/.github/workflows/biome.yml +++ b/.github/workflows/biome.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Biome uses: biomejs/setup-biome@v2 with: diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 8a18ca03454..95653d941f9 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -10,7 +10,7 @@ jobs: code-formatting: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 - name: Install packages run: tools/ci.sh c_code_formatting_setup diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index f82d1d0c735..aed41676788 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -25,7 +25,7 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 100 - name: Install packages diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 688134b4251..c413a430a5b 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -6,7 +6,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # codespell version should be kept in sync with .pre-commit-config.yml - run: pip install --user codespell==2.4.1 tomli - run: codespell diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml index ca63fc796b0..6abc3612a00 100644 --- a/.github/workflows/commit_formatting.yml +++ b/.github/workflows/commit_formatting.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 100 - uses: actions/setup-python@v6 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index aa05cfb1387..79755b74197 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 - name: Install Python packages run: pip install -r docs/requirements.txt diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d16122b720b..4adeaae2e5e 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -18,7 +18,7 @@ jobs: embedding: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: make -C examples/embedding -f micropython_embed.mk && make -C examples/embedding - name: Run diff --git a/.github/workflows/mpremote.yml b/.github/workflows/mpremote.yml index 36904764c99..5b9f1ff130a 100644 --- a/.github/workflows/mpremote.yml +++ b/.github/workflows/mpremote.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Setting this to zero means fetch all history and tags, # which hatch-vcs can use to discover the version tag. diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index e692a853e7b..b51110db677 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -17,7 +17,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh mpy_format_setup - name: Test mpy-tool.py diff --git a/.github/workflows/ports.yml b/.github/workflows/ports.yml index d4e89bd1aa9..5e71d4d076a 100644 --- a/.github/workflows/ports.yml +++ b/.github/workflows/ports.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build ports download metadata run: mkdir boards && ./tools/autobuild/build-downloads.py . ./boards diff --git a/.github/workflows/ports_alif.yml b/.github/workflows/ports_alif.yml index eea8f53900e..6fb225937a9 100644 --- a/.github/workflows/ports_alif.yml +++ b/.github/workflows/ports_alif.yml @@ -26,7 +26,7 @@ jobs: - alif_ae3_build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh alif_setup - name: Build ci_${{matrix.ci_func }} diff --git a/.github/workflows/ports_cc3200.yml b/.github/workflows/ports_cc3200.yml index c57309c23a6..194483ec218 100644 --- a/.github/workflows/ports_cc3200.yml +++ b/.github/workflows/ports_cc3200.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh cc3200_setup - name: Build diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 578bf33af5b..eea82126d4f 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -32,7 +32,7 @@ jobs: - esp32_build_c2_c5_c6 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: idf_ver name: Read the ESP-IDF version (including Python version) diff --git a/.github/workflows/ports_esp8266.yml b/.github/workflows/ports_esp8266.yml index 96cf0c5a5cd..eb7f59cdc49 100644 --- a/.github/workflows/ports_esp8266.yml +++ b/.github/workflows/ports_esp8266.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh esp8266_setup && tools/ci.sh esp8266_path >> $GITHUB_PATH - name: Build diff --git a/.github/workflows/ports_mimxrt.yml b/.github/workflows/ports_mimxrt.yml index bcbaf3de066..fd80f3f6329 100644 --- a/.github/workflows/ports_mimxrt.yml +++ b/.github/workflows/ports_mimxrt.yml @@ -24,7 +24,7 @@ jobs: run: working-directory: 'micropython repo' # test build with space in path steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: path: 'micropython repo' - name: Install packages diff --git a/.github/workflows/ports_nrf.yml b/.github/workflows/ports_nrf.yml index 995f65933e0..bec9a5dfb5b 100644 --- a/.github/workflows/ports_nrf.yml +++ b/.github/workflows/ports_nrf.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh nrf_setup - name: Build diff --git a/.github/workflows/ports_powerpc.yml b/.github/workflows/ports_powerpc.yml index 27417fb3c3c..a883d026806 100644 --- a/.github/workflows/ports_powerpc.yml +++ b/.github/workflows/ports_powerpc.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh powerpc_setup - name: Build diff --git a/.github/workflows/ports_qemu.yml b/.github/workflows/ports_qemu.yml index b038b03e71f..0ed95dbe5f9 100644 --- a/.github/workflows/ports_qemu.yml +++ b/.github/workflows/ports_qemu.yml @@ -30,7 +30,7 @@ jobs: - thumb_hardfp runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh qemu_setup_arm - name: Build and run test suite ci_qemu_build_arm_${{ matrix.ci_func }} @@ -42,7 +42,7 @@ jobs: build_and_test_rv32: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh qemu_setup_rv32 - name: Build and run test suite @@ -54,7 +54,7 @@ jobs: build_and_test_rv64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh qemu_setup_rv64 - name: Build and run test suite diff --git a/.github/workflows/ports_renesas-ra.yml b/.github/workflows/ports_renesas-ra.yml index 600b8ea8046..920691eca70 100644 --- a/.github/workflows/ports_renesas-ra.yml +++ b/.github/workflows/ports_renesas-ra.yml @@ -21,7 +21,7 @@ jobs: build_renesas_ra_board: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh renesas_ra_setup - name: Build diff --git a/.github/workflows/ports_rp2.yml b/.github/workflows/ports_rp2.yml index 0837c06c97b..ea19e2da7ff 100644 --- a/.github/workflows/ports_rp2.yml +++ b/.github/workflows/ports_rp2.yml @@ -24,7 +24,7 @@ jobs: run: working-directory: 'micropython repo' # test build with space in path steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: path: 'micropython repo' - name: Install packages diff --git a/.github/workflows/ports_samd.yml b/.github/workflows/ports_samd.yml index d159fde175d..eb806ceb044 100644 --- a/.github/workflows/ports_samd.yml +++ b/.github/workflows/ports_samd.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh samd_setup - name: Build diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index eae3bae871f..45aa0c61304 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -28,7 +28,7 @@ jobs: - stm32_misc_build runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh stm32_setup - name: Build ci_${{matrix.ci_func }} diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index deee7b265fc..19d6dcf8565 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -23,7 +23,7 @@ jobs: minimal: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: tools/ci.sh unix_minimal_build - name: Run main test suite @@ -35,7 +35,7 @@ jobs: reproducible: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build with reproducible date run: tools/ci.sh unix_minimal_build env: @@ -46,7 +46,7 @@ jobs: standard: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: tools/ci.sh unix_standard_build - name: Run main test suite @@ -58,7 +58,7 @@ jobs: standard_v2: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: tools/ci.sh unix_standard_v2_build - name: Run main test suite @@ -70,7 +70,7 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -105,7 +105,7 @@ jobs: coverage_32bit: runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -123,7 +123,7 @@ jobs: nanbox: runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -137,7 +137,7 @@ jobs: longlong: runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -151,7 +151,7 @@ jobs: float: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: tools/ci.sh unix_float_build - name: Run main test suite @@ -163,7 +163,7 @@ jobs: gil_enabled: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build run: tools/ci.sh unix_gil_enabled_build - name: Run main test suite @@ -175,7 +175,7 @@ jobs: stackless_clang: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_clang_setup - name: Build @@ -189,7 +189,7 @@ jobs: float_clang: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_clang_setup - name: Build @@ -203,7 +203,7 @@ jobs: settrace_stackless: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -220,7 +220,7 @@ jobs: repr_b: runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -234,7 +234,7 @@ jobs: macos: runs-on: macos-26 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.8' @@ -250,7 +250,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_qemu_mips_setup - name: Build @@ -265,7 +265,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_qemu_arm_setup - name: Build @@ -280,7 +280,7 @@ jobs: # ubuntu-22.04 is needed for older libffi. runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh unix_qemu_riscv64_setup - name: Build @@ -294,7 +294,7 @@ jobs: sanitize_address: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. @@ -319,7 +319,7 @@ jobs: sanitize_undefined: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. diff --git a/.github/workflows/ports_webassembly.yml b/.github/workflows/ports_webassembly.yml index 6bfbb5aec6c..f6619cc8976 100644 --- a/.github/workflows/ports_webassembly.yml +++ b/.github/workflows/ports_webassembly.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh webassembly_setup - name: Build diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index e4e0152d3e9..793eba36a7b 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -59,7 +59,7 @@ jobs: - uses: microsoft/setup-msbuild@v2 with: vs-version: ${{ matrix.vs_version }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build mpy-cross.exe run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} - name: Update submodules @@ -126,7 +126,7 @@ jobs: git diffutils path-type: inherit # Remove when setup-python is removed - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build mpy-cross.exe run: make -C mpy-cross -j2 - name: Update submodules @@ -144,7 +144,7 @@ jobs: cross-build-on-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: tools/ci.sh windows_setup - name: Build diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 3579f4e1bc9..812f4cbf47b 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -37,7 +37,7 @@ jobs: docker-images: false tool-cache: true swap-storage: false - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: versions name: Read Zephyr version run: source tools/ci.sh && echo "ZEPHYR=$ZEPHYR_VERSION" | tee "$GITHUB_OUTPUT" diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 633b0cdf82e..6a8d4055a91 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -6,7 +6,7 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # ruff version should be kept in sync with .pre-commit-config.yaml & also micropython-lib - run: pipx install ruff==0.11.6 - run: ruff check --output-format=github . From d1caa9df07d07a9c2897d106fe5fb7f4b595203e Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Fri, 21 Nov 2025 21:37:05 -0800 Subject: [PATCH 1525/2098] unix/modsocket: Add IP ADD and DROP MEMBERSHIP to socket constants. Add the IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP to modsocket in the Unix port so that the directives take on the values defined in the system headers. This is needed because the values of these directives are different for MacOS vs other Unix systems. Fixes issue #8456. Signed-off-by: Alex Tran --- ports/unix/modsocket.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/unix/modsocket.c b/ports/unix/modsocket.c index e7f25cdd43d..660ace79b52 100644 --- a/ports/unix/modsocket.c +++ b/ports/unix/modsocket.c @@ -709,6 +709,9 @@ static const mp_rom_map_elem_t mp_module_socket_globals_table[] = { C(SO_KEEPALIVE), C(SO_LINGER), C(SO_REUSEADDR), + + C(IP_ADD_MEMBERSHIP), + C(IP_DROP_MEMBERSHIP), #undef C }; From 4633d2a0374f79c3306e60b603377606664e8a24 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 19 Nov 2025 02:50:12 +0100 Subject: [PATCH 1526/2098] tools/ci.sh: Add zsh and fish shell completion support. This commit adds custom command completion functions for both the zsh and fish shell. The behaviour for those new completions follow the existing completion for the bash shell, including the way to generate the completion alias (with appropriately named command line switches). Signed-off-by: Alessandro Gatti --- docs/develop/gettingstarted.rst | 10 +++++++++- tools/ci.sh | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst index cb479458e11..a6afc5cad84 100644 --- a/docs/develop/gettingstarted.rst +++ b/docs/develop/gettingstarted.rst @@ -338,7 +338,15 @@ If you use the bash shell, you can add a ``ci`` command with tab completion: .. code-block:: bash - $ eval `tools/ci.sh --bash-completion` + $ eval $(tools/ci.sh --bash-completion) + +For the zsh shell, replace ``--bash-completion`` with ``--zsh-completion``. +For the fish shell, replace ``--bash-completion`` with ``--fish-completion``. + +Then, typing: + +.. code-block:: bash + $ ci unix_cov This will complete the ci step name to ``unix_coverage_``. diff --git a/tools/ci.sh b/tools/ci.sh index 63eed15bea2..fa7a529b212 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -1049,6 +1049,14 @@ function _ci_bash_completion { echo "alias ci=\"$(readlink -f "$0")\"; complete -W '$(grep '^function ci_' $0 | awk '{print $2}' | sed 's/^ci_//')' ci" } +function _ci_zsh_completion { + echo "alias ci=\"$(readlink -f "$0"\"); _complete_mpy_ci_zsh() { compadd $(grep '^function ci_' $0 | awk '{sub(/^ci_/,"",$2); print $2}' | tr '\n' ' ') }; autoload -Uz compinit; compinit; compdef _complete_mpy_ci_zsh $(readlink -f "$0")" +} + +function _ci_fish_completion { + echo "alias ci=\"$(readlink -f "$0"\"); complete -c ci -p $(readlink -f "$0") -f -a '$(grep '^function ci_' $(readlink -f "$0") | awk '{sub(/^ci_/,"",$2); print $2}' | tr '\n' ' ')'" +} + function _ci_main { case "$1" in (-h|-?|--help) @@ -1057,6 +1065,12 @@ function _ci_main { (--bash-completion) _ci_bash_completion ;; + (--zsh-completion) + _ci_zsh_completion + ;; + (--fish-completion) + _ci_fish_completion + ;; (-*) echo "Unknown option: $1" 1>&2 exit 1 From 1c47db32e4096c4fdb978771ef108d93bff28ace Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 6 Nov 2025 16:44:40 +0100 Subject: [PATCH 1527/2098] docs/library: Document OrderedDict.popitem()'s CPython differences. Fixes issue #18370. Signed-off-by: Jos Verlinde --- docs/library/collections.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/library/collections.rst b/docs/library/collections.rst index 6a23e456c66..f973e521e2e 100644 --- a/docs/library/collections.rst +++ b/docs/library/collections.rst @@ -101,3 +101,19 @@ Classes a 2 w 5 b 3 + + .. method:: OrderedDict.popitem() + + Remove and return a (key, value) pair from the dictionary. + Pairs are returned in LIFO order. + + .. admonition:: Difference to CPython + :class: attention + + ``OrderedDict.popitem()`` does not support the ``last=False`` argument and + will always remove and return the last item if present. + + A workaround for this is to use ``pop()`` to remove the first item:: + + first_key = next(iter(d)) + d.pop(first_key) From 9b3b3a53ed52340809f9b87f0c215bbd8e87f9a9 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Mon, 24 Nov 2025 16:57:00 -0800 Subject: [PATCH 1528/2098] docs/library: Fix typos under I2CTarget irq method description. There were a few typos in the documentation for the `I2CTarget.irq` method description where `IRQ_ADDR_MATCH_READ` was used, however `IRQ_ADDR_MATCH_WRITE` should have been used. Fixes issue #18470. Signed-off-by: Alex Tran --- docs/library/machine.I2CTarget.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/library/machine.I2CTarget.rst b/docs/library/machine.I2CTarget.rst index 0e4af84cb68..2765b98143a 100644 --- a/docs/library/machine.I2CTarget.rst +++ b/docs/library/machine.I2CTarget.rst @@ -126,7 +126,7 @@ General Methods - ``IRQ_ADDR_MATCH_READ`` indicates that the target was addressed by a controller for a read transaction. - - ``IRQ_ADDR_MATCH_READ`` indicates that the target was addressed by a + - ``IRQ_ADDR_MATCH_WRITE`` indicates that the target was addressed by a controller for a write transaction. - ``IRQ_READ_REQ`` indicates that the controller is requesting data, and this request must be satisfied by calling `I2CTarget.write` with the data to be @@ -141,7 +141,7 @@ General Methods Note the following restrictions: - - ``IRQ_ADDR_MATCH_READ``, ``IRQ_ADDR_MATCH_READ``, ``IRQ_READ_REQ`` and + - ``IRQ_ADDR_MATCH_READ``, ``IRQ_ADDR_MATCH_WRITE``, ``IRQ_READ_REQ`` and ``IRQ_WRITE_REQ`` must be handled by a hard IRQ callback (with the *hard* argument set to ``True``). This is because these events have very strict timing requirements and must usually be satisfied synchronously with the hardware event. From d5ad2cdcca3e94e4d1822dbedc2c3f42057a6ce4 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Tue, 2 Sep 2025 23:26:53 +1000 Subject: [PATCH 1529/2098] docs/library: Add machine.DAC documentation. Fixes issue #7915. Signed-off-by: Matt Trentini --- docs/library/machine.DAC.rst | 68 ++++++++++++++++++++++++++++++++++++ docs/library/machine.rst | 1 + 2 files changed, 69 insertions(+) create mode 100644 docs/library/machine.DAC.rst diff --git a/docs/library/machine.DAC.rst b/docs/library/machine.DAC.rst new file mode 100644 index 00000000000..befcba83230 --- /dev/null +++ b/docs/library/machine.DAC.rst @@ -0,0 +1,68 @@ +.. currentmodule:: machine +.. _machine.DAC: + +class DAC -- digital to analog conversion +========================================= + +The DAC is used to output an analog voltage based on a digital value. + +The output voltage will be between 0 and 3.3V. + +DAC is currently supported on ESP32 [#esp32_dac]_, SAMD and Renesas RA. + +.. note:: + The STM32 port has similar functionality to ``machine.DAC``. See + :ref:`pyb.DAC ` for details. + +Example usage (ESP32):: + + from machine import DAC + + dac = DAC(pin) # create a DAC object acting on a pin + dac.write(128) # write a value to the DAC + dac.write(255) # output maximum value, 3.3V + +Constructors +------------ + +.. class:: DAC(id) + + Construct a new DAC object. + + ``id`` is a pin object (ESP32 and Renesas RA) or an index to a DAC resource (SAMD). + +.. note:: + On the ESP32, DAC functionality is available on pins 25 and 26. On the + ESP32-S2, pins 17 and 18. See :ref:`ESP32 Quickref ` + for more details. + +.. note:: + SAMD21 has one DAC resource, SAMD51 has two. See :ref:`SAMD Quickref ` + for more details. + +Methods +------- + +.. method:: DAC.write(value) + + Output an analog voltage to the pin connected to the DAC. + + ``value`` is a representation of the desired output; a linear interpolation of + 0-3.3V, though the range differs depending on the port and micro, see below: + + +--------------+------+--------+ + | *Port/micro* | Bits | Range | + +==============+======+========+ + | ESP32 | 8 | 0-255 | + +--------------+------+--------+ + | SAMD21 | 10 | 0-1023 | + +--------------+------+--------+ + | SAMD51 | 12 | 0-4095 | + +--------------+------+--------+ + | Renesas RA | 12 | 0-4095 | + +--------------+------+--------+ + +.. rubric:: Footnotes + +.. [#esp32_dac] The original ESP32 and ESP32-S2 *only*, since DAC hardware is + not present on other microcontrollers in the family. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7acaddde815..69eda917e5a 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -260,6 +260,7 @@ Classes machine.Signal.rst machine.ADC.rst machine.ADCBlock.rst + machine.DAC.rst machine.PWM.rst machine.UART.rst machine.SPI.rst From bd111ccd4b4932ea94ba7c66007840cb88e3b4a7 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Thu, 20 Nov 2025 21:01:20 +0100 Subject: [PATCH 1530/2098] zephyr: Allow a custom dts. Allows using custom DTS things such as bindings. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 2 ++ ports/zephyr/dts/bindings/vendor-prefixes.txt | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 ports/zephyr/dts/bindings/vendor-prefixes.txt diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 9db0a95c663..cf35ba73da2 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -35,6 +35,8 @@ string(TOUPPER ZEPHYR_${BOARD} MICROPY_BOARD) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) +list(APPEND DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/dts) + if (CONFIG_MICROPY_FROZEN_MODULES) cmake_path(ABSOLUTE_PATH CONFIG_MICROPY_FROZEN_MANIFEST BASE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) set(MICROPY_FROZEN_MANIFEST ${CONFIG_MICROPY_FROZEN_MANIFEST}) diff --git a/ports/zephyr/dts/bindings/vendor-prefixes.txt b/ports/zephyr/dts/bindings/vendor-prefixes.txt new file mode 100644 index 00000000000..f2dc741d997 --- /dev/null +++ b/ports/zephyr/dts/bindings/vendor-prefixes.txt @@ -0,0 +1,15 @@ +# Device tree binding vendor prefix registry. Keep this list in +# alphabetical order. +# +# This isn't an exhaustive list, but you should add new prefixes to it +# before using them to avoid name-space collisions. +# +# The contents of this file are parsed during documentation generation. +# Anything that starts with a '#' is treated as a comment and ignored. +# Non-empty lines should be in this format: +# +# + +# zephyr-keep-sorted-start +micropython MicroPython Project +# zephyr-keep-sorted-stop From 3d9a3e89cf4a32fdc6fca2a8edd91714b5b67665 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Sat, 25 Oct 2025 23:39:33 +0200 Subject: [PATCH 1531/2098] zephyr: Add support for GC split-heap. Adds the ability to use PSRAM and non-contiguous memory. Example usage, add this to dts overlay: / { heap_psram { compatible = "micropython,heap"; size = ; memory-region = <&psram>; }; heap_sram1 { compatible = "micropython,heap"; size = ; memory-region = <&sram1>; }; }; Signed-off-by: Vdragon --- .../micropython/micropython,heap.yaml | 23 +++++++++++++++++ ports/zephyr/main.c | 25 +++++++++++++++++++ ports/zephyr/mpconfigport.h | 4 +++ 3 files changed, 52 insertions(+) create mode 100644 ports/zephyr/dts/bindings/micropython/micropython,heap.yaml diff --git a/ports/zephyr/dts/bindings/micropython/micropython,heap.yaml b/ports/zephyr/dts/bindings/micropython/micropython,heap.yaml new file mode 100644 index 00000000000..333baa96686 --- /dev/null +++ b/ports/zephyr/dts/bindings/micropython/micropython,heap.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: | + MicroPython Heap + Used to define micropython heap areas. + +compatible: "micropython,heap" + +include: [base.yaml] + +properties: + size: + type: int + required: true + description: | + Size of the heap + + memory-region: + type: phandle + required: true + description: | + Memory region to place the heap in diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 0fa4e97b241..c82814e04b5 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -58,6 +58,28 @@ #include "extmod/vfs.h" #endif +#if MICROPY_ENABLE_GC +#if MICROPY_GC_SPLIT_HEAP && DT_HAS_COMPAT_STATUS_OKAY(micropython_heap) + +#include + +#define MICROPY_HEAP_NAME(node) CONCAT(DT_NODE_FULL_NAME_TOKEN(node), _heap) + +#define MICROPY_HEAP_DEFINE(node) \ + static char MICROPY_HEAP_NAME(node)[DT_PROP(node, size)] \ + Z_GENERIC_SECTION(LINKER_DT_NODE_REGION_NAME(DT_PROP(node, memory_region))); + +DT_FOREACH_STATUS_OKAY(micropython_heap, MICROPY_HEAP_DEFINE) + +#define MICROPY_HEAP_ADD(node) \ + gc_add((void *)&MICROPY_HEAP_NAME(node), \ + (void *)&(MICROPY_HEAP_NAME(node)[DT_PROP(node, size) - 1])); + +#elif DT_HAS_COMPAT_STATUS_OKAY(micropython_heap) +#error Has additional Heap but split heap is not enabled +#endif +#endif + #include "modmachine.h" #include "modzephyr.h" @@ -107,6 +129,9 @@ int real_main(void) { mp_cstack_init_with_sp_here(CONFIG_MAIN_STACK_SIZE); #if MICROPY_ENABLE_GC gc_init(heap, heap + sizeof(heap)); + #if MICROPY_GC_SPLIT_HEAP && DT_HAS_COMPAT_STATUS_OKAY(micropython_heap) + DT_FOREACH_STATUS_OKAY(micropython_heap, MICROPY_HEAP_ADD) + #endif #endif mp_init(); diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 7f849734887..56a442c2697 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -151,6 +151,10 @@ void mp_hal_signal_event(void); #define MICROPY_PY_MACHINE_ADC_READ_UV (1) #endif +#if DT_HAS_COMPAT_STATUS_OKAY(micropython_heap) +#define MICROPY_GC_SPLIT_HEAP (1) +#endif + typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM From 65f994e26a852eecc2b641db4d35b5e1676d4442 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 4 Nov 2025 20:18:16 +0100 Subject: [PATCH 1532/2098] py/compile: Allow NULL emitter table entries. This commit fixes a regression introduced in 1b92bda5b8e5e2db8e57c4b7134930e52bc5207a, where a new architecture was added to mpy-cross but it had no matching native emitter exists. The result was that the architecture emitter entry point would be correctly calculated according to the native architecture index, but if the emitters entry points table was not updated to match the new number of architectures an out of bound access may be performed. Unfortunately adding RV64IMC shifted the debug emitter index further down the table, and that table wasn't updated to reflect the lack of an emitter for RV64. Adding a NULL entry there would cause a NULL pointer access as there was no need to perform any check about the emitter entry point function's validity until now. Signed-off-by: Alessandro Gatti --- py/compile.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/py/compile.c b/py/compile.c index 945ee2b2dee..6d7e9c4a146 100644 --- a/py/compile.c +++ b/py/compile.c @@ -103,6 +103,7 @@ static const emit_method_table_t *emit_native_table[] = { &emit_native_xtensa_method_table, &emit_native_xtensawin_method_table, &emit_native_rv32_method_table, + NULL, &emit_native_debug_method_table, }; @@ -3571,6 +3572,13 @@ void mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool case MP_EMIT_OPT_NATIVE_PYTHON: case MP_EMIT_OPT_VIPER: if (emit_native == NULL) { + // The check looks like this to work around a false + // warning in GCC 13 (and possibly later), where it + // assumes that the check will always fail. + if ((uintptr_t)NATIVE_EMITTER_TABLE == (uintptr_t)NULL) { + comp->compile_error = mp_obj_new_exception_msg(&mp_type_NotImplementedError, MP_ERROR_TEXT("cannot emit native code for this architecture")); + goto emit_finished; + } emit_native = NATIVE_EMITTER(new)(&comp->emit_common, &comp->compile_error, &comp->next_label, max_num_labels); } comp->emit_method_table = NATIVE_EMITTER_TABLE; @@ -3603,6 +3611,10 @@ void mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool } } + #if MICROPY_EMIT_NATIVE +emit_finished: + #endif + if (comp->compile_error != MP_OBJ_NULL) { // if there is no line number for the error then use the line // number for the start of this scope From 6b661ca3f6d877cfca224cc311cb84eba2d2b380 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 5 Nov 2025 21:58:51 +0100 Subject: [PATCH 1533/2098] github/workflows: Test mpy-cross debug emitter. This commit adds a new workflow step to the CI, to test the debug emitter provided by mpy-cross. The checks being done are limited to make sure that the debug emitter does not crash and emits opcodes for a simple test file that is guaranteed to work for all configurations. Signed-off-by: Alessandro Gatti --- .github/workflows/mpy_format.yml | 4 ++++ tools/ci.sh | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/mpy_format.yml b/.github/workflows/mpy_format.yml index b51110db677..ab668c1cb5e 100644 --- a/.github/workflows/mpy_format.yml +++ b/.github/workflows/mpy_format.yml @@ -6,6 +6,8 @@ on: paths: - '.github/workflows/*.yml' - 'examples/**' + - 'mpy-cross/**' + - 'py/**' - 'tests/**' - 'tools/**' @@ -22,3 +24,5 @@ jobs: run: tools/ci.sh mpy_format_setup - name: Test mpy-tool.py run: tools/ci.sh mpy_format_test + - name: Test mpy-cross debug emitter + run: tools/ci.sh mpy_cross_debug_emitter diff --git a/tools/ci.sh b/tools/ci.sh index fa7a529b212..e34940f7589 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -173,6 +173,15 @@ function ci_mpy_format_test { $micropython ./tools/mpy-tool.py -x -d examples/natmod/features1/features1.mpy } +function ci_mpy_cross_debug_emitter { + make ${MAKEOPTS} -C mpy-cross + mpy_cross=./mpy-cross/build/mpy-cross + + # Make sure the debug emitter does not crash or fail for simple files + $mpy_cross -X emit=native -march=debug ./tests/basics/0prelim.py | \ + grep -E "ENTRY|EXIT" | wc -l | grep "^2$" +} + ######################################################################################## # ports/cc3200 From b087cb41e85641b9aa530dc2001e151750219f0b Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 24 Nov 2025 11:51:28 +0100 Subject: [PATCH 1534/2098] stm32/mpconfigboard_common: Define TinyUSB MCU type for N6. Allows using TinyUSB stack on N6. Note there's still an issue with TinyUSB on the N6: `pyb_usbd_init()` can't be called multiple times (on soft-reboot). Signed-off-by: iabdalkader --- ports/stm32/mpconfigboard_common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index eefd5c05c1b..1ce42055d80 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -542,6 +542,9 @@ #define MICROPY_HW_MAX_UART (10) #define MICROPY_HW_MAX_LPUART (1) +#define CFG_TUSB_MCU OPT_MCU_STM32N6 +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) + // Configuration for STM32U5 series #elif defined(STM32U5) From cad9bb3a2b5c73637d67591c43ee10c39719fa5e Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Mon, 13 Oct 2025 11:06:24 -0300 Subject: [PATCH 1535/2098] stm32: Add support for STM32F469xx MCUs. Signed-off-by: Mike Tolkachev --- ports/stm32/adc.c | 2 +- ports/stm32/boards/stm32f469xi.ld | 35 +++++++++++++++++++++++++++++++ ports/stm32/mpu.h | 2 +- ports/stm32/powerctrl.c | 6 +++--- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 ports/stm32/boards/stm32f469xi.ld diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 326fc97b251..e9be7021322 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -138,7 +138,7 @@ defined(STM32F413xx) || defined(STM32F427xx) || \ defined(STM32F429xx) || defined(STM32F437xx) || \ defined(STM32F439xx) || defined(STM32F446xx) || \ - defined(STM32F479xx) + defined(STM32F479xx) || defined(STM32F469xx) #define VBAT_DIV (4) #elif defined(STM32F722xx) || defined(STM32F723xx) || \ defined(STM32F732xx) || defined(STM32F733xx) || \ diff --git a/ports/stm32/boards/stm32f469xi.ld b/ports/stm32/boards/stm32f469xi.ld new file mode 100644 index 00000000000..4da9bf16f1a --- /dev/null +++ b/ports/stm32/boards/stm32f469xi.ld @@ -0,0 +1,35 @@ +/* + GNU linker script for STM32F469xI (2Mbyte) +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Entire flash */ + FLASH_START (rx): ORIGIN = 0x08000000, LENGTH = 16K /* Sector 0 */ + FLASH_FS (r) : ORIGIN = 0x08004000, LENGTH = 112K /* Sectors 1, 2, 3, 4 */ + FLASH_TEXT (rx) : ORIGIN = 0x08020000, LENGTH = 1920K /* Sectors 5 - 23 */ + CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K /* CCM RAM used for storage cache */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K /* SRAM1, SRAM2, SRAM3 */ +} + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM extents for the garbage collector */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; + +/* Filesystem cache in RAM, and storage in flash */ +_micropy_hw_internal_flash_storage_ram_cache_start = ORIGIN(CCMRAM); +_micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); +_micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); +_micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 8713fe8370c..1d43e87f8d7 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -28,7 +28,7 @@ #include "irq.h" -#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32WB) +#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F469xx) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32WB) #define MPU_REGION_ETH (MPU_REGION_NUMBER0) #define MPU_REGION_QSPI1 (MPU_REGION_NUMBER1) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 7211ef873cd..a63c57f4a78 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -798,13 +798,13 @@ void powerctrl_enter_stop_mode(void) { #if defined(STM32H7) || \ defined(STM32F427xx) || defined(STM32F437xx) || \ - defined(STM32F429xx) || defined(STM32F439xx) || \ + defined(STM32F429xx) || defined(STM32F439xx) || defined(STM32F469xx) || \ defined(STM32WB55xx) || defined(STM32WB35xx) // Disable SysTick Interrupt // Note: This seems to be required at least on the H7 REV Y, // otherwise the MCU will leave stop mode immediately on entry. // Note: According to ST Errata ES0206 Rev 18, Section 2.2.1 this is needed - // for STM32F427xx, STM32F437xx, STM32F429xx and STM32F439xx + // for STM32F427xx, STM32F437xx, STM32F429xx, STM32F439xx, and STM32F469xx // Note: According to ST Errata ES0394 Rev 11, Section 2.2.17 this is needed // for STM32WB55xx and STM32WB35xx SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; @@ -1039,7 +1039,7 @@ void powerctrl_enter_stop_mode(void) { #if defined(STM32H7) || \ defined(STM32F427xx) || defined(STM32F437xx) || \ - defined(STM32F429xx) || defined(STM32F439xx) || \ + defined(STM32F429xx) || defined(STM32F439xx) || defined(STM32F469xx) || \ defined(STM32WB55xx) || defined(STM32WB35xx) // Enable SysTick Interrupt SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; From b4d546d82e01d16664b2d17d9f145cb4768460c9 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Mon, 13 Oct 2025 11:06:24 -0300 Subject: [PATCH 1536/2098] stm32/boards/STM32F469DISC: Add new board definition files. Signed-off-by: Mike Tolkachev --- ports/stm32/boards/STM32F469DISC/bdev.c | 45 ++++++ ports/stm32/boards/STM32F469DISC/board_init.c | 44 ++++++ .../boards/STM32F469DISC/mpconfigboard.h | 133 +++++++++++++++++ .../boards/STM32F469DISC/mpconfigboard.mk | 20 +++ ports/stm32/boards/STM32F469DISC/pins.csv | 135 ++++++++++++++++++ .../boards/STM32F469DISC/stm32f4xx_hal_conf.h | 41 ++++++ 6 files changed, 418 insertions(+) create mode 100644 ports/stm32/boards/STM32F469DISC/bdev.c create mode 100644 ports/stm32/boards/STM32F469DISC/board_init.c create mode 100644 ports/stm32/boards/STM32F469DISC/mpconfigboard.h create mode 100644 ports/stm32/boards/STM32F469DISC/mpconfigboard.mk create mode 100644 ports/stm32/boards/STM32F469DISC/pins.csv create mode 100644 ports/stm32/boards/STM32F469DISC/stm32f4xx_hal_conf.h diff --git a/ports/stm32/boards/STM32F469DISC/bdev.c b/ports/stm32/boards/STM32F469DISC/bdev.c new file mode 100644 index 00000000000..40e4917e26d --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/bdev.c @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Mike Tolkachev + * + * 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 "storage.h" +#include "qspi.h" + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +static mp_spiflash_cache_t spi_bdev_cache; +#endif + +// External SPI flash uses QSPI interface +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_QSPI, + .bus.u_qspi.data = NULL, + .bus.u_qspi.proto = &qspi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + .cache = &spi_bdev_cache, + #endif +}; + +// SPI flash device instance +spi_bdev_t spi_bdev; diff --git a/ports/stm32/boards/STM32F469DISC/board_init.c b/ports/stm32/boards/STM32F469DISC/board_init.c new file mode 100644 index 00000000000..e4f75f218f8 --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/board_init.c @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Mike Tolkachev + * + * 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 "storage.h" +#include "boardctrl.h" +#include "qspi.h" + +// Micron N25Q128A13EF840F of original STM32F469I-DISCO board +static const mp_spiflash_chip_params_t chip_params_n25q128a13ef840f = { + .jedec_id = 0, // Not used for detection + .memory_size_bytes_log2 = MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2, + .qspi_prescaler = MICROPY_HW_QSPI_PRESCALER, + .qread_num_dummy = MICROPY_HW_QSPIFLASH_DUMMY_CYCLES +}; + +// Early board initialization hook called before file system is mounted +void STM32F469DISC_board_early_init(void) { + // Initialize QSPI flash device parameters + MICROPY_HW_BDEV_SPIFLASH->spiflash.chip_params = &chip_params_n25q128a13ef840f; +} diff --git a/ports/stm32/boards/STM32F469DISC/mpconfigboard.h b/ports/stm32/boards/STM32F469DISC/mpconfigboard.h new file mode 100644 index 00000000000..1668618ce2b --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/mpconfigboard.h @@ -0,0 +1,133 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Mike Tolkachev + * + * 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. + */ + +// This board is configured to communicate over USB port, not ST-Link + +#define MICROPY_HW_BOARD_NAME "F469DISC" +#define MICROPY_HW_MCU_NAME "STM32F469" + +// Use external QSPI flash for storage by default +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) + +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_DAC (1) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_ENABLE_SDCARD (1) + +#define MICROPY_BOARD_EARLY_INIT STM32F469DISC_board_early_init +void STM32F469DISC_board_early_init(void); + +// QSPI flash storage configuration +#if !BUILDING_MBOOT +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#endif +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) +#define MICROPY_HW_SPIFLASH_SIZE_BITS (128 * 1024 * 1024) +#define MICROPY_HW_SPIFLASH_CHIP_PARAMS (1) // enable extended parameters +#define MICROPY_HW_QSPI_PRESCALER (3) +#define MICROPY_HW_QSPIFLASH_DUMMY_CYCLES (4) +#define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (27) +#define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI_CS) +#define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI_CLK) +#define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI_D0) +#define MICROPY_HW_QSPIFLASH_IO1 (pyb_pin_QSPI_D1) +#define MICROPY_HW_QSPIFLASH_IO2 (pyb_pin_QSPI_D2) +#define MICROPY_HW_QSPIFLASH_IO3 (pyb_pin_QSPI_D3) + +// QSPI flash block device configuration +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) // for extended block protocol + +// HSE is 8MHz +#define MICROPY_HW_CLK_PLLM (8) +#define MICROPY_HW_CLK_PLLN (336) +#define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2) +#define MICROPY_HW_CLK_PLLQ (7) + +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_6 + +// UART config +#define MICROPY_HW_UART3_NAME "YB" +#define MICROPY_HW_UART3_TX (pin_B10) +#define MICROPY_HW_UART3_RX (pin_B11) +#define MICROPY_HW_UART6_NAME "YA" +#define MICROPY_HW_UART6_TX (pin_G14) +#define MICROPY_HW_UART6_RX (pin_G9) +#define MICROPY_HW_UART2_NAME "UART2" +#define MICROPY_HW_UART2_TX (pin_A2) // Needed to enable AF +#define MICROPY_HW_UART2_RX (pin_A3) // Dummy, not routed on PCB +#define MICROPY_HW_UART2_CK (pin_A4) + +// I2C buses +#define MICROPY_HW_I2C1_SCL (pin_B8) +#define MICROPY_HW_I2C1_SDA (pin_B9) + +// SPI +#define MICROPY_HW_SPI2_NSS (pin_B9) +#define MICROPY_HW_SPI2_SCK (pin_D3) +#define MICROPY_HW_SPI2_MISO (pin_B14) +#define MICROPY_HW_SPI2_MOSI (pin_B15) + +// CAN buses +#define MICROPY_HW_CAN1_TX (pin_B9) +#define MICROPY_HW_CAN1_RX (pin_B8) + +// USRSW is pulled low. Pressing the button makes the input go high. +#define MICROPY_HW_USRSW_PIN (pin_A0) +#define MICROPY_HW_USRSW_PULL (GPIO_NOPULL) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_RISING) +#define MICROPY_HW_USRSW_PRESSED (1) + +// LEDs +#define MICROPY_HW_LED1 (pin_G6) // green +#define MICROPY_HW_LED2 (pin_D4) // orange +#define MICROPY_HW_LED3 (pin_D5) // red +#define MICROPY_HW_LED4 (pin_K3) // blue +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) + +// SD Card SDMMC +#define MICROPY_HW_SDCARD_CK (pin_C12) +#define MICROPY_HW_SDCARD_CMD (pin_D2) +#define MICROPY_HW_SDCARD_D0 (pin_C8) +#define MICROPY_HW_SDCARD_D1 (pin_C9) +#define MICROPY_HW_SDCARD_D2 (pin_C10) +#define MICROPY_HW_SDCARD_D3 (pin_C11) +#define MICROPY_HW_SDCARD_DETECT_PIN (pin_G2) +#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) +#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) + +// USB config +#define MICROPY_HW_USB_FS (1) +#define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9) +#define MICROPY_HW_USB_OTG_ID_PIN (pin_A10) diff --git a/ports/stm32/boards/STM32F469DISC/mpconfigboard.mk b/ports/stm32/boards/STM32F469DISC/mpconfigboard.mk new file mode 100644 index 00000000000..bc95739c7db --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/mpconfigboard.mk @@ -0,0 +1,20 @@ +# MCU settings +MCU_SERIES = f4 +CMSIS_MCU = STM32F469xx +MICROPY_FLOAT_IMPL = double +AF_FILE = boards/stm32f479_af.csv + +ifeq ($(USE_MBOOT),1) +# When using Mboot all the text goes together after the filesystem +LD_FILES = boards/stm32f469xi.ld boards/common_blifs.ld +TEXT0_ADDR = 0x08020000 +else +# When not using Mboot the ISR text goes first, then the rest after the filesystem +LD_FILES = boards/stm32f469xi.ld boards/common_ifs.ld +TEXT0_ADDR = 0x08000000 +TEXT1_ADDR = 0x08020000 +endif + +# MicroPython settings +MICROPY_PY_SSL = 1 +MICROPY_SSL_MBEDTLS = 1 diff --git a/ports/stm32/boards/STM32F469DISC/pins.csv b/ports/stm32/boards/STM32F469DISC/pins.csv new file mode 100644 index 00000000000..055581ba944 --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/pins.csv @@ -0,0 +1,135 @@ +A0,PB1 +A1,PC2 +A2,PC3 +A3,PC4 +A4,PC5 +A5,PA4 +D0,PG9 +D1,PG14 +D2,PG13 +D3,PA1 +D4,PG12 +D5,PA2 +D6,PA6 +D7,PG11 +D8,PG10 +D9,PA7 +D10,PH6 +D11,PB15 +D12,PB14 +D13,PD3 +D14,PB9 +D15,PB8 +LED1,PG6 +LED2,PD4 +LED3,PD5 +LED4,PK3 +SW,PA0 +TP1,PH2 +TP2,PI8 +TP3,PH15 +AUDIO_INT,PD6 +AUDIO_SDA,PH8 +AUDIO_SCL,PH7 +EXT_SDA,PB9 +EXT_SCL,PB8 +EXT_RST,PG3 +SD_D0,PC8 +SD_D1,PC9 +SD_D2,PC10 +SD_D3,PC11 +SD_CK,PC12 +SD_CMD,PD2 +SD_SW,PC2 +LCD_BL_CTRL,PK3 +LCD_INT,PI13 +LCD_SDA,PH8 +LCD_SCL,PH7 +OTG_FS_POWER,PD5 +OTG_FS_OVER_CURRENT,PD4 +OTG_HS_OVER_CURRENT,PE3 +USB_VBUS,PA9 +USB_ID,PA10 +USB_DM,PA11 +USB_DP,PA12 +USB_HS_CLK,PA5 +USB_HS_STP,PC0 +USB_HS_NXT,PH4 +USB_HS_DIR,PI11 +USB_HS_D0,PA3 +USB_HS_D1,PB0 +USB_HS_D2,PB1 +USB_HS_D3,PB10 +USB_HS_D4,PB11 +USB_HS_D5,PB12 +USB_HS_D6,PB13 +USB_HS_D7,PB5 +UART1_TX,PB10 +UART1_RX,PB11 +UART6_TX,PG14 +UART6_RX,PG9 +CAN2_TX,PB13 +CAN2_RX,PB12 +QSPI_CS,PB6 +QSPI_CLK,PF10 +QSPI_D0,PF8 +QSPI_D1,PF9 +QSPI_D2,PF7 +QSPI_D3,PF6 +FMC_SDCKE0,PH2 +FMC_SDNE0,PH3 +FMC_SDCLK,PG8 +FMC_SDNCAS,PG15 +FMC_SDNRAS,PF11 +FMC_SDNWE,PH5 +FMC_BA0,PG4 +FMC_BA1,PG5 +FMC_NBL0,PE0 +FMC_NBL1,PE1 +FMC_NBL2,PI4 +FMC_NBL3,PI5 +FMC_A0,PF0 +FMC_A1,PF1 +FMC_A2,PF2 +FMC_A3,PF3 +FMC_A4,PF4 +FMC_A5,PF5 +FMC_A6,PF12 +FMC_A7,PF13 +FMC_A8,PF14 +FMC_A9,PF15 +FMC_A10,PG0 +FMC_A11,PG1 +FMC_A12,PG2 +FMC_D0,PD14 +FMC_D1,PD15 +FMC_D2,PD0 +FMC_D3,PD1 +FMC_D4,PE7 +FMC_D5,PE8 +FMC_D6,PE9 +FMC_D7,PE10 +FMC_D8,PE11 +FMC_D9,PE12 +FMC_D10,PE13 +FMC_D11,PE14 +FMC_D12,PE15 +FMC_D13,PD8 +FMC_D14,PD9 +FMC_D15,PD10 +FMC_D16,PH8 +FMC_D17,PH9 +FMC_D18,PH10 +FMC_D19,PH11 +FMC_D20,PH12 +FMC_D21,PH13 +FMC_D22,PH14 +FMC_D23,PH15 +FMC_D24,PI0 +FMC_D25,PI1 +FMC_D26,PI2 +FMC_D27,PI3 +FMC_D28,PI6 +FMC_D29,PI7 +FMC_D30,PI9 +FMC_D31,PI10 diff --git a/ports/stm32/boards/STM32F469DISC/stm32f4xx_hal_conf.h b/ports/stm32/boards/STM32F469DISC/stm32f4xx_hal_conf.h new file mode 100644 index 00000000000..b5a54cbf7c1 --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/stm32f4xx_hal_conf.h @@ -0,0 +1,41 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2025 Mike Tolkachev + */ + +#ifndef MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H + +// Oscillator values in Hz +#define HSE_VALUE (8000000) +#define LSE_VALUE (32768) +#define EXTERNAL_CLOCK_VALUE (12288000) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (100) +#define LSE_STARTUP_TIMEOUT (5000) + +#define HAL_DMA2D_MODULE_ENABLED +#define HAL_LTDC_MODULE_ENABLED +#define HAL_QSPI_MODULE_ENABLED +#define HAL_DSI_MODULE_ENABLED + +#include "boards/stm32f4xx_hal_conf_base.h" + +#ifdef HAL_DMA2D_MODULE_ENABLED +#include "stm32f4xx_hal_dma2d.h" +#endif /* HAL_DMA2D_MODULE_ENABLED */ + +#ifdef HAL_LTDC_MODULE_ENABLED +#include "stm32f4xx_hal_ltdc.h" +#endif /* HAL_LTDC_MODULE_ENABLED */ + +#ifdef HAL_QSPI_MODULE_ENABLED +#include "stm32f4xx_hal_qspi.h" +#endif /* HAL_QSPI_MODULE_ENABLED */ + +#ifdef HAL_DSI_MODULE_ENABLED +#include "stm32f4xx_hal_dsi.h" +#endif /* HAL_DSI_MODULE_ENABLED */ + +#endif // MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H From 22cff2f6f590cdcd70c795ad95bf6678c3e80435 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Nov 2025 18:30:46 +1100 Subject: [PATCH 1537/2098] stm32/boards/STM32F469DISC: Add board.json file. Signed-off-by: Damien George --- ports/stm32/boards/STM32F469DISC/board.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 ports/stm32/boards/STM32F469DISC/board.json diff --git a/ports/stm32/boards/STM32F469DISC/board.json b/ports/stm32/boards/STM32F469DISC/board.json new file mode 100644 index 00000000000..9530e1e8128 --- /dev/null +++ b/ports/stm32/boards/STM32F469DISC/board.json @@ -0,0 +1,15 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [], + "images": [ + "stm32f469disc.jpg" + ], + "mcu": "stm32f4", + "product": "Discovery F469", + "thumbnail": "", + "url": "https://www.st.com/en/evaluation-tools/32f469idiscovery.html", + "vendor": "ST Microelectronics" +} From 9b73e344ebdb9f086013ff45e9dc73af33d2c0d1 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sat, 26 Jul 2025 08:39:41 +0900 Subject: [PATCH 1538/2098] stm32/boards/WEACTSTUDIO_MINI_STM32H743: Add WeAct H743VI board support. This change adds WeAct Studio Mini STM32H743 board support to the STM32 port. Some of the work from PR #12540 is combined here. WeAct Studio Mini STM32H43 board: https://github.com/WeActStudio/MiniSTM32H7xx This board uses STM32H743VI: https://www.st.com/en/microcontrollers-microprocessors/stm32h743vi.html Signed-off-by: Yuuki NAGAO --- .../boards/WEACTSTUDIO_MINI_STM32H743/bdev.c | 47 +++++ .../WEACTSTUDIO_MINI_STM32H743/board.json | 13 ++ .../WEACTSTUDIO_MINI_STM32H743/board_init.c | 16 ++ .../WEACTSTUDIO_MINI_STM32H743/deploy.md | 19 ++ .../WEACTSTUDIO_MINI_STM32H743/manifest.py | 4 + .../mpconfigboard.h | 171 ++++++++++++++++++ .../mpconfigboard.mk | 22 +++ .../WEACTSTUDIO_MINI_STM32H743/pins.csv | 100 ++++++++++ .../stm32h7xx_hal_conf.h | 19 ++ .../weact_stm32h743.ld | 33 ++++ 10 files changed, 444 insertions(+) create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/bdev.c create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board.json create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board_init.c create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/deploy.md create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/manifest.py create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.h create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.mk create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/pins.csv create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/stm32h7xx_hal_conf.h create mode 100644 ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/weact_stm32h743.ld diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/bdev.c b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/bdev.c new file mode 100644 index 00000000000..b7014230077 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/bdev.c @@ -0,0 +1,47 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ + +#include "storage.h" +#include "spi.h" +#include "qspi.h" +#include "py/mpconfig.h" + +static const spi_proto_cfg_t spi_bus = { + .spi = &spi_obj[0], // SPI1 + .baudrate = 25000000, + .polarity = 0, + .phase = 0, + .bits = 8, + .firstbit = SPI_FIRSTBIT_MSB, +}; + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +static mp_spiflash_cache_t spi_bdev_cache; +#endif + +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_SPI, + .bus.u_spi.cs = MICROPY_HW_SPIFLASH_CS, + + .bus.u_spi.data = (void *)&spi_bus, + .bus.u_spi.proto = &spi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + .cache = &spi_bdev_cache, + #endif +}; + +spi_bdev_t spi_bdev; + +// Second external SPI flash uses hardware QSPI interface +const mp_spiflash_config_t spiflash2_config = { + .bus_kind = MP_SPIFLASH_BUS_QSPI, + .bus.u_qspi.data = NULL, + .bus.u_qspi.proto = &qspi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + .cache = &spi_bdev_cache, + #endif +}; + +spi_bdev_t spi_bdev2; diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board.json b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board.json new file mode 100644 index 00000000000..11a6e08152f --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board.json @@ -0,0 +1,13 @@ +{ + "deploy": [ + "deploy.md" + ], + "features": ["External Flash", "DAC", "Display","microSD", "USB", "USB-C"], + "images": [ + "weact_stm32h743.jpg" + ], + "mcu": "stm32h7", + "product": "Mini STM32H743", + "url": "https://github.com/WeActStudio/MiniSTM32H7xx", + "vendor": "WeAct Studio" +} diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board_init.c b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board_init.c new file mode 100644 index 00000000000..0adf04982c8 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/board_init.c @@ -0,0 +1,16 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ + +#include "py/mphal.h" +#include "storage.h" + +void WeAct_Core_early_init(void) { + // Turn off the USB switch. + mp_hal_pin_output(pyb_pin_OTG_FS_POWER); + mp_hal_pin_low(pyb_pin_OTG_FS_POWER); + + // Explicitly init SPI2 because it's not enabled as a block device + spi_bdev_ioctl(&spi_bdev2, BDEV_IOCTL_INIT, (uint32_t)&spiflash2_config); +} diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/deploy.md b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/deploy.md new file mode 100644 index 00000000000..a4572bfe306 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/deploy.md @@ -0,0 +1,19 @@ +### WeAct Studio STM32H7xx + +WeAct Studio make a number of STM32H7xx-based boards, they can all be updated +using +[DFU](https://en.wikipedia.org/wiki/USB?useskin=vector#Device_Firmware_Upgrade_mechanism). + +### DFU update + +Hold the Boot button - the middle of the cluster of three buttons - while the +board is reset (either by connecting USB or by pressing reset). Release the Boot +button shortly after the board has reset. The board ought to now be in DFU mode +and detectable as such from a connected computer. + +Use a tool like [`dfu-util`](https://dfu-util.sourceforge.net/) to update the +firmware: + +```bash +dfu-util --alt 0 -D firmware.dfu +``` diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/manifest.py b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/manifest.py new file mode 100644 index 00000000000..7d163e18479 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/manifest.py @@ -0,0 +1,4 @@ +include("$(PORT_DIR)/boards/manifest.py") + +# Currently this file is a placeholder. +# It would be good to extend to add an LCD driver. diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.h b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.h new file mode 100644 index 00000000000..ad9f3bc45ee --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.h @@ -0,0 +1,171 @@ +#define MICROPY_HW_BOARD_NAME "WEACTSTUDIO_MINI_STM32H743" +#define MICROPY_HW_MCU_NAME "STM32H743VIT6" + +#define MICROPY_FATFS_EXFAT (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_ADC (1) +#define MICROPY_HW_ENABLE_DAC (1) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_ENABLE_SERVO (1) +#define MICROPY_HW_ENABLE_TIMER (1) +#define MICROPY_HW_ENABLE_SDCARD (1) +#define MICROPY_HW_ENABLE_MMCARD (0) + +// ROMFS config +#define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) +#define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_bdev2.spiflash) +#define MICROPY_HW_ROMFS_ENABLE_PART0 (1) + +// Flash storage config +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +// Disable internal filesystem to use spiflash. +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) + +// W25Q64 for storage +#define MICROPY_HW_SPIFLASH_SIZE_BYTES (8 * 1024 * 1024) + +// SPI flash #1, for R/W storage +#define MICROPY_HW_SPIFLASH_CS (pin_D6) +#define MICROPY_HW_SPIFLASH_SCK (pin_B3) +#define MICROPY_HW_SPIFLASH_MOSI (pin_D7) +#define MICROPY_HW_SPIFLASH_MISO (pin_B4) + +// External SPI Flash configuration +#define MICROPY_HW_SPI_IS_RESERVED(id) (id == 1) + +// SPI flash #1, block device config +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; + +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) // for extended block protocol +#define MICROPY_HW_SPIFLASH_SIZE_BITS (MICROPY_HW_SPIFLASH_SIZE_BYTES * 8) + +// SPI flash #2, to be memory mapped +#define MICROPY_HW_QSPI_PRESCALER (2) // 120 MHz +#define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (26) +#define MICROPY_HW_QSPIFLASH_CS (pin_B6) +#define MICROPY_HW_QSPIFLASH_SCK (pin_B2) +#define MICROPY_HW_QSPIFLASH_IO0 (pin_D11) +#define MICROPY_HW_QSPIFLASH_IO1 (pin_D12) +#define MICROPY_HW_QSPIFLASH_IO2 (pin_E2) +#define MICROPY_HW_QSPIFLASH_IO3 (pin_D13) + +// SPI flash #2, block device config +extern const struct _mp_spiflash_config_t spiflash2_config; +extern struct _spi_bdev_t spi_bdev2; + +#define MICROPY_BOARD_EARLY_INIT WeAct_Core_early_init + +// This board has 25MHz HSE. +// The following gives a 480MHz CPU speed. +#define MICROPY_HW_CLK_USE_HSE (1) +#define MICROPY_HW_CLK_PLLM (5) +#define MICROPY_HW_CLK_PLLN (192) +#define MICROPY_HW_CLK_PLLP (2) +#define MICROPY_HW_CLK_PLLQ (20) +#define MICROPY_HW_CLK_PLLR (2) +#define MICROPY_HW_CLK_PLLVCI (RCC_PLL1VCIRANGE_2) +#define MICROPY_HW_CLK_PLLVCO (RCC_PLL1VCOWIDE) +#define MICROPY_HW_CLK_PLLFRAC (0) + +// The USB clock is set using PLL3 +#define MICROPY_HW_CLK_PLL3M (5) +#define MICROPY_HW_CLK_PLL3N (192) +#define MICROPY_HW_CLK_PLL3P (2) +#define MICROPY_HW_CLK_PLL3Q (20) +#define MICROPY_HW_CLK_PLL3R (2) +#define MICROPY_HW_CLK_PLL3VCI (RCC_PLL3VCIRANGE_2) +#define MICROPY_HW_CLK_PLL3VCO (RCC_PLL3VCOWIDE) +#define MICROPY_HW_CLK_PLL3FRAC (0) + +// 32kHz crystal for RTC +#define MICROPY_HW_RTC_USE_LSE (1) +#define MICROPY_HW_RTC_USE_US (0) + +// 6 wait states +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_6 + +// UART +#define MICROPY_HW_UART1_TX (pin_A9) +#define MICROPY_HW_UART1_RX (pin_A10) +#define MICROPY_HW_UART2_TX (pin_A2) +#define MICROPY_HW_UART2_RX (pin_A3) +#define MICROPY_HW_UART3_TX (pin_B10) +#define MICROPY_HW_UART3_RX (pin_B11) +#define MICROPY_HW_UART4_TX (pin_C11) +#define MICROPY_HW_UART4_RX (pin_C10) +#define MICROPY_HW_UART5_TX (pin_B12) // or SPI2 +#define MICROPY_HW_UART5_RX (pin_B13) // or SPI2 +#define MICROPY_HW_UART6_TX (pin_C6) +#define MICROPY_HW_UART6_RX (pin_C7) +#define MICROPY_HW_UART7_TX (pin_E8) +#define MICROPY_HW_UART7_RX (pin_E7) + +// I2C buses +#define MICROPY_HW_I2C1_SCL (pin_B8) +#define MICROPY_HW_I2C1_SDA (pin_B9) + +#define MICROPY_HW_I2C2_SCL (pin_B10) +#define MICROPY_HW_I2C2_SDA (pin_B11) + +// SPI buses +// NOTE: SPI1 is used for the SPI flash. +#define MICROPY_HW_SPI1_NSS (pin_D6) +#define MICROPY_HW_SPI1_SCK (pin_B3) +#define MICROPY_HW_SPI1_MISO (pin_B4) +#define MICROPY_HW_SPI1_MOSI (pin_D7) + +#define MICROPY_HW_SPI2_NSS (pin_B12) +#define MICROPY_HW_SPI2_SCK (pin_B13) +#define MICROPY_HW_SPI2_MISO (pin_B14) +#define MICROPY_HW_SPI2_MOSI (pin_B15) +// NOTE: SPI3 is used for the QSPI flash. +#define MICROPY_HW_SPI3_NSS (pin_A4) +#define MICROPY_HW_SPI3_SCK (pin_B3) +#define MICROPY_HW_SPI3_MISO (pin_B4) +#define MICROPY_HW_SPI3_MOSI (pin_B5) +// NOTE: SPI4 is used for the ST7735 LCD. +#define MICROPY_HW_SPI4_NSS (pin_E11) +#define MICROPY_HW_SPI4_SCK (pin_E12) +#define MICROPY_HW_SPI4_MISO (pin_E13) +#define MICROPY_HW_SPI4_MOSI (pin_E14) + +// CAN buses +#define MICROPY_HW_CAN1_TX (pin_B9) +#define MICROPY_HW_CAN1_RX (pin_B8) + +// USRSW is pulled low. Pressing the button makes the input go high. +#define MICROPY_HW_USRSW_PIN (pin_C13) // K1 on the board. +#define MICROPY_HW_USRSW_PULL (GPIO_PULLDOWN) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_RISING) +#define MICROPY_HW_USRSW_PRESSED (1) + +// LEDs +#define MICROPY_HW_LED1 (pin_E3) // the only controllable LED on the board. +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) + +// SD Card SDMMC +#define MICROPY_HW_SDCARD_SDMMC (1) +#define MICROPY_HW_SDCARD_CK (pin_C12) +#define MICROPY_HW_SDCARD_CMD (pin_D2) +#define MICROPY_HW_SDCARD_D0 (pin_C8) +#define MICROPY_HW_SDCARD_D1 (pin_C9) +#define MICROPY_HW_SDCARD_D2 (pin_C10) +#define MICROPY_HW_SDCARD_D3 (pin_C11) + +// SD card detect switch +#define MICROPY_HW_SDCARD_DETECT_PIN (pin_D4) +#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) +#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_SET) + +// USB config +#define MICROPY_HW_USB_FS (1) + +void WeAct_Core_early_init(void); diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.mk b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.mk new file mode 100644 index 00000000000..c13c3ddad72 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/mpconfigboard.mk @@ -0,0 +1,22 @@ +USE_MBOOT ?= 0 + +# MCU settings +MCU_SERIES = h7 +CMSIS_MCU = STM32H743xx +MICROPY_FLOAT_IMPL = double +AF_FILE = boards/stm32h743_af.csv + +ifeq ($(USE_MBOOT),1) +# When using Mboot everything goes after the bootloader +LD_FILES = boards/stm32h723.ld boards/common_bl.ld +TEXT0_ADDR = 0x08020000 +else +# When not using Mboot everything goes at the start of flash +LD_FILES = boards/WEACTSTUDIO_MINI_STM32H743/weact_stm32h743.ld boards/common_basic.ld +TEXT0_ADDR = 0x08000000 +endif + +# MicroPython settings +MICROPY_VFS_LFS2 = 1 + +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/pins.csv b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/pins.csv new file mode 100644 index 00000000000..61b188cc6b6 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/pins.csv @@ -0,0 +1,100 @@ +A0,PA0 +A1,PA1 +A2,PA2 +A3,PA3 +A4,PA4 +A5,PA5 +A6,PA6 +A7,PA7 +A8,PA8 +A9,PA9 +A10,PA10 +A11,PA11 +A12,PA12 +A15,PA15 +B0,PB0 +B1,PB1 +B2,PB2 +B3,PB3 +B4,PB4 +B5,PB5 +B6,PB6 +B7,PB7 +B8,PB8 +B9,PB9 +B10,PB10 +B11,PB11 +B12,PB12 +B13,PB13 +B14,PB14 +B15,PB15 +C0,PC0 +C1,PC1 +C2,PC2 +C3,PC3 +C4,PC4 +C5,PC5 +C6,PC6 +C7,PC7 +C8,PC8 +C9,PC9 +C10,PC10 +C11,PC11 +C12,PC12 +C13,PC13 +D0,PD0 +D1,PD1 +D2,PD2 +D3,PD3 +D4,PD4 +D5,PD5 +D6,PD6 +D7,PD7 +D8,PD8 +D9,PD9 +D10,PD10 +D11,PD11 +D12,PD12 +D13,PD13 +D14,PD14 +D15,PD15 +E0,PE0 +E1,PE1 +E2,PE2 +E3,PE3 +E4,PE4 +E5,PE5 +E6,PE6 +E7,PE7 +E8,PE8 +E9,PE9 +E10,PE10 +E11,PE11 +E11,PE11 +E12,PE12 +E13,PE13 +E14,PE14 +E15,PE15 +LED_BLUE,PE3 +KEY_1,PC13 +QSPI_CS,PB6 +QSPI_CLK,PB2 +QSPI_D0,PD11 +QSPI_D1,PD12 +QSPI_D2,PE2 +QSPI_D3,PD13 +USB_DM,PA11 +USB_DP,PA12 +OSC32_IN,PC14 +OSC32_OUT,PC15 +SDIO_D0,PC8 +SDIO_D1,PC9 +SDIO_D2,PC10 +SDIO_D3,PC11 +SDIO_CMD,PD2 +SDIO_CK,PC12 +SD_SW,PD4 +OTG_FS_POWER,PD10 +OTG_FS_OVER_CURRENT,PG7 +USB_VBUS,PA9 +USB_ID,PA10 diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/stm32h7xx_hal_conf.h b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/stm32h7xx_hal_conf.h new file mode 100644 index 00000000000..c8f60c56055 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/stm32h7xx_hal_conf.h @@ -0,0 +1,19 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ +#ifndef MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H + +// Oscillator values in Hz +#define HSE_VALUE (25000000) +#define LSE_VALUE (32768) +#define EXTERNAL_CLOCK_VALUE (12288000) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (5000) +#define LSE_STARTUP_TIMEOUT (5000) + +#include "boards/stm32h7xx_hal_conf_base.h" + +#endif // MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H diff --git a/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/weact_stm32h743.ld b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/weact_stm32h743.ld new file mode 100644 index 00000000000..356d69171a5 --- /dev/null +++ b/ports/stm32/boards/WEACTSTUDIO_MINI_STM32H743/weact_stm32h743.ld @@ -0,0 +1,33 @@ +/* + GNU linker script for WeAct Studio STM32H743 +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* sectors (0-15) */ + FLASH_APP (rx) : ORIGIN = 0x08020000, LENGTH = 1920K /* sectors (1-15) */ + FLASH_ROMFS (rx): ORIGIN = 0x90000000, LENGTH = 8192K /* external QSPI */ + DTCM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* Used for storage cache */ + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K /* AXI SRAM */ + RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K +} + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM extents for the garbage collector */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; + +/* ROMFS location */ +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); From 14b5080515ec483f0f7be843a089eea835e4519f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Nov 2025 15:00:12 +1100 Subject: [PATCH 1539/2098] shared/tinyusb: Add optional port-specific hook on USBD init. This allows a port to do hardware initialization just before the TinyUSB stack is brought up. That means the hardware is only turned on when it's needed. Signed-off-by: Damien George --- shared/tinyusb/mp_usbd.h | 5 +++++ shared/tinyusb/mp_usbd_runtime.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 311575b3ad8..d73cb5ade82 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -45,6 +45,10 @@ #define MICROPY_WRAP_TUD_EVENT_HOOK_CB(name) name #endif +#ifndef MICROPY_HW_TINYUSB_LL_INIT +#define MICROPY_HW_TINYUSB_LL_INIT() +#endif + #if MICROPY_HW_ENABLE_USBDEV #include "py/obj.h" @@ -102,6 +106,7 @@ void mp_usbd_task_callback(mp_sched_node_t *node); static inline void mp_usbd_init(void) { // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() // which is called in the below helper function. + MICROPY_HW_TINYUSB_LL_INIT(); mp_usbd_init_tud(); } diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index 72e011732d4..39344729da1 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -430,6 +430,8 @@ void mp_usbd_init(void) { } if (need_usb) { + // Call any port-specific initialization code. + MICROPY_HW_TINYUSB_LL_INIT(); // The following will call tusb_init(), which is safe to call redundantly. mp_usbd_init_tud(); // Reconnect if mp_usbd_deinit() has disconnected. From f43810b1cd4142474a99df4e08d51a97eb9af00a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Nov 2025 23:24:24 +1100 Subject: [PATCH 1540/2098] stm32/usbd_conf: Clean up USBD hardware initialization functions. Break the FS and HS initialization routines out into separate functions, and call them as necessary from the TinyUSB or STM USB helper functions. Signed-off-by: Damien George --- ports/stm32/usbd_conf.c | 55 +++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 76142789d41..be56b2202ce 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -62,16 +62,8 @@ PCD_HandleTypeDef pcd_hs_handle; #define OTG_HS_IRQn USB1_OTG_HS_IRQn #endif -#if MICROPY_HW_TINYUSB_STACK -void pyb_usbd_init(void) -#else -void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) -#endif -{ - #if MICROPY_HW_USB_FS - #if MICROPY_HW_STM_USB_STACK - if (hpcd->Instance == USB_OTG_FS) - #endif +#if MICROPY_HW_USB_FS +static void mp_usbd_ll_init_fs(void) { { // Configure USB GPIO's. @@ -192,17 +184,12 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) #endif #endif #endif - - #if MICROPY_HW_STM_USB_STACK - return; - #endif } - #endif +} +#endif // MICROPY_HW_USB_FS - #if MICROPY_HW_USB_HS - #if MICROPY_HW_STM_USB_STACK - if (hpcd->Instance == USB_OTG_HS) - #endif +#if MICROPY_HW_USB_HS +static void mp_usbd_ll_init_hs(void) { { #if MICROPY_HW_USB_HS_IN_FS @@ -342,10 +329,36 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) NVIC_SetPriority(OTG_HS_IRQn, IRQ_PRI_OTG_HS); HAL_NVIC_EnableIRQ(OTG_HS_IRQn); } - #endif // MICROPY_HW_USB_HS } +#endif // MICROPY_HW_USB_HS + +#if MICROPY_HW_TINYUSB_STACK + +void pyb_usbd_init(void) { + #if MICROPY_HW_USB_FS + mp_usbd_ll_init_fs(); + #endif + + #if MICROPY_HW_USB_HS + mp_usbd_ll_init_hs(); + #endif +} + +#elif MICROPY_HW_STM_USB_STACK -#if MICROPY_HW_STM_USB_STACK +void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd) { + #if MICROPY_HW_USB_FS + if (hpcd->Instance == USB_OTG_FS) { + mp_usbd_ll_init_fs(); + } + #endif + + #if MICROPY_HW_USB_HS + if (hpcd->Instance == USB_OTG_HS) { + mp_usbd_ll_init_hs(); + } + #endif +} /** * @brief DeInitializes the PCD MSP. From 63c94fe73ed5c52d4d7ccbf4ddcc2a1e3178d21b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Nov 2025 15:00:32 +1100 Subject: [PATCH 1541/2098] stm32: Fix init sequence of USB hardware and TinyUSB stack. This commit fixes the initialization sequence for TinyUSB when enabled on the stm32 port: - Following other ports, `mp_usbd_init()` should be called just after running `boot.py`, to give the user a chance to configure USB. - Hardware initialization (via `pyb_usbd_init()`) should only occur once, the first time TinyUSB is started up. This is achieved by adding a hook to the shared TinyUSB bindings to call `pyb_usbd_init()`, and only do the hardware init if TinyUSB was not already initialized. Also, `pyb_usbd_init()` is renamed `mp_usbd_ll_init()` to make it match with the rest of the stared TinyUSB binding code. Signed-off-by: Damien George --- ports/stm32/main.c | 11 +++++------ ports/stm32/mpconfigboard_common.h | 1 + ports/stm32/mphalport.h | 1 + ports/stm32/usbd_conf.c | 8 +++++++- ports/stm32/usbd_conf.h | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 6f7413694b0..8085a5e2576 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -611,14 +611,9 @@ void stm32_main(uint32_t reset_mode) { pyb_can_init0(); #endif - #if MICROPY_HW_ENABLE_USB - #if MICROPY_HW_TINYUSB_STACK - pyb_usbd_init(); - mp_usbd_init(); - #else + #if MICROPY_HW_STM_USB_STACK && MICROPY_HW_ENABLE_USB pyb_usb_init0(); #endif - #endif #if MICROPY_PY_MACHINE_I2S machine_i2s_init0(); @@ -690,6 +685,10 @@ void stm32_main(uint32_t reset_mode) { } #endif + #if MICROPY_HW_TINYUSB_STACK && MICROPY_HW_ENABLE_USBDEV + mp_usbd_init(); + #endif + #if MICROPY_HW_HAS_MMA7660 // MMA accel: init and reset accel_init(); diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 1ce42055d80..e21f474d7af 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -261,6 +261,7 @@ #if MICROPY_HW_TINYUSB_STACK #ifndef MICROPY_HW_ENABLE_USBDEV #define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_TINYUSB_LL_INIT mp_usbd_ll_init #endif #ifndef MICROPY_HW_USB_CDC diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index 098a848fb84..50aa4569711 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -1,6 +1,7 @@ // We use the ST Cube HAL library for most hardware peripherals #include STM32_HAL_H #include "pin.h" +#include "usbd_conf.h" #include "py/ringbuf.h" #include "shared/runtime/interrupt_char.h" diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index be56b2202ce..e5ac9311d13 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -32,6 +32,7 @@ #include "usbd_core.h" #include "py/obj.h" #include "py/mphal.h" +#include "shared/tinyusb/mp_usbd.h" #include "irq.h" #include "usb.h" @@ -334,7 +335,12 @@ static void mp_usbd_ll_init_hs(void) { #if MICROPY_HW_TINYUSB_STACK -void pyb_usbd_init(void) { +void mp_usbd_ll_init(void) { + // Only initialize the USB hardware once. + if (tusb_inited()) { + return; + } + #if MICROPY_HW_USB_FS mp_usbd_ll_init_fs(); #endif diff --git a/ports/stm32/usbd_conf.h b/ports/stm32/usbd_conf.h index cb0457982d5..5829d687010 100644 --- a/ports/stm32/usbd_conf.h +++ b/ports/stm32/usbd_conf.h @@ -65,7 +65,7 @@ #define USBD_HS_NUM_FIFO (1 + USBD_HS_NUM_TX_FIFO) #if MICROPY_HW_TINYUSB_STACK -void pyb_usbd_init(void); +void mp_usbd_ll_init(void); #endif #endif // MICROPY_INCLUDED_STM32_USBD_CONF_H From 41acdd8083effcd513ee245075280b78af1ad938 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 26 Nov 2025 13:02:16 +1100 Subject: [PATCH 1542/2098] stm32/rtc: Make sure RTC is using LSE on N6 MCUs. Signed-off-by: Damien George --- ports/stm32/rtc.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index 72abcd75f99..ae33f834cbf 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -134,19 +134,27 @@ void rtc_init_start(bool force_init) { if (!force_init) { bool rtc_running = false; #if defined(STM32N6) + // Note: the low-level boot on the N6 seems to always enable the RTC and the LSI, and + // switch the RTC to LSI mode. So the logic below needs to account for that: + // - if LSE is ready then switch back to the LSE + // - even if LSI is ready, don't use it if the board is configured to use LSE + uint32_t rtc_clock_source = LL_RCC_GetRTCClockSource(); if (LL_RCC_IsEnabledRTC() - && LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSE && LL_RCC_LSE_IsReady()) { // LSE is enabled & ready --> no need to (re-)init RTC rtc_running = true; + if (rtc_clock_source != LL_RCC_RTC_CLKSOURCE_LSE) { + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); + } // remove Backup Domain write protection HAL_PWR_EnableBkUpAccess(); // Clear source Reset Flag __HAL_RCC_CLEAR_RESET_FLAGS(); // provide some status information rtc_info |= 0x40000; - } else if (LL_RCC_IsEnabledRTC() - && LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSI) { + } else if (!rtc_use_lse + && LL_RCC_IsEnabledRTC() + && rtc_clock_source == LL_RCC_RTC_CLKSOURCE_LSI) { // LSI configured as the RTC clock source --> no need to (re-)init RTC rtc_running = true; // remove Backup Domain write protection From 9c2b0cd2e3c9bfc96fe23f4a395f8f8115768fea Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 30 Nov 2025 08:25:17 +1100 Subject: [PATCH 1543/2098] stm32/usbd_conf: Fix build for boards with USB disabled. Some boards (eg NUCLEO_G0B1RE and NUCLEO_G474RE) have USB disabled but still configure MICROPY_HW_USB_FS/HS for the cases where USB does get enabled. Such a configuration should not build any of the code in `usbd_conf.c`, nor the USB interrupt handlers. Signed-off-by: Damien George --- ports/stm32/stm32_it.c | 4 ++++ ports/stm32/usbd_conf.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 19778020d9a..17b95326460 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -301,6 +301,8 @@ void DebugMon_Handler(void) { /* file (startup_stm32f4xx.s). */ /******************************************************************************/ +#if MICROPY_HW_STM_USB_STACK || MICROPY_HW_TINYUSB_STACK + #if defined(STM32G0) #if MICROPY_HW_USB_FS @@ -499,6 +501,8 @@ void OTG_HS_WKUP_IRQHandler(void) { #endif // !defined(STM32L0) +#endif // MICROPY_HW_STM_USB_STACK || MICROPY_HW_TINYUSB_STACK + /** * @brief This function handles PPP interrupt request. * @param None diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index e5ac9311d13..46d7985253d 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -36,7 +36,7 @@ #include "irq.h" #include "usb.h" -#if MICROPY_HW_USB_FS || MICROPY_HW_USB_HS +#if MICROPY_HW_STM_USB_STACK || MICROPY_HW_TINYUSB_STACK #if BUILDING_MBOOT // TinyUSB not used in mboot From f63e64f4bc7097b4d41494d51198f41011e0daf0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 30 Nov 2025 08:34:20 +1100 Subject: [PATCH 1544/2098] tools/ci.sh: Install latest ARM toolchain for stm32 CI. This is needed to build Cortex-M55 (STM32N6) based boards. Signed-off-by: Damien George --- .github/workflows/ports_stm32.yml | 2 +- tools/ci.sh | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 45aa0c61304..2ed730eb4e8 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Install packages - run: tools/ci.sh stm32_setup + run: tools/ci.sh stm32_setup && tools/ci.sh stm32_path >> $GITHUB_PATH - name: Build ci_${{matrix.ci_func }} run: tools/ci.sh ${{ matrix.ci_func }} diff --git a/tools/ci.sh b/tools/ci.sh index e34940f7589..eb658400b4d 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -494,12 +494,19 @@ function ci_samd_build { # ports/stm32 function ci_stm32_setup { - ci_gcc_arm_setup + # Use a recent version of the ARM toolchain, to work with Cortex-M55. + wget https://developer.arm.com/-/media/Files/downloads/gnu/14.3.rel1/binrel/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi.tar.xz + xzcat arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi.tar.xz | tar x + pip3 install pyelftools pip3 install ar pip3 install pyhy } +function ci_stm32_path { + echo $(pwd)/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi/bin +} + function ci_stm32_pyb_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/stm32 MICROPY_PY_NETWORK_WIZNET5K=5200 submodules From 2fbd72ad78a490bab19797e768707f0190fbd4a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 13 Oct 2025 12:18:44 +1100 Subject: [PATCH 1545/2098] tools/ci.sh: Build all stm32 MCU families in stm32 CI functions. Currently the CI for stm32 only tests building about half of the available MCU families. This commit adds the remaining families to the stm32 CI jobs. Signed-off-by: Damien George --- tools/ci.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/ci.sh b/tools/ci.sh index eb658400b4d..098efbf9a23 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -508,6 +508,8 @@ function ci_stm32_path { } function ci_stm32_pyb_build { + # This function builds the following MCU families: F4, F7. + make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/stm32 MICROPY_PY_NETWORK_WIZNET5K=5200 submodules make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2 submodules @@ -522,6 +524,8 @@ function ci_stm32_pyb_build { } function ci_stm32_nucleo_build { + # This function builds the following MCU families: F0, H5, H7, L0, L4, WB. + make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI submodules git submodule update --init lib/mynewt-nimble @@ -548,9 +552,17 @@ function ci_stm32_nucleo_build { } function ci_stm32_misc_build { + # This function builds the following MCU families: G0, G4, H7, L1, N6, U5, WL. + make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA submodules make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_G0B1RE + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_G474RE + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L152RE + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_N657X0 + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_U5A5ZJ_Q + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WL55 } ######################################################################################## From 83131c106d2a7d78055fb83f9a17c9c9c348ea73 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 18 Nov 2025 12:06:55 +0100 Subject: [PATCH 1546/2098] pyproject.toml: Update ruff configuration to target Python 3.8. As per 4c9ce826cbfbd99cff10cc933064f87a13bee4ac the tests now target Python 3.8 syntax and features, so update the ruff configuration to match. Changes in this commit: - Update to Python 3.8 syntax. - Ignore import not at top of module warnings. - Exclude common SDK folders. - Exclude cpydiff test with intentional error. Also see: https://github.com/micropython/micropython-lib/pull/1059 Signed-off-by: Jos Verlinde --- pyproject.toml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f3693eae1b4..1cf57166e14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,11 +22,17 @@ ACKNOWLEDGEMENTS,\ [tool.ruff] # Exclude third-party code from linting and formatting -extend-exclude = ["lib"] +extend-exclude = [ + "lib", + "esp-idf", + "pico-sdk", + "emsdk", + "tests/cpydiff/syntax_assign_expr.py" # intentionally incorrect CPython code +] # Include Python source files that don't end with .py extend-include = ["tools/cc1"] line-length = 99 -target-version = "py37" +target-version = "py38" [tool.ruff.lint] exclude = [ # Ruff finds Python SyntaxError in these files @@ -44,7 +50,7 @@ exclude = [ # Ruff finds Python SyntaxError in these files "tests/micropython/viper_args.py", ] extend-select = ["C9", "PLC"] -ignore = [ +extend-ignore = [ "E401", "E402", "E722", @@ -54,6 +60,7 @@ ignore = [ "F403", "F405", "PLC0206", + "PLC0415", # conditional imports are common in MicroPython ] mccabe.max-complexity = 40 @@ -64,6 +71,8 @@ mccabe.max-complexity = 40 # manifest.py files are evaluated with some global names pre-defined "**/manifest.py" = ["F821"] "ports/**/boards/**/manifest_*.py" = ["F821"] +# Uses assignment expressions. +"tests/cpydiff/syntax_assign_expr.py" = ["F821"] [tool.ruff.format] # Exclude third-party code, and exclude the following tests: From 521b2f86bede90f5f30b6c9ce6a75e987a5f021d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Nov 2025 15:32:25 +1100 Subject: [PATCH 1547/2098] shared/tinyusb: Remove USBD_RHPORT constant. TinyUSB defines TUD_OPT_RHPORT which is the same thing, make shorter definition RHPORT in the two files which use it. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/machine_usb_device.c | 12 +++++++----- shared/tinyusb/mp_usbd_runtime.c | 10 ++++++---- shared/tinyusb/tusb_config.h | 6 ++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index 5019cd98779..3d4cde942cb 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -42,6 +42,8 @@ #define HAS_BUILTIN_DRIVERS (MICROPY_HW_USB_CDC || MICROPY_HW_USB_MSC) +#define RHPORT TUD_OPT_RHPORT + const mp_obj_type_t machine_usb_device_type; static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { @@ -111,11 +113,11 @@ static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buff mp_raise_ValueError(MP_ERROR_TEXT("ep")); } - if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { + if (!usbd_edpt_claim(RHPORT, ep_addr)) { mp_raise_OSError(MP_EBUSY); } - result = usbd_edpt_xfer(USBD_RHPORT, ep_addr, buf_info.buf, buf_info.len); + result = usbd_edpt_xfer(RHPORT, ep_addr, buf_info.buf, buf_info.len); if (result) { // Store the buffer object until the transfer completes @@ -168,14 +170,14 @@ static mp_obj_t usb_device_stall(size_t n_args, const mp_obj_t *args) { usb_device_check_active(self); - mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(USBD_RHPORT, epnum)); + mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(RHPORT, epnum)); if (n_args == 3) { // Set stall state mp_obj_t stall = args[2]; if (mp_obj_is_true(stall)) { - usbd_edpt_stall(USBD_RHPORT, epnum); + usbd_edpt_stall(RHPORT, epnum); } else { - usbd_edpt_clear_stall(USBD_RHPORT, epnum); + usbd_edpt_clear_stall(RHPORT, epnum); } } diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index 39344729da1..ef6bd87edda 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -44,6 +44,8 @@ #include "device/usbd_pvt.h" #endif +#define RHPORT TUD_OPT_RHPORT + static bool in_usbd_task; // Flags if mp_usbd_task() is currently running // Some top-level functions that manage global TinyUSB USBD state, not the @@ -233,7 +235,7 @@ static uint16_t _runtime_dev_claim_itfs(tusb_desc_interface_t const *itf_desc, u } else if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { // Open any endpoints that we come across if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { - bool r = usbd_edpt_open(USBD_RHPORT, (const void *)p_desc); + bool r = usbd_edpt_open(RHPORT, (const void *)p_desc); if (!r) { mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(MP_ENODEV)); usbd_pend_exception(exc); @@ -322,7 +324,7 @@ static bool runtime_dev_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_cont // Check if callback returned any data to submit if (mp_get_buffer(cb_res, &buf_info, dir == TUSB_DIR_IN ? MP_BUFFER_READ : MP_BUFFER_RW)) { - result = tud_control_xfer(USBD_RHPORT, + result = tud_control_xfer(RHPORT, request, buf_info.buf, buf_info.len); @@ -468,7 +470,7 @@ static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) { for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) { for (int dir = 0; dir < 2; dir++) { if (usbd->xfer_data[epnum][dir] != mp_const_none) { - usbd_edpt_stall(USBD_RHPORT, tu_edpt_addr(epnum, dir)); + usbd_edpt_stall(RHPORT, tu_edpt_addr(epnum, dir)); usbd->xfer_data[epnum][dir] = mp_const_none; } } @@ -479,7 +481,7 @@ static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) { // Ensure no pending static CDC writes, as these can cause TinyUSB to crash tud_cdc_write_clear(); // Prevent cdc write flush from initiating any new transfers while disconnecting - usbd_edpt_stall(USBD_RHPORT, USBD_CDC_EP_IN); + usbd_edpt_stall(RHPORT, USBD_CDC_EP_IN); #endif bool was_connected = tud_connected(); diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 0cc5ef03985..40cb00ca485 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -83,7 +83,7 @@ #ifndef CFG_TUD_CDC_TX_BUFSIZE #define CFG_TUD_CDC_TX_BUFSIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 256) #endif -#endif +#endif // CFG_TUD_CDC // MSC Configuration #if CFG_TUD_MSC @@ -92,9 +92,7 @@ #endif // Set MSC EP buffer size to FatFS block size to avoid partial read/writes (offset arg). #define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS) -#endif - -#define USBD_RHPORT (0) // Currently only one port is supported +#endif // CFG_TUD_MSC // Define built-in interface, string and endpoint numbering based on the above config From 604cda5d545564523c4deaedb2e93a464e3dcf8f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Nov 2025 16:28:11 +1100 Subject: [PATCH 1548/2098] esp32: Support building with network and/or bluetooth disabled. (and a smaller binary size as a result) Signed-off-by: Angus Gratton --- ports/esp32/main.c | 6 +++++- ports/esp32/modsocket.c | 5 +++++ ports/esp32/mpconfigport.h | 16 +++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 41eea29b08e..7460bf35938 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -90,7 +90,8 @@ int vprintf_null(const char *format, va_list ap) { return 0; } -time_t platform_mbedtls_time(time_t *timer) { +#if MICROPY_SSL_MBEDTLS +static time_t platform_mbedtls_time(time_t *timer) { // mbedtls_time requires time in seconds from EPOCH 1970 struct timeval tv; @@ -98,6 +99,7 @@ time_t platform_mbedtls_time(time_t *timer) { return tv.tv_sec + TIMEUTILS_SECONDS_1970_TO_2000; } +#endif void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)esp_cpu_get_sp(); @@ -114,8 +116,10 @@ void mp_task(void *pvParameter) { #endif machine_init(); + #if MICROPY_SSL_MBEDTLS // Configure time function, for mbedtls certificate time validation. mbedtls_platform_set_time(platform_mbedtls_time); + #endif esp_err_t err = esp_event_loop_create_default(); if (err != ESP_OK) { diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 2050d1d04d4..d8ebd3a8954 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -55,6 +55,9 @@ #include "lwip/igmp.h" #include "esp_log.h" +// See note at bottom of file about why this isn't MICROPY_PY_SOCKET +#if MICROPY_PY_NETWORK + #define SOCKET_POLL_US (100000) #define MDNS_QUERY_TIMEOUT_MS (5000) #define MDNS_LOCAL_SUFFIX ".local" @@ -1028,3 +1031,5 @@ const mp_obj_module_t mp_module_socket = { // this will not conflict with the common implementation provided by // extmod/mod{lwip,socket}.c. MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_socket, mp_module_socket); + +#endif // MICROPY_PY_NETWORK diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7b973ebb969..c0bd4516e58 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -92,6 +92,9 @@ #endif #ifndef MICROPY_PY_BLUETOOTH #define MICROPY_PY_BLUETOOTH (1) +#endif + +#if MICROPY_PY_BLUETOOTH #define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (1) #define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK (1) // Event stack size is the RTOS stack size minus an allowance for the stack used @@ -102,7 +105,8 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) #define MICROPY_BLUETOOTH_NIMBLE (1) #define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) -#endif +#endif // MICROPY_PY_BLUETOOTH + #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (esp_random()) #define MICROPY_PY_OS_INCLUDEFILE "ports/esp32/modos.c" #define MICROPY_PY_OS_DUPTERM (1) @@ -158,7 +162,9 @@ #define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/esp32/machine_wdt.c" +#ifndef MICROPY_PY_NETWORK #define MICROPY_PY_NETWORK (1) +#endif #ifndef MICROPY_PY_NETWORK_HOSTNAME_DEFAULT #if CONFIG_IDF_TARGET_ESP32 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32" @@ -189,10 +195,10 @@ #ifndef MICROPY_HW_ESP_NEW_I2C_DRIVER #define MICROPY_HW_ESP_NEW_I2C_DRIVER (0) #endif -#define MICROPY_PY_SSL (1) -#define MICROPY_SSL_MBEDTLS (1) -#define MICROPY_PY_WEBSOCKET (1) -#define MICROPY_PY_WEBREPL (1) +#define MICROPY_PY_SSL (MICROPY_PY_NETWORK) +#define MICROPY_SSL_MBEDTLS (MICROPY_PY_SSL) +#define MICROPY_PY_WEBSOCKET (MICROPY_PY_NETWORK) +#define MICROPY_PY_WEBREPL (MICROPY_PY_NETWORK) #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_SOCKET_EVENTS (MICROPY_PY_WEBREPL) #define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) From e6f1f7871380950657947a64f6ba0ae724258dc0 Mon Sep 17 00:00:00 2001 From: Vincent1-python Date: Fri, 15 Aug 2025 17:26:05 +0800 Subject: [PATCH 1549/2098] esp32: Add support for ESP32-P4. This commit adds support for ESP32-P4 SoCs. Signed-off-by: Vincent1-python Signed-off-by: Angus Gratton Signed-off-by: Damien George --- ports/esp32/README.md | 2 +- ports/esp32/boards/make-pins.py | 4 +- ports/esp32/boards/sdkconfig.p4 | 15 +++ ports/esp32/boards/sdkconfig.p4_wifi_c5 | 2 + ports/esp32/boards/sdkconfig.p4_wifi_c6 | 2 + ports/esp32/boards/sdkconfig.p4_wifi_common | 59 ++++++++++++ ports/esp32/esp32_common.cmake | 3 +- ports/esp32/lockfiles/dependencies.lock.esp32 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c2 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c3 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c5 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32c6 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32p4 | 93 +++++++++++++++++++ .../esp32/lockfiles/dependencies.lock.esp32s2 | 2 +- .../esp32/lockfiles/dependencies.lock.esp32s3 | 2 +- ports/esp32/machine_adc.c | 15 +++ ports/esp32/machine_hw_spi.c | 4 + ports/esp32/machine_pin.c | 2 + ports/esp32/machine_pin.h | 60 ++++++++++++ ports/esp32/machine_timer.c | 3 + ports/esp32/machine_touchpad.c | 67 ++++++++++++- ports/esp32/machine_uart.c | 8 ++ ports/esp32/main.c | 3 +- ports/esp32/main/idf_component.yml | 11 ++- ports/esp32/modesp32.c | 4 +- ports/esp32/modmachine.c | 9 ++ ports/esp32/mpconfigport.h | 21 ++++- ports/esp32/network_lan.c | 10 +- ports/esp32/uart.c | 4 + ports/esp32/usb.c | 7 +- shared/tinyusb/tusb_config.h | 2 +- 31 files changed, 393 insertions(+), 31 deletions(-) create mode 100644 ports/esp32/boards/sdkconfig.p4 create mode 100644 ports/esp32/boards/sdkconfig.p4_wifi_c5 create mode 100644 ports/esp32/boards/sdkconfig.p4_wifi_c6 create mode 100644 ports/esp32/boards/sdkconfig.p4_wifi_common create mode 100644 ports/esp32/lockfiles/dependencies.lock.esp32p4 diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 55964febeaf..b5cd1c2a8c6 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -6,7 +6,7 @@ microcontrollers. It uses the ESP-IDF framework and MicroPython runs as a task under FreeRTOS. Currently supports ESP32, ESP32-C2 (aka ESP8684), ESP32-C3, ESP32-C5, ESP32-C6, -ESP32-S2 and ESP32-S3. ESP8266 is supported by a separate MicroPython port. +ESP32-P4, ESP32-S2 and ESP32-S3. ESP8266 is supported by a separate MicroPython port. Supported features include: - REPL (Python prompt) over UART0 and/or the integrated USB peripheral. diff --git a/ports/esp32/boards/make-pins.py b/ports/esp32/boards/make-pins.py index 49b10f0ce15..c7375e67d66 100755 --- a/ports/esp32/boards/make-pins.py +++ b/ports/esp32/boards/make-pins.py @@ -7,8 +7,8 @@ import boardgen -# Pins start at zero, and the highest pin index on any ESP32* chip is 48. -NUM_GPIOS = 49 +# Pins start at zero, and the highest pin index on any ESP32* chip is 54. +NUM_GPIOS = 55 class Esp32Pin(boardgen.Pin): diff --git a/ports/esp32/boards/sdkconfig.p4 b/ports/esp32/boards/sdkconfig.p4 new file mode 100644 index 00000000000..3ec0ff70579 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.p4 @@ -0,0 +1,15 @@ +# Flash +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y + +# Memory +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MEMTEST= +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=50768 + +# ULP: not fixed +CONFIG_SOC_ULP_SUPPORTED=n +CONFIG_ULP_COPROC_ENABLED=n +CONFIG_ULP_COPROC_TYPE_FSM=n diff --git a/ports/esp32/boards/sdkconfig.p4_wifi_c5 b/ports/esp32/boards/sdkconfig.p4_wifi_c5 new file mode 100644 index 00000000000..bee529f9b13 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.p4_wifi_c5 @@ -0,0 +1,2 @@ +# Most settings are in sdkconfig.p4_wifi_common +CONFIG_SLAVE_IDF_TARGET_ESP32C5=y diff --git a/ports/esp32/boards/sdkconfig.p4_wifi_c6 b/ports/esp32/boards/sdkconfig.p4_wifi_c6 new file mode 100644 index 00000000000..4a80725c53d --- /dev/null +++ b/ports/esp32/boards/sdkconfig.p4_wifi_c6 @@ -0,0 +1,2 @@ +# Most settings are in sdkconfig.p4_wifi_common +CONFIG_SLAVE_IDF_TARGET_ESP32C6=y diff --git a/ports/esp32/boards/sdkconfig.p4_wifi_common b/ports/esp32/boards/sdkconfig.p4_wifi_common new file mode 100644 index 00000000000..b7bd0bffa05 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.p4_wifi_common @@ -0,0 +1,59 @@ +# This sdkconfig file has the common settings for an ESP32-P4 +# host with an external ESP-Hosted Wi-Fi/BT interface. + +# Wifi +CONFIG_ESP_HOSTED_ENABLED=y +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=64 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=64 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=32 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=32 + +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65534 +CONFIG_LWIP_TCP_WND_DEFAULT=65534 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=64 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=64 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64 + +CONFIG_LWIP_TCP_SACK_OUT=y + +# Bluetooth Support +CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID=y +CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y +CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y +CONFIG_ESP_WIFI_REMOTE_ENABLED=y +CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y +CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SLAVE_SOC_WIFI_HW_TSF=y +CONFIG_SLAVE_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SLAVE_FREERTOS_UNICORE=y +CONFIG_SLAVE_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SLAVE_IDF_TARGET_ARCH_RISCV=y +CONFIG_SLAVE_SOC_WIFI_HE_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MAC_VERSION_NUM=2 +CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y + +CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y + +# BLE +CONFIG_ESP_ENABLE_BT=y +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_CONTROLLER_DISABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_TRANSPORT_UART=n +CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR=y + +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="MPY ESP32" +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4 + +CONFIG_BT_HCI_LOG_DEBUG_EN=y + +# Increase NimBLE task stack size from the default, because Python code +# (BLE IRQ handlers) will most likely run on this task. +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=6144 diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 807d712830a..a52498a7fec 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -23,7 +23,7 @@ if(CONFIG_IDF_TARGET_ARCH_RISCV) endif() if(NOT DEFINED MICROPY_PY_TINYUSB) - if(CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + if(CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4) set(MICROPY_PY_TINYUSB ON) endif() endif() @@ -167,6 +167,7 @@ list(APPEND IDF_COMPONENTS esp_adc esp_app_format esp_common + esp_driver_touch_sens esp_eth esp_event esp_hw_support diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 index 4b0b2d9729d..3f3a4166716 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -30,6 +30,6 @@ direct_dependencies: - espressif/lan867x - espressif/mdns - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 index 5c5cea0c9a1..9530e74e972 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32c2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 index 4c4c869c27e..5edbe537d8b 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32c3 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c5 b/ports/esp32/lockfiles/dependencies.lock.esp32c5 index 6f24d013470..3b52c82b143 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c5 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c5 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32c5 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 index b7435e10767..1c84d5bfd5f 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c6 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32c6 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32p4 b/ports/esp32/lockfiles/dependencies.lock.esp32p4 new file mode 100644 index 00000000000..8923760482f --- /dev/null +++ b/ports/esp32/lockfiles/dependencies.lock.esp32p4 @@ -0,0 +1,93 @@ +dependencies: + espressif/eppp_link: + component_hash: 41f6519edda527ec6a0553c872ebaf8fc6d3812523c9d4c8d1660ad21c720abe + dependencies: + - name: espressif/esp_serial_slave_link + registry_url: https://components.espressif.com + require: private + version: ^1.1.0 + - name: idf + require: private + version: '>=5.2' + source: + registry_url: https://components.espressif.com + type: service + version: 1.1.3 + espressif/esp_hosted: + component_hash: f32400eec7f35652052ae79ecb301148d4011769e94eb8d47262fb22fce933d2 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 2.2.4 + espressif/esp_serial_slave_link: + component_hash: ac1776806de0a6e371c84e87898bb983e19ce62aa7f1e2e5c4a3b0234a575d2c + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + version: 1.1.2 + espressif/esp_wifi_remote: + component_hash: 4ed1ebe454d63ddb4a91bbd8db74a6f883c85a863db1f79770c57fbd2e5c134c + dependencies: + - name: espressif/eppp_link + registry_url: https://components.espressif.com + require: private + version: '>=0.1' + - name: espressif/esp_hosted + registry_url: https://components.espressif.com + require: private + rules: + - if: target in [esp32h2, esp32p4] + version: '>=0.0.6' + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 0.15.2 + espressif/mdns: + component_hash: 46ee81d32fbf850462d8af1e83303389602f6a6a9eddd2a55104cb4c063858ed + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + espressif/tinyusb: + component_hash: ee1c962cff61eb975d508258d509974d58031cc27ff0d6c4117a67a613a49594 + dependencies: + - name: idf + version: '>=5.0' + source: + git: https://github.com/micropython/tinyusb-espressif.git + path: . + type: git + targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + version: e4c0ec3caab3d9c25374de7047653b9ced8f14ff + idf: + source: + type: idf + version: 5.5.1 +direct_dependencies: +- espressif/esp_hosted +- espressif/esp_wifi_remote +- espressif/mdns +- espressif/tinyusb +- idf +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +target: esp32p4 +version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 index a13d9fd401d..a7bb041539d 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32s2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 index d5e7045b77a..9a8b179f72b 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: da32add5eb5e196ac97a99eb579025222ec572f5db4038873fbf9d3b9d6ed5a3 +manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 target: esp32s3 version: 2.0.0 diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index 432df3d3a20..ff80762ebd7 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -136,6 +136,21 @@ static const machine_adc_obj_t madc_obj[] = { {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_18}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_19}, {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_20}, + #elif CONFIG_IDF_TARGET_ESP32P4 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_16}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_17}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_18}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_19}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_20}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_21}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_22}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_23}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_49}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_50}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_51}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_52}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_53}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_54}, #endif }; diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index dcf8b3942a7..aea6bd00fe8 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -73,6 +73,10 @@ #define MICROPY_HW_SPI2_SCK (36) #define MICROPY_HW_SPI2_MOSI (35) #define MICROPY_HW_SPI2_MISO (37) +#elif CONFIG_IDF_TARGET_ESP32P4 +#define MICROPY_HW_SPI2_SCK (43) +#define MICROPY_HW_SPI2_MOSI (44) +#define MICROPY_HW_SPI2_MISO (39) #endif #endif diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index efe6733194c..74ee15a24ac 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -55,6 +55,8 @@ #define GPIO_FIRST_NON_OUTPUT (34) #elif CONFIG_IDF_TARGET_ESP32S2 #define GPIO_FIRST_NON_OUTPUT (46) +#elif CONFIG_IDF_TARGET_ESP32P4 +#define GPIO_FIRST_NON_OUTPUT (54) #endif // Return the gpio_num_t index for a given machine_pin_obj_t pointer. diff --git a/ports/esp32/machine_pin.h b/ports/esp32/machine_pin.h index 9e247a7367f..4e21b032e66 100644 --- a/ports/esp32/machine_pin.h +++ b/ports/esp32/machine_pin.h @@ -216,6 +216,66 @@ #define MICROPY_HW_ENABLE_GPIO47 (1) #define MICROPY_HW_ENABLE_GPIO48 (1) #endif +#elif CONFIG_IDF_TARGET_ESP32P4 +#define MICROPY_HW_ENABLE_GPIO0 (1) +#define MICROPY_HW_ENABLE_GPIO1 (1) +#define MICROPY_HW_ENABLE_GPIO2 (1) +#define MICROPY_HW_ENABLE_GPIO3 (1) +#define MICROPY_HW_ENABLE_GPIO4 (1) +#define MICROPY_HW_ENABLE_GPIO5 (1) +#define MICROPY_HW_ENABLE_GPIO6 (1) +#define MICROPY_HW_ENABLE_GPIO7 (1) +#define MICROPY_HW_ENABLE_GPIO8 (1) +#define MICROPY_HW_ENABLE_GPIO9 (1) +#define MICROPY_HW_ENABLE_GPIO10 (1) +#define MICROPY_HW_ENABLE_GPIO11 (1) +#define MICROPY_HW_ENABLE_GPIO12 (1) +#define MICROPY_HW_ENABLE_GPIO13 (1) +#define MICROPY_HW_ENABLE_GPIO14 (1) +#define MICROPY_HW_ENABLE_GPIO15 (1) +#define MICROPY_HW_ENABLE_GPIO16 (1) +#define MICROPY_HW_ENABLE_GPIO17 (1) +#define MICROPY_HW_ENABLE_GPIO18 (1) +#define MICROPY_HW_ENABLE_GPIO19 (1) +#define MICROPY_HW_ENABLE_GPIO20 (1) +#define MICROPY_HW_ENABLE_GPIO21 (1) +#define MICROPY_HW_ENABLE_GPIO22 (1) +#define MICROPY_HW_ENABLE_GPIO23 (1) +#if !MICROPY_HW_ESP_USB_SERIAL_JTAG +// Note: ESP32-P4 can switch USJ to 26/27 instead, but +// this isn't supported +#define MICROPY_HW_ENABLE_GPIO24 (1) +#define MICROPY_HW_ENABLE_GPIO25 (1) +#endif +#define MICROPY_HW_ENABLE_GPIO26 (1) +#define MICROPY_HW_ENABLE_GPIO27 (1) +#define MICROPY_HW_ENABLE_GPIO28 (1) +#define MICROPY_HW_ENABLE_GPIO29 (1) +#define MICROPY_HW_ENABLE_GPIO30 (1) +#define MICROPY_HW_ENABLE_GPIO31 (1) +#define MICROPY_HW_ENABLE_GPIO32 (1) +#define MICROPY_HW_ENABLE_GPIO33 (1) +#define MICROPY_HW_ENABLE_GPIO34 (1) +#define MICROPY_HW_ENABLE_GPIO35 (1) +#define MICROPY_HW_ENABLE_GPIO36 (1) +#define MICROPY_HW_ENABLE_GPIO37 (1) +#define MICROPY_HW_ENABLE_GPIO38 (1) +#define MICROPY_HW_ENABLE_GPIO39 (1) +#define MICROPY_HW_ENABLE_GPIO40 (1) +#define MICROPY_HW_ENABLE_GPIO41 (1) +#define MICROPY_HW_ENABLE_GPIO42 (1) +#define MICROPY_HW_ENABLE_GPIO43 (1) +#define MICROPY_HW_ENABLE_GPIO44 (1) +#define MICROPY_HW_ENABLE_GPIO45 (1) +#define MICROPY_HW_ENABLE_GPIO46 (1) +#define MICROPY_HW_ENABLE_GPIO47 (1) +#define MICROPY_HW_ENABLE_GPIO48 (1) +#define MICROPY_HW_ENABLE_GPIO49 (1) +#define MICROPY_HW_ENABLE_GPIO50 (1) +#define MICROPY_HW_ENABLE_GPIO51 (1) +#define MICROPY_HW_ENABLE_GPIO52 (1) +#define MICROPY_HW_ENABLE_GPIO53 (1) +#define MICROPY_HW_ENABLE_GPIO54 (1) #endif diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index ea9ce5469b0..b9cd80f48ef 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -47,6 +47,9 @@ #define TIMER_FLAGS 0 +#if CONFIG_IDF_TARGET_ESP32P4 +static uint8_t __DECLARE_RCC_ATOMIC_ENV __attribute__ ((unused)); +#endif const mp_obj_type_t machine_timer_type; 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); diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index 299c489f5a8..88b34d64ff0 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -41,8 +41,11 @@ #if SOC_TOUCH_SENSOR_VERSION == 1 // ESP32 only #include "driver/touch_pad.h" -#elif SOC_TOUCH_SENSOR_VERSION == 2 // All other SoCs with touch, to date +#elif SOC_TOUCH_SENSOR_VERSION == 2 // most ESP32 #include "driver/touch_sensor.h" +#elif SOC_TOUCH_SENSOR_VERSION == 3 // At present, it can only be used on ESP32P4. +#include "driver/touch_sens.h" +#include "soc/touch_sensor_channel.h" #else #error "Unknown touch hardware version" #endif @@ -50,9 +53,18 @@ typedef struct _mtp_obj_t { mp_obj_base_t base; gpio_num_t gpio_id; + #if SOC_TOUCH_SENSOR_VERSION == 1 || SOC_TOUCH_SENSOR_VERSION == 2 touch_pad_t touchpad_id; + #elif SOC_TOUCH_SENSOR_VERSION == 3 + int touchpad_id; + #endif } mtp_obj_t; +#if SOC_TOUCH_SENSOR_VERSION == 3 +static touch_sensor_handle_t touch_sens_handle; +static touch_channel_handle_t touch_chan_handle[15]; +#endif + static const mtp_obj_t touchpad_obj[] = { #if CONFIG_IDF_TARGET_ESP32 {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_NUM0}, @@ -80,6 +92,21 @@ static const mtp_obj_t touchpad_obj[] = { {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM12}, {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM13}, {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM14}, + #elif CONFIG_IDF_TARGET_ESP32P4 + {{&machine_touchpad_type}, GPIO_NUM_2, TOUCH_PAD_GPIO2_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_3, TOUCH_PAD_GPIO3_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_GPIO4_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_5, TOUCH_PAD_GPIO5_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_6, TOUCH_PAD_GPIO6_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_7, TOUCH_PAD_GPIO7_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_8, TOUCH_PAD_GPIO8_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_9, TOUCH_PAD_GPIO9_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_10, TOUCH_PAD_GPIO10_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_11, TOUCH_PAD_GPIO11_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_GPIO12_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_GPIO13_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_GPIO14_CHANNEL}, + {{&machine_touchpad_type}, GPIO_NUM_15, TOUCH_PAD_GPIO15_CHANNEL}, #else #error "Please add GPIO mapping for this SoC" #endif @@ -102,14 +129,45 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ static int initialized = 0; if (!initialized) { + #if SOC_TOUCH_SENSOR_VERSION == 1 || SOC_TOUCH_SENSOR_VERSION == 2 touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + #elif SOC_TOUCH_SENSOR_VERSION == 3 + touch_sensor_sample_config_t sample_cfg[1] = { + TOUCH_SENSOR_V3_DEFAULT_SAMPLE_CONFIG(1, 1, 1), + }; + touch_sensor_config_t sens_cfg = TOUCH_SENSOR_DEFAULT_BASIC_CONFIG(1, sample_cfg); + check_esp_err(touch_sensor_new_controller(&sens_cfg, &touch_sens_handle)); + touch_sensor_filter_config_t filter_cfg = TOUCH_SENSOR_DEFAULT_FILTER_CONFIG(); + check_esp_err(touch_sensor_config_filter(touch_sens_handle, &filter_cfg)); + #endif initialized = 1; } + #if SOC_TOUCH_SENSOR_VERSION == 3 + else { + // Stop the touch controller so a new channel can be added. + check_esp_err(touch_sensor_stop_continuous_scanning(touch_sens_handle)); + check_esp_err(touch_sensor_disable(touch_sens_handle)); + } + #endif #if SOC_TOUCH_SENSOR_VERSION == 1 esp_err_t err = touch_pad_config(self->touchpad_id, 0); #elif SOC_TOUCH_SENSOR_VERSION == 2 esp_err_t err = touch_pad_config(self->touchpad_id); + #elif SOC_TOUCH_SENSOR_VERSION == 3 + touch_channel_config_t chan_cfg = { + .active_thresh = {1000}, + }; + esp_err_t err = ESP_OK; + if (touch_chan_handle[self->touchpad_id] != NULL) { + err = touch_sensor_del_channel(touch_chan_handle[self->touchpad_id]); + touch_chan_handle[self->touchpad_id] = NULL; + } + if (err == ESP_OK) { + err = touch_sensor_new_channel(touch_sens_handle, self->touchpad_id, &chan_cfg, &touch_chan_handle[self->touchpad_id]); + } + check_esp_err(touch_sensor_enable(touch_sens_handle)); + check_esp_err(touch_sensor_start_continuous_scanning(touch_sens_handle)); #endif if (err == ESP_OK) { #if SOC_TOUCH_SENSOR_VERSION == 2 @@ -121,6 +179,7 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); } +#if SOC_TOUCH_SENSOR_VERSION == 1 || SOC_TOUCH_SENSOR_VERSION == 2 static mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { mtp_obj_t *self = self_in; #if SOC_TOUCH_SENSOR_VERSION == 1 @@ -135,6 +194,7 @@ static mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); } MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); +#endif static mp_obj_t mtp_read(mp_obj_t self_in) { mtp_obj_t *self = self_in; @@ -144,6 +204,9 @@ static mp_obj_t mtp_read(mp_obj_t self_in) { #elif SOC_TOUCH_SENSOR_VERSION == 2 uint32_t value; esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); + #elif SOC_TOUCH_SENSOR_VERSION == 3 + uint32_t value; + esp_err_t err = touch_channel_read_data(touch_chan_handle[self->touchpad_id], TOUCH_CHAN_DATA_TYPE_SMOOTH, &value); #endif if (err == ESP_OK) { return MP_OBJ_NEW_SMALL_INT(value); @@ -154,7 +217,9 @@ MP_DEFINE_CONST_FUN_OBJ_1(mtp_read_obj, mtp_read); static const mp_rom_map_elem_t mtp_locals_dict_table[] = { // instance methods + #if SOC_TOUCH_SENSOR_VERSION == 1 || SOC_TOUCH_SENSOR_VERSION == 2 { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&mtp_config_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mtp_read_obj) }, }; diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index ea403f42218..a2034b74927 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -293,6 +293,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, self->tx = 5; break; #endif + #if SOC_UART_HP_NUM > 3 + case UART_NUM_3: + break; + #endif + #if SOC_UART_HP_NUM > 4 + case UART_NUM_4: + break; + #endif case UART_NUM_MAX: assert(0); // Range is checked in mp_machine_uart_make_new, value should be unreachable } diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 7460bf35938..12835f313e7 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -108,7 +108,8 @@ void mp_task(void *pvParameter) { #endif #if MICROPY_HW_ESP_USB_SERIAL_JTAG usb_serial_jtag_init(); - #elif MICROPY_HW_ENABLE_USBDEV + #endif + #if MICROPY_HW_ENABLE_USBDEV usb_phy_init(); #endif #if MICROPY_HW_ENABLE_UART_REPL diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index 77904865baa..cb75296fe51 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -1,13 +1,20 @@ -## IDF Component Manager Manifest File dependencies: espressif/mdns: "~1.1.0" espressif/tinyusb: rules: - - if: "target in [esp32s2, esp32s3]" + - if: "target in [esp32s2, esp32s3, esp32p4]" # Temporary workaround for https://github.com/hathach/tinyusb/issues/3154 # Can be removed once fix is released in espressif/tinyusb git: https://github.com/micropython/tinyusb-espressif.git version: cherrypick/dwc2_zlp_fix + espressif/esp_hosted: + rules: + - if: "target == esp32p4" + version: "2.2.4" + espressif/esp_wifi_remote: + rules: + - if: "target == esp32p4" + version: "0.15.2" espressif/lan867x: version: "~1.0.0" rules: diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 1d002fc84bb..a2ad9267cb2 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -196,7 +196,7 @@ static mp_obj_t esp32_wake_on_gpio(size_t n_args, const mp_obj_t *pos_args, mp_m } static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_gpio_obj, 0, esp32_wake_on_gpio); -#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP +#if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP static mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { if (mp_obj_is_true(enable)) { gpio_deep_sleep_hold_en(); @@ -331,7 +331,7 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_wake_on_gpio), MP_ROM_PTR(&esp32_wake_on_gpio_obj) }, - #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP + #if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, #endif #if CONFIG_IDF_TARGET_ESP32 diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 62f0bf73cf9..ec972bfc68e 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -260,8 +260,12 @@ static mp_int_t mp_machine_reset_cause(void) { } #if MICROPY_ESP32_USE_BOOTLOADER_RTC +#if !CONFIG_IDF_TARGET_ESP32P4 #include "soc/rtc_cntl_reg.h" #include "usb.h" +#else +#include "soc/lp_system_reg.h" +#endif #if CONFIG_IDF_TARGET_ESP32S3 #include "esp32s3/rom/usb/usb_dc.h" #include "esp32s3/rom/usb/usb_persist.h" @@ -274,8 +278,13 @@ MP_NORETURN static void machine_bootloader_rtc(void) { usb_dc_prepare_persist(); chip_usb_set_persist_flags(USBDC_BOOT_DFU); #endif + #if !CONFIG_IDF_TARGET_ESP32P4 REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); esp_restart(); + #else + REG_WRITE(LP_SYSTEM_REG_SYS_CTRL_REG, LP_SYSTEM_REG_FORCE_DOWNLOAD_BOOT); + esp_restart(); + #endif } #endif diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index c0bd4516e58..55503ff0bae 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -180,6 +180,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c5" #elif CONFIG_IDF_TARGET_ESP32C6 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c6" +#elif CONFIG_IDF_TARGET_ESP32P4 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32p4" #endif #endif #define MICROPY_PY_NETWORK_INCLUDEFILE "ports/esp32/modnetwork.h" @@ -257,6 +259,17 @@ #define MICROPY_HW_USB_PRODUCT_FS_STRING "Espressif Device" #endif +#if CONFIG_IDF_TARGET_ESP32P4 +// By default, ESP32-P4 uses the HS USB PHY (RHPORT1) for TinyUSB +// and configures the full speed USB port as USB Serial/JTAG device +#ifndef MICROPY_HW_USB_HS +#define MICROPY_HW_USB_HS 1 +#endif // MICROPY_HW_USB_HS +#if MICROPY_HW_USB_HS && !defined(CFG_TUSB_RHPORT0_MODE) && !defined(CFG_TUSB_RHPORT1_MODE) +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#endif +#endif + #endif // MICROPY_HW_ENABLE_USBDEV // Enable stdio over native USB peripheral CDC via TinyUSB @@ -265,14 +278,14 @@ #endif // Enable stdio over USB Serial/JTAG peripheral +// (SOC_USB_OTG_PERIPH_NUM is only 2 on the ESP32-P4, which supports both native USB & Serial/JTAG simultaneously) #ifndef MICROPY_HW_ESP_USB_SERIAL_JTAG -#define MICROPY_HW_ESP_USB_SERIAL_JTAG (SOC_USB_SERIAL_JTAG_SUPPORTED && !MICROPY_HW_USB_CDC) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (SOC_USB_SERIAL_JTAG_SUPPORTED && (!MICROPY_HW_USB_CDC || SOC_USB_OTG_PERIPH_NUM > 1)) #endif -#if MICROPY_HW_USB_CDC && MICROPY_HW_ESP_USB_SERIAL_JTAG +#if MICROPY_HW_USB_CDC && MICROPY_HW_ESP_USB_SERIAL_JTAG && (SOC_USB_OTG_PERIPH_NUM <= 1) #error "Invalid build config: Can't enable both native USB and USB Serial/JTAG peripheral" #endif - // type definitions for the specific machine #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) @@ -351,7 +364,7 @@ typedef long mp_off_t; #ifndef MICROPY_BOARD_ENTER_BOOTLOADER // RTC has a register to trigger bootloader on these targets -#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32P4 #define MICROPY_ESP32_USE_BOOTLOADER_RTC (1) #define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) machine_bootloader_rtc() #endif diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 309ee0b14a2..37f72c26349 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -182,13 +182,13 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); - #if CONFIG_IDF_TARGET_ESP32 + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32P4 eth_esp32_emac_config_t esp32_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); #endif esp_eth_mac_t *mac = NULL; - #if CONFIG_IDF_TARGET_ESP32 + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32P4 // Dynamic ref_clk configuration. if (args[ARG_ref_clk_mode].u_int != -1) { // Map the GPIO_MODE constants to EMAC_CLK constants. @@ -223,7 +223,7 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar #endif switch (args[ARG_phy_type].u_int) { - #if CONFIG_IDF_TARGET_ESP32 + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32P4 case PHY_LAN8710: case PHY_LAN8720: self->phy = esp_eth_phy_new_lan87xx(&phy_config); @@ -251,7 +251,7 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar self->phy = esp_eth_phy_new_generic(&phy_config); break; #endif - #endif // CONFIG_IDF_TARGET_ESP32 + #endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32P4 #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL case PHY_KSZ8851SNL: { @@ -286,7 +286,7 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar #endif } - #if CONFIG_IDF_TARGET_ESP32 + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32P4 if (!IS_SPI_PHY(args[ARG_phy_type].u_int)) { if (self->mdc_pin == -1 || self->mdio_pin == -1) { mp_raise_ValueError(MP_ERROR_TEXT("mdc and mdio must be specified")); diff --git a/ports/esp32/uart.c b/ports/esp32/uart.c index 491314e04d1..6e00fa3aa26 100644 --- a/ports/esp32/uart.c +++ b/ports/esp32/uart.c @@ -40,6 +40,10 @@ static void uart_irq_handler(void *arg); +#if CONFIG_IDF_TARGET_ESP32P4 +static uint8_t __DECLARE_RCC_ATOMIC_ENV __attribute__ ((unused)); +#endif + // Declaring the HAL structure on the stack saves a tiny amount of static RAM #define REPL_HAL_DEFN() { .dev = UART_LL_GET_HW(MICROPY_HW_UART_REPL) } diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c index b90f53aa49d..bca2305f19c 100644 --- a/ports/esp32/usb.c +++ b/ports/esp32/usb.c @@ -42,18 +42,17 @@ void usb_phy_init(void) { // ref: https://github.com/espressif/esp-usb/blob/4b6a798d0bed444fff48147c8dcdbbd038e92892/device/esp_tinyusb/tinyusb.c // Configure USB PHY - usb_phy_config_t phy_conf = { + static const usb_phy_config_t phy_conf = { .controller = USB_PHY_CTRL_OTG, .otg_mode = USB_OTG_MODE_DEVICE, + .target = USB_PHY_TARGET_INT, }; - // Internal USB PHY - phy_conf.target = USB_PHY_TARGET_INT; // Init ESP USB Phy usb_new_phy(&phy_conf, &phy_hdl); } -#if CONFIG_IDF_TARGET_ESP32S3 +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 void usb_usj_mode(void) { // Switch the USB PHY back to Serial/Jtag mode, disabling OTG support // This should be run before jumping to bootloader. diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 40cb00ca485..8abd021548d 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -59,7 +59,7 @@ #define MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING "1.00" #endif -#ifndef CFG_TUSB_RHPORT0_MODE +#if !defined(CFG_TUSB_RHPORT0_MODE) && !defined(CFG_TUSB_RHPORT1_MODE) #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) #endif From 50a06b64042feb5387b7d520152f5f09b1537a68 Mon Sep 17 00:00:00 2001 From: Vincent1-python Date: Fri, 15 Aug 2025 17:26:05 +0800 Subject: [PATCH 1550/2098] esp32/boards/ESP32_GENERIC_P4: Add board definition for ESP32P4. Includes a base variant with LAN, and C5_WIFI and C6_WIFI variants with LAN, WiFi and BLE. And builds this board in the esp32 CI, to cover the P4 support. Signed-off-by: Vincent1-python Signed-off-by: Angus Gratton Signed-off-by: Damien George --- .github/workflows/ports_esp32.yml | 1 + .../esp32/boards/ESP32_GENERIC_P4/board.json | 21 +++++++++++++ ports/esp32/boards/ESP32_GENERIC_P4/board.md | 2 ++ .../ESP32_GENERIC_P4/mpconfigboard.cmake | 6 ++++ .../boards/ESP32_GENERIC_P4/mpconfigboard.h | 31 +++++++++++++++++++ .../mpconfigvariant_C5_WIFI.cmake | 14 +++++++++ .../mpconfigvariant_C6_WIFI.cmake | 14 +++++++++ tools/ci.sh | 7 +++++ 8 files changed, 96 insertions(+) create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/board.json create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/board.md create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.cmake create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C5_WIFI.cmake create mode 100644 ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C6_WIFI.cmake diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index eea82126d4f..6b3700ed5ee 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -30,6 +30,7 @@ jobs: - esp32_build_cmod_spiram_s2 - esp32_build_s3_c3 - esp32_build_c2_c5_c6 + - esp32_build_p4 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/board.json b/ports/esp32/boards/ESP32_GENERIC_P4/board.json new file mode 100644 index 00000000000..00761d511f7 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x2000" + }, + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "esp32p4_devkitmini.jpg" + ], + "mcu": "esp32p4", + "product": "ESP32-P4", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/board.md b/ports/esp32/boards/ESP32_GENERIC_P4/board.md new file mode 100644 index 00000000000..22a450cb898 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/board.md @@ -0,0 +1,2 @@ +The following firmware is applicable to most development boards based on ESP32-P4, and +the development boards must be equipped with at least 16 MiB external SPI Flash. diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.cmake b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.cmake new file mode 100644 index 00000000000..876a186decc --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.cmake @@ -0,0 +1,6 @@ +set(IDF_TARGET esp32p4) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.p4 +) diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h new file mode 100644 index 00000000000..b7a88784802 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h @@ -0,0 +1,31 @@ +// Both of these can be set by mpconfigboard.cmake if a BOARD_VARIANT is +// specified. + +#ifndef MICROPY_HW_BOARD_NAME +#define MICROPY_HW_BOARD_NAME "Generic ESP32P4 module" +#endif + +#ifndef MICROPY_HW_MCU_NAME +#define MICROPY_HW_MCU_NAME "ESP32P4" +#endif + +#define MICROPY_PY_ESPNOW (0) + +#define MICROPY_HW_ENABLE_SDCARD (1) + +#ifndef USB_SERIAL_JTAG_PACKET_SZ_BYTES +#define USB_SERIAL_JTAG_PACKET_SZ_BYTES (64) +#endif + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_PY_MACHINE_I2S (1) + +// Disable Wi-Fi and Bluetooth by default, these are re-enabled in the WIFI variants +#ifndef MICROPY_PY_NETWORK_WLAN +#define MICROPY_PY_NETWORK_WLAN (0) +#endif +#ifndef MICROPY_PY_BLUETOOTH +#define MICROPY_PY_BLUETOOTH (0) +#endif diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C5_WIFI.cmake b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C5_WIFI.cmake new file mode 100644 index 00000000000..507ed2ccc56 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C5_WIFI.cmake @@ -0,0 +1,14 @@ +set(IDF_TARGET esp32p4) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.p4 + boards/sdkconfig.p4_wifi_common + boards/sdkconfig.p4_wifi_c5 +) + +list(APPEND MICROPY_DEF_BOARD + MICROPY_HW_BOARD_NAME="Generic ESP32P4 module with WIFI module of external ESP32C5" + MICROPY_PY_NETWORK_WLAN=1 + MICROPY_PY_BLUETOOTH=1 +) diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C6_WIFI.cmake b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C6_WIFI.cmake new file mode 100644 index 00000000000..36de6d1bf70 --- /dev/null +++ b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigvariant_C6_WIFI.cmake @@ -0,0 +1,14 @@ +set(IDF_TARGET esp32p4) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.p4 + boards/sdkconfig.p4_wifi_common + boards/sdkconfig.p4_wifi_c6 +) + +list(APPEND MICROPY_DEF_BOARD + MICROPY_HW_BOARD_NAME="Generic ESP32P4 module with WIFI module of external ESP32C6" + MICROPY_PY_NETWORK_WLAN=1 + MICROPY_PY_BLUETOOTH=1 +) diff --git a/tools/ci.sh b/tools/ci.sh index 098efbf9a23..60e870ce65b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -254,6 +254,13 @@ function ci_esp32_build_c2_c5_c6 { make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C6 } +function ci_esp32_build_p4 { + ci_esp32_build_common + + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_P4 + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_P4 BOARD_VARIANT=C6_WIFI +} + ######################################################################################## # ports/esp8266 From eb5d89cd8324407eda67066292d4cabbc4ce4cd4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 20 Nov 2025 17:33:01 +1100 Subject: [PATCH 1551/2098] esp32/usb_serial_jtag: Flush usb_serial_jtag TXFIFO from ISR. This was necessary to un-wedge the USJ TX path on ESP32-P4, I think because the bootloader prints a lot on this chip. I think it might be possible to hit it on other chips, though. The implementation is based on the ESP-IDF driver, which will always add an extra flush when the TXFIFO is empty in case the host is expecting a ZLP. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/usb_serial_jtag.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ports/esp32/usb_serial_jtag.c b/ports/esp32/usb_serial_jtag.c index c4834e56f96..2df7e200862 100644 --- a/ports/esp32/usb_serial_jtag.c +++ b/ports/esp32/usb_serial_jtag.c @@ -71,23 +71,31 @@ static void usb_serial_jtag_handle_rx(void) { static void usb_serial_jtag_isr_handler(void *arg) { uint32_t flags = usb_serial_jtag_ll_get_intsts_mask(); - - if (flags & USB_SERIAL_JTAG_INTR_SOF) { - usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SOF); - } + usb_serial_jtag_ll_clr_intsts_mask(flags); if (flags & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) { - usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); usb_serial_jtag_handle_rx(); mp_hal_wake_main_task_from_isr(); } + + if (flags & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) { + // As per the ESP-IDF driver, allow for the possibility the USJ just sent a full + // 64-bit endpoint to the host and now it's waiting for another ZLP to flush the result + // to the OS + usb_serial_jtag_ll_txfifo_flush(); + + // Disable this interrupt until next time we write into the FIFO + usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); + } } void usb_serial_jtag_init(void) { + // Note: Don't clear the SERIAL_IN_EMPTY interrupt, as it's possible the + // bootloader wrote enough data to the host that we need the interrupt to flush it. usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_SOF); usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | - USB_SERIAL_JTAG_INTR_SOF); + USB_SERIAL_JTAG_INTR_SOF | USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); ESP_ERROR_CHECK(esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, usb_serial_jtag_isr_handler, NULL, NULL)); } @@ -114,10 +122,11 @@ void usb_serial_jtag_tx_strn(const char *str, size_t len) { } terminal_connected = true; l = usb_serial_jtag_ll_write_txfifo((const uint8_t *)str, l); - usb_serial_jtag_ll_txfifo_flush(); str += l; len -= l; + usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); } + usb_serial_jtag_ll_txfifo_flush(); } #endif // MICROPY_HW_ESP_USB_SERIAL_JTAG From ff7f7449d2c40a3aa1e16abf7fc337d1ee43d712 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Dec 2025 10:44:51 +1100 Subject: [PATCH 1552/2098] py/emitglue: Add hook for RV32 arch to flush D-cache for native emitter. Eventually this cache flushing mechanism should be generalised to work the same way for all architectures. But for now, this allows ESP32 RV32 SoCs to flush the D-cache whenn needed. Signed-off-by: Damien George --- py/emitglue.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/emitglue.c b/py/emitglue.c index 27cbb349ef6..8a32b227fff 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -32,6 +32,7 @@ #include #include "py/emitglue.h" +#include "py/mphal.h" #include "py/runtime0.h" #include "py/bc.h" #include "py/objfun.h" @@ -126,6 +127,9 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons "mcr p15, 0, r0, c7, c7, 0\n" // invalidate I-cache and D-cache : : : "r0", "cc"); #endif + #elif (MICROPY_EMIT_RV32 || MICROPY_EMIT_INLINE_RV32) && defined(MP_HAL_CLEAN_DCACHE) + // Flush the D-cache. + MP_HAL_CLEAN_DCACHE(fun_data, fun_len); #endif rc->kind = kind; From bdf761329664ef09bdd33e659de7fa25f6970112 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Dec 2025 10:46:43 +1100 Subject: [PATCH 1553/2098] esp32/mphalport: Enable D-cache flushing on P4 for native code. This is necessary to get native code running on the ESP32-P4. Signed-off-by: Damien George --- ports/esp32/esp32_common.cmake | 1 + ports/esp32/mphalport.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index a52498a7fec..e3b1b81cae7 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -166,6 +166,7 @@ list(APPEND IDF_COMPONENTS driver esp_adc esp_app_format + esp_mm esp_common esp_driver_touch_sens esp_eth diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index d779b6e66c0..a2b11520a5a 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -36,6 +36,7 @@ #include "freertos/task.h" #include "driver/spi_master.h" +#include "esp_cache.h" #include "soc/gpio_reg.h" #define MICROPY_PLATFORM_VERSION "IDF" IDF_VER @@ -51,6 +52,11 @@ #define MP_TASK_COREID (1) #endif +#if CONFIG_IDF_TARGET_ESP32P4 +#define MP_HAL_CLEAN_DCACHE(data, len) \ + esp_cache_msync((void *)(data), (len), ESP_CACHE_MSYNC_FLAG_UNALIGNED | ESP_CACHE_MSYNC_FLAG_DIR_C2M) +#endif + extern TaskHandle_t mp_main_task_handle; extern ringbuf_t stdin_ringbuf; From 7357fc583b5a0407d1b4b7e07aa932f99b0eb32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20=C5=A0imun=20Ku=C4=8Di?= Date: Thu, 18 Sep 2025 09:59:16 +0200 Subject: [PATCH 1554/2098] esp32/boards/SOLDERED_NULA_MINI: Add new board definition. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the upcoming Soldered NULA Mini ESP32C6 board by Soldered Electronics. Signed-off-by: Josip Šimun Kuči --- .../boards/SOLDERED_NULA_MINI/board.json | 23 +++++++++++++++++++ .../SOLDERED_NULA_MINI/mpconfigboard.cmake | 8 +++++++ .../boards/SOLDERED_NULA_MINI/mpconfigboard.h | 10 ++++++++ .../esp32/boards/SOLDERED_NULA_MINI/pins.csv | 7 ++++++ 4 files changed, 48 insertions(+) create mode 100644 ports/esp32/boards/SOLDERED_NULA_MINI/board.json create mode 100644 ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.h create mode 100644 ports/esp32/boards/SOLDERED_NULA_MINI/pins.csv diff --git a/ports/esp32/boards/SOLDERED_NULA_MINI/board.json b/ports/esp32/boards/SOLDERED_NULA_MINI/board.json new file mode 100644 index 00000000000..85b12e98df2 --- /dev/null +++ b/ports/esp32/boards/SOLDERED_NULA_MINI/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0" + }, + "docs": "", + "features": [ + "BLE", + "WiFi", + "USB-C", + "JST-PH" + ], + "images": [ + "soldered-nula-mini-esp32c6.jpg" + ], + "mcu": "esp32c6", + "product": "NULA Mini", + "thumbnail": "", + "url": "https://soldered.com/product/nula-mini-esp32-c6/", + "vendor": "Soldered Electronics" +} diff --git a/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.cmake b/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.cmake new file mode 100644 index 00000000000..48946f70945 --- /dev/null +++ b/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.cmake @@ -0,0 +1,8 @@ +set(IDF_TARGET esp32c6) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.riscv + boards/sdkconfig.c6 + boards/sdkconfig.ble +) diff --git a/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.h b/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.h new file mode 100644 index 00000000000..658919eaf4f --- /dev/null +++ b/ports/esp32/boards/SOLDERED_NULA_MINI/mpconfigboard.h @@ -0,0 +1,10 @@ +// This configuration is for a generic ESP32C6 board with 4MiB (or more) of flash. + +#define MICROPY_HW_BOARD_NAME "Soldered NULA Mini" +#define MICROPY_HW_MCU_NAME "ESP32C6" + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (7) +#define MICROPY_HW_I2C0_SDA (6) diff --git a/ports/esp32/boards/SOLDERED_NULA_MINI/pins.csv b/ports/esp32/boards/SOLDERED_NULA_MINI/pins.csv new file mode 100644 index 00000000000..1ed8bbd91ce --- /dev/null +++ b/ports/esp32/boards/SOLDERED_NULA_MINI/pins.csv @@ -0,0 +1,7 @@ +IO2,GPIO2 +IO3,GPIO3 +IO4,GPIO4 +IO5,GPIO5 +IO18,GPIO18 +IO19,GPIO19 +USER_BUTTON,GPIO9 From bfacf821f448c4d99055738e90de81e50dcb156c Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Fri, 25 Apr 2025 14:39:25 +1000 Subject: [PATCH 1555/2098] rp2/boards/WEACTSTUDIO_RP2350B_CORE: Add board.pinout. This adds an ANSI-rendered pinout for the WeAct Studio RP2350B Core board. Signed-off-by: Matt Trentini --- .../WEACTSTUDIO_RP2350B_CORE/manifest.py | 1 + .../WEACTSTUDIO_RP2350B_CORE/modules/board.py | 8 ++ tools/make_pinout_diagram/README.md | 43 ++++++++ tools/make_pinout_diagram/compress.py | 10 ++ tools/make_pinout_diagram/pinout.py | 104 ++++++++++++++++++ tools/make_pinout_diagram/weact_pinout.png | Bin 0 -> 22295 bytes 6 files changed, 166 insertions(+) create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py create mode 100644 tools/make_pinout_diagram/README.md create mode 100644 tools/make_pinout_diagram/compress.py create mode 100644 tools/make_pinout_diagram/pinout.py create mode 100644 tools/make_pinout_diagram/weact_pinout.png diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py index 832942f0526..7ae2ed15d91 100644 --- a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py @@ -1 +1,2 @@ include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py new file mode 100644 index 00000000000..8e3cc2953da --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py @@ -0,0 +1,8 @@ +import deflate +import io + +_compressed_pinout = b'(\x91\xcd\x92\xbdn\xdb0\x10\xc7w\xbdB\x96\x1b\x93M$E\xc9\x82\xa7:Q\x83"A\x1c\xd8\x89;d*\x1a\x0f\x1d\x04\x03A:t\x0b\xbc6F:HA\x91%C\xbb\x14h\x9d\x89amo\xa7\xaf\xde_\xc3\xf8\xfa\xe3\xe5\x87\x19\x8cN)\xe3\xee\x00\xf6gWS\x18\xcc\xde]]\xfe\r\xdc\xb9pc\xc7\x01\x83\x89\xf4\x97Hn\xe4\x82Q4\x8e\xceVqG\xd3O\xb0{(\xab\xeeA\xee\xceV\xba\x94\xf1\xc9|3i0\x1c\x9eUk\xaf\xbd\xffp\xa5KG\x82\xb1^\x9f\xf7\t\x8f\xa58\xd9\x8ey1P?\'\x06\x83\x9b\xaf\xdc\x12\xa4\x88\xa0\xd4\x8d\xc5\xedou\xac9*p\xd2e}\xb3\x81[\xbb\x8d\x12?\x86\xfd\xe3\xa3\xe2:\xd0\x95\xae\x1f)y\xf5/\xf7P0\xa0\xach\xb5!\xbd\x87\xeb\x12\xb4\xd7%\x99\x8b\x9f\xdf\xb3om\x93\xeb"\xff\x986\x07o\x86\xe5\x17uV\x866(CPe\x98\x8b+\x13v\x98\x98g5\x1fO\xf5M\xed\x8aL\tq\xf7\xa0\xae\xc8&J\xa9fV\x8e\xc8\xf7N\x98\x8a\xb6U\r\xc7\x06\x12\xe2\xaa\xe1\xa23\xd2Z5i\xc7\xd1\xc1\xba\x1c\rcq\xffc\xe5\xd9\xae\xc4\xd6\x17Y\xcf\x1c\xe95\xa8\x17\xe0\xeay\xb8z\xac\x83z\xbb\x87\xa7\x94\xef\xc9\x8dx\xfc,\x1e\x17\xb0a\xb6\xb0~\x03,\xc7a\xf1t\xc6\xbb\x8cJn\x125\xff-^\x0e\x16o\r\x10\x86\xc3\xe2\x83\xc1\x82\xae\xb0\x05j\x05\xd7\x16\x966\xc0\x12#,\xed\xc5\xe0\xb98l\xd8\x1dv\xa1\xe1\xb6\x85\xc5_\x0b\x10\xe2\xb0f\xad\x94\x9b\xd8\x8c\xf1\xa2\x82j\r\x0b\xf8\x1c\x02\x048\xac\x87\xc3\xb2\xee\xb0\n\xb7\x84j\x0f\xeb7\xc0r\x1c\xd6\x9c\xae\xdc\xdc\x06\xb6l\xb6\xa0\xe6\xb6\xe4\xa0l\x05\n\xa3\xf3\x130\x01\x05\xff\x0f\x10m\x00"\xc6\xce\x91\x18\xd8\x84\x99\xb2K\xde\xd6\x98"\xb9\x15\xc9Mm}y1l\x17\x9d\xb8\xc9(zm\xc4\xf63\xa0\xbb\x87j\x01\x7f]\x80\xca\x0b\xa2\x13C\x8b\xef\xbf5\xb3\xcf+\x9b\n\xaf\xa6D~\xa4\xe75=X\x13\xd0\x96\xd9\x18\xd0\x0c]\x1cm\r\xdd\xb2\xc9r\xfc\xf8\xc4\xfc\\\xd9\xe2\xc1\xf9\xb8\xc0-\x9bH\x9fu\xa3W_\x89\xee\xf0\xebV\xb9\xe9\x93\xe3\xfc\x01\x83\'\xd6K' + + +def pinout(): + print(str(deflate.DeflateIO(io.BytesIO(_compressed_pinout), deflate.ZLIB).read(), "utf-8")) diff --git a/tools/make_pinout_diagram/README.md b/tools/make_pinout_diagram/README.md new file mode 100644 index 00000000000..bebd7df7c8c --- /dev/null +++ b/tools/make_pinout_diagram/README.md @@ -0,0 +1,43 @@ +# Pinout diagram + +Pinout diagrams can be a helpful tool to display the pin layout for a board. + +Rendering them using ANSI and building them into a board's firmware means they +are always conveniently available, even over a serial connection. + +![WeAct RP2350B Core pinout diagram](weact_pinout.png) + +## Overview + +`pinout.py` generates a unicode pinout diagram that uses ANSI escape characters +to add colour. It currently generates the pinout diagram for the WeActStudio +RP2350B board but is an example that could be extended for any board. + +Display the output by executing the script: + +```bash +python pinout.py +``` + + +## Compression + +`compress.py` uses zlib to _compress input_ and output a _byte string_. + +The output from `pinout.py` can be large but compresses efficiently, so the +intent is that the byte string output from `compress.py` can then be copied to +`../modules/board.py` so that the _compressed_ pinout will be included in the +firmware. + +To execute: + +```bash +python pinout.py | python compress.py +``` + +## Reference + +[Build your own Command Line with ANSI escape +codes](https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html) +provides a good reference of how to use ANSI codes, including helpful colour +lookups. diff --git a/tools/make_pinout_diagram/compress.py b/tools/make_pinout_diagram/compress.py new file mode 100644 index 00000000000..7e95ab03527 --- /dev/null +++ b/tools/make_pinout_diagram/compress.py @@ -0,0 +1,10 @@ +import sys +import zlib + +data_str = sys.stdin.read() +compressed = zlib.compress(bytes(data_str, "utf-8"), wbits=10) +print( + f"{len(data_str) / len(compressed):.1f}x smaller: uncompressed={len(data_str)}, compressed={len(compressed)}", + file=sys.stderr, +) +print(compressed) diff --git a/tools/make_pinout_diagram/pinout.py b/tools/make_pinout_diagram/pinout.py new file mode 100644 index 00000000000..569f40510cc --- /dev/null +++ b/tools/make_pinout_diagram/pinout.py @@ -0,0 +1,104 @@ +import re + +pinout_str = """ + \u001b[44;1m WeAct Studio RP2350B Core Board \u001b[0m + + ╭── RESET + Key (GP23) ───╮ │ ╭── BOOT + ╭────────────────────────────────╮ + [26 ] [25 ] │⌾ ⌾ ╭─╮╭─╮╭─╮ ╭─[CLK] ⌾ ⌾│ [24 ] [ 23 ] + [28 ] [27 ] │⌾ ⌾ │⬤││⬤││⬤│ │ ╭─[DIO] ⌾ ⌾│ [22 ] [ 21 ] + [30 ] [29 ] │⌾ ⌾ ╰─╯╰─╯╰─╯[⏚]╮ │ │ ╭─[3V3]⌾ ⌾│ [20 ] [ 19 ] + [32 ] [31 ] │⌾ ⌾ LED ▩ ⌾ ⌾ ⌾ ⌾ ⌾ ⌾│ [18 ] [ 17 ] + [34 ] [33 ] │⌾ ⌾ (GP25) ⟋⟍ ⌾ ⌾│ [16 ] [ 15 ] + [36 ] [35 ] │⌾ ⌾ ⟋ ⟍ ⌾ ⌾│ [14 ] [ 13 ] + [38 ] [37 ] │⌾ ⌾ ⟋ ⟍ ⌾ ⌾│ [12 ] [ 11 ] + [40 ] [39 ] │⌾ ⌾ ⟍ ⟋ ⌾ ⌾│ [10 ] [ 9 ] + [42 ] [41 ] │⌾ ⌾ ⟍ ⟋ ⌾ ⌾│ [ 8 ] [ 7 ] + [44 ] [43 ] │⌾ ⌾ ⟍⟋ ⌾ ⌾│ [ 6 ] [ 5 ] + [46 ] [45 ] │⌾ ⌾ ⌾ ⌾│ [ 4 ] [ 3 ] + [RUN] [47 ] │⌾ ⌾ ⌾ ⌾│ [ 2 ] [ 1 ] + [3V3] [3V3] │⌾ ⌾ ┌──────┐ ⌾ ⌾│ [ 0 ] [VREF] + [ ⏚ ] [EN ] │▣ ⌾ │ │ ▣ ▣│ [ ⏚ ] [ ⏚ ] + [ ⏚ ] [ ⏚ ] │▣ ▣ │ │ ⌾ ⌾│ [5V ] [VBUS] + ╰────────────└──────┘────────────╯ +""" + + +def ansi_colour(wrap_str: str, colours: tuple[int | None, int | None]) -> str: + """Wrap a string in the ANSI foreground and background colour escape sequences.""" + wrapped_str = "" + + wrapped_str += "\u001b[38;5;" + str(colours[0]) + "m" if colours[0] else "" + wrapped_str += "\u001b[48;5;" + str(colours[1]) + "m" if colours[1] else "" + wrapped_str += wrap_str + wrapped_str += "\u001b[0m" if colours[0] or colours[1] else "" + + return wrapped_str + + +def add_colour(pinout_str): + symbol_colours = { + "⌾": (220, None), # Pin (Yellow) + "▣": (220, None), # Ground pin (Yellow) + "↺": (15, None), # Reset (White) + "▩": (129, None), # LED (Purple) + } + + for symbol, colours in symbol_colours.items(): + pinout_str = pinout_str.replace(symbol, ansi_colour(symbol, colours)) + return pinout_str + + +def colour_tags(matchobj: re.Match) -> str: + white_on_red = (15, 1) + white_on_peach = (15, 216) + white_on_dark_green = (15, 28) + white_on_black = (15, 16) + black_on_pink = (16, 224) + + tag_colours = { + "5V": white_on_red, + "VBUS": white_on_red, + "VREF": white_on_dark_green, + "3V3": white_on_red, + "⏚": white_on_black, + "EN": black_on_pink, + "CLK": white_on_peach, + "DIO": white_on_peach, + } + + if matchobj.group(2) not in tag_colours.keys(): + return matchobj.group(0) + + pin_colours = tag_colours[matchobj.group(2)] + + return ansi_colour(matchobj.group(1), pin_colours) + + +def replace_tags(pinout_str): + return re.sub(r"(\[\s*(\S+)\s*\])", colour_tags, pinout_str) + + +def colour_pins(matchobj: re.Match) -> str: + # Regular GPIO is green, ADC pins (40-47) are darker greeen + gpio_colours = (15, 34) # White on green + adc_colours = (15, 28) # White on dark green + pin_number = int(matchobj.group(2)) + + pin_colours = gpio_colours if pin_number < 40 else adc_colours + + return ansi_colour(matchobj.group(1), pin_colours) + + +def replace_gpio(pinout_str) -> str: + return re.sub(r"(\[\s*(\d+)\s*\])", colour_pins, pinout_str) + + +pinout_str = replace_gpio(replace_tags(add_colour(pinout_str))) +# Colours include a square bracket; temporarily change them! +pinout_str = pinout_str.replace("\u001b[", "~") +pinout_str = pinout_str.replace("[", " ").replace("]", " ") +pinout_str = pinout_str.replace("~", "\u001b[") # Put them back + +print(pinout_str) diff --git a/tools/make_pinout_diagram/weact_pinout.png b/tools/make_pinout_diagram/weact_pinout.png new file mode 100644 index 0000000000000000000000000000000000000000..35240ed9e16d09d51c09ea3d4424cf8a25a1508d GIT binary patch literal 22295 zcmd422UJwsvMy}cU_eAcB+~*4N)*Wmw1FTwNt7TUIZ4jl27<&E$yqW;lAJ+FjpPOd z0RhPwBy>Y3c&i)t*`9Ovx%d8ey!*z_7(IHidd<0N)tvQJ)mOEG)l}rj&(ohjapDBI z{3B_N6DLl=fX~^pr-3t~*u7i8*GU%*xd$hT`xsY%fBvw%uYCW+iBcFD&V&T`ZCp+1 zvCQrB*FRM>n9AzSe*cjdpLuk2RGC*A?D}ea_}j|DvWJmlVonqS41w(L2CuMU1c*x1Igs84qe@1qV58@rKGR&$>! z8&?-sl(-+XR5vfq%sLxS-rC~9*H+~6tS7>Ad26|MxmUDVGzW5NyX(|^1If|oiQb%Z zI=7j2H&#(UzM{L!OMaBR`T8bqEN-myOzpc1miOq9vtwvP!_L;m{#e&rD=L3O>bB(b z*!F^wHy4v!FYhf(;+o$BT56$Zd-LXvxw(07Z*NvsR;Q5UTw%$^*!Xxr$dB}_rRL8I zRkc4?Rt8NhC!^vvXJ&hqG)G*$))0fson0zt^-M{<@1J`B`QrodaIvR`^G_gCPELFL zk(POK^^MERvdp4|A9ENq=0wm5wE{Ksa`UW#tk%ia>DB3x#S!Kh=H-p$lCLGTW3_Qz zalNy>qVGlDw7&6d@C$ZDQm1AFeo z&6B5z=T8C0{3lPHxCDIb10U<4KZxgTfMdd+=bck0mVvGL=l8$d^aSwJ+cPKr0Is}! zN8t9!_s6pm?nv_fc&2FJnDBQBnD-R%N)0hl;ON&V{@b?+zqajmRXWUocAU0IT{c!>0d0(*)b~-=f|sn8Je#pHViKPX-Hxn!~lZ z`8JK40v#I*+K8a7KJ;Ns3*PO>gB4Ap=Bgx zuyh8mpXs^WzQAlqGnn?rFiCk~#7vg7rL>36WiC-S;lgE+*8IxgLp!Nx{S723I3$sKHMf#&`D;26iFVQ)}&!(*QQ2}Ww5!E;ij);qh?fskhXV7tQ9hm zG}3MeqF33Vt939`78c?7@a$3Py65uIfFl0zh2_`P;x>$vbp|QEH&}l)JkaL*G-Cnv zcF2rM2$Ji$GM7hL04NijL?NWcwH9^1EiryPowMj38JHKufZX1QvtDb?m;Crac)-(d zT#PYv?BPddKCY?O;)WM%+ACB=(MR~*GB!+*-gY{d1xYD{@#gr2VxA~^ds119%Qu}1 z9vZ&F{2t!=wNX5{Z*(>v$EOW$z*bU1>Eg4)gA?tzSICzJb2O5iyB%=fbhyy`tsnGP zlbD!!CUxK)HiaH+?Ua6%7P)${JqN|~I;v>(Ov#I~1It3R)II_azt+=LFmo5=hBHFu z>$G-1G=Q(|An$6&I|wq*f>T;x-wlnxc;>o$jxstwsPw?R+k0O;Fy zO)KgkNH3HfyMuQ-EV0ydJc!MY^b=xS`NFVwKLXKAw(Dq9DBGy)UGHYdjT*%_yl(KH z;(D$l1h;~l^ml!SFb;rw(#Id0;Y;4CzrG<G~oQDmWT6zKe6I=mC9(4AEc@aDNr44O_W>qyN~8}qTke$$=nXQ@PrrHJC41mnH^6xv^7wI&e zz2Ye{64kvpbfIaV7KXI06)St%Q^qEgL-r-)VpYm8a_(K?RN@!BkdUMe)DUCWWWAu0 z0cc?_o~uzRfX&+#-A}~Yo{e==kga*pXuY}DOrTdgSEpR(8hs!EB{d5-h7U;qE*(8>t$I> z#49y0@94IaI;?qJSez8U zVTSgt(iJ}HwuHX`S?lDHjc7qzJ#g{HTj}ifSJ+xh6uzl+A(K|~OQJL(+#QcVo; z(xG53c*?M3;IMdFB<{_Qz_6Ck*sz=u#>N$D7ueOuM7JNW z5~vmH!j+!SGQn>8-kR=lqg(Oxs51pNW<}M4bf|`<(Z9CEp zWLxFU(K;TkxiZ{rwh@I7Nq?~-dZKO?le&g&>GGiRo^`32%6u_B_ic~H#Z-@VpUsH! zt6gH#bB}h6y~^<7c_G~`65mnKiudi5Scq&cF`I;zaJfkGi)p;D|9u~&N z&NX-$Gr6aAXC}EzJ^pkzI+i8<$W4XmRMi7z($^<(Ku_aH`l+mgj=$_EKn=n6+Y?zfHtRvqnC;P8l2&&{swm zdPvb-)||5FOoEYcSg*xnz6mQlvK6Z%`(r-rDI!! zru6lucO5QwdpExx+3NOs$C|_v=Qi*ZpA7l_vHBa$8iy9QU#0M4szy$fqQNEzqOhDb z&RBqC*?a1eLAvN*1E+*huFFLR-5jJO@jc4z>AIqKe#z5zVbHadd-`0G;&Yhg1_01@uI9(`g&OS1+y{-9}I?vG?gl; zURmRK%#C6HglL;G%5kit@_Lu?4Iy7=Yk7O1X=#7JW7I}{lqhlE!#5TF?IKeFX*=6j z>8Q#j;`0uowys;uV?!k58Nk{_X()4RlJ(S)DH6vK)x75{`776#t+Ja79rdZbC=a8D zQm#O_r&{eMks8G}m~XUgn?(j)vOX8uW81mSYw+V6NM)-80rL$qmI|NNnU`NZDE)%b z+AL|u#B9A8p%ubx-C+_)B$H0&xSZzvA!=C;Gcn8n7LWCezJ0_DU)){tIvW{#^O9G1 z-JE^$CYp(C38mgp(it_pZ5D+j!y+6&xl`5Oq$2ie;j+9AyVr5Im{Km-ftVB}6yl9R zKgv(ipFAGV%0)%%_(4Aqy3$@&JSVr^bVt=xE^d1C{%d5xp4rL)Vg`;oP%oKy1C@n; zpW_*n9!ha#25ZR5h1LPW=%#mfQmt|c&_4~zBY4OD8))ZjA0Zl z)XcXeFPikZd-X}~!mHkqelN-Im{Q&5cOf!I=g*{QYo5pkkI>O>f=coWqSoa$KeMM8 zuMN2Zt8hie-GBFzjQ#`G9eOWq*&N%DtJK}I5k{&u zWe{yWm0sg-NUnnUy|1M&6W=zD!fj0N+|GbDH&x)dBi?|Yuy(>-;Gp)nH>}8t9 zKl@X2$1Ec;+zi!3J6fPEii^g@=UHeW8c#IoTmYN-?}Aum?xYAiEgF(y@~UMju{Li4+}TkHBxdP(jGe?NE9LHbDz@V|T9y%TbKWC-%m#X}xTPJ-+X; z7rU9$fvqE1bl4tjM6kZp;O5$lQ;{$Yi#?-IG)VroSceh@T4`RCAlcmXIQTM54H;JK z{6nG1UvIF~ch)2qtRAK8#{@@y@Q1@RC*bcv)=)!k7OI=>V-Iz&!W>!AwO@rb^bL~La!4}2mr)0PR z0FiDX$yLq**@v{H2+uTxn!jfDvx2xb)Ew$wSDPoyaoy(Krh>A>qw8RH=Iif3R1}n7 zV8O%{;m&l!7c?HQi3dyj%xa3REuLiLQc(WMg>1_VK`+*_YfVdxD$cxF@2yLWf5I=< zjq&(K8GI41%NM|QrNbArm{_K&{zxLLMnj+&r(_8!Ji}Gkopy%{k}0_Tc2=RcW=FvO z9bamwUq!e$qlc%yj$fWn3tzxQL?HMKY)P|IHjcd(9W?dx00La9ridD^#rs_ zerEPeMT_(dRe!v}ENqaUkWaXXY?|Xsk|_S}nD^Z-?*Pz3;BEAFWNS>~=i?vcc=1+g zzF)k*z|L9PbVR1{SKY+X#`w-8hmXSa`Oy3`M0q-{_P*mQs)DWfE=IP*BswivR_K^U z@pW5Jtw}*DAklCbX69)Yd@N}YnL3*g7#ws($HoG)$~Jag3SE}dGRkh4w$YjYJ| zN);U7DC_en!VCMFN+7xGl%*AWW{wqhn<8As>8jTg5hK9L!~qKhqpqvp<$4qPz`q`BIieeTH< zjtDTRW>r;_uSR~O(r0g<@|dH#KY!PU?h;#E1v*Mto+%PuM|r+m+Czro`BBHYXdt$H7e;#wFP-O-#_?7B1h|dzWq2F8hn@;ci@bctU`h> zH5w=za2nb8W~kizb6OSq6mYp)2x%PZ6$esw*57-b-)uN^sjpyniPyn?3Cw?vEzMD* z%|Nosq9b5;wT5{?v*B53u6YFr>D&Pe;Dq6-GKx&MbBpgP2GQ0=DZq1<(8e0u4hs39 zG*|3jx94(S;%tB@+;X|V8p|VvVElFC=L#v~8a8YEZZ>PP=v|tsek%rtf>0Xd&tyqJ zTOb|FDi!-QO?Y*6wc=@XJd-Ye9KNQg&o1q*e5Qw!4>p-j#v4Yq>T5k6J^^om8EcGF zST_4^Ua((R$&Z4E%M5Q9h0;{_S)Cb5MKRi^^Kcvtyq>nl*3>a>x|d(-XvI!$QpI(*ml zMRkg5|56%TsGq!}?fIQKxq5p^AK3+zxXP3EqM}`eluVl3;V&+^{X^&T=V-p7LoGt} z?K-$kCEM%m3!*HM+}pRvbV)|G4!kgqrFJV+z4XL=oJ?Z*6%_{qfh(2GeBQASjI45# zcZ!t-Na9fkPHV%~=7H|xFpIn9xl2b&l02D{?=9ChqNU3DL+5kEu;0+8q28}h9jgcD zvYo*iGhU60V#FIh(K?L2Q1IqPU+OqSY5%=$749D!`42Y}Y)h@Zc&~8N`%+J(l&L8x z6-z1>u!Fs!L{Y9P7NO*XzRb%TDN=R4Lbs#}?x+0PnYVG=5*yvAvu7hN{API5{(t zGW9v^^;n`3w)7CC?ijgpUAiW1y%X-~9lY`2W%A8F%OAt>eg@-G<47(kVBU60OJw?X z?K{vA%IP8_7=kP1V-lIT>67Sfgk6}YOVqZrQ~7kmWn6gmXnCdGU<2R=54M$h=j8Nv z812HVzsj4?HTp{>ODJO3$Yk4@og7kl4aFsN3SCwX_&qK|6#}(FZtP)Fg=qLg%@xzE zM&2`Ar6`_J67^f`$=BqtKHEPiv%KNLQKb?eVXVfnV@4h@?T<5!tf$K>%R?ZDTv_8*{5QwZdw?3MUJ$C=NWaOH1!(iCs_?JVOM)=j?V| zZrlE1DWX1`VH!)SV`RLTSIMas!nbHt({vUb@w~}dZ2T^-40{@%8ySjbldRZG%9K+J zB!6f)w1Q3b7zY*=kyWT*aO1vr^QVh&e(BVUqjKLdM9$goI zc6%A=ow@DbV3~{jDvtNKo;x-uZ5m4(6}goK+AtDB^Ya@UOu)^BddL%S@yqXGN_@Yj zzIh}q1HpZ(hKFeecl* zKI#y0V!g+{#x;H_7S-FBtySK8B?<^L>~+b!bA?!>!8WfjfmYVMtXH zWs~e)^Zxv}zbDj@4csBuS1Z7MWaL@x{yOsKXV1)LF=6J)Az%`oTPf8CA4fqtZTkU9 z55NXjU`706g@F3=0Je}{*+A~AtSh+pZ3Vv-Hw;Rn>g_^fArIhuDN^a(7 z_=)PLwjETHG94fa%Z-|2(lt&uzCvkr4Ra@BAdIS=j*U};(&U&Vs>)|gK12D@>GUX( z;b+puj1<;6w+x4>gs@8x8Nx8eIccAVPf6CI-?EZojEXDG>(|wIkg>28 zAEVI~TFvPQWd>!RCl5VbXXWsEn2IUeJwA(T#@Sn!Xu_G(%w-_@PqZ*qkX;=|t(Ypx zB(#dsbpF%Z5bm;DTq6OEO@`4NZVx;l5+hGE3ikFt&~I}YzQxr%vhtNR8z16TW>3QN z+jFve^A!YY2KY}^b;<{TU@PR7julP(yj3AW!VQ;!`;HqshOL8Rq;8aMa2XD)&~vcT z-4svcI9ewELCHk!@a~p1!>IuJh}8u7%+j&qfi&*|QFWWlZ)LG~D^;5BzDTb0p*QbA zwpq5zTc4dvS5zRrA8GSS?^rUOZj{WL(dAOo;}kp^ZI$)7v^YVZ(!inZD%GB>qgu#f zhRch_jDtFNZk>V@#ucY)rCf*tfs{yX%1RS+l?4SETjfhc0yXCb563T*)w;j4M2@gm z42cx#!elo}n$h7U1tl|WlqbO>3ZquRW`hA=U=)`Gr4-ri@942u71R`t#GO}M_=Hw! zj;+3~STp%XjTKvqvYchvRVGh8%erdF}>D zwPF^^I@Tme$Db>(wvu;%=3<%7ICpRFYtAo;BlA&@lWK$cZgWwMYMWLt?zV4gOn--5 zmZ{7g?@{(u*Bhh#;N-tumo=_wzsTN{x5sfBv(OiywvjLFLiKl;#cU%X7Ax)7Vx1wa z+yES1c`2x#!S3eF9eiR2IAX2@j;^Kud32lbEAia%(TY@C@uQUWSHtap|8-#M?#w~| z_}WMI3!J)5@rHZbMOJF*VuvcTy8ib`PT88?<00j0eShkukcryo*{xZc%dSsoA6rE< zM~PlFQG59*kyb7AbJy*>dry^VAAdZkKalxj@+v8H;@sBt2pL+!yrjdXAMae?yrfG- zO3i)93%Pv1I^cf5;=$cuv&*E^XA6T*R?@0h)K{mO$MGhCiwz_@+aykMUNWsx`y!~* zrUVQs3x$CX?Zvw9ZhV#z-+-lh-sw)o%{lt5-Azb&X0$zxfVjG~suV29@$1Kxtgo-j zgk2r6(slIMy4HQMDU<{dKpJu1{iBD_-S0O_74_UJJd}H$slB{Z9-!d;;GLA4nd5Amp{P@ftkM5L*f^b zL{0#*@2!WYq@=9T>0vb7W3&}M8SO;9gp^~P*#h&9La&RnUazC zFaz}N%7z(=Nj>m9bxj>h&et6_*ocFB{51Zn*Xs zvM>x#MpfuE_H#8LZY~6~k#^|6RGTCD4VZI3%#Q?un_EA+8DffCUJXpv?uI4$_Bgu! zkP9CfS~qv{fm?rN7%Q(RYDBGnUE9=hu`vnPu6@VEXfD@3D{-1r*Nz7IGZ%`&!=VRz zX;AE~!=)#hfed?B1T@r5FCM77 zNJmI@7F%Ky)Kg9Y>x$siDSKTIcX1^x<@Do^-*}~v2Wdy9JXv84gx3O8*YZ|WEL-&LM5p~kJC6fp0Ya8o~7Gqlbi->&7 z7cE?_S4yK0r2EcQ6awUV_wEI&BMhVpOCEd#K`}!{Tm7l4_=l>YdonJQoQXFTw`2MOUXT7gyUVlHu%CtgQlmdyixhm+Z zY~H1+P$&iuIvg8Pg_i6usqRXQ3hK&%{LvdEew7IwoLBV zJu@kNot}2n{smKlp}`}XEJW^d_Ex;V%2t3XJI&@M9T)D-hN^IGQmeh1`{0TpZe{g0)V4!{Z_zwSt)7bf@mcUZwc4N~#K&EHjU?Ex2rID!Z{!Q7>r@p6MIf zSYEz6*}3K2wtkhJaQpfA=jER=$ILFh)WUv3iv%(9nfkg)Z zG?Xa+u$uw$yXSK~*C+8iJNBR4OrCSM5FnMGVB|y;y`A@T+XE3~8(bGrkjkK%Vvcxe zqL$8l==QnvEMcm)7y1{p0||iK^8Qu(v<#^D#;SO+w}XURtPBB8y$?o7;H*THJ#E9U zOGg0KbFa@|h0^%d#OAHltk!N1>j4i%O1(N`O!HqnlcpsgDedFDAgJMAg0h83$Fian zs^WV&(b?+zG=h0gBMl1`f^HW#jS?Skm76O}R;MEkec7)B?{7^#d3tY-qkR1?_^K8) zBzaSI4ySZWGJx>d^egF#?dGUkYN3~9lKtL0Vv0e+*rvBydrWLT*aQLei}P1EP2I%( zOBD-CE|M0Y8!rt-*xnII74JTu>9>@?0XP7kO`2)U2rSKWHo-F0a~AGuHtqt(AY2jz zuXE}eOj;$UU6xO@2K*B}dBM8*800S488t)cXFpHI^ZpczMR`b5wVB9+fSYx+kAiNa zDH#?cTFJ$V7QTA|Pl12*B>~)b7;{Deo-&T;1H%asnm&0W2VY@CE1Dmp&dFk6G+! z9A7n+%+|-nxM2 zp75AJ*nKeEPub$`j%!!OV-t#dn6eKERS+uX_&2AGSUV@}S( z;W#3EkuaX{;H4iGY{BE}ujM7BuNy0nu->1UG`y>IcA{NP+@D`@uP8TSo1g?Io+pv{ z{8w=*D6+#j8uk&jIp2=2z$|o1P0<9MI{Pa;0bd;pdRXpPnC2U~YyPUGzTW-AB^jF2 zL}hlVD(ta4bUF1dcFgHlv1&#cZ}E@1@2pAo$FE7MhLjD?h7~aI>sdcOpn>^t z7c<&wNZ6DwuPYN~l5OSGxhYwnTT-g@T0iV2rCtcbo?rTtuKXJo-O_!`C>PvQYkCIHs{a2U*TufibqvVCI`+>dl_hH_0Yo)XyT&tfb+r#RBTYSbezQ&~g z1q9W^wp%Y(-CkH&d$ra5pJDJ)8rJtlO;~%0PH(T3nhE_8_V2?{B4RV@K-#Z^RsJ1J ziWcKSbbo+<0Iu2RfCS%(E?HeO6F`g^ZgaSN=JH{b}lG}mtW!0 z5cOX-)e`r2LN-Rsb(PTOf{svI3eed*SDTH5TCDiKd-Hc2`K~PXJ{eQo+gBXp^P)G4 zO(LRS5Q{ zyqbLh!EHIui^vl9`65?L^R01LhcA%by*aW9I$xoBx821Jnw2e@txju_R0qNS{t7ym zKuv*gS*X4im|uF@`s(hJdfirZLVW$mvC)0T)|x^h$H3Lkn!PaUE?hq0RndIk8_@cw zvSjTan*i=8t?^gSL+k`G?`_g(-!W|%)K|>~b7cT1f{_v^m%YsCzhQcli8b#~858KD zh)OB}8GEiQ+_iXpK#5<{5-a*dgpUz3hg)d=2mNZC<*FCHL=>9`jj}d`3!ydbir3e_ zivj`OH{QTOmRGBm?t;mHj-o++{rOH_pM=N-ga)le94*JXD{lFQU%ZBteV0pJQx9O- zkarJ{J9P5dp1Kg!^pU%(Shs#*lDwL)NH>b2MDF`n#gCwZA(k5-(yWKAczw1Ucc0{y zadRoH_^5<;pDwmO*t#davDiyv)aaMgBNr6mv3LKvYWeZZ?mcAAU}1W^wHChZ?Yh;{ z&H_{a=^%9=YW(&`d(4_n8glYf@cCF%DAS{$9(q(J*|dv|0rPX^*111S1bf#o-!U=k zm7@pOJCuG+=-fl+qvIKcNYnl*SK(bm51NQ|rsZ$;yhnWMa@o4AtqagzP2XS7+@I-> z5afI2xU2{(X-aG~O0{tE06a?0WIWk31s3pXi-k32wywW>(v}Y-On}nLWid|le?HUN&laodsRp;T-X2D-Ca)z_k|+^IZU(Qcp&)iZkA~=fU5N9d$cG~F=&x@>w ztOu=;H~F?sO~3wojB7`~y2$;Ix>(3woSYEhHIVsI{2S0(A`}03L91q@VnvV`w(+GT-v2JCA4dhjNAn>`l75>p$&3{VQ(zJ;39?9Q5z<=J+?Q)AD~GmnpT){JUhD zefFmZ#vMFtC(b>@_JH7$6YiWHNqw`(3} z>9AFyY( z9m1UPu~0iS!)U9hUJS~|9{Bv12(!Ba?qTB+E^kaE@hK+jLqR!OZV=g_MZN3V*|ngD z!2Lb(-xxSc;gGe>Nq{^mIb!L86}wpr@G!?IMB$WXtyvZ_%g)x*Ub~>EqC~B@I5Sw#iPqB58vDD z3M6*)vtH)x#nUPT4WEL6;>f_oNz=t54Q~6Rj+2epkrFf9vVNTK(5zFw;Ftby{5IGg zmQY%k>5Ul`$%nPP{r#7CQe|2=?QuYKluwds97P=t(s`WLJynNIf;RZGP@Rh&h zmy)$l6MKBE<9l_e+bZ2hjU`J3)yww@p1ff2J1pJx5s%GQ$B?JF=rX6PD_aKhmwmf6_854*04s^!<48`x_vxUs>S{4svEARK8 zOb7Co?kg(KSYN*-pFP5xk87ues}2IVZ^$HlIkiFzX4sz7R&3Kv-913hxg6EWg!De0J}s8W*^;{ z!>xVF4yNA|;ov-KdR{2J-1pN2`cS#E*v6{q!S#`0T4_$&@FQcHeGOh(9TEB^OLuoZ z^D%@`tB(=S1HQZOOOn^t=%g=m81LDyk>hVZ4$--&_W2kQObzW5X-;n>R+{^O+X;mH zitKk~nR-hvif4`wRX?;_i89JW_n+4hNd#u)HP`Vo7P%`&W-IuhU*IP_z$9vL7#9zi zIniU(fvy0E7LgDGOociL-Q*FxHOmj--t-=>o{La`a8IT`IX6~1I&pLIUFiuRXo^hR zmZ=paq-r-tCpJF8gwYvA-@d_;({3#|bpctmJAd@W{FW($s=lkI!OWTDOCj_Zhu?qO zigmbO@adb=cAzUEUt6?s-xwAtYFnZE`0gE_&d!9>B*P;he+f)8 zquiCIY9VUuRG%eg946soZ)zt!p`5C@=9^;&Z>Jyp3gm-yQS%``DMoaiC8K!X3sc}Z@xla{h@FaLsgBmELhRb0s2Bk+*B zK_Lq<`AVOT8lP}PB`Nnr8{v1^Uf-nbPh+9SL~7kDA#yrlPYua;C58QrMW^0ED&*-o z890RmpR^;L!h7e&pLRtwPIa9ADHBcw7N+&-_x96QCv#f-||TR36RdV{R&I_K@J}~)fVOq?dm@#vC>_SM$n8UD-gT4sSmzNYz^K~WYH8TJ$D{u<|h7(xoyHJ^%xCu%q>D@@NT}| zHu_pihWeJbq;1#%S_w4@gxHK$rf@NW9RM2{m2fLm0urXIs$8(zpt$y%{1&PLcl-L^ z78CQZekHvuklB#t&%0>MXWR}3#?J+x5K{Kr-EB`NN%0Ep{m8QB3U?(&Kg z)(iPX!U8$u9oxhSCkR12Cm@;{hs}BkD-UYk(2Dd3?v;8o#`E|rlx8VZfJ-y(46Ekc zRu(O(r>q*l!5U@mMaxxE5}0J7IsE@)8~)GyxhPFt-zV21nwP(a+m~fnw=enJc+z7* zjSbJ+C8K}*v6ELj`=Q0d$wDPVixdLKX9BVCa0GYr*3OaiAzJ89FFDOe*-f_j_yo>BY3JEtIvN>TtQ6L+s z5BpatT`E3y3>JahW4>uR2)7C0vVjz|8^=l>@-{68~*e@Q1Bn8-OV9YBaB+<%?Wfec{I znv99n+kYAMeu7Y@*rEZVtV7;^$u$8UFYn$DYyh#wA@Gd(R-9LZ+_`@WUQnc;JujPL?(8sR68Mit)jhWtG7`Rs%oBlk9r5~% zgS3R-3ELO4nZ`SF_0R5Yz*X>$G4K7Bw-(;Rwa7Ol(DQ<=!XB*s~A@n~ii*c}tUo68Ec6x!}Cg`tmw~ zG;^r(EsG7BF@;-KAN>jv0f^~%TmFyX zeGA=pQ-hQUY4@WjL>$Cdq?tv)XH>Mb7kvRl1INvtB%({tD?M6g3wKv{AZC`mTiUuy zr@X$b3I%8GkAlq85Xe*?QBA_eOY7}lH`@2Tw?$7)baW5)1CvvjCJ9JbtD>IE(0hlI zc~)+E4otipjtY+?QnJ{$;-iCgQa~^Tqu%VxI=FEZ8nK#J)9X zG1dpLLX-b!u2_j=1@F!@?#~qmb)%D8(33t1*w_oO^DG2{C_%{nj!^lRuA1j=Y`oys zwnf8_q#r#Vib7*9RMHYh-ba0Igqbz*MemZu_~A8)8(y$M$XqB24L`R@d~0;nOCddrK-L%)QP-IYh>$A2Nz{o)=2aztND zAlnX1PGd@$w0f6TTH(F;mVrp(y#9E0$?}cIis%3d;G^B;0Y(I1Id@q`-nvu(#-qF= zAjXp>Hs4lV@4ia+jfEBbP{)4LN2Mf1WwzG*m(bG5EKAmMmVe6J`O?v2V_4whLurWY z1i}T4Sh=41M8kT9Ah1yzt#dKKK;$9p9OBI|p&^3XkvxrQTesda2OX?0yHbu+OdAIc zvbbb6u`Jojd5F*-tHO$&$e`Ryv*Kusn><>cYV-*&-nXjgs;`)C@+djf6aP%0V|rk9 zfyvc?X}sy_zipo?Hvbu@Sgq0xaE!~R&0`Ic^9fb5olvPnOuimK#0IDfXqI;thLJ%z z5KMsp_u@P^O`v&}qlo+PFAwp5bt6Pthyeg60(ktZiJ5!V!`2K4$HSCV>YQg@5M^V9s)$S4*hPhB@VlDh>btPyw>6*fk6TxtJ;_yE`K+V%X z4*-+UyE6Eetn%1J=#<;`8#Qi9eRsi}v3CwP?ddV@{xtc7TEUs^OhIO*2bDRnzjHDPaJmuv8947FI}CPR}_;!O|-&T{Lgai z{$!vu2^kj1Sjt2TS!nEzN8cH5HtMTu0OPf%BbkJ6#$JUgOUh5pehD&jyN2qBU2ZCuXSuXEqp-}Z$$$W#7Lv$qZYOpuB zd$S&Pf0EG3LP2SJGzXo{IsSfO=KF>XNJMx!+xU09{py>hFd9mpvC6EQ2bl=xErdS&83EDCrIDEpHYexMW-_a8+bF^<1+?NHMVuXE8laK zGj*BJDn}%k{L2Z61V$aigmH=!4)6DVKt>qQiRkh$1KKgPAd9lO zkl}TNveZSOJLM|B$ie_rs!s~PJ_q}L2BN|dJXW~lfcnfa#<7_Ocl!V|Z2?i)aqeZ` zu7a<@P8MM8BS2#4o%<+t6l7%G>k)APd}}hdH=Ec82atm2bO?sBw_7WYoHL0vbaP}* z@g_C7tkU$XBG-?@$N1Q7viR)w;W3M&Npdxp5VG>USfqbQ-J!V~8W$^v+AZ$f=s1s#BHRmGw z!mrXS;SOGq#SkWuB~rq2IF;I_+6a)?@Gny}=HmS!u1l?NBzZ>WInz4wi5(Pgk2Ra< zus^!@N8;jy+-vv2t!1>pmWa<`rR;}cFc&K^PWP{82nUKQ7x6V7){a{mBeMsHr+v~8 z8nmL9?vPTzPY)t5zkD(0A=qfTzwF^YBJaunQ=@PrC&YO`Q`bM+%Kw$qVstmNhvS)Z z@6awcpAob#Kd3*oQo#x~YrgCTxetKnSo;UpN3Q_Y|3*Bh96cr9nN&=`!a)}MG1zq%Z=Z`AvYmM+hYv%K1>ItB5F6lK*yC6 zWF8lWnsc12Uwcm|3i%U>Pbj zRDsMCgRIBlza{Nu;&dVIy}7BAZUcn6*0H^WA0iwHjZ?jX_V`CjqtD=FAQ8AagnP-- zw|M>M8c4j=N38AEruZ^|@z-YvsalVb`;YR>Auu2X4a9(Tt0>P2>XM-t$BFj@r4J;ov3 z{U7T$6O3E1hNy{ZaJub}tsx5Ns?MUvy#*%n%Mf2NpY0K@>T=^roEGXl9YqAhtQUFAOFn-Arwd@5FSq5KJd->F3p*a`#8SB`L5AIWHgc=|6 zIy(t)10>MLH;dXRdiyz}8n4~Tb#t*IpbL)w34m@yiW=CQ6`lQz2$4~ME8t#7@=pry z-XcL)fGTO~f6kHdHTuHsx{NZ88aRpfUlP$F_WS)ZyD>yxrS09+H=LZ8T8z>VC+96) z9Zjky;1hbNJN9ixnGeB@UUSwYdG|Wey~jOr&)k=XWzW2K7_A{R^F=Wkc3&)6DVhgL zUuS*%6wZWsvn7DQQ{`}&E4DxksFR%y)eqb2>`mfp@Wu*{1zyB#F1LbHE$)*%=y6B~ zTB-@H9;f}jUK!q?CpxaO%x=Ar0T^aL{Y-9;o=}?c(BfdS@fuStNhPhxWuUR8@POFZ zve&9qPSB@qV*aJ}LvbMgV>)ySOJ|7TBpUXY2w=sh#H<$KurgJ!5HHRjCpXc;iyEz9 z>Yf^)Avg9D@@bUleFf~4BI}&v&Y$vDa0I_=rUnp<0~E-3FnPb_)S)42%yQgq9!`OL zJt@93$JeXP?St-}5aj`!B5@UnZJs?Bk3>5L!WC{1p~4_H?NrU>ZS0jBugyWtmZ>YU z^$W0-y_`mnW24vFNK%4W7vO$3M4a?55^_PZr>K5j_wGITF+N761t>`z0A8ANkYrA+2n^p_p0d`k^208<=?@qfb$1BJjT_C zKTcgVKFx@DmB$ZHJXLc3HPE4b)l*9y5S0po!|fhft;_A|jKP30`~QtGQ4rkz(otn8 zC=G&HY5oITN69uh2ZGbqsc+A%s(B?6H2hDRIexlcM(|<1m@}>#YP}B6P4!;2{+Y?e zKPX8wG-`11#h6_5=n1)sYc@TlIiCtz2<3#f>BZ{v=6Z{7WdZf{CL2ec10Uh`k8sWP z$T?TjEFat)?L!exhyjz$9Q=SEPM@J4aR2x%AeJ!p{n6&5(WRs2i}+=QFjp9q(QH|^ z9Vj*fZ7%?2sY^v@Kes-(+iE=L3UK=-74JaMBQ#VHnVGXLr}Iyfdq`2;UDP$p(=Y*E zAyRl4LjX7{bKr5t+z5H)8a!9X94t=7mh!J^ATKqG5DMg^2GT=9nbZGvVhyv33a(lf z;*0YUd%iLeL%TvxH1-f6kL()4*}h<-|409451K*zx*L=tm@x35>22~se>m{!-ao8~ zR!wm}-b`5~fViTBo_j(=C_sSsg*pK5;l>Cb0QwaCGod1uV1GH~|21>@tMR|x3jw^J zojFK)ME|sk_fH{y_sqWu@%J>1laXsV>11BT=VsF13C8mcx6T52Le4b>ASC~{*LeJ& z@h$|8`?3atdb5zMt$HM6!QyM5)0Y^ZIcu^0^m0~92iB(NU8(mKO#d!fX#Qn=?W_O$ zT>RxH@n0_h4Lz|2`W>3pq#=5!#BdJ0Ldc5ATx4~A{={=9;(#j%udTiQplCY=7bDyx!Y>-@LnZPp4WREjHu0l7zz?FQ7S5=jRI^+Y$L^o|(Pw3D8C|GyUIg z%gm>2Um}0@+2?6>>m2z&ZJ8zw^+aFCDO`zvGyZ+L?`vueN%f&SF1dW2XRqAz(dnUk zMN|NAOz22XoZ0us`lsu3>NekQisJdTMB$WYM6ke8ClOGqFRdM%2bOY$&suTsb9#$m z-1)c~*MPje|^0A#??dx+4jI6h6Ue0zxmLg%yV}|$U`sT ztJVK{4353N*$rHaIeT0Br%cCSP!;-l)2|x0-_di<@0paEKSi$X=@sJz{vEGGSb$m1 z`N(R4ll`TIOFLiL)-OxpwUXcI>)s302^<6|s@u5cUi^N)Y=u*6%F}z&^zkt~Z-X>S|y2zTumz zd3R&@vG;pwUQe}tW<5s+lyodb!AV_BJqR_aACh>s+a_5QVaBgNR@IZuI$J+!>tC`g zU%TA5dTt0XML~BmBFX~?tHpA_o{TSWlMwPq{Ub!}cmex(Jt8+Rz&-+ss6HKFp;c{wwEF6aa? fjN?`cKK*A_^W!t|6+M*zI``Al)z4*}Q$iB}6cuwk literal 0 HcmV?d00001 From 8dc05cdba35063e5a9218fa71488c13b008cb4c9 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 25 Nov 2025 23:33:34 +0000 Subject: [PATCH 1556/2098] rp2/main: Add guard around machine_i2s_init0(). Add a #if MICROPY_PY_MACHINE_I2S guard around the call to machine_i2s_init0() in ports/rp2/main.c. This matches the existing guard around machine_i2s_deinit_all() in the same function. Signed-off-by: David Lechner --- ports/rp2/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index f01522f2438..50946b0a5ca 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -192,7 +192,9 @@ int main(int argc, char **argv) { machine_pin_init(); rp2_pio_init(); rp2_dma_init(); + #if MICROPY_PY_MACHINE_I2S machine_i2s_init0(); + #endif #if MICROPY_PY_BLUETOOTH mp_bluetooth_hci_init(); From 1ef76c7fba267753aa445678a36608a747f26334 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 21 Nov 2025 10:51:30 -0700 Subject: [PATCH 1557/2098] rp2: Add HSTX alternate function. A follow up to 40df95357c7b3270dc60421a0078fd73b122473f / PR #17692, this commit adds the HSTX alternate pin function for GPIO12-19 on the RP2350. Signed-off-by: Dryw Wade --- ports/rp2/boards/make-pins.py | 4 +- ports/rp2/boards/rp2040_af.csv | 62 ++++++++++----------- ports/rp2/boards/rp2350_af.csv | 62 ++++++++++----------- ports/rp2/boards/rp2350b_af.csv | 98 ++++++++++++++++----------------- ports/rp2/machine_pin.c | 1 + 5 files changed, 114 insertions(+), 113 deletions(-) diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index 07ce052bba3..9f48806ffca 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -60,7 +60,7 @@ def add_af(self, af_idx, _af_name, af): m = re.match("([A-Z][A-Z0-9][A-Z]+)(([0-9]+)(_.*)?)?", af) af_fn = m.group(1) af_unit = int(m.group(3)) if m.group(3) is not None else 0 - if af_idx == 10: + if af_idx == 11: # AF11 uses UART_AUX in lieu of UART af_fn = "UART_AUX" if af_fn.startswith("QMI"): @@ -74,7 +74,7 @@ def add_af(self, af_idx, _af_name, af): # pin can only be I2C0 _or_ I2C1, both sharing the same AF # index), so each PIO unit has a distinct AF index. af_fn = "{:s}{:d}".format(af_fn, af_unit) - self._afs.append((af_idx + 1, af_fn, af_unit, af)) + self._afs.append((af_idx, af_fn, af_unit, af)) # This will be called at the start of the output (after the prefix). Use # it to emit the af objects (via the AF() macro in rp2_prefix.c). diff --git a/ports/rp2/boards/rp2040_af.csv b/ports/rp2/boards/rp2040_af.csv index 454f7ed2d12..13ccca28265 100644 --- a/ports/rp2/boards/rp2040_af.csv +++ b/ports/rp2/boards/rp2040_af.csv @@ -1,31 +1,31 @@ -Pin,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9 -GPIO0,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO1,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO2,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO3,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO4,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO5,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO6,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO7,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO8,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO9,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO10,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO11,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO12,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO13,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO14,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO15,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO16,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO17,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO18,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO19,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO20,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,GPCK_GPIN0,USB_VBUS_EN -GPIO21,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,GPCK_GPOUT0,USB_OVCUR_DET -GPIO22,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,GPCK_GPIN1,USB_VBUS_DET -GPIO23,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,GPCK_GPOUT1,USB_VBUS_EN -GPIO24,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,GPCK_GPOUT2,USB_OVCUR_DET -GPIO25,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,GPCK_GPOUT3,USB_VBUS_DET -GPIO26,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,,USB_VBUS_EN -GPIO27,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,,USB_OVCUR_DET -GPIO28,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,,USB_VBUS_DET -GPIO29,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,,USB_VBUS_EN +Pin,AF0,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9 +GPIO0,,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO1,,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO2,,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO3,,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO4,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO5,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO6,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO7,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO8,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO9,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO10,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO11,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO12,,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO13,,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO14,,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO15,,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO16,,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO17,,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO18,,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO19,,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO20,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,GPCK_GPIN0,USB_VBUS_EN +GPIO21,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,GPCK_GPOUT0,USB_OVCUR_DET +GPIO22,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,GPCK_GPIN1,USB_VBUS_DET +GPIO23,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,GPCK_GPOUT1,USB_VBUS_EN +GPIO24,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,GPCK_GPOUT2,USB_OVCUR_DET +GPIO25,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,GPCK_GPOUT3,USB_VBUS_DET +GPIO26,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,,USB_VBUS_EN +GPIO27,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,,USB_OVCUR_DET +GPIO28,,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,,USB_VBUS_DET +GPIO29,,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,,USB_VBUS_EN diff --git a/ports/rp2/boards/rp2350_af.csv b/ports/rp2/boards/rp2350_af.csv index cca30d9e74c..285c175bbde 100644 --- a/ports/rp2/boards/rp2350_af.csv +++ b/ports/rp2/boards/rp2350_af.csv @@ -1,31 +1,31 @@ -Pin,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 -GPIO0,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, -GPIO1,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, -GPIO2,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX -GPIO3,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX -GPIO4,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, -GPIO5,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, -GPIO6,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX -GPIO7,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX -GPIO8,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, -GPIO9,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, -GPIO10,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX -GPIO11,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX -GPIO12,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, -GPIO13,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, -GPIO14,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX -GPIO15,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX -GPIO16,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO17,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO18,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX -GPIO19,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX -GPIO20,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, -GPIO21,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, -GPIO22,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX -GPIO23,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX -GPIO24,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, -GPIO25,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, -GPIO26,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX -GPIO27,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX -GPIO28,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO29,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +Pin,AF0,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 +GPIO0,,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, +GPIO1,,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, +GPIO2,,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX +GPIO3,,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX +GPIO4,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, +GPIO5,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, +GPIO6,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO7,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO8,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, +GPIO9,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO10,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX +GPIO11,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX +GPIO12,HSTX,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, +GPIO13,HSTX,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, +GPIO14,HSTX,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX +GPIO15,HSTX,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX +GPIO16,HSTX,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO17,HSTX,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO18,HSTX,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO19,HSTX,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX +GPIO20,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, +GPIO21,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, +GPIO22,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX +GPIO23,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX +GPIO24,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, +GPIO25,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, +GPIO26,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO27,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO28,,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO29,,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, diff --git a/ports/rp2/boards/rp2350b_af.csv b/ports/rp2/boards/rp2350b_af.csv index 3b9053f58cd..d30762f5853 100644 --- a/ports/rp2/boards/rp2350b_af.csv +++ b/ports/rp2/boards/rp2350b_af.csv @@ -1,49 +1,49 @@ -Pin,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 -GPIO0,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, -GPIO1,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, -GPIO2,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX -GPIO3,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX -GPIO4,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, -GPIO5,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, -GPIO6,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX -GPIO7,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX -GPIO8,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, -GPIO9,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, -GPIO10,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX -GPIO11,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX -GPIO12,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, -GPIO13,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, -GPIO14,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX -GPIO15,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX -GPIO16,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO17,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO18,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX -GPIO19,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX -GPIO20,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, -GPIO21,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, -GPIO22,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX -GPIO23,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX -GPIO24,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, -GPIO25,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, -GPIO26,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX -GPIO27,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX -GPIO28,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO29,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO30,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX -GPIO31,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_RX -GPIO32,SPI0_RX,UART0_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO33,SPI0_CS,UART0_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, -GPIO34,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX -GPIO35,SPI0_TX,UART0_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART0_RX -GPIO36,SPI0_RX,UART1_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, -GPIO37,SPI0_CS,UART1_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO38,SPI0_SCK,UART1_CTS,I2C1_SCL,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX -GPIO39,SPI0_TX,UART1_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX -GPIO40,SPI1_RX,UART1_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, -GPIO41,SPI1_CS,UART1_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO42,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX -GPIO43,SPI1_TX,UART1_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX -GPIO44,SPI1_RX,UART0_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, -GPIO45,SPI1_CS,UART0_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, -GPIO46,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX -GPIO47,SPI1_TX,UART0_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN,UART0_RX +Pin,AF0,AF1,AF2,AF3,AF4,AF5,AF6,AF7,AF8,AF9,AF10,AF11 +GPIO0,,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_OVCUR_DET, +GPIO1,,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,TRACECLK,USB_VBUS_DET, +GPIO2,,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,TRACEDATA0,USB_VBUS_EN,UART0_TX +GPIO3,,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,TRACEDATA1,USB_OVCUR_DET,UART0_RX +GPIO4,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,TRACEDATA2,USB_VBUS_DET, +GPIO5,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,TRACEDATA3,USB_VBUS_EN, +GPIO6,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO7,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO8,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN, +GPIO9,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO10,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_TX +GPIO11,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_RX +GPIO12,HSTX,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_OVCUR_DET, +GPIO13,HSTX,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_VBUS_DET, +GPIO14,HSTX,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_EN,UART0_TX +GPIO15,HSTX,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_OVCUR_DET,UART0_RX +GPIO16,HSTX,SPI0_RX,UART0_TX,I2C0_SDA,PWM0_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO17,HSTX,SPI0_CS,UART0_RX,I2C0_SCL,PWM0_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO18,HSTX,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM1_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO19,HSTX,SPI0_TX,UART0_RTS,I2C1_SCL,PWM1_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_DET,UART0_RX +GPIO20,,SPI0_RX,UART1_TX,I2C0_SDA,PWM2_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN0,USB_VBUS_EN, +GPIO21,,SPI0_CS,UART1_RX,I2C0_SCL,PWM2_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT0,USB_OVCUR_DET, +GPIO22,,SPI0_SCK,UART1_CTS,I2C1_SDA,PWM3_A,SIO,PIO0,PIO1,PIO2,GPCK_GPIN1,USB_VBUS_DET,UART1_TX +GPIO23,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM3_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT1,USB_VBUS_EN,UART1_RX +GPIO24,,SPI1_RX,UART1_TX,I2C0_SDA,PWM4_A,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT2,USB_OVCUR_DET, +GPIO25,,SPI1_CS,UART1_RX,I2C0_SCL,PWM4_B,SIO,PIO0,PIO1,PIO2,GPCK_GPOUT3,USB_VBUS_DET, +GPIO26,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM5_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO27,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM5_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO28,,SPI1_RX,UART0_TX,I2C0_SDA,PWM6_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO29,,SPI1_CS,UART0_RX,I2C0_SCL,PWM6_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO30,,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM7_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART0_TX +GPIO31,,SPI1_TX,UART0_RTS,I2C1_SCL,PWM7_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_RX +GPIO32,,SPI0_RX,UART0_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO33,,SPI0_CS,UART0_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO34,,SPI0_SCK,UART0_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX +GPIO35,,SPI0_TX,UART0_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART0_RX +GPIO36,,SPI0_RX,UART1_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO37,,SPI0_CS,UART1_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO38,,SPI0_SCK,UART1_CTS,I2C1_SCL,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN,UART1_TX +GPIO39,,SPI0_TX,UART1_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_RX +GPIO40,,SPI1_RX,UART1_TX,I2C0_SDA,PWM8_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET, +GPIO41,,SPI1_CS,UART1_RX,I2C0_SCL,PWM8_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO42,,SPI1_SCK,UART1_CTS,I2C1_SDA,PWM9_A,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET,UART1_TX +GPIO43,,SPI1_TX,UART1_RTS,I2C1_SCL,PWM9_B,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART1_RX +GPIO44,,SPI1_RX,UART0_TX,I2C0_SDA,PWM10_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_EN, +GPIO45,,SPI1_CS,UART0_RX,I2C0_SCL,PWM10_B,SIO,PIO0,PIO1,PIO2,,USB_OVCUR_DET, +GPIO46,,SPI1_SCK,UART0_CTS,I2C1_SDA,PWM11_A,SIO,PIO0,PIO1,PIO2,,USB_VBUS_DET,UART0_TX +GPIO47,,SPI1_TX,UART0_RTS,I2C1_SCL,PWM11_B,SIO,PIO0,PIO1,PIO2,QMI_CS1,USB_VBUS_EN,UART0_RX diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index e1a184f322c..336e6ff5a23 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -521,6 +521,7 @@ static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ALT_GPCK), MP_ROM_INT(GPIO_FUNC_GPCK) }, { MP_ROM_QSTR(MP_QSTR_ALT_USB), MP_ROM_INT(GPIO_FUNC_USB) }, #if PICO_RP2350 + { MP_ROM_QSTR(MP_QSTR_ALT_HSTX), MP_ROM_INT(GPIO_FUNC_HSTX) }, { MP_ROM_QSTR(MP_QSTR_ALT_XIP_CS1), MP_ROM_INT(GPIO_FUNC_XIP_CS1) }, { MP_ROM_QSTR(MP_QSTR_ALT_CORESIGHT_TRACE), MP_ROM_INT(GPIO_FUNC_CORESIGHT_TRACE) }, { MP_ROM_QSTR(MP_QSTR_ALT_UART_AUX), MP_ROM_INT(GPIO_FUNC_UART_AUX) }, From b24c9cf2a9d8b0d12a17ea3c7d83b133483a7024 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 24 Nov 2025 14:00:34 -0700 Subject: [PATCH 1558/2098] rp2/rp2_dma: Properly close DMA channels. Clears the control registers and aborts the closed channel upon a call to `.close()` and `.__del__()` (GC collect). Fixes issue #18446. Signed-off-by: Dryw Wade --- ports/rp2/rp2_dma.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 94c61e226e6..471cf24ea2c 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -427,6 +427,14 @@ static mp_obj_t rp2_dma_close(mp_obj_t self_in) { uint8_t channel = self->channel; if (channel != CHANNEL_CLOSED) { + // Reset this channel's registers to their default values (zeros). + dma_channel_config config = { .ctrl = 0 }; + dma_channel_configure(channel, &config, NULL, NULL, 0, false); + + // Abort this channel. Must be done after clearing EN bit in control + // register due to errata RP2350-E5. + dma_channel_abort(channel); + // Clean up interrupt handler to ensure garbage collection mp_irq_obj_t *irq = MP_STATE_PORT(rp2_dma_irq_obj[channel]); MP_STATE_PORT(rp2_dma_irq_obj[channel]) = MP_OBJ_NULL; From 2db2f536acd1b5701ca23d9cd065ef5479adf467 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 21 Mar 2025 17:03:27 +0000 Subject: [PATCH 1559/2098] rp2/rp2_pio: Fix support for pin wrapping and RP2350B upper-bank pins. On RP2350B where there are more than 32 pins, using `pio_sm_set_pins_with_mask()` and `pio_sm_set_pindirs_with_mask()` is not correct because their arguments are `uint32_t` and higher bits get lost when `pio.gpio_base(16)` is used. This commit fixes the issue by using the 64-bit API functions on RP2350B. It also makes sure pin wrapping is supported, i.e. using [30, 31, 0, 1] or [46, 47, 16, 17] as contiguous pin ranges for a PIO program. Fixes issue #16199. Signed-off-by: Anson Mansfield --- ports/rp2/rp2_pio.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 611e74a1587..81351431cb9 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -271,12 +271,32 @@ static void asm_pio_get_pins(PIO pio, const char *type, mp_obj_t prog_pins, mp_o } } +static inline uint32_t rotl32a(const uint32_t x, const int k) { + return (x << k) | (x >> (32 - k)); +} + 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); - for (size_t i = 0; i < config->count; ++i) { - gpio_set_function(config->base + i, GPIO_FUNC_PIO0 + pio_get_index(pio)); + uint gpio_base = pio_get_gpio_base(pio); + uint32_t pinmask = rotl32a(~(-1 << config->count), config->base - gpio_base); + uint32_t pinvals = rotl32a(config->pinvals, config->base - gpio_base); + uint32_t pindirs = rotl32a(config->pindirs, config->base - gpio_base); + + #if !PICO_PIO_USE_GPIO_BASE + // optimization: avoid 64-bit arithmetic on RP2040 and RP2350A + pio_sm_set_pins_with_mask(pio, sm, pinvals, pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, pindirs, pinmask); + #else + uint64_t pinmask64 = (uint64_t)pinmask << gpio_base; + uint64_t pinvals64 = (uint64_t)pinvals << gpio_base; + uint64_t pindirs64 = (uint64_t)pindirs << gpio_base; + pio_sm_set_pins_with_mask64(pio, sm, pinvals64, pinmask64); + pio_sm_set_pindirs_with_mask64(pio, sm, pindirs64, pinmask64); + #endif + + for (size_t i = 0; i < 32; ++i) { + if (pinmask & (1 << i)) { + gpio_set_function(gpio_base + i, GPIO_FUNC_PIO0 + pio_get_index(pio)); + } } } From e6a7dc111438d9e9d76f2d6c9384373c382d9c27 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 30 Nov 2025 16:20:56 +1100 Subject: [PATCH 1560/2098] rp2/mpconfigport: Enable MD5, SHA1 and cryptolib on all boards. This fixes a regression introduced by PR #17926 / commit b5fcb33eaa682bb666c839cd4fb301175cc3564f which accidentally disabled `hashlib.sha1` and the `cryptolib` module on rp2 boards that don't have networking enabled, eg RPI_PICO. `hashlib.md5` is enabled to keep the configuration the same as boards that do have networking enabled. Signed-off-by: Damien George --- ports/rp2/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 6ce90e4f1f4..8cc12aff1c2 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -151,6 +151,9 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_RE_MATCH_GROUPS (1) #define MICROPY_PY_RE_MATCH_SPAN_START_END (1) +#define MICROPY_PY_HASHLIB_MD5 (1) +#define MICROPY_PY_HASHLIB_SHA1 (1) +#define MICROPY_PY_CRYPTOLIB (1) #define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) #define MICROPY_PY_TIME_TIME_TIME_NS (1) #define MICROPY_PY_TIME_INCLUDEFILE "ports/rp2/modtime.c" From 797925c057bddc855b089397cb7fba024341caef Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 3 Dec 2025 10:39:29 +1100 Subject: [PATCH 1561/2098] esp32: Fix board images for ESP32_GENERIC_[C2|C5|P4]. These recently added boards had copy-paste image names, change them to match the images pending addition to micropython-media. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/boards/ESP32_GENERIC_C2/board.json | 2 +- ports/esp32/boards/ESP32_GENERIC_C5/board.json | 2 +- ports/esp32/boards/ESP32_GENERIC_P4/board.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/esp32/boards/ESP32_GENERIC_C2/board.json b/ports/esp32/boards/ESP32_GENERIC_C2/board.json index c496396ef1a..ab05c93881d 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C2/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_C2/board.json @@ -12,7 +12,7 @@ "WiFi" ], "images": [ - "esp32c2_devkitmini.jpg" + "esp8684_devkitc.jpg" ], "mcu": "esp32c2", "product": "ESP32-C2", diff --git a/ports/esp32/boards/ESP32_GENERIC_C5/board.json b/ports/esp32/boards/ESP32_GENERIC_C5/board.json index 371da3929c5..9ee69f0ba65 100644 --- a/ports/esp32/boards/ESP32_GENERIC_C5/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_C5/board.json @@ -12,7 +12,7 @@ "WiFi" ], "images": [ - "esp32c5_devkitmini.jpg" + "esp32c5_devkitc.jpg" ], "mcu": "esp32c5", "product": "ESP32-C5", diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/board.json b/ports/esp32/boards/ESP32_GENERIC_P4/board.json index 00761d511f7..f0b5478de31 100644 --- a/ports/esp32/boards/ESP32_GENERIC_P4/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_P4/board.json @@ -11,7 +11,7 @@ "WiFi" ], "images": [ - "esp32p4_devkitmini.jpg" + "esp32_p4_function_ev_board.jpg" ], "mcu": "esp32p4", "product": "ESP32-P4", From a94f8114f3bd6a01093b99a75ef62ff619a61a23 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 3 Dec 2025 10:41:47 +1100 Subject: [PATCH 1562/2098] esp32/boards/ESP32_GENERIC_P4: Add variants to board.json and .md files. Add variants to the new ESP32_GENERIC_P4 board, so they appear on the download page. Signed-off-by: Damien George --- ports/esp32/boards/ESP32_GENERIC_P4/board.json | 4 ++++ ports/esp32/boards/ESP32_GENERIC_P4/board.md | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/board.json b/ports/esp32/boards/ESP32_GENERIC_P4/board.json index f0b5478de31..480b4fbfd1d 100644 --- a/ports/esp32/boards/ESP32_GENERIC_P4/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_P4/board.json @@ -17,5 +17,9 @@ "product": "ESP32-P4", "thumbnail": "", "url": "https://www.espressif.com/en/products/modules", + "variants": { + "C5_WIFI": "Support for external C5 WiFi/BLE", + "C6_WIFI": "Support for external C6 WiFi/BLE" + }, "vendor": "Espressif" } diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/board.md b/ports/esp32/boards/ESP32_GENERIC_P4/board.md index 22a450cb898..29f72832aa6 100644 --- a/ports/esp32/boards/ESP32_GENERIC_P4/board.md +++ b/ports/esp32/boards/ESP32_GENERIC_P4/board.md @@ -1,2 +1,11 @@ The following firmware is applicable to most development boards based on ESP32-P4, and the development boards must be equipped with at least 16 MiB external SPI Flash. + +This board has multiple variants available: + +* If your board has a standalone ESP32-P4 processor (or the board has a coprocessor but + you do not want to use it) then choose the generic variant (first heading below). +* If your board has an external ESP32-C5 coprocessor for WiFi and BLE then choose the + "C5 WiFi/BLE" variant. +* If your board has an external ESP32-C6 coprocessor for WiFi and BLE then choose the + "C6 WiFi/BLE" variant. From 5ea9a2662d9f530f88d566aec76cfaef5f6d392d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 3 Dec 2025 11:23:34 +1100 Subject: [PATCH 1563/2098] esp32: Don't disable component manager when updating submodules. - Reverts the change from ec527a1 - since later change cccac2cc we no longer exit CMake early to get the submodule list, so it's OK to run component manager during this phase. - Fixes issue where "make submodules BOARD=ESP32_GENERIC_S3" (or any other board that depends on USB) would fail due to missing component(s). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index d0984127ce3..e767f96c290 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -112,4 +112,4 @@ size-files: # This is done in a dedicated build directory as some CMake cache values are not # set correctly if not all submodules are loaded yet. submodules: - $(Q)IDF_COMPONENT_MANAGER=0 idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D UPDATE_SUBMODULES=1 reconfigure + $(Q)idf.py $(IDFPY_FLAGS) -B $(BUILD)/submodules -D UPDATE_SUBMODULES=1 reconfigure From 0b1a6bebae857053a0c6cc865e3ca38a8f5f28df Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Nov 2025 11:22:18 +1100 Subject: [PATCH 1564/2098] docs/library/machine.Timer: Explain the id parameter in more detail. As noted in discussion on PR #18263, the id parameter is optional on ports that support virtual timers. Add some more general explanation of hardware vs virtual timers, and remove redundant documentation about timer callbacks in favour of the isr_rules page. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.Timer.rst | 60 +++++++++++++++++------------- docs/library/machine.TimerWiPy.rst | 13 ++----- docs/library/machine.rst | 8 ---- 3 files changed, 38 insertions(+), 43 deletions(-) diff --git a/docs/library/machine.Timer.rst b/docs/library/machine.Timer.rst index 5d228ea7b67..69eea9d8d1f 100644 --- a/docs/library/machine.Timer.rst +++ b/docs/library/machine.Timer.rst @@ -4,37 +4,47 @@ class Timer -- control hardware timers ====================================== -Hardware timers deal with timing of periods and events. Timers are perhaps -the most flexible and heterogeneous kind of hardware in MCUs and SoCs, -differently greatly from a model to a model. MicroPython's Timer class -defines a baseline operation of executing a callback with a given period -(or once after some delay), and allow specific boards to define more -non-standard behaviour (which thus won't be portable to other boards). +Timer class provides the ability to trigger a Python callback function after an +expiry time, or periodically at a regular interval. -See discussion of :ref:`important constraints ` on -Timer callbacks. - -.. note:: - - Memory can't be allocated inside irq handlers (an interrupt) and so - exceptions raised within a handler don't give much information. See - :func:`micropython.alloc_emergency_exception_buf` for how to get around - this limitation, which applies to all callbacks of Timers created with - ``hard=True``. +The available features and restrictions of Timer objects vary depending on the +MicroPython board and port. If you are using a WiPy board please refer to :ref:`machine.TimerWiPy ` instead of this class. +Timer Types +----------- + +There are two types of Timer in MicroPython, but not all ports support both: + +- Virtual timers. These are managed in software, and are generally more + flexible. Multiple virtual timers can be constructed and active at once. The + ``id`` of a virtual timer is ``-1``. Not all ports support virtual timers, but + it's recommended to use them when available. +- Hardware timers. Hardware timers have integer ``id`` values starting at ``0``. + The number of available ``id`` values is determined by the hardware. Hardware + timers may be more accurate for very fine sub-millisecond timing (especially + when ``hard=True`` is supported and set, see :ref:`isr_rules`.) Most + microcontroller ports support hardware timers, except Zephyr and RP2 which + only support virtual timers. + Constructors ------------ .. class:: Timer(id, /, ...) - Construct a new timer object of the given ``id``. ``id`` of -1 constructs a - virtual timer (if supported by a board). + Construct a new Timer object with the given ``id``. + + On ports which support virtual timers the ``id`` parameter is optional - the + default value is ``-1`` which constructs a virtual timer. + + On ports which support hardware timers, setting the ``id`` parameter to a + non-negative integer determines which timer to use. + ``id`` shall not be passed as a keyword argument. - See ``init`` for parameters of initialisation. + Any additional parameters are handled the same as :func:`Timer.init()`. Methods ------- @@ -73,22 +83,22 @@ Methods - ``callback`` - The callable to call upon expiration of the timer period. The callback must take one argument, which is passed the Timer object. + The ``callback`` argument shall be specified. Otherwise an exception will occur upon timer expiration: ``TypeError: 'NoneType' object isn't callable`` - ``hard`` can be one of: - - ``True`` - The callback will be executed in hard interrupt - context, which minimises delay and jitter but is subject to the - limitations described in :ref:`isr_rules` including being unable - to allocate on the heap. + - ``True`` - The callback will be executed in hard interrupt context, + which minimises delay and jitter but is subject to the limitations + described in :ref:`isr_rules`. Not all ports support hard interrupts, + see the port documentation for more information. - ``False`` - The callback will be scheduled as a soft interrupt, allowing it to allocate but possibly also introducing garbage-collection delays and jitter. - The default value of this option is port-specific for historical - reasons. + The default value of this parameter is port-specific for historical reasons. .. method:: Timer.deinit() diff --git a/docs/library/machine.TimerWiPy.rst b/docs/library/machine.TimerWiPy.rst index 54280a5994a..17215d50203 100644 --- a/docs/library/machine.TimerWiPy.rst +++ b/docs/library/machine.TimerWiPy.rst @@ -18,16 +18,6 @@ defines a baseline operation of executing a callback with a given period (or once after some delay), and allow specific boards to define more non-standard behaviour (which thus won't be portable to other boards). -See discussion of :ref:`important constraints ` on -Timer callbacks. - -.. note:: - - Memory can't be allocated inside irq handlers (an interrupt) and so - exceptions raised within a handler don't give much information. See - :func:`micropython.alloc_emergency_exception_buf` for how to get around this - limitation. - Constructors ------------ @@ -134,6 +124,9 @@ Methods ``TimerWiPy.ONE_SHOT``. In the case that mode is ``TimerWiPy.PWM`` then trigger must be equal to ``TimerWiPy.MATCH``. + Note that callback handlers are hard interrupts, and the constraints described in :ref:`isr_rules` + apply when they are executed. + Returns a callback object. .. method:: timerchannel.freq([value]) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 69eda917e5a..31acb74920c 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -11,14 +11,6 @@ and unrestricted access to and control of hardware blocks on a system malfunction, lockups, crashes of your board, and in extreme cases, hardware damage. -.. _machine_callbacks: - -A note of callbacks used by functions and class methods of :mod:`machine` module: -all these callbacks should be considered as executing in an interrupt context. -This is true for both physical devices with IDs >= 0 and "virtual" devices -with negative IDs like -1 (these "virtual" devices are still thin shims on -top of real hardware and real hardware interrupts). See :ref:`isr_rules`. - Memory access ------------- From 3f796b687b4ca2b1bf7db7ed0220dbf6b8a55b14 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Dec 2025 00:33:04 +1100 Subject: [PATCH 1565/2098] tools/autobuild: Make firmware destination directory configurable. The destination directory for the firmware built by `autobuild.sh` is currently hard-coded to `/tmp/autobuild-firmware-$$`. Now that there are many boards built by this script, the `/tmp` partition can run out of space. This commit makes the destination directory configurable via the `MICROPY_AUTOBUILD_DEST` environment variable. Signed-off-by: Damien George --- tools/autobuild/autobuild.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/autobuild/autobuild.sh b/tools/autobuild/autobuild.sh index 9c94d887f04..ed748fc01e1 100755 --- a/tools/autobuild/autobuild.sh +++ b/tools/autobuild/autobuild.sh @@ -7,6 +7,9 @@ # - IDF_PATH_V50 must be set # - MICROPY_AUTOBUILD_MICROPYTHON_REPO must be set to location of micropython repository # - MICROPY_AUTOBUILD_MAKE must be set to the make command to use, eg "make -j2" +# - MICROPY_AUTOBUILD_DEST must be set to a directory name to place the output firmware +# (this directory will be created, and removed at the end if firmware is copied to a +# remote machine using MICROPY_AUTOBUILD_REMOTE_MACHINE and MICROPY_AUTOBUILD_REMOTE_DIR) # # Optional settings: # - MICROPY_AUTOBUILD_REMOTE_MACHINE can be set to a remote ssh machine to copy files to @@ -27,6 +30,11 @@ if [ -z "$MICROPY_AUTOBUILD_MAKE" ]; then exit 1 fi +if [ -z "$MICROPY_AUTOBUILD_DEST" ]; then + echo "must set MICROPY_AUTOBUILD_DEST" + exit 1 +fi + ######################################## # Initialisation @@ -37,7 +45,7 @@ AUTODIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source ${AUTODIR}/build-boards.sh # make local directory to put firmware -LOCAL_FIRMWARE=/tmp/autobuild-firmware-$$ +LOCAL_FIRMWARE=${MICROPY_AUTOBUILD_DEST} mkdir -p ${LOCAL_FIRMWARE} # get latest MicroPython From 37bac07f5da59ac7352c28de813704831494e02b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Nov 2025 16:30:12 +1100 Subject: [PATCH 1566/2098] tests/serial_test.py: Factor common reset code into send_script func. Signed-off-by: Damien George --- tests/serial_test.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/serial_test.py b/tests/serial_test.py index 455d2277f64..006e6fec9ce 100755 --- a/tests/serial_test.py +++ b/tests/serial_test.py @@ -86,6 +86,8 @@ def drain_input(ser): def send_script(ser, script): + ser.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot + drain_input(ser) chunk_size = 32 for i in range(0, len(script), chunk_size): ser.write(script[i : i + chunk_size]) @@ -109,8 +111,6 @@ def read_test(ser_repl, ser_data, bufsize, nbuf): READ_TIMEOUT_S = 2 # Load and run the read_test_script. - ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot - drain_input(ser_repl) script = bytes(read_test_script % (bufsize, nbuf), "ascii") send_script(ser_repl, script) @@ -166,8 +166,6 @@ def write_test(ser_repl, ser_data, bufsize, nbuf, verified): global test_passed # Load and run the write_test_script. - ser_repl.write(b"\x03\x01\x04") # break, raw-repl, soft-reboot - drain_input(ser_repl) if verified: script = write_test_script_verified else: From 9939eae599df0c285b5f4f998b00a72b37b0d748 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Nov 2025 16:30:43 +1100 Subject: [PATCH 1567/2098] tests/serial_test.py: Add a serial echo test. The existing `serial_test.py` script tests data in/out throughput and reliability. But it only tests data sizes that are a power of two, which may not catch certain errors with USB transmission (because FS packet size is 64 bytes). This commit adds a new echo sub-test to the `serial_test.py` script. It sends out data to the target and gets the target to echo it back, and then compares the result (the echo'd data should be equal to the sent data). It does this for data packets between 1 and 520 (inclusive) bytes, which covers USB FS and HS packet sizes (64 and 512 respectively). It uses random data for the test, but seeded by a constant seed so that it's deterministic. If there's an error then it prints out all the sent and echo'd data to make it easier to see where it went wrong (eg if the previous packet was repeated). Signed-off-by: Damien George --- tests/serial_test.py | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/serial_test.py b/tests/serial_test.py index 006e6fec9ce..3b5940d91a9 100755 --- a/tests/serial_test.py +++ b/tests/serial_test.py @@ -8,12 +8,25 @@ # The `serial-device` will default to /dev/ttyACM0. import argparse +import random import serial import sys import time run_tests_module = __import__("run-tests") +echo_test_script = """ +import sys +bytes_min=%u +bytes_max=%u +repeat=%u +b=memoryview(bytearray(bytes_max)) +for n in range(bytes_min,bytes_max+1): + for _ in range(repeat): + n2 = sys.stdin.readinto(b[:n]) + sys.stdout.write(b[:n2]) +""" + read_test_script = """ bin = True try: @@ -100,6 +113,59 @@ def send_script(ser, script): raise TestError("could not send script", response) +def echo_test(ser_repl, ser_data): + global test_passed + + # Make the test data deterministic. + random.seed(0) + + # Set parameters for the test. + # Go just a bit above the size of a USB high-speed packet. + bytes_min = 1 + bytes_max = 520 + num_repeat = 1 + + # Load and run the write_test_script. + script = bytes(echo_test_script % (bytes_min, bytes_max, num_repeat), "ascii") + send_script(ser_repl, script) + + # A selection of printable bytes for echo data. + printable_bytes = list(range(48, 58)) + list(range(65, 91)) + list(range(97, 123)) + + # Write data to the device and record the echo'd data. + # Use a different selection of random printable characters for each + # echo, to make it easier to debug when the echo doesn't match. + num_errors = 0 + echo_results = [] + for num_bytes in range(bytes_min, bytes_max + 1): + print(f"DATA ECHO: {num_bytes} / {bytes_max}", end="\r") + for repeat in range(num_repeat): + rand_bytes = list(random.choice(printable_bytes) for _ in range(8)) + buf = bytes(random.choice(rand_bytes) for _ in range(num_bytes)) + ser_data.write(buf) + buf2 = ser_data.read(len(buf)) + match = buf == buf2 + num_errors += not match + echo_results.append((match, buf, buf2)) + if num_errors > 8: + # Stop early if there are too many errors. + break + ser_repl.write(b"\x03") + + # Print results. + if all(match for match, _, _ in echo_results): + print("DATA ECHO: OK for {}-{} bytes at a time".format(bytes_min, bytes_max)) + else: + test_passed = False + print("DATA ECHO: FAIL ") + for match, buf, buf2 in echo_results: + print(" sent", len(buf), buf) + if match: + print(" echo match") + else: + print(" echo", len(buf), buf2) + + def read_test(ser_repl, ser_data, bufsize, nbuf): global test_passed @@ -212,6 +278,12 @@ def do_test(dev_repl, dev_data=None, time_per_subtest=1): ser_repl = serial.Serial(dev_repl, baudrate=115200, timeout=1) ser_data = serial.Serial(dev_data, baudrate=115200, timeout=1) + # Do echo test first, and abort if it doesn't pass. + echo_test(ser_repl, ser_data) + if not test_passed: + return + + # Do read and write throughput test. for test_func, test_args, bufsize in ( (read_test, (), 256), (write_test, (True,), 128), From d357eff2bc83c7b00f7a044a255ca574976e0aa1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Dec 2025 12:18:34 +1100 Subject: [PATCH 1568/2098] tests/net_inet: Update micropython.org certificate for SSL tests. Signed-off-by: Damien George --- tests/net_inet/mpycert.der | Bin 1290 -> 1290 bytes tests/net_inet/ssl_cert.py | 52 ++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/net_inet/mpycert.der b/tests/net_inet/mpycert.der index 0b0eabc9bc8135de25785cb8574ab78e03947aa9..f583b217ae5d960d2ba534fbbbf2a5b1e492777f 100644 GIT binary patch delta 850 zcmV-Y1Figu3W^FKFoFdJFoFW^paTK{0s;{L!V)q|aH+JBaoj-n;)NzWks!1IGLh9G zf7+NRbgjOW{yX3v;^j+qt1J?K?WY4Wem0lv=rNC_6O9X*E*{MRQ9VrMC~gT=F{-5^ zU2^m-2f|H2Jl_E$xp+7A8f7vx=7w>d1n-0hTfG7rnd_zj0Ge}b)w~n8%xOf2%um4?NqlV7^*QP8jLiDsxUZei#+W?``^y3*#^6q-} zC!XcaoGqgAf1DSs?-MsU(X_}cLDq)A>Fgo+SsQ=3I((m80i9w5!YI88tvt#Q0_+=; z_5oQG0JSOdEsk$7=$lNfcs}~v=?u}*lVbv>e~)$03D0VFNM$mT-nB%?g?986gp2~e zcI&h8;`wfbs8*5Tl1umD>Hv61k9Csc&p>jJ?- z@c8vkWFUK}<1xg*XbDG)*tvM1%1%nYX(TF3H+xY7YdSwti?h_ytyoTz?#o|q4KYbb zy2L-963HMv5rhYYwD|HkBdl=%K<9sre?~NdYEIAK4r5=sX_`j~5F-9dNVk!7RLhed z*h-7GdzuHqL{v@f1|5kwp8d%mZ_}suv*I-dNz|Jo6cBLfS)xA zUM;_Ln_?yxoRKjp3-f=?!(xkQ$9MF93;V`d^#f*0rcj&E@1^_#v|vpGOJPsNe;@~o zNU$;Www%$)t#=sQvd0g;;6@1E;7i`QxVFa@zBXw;hGP#cFy2ti?#B4t-r= zx2WvF=ks!1IF_G0F zf4YafTpIv6%gPGP)MpU+0X(HnrD4k^%2N9k0k^p*^(Y?gC$l6NIcEtB2<+fGuse4S zz4PTcR6z_*u0htBbly?h?|c7Py{NYn_TDko@^Z;?Y72vOm7GQZWE7e6vDqTo&8U*9 z942}{{_j`?dkB?*+i?$$`{OYQy9vHfe@$PHk#;>=ot}g8>BMHU;Ra?yNEm=+pf?zr zn}jl#v9i6y_!R)1Cy4vvV;7cenxdX~KE%NnGG0P~pV9p#`jaKy3iD!a+Z$ zShz36T$`o437+x(>#&76{iq7>MZ|dPWh%nYUp(I9TumGI3nwe83qp5OPkCw`0*Bd? z_5oQG#m`2h>h#0FdTf=%EwEmaFDB!;lVbv>e@^0wT?zvnkT}rwANg@y_`{E~H;8&~ zuq7YuzPH$h29D_ogw1llf%E*z&r?Y4z4mEB)TI$VHOLdPk{tzK>MqpS_({X9^_iBO z?8TQTRbT9 z47O}D2u;^G?MN*y1!%5-UGGfX*dRe)Xz{yR&EpQR3^9kBL*7Y4;m8C((L5W6MKEco zsd5f^5*#tuJ|kD%r%#~p9D&hyA8*#uxu~-P7J)fSpH;5XYUH!O>ZR46OQXrwe>-yX zO7>K%4E#9vV1VClHOh6NROBzbZWqZKr8l*rn!4=tz;#AXcc|!DR*_~cZr&+@)~1&Q zUSvhSEY9j=LPW5`Pp$B@+yly)n+bxIf(>JeX89R%_|>_I5aNlL2{rkm9KMosBXsGV cAJ;odnuJ&o!t/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R12 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 # a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT @@ -17,39 +17,39 @@ # Then convert to hex format using: for i in range(0,len(data),40):print(data[i:i+40].hex()) ca_cert_chain = bytes.fromhex( - "30820506308202eea0030201020211008a7d3e13d62f30ef2386bd29076b34f8300d06092a864886" + "30820506308202eea003020102021100c212324b70a9b49171dc40f7e285263c300d06092a864886" "f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65" "742053656375726974792052657365617263682047726f7570311530130603550403130c49535247" "20526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a" "3033310b300906035504061302555331163014060355040a130d4c6574277320456e637279707431" - "0c300a0603550403130352313130820122300d06092a864886f70d01010105000382010f00308201" - "0a0282010100ba87bc5c1b0039cbca0acdd46710f9013ca54ea561cb26ca52fb1501b7b928f5281e" - "ed27b324183967090c08ece03ab03b770ebdf3e53954410c4eae41d69974de51dbef7bff58bda8b7" - "13f6de31d5f272c9726a0b8374959c4600641499f3b1d922d9cda892aa1c267a3ffeef58057b0895" - "81db710f8efbe33109bb09be504d5f8f91763d5a9d9e83f2e9c466b3e106664348188065a037189a" - "9b843297b1b2bdc4f815009d2788fbe26317966c9b27674bc4db285e69c279f0495ce02450e1c4bc" - "a105ac7b406d00b4c2413fa758b82fc55c9ba5bb099ef1feebb08539fda80aef45c478eb652ac2cf" - "5f3cdee35c4d1bf70b272baa0b4277534f796a1d87d90203010001a381f83081f5300e0603551d0f" + "0c300a0603550403130352313230820122300d06092a864886f70d01010105000382010f00308201" + "0a0282010100da982874adbe94fe3be01ee2e54b75ab2c127feda703327e3697ece8318fa5138d0b" + "992e1ecd01513d4ce5286e095531aaa5225d72f42d07c24d403cdf0123b97837f51a653234e68671" + "9d04ef84085bbd021a99eba601009a73906d8fa207a0d097d3da456181353d14f9c4c05f6adc0b96" + "1ab09fe32aeabd2ad698c79b71ab3b740f3cdbb260be5a4b4e18e9db2a735c8961659efeed3ca6cb" + "4e6fe49ef90046b3ff194d2a63b38e66c6188570c750656f3b74e548830f08585d2d239d5ea3fee8" + "db00a1d2f4e3194df2ee7af6279ee5cd9c2da2f27f9c17adef133739d1b4c82c41d686c0e9ec21f8" + "591b7fb93a7c9f5c019d6204c228bd0aad3cca10ec1b0203010001a381f83081f5300e0603551d0f" "0101ff040403020186301d0603551d250416301406082b0601050507030206082b06010505070301" - "30120603551d130101ff040830060101ff020100301d0603551d0e04160414c5cf46a4eaf4c3c07a" - "6c95c42db05e922f26e3b9301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" + "30120603551d130101ff040830060101ff020100301d0603551d0e0416041400b529f22d8e6f31e8" + "9b4cad783efadce90cd1d2301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" "f6e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f" "2f78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c010201302706" "03551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d" - "06092a864886f70d01010b050003820201004ee2895d0a031c9038d0f51ff9715cf8c38fb237887a" - "6fb0251fedbeb7d886068ee90984cd72bf81f3fccacf5348edbdf66942d4a5113e35c813b2921d05" - "5fea2ed4d8f849c3adf599969cef26d8e1b4240b48204dfcd354b4a9c621c8e1361bff77642917b9" - "f04bef5deacd79d0bf90bfbe23b290da4aa9483174a9440be1e2f62d8371a4757bd294c10519461c" - "b98ff3c47448252a0de5f5db43e2db939bb919b41f2fdf6a0e8f31d3630fbb29dcdd662c3fb01b67" - "51f8413ce44db9acb8a49c6663f5ab85231dcc53b6ab71aedcc50171da36ee0a182a32fd09317c8f" - "f673e79c9cb54a156a77825acfda8d45fe1f2a6405303e73c2c60cb9d63b634aab4603fe99c04640" - "276063df503a0747d8154a9fea471f995a08620cb66c33084dd738ed482d2e0568ae805def4cdcd8" - "20415f68f1bb5acde30eb00c31879b43de4943e1c8043fd13c1b87453069a8a9720e79121c31d83e" - "2357dda74fa0f01c81d1771f6fd6d2b9a8b3031681394b9f55aed26ae4b3bfeaa5d59f4ba3c9d63b" - "72f34af654ab0cfc38f76080df6e35ca75a154e42fbc6e17c91aa537b5a29abaecf4c075464f77a8" - "e8595691662d6ede2981d6a697055e6445be2cceea644244b0c34fadf0b4dc03ca999b098295820d" - "638a66f91972f8d5b98910e289980935f9a21cbe92732374e99d1fd73b4a9a845810c2f3a7e235ec" - "7e3b45ce3046526bc0c0" + "06092a864886f70d01010b050003820201008f75d009cf6a7648653292deb544c88576f415848c02" + "bf76ebb3f1e2f96e84a85691e1924bf7e1ea0078488f7592e3e4467b1b602b20afa0ce14e5450d6a" + "e05286a4f3da1414a9a95ff16d46f952501740e9e41e7de61558fea98bfceff59e63e066e2c3773b" + "1f01872694ed4010dcb799ecdd57d35c7141ee30200004dc954b5028879992feaa8094b6060814f8" + "1c837e7440c5085a0c4f5cd1849dc4fddb59deee796e234d95f292d498296a5ceb02c142f0f8f54e" + "64207ba8e331c4c06809478bd8b978a0ca4e4abe69242a4b377b51036b3a3f528bb3d4d2ad584e93" + "eecb5f6f0d314948bac43f9f12c9203d11840785b4f8f23823ac710040e77f8d4634826a4ecfe00e" + "635fba699a47091022fe4b48b7917554cb931ee416eb53cf7bde364dbff6b1ebe64ae9333c8d69a2" + "98bea87fa3ab5fb654e84d96a9acf3b05acb1b7a3693249bce5852809f350a5e2dbf749b6226179c" + "9131290bf37fcdc3628b68c777f47f0bfbc659f503664ba6509bd0efa5fc02b4604d034b614fc520" + "078b48b031f5b69cd1c9ad7718dcb2c70fbee04608dee04bdeb9b8b6c716be36693f86684b748113" + "8950c56a7a02acc548a50e7d5d61e4cdd166a075c7055ee889b5631923bb50b490ecc275373e75a6" + "1b83252800214ec0d33acb9ceac08ff75fae51164610af0206eec0b657d40dac8cd8d7a0f3876ec3" + "e2cbe94ed4a17cfd763b" ) From d3391b7632d76a728c723ad58d74371677201531 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 5 Dec 2025 13:20:19 +1100 Subject: [PATCH 1569/2098] py/asmbase: Cast prior to bitwise invert when the type is widened. Add a cast to fix build error of mpy-cross on Windows CI. Prior to this fix the failure was: D:\a\micropython\micropython\py\asmbase.c(105,56): warning C4319: '~': zero extending 'unsigned int' to 'size_t' of greater size [D:\a\micropython\micropython\mpy-cross\mpy-cross.vcxproj] Signed-off-by: Damien George --- py/asmbase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/asmbase.c b/py/asmbase.c index f1b823fa368..07dbf4430f9 100644 --- a/py/asmbase.c +++ b/py/asmbase.c @@ -102,7 +102,7 @@ void mp_asm_base_label_assign(mp_asm_base_t *as, size_t label) { // align must be a multiple of 2 void mp_asm_base_align(mp_asm_base_t *as, unsigned int align) { - as->code_offset = (as->code_offset + align - 1) & (~(align - 1)); + as->code_offset = (as->code_offset + align - 1) & (~(size_t)(align - 1)); } // this function assumes a little endian machine From 45385994752c1ee30d115bd77165449e918b3daf Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 5 Dec 2025 16:03:51 +1100 Subject: [PATCH 1570/2098] py/emitinlinerv32: Change mask arg of is_in_signed_mask to uint32_t. Prior to this change mpy-cross would fail to build under Windows with: D:\a\micropython\micropython\py\emitinlinerv32.c(398,40): warning C4319: '~': zero extending 'unsigned int' to 'mp_uint_t' of greater size [D:\a\micropython\micropython\mpy-cross\mpy-cross.vcxproj] Signed-off-by: Damien George --- py/emitinlinerv32.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index a5f4c49c294..e81b152087d 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -390,9 +390,9 @@ static const opcode_t OPCODES[] = { // These two checks assume the bitmasks are contiguous. -static bool is_in_signed_mask(mp_uint_t mask, mp_uint_t value) { - mp_uint_t leading_zeroes = mp_clz(mask); - if (leading_zeroes == 0 || leading_zeroes > 32) { +static bool is_in_signed_mask(uint32_t mask, mp_uint_t value) { + uint32_t leading_zeroes = mp_clz(mask); + if (leading_zeroes == 0) { return true; } mp_uint_t positive_mask = ~(mask & ~(1U << (31 - leading_zeroes))); From 84061266ece6ea9fae7da45e47cc6e1c51182c8a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 5 Dec 2025 15:26:25 -0500 Subject: [PATCH 1571/2098] py/builtinhelp: Don't print removed sentinel entries. This fixes the test used by the help function to iterate over its argument's attribute to use the proper `mp_map_slot_is_filled` function to check if a slot in the map is filled; the previous test only checked for `MP_OBJ_NULL` keys and would attempt to print the null value whenever a `MP_OBJ_SENTINEL` key marking a deleted entry was present. Fixes: #18061 Fixes: #18481 Signed-off-by: Anson Mansfield --- py/builtinhelp.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/py/builtinhelp.c b/py/builtinhelp.c index dc4fe582f75..5a84b3d7d24 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -152,9 +152,8 @@ static void mp_help_print_obj(const mp_obj_t obj) { } if (map != NULL) { for (uint i = 0; i < map->alloc; i++) { - mp_obj_t key = map->table[i].key; - if (key != MP_OBJ_NULL) { - mp_help_print_info_about_object(key, map->table[i].value); + if (mp_map_slot_is_filled(map, i)) { + mp_help_print_info_about_object(map->table[i].key, map->table[i].value); } } } From c25667f6a9de7b85cede4eb651cb5195c62eef80 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 5 Dec 2025 15:25:23 -0500 Subject: [PATCH 1572/2098] tests/basics/builtin_help.py: Test correct handling of deleted entries. This test reproduces the bug that gave rise to the esp32 segfaults in issues #18061 and #18481 on all platforms. Signed-off-by: Anson Mansfield --- tests/basics/builtin_help.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/basics/builtin_help.py b/tests/basics/builtin_help.py index 6ec39653fe9..3d4e4f26bd2 100644 --- a/tests/basics/builtin_help.py +++ b/tests/basics/builtin_help.py @@ -14,4 +14,10 @@ help(micropython) # help for a module help('modules') # list available modules +class A: + x = 1 + y = 2 + del x +help(A) + print('done') # so last bit of output is predictable From 4d7c2fd18638565768eef011ef5dc6752cf34a61 Mon Sep 17 00:00:00 2001 From: Yuuki NAGAO Date: Sun, 7 Dec 2025 13:07:20 +0900 Subject: [PATCH 1573/2098] stm32/system_stm32: Fix clock config for STM32G4. Add peripheral clock selection for ADC345 to use analog port under ADC3. Fixes #18527. Signed-off-by: Yuuki NAGAO --- ports/stm32/system_stm32.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/system_stm32.c b/ports/stm32/system_stm32.c index 514bf91786c..53867719915 100644 --- a/ports/stm32/system_stm32.c +++ b/ports/stm32/system_stm32.c @@ -535,13 +535,14 @@ MP_WEAK void SystemClock_Config(void) { MICROPY_BOARD_FATAL_ERROR("HAL_RCC_ClockConfig"); } PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC | RCC_PERIPHCLK_LPUART1 - | RCC_PERIPHCLK_RNG | RCC_PERIPHCLK_ADC12 + | RCC_PERIPHCLK_RNG | RCC_PERIPHCLK_ADC12 | RCC_PERIPHCLK_ADC345 | RCC_PERIPHCLK_FDCAN | RCC_PERIPHCLK_USB; PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; PeriphClkInitStruct.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_PCLK1; PeriphClkInitStruct.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; PeriphClkInitStruct.RngClockSelection = RCC_RNGCLKSOURCE_HSI48; PeriphClkInitStruct.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK; + PeriphClkInitStruct.Adc345ClockSelection = RCC_ADC345CLKSOURCE_SYSCLK; PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { MICROPY_BOARD_FATAL_ERROR("HAL_RCCEx_PeriphCLKConfig"); From 504adc689bf1e56783b394b91ecc9d7a38ccf72e Mon Sep 17 00:00:00 2001 From: Steve Sanbeg Date: Sat, 6 Dec 2025 17:28:26 -0500 Subject: [PATCH 1574/2098] stm32/boards/NUCLEO_G474RE: Restore disabled modules on g474re. Among other things, the framebuf module is missing on NUCEO_G474RE. This board seems to disable a lot of modules, while other *E boards with the same flash configuration (eg NUCLEO_F411RE) don't seem to disable any modules in this way. So, remove all of the lines disabling modules to make it consistent with other boards. Signed-off-by: Steve Sanbeg --- ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h index e807a54780d..66e53394507 100644 --- a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h @@ -9,16 +9,6 @@ #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (0) // QSPI extflash not mounted -#define MICROPY_PY_ASYNCIO (0) -#define MICROPY_PY_DEFLATE (0) -#define MICROPY_PY_BINASCII (0) -#define MICROPY_PY_HASHLIB (0) -#define MICROPY_PY_JSON (0) -#define MICROPY_PY_RE (0) -#define MICROPY_PY_FRAMEBUF (0) -#define MICROPY_PY_SOCKET (0) -#define MICROPY_PY_NETWORK (0) - // The board has an 24MHz HSE, the following gives 170MHz CPU speed #define MICROPY_HW_CLK_PLLM (6) #define MICROPY_HW_CLK_PLLN (85) From 365cdb6f56dd15693f43c7a0a45eca60dfcfe4fa Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Dec 2025 12:41:51 +1100 Subject: [PATCH 1575/2098] esp32/boards: Enable ESP32P4_REV_MIN_0 option for P4 boards. The ESP32-P4 currently comes in three revisions (0.0, 0.1 and 1.0) and all of them are out in the wild. Even though the IDF defaults to a minimum of 0.1 we would like to support as many as possible, so configure MicroPython to work down to revision 0.0. The firmware only grows by 32 bytes when enabling this option. Signed-off-by: Damien George --- ports/esp32/boards/sdkconfig.p4 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/esp32/boards/sdkconfig.p4 b/ports/esp32/boards/sdkconfig.p4 index 3ec0ff70579..042eec40cf7 100644 --- a/ports/esp32/boards/sdkconfig.p4 +++ b/ports/esp32/boards/sdkconfig.p4 @@ -1,3 +1,6 @@ +# Select the minimum chip revision to cover all possible boards. +CONFIG_ESP32P4_REV_MIN_0=y + # Flash CONFIG_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y From 7702f7f59daf8dd11b4134c05edd34002204cd99 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 7 Dec 2025 23:56:42 +1100 Subject: [PATCH 1576/2098] esp32/main: Update esp_hosted component to latest version 2.7.0. This updates the esp_hosted component for ESP32-P4 boards to use the latest version 2.7.0. Testing on a P4 board with C6 WiFi shows there are no regressions for WiFi or BLE. Also rename the `CONFIG_ESP_ENABLE_BT` option to the new `CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE` option used by the component. This change is made partly to work around a current issue with the IDF component manager, that esp_hosted versions after 2.0.17 and prior to 2.7.0 have all disappeared. Signed-off-by: Damien George --- ports/esp32/boards/sdkconfig.p4_wifi_common | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c2 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c3 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c5 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c6 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32p4 | 6 +++--- ports/esp32/lockfiles/dependencies.lock.esp32s2 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32s3 | 2 +- ports/esp32/main/idf_component.yml | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ports/esp32/boards/sdkconfig.p4_wifi_common b/ports/esp32/boards/sdkconfig.p4_wifi_common index b7bd0bffa05..a55e941ba68 100644 --- a/ports/esp32/boards/sdkconfig.p4_wifi_common +++ b/ports/esp32/boards/sdkconfig.p4_wifi_common @@ -41,7 +41,7 @@ CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y # BLE -CONFIG_ESP_ENABLE_BT=y +CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_CONTROLLER_DISABLED=y diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 index 3f3a4166716..8ba25c77011 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -30,6 +30,6 @@ direct_dependencies: - espressif/lan867x - espressif/mdns - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 index 9530e74e972..8a366af3423 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32c2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 index 5edbe537d8b..3aa99692d9f 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32c3 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c5 b/ports/esp32/lockfiles/dependencies.lock.esp32c5 index 3b52c82b143..2fb130b8e52 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c5 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c5 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32c5 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 index 1c84d5bfd5f..c81806909a6 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c6 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32c6 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32p4 b/ports/esp32/lockfiles/dependencies.lock.esp32p4 index 8923760482f..aea6ec2cc36 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32p4 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32p4 @@ -14,7 +14,7 @@ dependencies: type: service version: 1.1.3 espressif/esp_hosted: - component_hash: f32400eec7f35652052ae79ecb301148d4011769e94eb8d47262fb22fce933d2 + component_hash: 53544436deb5fcbfdbf4a8e2c643cdbe31126556781784278c016d674df99cd2 dependencies: - name: idf require: private @@ -22,7 +22,7 @@ dependencies: source: registry_url: https://components.espressif.com/ type: service - version: 2.2.4 + version: 2.7.0 espressif/esp_serial_slave_link: component_hash: ac1776806de0a6e371c84e87898bb983e19ce62aa7f1e2e5c4a3b0234a575d2c dependencies: @@ -88,6 +88,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32p4 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 index a7bb041539d..8717181c10a 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32s2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 index 9a8b179f72b..0b8b8e92bd5 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: cc42b6ea8bc1d77d04370604f0b1c1a93a4f2c9b9200690722458faedaee68a4 +manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 target: esp32s3 version: 2.0.0 diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index cb75296fe51..176e29c3c48 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -10,7 +10,7 @@ dependencies: espressif/esp_hosted: rules: - if: "target == esp32p4" - version: "2.2.4" + version: "2.7.0" espressif/esp_wifi_remote: rules: - if: "target == esp32p4" From 2bd337ef18efdcb04c28adc2b03b26432ae24715 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Dec 2025 16:23:43 +1100 Subject: [PATCH 1577/2098] lib/micropython-lib: Update submodule to latest. This brings in: - usb-device: raise RuntimeError when DCD error occurs - usb-device-hid: return True after submit_xfer - inspect: implement a very basic signature function - datetime: apply localtz patch to include naive date/time support - datetime: optimize for code size - sdcard: updating sector calculation for SDXC - mip: optimize _CHUNK_SIZE const for code size - aioble/examples: change variable name to _ADV_INTERVAL_US - aioble: fix typo in README in aioble.ADDR_PUBLIC - copy: fix typo in _deepcopy_dispatch - requests: update example for fetching using requests - all: fix formatting errors in docstrings - CONTRIBUTING: add guidelines for module documentation and versioning - cbor2: silence missing `__eq__` warning - pyproject.toml: reorganize ruff lint settings for newer ruff - all: correct various typos in comments and docs - lora: fix import error detection for missing drivers - inspect: support closures/generators/async-funcs in inspect.signature - usb-device-hid: fix descriptor protocol config and set correct default - usb-device-hid: use report protocol after report descriptor requested - umqtt.simple: add unsubscribe method - aiohttp: correctly handle WebSocket message fragmentation - iperf3: fix use as a CLI on the unix port of MicroPython - iperf3: factor out data transfer logic to separate function - iperf3: fix server UDP mode - unix-ffi/socket: remove ip add/drop membership from socket - tarfile: add basic unittest for tarfile.TarFile - tarfile: fix FileSection.skip to not rely on extended readinto args - argparse: Add support for custom argument types - pyproject.toml: add codespell configuration, CI and precommit - all: fix spelling and typos in comments and docstring - aioble/examples: change variable name to _ADV_INTERVAL_US Signed-off-by: Damien George --- lib/micropython-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/micropython-lib b/lib/micropython-lib index 34c4ee1647a..6ae440a8a14 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit 34c4ee1647ac4b177ae40adf0ec514660e433dc0 +Subproject commit 6ae440a8a144233e6e703f6759b7e7a0afaa37a4 From 78ff170de9e32c79db6e64d3e33d2bd60002bdcd Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Dec 2025 01:44:16 +1100 Subject: [PATCH 1578/2098] all: Bump version to 1.27.0. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 97c40389b32..798d4089aa1 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -41,7 +41,7 @@ #define MICROPY_VERSION_MAJOR 1 #define MICROPY_VERSION_MINOR 27 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 1 +#define MICROPY_VERSION_PRERELEASE 0 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 099cc9f50722f6ef542a9065b7c92d0f964fe2b1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 15 Dec 2025 10:15:42 +1100 Subject: [PATCH 1579/2098] all: Bump version to 1.28.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 798d4089aa1..666b15573d9 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -39,9 +39,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 27 +#define MICROPY_VERSION_MINOR 28 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 9274f80130b483df6b1d3af5f40bce34a0c602ef Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Dec 2025 12:19:20 +1100 Subject: [PATCH 1580/2098] esp32/boards/ESP32_GENERIC_S3: Reinstate old FLASH_4M variant. Commit 6201e77999b3614518abc4b21773e735d9b0b0ee removed the ESP32_GENERIC_S3 FLASH_4M variant from `board.json`. But the firmware still exists on the download server, and it makes sense to still keep those old versions available for download, just like all other older versions are still available. This commit introduce a new scheme for `board.json` whereby old variants that are no longer built can be moved to the "old_variants" section. This keeps them available on the download page, allowing a way to deprecate individual board variants without removing them entirely. An optional string can be added to the old variant to describe why it's obsolete and what to use instead. Signed-off-by: Damien George --- ports/esp32/boards/ESP32_GENERIC_S3/board.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/esp32/boards/ESP32_GENERIC_S3/board.json b/ports/esp32/boards/ESP32_GENERIC_S3/board.json index 7a546d35fcd..b383dfa068a 100644 --- a/ports/esp32/boards/ESP32_GENERIC_S3/board.json +++ b/ports/esp32/boards/ESP32_GENERIC_S3/board.json @@ -22,5 +22,8 @@ "vendor": "Espressif", "variants": { "SPIRAM_OCT": "Support for Octal-SPIRAM" + }, + "old_variants": { + "FLASH_4M": ["4MiB flash", "Use the standard variant instead."] } } From 53052857e2e5845cbf78f778d40ccd59f4f2e3ef Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Dec 2025 11:47:25 +1100 Subject: [PATCH 1581/2098] esp8266/boards/ESP8266_GENERIC: Remove OTA board variant. The OTA variant of the ESP8266_GENERIC board was never fully completed in its functionality. It relies on the https://github.com/pfalcon/yaota8266 component which was also never fully finished and has been unmaintained for many years. This commit removes this variant and it's associated build support. It makes it an "old_variant" so the existing historical firmware is still listed on the download page. Signed-off-by: Damien George --- ports/esp8266/Makefile | 10 ---------- ports/esp8266/boards/ESP8266_GENERIC/board.json | 4 +++- ports/esp8266/boards/ESP8266_GENERIC/board.md | 10 ---------- .../boards/ESP8266_GENERIC/mpconfigvariant_OTA.mk | 10 ---------- ports/esp8266/modesp.c | 9 --------- 5 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_OTA.mk diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 1c9de01503a..abdc9076a01 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -208,20 +208,10 @@ erase: reset: echo -e "\r\nimport machine; machine.reset()\r\n" >$(PORT) -ifeq ($(BOARD_VARIANT),OTA) -$(FWBIN): $(BUILD)/firmware.elf - $(ECHO) "Create $@" - $(Q)$(ESPTOOL) elf2image $^ - $(Q)$(PYTHON) makeimg.py $(BUILD)/firmware.elf-0x00000.bin $(BUILD)/firmware.elf-0x[0-5][1-f]000.bin $(BUILD)/firmware-ota.bin - - $(Q)cat $(YAOTA8266)/yaota8266.bin $(BUILD)/firmware-ota.bin > $@ - $(Q)$(PYTHON) $(YAOTA8266)/ota-client/ota_client.py sign $@ -else $(FWBIN): $(BUILD)/firmware.elf $(ECHO) "Create $@" $(Q)$(ESPTOOL) elf2image $^ $(Q)$(PYTHON) makeimg.py $(BUILD)/firmware.elf-0x00000.bin $(BUILD)/firmware.elf-0x[0-5][1-f]000.bin $@ -endif $(BUILD)/firmware.elf: $(OBJ) $(ECHO) "LINK $@" diff --git a/ports/esp8266/boards/ESP8266_GENERIC/board.json b/ports/esp8266/boards/ESP8266_GENERIC/board.json index 8f2aa1240f2..84c4ff49510 100644 --- a/ports/esp8266/boards/ESP8266_GENERIC/board.json +++ b/ports/esp8266/boards/ESP8266_GENERIC/board.json @@ -15,10 +15,12 @@ "thumbnail": "", "url": "https://www.espressif.com/en/products/modules", "variants": { - "OTA": "OTA compatible", "FLASH_1M": "1MiB flash", "FLASH_512K": "512kiB flash", "FLASH_2M_ROMFS": "2MiB flash with ROMFS" }, + "old_variants": { + "OTA": ["OTA compatible", "OTA firmware is no longer supported."] + }, "vendor": "Espressif" } diff --git a/ports/esp8266/boards/ESP8266_GENERIC/board.md b/ports/esp8266/boards/ESP8266_GENERIC/board.md index e8d63fcead0..b83d2910cd6 100644 --- a/ports/esp8266/boards/ESP8266_GENERIC/board.md +++ b/ports/esp8266/boards/ESP8266_GENERIC/board.md @@ -14,13 +14,3 @@ Note: v1.12-334 and newer (including v1.13) require an ESP8266 module with upgrading from older firmware please backup your files first, and either erase all flash before upgrading, or after upgrading execute `vfs.VfsLfs2.mkfs(bdev)`. - -### OTA builds -Over-The-Air (OTA) builds of the ESP8266 firmware are also provided. - -The first time you use this build you need to flash one of the "initial image" -images using esptool.py as described above. After that, you can update the -firmware over the air using the "OTA update" file in conjunction with the -ota-client script from yaota8266. The "OTA update" files are digitally signed -and will only work with the provided "initial image" files, and vice versa. -(Note: this feature is work-in-progress.) diff --git a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_OTA.mk b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_OTA.mk deleted file mode 100644 index 759eab3dc75..00000000000 --- a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigvariant_OTA.mk +++ /dev/null @@ -1,10 +0,0 @@ -LD_FILES = boards/esp8266_ota.ld - -MICROPY_PY_ESPNOW ?= 1 -MICROPY_PY_BTREE ?= 1 -MICROPY_VFS_LFS2 ?= 1 - -# Note: Implicitly uses the port manifest. - -# Configure mpconfigboard.h. -CFLAGS += -DMICROPY_ESP8266_1M diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c index e1f9d396875..724545202d8 100644 --- a/ports/esp8266/modesp.c +++ b/ports/esp8266/modesp.c @@ -159,10 +159,6 @@ static mp_obj_t esp_flash_size(void) { } static MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); -// If there's just 1 loadable segment at the start of flash, -// we assume there's a yaota8266 bootloader. -#define IS_OTA_FIRMWARE() ((*(uint32_t *)0x40200000 & 0xff00) == 0x100) - extern byte _firmware_size[]; #if MICROPY_VFS_ROM_IOCTL extern uint8_t _micropy_hw_romfs_part0_size; @@ -180,11 +176,6 @@ static MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_user_start_obj, esp_flash_user_start) static mp_obj_t esp_check_fw(void) { MD5_CTX ctx; char *fw_start = (char *)0x40200000; - if (IS_OTA_FIRMWARE()) { - // Skip yaota8266 bootloader - fw_start += 0x3c000; - } - uint32_t size = *(uint32_t *)(fw_start + 0x8ffc); printf("size: %d\n", size); if (size > 1024 * 1024) { From b78fac161e34eb953c499eb17630e67355a9508a Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 10 Dec 2025 12:39:01 +1100 Subject: [PATCH 1582/2098] tests/ports/esp32/check_err_str.py: Preallocate global variable. This test fails on all esp32 boards without this fix, because the try/except that runs with the heap locked attempts to increase the size of the globals dict when assigning to the exception variable `e`. Fix that by preallocating the global variable `e`. Signed-off-by: Damien George --- tests/ports/esp32/check_err_str.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ports/esp32/check_err_str.py b/tests/ports/esp32/check_err_str.py index 055eecf7476..9efe1afa196 100644 --- a/tests/ports/esp32/check_err_str.py +++ b/tests/ports/esp32/check_err_str.py @@ -1,3 +1,5 @@ +# This tests checks the behaviour of the `check_esp_err`/`check_esp_err_` C function. + try: from esp32 import Partition as p import micropython @@ -23,6 +25,7 @@ # same but with out of memory condition by locking the heap exc = "FAILED TO RAISE" +e = None # preallocate entry in globals dict micropython.heap_lock() try: fun(part) @@ -34,6 +37,7 @@ # same again but having an emergency buffer micropython.alloc_emergency_exception_buf(256) exc = "FAILED TO RAISE" +e = None # preallocate entry in globals dict micropython.heap_lock() try: fun(part) From ef567dc9281e6ffff991bf4cd18df48c9043b207 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 30 Nov 2025 16:24:01 -0600 Subject: [PATCH 1583/2098] tests/basics/string_fstring.py: Test fstring nested replacement fields. This does not actually cover any additional lines, but it does cover new functionality not previously covered. Signed-off-by: Jeff Epler --- tests/basics/string_fstring.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/basics/string_fstring.py b/tests/basics/string_fstring.py index d94cc0cd3e6..200065e9d9d 100644 --- a/tests/basics/string_fstring.py +++ b/tests/basics/string_fstring.py @@ -79,3 +79,14 @@ def foo(a, b): # Raw f-strings. print(rf"\r\a\w {'f'} \s\t\r\i\n\g") print(fr"\r{x}") + +# Format specifiers with nested replacement fields +space = 5 +prec = 2 +print(f"{3.14:{space}.{prec}}") + +space_prec = "5.2" +print(f"{3.14:{space_prec}}") + +radix = "x" +print(f"{314:{radix}}") From 750a366f205f5c688532a4a7f4376240f3cdb78a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 10 Dec 2025 05:10:39 +0100 Subject: [PATCH 1584/2098] tests/run-tests.py: Skip list sort stress test for ESP8266. This commit marks the "stress/list_sort.py" test to be skipped when running on ESP8266. The test just takes too long without yielding to the OS whilst doing the sort, causing the internal software watchdog to kick in and reboot the board. Signed-off-by: Alessandro Gatti --- tests/run-tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index fba011fb54c..aed67cbac8e 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -180,6 +180,9 @@ def open(self, path, mode): # Tests to skip on specific targets. # These are tests that are difficult to detect that they should not be run on the given target. platform_tests_to_skip = { + "esp8266": ( + "stress/list_sort.py", # watchdog kicks in because it takes too long + ), "minimal": ( "basics/class_inplace_op.py", # all special methods not supported "basics/subclass_native_init.py", # native subclassing corner cases not support From dbf59db5a10768b374d11f3a5d7cc40dc7afaa79 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 10 Dec 2025 05:59:16 +0100 Subject: [PATCH 1585/2098] tests/extmod/vfs_blockdev_invalid.py: Handle low memory conditions. This commit modifies the "extmod/vfs_blockdev_invalid" test to better behave on boards with low available memory. Before these changes the test would fail on ESP8266 (at least), due to low memory, but in a way that could not be easily solved as the error occurred in the middle of the test. The test has been rewritten to delay its output until the very end, so if a low memory condition occurs and needs to stop execution then no real output will show up before the skip marker. Signed-off-by: Alessandro Gatti --- tests/extmod/vfs_blockdev_invalid.py | 54 ++++++++++++++++++------ tests/extmod/vfs_blockdev_invalid.py.exp | 29 +------------ 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/tests/extmod/vfs_blockdev_invalid.py b/tests/extmod/vfs_blockdev_invalid.py index 29d6bd6b2f9..955f8495b3f 100644 --- a/tests/extmod/vfs_blockdev_invalid.py +++ b/tests/extmod/vfs_blockdev_invalid.py @@ -46,12 +46,16 @@ def ioctl(self, op, arg): try: bdev = RAMBlockDevice(50) except MemoryError: - print("SKIP") + print("SKIP-TOO-LARGE") raise SystemExit -def test(vfs_class): - print(vfs_class) +ERROR_EIO = (OSError, "[Errno 5] EIO") +ERROR_EINVAL = (OSError, "[Errno 22] EINVAL") +ERROR_TYPE = (TypeError, "can't convert str to int") + + +def test(vfs_class, test_data): bdev.read_res = 0 # reset function results bdev.write_res = 0 @@ -61,17 +65,15 @@ def test(vfs_class): with fs.open("test", "w") as f: f.write("a" * 64) - for res in (0, -5, 5, 33, "invalid"): - # -5 is a legitimate negative failure (EIO), positive integer - # is not - + for res, error_open, error_read in test_data: # This variant will fail on open bdev.read_res = res try: with fs.open("test", "r") as f: - print("opened") + assert error_open is None except Exception as e: - print(type(e), e) + assert error_open is not None + assert (type(e), str(e)) == error_open # This variant should succeed on open, may fail on read # unless the filesystem cached the contents already @@ -79,11 +81,35 @@ def test(vfs_class): try: with fs.open("test", "r") as f: bdev.read_res = res - print("read 1", f.read(1)) - print("read rest", f.read()) + assert f.read(1) == "a" + assert f.read() == "a" * 63 + assert error_read is None except Exception as e: - print(type(e), e) + assert error_read is not None + assert (type(e), str(e)) == error_read -test(vfs.VfsLfs2) -test(vfs.VfsFat) +try: + test( + vfs.VfsLfs2, + ( + (0, None, None), + (-5, ERROR_EIO, None), + (5, ERROR_EINVAL, None), + (33, ERROR_EINVAL, None), + ("invalid", ERROR_TYPE, None), + ), + ) + test( + vfs.VfsFat, + ( + (0, None, None), + (-5, ERROR_EIO, ERROR_EIO), + (5, ERROR_EIO, ERROR_EIO), + (33, ERROR_EIO, ERROR_EIO), + ("invalid", ERROR_TYPE, ERROR_TYPE), + ), + ) + print("OK") +except MemoryError: + print("SKIP-TOO-LARGE") diff --git a/tests/extmod/vfs_blockdev_invalid.py.exp b/tests/extmod/vfs_blockdev_invalid.py.exp index 0ea0353501d..d86bac9de59 100644 --- a/tests/extmod/vfs_blockdev_invalid.py.exp +++ b/tests/extmod/vfs_blockdev_invalid.py.exp @@ -1,28 +1 @@ - -opened -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - [Errno 5] EIO -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - [Errno 22] EINVAL -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - [Errno 22] EINVAL -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - can't convert str to int -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - -opened -read 1 a -read rest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - [Errno 5] EIO - [Errno 5] EIO - [Errno 5] EIO - [Errno 5] EIO - [Errno 5] EIO - [Errno 5] EIO - can't convert str to int - can't convert str to int +OK From 9c9b99686edfc2809d09eebfb012eaeed5e3bb96 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 17 Dec 2025 03:57:52 +0100 Subject: [PATCH 1586/2098] tests/target_wiring: Provide an ESP8266 target wiring module. This commit introduces ESP8266 support for target wiring tests, fixing execution of relevant tests that once failed on that platform. ESP8266 boards need to have GPIO4 and GPIO5 connected together to provide a UART loopback, in order to test whether UART data effectively flows through. The wiring-enabled UART transmission timing test was also updated with measurements compatible with a few ESP8266 test boards. Signed-off-by: Alessandro Gatti --- tests/extmod/machine_uart_tx.py | 2 ++ tests/target_wiring/esp8266.py | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 tests/target_wiring/esp8266.py diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index 70d57be4647..1ff9af64bd4 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -19,6 +19,8 @@ bit_margin = 1 elif "esp32" in sys.platform: timing_margin_us = 400 +elif "esp8266" in sys.platform: + timing_margin_us = 4100 elif "mimxrt" in sys.platform: initial_delay_ms = 20 # UART sends idle frame after init, so wait for that bit_margin = 1 diff --git a/tests/target_wiring/esp8266.py b/tests/target_wiring/esp8266.py new file mode 100644 index 00000000000..336deb3dda0 --- /dev/null +++ b/tests/target_wiring/esp8266.py @@ -0,0 +1,7 @@ +# Target wiring for general esp8266 board. +# +# Connect: +# - GPIO4 to GPIO5 + +uart_loopback_args = (1,) +uart_loopback_kwargs = {} From 8b8cd825605a6cf93e7de3db1c871ba56bd2abfc Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 21:17:12 -0600 Subject: [PATCH 1587/2098] py/misc: Remove unused mp_check function. This is unused since 007f127a61ea058ca010b85883072bdefe0234c0 "all: Simplify mp_int_t/mp_uint_t definition". Signed-off-by: Jeff Epler --- py/misc.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py/misc.h b/py/misc.h index 5188b8030db..f3688c73e45 100644 --- a/py/misc.h +++ b/py/misc.h @@ -411,11 +411,6 @@ static inline uint32_t mp_ctz(uint32_t x) { return _BitScanForward(&tz, x) ? tz : 0; } -// Workaround for 'warning C4127: conditional expression is constant'. -static inline bool mp_check(bool value) { - return value; -} - static inline uint32_t mp_popcount(uint32_t x) { return __popcnt(x); } @@ -424,7 +419,6 @@ static inline uint32_t mp_popcount(uint32_t x) { #define mp_clzl(x) __builtin_clzl(x) #define mp_clzll(x) __builtin_clzll(x) #define mp_ctz(x) __builtin_ctz(x) -#define mp_check(x) (x) #if __has_builtin(__builtin_popcount) #define mp_popcount(x) __builtin_popcount(x) #else From 50a5fe0020cc8d16fd9b4c108bd992209cb041f6 Mon Sep 17 00:00:00 2001 From: Thomas Kiss Date: Tue, 9 Dec 2025 21:12:56 +0100 Subject: [PATCH 1588/2098] docs/library/network.WLAN: Fix typo for ESP32 protocol constants. Signed-off-by: Thomas Kiss --- docs/library/network.WLAN.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 27d6b383a36..e4653bc35ca 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -145,7 +145,7 @@ Methods reconnects Number of reconnect attempts to make (integer, 0=none, -1=unlimited) txpower Maximum transmit power in dBm (integer or float) pm WiFi Power Management setting (see below for allowed values) - protocol (ESP32 Only.) WiFi Low level 802.11 protocol. See `WLAN.PROTOCOL_DEFAULTS`. + protocol (ESP32 Only.) WiFi Low level 802.11 protocol. See `WLAN.PROTOCOL_DEFAULT`. ============= =========== Constants @@ -170,7 +170,7 @@ ESP32 Protocol Constants The following ESP32-only constants relate to the ``WLAN.config(protocol=...)`` network interface parameter: -.. data:: WLAN.PROTOCOL_DEFAULTS +.. data:: WLAN.PROTOCOL_DEFAULT A bitmap representing all of the default 802.11 Wi-Fi modes supported by the chip. Consult `ESP-IDF Wi-Fi Protocols`_ documentation for details. @@ -184,7 +184,7 @@ network interface parameter: `. This mode can be bitwise ORed with some standard 802.11 protocol bits - (including `WLAN.PROTOCOL_DEFAULTS`) in order to support a mix of standard + (including `WLAN.PROTOCOL_DEFAULT`) in order to support a mix of standard Wi-Fi modes as well as LR mode, consult the `Espressif long-range documentation`_ for more details. From e67d4a2a777f09215cdda928ec259696b86fcb81 Mon Sep 17 00:00:00 2001 From: jetpax Date: Mon, 15 Dec 2025 19:41:05 -0800 Subject: [PATCH 1589/2098] esp32/machine_sdcard: Fix SDMMC slot assignment for non-default slots. The SDMMC_HOST_DEFAULT() macro sets slot to SDMMC_HOST_SLOT_1, but this was not being overridden when the user specified a different slot number. This caused SDMMC initialization to fail on chips like ESP32-P4 when trying to use slot 0. This commit ensures the slot number passed to the SDCard constructor is properly assigned to the host configuration structure. Tested on ESP32-P4 with SD card on slot 0. Signed-off-by: jetpax --- ports/esp32/machine_sdcard.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 0f8bd844692..4785a7254a8 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -166,6 +166,8 @@ static esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { esp_err_t err = sdmmc_card_init(&(self->host), &(self->card)); if (err == ESP_OK) { + // Ensure card's host structure has the correct slot after init + self->card.host.slot = self->host.slot; self->flags |= SDCARD_CARD_FLAGS_CARD_INIT_DONE; } else { self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; @@ -307,6 +309,7 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args else { sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); _temp_host.max_freq_khz = freq / 1000; + _temp_host.slot = slot_num; self->host = _temp_host; } #endif From f0895f0ea0d3daea42161bc29c3ff8abeb101e57 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 4 Nov 2025 09:46:16 +0100 Subject: [PATCH 1590/2098] py/emitnative: Optimise register clearing. This commit introduces a new generic ASM API function to clear a register (i.e. clearing all the registers' bits). The native emitter used to perform a XOR operation to clear a given register, but different platform have more optimised method to achieve the same result taking up less space - either for the generated code or for the code generator itself. Arm, RV32, X86, and X64 already had an already optimised generator and generated optimised code. The code generator when build for Thumb takes less space generating a constant immediate move rather than a XOR operation, even though both operations would distill down to a single narrow opcode. On Xtensa the situation is almost the same as Thumb, with the exception that a constant immediate move would take one byte less than a XOR operation. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 2 ++ py/asmrv32.h | 2 +- py/asmthumb.h | 2 ++ py/asmx64.h | 2 ++ py/asmx86.h | 2 ++ py/asmxtensa.h | 2 ++ py/emitnative.c | 22 ++++++++-------------- py/emitndebug.c | 3 +++ 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 405457d4408..5ae952ee8a7 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -230,6 +230,8 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_strh_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) #define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_str_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) +#define ASM_CLR_REG(as, reg_dest) asm_arm_eor_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_dest)) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMARM_H diff --git a/py/asmrv32.h b/py/asmrv32.h index 6f709daa11b..1100d098019 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -804,7 +804,7 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_STORE32_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset, 2) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) -#define ASM_CLR_REG(state, rd) +#define ASM_CLR_REG(state, rd) asm_rv32_emit_optimised_xor(state, rd, rd) #define ASM_LOAD8_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 0) #define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 1) #define ASM_LOAD32_REG_REG_REG(state, rd, rs1, rs2) asm_rv32_emit_load_reg_reg_reg(state, rd, rs1, rs2, 2) diff --git a/py/asmthumb.h b/py/asmthumb.h index 5edf6573e1a..88f4e399bc8 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -485,6 +485,8 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); asm_thumb_str_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)); \ } while (0) +#define ASM_CLR_REG(as, reg_dest) asm_thumb_mov_rlo_i8((as), (reg_dest), 0) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMTHUMB_H diff --git a/py/asmx64.h b/py/asmx64.h index d80c5dcc13f..efc3027b170 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -221,6 +221,8 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, dword_offset) asm_x64_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (dword_offset)) +#define ASM_CLR_REG(as, reg_dest) asm_x64_xor_r64_r64((as), (reg_dest), (reg_dest)) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMX64_H diff --git a/py/asmx86.h b/py/asmx86.h index d2e078ad51d..80a67794d20 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -216,6 +216,8 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_STORE32_REG_REG(as, reg_src, reg_base) ASM_STORE32_REG_REG_OFFSET((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG_OFFSET(as, reg_src, reg_base, dword_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (dword_offset)) +#define ASM_CLR_REG(as, reg_dest) asm_x86_xor_r32_r32((as), (reg_dest), (reg_dest)) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMX86_H diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 559b3cacd5d..15f8b4d92e2 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -464,6 +464,8 @@ void asm_xtensa_l32r(asm_xtensa_t *as, mp_uint_t reg, mp_uint_t label); asm_xtensa_op_s32i_n((as), (reg_val), (reg_base), 0); \ } while (0) +#define ASM_CLR_REG(as, reg_dest) asm_xtensa_op_movi_n((as), (reg_dest), 0) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMXTENSA_H diff --git a/py/emitnative.c b/py/emitnative.c index a33ec01ec0f..6cf01dcab13 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -282,17 +282,13 @@ struct _emit_t { ASM_T *as; }; -#ifndef REG_ZERO -#define REG_ZERO REG_TEMP0 -#define ASM_CLR_REG(state, rd) ASM_XOR_REG_REG(state, rd, rd) -#endif - -#if N_RV32 +#ifdef REG_ZERO #define ASM_MOV_LOCAL_MP_OBJ_NULL(as, local_num, reg_temp) \ ASM_MOV_LOCAL_REG(as, local_num, REG_ZERO) #else +#define REG_ZERO REG_TEMP0 #define ASM_MOV_LOCAL_MP_OBJ_NULL(as, local_num, reg_temp) \ - ASM_MOV_REG_IMM(as, reg_temp, (mp_uint_t)MP_OBJ_NULL); \ + ASM_CLR_REG(as, reg_temp); \ ASM_MOV_LOCAL_REG(as, local_num, reg_temp) #endif @@ -1145,7 +1141,7 @@ static void emit_native_leave_exc_stack(emit_t *emit, bool start_of_handler) { // Optimisation: PC is already cleared by global exc handler return; } - ASM_XOR_REG_REG(emit->as, REG_RET, REG_RET); + ASM_CLR_REG(emit->as, REG_RET); } else { // Found new active handler, get its PC ASM_MOV_REG_PCREL(emit->as, REG_RET, e->label); @@ -1242,8 +1238,7 @@ static void emit_native_global_exc_entry(emit_t *emit) { ASM_JUMP_IF_REG_ZERO(emit->as, REG_RET, start_label, true); } else { // Clear the unwind state - ASM_CLR_REG(emit->as, REG_ZERO); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_ZERO); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_ZERO); // clear nlr.ret_val, because it's passed to mp_native_raise regardless // of whether there was an exception or not @@ -1263,8 +1258,7 @@ static void emit_native_global_exc_entry(emit_t *emit) { ASM_JUMP_IF_REG_NONZERO(emit->as, REG_RET, global_except_label, true); // Clear PC of current code block, and jump there to resume execution - ASM_CLR_REG(emit->as, REG_ZERO); - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_ZERO); + ASM_MOV_LOCAL_MP_OBJ_NULL(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_ZERO); ASM_JUMP_REG(emit->as, REG_LOCAL_1); // Global exception handler: check for valid exception handler @@ -1945,7 +1939,7 @@ static void emit_native_delete_attr(emit_t *emit, qstr qst) { vtype_kind_t vtype_base; emit_pre_pop_reg(emit, &vtype_base, REG_ARG_1); // arg1 = base assert(vtype_base == VTYPE_PYOBJ); - ASM_XOR_REG_REG(emit->as, REG_ARG_3, REG_ARG_3); // arg3 = value (null for delete) + ASM_CLR_REG(emit->as, REG_ARG_3); // arg3 = value (null for delete) emit_call_with_qstr_arg(emit, MP_F_STORE_ATTR, qst, REG_ARG_2); // arg2 = attribute name emit_post(emit); } @@ -2091,7 +2085,7 @@ static void emit_native_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t exc // No finally, handle the jump ourselves // First, restore the exception handler address for the jump if (e < emit->exc_stack) { - ASM_XOR_REG_REG(emit->as, REG_RET, REG_RET); + ASM_CLR_REG(emit->as, REG_RET); } else { ASM_MOV_REG_PCREL(emit->as, REG_RET, e->label); } diff --git a/py/emitndebug.c b/py/emitndebug.c index e49c5cdbffa..2144d14e6b5 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -271,6 +271,9 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_STORE32_REG_REG(as, reg_src, reg_base) \ asm_debug_reg_reg(as, "store32", reg_src, reg_base) +#define ASM_CLR_REG(as, reg_dest) \ + asm_debug_reg(as, "clr", reg_dest) + // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (5) // rbx From cb9d8fcc3162b6a4935200c73f58661e9d55888b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 28 Oct 2025 05:29:38 +0100 Subject: [PATCH 1591/2098] tests/run-tests.py: Discover RV32 extension inlineasm tests. This commit extends the test runner to automatically discover inline assembler tests for known RV32 extensions, and checks whether to add the discovered tests to the enabled tests list. Automatic discovery requires that inline assembler tests for RV32 extensions follow a specific pattern both for filenames and for the tests' output in case of success. A valid RV32 extension test must have: * A code fragment that checks for support of the extension on the running target in "/tests/feature_check", called "inlineasm_rv32_.py" that should print the string "rv32_" if the extension is supported * A matching expected result file in "/tests/feature_check" called "inlineasm_rv32_.py.exp" that must contain the string "rv32_" (without quotes) * A regular MicroPython test file in "/tests/inlineasm/rv32" called "asm_ext_.py" For example, to test the Zba extension, there must be a file called "/tests/feature_check/inlineasm_rv32_zba.py" that should print the string "rv32_zba" if the extension is supported, together with a file called "/test/feature_check/inlineasm_rv32_zba.py.exp" that contains the string "rv32_zba" in it, and finally there must be a regular MicroPython test file called "/tests/inlineasm/rv32/asm_ext_zba.py". Signed-off-by: Alessandro Gatti --- tests/inlineasm/rv32/{asmzba.py => asm_ext_zba.py} | 0 .../rv32/{asmzba.py.exp => asm_ext_zba.py.exp} | 0 tests/run-tests.py | 13 +++++++++---- 3 files changed, 9 insertions(+), 4 deletions(-) rename tests/inlineasm/rv32/{asmzba.py => asm_ext_zba.py} (100%) rename tests/inlineasm/rv32/{asmzba.py.exp => asm_ext_zba.py.exp} (100%) diff --git a/tests/inlineasm/rv32/asmzba.py b/tests/inlineasm/rv32/asm_ext_zba.py similarity index 100% rename from tests/inlineasm/rv32/asmzba.py rename to tests/inlineasm/rv32/asm_ext_zba.py diff --git a/tests/inlineasm/rv32/asmzba.py.exp b/tests/inlineasm/rv32/asm_ext_zba.py.exp similarity index 100% rename from tests/inlineasm/rv32/asmzba.py.exp rename to tests/inlineasm/rv32/asm_ext_zba.py.exp diff --git a/tests/run-tests.py b/tests/run-tests.py index aed67cbac8e..d4c6d445e95 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -950,10 +950,15 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("inlineasm/thumb/asmfpsqrt.py") if args.inlineasm_arch == "rv32": - # Check if @micropython.asm_rv32 supports Zba instructions, and skip such tests if it doesn't - output = run_feature_check(pyb, args, "inlineasm_rv32_zba.py") - if output != b"rv32_zba\n": - skip_tests.add("inlineasm/rv32/asmzba.py") + # Discover extension-specific inlineasm tests and add them to the + # list of tests to run if applicable. + for extension in RV32_ARCH_FLAGS: + try: + output = run_feature_check(pyb, args, "inlineasm_rv32_{}.py".format(extension)) + if output.strip() != "rv32_{}".format(extension).encode(): + skip_tests.add("inlineasm/rv32/asm_ext_{}.py".format(extension)) + except FileNotFoundError: + pass # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") From d938d5af4e1d4a860c80d3ecfbe0288c60858cb9 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 10 Nov 2025 21:12:37 +0100 Subject: [PATCH 1592/2098] qemu/Makefile: Allow usage of a custom QEMU binary to run code. This commit introduces a new optional makefile variable to let the build system know that, when running code, a custom QEMU binary must be used instead of the one provided by the system's PATH. Given that the CI machine won't keep up with QEMU updates unless its base image tracks a new version of QEMU itself, sometimes it is needed to use a custom QEMU build to be able to test new code in an emulated context rather than having to perform on-device testing during development. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 3 ++- ports/qemu/README.md | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index ba9c53841ae..c8ce3de8720 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -153,7 +153,8 @@ endif ################################################################################ # Project specific settings and compiler/linker flags -QEMU_SYSTEM = qemu-system-$(QEMU_ARCH) +QEMU_BASE ?= qemu-system- +QEMU_SYSTEM = $(QEMU_BASE)$(QEMU_ARCH) QEMU_ARGS += -machine $(QEMU_MACHINE) -nographic -monitor null -semihosting QEMU_ARGS += $(QEMU_EXTRA) diff --git a/ports/qemu/README.md b/ports/qemu/README.md index bf5788961db..a7134d0eb87 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -157,6 +157,10 @@ The following options can be specified on the `make` command line: - `CFLAGS_EXTRA`: pass in extra flags for the compiler. - `RUN_TESTS_EXTRA`: pass in extra flags for `run-tests.py` and `run-natmodtests.py` when invoked via `make test` or `make test_natmod`. +- `QEMU_BASE`: pass an optional partial path of the qemu binary to run code with, eg. + `/opt/custom-directory/qemu/bin/qemu-system-`, similar to how a cross compiler name + is passed to the MicroPython makefile. By default it will use the appropriate QEMU + binary found through the system's PATH environment variable. - `QEMU_DEBUG=1`: when running qemu (via `repl`, `run` or `test` target), qemu will block until a debugger is connected. By default it waits for a gdb connection on TCP port 1234. From 1df86516e0b4515c4d9b94811ca7da969de6dbc0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 28 Oct 2025 05:03:44 +0100 Subject: [PATCH 1593/2098] py/asmrv32: Reserve a flag for the Zcmp RV32 CPU extension. This commit performs the necessary changes to handle an additional RV32 CPU extension flag, for the Zcmp extension in this case. The changes are not limited to RV32-only code, as other parts of the tooling need to be modified for this: the testing framework has to be made aware that an extra bit can be set in sys.implementation._mpy and needs to know how it is called, and "mpy-cross" must be able to actually set that flag bit in the first place via the appropriate command line argument. Signed-off-by: Alessandro Gatti --- mpy-cross/main.c | 37 +++++++++++++++++++++++++++++++------ py/asmrv32.h | 6 ++++-- py/mpconfig.h | 5 +++++ tests/run-tests.py | 5 ++++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/mpy-cross/main.c b/mpy-cross/main.c index 38190671c6d..c9109788fb3 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -145,8 +145,8 @@ static int usage(char **argv) { "-march= : set architecture for native emitter;\n" " x86, x64, armv6, armv6m, armv7m, armv7em, armv7emsp,\n" " armv7emdp, xtensa, xtensawin, rv32imc, rv64imc, host, debug\n" - "-march-flags= : set architecture-specific flags (can be either a dec/hex/bin value or a string)\n" - " supported flags for rv32imc: zba\n" + "-march-flags= : set architecture-specific flags (can be either a dec/hex/bin value or a comma-separated flags string)\n" + " supported flags for rv32imc: zba, zcmp\n" "\n" "Implementation specific options:\n", argv[0] ); @@ -259,6 +259,34 @@ static bool parse_integer(const char *value, mp_uint_t *integer) { return valid; } +#if MICROPY_EMIT_NATIVE && MICROPY_EMIT_RV32 +static bool parse_rv32_flags_string(const char *source, mp_uint_t *flags) { + assert(source && "Flag arguments string is NULL."); + assert(flags && "Collected flags pointer is NULL."); + + const char *current = source; + const char *end = source + strlen(source); + mp_uint_t collected_flags = 0; + while (current < end) { + const char *separator = strchr(current, ','); + if (separator == NULL) { + separator = end; + } + ptrdiff_t length = separator - current; + if (length == (sizeof("zba") - 1) && memcmp(current, "zba", length) == 0) { + collected_flags |= RV32_EXT_ZBA; + } else if (length == (sizeof("zcmp") - 1) && memcmp(current, "zcmp", length) == 0) { + collected_flags |= RV32_EXT_ZCMP; + } else { + return false; + } + current = separator + 1; + } + *flags = collected_flags; + return collected_flags != 0; +} +#endif + MP_NOINLINE int main_(int argc, char **argv) { pre_process_options(argc, argv); @@ -412,14 +440,11 @@ MP_NOINLINE int main_(int argc, char **argv) { if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_RV32IMC) { mp_dynamic_compiler.backend_options = (void *)&rv32_options; mp_uint_t raw_flags = 0; - if (parse_integer(arch_flags, &raw_flags)) { + if (parse_integer(arch_flags, &raw_flags) || parse_rv32_flags_string(arch_flags, &raw_flags)) { if ((raw_flags & ~((mp_uint_t)RV32_EXT_ALL)) == 0) { rv32_options.allowed_extensions = raw_flags; processed = true; } - } else if (strncmp(arch_flags, "zba", sizeof("zba") - 1) == 0) { - rv32_options.allowed_extensions |= RV32_EXT_ZBA; - processed = true; } } #endif diff --git a/py/asmrv32.h b/py/asmrv32.h index 1100d098019..ed1b5a835c5 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -125,8 +125,9 @@ typedef struct _asm_rv32_t { enum { RV32_EXT_NONE = 0, RV32_EXT_ZBA = 1 << 0, + RV32_EXT_ZCMP = 1 << 1, - RV32_EXT_ALL = RV32_EXT_ZBA + RV32_EXT_ALL = RV32_EXT_ZBA | RV32_EXT_ZCMP }; typedef struct _asm_rv32_backend_options_t { @@ -710,7 +711,8 @@ static inline void asm_rv32_opcode_xori(asm_rv32_t *state, mp_uint_t rd, mp_uint } #define MICROPY_RV32_EXTENSIONS \ - (MICROPY_EMIT_RV32_ZBA ? RV32_EXT_ZBA : 0) + ((MICROPY_EMIT_RV32_ZBA ? RV32_EXT_ZBA : 0) | \ + (MICROPY_EMIT_RV32_ZCMP ? RV32_EXT_ZCMP : 0)) static inline uint8_t asm_rv32_allowed_extensions(void) { uint8_t extensions = MICROPY_RV32_EXTENSIONS; diff --git a/py/mpconfig.h b/py/mpconfig.h index 666b15573d9..e272564a7f2 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -507,6 +507,11 @@ typedef uint64_t mp_uint_t; #define MICROPY_EMIT_RV32_ZBA (0) #endif +// Whether to emit RISC-V RV32 Zcmp opcodes in native code +#ifndef MICROPY_EMIT_RV32_ZCMP +#define MICROPY_EMIT_RV32_ZCMP (0) +#endif + // Whether to enable the RISC-V RV32 inline assembler #ifndef MICROPY_EMIT_INLINE_RV32 #define MICROPY_EMIT_INLINE_RV32 (0) diff --git a/tests/run-tests.py b/tests/run-tests.py index d4c6d445e95..7f666e09b56 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -22,7 +22,10 @@ # are guaranteed to always work, this one should though. BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) -RV32_ARCH_FLAGS = {"zba": 1 << 0} +RV32_ARCH_FLAGS = { + "zba": 1 << 0, + "zcmp": 1 << 1, +} def base_path(*p): From 7a69b2d786f3a5d0ae75a7d1aa89f38be82fcd88 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 10 Nov 2025 22:43:44 +0100 Subject: [PATCH 1594/2098] py/asmrv32: Use Zcmp opcodes for function prologues and epilogues. This commit introduces the possibility of using Zcmp opcodes when generating function prologues and epilogues, reducing the generated code size. With the addition of selected Zcmp opcodes, each generated function can be up to 30 bytes shorter and having a faster prologue and epilogue. If Zcmp opcodes can be used then register saving is a matter of a simple CM.PUSH opcode rather than a series of C.SWSP opcodes. Conversely, register restoring is a single CM.POPRET opcode instead of a series of C.LWSP opcodes followed by a C.JR RA opcode. This should also lead to faster code given that there's only one opcode doing the registers saving rather than a series of them. For functions that allocate less than three locals then the generated code will allocate up to 12 bytes of unused stack space. Whilst this is a relatively rare occurrence for generated native and viper code, inline assembler blocks will probably incur into this penalty. Still, considering that at the moment the only targets that support Zcmp opcodes are relatively high-end MCUs (the RP2350 in RV32 mode and the ESP32P4), this is probably not much of an issue. Signed-off-by: Alessandro Gatti --- py/asmrv32.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++------ py/asmrv32.h | 20 ++++++++++++-- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/py/asmrv32.c b/py/asmrv32.c index 1d0cea6c026..8b643af5623 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -53,6 +53,14 @@ ((((value) & ~((1U << ((bits) - 1)) - 1)) == 0) || \ (((value) & ~((1U << ((bits) - 1)) - 1)) == ~((1U << ((bits) - 1)) - 1))) +static bool asm_rv32_allow_zba_opcodes(void) { + return asm_rv32_allowed_extensions() & RV32_EXT_ZBA; +} + +static bool asm_rv32_allow_zcmp_opcodes(void) { + return asm_rv32_allowed_extensions() & RV32_EXT_ZCMP; +} + /////////////////////////////////////////////////////////////////////////////// void asm_rv32_emit_word_opcode(asm_rv32_t *state, mp_uint_t word) { @@ -214,6 +222,14 @@ static void adjust_stack(asm_rv32_t *state, mp_int_t stack_size) { return; } + // WARNING: If REG_TEMP0 is not set to a caller-saved register, then this + // bit has to be rewritten to avoid clobbering the temporary + // register when performing the stack adjustment. + + MP_STATIC_ASSERT(((REG_TEMP0 >= ASM_RV32_REG_T0) && (REG_TEMP0 <= ASM_RV32_REG_T2)) || \ + ((REG_TEMP0 >= ASM_RV32_REG_A0) && (REG_TEMP0 <= ASM_RV32_REG_A7)) || \ + ((REG_TEMP0 >= ASM_RV32_REG_T3) && (REG_TEMP0 <= ASM_RV32_REG_T6))); + // li temporary, stack_size // c.add sp, temporary load_full_immediate(state, REG_TEMP0, stack_size); @@ -245,6 +261,45 @@ static void emit_function_epilogue(asm_rv32_t *state, mp_uint_t registers) { state->saved_registers_mask = old_saved_registers_mask; } +static mp_uint_t compute_zcmp_sequence_length(mp_uint_t registers) { + // Can only handle RA and S0..S11 and must have at least one entry. + assert((registers != 0) && (registers & (~0x0FFC0302U)) == 0 && "Invalid Zcmp registers set."); + mp_uint_t length = 32 - mp_clz(((registers & 0x00000002) >> 1) | ((registers & 0x00000300) >> 7) | ((registers & 0x0FFC0000) >> 15)); + return length == 12 ? 13 : length; +} + +#define EMIT_ASSERT(state, condition, message) assert((((state)->base.pass != MP_ASM_PASS_EMIT) ? true : (condition)) && (message)) + +static void emit_compressed_function_prologue(asm_rv32_t *state, mp_uint_t registers_mask) { + mp_uint_t sequence_length = compute_zcmp_sequence_length(registers_mask); + mp_uint_t allocated_stack = (sequence_length + 3) & (mp_uint_t)-4; + EMIT_ASSERT(state, allocated_stack >= sequence_length, "Incorrect allocated stack calculation."); + mp_uint_t tail_slack = allocated_stack - sequence_length; + mp_uint_t locals_left = (state->locals_count < tail_slack) ? 0 : (state->locals_count - tail_slack); + mp_uint_t adjustment_chunks = MIN(3, locals_left / 4); + EMIT_ASSERT(state, (adjustment_chunks * 4) <= locals_left, "Incorrect adjustment chunks rounding."); + locals_left -= adjustment_chunks * 4; + EMIT_ASSERT(state, locals_left <= (MP_INT_MAX / sizeof(uint32_t)), "Too many locals."); + mp_int_t stack_size = (mp_int_t)(locals_left * sizeof(uint32_t)); + asm_rv32_opcode_cmpush(state, MIN(3 + sequence_length, 15), adjustment_chunks); + // CM.PUSH allocates a stack block and then puts the registers *at the end* + // of the block, so for example "CM.PUSH {RA, S0-S11}, -64" will put RA at + // SP + 60, not at SP + 0. + adjust_stack(state, -stack_size); + // The stack size is expressed in bytes and as a multiple of 4, hence the + // bottom two bits are not used. Since there can be up to three adjustment + // chunks, that number can be expressed in two bits, fitting nicely in the + // existing variable. + state->stack_size = ((mp_uint_t)stack_size) | adjustment_chunks; +} + +static void emit_compressed_function_epilogue(asm_rv32_t *state, mp_uint_t registers_mask) { + mp_uint_t sequence_length = compute_zcmp_sequence_length(registers_mask); + mp_uint_t stack_size = state->stack_size & (mp_uint_t)(~0x03U); + adjust_stack(state, stack_size); + asm_rv32_opcode_cmpopret(state, MIN(3 + sequence_length, 15), state->stack_size & 0x03); +} + static bool calculate_displacement_for_label(asm_rv32_t *state, mp_uint_t label, ptrdiff_t *displacement) { assert(displacement != NULL && "Displacement pointer is NULL"); @@ -256,16 +311,24 @@ static bool calculate_displacement_for_label(asm_rv32_t *state, mp_uint_t label, /////////////////////////////////////////////////////////////////////////////// void asm_rv32_entry(asm_rv32_t *state, mp_uint_t locals) { + state->locals_count = locals; state->saved_registers_mask |= (1U << REG_FUN_TABLE) | (1U << REG_LOCAL_1) | \ (1U << REG_LOCAL_2) | (1U << REG_LOCAL_3); - state->locals_count = locals; - emit_function_prologue(state, state->saved_registers_mask); + if (asm_rv32_allow_zcmp_opcodes()) { + emit_compressed_function_prologue(state, state->saved_registers_mask); + } else { + emit_function_prologue(state, state->saved_registers_mask); + } } void asm_rv32_exit(asm_rv32_t *state) { - emit_function_epilogue(state, state->saved_registers_mask); - // c.jr ra - asm_rv32_opcode_cjr(state, ASM_RV32_REG_RA); + if (asm_rv32_allow_zcmp_opcodes()) { + emit_compressed_function_epilogue(state, state->saved_registers_mask); + } else { + emit_function_epilogue(state, state->saved_registers_mask); + // c.jr ra + asm_rv32_opcode_cjr(state, ASM_RV32_REG_RA); + } } void asm_rv32_end_pass(asm_rv32_t *state) { @@ -557,10 +620,6 @@ void asm_rv32_emit_optimised_xor(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs) asm_rv32_opcode_xor(state, rd, rd, rs); } -static bool asm_rv32_allow_zba_opcodes(void) { - return asm_rv32_allowed_extensions() & RV32_EXT_ZBA; -} - static void asm_rv32_fix_up_scaled_reg_reg_reg(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t operation_size) { assert(operation_size <= 2 && "Operation size value out of range."); diff --git a/py/asmrv32.h b/py/asmrv32.h index ed1b5a835c5..c25b1aa4e26 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -197,6 +197,10 @@ void asm_rv32_end_pass(asm_rv32_t *state); ((rs & 0x07) << 7) | ((imm & 0x40) >> 1) | ((imm & 0x38) << 7) | \ ((imm & 0x04) << 4)) +#define RV32_ENCODE_TYPE_CMPP(op, ft6, ft2, rlist, imm) \ + ((op & 0x03) | ((ft6 & 0x3F) << 10) | ((ft2 & 0x03) << 8) | \ + ((rlist & 0x0F) << 4) | ((imm & 0x03) << 2)) + #define RV32_ENCODE_TYPE_CR(op, ft4, rs1, rs2) \ ((op & 0x03) | ((rs2 & 0x1F) << 2) | ((rs1 & 0x1F) << 7) | ((ft4 & 0x0F) << 12)) @@ -440,6 +444,18 @@ static inline void asm_rv32_opcode_cxor(asm_rv32_t *state, mp_uint_t rd, mp_uint asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x01, rd, rs)); } +// CM.POPRET {REG_LIST}, IMMEDIATE +static inline void asm_rv32_opcode_cmpopret(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { + // CMPP: 10111110 ... .. 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMPP(0x02, 0x2F, 0x02, reg_list, immediate)); +} + +// CM.PUSH {REG_LIST}, -IMMEDIATE +static inline void asm_rv32_opcode_cmpush(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { + // CMPP: 10111000 .... .. 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMPP(0x02, 0x2E, 0x00, reg_list, immediate)); +} + // CSRRC RD, RS, IMMEDIATE static inline void asm_rv32_opcode_csrrc(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, mp_int_t immediate) { // I: ............ ..... 011 ..... 1110011 @@ -737,8 +753,8 @@ static inline uint8_t asm_rv32_allowed_extensions(void) { #define REG_TEMP2 ASM_RV32_REG_T3 #define REG_FUN_TABLE ASM_RV32_REG_S1 #define REG_LOCAL_1 ASM_RV32_REG_S3 -#define REG_LOCAL_2 ASM_RV32_REG_S4 -#define REG_LOCAL_3 ASM_RV32_REG_S5 +#define REG_LOCAL_2 ASM_RV32_REG_S2 +#define REG_LOCAL_3 ASM_RV32_REG_S4 #define REG_ZERO ASM_RV32_REG_ZERO void asm_rv32_meta_comparison_eq(asm_rv32_t *state, mp_uint_t rs1, mp_uint_t rs2, mp_uint_t rd); From 08096392b58411c6b30031683af7d49760c19831 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 10 Nov 2025 22:57:42 +0100 Subject: [PATCH 1595/2098] rp2/mpconfigport: Enable Zcmp opcodes for RP2350 in RV32 mode. This commit enables support for Zcmp opcodes when the firmware is built for the RP2350 in RV32 mode. The RP2350 explicitly supports the Zcmp extension for reducing the amount of code needed for function prologues and epilogues (see section 3.8.1.20 of the datasheet). Signed-off-by: Alessandro Gatti --- ports/rp2/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8cc12aff1c2..1bcd7384911 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -110,6 +110,7 @@ #elif PICO_RISCV #define MICROPY_EMIT_RV32 (1) #define MICROPY_EMIT_RV32_ZBA (1) +#define MICROPY_EMIT_RV32_ZCMP (1) #define MICROPY_EMIT_INLINE_RV32 (1) #endif From ec5f2bc686a51aad903518c0f84d7d39d3b3432c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 10 Nov 2025 23:14:15 +0100 Subject: [PATCH 1596/2098] rp2/CMakeLists.txt: Set the appropriate mpy-cross flags on all targets. This commit lets the RP2 port build system use the appropriate flags to pass to "mpy-cross" when building frozen MPY files as part of the build process. Now all possible variants (RP2040, RP2350/Arm, and RP2350/RV32) have their right flags assigned, falling back the flags set of the RP2040 if a new variant is introduced. Before these changes all variants would use the RP2040 set of flags which may be a bit of an issue when building code for the RP2350 in RV32 mode. Signed-off-by: Alessandro Gatti --- ports/rp2/CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index c4a081e2efd..d88a96d4a70 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -487,7 +487,16 @@ list(APPEND MICROPY_SOURCE_QSTR ) # Define mpy-cross flags -set(MICROPY_CROSS_FLAGS -march=armv6m) +if (${PICO_PLATFORM} STREQUAL "rp2040") + set(MICROPY_CROSS_FLAGS "-march=armv6m") +elseif (${PICO_PLATFORM} STREQUAL "rp2350-arm-s") + set(MICROPY_CROSS_FLAGS "-march=armv7m") +elseif (${PICO_PLATFORM} STREQUAL "rp2350-riscv") + set(MICROPY_CROSS_FLAGS "-march=rv32imc -march-flags=zba,zcmp") +else() + message(WARNING "Unknown PICO_PLATFORM version (${PICO_PLATFORM}), falling back to rp2040") + set(MICROPY_CROSS_FLAGS "-march=armv6m") +endif() # Set the frozen manifest file if (MICROPY_USER_FROZEN_MANIFEST) From 634125820744efa33679fb95a6e441dadaa4f6a7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 11 Dec 2025 03:42:33 +0100 Subject: [PATCH 1597/2098] esp32/mpconfigport: Enable Zcmp opcodes for ESP32P4. This commit enables support for Zcmp opcodes when the firmware is built to target ESP32P4 microcontrollers. The ESP32P4 explicitly supports the Zcmp extension for reducing the amount of code needed for function prologues and epilogues (see section 4.1.1.1 of the ESP32P4 datasheet). Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 6 +++++- ports/esp32/mpconfigport.h | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index e3b1b81cae7..ea60c395a76 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -244,7 +244,11 @@ set(MICROPY_TARGET ${COMPONENT_TARGET}) if(CONFIG_IDF_TARGET_ARCH_XTENSA) set(MICROPY_CROSS_FLAGS -march=xtensawin) elseif(CONFIG_IDF_TARGET_ARCH_RISCV) - set(MICROPY_CROSS_FLAGS -march=rv32imc) + if (CONFIG_IDF_TARGET_ESP32P4) + set(MICROPY_CROSS_FLAGS "-march=rv32imc -march-flags=zcmp") + else() + set(MICROPY_CROSS_FLAGS -march=rv32imc) + endif() endif() # Set compile options for this port. diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 55503ff0bae..69419ce009b 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -44,6 +44,9 @@ #define MICROPY_EMIT_RV32 (0) #else #define MICROPY_EMIT_RV32 (1) +#if CONFIG_IDF_TARGET_ESP32P4 +#define MICROPY_EMIT_RV32_ZCMP (1) +#endif #endif #else #define MICROPY_EMIT_XTENSAWIN (1) From b0f3ecd96c8ca87f38366422f8cb74e0ce9fa2ec Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 07:13:51 +0100 Subject: [PATCH 1598/2098] py/persistentcode: Decouple native code loading from emitters' presence. This commit lets the interpreter load MPY files containing native code even if the target platform does not have a native emitter, or if native code generation is disabled. Native code loading has been tied to native code generation being enabled as a discriminant to allow said operation. This blocks native code loading on platforms that could benefit from such a thing but they don't (and probably won't) have a native code generation target written for them (ie. AArch64 and RISC-V 64). This also forces a firmware image to have a full native code compiler present even if it doesn't need to generate anything, as native modules already have all the code they will ever need to load. There is a new configuration setting, MICROPY_PERSISTENT_CODE_LOAD_NATIVE, that if enabled it will allow loading native code modules even if code generation (MICROPY_EMIT_ and MICROPY_EMIT_INLINE_) is explicitly turned off. Signed-off-by: Alessandro Gatti --- py/bc.c | 2 +- py/emitglue.c | 4 ++-- py/emitglue.h | 4 ++-- py/mpconfig.h | 15 ++++++++++++--- py/nativeglue.c | 10 +++++----- py/nativeglue.h | 4 ++-- py/objfun.c | 10 +++------- py/objfun.h | 2 +- py/objgenerator.c | 10 +++++----- py/persistentcode.c | 14 +++++++------- py/runtime.c | 2 +- 11 files changed, 41 insertions(+), 36 deletions(-) diff --git a/py/bc.c b/py/bc.c index cea31c93bd8..777ff66fcd4 100644 --- a/py/bc.c +++ b/py/bc.c @@ -332,7 +332,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw mp_setup_code_state_helper(code_state, n_args, n_kw, args); } -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE // On entry code_state should be allocated somewhere (stack/heap) and // contain the following valid entries: // - code_state->fun_bc should contain a pointer to the function object diff --git a/py/emitglue.c b/py/emitglue.c index 8a32b227fff..4711e146fc5 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -92,7 +92,7 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif } -#if MICROPY_EMIT_MACHINE_CODE +#if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, const void *fun_data, mp_uint_t fun_len, mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE @@ -201,7 +201,7 @@ mp_obj_t mp_make_function_from_proto_fun(mp_proto_fun_t proto_fun, const mp_modu // make the function, depending on the raw code kind mp_obj_t fun; switch (rc->kind) { - #if MICROPY_EMIT_NATIVE + #if MICROPY_ENABLE_NATIVE_CODE case MP_CODE_NATIVE_PY: fun = mp_obj_new_fun_native(def_args, rc->fun_data, context, rc->children); // Check for a generator function, and if so change the type of the object diff --git a/py/emitglue.h b/py/emitglue.h index 126462671b0..6e2add80339 100644 --- a/py/emitglue.h +++ b/py/emitglue.h @@ -80,7 +80,7 @@ typedef struct _mp_raw_code_t { #if MICROPY_PERSISTENT_CODE_SAVE uint32_t fun_data_len; // for mp_raw_code_save uint16_t n_children; - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE uint16_t prelude_offset; #endif #if MICROPY_PY_SYS_SETTRACE @@ -110,7 +110,7 @@ typedef struct _mp_raw_code_truncated_t { #if MICROPY_PERSISTENT_CODE_SAVE uint32_t fun_data_len; uint16_t n_children; - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE uint16_t prelude_offset; #endif #if MICROPY_PY_SYS_SETTRACE diff --git a/py/mpconfig.h b/py/mpconfig.h index e272564a7f2..3211d4d398e 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -411,6 +411,11 @@ typedef uint64_t mp_uint_t; #define MICROPY_PERSISTENT_CODE_LOAD (0) #endif +// Whether to support loading of persistent native code +#ifndef MICROPY_PERSISTENT_CODE_LOAD_NATIVE +#define MICROPY_PERSISTENT_CODE_LOAD_NATIVE (MICROPY_EMIT_MACHINE_CODE) +#endif + // Whether to support saving of persistent code, i.e. for mpy-cross to // generate .mpy files. Enabling this enables additional metadata on raw code // objects which is also required for sys.settrace. @@ -431,7 +436,7 @@ typedef uint64_t mp_uint_t; // Whether generated code can persist independently of the VM/runtime instance // This is enabled automatically when needed by other features #ifndef MICROPY_PERSISTENT_CODE -#define MICROPY_PERSISTENT_CODE (MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE || MICROPY_MODULE_FROZEN_MPY) +#define MICROPY_PERSISTENT_CODE (MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_LOAD_NATIVE || MICROPY_PERSISTENT_CODE_SAVE || MICROPY_MODULE_FROZEN_MPY) #endif // Whether bytecode uses a qstr_table to map internal qstr indices in the bytecode @@ -536,6 +541,10 @@ typedef uint64_t mp_uint_t; // Convenience definition for whether any native or inline assembler emitter is enabled #define MICROPY_EMIT_MACHINE_CODE (MICROPY_EMIT_NATIVE || MICROPY_EMIT_INLINE_ASM) +// Convenience definition for whether native code has to be dealt with (either +// generated or loaded from a file). This does not cover inline asm code. +#define MICROPY_ENABLE_NATIVE_CODE (MICROPY_EMIT_NATIVE || MICROPY_PERSISTENT_CODE_LOAD_NATIVE) + /*****************************************************************************/ /* Compiler configuration */ @@ -2276,7 +2285,7 @@ typedef time_t mp_timestamp_t; // can be overridden if needed by defining both MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA // and MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA. #ifndef MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA -#if MICROPY_EMIT_MACHINE_CODE && MICROPY_PERSISTENT_CODE_LOAD +#if (MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE) && MICROPY_PERSISTENT_CODE_LOAD // Pointer tracking is required when loading native code is enabled. #if defined(MP_PLAT_ALLOC_EXEC) || defined(MP_PLAT_COMMIT_EXEC) // If a port defined a custom allocator or commit function for native text, then the @@ -2297,7 +2306,7 @@ typedef time_t mp_timestamp_t; #define MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA (1) #define MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA (0) #endif -#else // MICROPY_EMIT_MACHINE_CODE && MICROPY_PERSISTENT_CODE_LOAD +#else // (MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE) && MICROPY_PERSISTENT_CODE_LOAD // Pointer tracking not needed when loading native code is disabled. #define MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA (0) #define MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA (0) diff --git a/py/nativeglue.c b/py/nativeglue.c index 6bf16f1acc2..e4aa635cf1c 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -41,7 +41,7 @@ #define DEBUG_printf(...) (void)0 #endif -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE int mp_native_type_from_qstr(qstr qst) { switch (qst) { @@ -91,7 +91,7 @@ mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type) { #endif -#if MICROPY_EMIT_MACHINE_CODE +#if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE // convert a native value to a MicroPython object based on type mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type) { @@ -115,7 +115,7 @@ mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type) { #endif -#if MICROPY_EMIT_NATIVE && !MICROPY_DYNAMIC_COMPILER +#if MICROPY_ENABLE_NATIVE_CODE && !MICROPY_DYNAMIC_COMPILER #if !MICROPY_PY_BUILTINS_SET mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { @@ -354,8 +354,8 @@ const mp_fun_table_t mp_fun_table = { &mp_stream_write_obj, }; -#elif MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER +#elif MICROPY_ENABLE_NATIVE_CODE && MICROPY_DYNAMIC_COMPILER const int mp_fun_table; -#endif // MICROPY_EMIT_NATIVE +#endif // MICROPY_ENABLE_NATIVE_CODE diff --git a/py/nativeglue.h b/py/nativeglue.h index 2c7923c56df..92d3889df39 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -181,9 +181,9 @@ typedef struct _mp_fun_table_t { const mp_obj_fun_builtin_var_t *stream_write_obj; } mp_fun_table_t; -#if (MICROPY_EMIT_NATIVE && !MICROPY_DYNAMIC_COMPILER) || MICROPY_ENABLE_DYNRUNTIME +#if (MICROPY_ENABLE_NATIVE_CODE && !MICROPY_DYNAMIC_COMPILER) || MICROPY_ENABLE_DYNRUNTIME extern const mp_fun_table_t mp_fun_table; -#elif MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER +#elif MICROPY_ENABLE_NATIVE_CODE && MICROPY_DYNAMIC_COMPILER // In dynamic-compiler mode eliminate dependency on entries in mp_fun_table. // This only needs to be an independent pointer, content doesn't matter. extern const int mp_fun_table; diff --git a/py/objfun.c b/py/objfun.c index a742c526725..933da1e494d 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -136,7 +136,7 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { const mp_obj_fun_bc_t *fun = MP_OBJ_TO_PTR(fun_in); const byte *bc = fun->bytecode; - #if MICROPY_EMIT_NATIVE + #if MICROPY_ENABLE_NATIVE_CODE if (fun->base.type == &mp_type_fun_native || fun->base.type == &mp_type_native_gen_wrap) { bc = mp_obj_fun_native_get_prelude_ptr(fun); } @@ -443,7 +443,7 @@ mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_ /******************************************************************************/ /* native functions */ -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE static mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_cstack_check(); @@ -472,13 +472,9 @@ MP_DEFINE_CONST_OBJ_TYPE( call, fun_native_call ); -#endif // MICROPY_EMIT_NATIVE - /******************************************************************************/ /* viper functions */ -#if MICROPY_EMIT_NATIVE - static mp_obj_t fun_viper_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_cstack_check(); mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); @@ -493,7 +489,7 @@ MP_DEFINE_CONST_OBJ_TYPE( call, fun_viper_call ); -#endif // MICROPY_EMIT_NATIVE +#endif // MICROPY_ENABLE_NATIVE_CODE /******************************************************************************/ /* inline assembler functions */ diff --git a/py/objfun.h b/py/objfun.h index f16ef76b80e..8494eb4bf66 100644 --- a/py/objfun.h +++ b/py/objfun.h @@ -53,7 +53,7 @@ typedef struct _mp_obj_fun_asm_t { mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_module_context_t *cm, struct _mp_raw_code_t *const *raw_code_table); void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE static inline mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) { mp_obj_fun_bc_t *o = (mp_obj_fun_bc_t *)MP_OBJ_TO_PTR(mp_obj_new_fun_bc(def_args, (const byte *)fun_data, mc, child_table)); diff --git a/py/objgenerator.c b/py/objgenerator.c index df9701094b8..e2a02d494ed 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -87,7 +87,7 @@ MP_DEFINE_CONST_OBJ_TYPE( /******************************************************************************/ // native generator wrapper -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE // Based on mp_obj_gen_instance_t. typedef struct _mp_obj_gen_instance_native_t { @@ -139,7 +139,7 @@ MP_DEFINE_CONST_OBJ_TYPE( NATIVE_GEN_WRAP_TYPE_ATTR ); -#endif // MICROPY_EMIT_NATIVE +#endif // MICROPY_ENABLE_NATIVE_CODE /******************************************************************************/ /* generator instance */ @@ -175,7 +175,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // If the generator is started, allow sending a value. void *state_start = self->code_state.state - 1; - #if MICROPY_EMIT_NATIVE + #if MICROPY_ENABLE_NATIVE_CODE if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { state_start = ((mp_obj_gen_instance_native_t *)self)->code_state.state - 1; } @@ -197,7 +197,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ mp_vm_return_kind_t ret_kind; - #if MICROPY_EMIT_NATIVE + #if MICROPY_ENABLE_NATIVE_CODE if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { // A native generator. typedef uintptr_t (*mp_fun_native_gen_t)(void *, mp_obj_t); @@ -235,7 +235,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ case MP_VM_RETURN_EXCEPTION: { self->code_state.ip = 0; - #if MICROPY_EMIT_NATIVE + #if MICROPY_ENABLE_NATIVE_CODE if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { *ret_val = ((mp_obj_gen_instance_native_t *)self)->code_state.state[0]; } else diff --git a/py/persistentcode.c b/py/persistentcode.c index b12c1007433..d83386736b2 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -76,7 +76,7 @@ typedef struct _bytecode_prelude_t { static int read_byte(mp_reader_t *reader); static size_t read_uint(mp_reader_t *reader); -#if MICROPY_EMIT_MACHINE_CODE +#if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA @@ -222,7 +222,7 @@ static mp_obj_t mp_obj_new_str_static(const mp_obj_type_t *type, const byte *dat static mp_obj_t load_obj(mp_reader_t *reader) { byte obj_type = read_byte(reader); - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE if (obj_type == MP_PERSISTENT_OBJ_FUN_TABLE) { return MP_OBJ_FROM_PTR(&mp_fun_table); } else @@ -295,14 +295,14 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co bool has_children = !!(kind_len & 4); size_t fun_data_len = kind_len >> 3; - #if !MICROPY_EMIT_MACHINE_CODE + #if !(MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE) if (kind != MP_CODE_BYTECODE) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); } #endif uint8_t *fun_data = NULL; - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE size_t prelude_offset = 0; mp_uint_t native_scope_flags = 0; mp_uint_t native_n_pos_args = 0; @@ -322,7 +322,7 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co read_bytes(reader, fun_data, fun_data_len); } - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE } else { // Allocate memory for native data and load it size_t fun_alloc; @@ -349,7 +349,7 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co size_t n_children = 0; mp_raw_code_t **children = NULL; - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE // Load optional BSS/rodata for viper. uint8_t *rodata = NULL; uint8_t *bss = NULL; @@ -404,7 +404,7 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co #endif scope_flags); - #if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE } else { const uint8_t *prelude_ptr = NULL; #if MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE diff --git a/py/runtime.c b/py/runtime.c index ddc1ef7d3ea..8d7e11e5f0d 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -123,7 +123,7 @@ void mp_init(void) { MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif - #if MICROPY_EMIT_MACHINE_CODE && (MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA) + #if (MICROPY_EMIT_INLINE_ASM || MICROPY_ENABLE_NATIVE_CODE) && (MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA) MP_STATE_VM(persistent_code_root_pointers) = MP_OBJ_NULL; #endif From 5ae928a2b4ebaab62971280ce531b4e47014f247 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 19 Dec 2025 21:20:49 +0100 Subject: [PATCH 1599/2098] py/persistentcode: Detect the target architecture from compiler defines. This commit replaces the target architecture definition method, from relying on defines in MicroPython's configuration, to using the host compiler's environment definitions instead. Before these changes the target type was inferred from which kind of native emitter was enabled, since there can be only just one available at all times and it has to be the correct one otherwise generated code would crash the target. However, with the introduction of RV64 there is now a platform without an emitter, and thus RV64 targets would not be able to report their platform via "sys.implementation._mpy". The target is reported only if the interpreter is configured to load external MPY code. Now a series of compile definitions are used to detect the target platform the firmware image is compiled for, making the target definition more accurate and much harder to force the interpreter to report the wrong target architecture due to a misconfiguration. Whilst this is probably not directly affecting user-facing code, some infrastructure components like the testing framework and its associated feature scripts rely on this feature to properly execute test runs. This is also a concern for situations in which loading external binary code is needed but all code generation functions have to be disabled. Signed-off-by: Alessandro Gatti --- py/persistentcode.h | 55 ++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/py/persistentcode.h b/py/persistentcode.h index 8fd7068e3cd..1e0bbbd272f 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -48,31 +48,44 @@ #define MPY_FEATURE_DECODE_ARCH(feat) (((feat) >> 2) & 0x2F) // Define the host architecture -#if MICROPY_EMIT_X86 - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86) -#elif MICROPY_EMIT_X64 - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X64) -#elif MICROPY_EMIT_THUMB - #if defined(__thumb2__) - #if defined(__ARM_FP) && (__ARM_FP & 8) == 8 - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMDP) - #elif defined(__ARM_FP) && (__ARM_FP & 4) == 4 - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMSP) +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE + #if defined(__i386__) || defined(_M_IX86) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86) + #elif defined(__x86_64__) || defined(_M_X64) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X64) + #elif defined(__thumb2__) || defined(__thumb__) + #if defined(__thumb2__) + #if defined(__ARM_FP) && (__ARM_FP & 8) == 8 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMDP) + #elif defined(__ARM_FP) && (__ARM_FP & 4) == 4 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMSP) + #else + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EM) + #endif #else - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EM) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6M) + #endif + #define MPY_FEATURE_ARCH_TEST(x) (MP_NATIVE_ARCH_ARMV6M <= (x) && (x) <= MPY_FEATURE_ARCH) + #elif defined(__arm__) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6) + #elif defined(__xtensa__) + #include + #if XCHAL_HAVE_WINDOWED + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSAWIN) + #else + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSA) + #endif + #elif defined(__riscv) + #if __riscv_xlen == 32 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_RV32IMC) + #elif __riscv_xlen == 64 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_RV64IMC) + #else + #error "Unsupported RISC-V architecture." #endif #else - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6M) + #error "Unsupported native architecture." #endif - #define MPY_FEATURE_ARCH_TEST(x) (MP_NATIVE_ARCH_ARMV6M <= (x) && (x) <= MPY_FEATURE_ARCH) -#elif MICROPY_EMIT_ARM - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6) -#elif MICROPY_EMIT_XTENSA - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSA) -#elif MICROPY_EMIT_XTENSAWIN - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSAWIN) -#elif MICROPY_EMIT_RV32 - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_RV32IMC) #else #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_NONE) #endif From 9e9da6cb0d794e85dbafcabd39972528afc15474 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 10:02:57 +0100 Subject: [PATCH 1600/2098] py/emitglue: Flush caches when loading native code without emitters. This commit forces a data/instruction cache flush after loading native code blocks even if there's no emitter available. Caches flush upon native code load was still tied to the presence of a native emitter, but this assumption is no longer valid due to previous commits decoupling native code loading from code generation availability. Signed-off-by: Alessandro Gatti --- py/emitglue.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/emitglue.c b/py/emitglue.c index 4711e146fc5..a72f94c88bd 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -107,14 +107,14 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons // Some architectures require flushing/invalidation of the I/D caches, // so that the generated native code which was created in data RAM will // be available for execution from instruction RAM. - #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB + #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB || (MP_NATIVE_ARCH_ARMV6M <= MPY_FEATURE_ARCH && MPY_FEATURE_ARCH <= MP_NATIVE_ARCH_ARMV7EMDP) #if __ICACHE_PRESENT == 1 // Flush D-cache, so the code emitted is stored in RAM. MP_HAL_CLEAN_DCACHE(fun_data, fun_len); // Invalidate I-cache, so the newly-created code is reloaded from RAM. SCB_InvalidateICache(); #endif - #elif MICROPY_EMIT_ARM + #elif MICROPY_EMIT_ARM || (MPY_FEATURE_ARCH == MP_NATIVE_ARCH_ARMV6) #if (defined(__linux__) && defined(__GNUC__)) || __ARM_ARCH == 7 __builtin___clear_cache((void *)fun_data, (char *)fun_data + fun_len); #elif defined(__arm__) @@ -127,7 +127,7 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons "mcr p15, 0, r0, c7, c7, 0\n" // invalidate I-cache and D-cache : : : "r0", "cc"); #endif - #elif (MICROPY_EMIT_RV32 || MICROPY_EMIT_INLINE_RV32) && defined(MP_HAL_CLEAN_DCACHE) + #elif (MICROPY_EMIT_RV32 || MICROPY_EMIT_INLINE_RV32 || (MPY_FEATURE_ARCH == MP_NATIVE_ARCH_RV32IMC)) && defined(MP_HAL_CLEAN_DCACHE) // Flush the D-cache. MP_HAL_CLEAN_DCACHE(fun_data, fun_len); #endif From 2b82f512d21b6d57f0ec803f892ce743f8864aa0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 07:23:31 +0100 Subject: [PATCH 1601/2098] unix/alloc: Map executable memory for code loading when needed. This commit lets the Unix port use the new MICROPY_PERSISTENT_CODE_LOAD_NATIVE configuration entry. The Unix port needs to allocate memory with specific flags that let the operating system run executable code from there. This functionality was gated behind the presence of a native emitter and thus its inclusion condition had to be updated. Signed-off-by: Alessandro Gatti --- ports/unix/alloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/unix/alloc.c b/ports/unix/alloc.c index 9ab2ca04ebc..7e1b261261e 100644 --- a/ports/unix/alloc.c +++ b/ports/unix/alloc.c @@ -32,7 +32,7 @@ #include "py/mpstate.h" -#if MICROPY_EMIT_NATIVE +#if MICROPY_ENABLE_NATIVE_CODE #if defined(__OpenBSD__) || defined(__MACH__) #define MAP_ANONYMOUS MAP_ANON @@ -80,4 +80,4 @@ void mp_unix_free_exec(void *ptr, size_t size) { MP_REGISTER_ROOT_POINTER(void *mmap_region_head); -#endif // MICROPY_EMIT_NATIVE +#endif // MICROPY_ENABLE_NATIVE_CODE From ecf88732938de7996d7d8f705fc9f9d9bed0850e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 09:26:02 +0100 Subject: [PATCH 1602/2098] qemu/Makefile: Allow overriding the test natmods list. This commit modifies the QEMU port makefile to let the user provide their own list of natmods to test as part of "test_natmod". The makefile now will replace the list of natmods to test with the contents of the "TEST_NATMODS" command line variable, so if there's a specific subset of natmods causing problems test runs can be limited to that very subset. "TEST_NATMODS" accepts a whitespace-separated list of natmod names for which there are available matching tests in "tests/extmod" (eg. make test_natmod TEST_NATMODS="btree heapq"). Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 5 ++++- ports/qemu/README.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index c8ce3de8720..3bc46ffefdc 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -164,6 +164,9 @@ QEMU_DEBUG_ARGS ?= -s QEMU_ARGS += -S $(QEMU_DEBUG_ARGS) $(QEMU_DEBUG_EXTRA) endif +# Allow selecting a subset of natmods to test. +TEST_NATMODS ?= btree deflate framebuf heapq random_basic re + INC += -I. INC += -I$(TOP) INC += -I$(BUILD) @@ -265,7 +268,7 @@ test_full: $(BUILD)/firmware.elf test_natmod: $(BUILD)/firmware.elf $(eval DIRNAME=ports/$(notdir $(CURDIR))) cd $(TOP)/tests && \ - for natmod in btree deflate framebuf heapq random_basic re; do \ + for natmod in $(TEST_NATMODS); do \ ./run-natmodtests.py -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../$(DIRNAME)/$<" $(RUN_TESTS_EXTRA) extmod/$$natmod*.py; \ done diff --git a/ports/qemu/README.md b/ports/qemu/README.md index a7134d0eb87..0f53c000082 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -167,5 +167,8 @@ The following options can be specified on the `make` command line: - `QEMU_DEBUG_ARGS`: defaults to `-s` (gdb on TCP port 1234), but can be overridden with different qemu gdb arguments. - `QEMU_DEBUG_EXTRA`: extra options to pass to qemu when `QEMU_DEBUG=1` is used. +- `TEST_NATMODS`: pass an optional list of space-separated names of natmods to test, + so only the given subset of example natmods will be used by `test_natmod` (for + example, `make test_natmod TEST_NATMODS="btree heapq re"`). - `MICROPY_HEAP_SIZE`: pass in an optional value (in bytes) for overriding the GC heap size used by the port. From 14ea6d7fd867557be3f801b5bb2d2e6e2721b3e7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 10:10:14 +0100 Subject: [PATCH 1603/2098] esp8266/modesp: Allocate executable memory when needed. This commit lets the ESP8266 port use the new MICROPY_PERSISTENT_CODE_LOAD_NATIVE configuration entry. The ESP8266 port needs a special procedure to allocate memory used to hold executable native code. This functionality was gated behind the presence of a native emitter and thus its inclusion condition had to be updated. Signed-off-by: Alessandro Gatti --- ports/esp8266/modesp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c index 724545202d8..4b621755094 100644 --- a/ports/esp8266/modesp.c +++ b/ports/esp8266/modesp.c @@ -234,7 +234,7 @@ static mp_obj_t esp_esf_free_bufs(mp_obj_t idx_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(esp_esf_free_bufs_obj, esp_esf_free_bufs); -#if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA +#if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_PERSISTENT_CODE_LOAD_NATIVE // We provide here a way of committing executable data to a region from // which it can be executed by the CPU. There are 2 such writable regions: @@ -358,7 +358,7 @@ static const mp_rom_map_elem_t esp_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_malloc), MP_ROM_PTR(&esp_malloc_obj) }, { MP_ROM_QSTR(MP_QSTR_free), MP_ROM_PTR(&esp_free_obj) }, { MP_ROM_QSTR(MP_QSTR_esf_free_bufs), MP_ROM_PTR(&esp_esf_free_bufs_obj) }, - #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA + #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_PERSISTENT_CODE_LOAD_NATIVE { MP_ROM_QSTR(MP_QSTR_set_native_code_location), MP_ROM_PTR(&esp_set_native_code_location_obj) }, #endif From 14152e7f49fffdfaa404bf33e00ad247c70e1a4a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 10:42:44 +0100 Subject: [PATCH 1604/2098] nrf/main: Allocate executable memory when needed. This commit lets the nRF port use the new MICROPY_PERSISTENT_CODE_LOAD_NATIVE configuration entry. The nRF port needs a special procedure to allocate memory used to hold executable native code. This functionality was gated behind the presence of a native emitter and thus its inclusion condition had to be updated. Signed-off-by: Alessandro Gatti --- ports/nrf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index e5d7828a82a..7278a646ab6 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -368,7 +368,7 @@ void MP_WEAK __assert_func(const char *file, int line, const char *func, const c __fatal_error("Assertion failed"); } -#if MICROPY_EMIT_MACHINE_CODE +#if MICROPY_EMIT_INLINE_THUMB || MICROPY_ENABLE_NATIVE_CODE void *nrf_native_code_commit(void *buf, unsigned int len, void *reloc) { (void)len; if (reloc) { From 7173ddee5f979a3c54be7905d6c00aac29a90fda Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 20 Dec 2025 17:03:18 +0100 Subject: [PATCH 1605/2098] tests/run-natmodtests.py: Explicitly open prelude file. This change lets the natmod test runner report status information on session end if a prelude script file is chosen. The script serialises its input data as part of the end of session reporting data, but since the prelude file is not a serialisable object serialisation would fail (it's a file handle as far as the argument container structure is aware). Now the file is explicitly open by the script rather than relying on argparse's file handle argument class wrapper. Signed-off-by: Alessandro Gatti --- tests/run-natmodtests.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index 50e8d127291..e35e580dc1d 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -133,13 +133,6 @@ def detect_architecture(target): def run_tests(target_truth, target, args, resolved_arch): - global injected_import_hook_code - - prelude = "" - if args.begin: - prelude = args.begin.read() - injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prelude) - test_results = [] for test_file in args.files: # Find supported test @@ -223,6 +216,8 @@ def run_tests(target_truth, target, args, resolved_arch): def main(): + global injected_import_hook_code + cmd_parser = argparse.ArgumentParser( description="Run dynamic-native-module tests under MicroPython", epilog=run_tests_module.test_instance_epilog, @@ -240,7 +235,7 @@ def main(): cmd_parser.add_argument( "-b", "--begin", - type=argparse.FileType("rt"), + metavar="PROLOGUE", default=None, help="prologue python file to execute before module import", ) @@ -253,6 +248,12 @@ def main(): cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() + prologue = "" + if args.begin: + with open(args.begin, "rt") as source: + prologue = source.read() + injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prologue) + target_truth = TargetSubprocess([CPYTHON3]) target = run_tests_module.get_test_instance( From 8fb848f3aacd0d2216e02954cff4e9e15b0915a2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 22 Dec 2025 08:34:41 +0100 Subject: [PATCH 1606/2098] mpy-cross/mpconfigport: Explicitly disable native code loading. This commit makes use of the new native code loading changes to provide a test bed for a target with the native compiler framework available but without no code loading capabilities. mpy-cross already fully disables native code loading, however those changes do not really propagate anywhere outside `py/persistentcode`. Recent changes in the code loading framework actually touch the core code in a few more places, but no default CI target is configured to run with the new native code loading defines switched off. Given that mpy-cross doesn't really need to load code anyway, it is a good target for setting up a configuration that goes a bit deeper when it comes to disabling code loading. Since mpy-cross is built several times during the CI process, it can be repurposed as an early warning system for issues related to these new changes. Normal operation should not be affected in any way. Signed-off-by: Alessandro Gatti --- mpy-cross/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index febd18cc2b0..9c23257260c 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -28,6 +28,7 @@ #define MICROPY_ALLOC_PATH_MAX (PATH_MAX) #define MICROPY_PERSISTENT_CODE_LOAD (0) +#define MICROPY_PERSISTENT_CODE_LOAD_NATIVE (0) #define MICROPY_PERSISTENT_CODE_SAVE (1) #ifndef MICROPY_PERSISTENT_CODE_SAVE_FILE From 984027b88e90323b9aa96ec793d5295a8873cd83 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 17 Nov 2025 03:30:47 +0100 Subject: [PATCH 1607/2098] tools/mpy_ld.py: Fix R_RISCV_GOT32_PCREL handling. This commit fixes the implementation of the R_RISCV_GOT32_PCREL RISC-V relocation type when linking native modules for RV32IMC. The previous implementation of R_RISCV_GOT32_PCREL ended up not being fully updated when the initial RV32 support was checked in. Said relocation was not emitted in the sample natmods that ship with the MicroPython source tree, and since they're the testbed for CI jobs that should check RV32 natmod support, this was not caught. On the other hand, nobody raised an issue about this problem yet, so hopefully it didn't cause much trouble. Signed-off-by: Alessandro Gatti --- tools/mpy_ld.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index af8450a8424..20418c67487 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -829,10 +829,10 @@ def do_relocation_data(env, text_addr, r): R_RISCV_SUB16: ("riscv_addsub", " Date: Tue, 30 Dec 2025 17:31:29 +0100 Subject: [PATCH 1608/2098] tools/mpy_ld.py: Fix handling of R_RISCV_TLSDESC_LOAD_LO12. This commit fixes handling of the R_RISCV_TLSDESC_LOAD_LO12 RISC-V object file relocation, fixing a couple of mistakes in its usage. The condition check for TLS relocations presence and their rejection when found in object files skipped checking for R_RISCV_TLSDESC_LOAD_LO12 relocations, which is part of the set of unsupported TLS relocations and thus needing an object file rejection. Interestingly, that relocation name constant was actually misspelled in the file - since it was skipped in the list of relocations being checked its wrong name did pass unnoticed until now. This is not a critical change as the linker will raise an error about an unknown relocation type rather than report a more descriptive message to the user, but it's nice to have nonetheless. Signed-off-by: Alessandro Gatti --- tools/mpy_ld.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 20418c67487..b3b28e453d5 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -121,7 +121,7 @@ R_RISCV_SET_ULEB128 = 60 R_RISCV_SUB_ULEB128 = 61 R_RISCV_TLSDESC_HI20 = 62 -R_RISCC_TLSDESC_LOAD_LO12 = 63 +R_RISCV_TLSDESC_LOAD_LO12 = 63 R_RISCV_TLSDESC_ADD_LO12 = 64 R_RISCV_TLSDESC_CALL = 65 @@ -682,6 +682,7 @@ def do_relocation_text(env, text_addr, r): elif env.arch.name == "EM_RISCV" and r_info_type in ( R_RISCV_TLS_GD_HI20, R_RISCV_TLSDESC_HI20, + R_RISCV_TLSDESC_LOAD_LO12, R_RISCV_TLSDESC_ADD_LO12, R_RISCV_TLSDESC_CALL, ): From e939d3ec76eba180deb36696a69cf5ea34c9876d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 17 Nov 2025 03:42:06 +0100 Subject: [PATCH 1609/2098] tools/mpy_ld.py: Add RV64 natmod support. This commit adds the ability to compile native modules for the RV64 platform, using "rv64imc" as its architecture name (eg. "make ARCH=rv64imc" should build a RV64 natmod). The rest of 64-bits relocations needed to build a native module are now implemented, and all sample native modules build without errors or warnings. The same Picolibc caveats on RV32 also apply on RV64, thus the documentation was updated accordingly. RV64 native modules are also built as part of the CI process, but not yet executed as the QEMU port is not yet able to load and run them. Signed-off-by: Alessandro Gatti --- docs/develop/natmod.rst | 11 ++++++----- examples/natmod/btree/Makefile | 2 +- examples/natmod/deflate/Makefile | 2 +- examples/natmod/features0/Makefile | 2 +- examples/natmod/features1/Makefile | 2 +- examples/natmod/features2/Makefile | 2 +- examples/natmod/features3/Makefile | 2 +- examples/natmod/features4/Makefile | 2 +- examples/natmod/framebuf/Makefile | 2 +- examples/natmod/heapq/Makefile | 2 +- examples/natmod/random/Makefile | 2 +- examples/natmod/re/Makefile | 2 +- py/dynruntime.mk | 21 ++++++++++++++++++++- tests/run-natmodtests.py | 1 + tools/ci.sh | 9 +++++++-- tools/mpy_ld.py | 15 ++++++++++++--- 16 files changed, 57 insertions(+), 22 deletions(-) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index 072d78b2076..142e475366f 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -41,6 +41,7 @@ options for the ``ARCH`` variable, see below): * ``xtensa`` (non-windowed, eg ESP8266) * ``xtensawin`` (windowed with window size 8, eg ESP32, ESP32S3) * ``rv32imc`` (RISC-V 32 bits with compressed instructions, eg ESP32C3, ESP32C6) +* ``rv64imc`` (RISC-V 64 bits with compressed instructions) When compiling and linking the native .mpy file the architecture must be chosen and the corresponding file can only be imported on that architecture. For more @@ -190,7 +191,7 @@ The file ``Makefile`` contains: # Source files (.c or .py) SRC = factorial.c - # Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc) + # Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Include to get the rules for compiling and linking the module @@ -232,15 +233,15 @@ Using Picolibc when building modules ------------------------------------ Using `Picolibc `_ as your C standard -library is not only supported, but in fact it is the default for the rv32imc -platform. However, there are a couple of things worth mentioning to make sure -you don't run into problems later when building code. +library is not only supported, but in fact it is the default for the rv32imc and +rv64imc platforms. However, there are a couple of things worth mentioning to make +sure you don't run into problems later when building code. Some pre-built Picolibc versions (for example, those provided by Ubuntu Linux as the ``picolibc-arm-none-eabi``, ``picolibc-riscv64-unknown-elf``, and ``picolibc-xtensa-lx106-elf`` packages) assume thread-local storage (TLS) is available at runtime, but unfortunately MicroPython modules do not support that -on some architectures (namely ``rv32imc``). This means that some +on some architectures (namely ``rv32imc`` and ``rv64imc``). This means that some functionalities provided by Picolibc will default to use TLS, returning an error either during compilation or during linking. diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index 1910c67c1fc..4ded62bafde 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -7,7 +7,7 @@ MOD = btree_$(ARCH) # Source files (.c or .py) SRC = btree_c.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH ?= x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx diff --git a/examples/natmod/deflate/Makefile b/examples/natmod/deflate/Makefile index 5823aa4d45b..0574bbaf412 100644 --- a/examples/natmod/deflate/Makefile +++ b/examples/natmod/deflate/Makefile @@ -7,7 +7,7 @@ MOD = deflate_$(ARCH) # Source files (.c or .py) SRC = deflate.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH ?= x64 ifeq ($(ARCH),armv6m) diff --git a/examples/natmod/features0/Makefile b/examples/natmod/features0/Makefile index fb01b8d031a..788d035eb8b 100644 --- a/examples/natmod/features0/Makefile +++ b/examples/natmod/features0/Makefile @@ -7,7 +7,7 @@ MOD = features0 # Source files (.c or .py) SRC = features0.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/features1/Makefile b/examples/natmod/features1/Makefile index 49040511020..47deeed8f2a 100644 --- a/examples/natmod/features1/Makefile +++ b/examples/natmod/features1/Makefile @@ -7,7 +7,7 @@ MOD = features1 # Source files (.c or .py) SRC = features1.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/features2/Makefile b/examples/natmod/features2/Makefile index 5ddb74087b7..efd096c4ede 100644 --- a/examples/natmod/features2/Makefile +++ b/examples/natmod/features2/Makefile @@ -7,7 +7,7 @@ MOD = features2 # Source files (.c or .py) SRC = main.c prod.c test.py -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Link with libm.a and libgcc.a from the toolchain diff --git a/examples/natmod/features3/Makefile b/examples/natmod/features3/Makefile index 3573f41caca..85d1a13421f 100644 --- a/examples/natmod/features3/Makefile +++ b/examples/natmod/features3/Makefile @@ -7,7 +7,7 @@ MOD = features3 # Source files (.c or .py) SRC = features3.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/features4/Makefile b/examples/natmod/features4/Makefile index 34fc3a7ef7b..4eb657b7220 100644 --- a/examples/natmod/features4/Makefile +++ b/examples/natmod/features4/Makefile @@ -7,7 +7,7 @@ MOD = features4 # Source files (.c or .py) SRC = features4.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 # Include to get the rules for compiling and linking the module diff --git a/examples/natmod/framebuf/Makefile b/examples/natmod/framebuf/Makefile index 35453c0bb4b..a86efef41f4 100644 --- a/examples/natmod/framebuf/Makefile +++ b/examples/natmod/framebuf/Makefile @@ -7,7 +7,7 @@ MOD = framebuf_$(ARCH) # Source files (.c or .py) SRC = framebuf.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH ?= x64 ifeq ($(ARCH),armv6m) diff --git a/examples/natmod/heapq/Makefile b/examples/natmod/heapq/Makefile index 61e2fc8fcc0..345359abb3c 100644 --- a/examples/natmod/heapq/Makefile +++ b/examples/natmod/heapq/Makefile @@ -7,7 +7,7 @@ MOD = heapq_$(ARCH) # Source files (.c or .py) SRC = heapq.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/random/Makefile b/examples/natmod/random/Makefile index bffbb32d60e..27d8ec935fe 100644 --- a/examples/natmod/random/Makefile +++ b/examples/natmod/random/Makefile @@ -7,7 +7,7 @@ MOD = random_$(ARCH) # Source files (.c or .py) SRC = random.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH ?= x64 ifeq ($(ARCH),xtensa) diff --git a/examples/natmod/re/Makefile b/examples/natmod/re/Makefile index 6535693dcb8..c5f05e64ab4 100644 --- a/examples/natmod/re/Makefile +++ b/examples/natmod/re/Makefile @@ -7,7 +7,7 @@ MOD = re_$(ARCH) # Source files (.c or .py) SRC = re.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) ARCH = x64 ifeq ($(ARCH),armv6m) diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 030728cfc9a..866d5fae7b1 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -106,7 +106,7 @@ else ifeq ($(ARCH),rv32imc) # rv32imc CROSS = riscv64-unknown-elf- CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax -# If Picolibc is available then select it explicitly. Ubuntu 22.04 ships its +# If Picolibc is available then select it explicitly. Ubuntu 24.04 ships its # bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default # is "nosys" so a value must be provided. To avoid having per-distro # workarounds, always select Picolibc if available. @@ -120,6 +120,25 @@ endif MICROPY_FLOAT_IMPL ?= none +else ifeq ($(ARCH),rv64imc) + +# rv64imc +CROSS = riscv64-unknown-elf- +CFLAGS_ARCH += -march=rv64imac -mabi=lp64 -mno-relax +# If Picolibc is available then select it explicitly. Ubuntu 24.04 ships its +# bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default +# is "nosys" so a value must be provided. To avoid having per-distro +# workarounds, always select Picolibc if available. +PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs) +ifneq ($(PICOLIBC_SPECS),picolibc.specs) +CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS) +USE_PICOLIBC := 1 +PICOLIBC_ARCH := rv64imac +PICOLIBC_ABI := lp64 +endif + +MICROPY_FLOAT_IMPL ?= none + else $(error architecture '$(ARCH)' not supported) endif diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index e35e580dc1d..cd6c643bf9b 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -40,6 +40,7 @@ "xtensa", "xtensawin", "rv32imc", + "rv64imc", ) ARCH_MAPPINGS = {"armv7em": "armv7m"} diff --git a/tools/ci.sh b/tools/ci.sh index 60e870ce65b..4ade31160b5 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -378,6 +378,8 @@ function ci_qemu_setup_rv64 { ci_gcc_riscv_setup sudo apt-get update sudo apt-get install qemu-system + sudo pip3 install pyelftools + sudo pip3 install ar qemu-system-riscv64 --version } @@ -436,6 +438,9 @@ function ci_qemu_build_rv64 { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 submodules make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 test + + # Test building native .mpy with rv64imc architecture. + ci_native_mpy_modules_build rv64imc } ######################################################################################## @@ -669,9 +674,9 @@ function ci_native_mpy_modules_build { make -C examples/natmod/$natmod ARCH=$arch done - # features2 requires soft-float on rv32imc and xtensa. + # features2 requires soft-float on rv32imc, rv64imc, and xtensa. make -C examples/natmod/features2 ARCH=$arch clean - if [ $arch = "rv32imc" ] || [ $arch = "xtensa" ]; then + if [ $arch = "rv32imc" ] || [ $arch = "rv64imc" ] || [ $arch = "xtensa" ]; then make -C examples/natmod/features2 ARCH=$arch MICROPY_FLOAT_IMPL=float else make -C examples/natmod/features2 ARCH=$arch diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index b3b28e453d5..b363c6a25f7 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -49,6 +49,7 @@ MP_NATIVE_ARCH_XTENSA = 9 MP_NATIVE_ARCH_XTENSAWIN = 10 MP_NATIVE_ARCH_RV32IMC = 11 +MP_NATIVE_ARCH_RV64IMC = 12 MP_PERSISTENT_OBJ_STR = 5 MP_SCOPE_FLAG_VIPERRELOC = 0x10 MP_SCOPE_FLAG_VIPERRODATA = 0x20 @@ -62,6 +63,7 @@ R_X86_64_64 = 1 R_XTENSA_32 = 1 R_386_PC32 = 2 +R_RISCV_64 = 2 R_X86_64_PC32 = 2 R_ARM_ABS32 = 2 R_386_GOT32 = 3 @@ -175,7 +177,7 @@ def asm_jump_xtensa(entry): return struct.pack("> 8) -def asm_jump_rv32(entry): +def asm_jump_riscv(entry): # This could be 6 bytes shorter, but the code currently cannot # support a trampoline with varying length depending on the offset. @@ -261,7 +263,14 @@ def __init__(self, name, mpy_feature, word_size, arch_got, asm_jump, *, separate MP_NATIVE_ARCH_RV32IMC << 2, 4, (R_RISCV_32, R_RISCV_GOT_HI20, R_RISCV_GOT32_PCREL), - asm_jump_rv32, + asm_jump_riscv, + ), + "rv64imc": ArchData( + "EM_RISCV", + MP_NATIVE_ARCH_RV64IMC << 2, + 8, + (R_RISCV_64, R_RISCV_GOT_HI20, R_RISCV_GOT32_PCREL), + asm_jump_riscv, ), } @@ -779,7 +788,7 @@ def do_relocation_data(env, text_addr, r): or env.arch.name == "EM_XTENSA" and r_info_type == R_XTENSA_32 or env.arch.name == "EM_RISCV" - and r_info_type == R_RISCV_32 + and r_info_type in (R_RISCV_32, R_RISCV_64) ): # Relocation in data.rel.ro to internal/external symbol if env.arch.word_size == 4: From 9af103300fb05da0bbd52ff10e36552ebbb23d27 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 17 Nov 2025 03:57:05 +0100 Subject: [PATCH 1610/2098] qemu: Enable loading natmods on RV64. This commit lets the QEMU port's VIRT_RV64 board load and run native modules, via the appropriate configuration changes in mpconfigport.h. Now the CI test job for the QEMU/RV64 port can also run natmods and see whether they actually work, so the CI tasks script has been updated to bring RV64 to parity with RV32 as far as CI checks go. Documentation was also updated, since now all supported boards in the QEMU port should be able to run natmod tests. Signed-off-by: Alessandro Gatti --- ports/qemu/README.md | 3 +-- ports/qemu/mpconfigport.h | 8 +++++++- tools/ci.sh | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ports/qemu/README.md b/ports/qemu/README.md index 0f53c000082..e6c16e3662d 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -142,8 +142,7 @@ tests against the serial device, for example: $ ./run-tests.py -t /dev/pts/1 Selected native modules that come as examples with the MicroPython source tree -can also be tested with this command (this is currently not supported for the -`VIRT_RV64` board): +can also be tested with this command: $ make test_natmod diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index dcaf6a5c51e..522c5926328 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -39,10 +39,16 @@ #define MICROPY_EMIT_INLINE_THUMB (1) #endif #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) -#elif defined(__riscv) && (__riscv_xlen == 32) +#elif defined(__riscv) +#if (__riscv_xlen == 32) #define MICROPY_EMIT_RV32 (1) #define MICROPY_EMIT_RV32_ZBA (1) #define MICROPY_EMIT_INLINE_RV32 (1) +#elif (__riscv_xlen == 64) +#define MICROPY_PERSISTENT_CODE_LOAD_NATIVE (1) +#else +#error "Unsupported RISC-V platform!" +#endif #endif #define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) diff --git a/tools/ci.sh b/tools/ci.sh index 4ade31160b5..1856733f09e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -439,8 +439,9 @@ function ci_qemu_build_rv64 { make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 submodules make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 test - # Test building native .mpy with rv64imc architecture. + # Test building and running native .mpy with rv64imc architecture. ci_native_mpy_modules_build rv64imc + make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 test_natmod } ######################################################################################## From d44685e103784a123a0f18aa54cfb6ea41987ad9 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 1 Jan 2026 11:16:43 +0100 Subject: [PATCH 1611/2098] unix/mpconfigport: Enable natmod loading for RISC-V builds. This commit lets the Unix port load native modules when built for both RV32 and RV64. Since RV64 doesn't have a native emitter, the new native code loading configuration settings have been used to allow such a feature. RV32 is fully supported by the native emitter framework but the emitter wasn't enabled for Unix builds (and therefore no natmod support). RV64 builds have been tested on QEMU and on a MilkV-Duo board, and whilst RV32 isn't really tested, its native code loading functionality is routinely tested both on bare-metal QEMU and on microcontroller boards. Since the code for RV64 under Unix and RV32 is essentially the same, having RV64 working is a strong indicator that RV32 will work too. Signed-off-by: Alessandro Gatti --- ports/unix/mpconfigport.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 854da1dbd42..e290935bca9 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -72,6 +72,12 @@ #if !defined(MICROPY_EMIT_ARM) && defined(__arm__) && !defined(__thumb2__) #define MICROPY_EMIT_ARM (1) #endif +#if !defined(MICROPY_EMIT_RV32) && defined(__riscv) && __riscv_xlen == 32 + #define MICROPY_EMIT_RV32 (1) +#endif +#if !defined(MICROPY_PERSISTENT_CODE_LOAD_NATIVE) && defined(__riscv) && __riscv_xlen == 64 + #define MICROPY_PERSISTENT_CODE_LOAD_NATIVE (1) +#endif // Cannot include , as it may lead to symbol name clashes #if _FILE_OFFSET_BITS == 64 && !defined(__LP64__) @@ -93,7 +99,7 @@ typedef long mp_off_t; // Always enable GC. #define MICROPY_ENABLE_GC (1) -#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && (__riscv_xlen == 64))) +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && __riscv_xlen <= 64)) // Fall back to setjmp() implementation for discovery of GC pointers in registers. #define MICROPY_GCREGS_SETJMP (1) #endif From d871c970c0402a26dcec04477136914e3d0ee782 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 1 Jan 2026 11:28:52 +0100 Subject: [PATCH 1612/2098] tools/ci.sh: Run natmod tests as part of CI for Unix/RV64. This commit updates the test procedure for the Unix port targeting the RV64 platform to also run the battery of native modules test that is part of the QEMU port. Unfortunately this required a few changes to the CI setup since the Unix port was still using an older version of Ubuntu LTS than the RISC-V natmods build infrastructure expects. Updating the OS version just for the RV64 Unix target brought a couple of issues when building the code (an extra package is now needed to let FFI build) and running tests (QEMU binfmt support requires a new setup). Signed-off-by: Alessandro Gatti --- .github/workflows/ports_unix.yml | 3 +-- tools/ci.sh | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 19d6dcf8565..6ef6c28d331 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -277,8 +277,7 @@ jobs: run: tests/run-tests.py --print-failures qemu_riscv64: - # ubuntu-22.04 is needed for older libffi. - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install packages diff --git a/tools/ci.sh b/tools/ci.sh index 1856733f09e..ec4b57a8f56 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -946,26 +946,34 @@ function ci_unix_qemu_arm_run_tests { } function ci_unix_qemu_riscv64_setup { + ci_gcc_riscv_setup sudo apt-get update - sudo apt-get install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu - sudo apt-get install qemu-user - qemu-riscv64 --version - sudo mkdir /etc/qemu-binfmt - sudo ln -s /usr/riscv64-linux-gnu/ /etc/qemu-binfmt/riscv64 + sudo apt-get install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libltdl-dev + sudo pip3 install pyelftools + sudo pip3 install ar + sudo apt-get install qemu-user-static + qemu-riscv64-static --version + sudo mkdir -p /usr/gnemul + sudo ln -s /usr/riscv64-linux-gnu /usr/gnemul/qemu-riscv64 } function ci_unix_qemu_riscv64_build { ci_unix_build_helper "${CI_UNIX_OPTS_QEMU_RISCV64[@]}" ci_unix_build_ffi_lib_helper riscv64-linux-gnu-gcc + ci_native_mpy_modules_build rv64imc } function ci_unix_qemu_riscv64_run_tests { # Issues with RISCV-64 tests: - # - thread/stress_aes.py takes around 140 seconds + # - misc/sys_settrace_features.py doesn't work with CPython 3.12 + # - thread/stress_aes.py takes around 180 seconds # - thread/stress_recurse.py is flaky # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') + pushd tests + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=200 ./run-tests.py --exclude 'misc/sys_settrace_features.py|thread/stress_recurse.py|thread/thread_gc1.py' + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-natmodtests.py extmod/btree*.py extmod/deflate*.py extmod/framebuf*.py extmod/heapq*.py extmod/random_basic*.py extmod/re*.py + popd } function ci_unix_repr_b_build { From b857a9131e5c1b82e130af75ae2640e12c89fa39 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 21 Nov 2024 17:03:37 +0100 Subject: [PATCH 1613/2098] mimxrt: Add support for VfsRom filesystem. The default size for the ROM partition is 256k, and it is placed in flash between the text and writable vfs sections. Its size can be changed in `mpconfigboard.mk` by defining `MICROPY_HW_ROMFS_BYTES` to a different value, but it must not be smaller than a single sector. The MIMXRT1170_EVK and SEEED ARCH MIX ROMFS boards use a larger size of 512kB. ROMFS support is disabled for MIMXRT1050_EVKB due to issues with Hyperflash. The extents of the ROM partition are defined by the linker-level symbols `_micropy_hw_romfs_part0_start` and `_micropy_hw_romfs_part0_size`, following existing ports. Signed-off-by: Damien George Signed-off-by: robert-hh --- ports/mimxrt/Makefile | 10 +++- ports/mimxrt/boards/MIMXRT1011.ld | 4 +- ports/mimxrt/boards/MIMXRT1015.ld | 4 +- ports/mimxrt/boards/MIMXRT1021.ld | 4 +- .../boards/MIMXRT1050_EVK/mpconfigboard.mk | 1 + ports/mimxrt/boards/MIMXRT1052.ld | 4 +- ports/mimxrt/boards/MIMXRT1062.ld | 4 +- ports/mimxrt/boards/MIMXRT1064.ld | 4 +- .../boards/MIMXRT1170_EVK/mpconfigboard.mk | 1 + ports/mimxrt/boards/MIMXRT1176.ld | 4 +- .../boards/SEEED_ARCH_MIX/mpconfigboard.mk | 1 + ports/mimxrt/mimxrt_flash.c | 49 +++++++++++++++++++ ports/mimxrt/mpconfigport.h | 3 ++ 13 files changed, 84 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index fea7e56da22..52003ed4a4d 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -311,6 +311,11 @@ ifeq ($(USE_UF2_BOOTLOADER),1) CFLAGS += -DMICROPY_MACHINE_UF2_BOOTLOADER=1 endif +# Set the default size of the VfsRom file system. Can be changed at board level +ifeq ($(MICROPY_HW_ROMFS_BYTES),) + MICROPY_HW_ROMFS_BYTES ?= 0x40000 +endif + # Add sources for respective board flash type # Add hal/flexspi_nor_flash.c or hal/flashspi_hyper_flash.c respectively SRC_HAL_C += hal/flexspi_$(subst qspi_,,$(FLEXSPI_FLASH_TYPE)).c @@ -494,9 +499,10 @@ LDFLAGS += \ # LDDEFINES are used for link time adaptation of linker scripts, utilizing # the C preprocessor. Therefore keep LDDEFINES separated from LDFLAGS! -LDDEFINES = \ +LDDEFINES += \ -DMICROPY_HW_FLASH_BASE=$(MICROPY_HW_FLASH_BASE) \ - -DMICROPY_HW_FLASH_SIZE=$(MICROPY_HW_FLASH_SIZE) + -DMICROPY_HW_FLASH_SIZE=$(MICROPY_HW_FLASH_SIZE) \ + -DMICROPY_HW_ROMFS_BYTES=$(MICROPY_HW_ROMFS_BYTES) ifdef MICROPY_HW_FLASH_RESERVED LDDEFINES += -DMICROPY_HW_FLASH_RESERVED=$(MICROPY_HW_FLASH_RESERVED) diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index 0e961a49433..dba02f0ec39 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -18,8 +18,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00008000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1015.ld b/ports/mimxrt/boards/MIMXRT1015.ld index 58b88e0fe30..70959685fa0 100644 --- a/ports/mimxrt/boards/MIMXRT1015.ld +++ b/ports/mimxrt/boards/MIMXRT1015.ld @@ -18,8 +18,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00008000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1021.ld b/ports/mimxrt/boards/MIMXRT1021.ld index 78add04c0c2..78e8e22a0ff 100644 --- a/ports/mimxrt/boards/MIMXRT1021.ld +++ b/ports/mimxrt/boards/MIMXRT1021.ld @@ -18,8 +18,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00010000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk index 8b048c85eaa..3631df4b0d4 100644 --- a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.mk @@ -10,6 +10,7 @@ MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz MICROPY_HW_FLASH_QE_CMD = 0x01 MICROPY_HW_FLASH_QE_ARG = 0x40 +MICROPY_HW_ROMFS_BYTES = 0 # Disabled for a board with Hyperflash MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 diff --git a/ports/mimxrt/boards/MIMXRT1052.ld b/ports/mimxrt/boards/MIMXRT1052.ld index ea034d713e2..0370d805acc 100644 --- a/ports/mimxrt/boards/MIMXRT1052.ld +++ b/ports/mimxrt/boards/MIMXRT1052.ld @@ -20,8 +20,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00200000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00020000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1062.ld b/ports/mimxrt/boards/MIMXRT1062.ld index 3d7e6d06341..686d1bdc845 100644 --- a/ports/mimxrt/boards/MIMXRT1062.ld +++ b/ports/mimxrt/boards/MIMXRT1062.ld @@ -20,8 +20,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00020000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1064.ld b/ports/mimxrt/boards/MIMXRT1064.ld index 7c35cb60c75..16f6f19b5ad 100644 --- a/ports/mimxrt/boards/MIMXRT1064.ld +++ b/ports/mimxrt/boards/MIMXRT1064.ld @@ -14,8 +14,10 @@ interrupts_start = flash_start + 0x0000C000; interrupts_size = 0x00000400; text_start = flash_start + 0x0000C400; vfs_start = flash_start + 0x00100000; -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00020000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk index ef4ab683a21..f534406fcde 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk @@ -9,6 +9,7 @@ MICROPY_HW_FLASH_RESERVED ?= 0x100000 # 1MB CM4 Code address space MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz MICROPY_HW_FLASH_QE_CMD = 0x31 MICROPY_HW_FLASH_QE_ARG = 0x02 +MICROPY_HW_ROMFS_BYTES = 0x80000 # 512kB MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x4000000 # 64MB diff --git a/ports/mimxrt/boards/MIMXRT1176.ld b/ports/mimxrt/boards/MIMXRT1176.ld index 4d114ef96fa..57243b68da7 100644 --- a/ports/mimxrt/boards/MIMXRT1176.ld +++ b/ports/mimxrt/boards/MIMXRT1176.ld @@ -30,8 +30,10 @@ m_core1_image_start = vfs_start - 0x00040000; m_core1_image_size = 0x00040000; #endif -text_size = ((vfs_start) - (text_start)); vfs_size = ((flash_end) - (vfs_start)); +_micropy_hw_romfs_part0_start = ((vfs_start) - MICROPY_HW_ROMFS_BYTES); +_micropy_hw_romfs_part0_size = MICROPY_HW_ROMFS_BYTES; +text_size = ((_micropy_hw_romfs_part0_start) - (text_start)); itcm_start = 0x00000000; itcm_size = 0x00020000; dtcm_start = 0x20000000; diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk index 1bc38ccbad7..1cdfe8fe805 100644 --- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk +++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.mk @@ -7,6 +7,7 @@ MICROPY_HW_FLASH_SIZE = 0x800000 # 8MB MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_133MHz MICROPY_HW_FLASH_QE_CMD = 0x01 MICROPY_HW_FLASH_QE_ARG = 0x40 +MICROPY_HW_ROMFS_BYTES = 0x80000 # 512kB MICROPY_HW_SDRAM_AVAIL = 1 MICROPY_HW_SDRAM_SIZE = 0x2000000 # 32MB diff --git a/ports/mimxrt/mimxrt_flash.c b/ports/mimxrt/mimxrt_flash.c index fdd48e280bc..3819d8b9043 100644 --- a/ports/mimxrt/mimxrt_flash.c +++ b/ports/mimxrt/mimxrt_flash.c @@ -5,6 +5,7 @@ * * Copyright (c) 2020-2021 Damien P. George * Copyright (c) 2021-2023 Philipp Ebensberger + * Copyright (c) 2021-2024 Robert Hammelrath * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +30,7 @@ #include "py/runtime.h" #include "extmod/vfs.h" +#include "py/mperrno.h" #include "modmimxrt.h" #include "flash.h" #include BOARD_FLASH_OPS_HEADER_H @@ -60,6 +62,19 @@ static mp_obj_t mimxrt_flash_make_new(const mp_obj_type_t *type, size_t n_args, return MP_OBJ_FROM_PTR(&mimxrt_flash_obj); } +static mp_int_t mimxrt_flash_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mimxrt_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)((uintptr_t)&__flash_start + self->flash_base); + bufinfo->len = self->flash_size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Write unsupported. + return 1; + } +} + // readblocks(block_num, buf, [offset]) // read size of buffer number of bytes from block (with offset) into buffer static mp_obj_t mimxrt_flash_readblocks(size_t n_args, const mp_obj_t *args) { @@ -151,5 +166,39 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_QSTR_Flash, MP_TYPE_FLAG_NONE, make_new, mimxrt_flash_make_new, + buffer, mimxrt_flash_get_buffer, locals_dict, &mimxrt_flash_locals_dict ); + +#if MICROPY_VFS_ROM + +extern uint8_t _micropy_hw_romfs_part0_start; +extern uint8_t _micropy_hw_romfs_part0_size; + +// Put VfsRom file system between the code space and the VFS file system. +// The size is defined in Makefile(s) as linker symbol MICROPY_HW_ROMFS_BYTES. +// For machine.mem32 the absolute address is required, for the flash functions +// erase and write the offset to the flash start address. +#define MICROPY_HW_ROMFS_BASE ((uintptr_t)&_micropy_hw_romfs_part0_start - (uintptr_t)&__flash_start) +#define MICROPY_HW_ROMFS_BYTES ((uintptr_t)&_micropy_hw_romfs_part0_size) + +static mimxrt_flash_obj_t mimxrt_flash_romfs_obj = { + .base = { &mimxrt_flash_type }, +}; + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + if (MICROPY_HW_ROMFS_BYTES <= 0) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + mimxrt_flash_romfs_obj.flash_base = MICROPY_HW_ROMFS_BASE; + mimxrt_flash_romfs_obj.flash_size = MICROPY_HW_ROMFS_BYTES; + return MP_OBJ_FROM_PTR(&mimxrt_flash_romfs_obj); + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} +#endif // MICROPY_VFS_ROM diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 0a623a63187..45316b904b6 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -60,6 +60,9 @@ uint32_t trng_random_u32(void); #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_VFS (1) +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (1) +#endif // Control over Python builtins #define MICROPY_PY_BUILTINS_HELP_TEXT mimxrt_help_text From c07b178dbda65b86a72a81dfd1106c24a2f252d6 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 19 Nov 2024 08:48:29 +0100 Subject: [PATCH 1614/2098] samd: Add support for VfsRom filesystem for SAMD21 and SAMD51. The flash driver is update to support the new `mp_vfs_rom_ioctl()` function, and the buffer protocol is added to `samd_flash_type` (it is needed for VfsRom on devices without external flash). For SAMD21, only boards with external SPI flash have VfsRom enabled, due to size constraints. For such boards, the VfsRom filesystem has a size of 12k and is placed at the upper end of the flash. The `onewire`, `ds18x20` and `dht` drivers have been removed from frozen bytecode as they can be placed into the VfsRom files when needed. For SAMD51, the VfsRom filesystem has a default size of 64k for SAMD51x19 and 256K for SAMD51x20. It is placed at the upper end of the flash. For boards with external SPI flash, the code size is reduced from 496K to 432K. If that is not sufficient for some boards or configurations, it can be changed for each board or board variant. Tested with ADAFRUIT_ITSYBITSY_M0_EXPRESS, ADAFRUIT_ITSYBITSY_M4_EXPRESS, SPARKFUN_SAMD51_THING_PLUS, SEEED_XIAO_SAMD21, SAMD_GENERIC_D51X19, and SAMD_GENERIC_D51X20. Signed-off-by: Damien George Signed-off-by: robert-hh --- ports/samd/Makefile | 6 +- .../mpconfigboard.mk | 2 +- .../mpconfigboard.mk | 5 +- .../mpconfigboard.mk | 2 +- .../mpconfigboard.mk | 5 +- .../mpconfigboard.mk | 5 +- .../mpconfigvariant_SPIFLASH.mk | 2 +- ports/samd/boards/MINISAM_M4/mpconfigboard.mk | 5 +- .../SAMD21_XPLAINED_PRO/mpconfigboard.mk | 2 +- .../SAMD_GENERIC_D51X19/mpconfigboard.mk | 4 +- .../SAMD_GENERIC_D51X20/mpconfigboard.mk | 3 +- .../SEEED_WIO_TERMINAL/mpconfigboard.mk | 5 +- .../SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk | 2 +- .../mpconfigboard.mk | 5 +- ports/samd/boards/samd21x18a.ld | 17 ++- ports/samd/boards/samd51x19a.ld | 17 ++- ports/samd/boards/samd51x20a.ld | 17 ++- ports/samd/mcu/samd21/manifest.py | 3 - ports/samd/mcu/samd21/mpconfigmcu.h | 5 - ports/samd/mcu/samd21/mpconfigmcu.mk | 7 +- ports/samd/mcu/samd51/manifest.py | 3 - ports/samd/mcu/samd51/mpconfigmcu.h | 8 +- ports/samd/mcu/samd51/mpconfigmcu.mk | 1 + ports/samd/mpconfigport.h | 3 + ports/samd/samd_flash.c | 121 +++++++++++++++--- ports/samd/samd_soc.c | 4 + 26 files changed, 197 insertions(+), 62 deletions(-) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index bec530d803f..3344be75026 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -90,7 +90,7 @@ CFLAGS += $(CFLAGS_EXTRA) CFLAGS += -DMICROPY_HW_CODESIZE=$(strip $(subst K,' ', $(MICROPY_HW_CODESIZE))) LDFLAGS += -nostdlib $(addprefix -T,$(LD_FILES)) -Map=$@.map --cref -LDFLAGS += --defsym=_codesize=$(MICROPY_HW_CODESIZE) +LDFLAGS += --defsym=_codesize=$(MICROPY_HW_CODESIZE) --defsym=_micropy_hw_romfs_part0_size=$(MICROPY_HW_ROMFS_BYTES) LIBS += $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) @@ -103,6 +103,10 @@ CFLAGS += -Os -DNDEBUG LDFLAGS += --gc-sections --print-memory-usage CFLAGS += -fdata-sections -ffunction-sections endif +# Disable VFSROM support if the size was set to 0 +ifeq ($(MICROPY_HW_ROMFS_BYTES),0) + CFLAGS += -DMICROPY_VFS_ROM=0 +endif # Flags for optional C++ source code CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk index e2895c7c0c2..6e1c7375a2e 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk @@ -5,4 +5,4 @@ TEXT0 = 0x2000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 248K +MICROPY_HW_CODESIZE ?= 236K diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk index 6ec2d43dedb..dbce935140c 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk @@ -5,4 +5,7 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 496K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K +MICROPY_HW_CODESIZE ?= 432K diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk index e2895c7c0c2..6e1c7375a2e 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk @@ -5,4 +5,4 @@ TEXT0 = 0x2000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 248K +MICROPY_HW_CODESIZE ?= 236K diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk index 740154a6d68..fb6ec0eb279 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk @@ -5,4 +5,7 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 496K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K +MICROPY_HW_CODESIZE ?= 432K diff --git a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk index 43ca5a59cc6..9ab91a8521e 100644 --- a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk @@ -7,4 +7,7 @@ TEXT0 = 0x4000 MICROPY_PY_NETWORK ?= 1 MICROPY_PY_NETWORK_NINAW10 ?= 1 -MICROPY_HW_CODESIZE ?= 496K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K +MICROPY_HW_CODESIZE ?= 432K diff --git a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk index 69537d5bf3c..b5d11f7e899 100644 --- a/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk +++ b/ports/samd/boards/ADAFRUIT_QTPY_SAMD21/mpconfigvariant_SPIFLASH.mk @@ -1,2 +1,2 @@ CFLAGS += -DMICROPY_HW_SPIFLASH=1 -MICROPY_HW_CODESIZE ?= 232K +MICROPY_HW_CODESIZE ?= 236K diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.mk b/ports/samd/boards/MINISAM_M4/mpconfigboard.mk index 54948627d2b..a1b97af0564 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.mk +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.mk @@ -6,4 +6,7 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 496K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K +MICROPY_HW_CODESIZE ?= 432K diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk index cc43c22cea5..a396c543a42 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk @@ -3,4 +3,4 @@ CMSIS_MCU = SAMD21J18A LD_FILES = boards/samd21x18a.ld sections.ld TEXT0 = 0x2000 -MICROPY_HW_CODESIZE ?= 248K +MICROPY_HW_CODESIZE ?= 236K diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk index 1a20643214f..5c3664267de 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/mpconfigboard.mk @@ -6,6 +6,6 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings # The size of a MCU flash filesystem will be -# 496k - MICROPY_HW_CODESIZE - MICROPY_HW_VFSROMSIZE -# The default for MICROPY_HW_VFSROMSIZE is 64K +# 496k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K MICROPY_HW_CODESIZE ?= 368K diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk index b240c2587f8..2e000225d54 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk @@ -7,5 +7,6 @@ TEXT0 = 0x4000 # MicroPython settings # The size of a MCU flash filesystem will be # 1008k - MICROPY_HW_CODESIZE -# The default for MICROPY_HW_VFSROMSIZE is 64K +# The default for MICROPY_HW_ROMFS_BYTES is 64K MICROPY_HW_CODESIZE ?= 752K +MICROPY_HW_ROMFS_BYTES ?= 256K diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk index 7bf70ac6695..bb14efe7e80 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk @@ -5,4 +5,7 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 496K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +# The default for MICROPY_HW_ROMFS_BYTES is 64K +MICROPY_HW_CODESIZE ?= 432K diff --git a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk index 6ea327a650d..6e1c7375a2e 100644 --- a/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk +++ b/ports/samd/boards/SPARKFUN_REDBOARD_TURBO/mpconfigboard.mk @@ -5,4 +5,4 @@ TEXT0 = 0x2000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 232K +MICROPY_HW_CODESIZE ?= 236K diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk index 263e5826944..2ee0b2cca5e 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk @@ -5,4 +5,7 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings -MICROPY_HW_CODESIZE ?= 1008K +# The size of a MCU flash filesystem will be +# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_ROMFS_BYTES +MICROPY_HW_CODESIZE ?= 752K +MICROPY_HW_ROMFS_BYTES ?= 256K diff --git a/ports/samd/boards/samd21x18a.ld b/ports/samd/boards/samd21x18a.ld index 3ab051569fb..5bb031d9c07 100644 --- a/ports/samd/boards/samd21x18a.ld +++ b/ports/samd/boards/samd21x18a.ld @@ -3,8 +3,8 @@ */ /* -_codesize is defined in mpconfigmcu.mk or mpconfigboard.mk as -MICROPY_HW_CODESIZE and is set in Makefile +_codesize and _micropy_hw_romfs_part0_size are defined in mpconfigmcu.mk or mpconfigboard.mk +as MICROPY_HW_CODESIZE and MICROPY_HW_ROMFS_BYTES and are set in Makefile. */ _flashsize = 256K; /* The physical flash size */ @@ -21,8 +21,17 @@ MEMORY _estack = ORIGIN(RAM) + LENGTH(RAM) - 8; _sstack = _estack - 8K; -_oflash_fs = ORIGIN(FLASH) + _codesize; -_sflash_fs = _flashsize - _codesize - _bootloader; +/* +The VfsROM file system is placed at the end of the flash. +For device with SPI flash the number for _sflash_fs might be 0 and +the origin beyond the end of the flash. That does not matter since +these devices do not use the MCU flash file system. +*/ + +_oflash_fs = ORIGIN(FLASH) + _codesize + _micropy_hw_romfs_part0_size; +_sflash_fs = _flashsize - _codesize - _bootloader - _micropy_hw_romfs_part0_size; + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH) + _codesize; _sheap = _ebss; _eheap = _sstack; diff --git a/ports/samd/boards/samd51x19a.ld b/ports/samd/boards/samd51x19a.ld index 30bc8e33281..77ae8656d4a 100644 --- a/ports/samd/boards/samd51x19a.ld +++ b/ports/samd/boards/samd51x19a.ld @@ -3,8 +3,8 @@ */ /* -_codesize is defined in mpconfigmcu.mk or mpconfigboard.mk as -MICROPY_HW_CODESIZE and is set in Makefile +_codesize and _micropy_hw_romfs_part0_size are defined in mpconfigmcu.mk or mpconfigboard.mk +as MICROPY_HW_CODESIZE and MICROPY_HW_ROMFS_BYTES and are set in Makefile. */ _flashsize = 512K; /* The physical flash size */ @@ -21,8 +21,17 @@ MEMORY _estack = ORIGIN(RAM) + LENGTH(RAM) - 8; _sstack = _estack - 16K; -_oflash_fs = ORIGIN(FLASH) + _codesize; -_sflash_fs = _flashsize - _codesize - _bootloader; +/* +The VfsROM file system is placed at the end of the flash. +For device with SPI flash the number for _sflash_fs might be 0 and +the origin beyond the end of the flash. That does not matter since +these devices do not use the MCU flash file system. +*/ + +_oflash_fs = ORIGIN(FLASH) + _codesize + _micropy_hw_romfs_part0_size; +_sflash_fs = _flashsize - _codesize - _bootloader - _micropy_hw_romfs_part0_size; + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH) + _codesize; _sheap = _ebss; _eheap = _sstack; diff --git a/ports/samd/boards/samd51x20a.ld b/ports/samd/boards/samd51x20a.ld index 472ab316c6f..595042128d5 100644 --- a/ports/samd/boards/samd51x20a.ld +++ b/ports/samd/boards/samd51x20a.ld @@ -3,8 +3,8 @@ */ /* -_codesize is defined in mpconfigmcu.mk or mpconfigboard.mk as -MICROPY_HW_CODESIZE and is set in Makefile +_codesize and _micropy_hw_romfs_part0_size are defined in mpconfigmcu.mk or mpconfigboard.mk +as MICROPY_HW_CODESIZE and MICROPY_HW_ROMFS_BYTES and are set in Makefile. */ _flashsize = 1024K; /* The physical flash size */ @@ -21,8 +21,17 @@ MEMORY _estack = ORIGIN(RAM) + LENGTH(RAM) - 8; _sstack = _estack - 16K; -_oflash_fs = ORIGIN(FLASH) + _codesize; -_sflash_fs = _flashsize - _codesize - _bootloader; +/* +The VfsROM file system is placed at the end of the flash. +For device with SPI flash the number for _sflash_fs might be 0 and +the origin beyond the end of the flash. That does not matter since +these devices do not use the MCU flash file system. +*/ + +_oflash_fs = ORIGIN(FLASH) + _codesize + _micropy_hw_romfs_part0_size; +_sflash_fs = _flashsize - _codesize - _bootloader - _micropy_hw_romfs_part0_size; + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH) + _codesize; _sheap = _ebss; _eheap = _sstack; diff --git a/ports/samd/mcu/samd21/manifest.py b/ports/samd/mcu/samd21/manifest.py index 2a19a843f8a..8ad1e38ba09 100644 --- a/ports/samd/mcu/samd21/manifest.py +++ b/ports/samd/mcu/samd21/manifest.py @@ -1,5 +1,2 @@ include("$(PORT_DIR)/boards/manifest.py") include("$(MPY_DIR)/extmod/asyncio") -require("onewire") -require("ds18x20") -require("dht") diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index a29d5c0a04d..a757893943b 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -2,11 +2,6 @@ #include "samd21.h" #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) -#if MICROPY_HW_CODESIZE == 248 -#define SAMD21_EXTRA_FEATURES 1 -#else -#define SAMD21_EXTRA_FEATURES 0 -#endif // MicroPython emitters #define MICROPY_EMIT_THUMB (SAMD21_EXTRA_FEATURES) diff --git a/ports/samd/mcu/samd21/mpconfigmcu.mk b/ports/samd/mcu/samd21/mpconfigmcu.mk index 34209775c25..b809a47679a 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.mk +++ b/ports/samd/mcu/samd21/mpconfigmcu.mk @@ -6,8 +6,13 @@ MPY_CROSS_MCU_ARCH = armv6m MICROPY_HW_CODESIZE ?= 184K -ifeq ($(MICROPY_HW_CODESIZE), 248K) +ifeq ($(MICROPY_HW_CODESIZE), 236K) FROZEN_MANIFEST ?= mcu/$(MCU_SERIES_LOWER)/manifest.py +MICROPY_HW_ROMFS_BYTES ?= 12K +CFLAGS_MCU += -DSAMD21_EXTRA_FEATURES=1 +else +MICROPY_HW_ROMFS_BYTES ?= 0 +CFLAGS_MCU += -DSAMD21_EXTRA_FEATURES=0 endif MICROPY_VFS_LFS1 ?= 1 diff --git a/ports/samd/mcu/samd51/manifest.py b/ports/samd/mcu/samd51/manifest.py index 2a19a843f8a..8ad1e38ba09 100644 --- a/ports/samd/mcu/samd51/manifest.py +++ b/ports/samd/mcu/samd51/manifest.py @@ -1,5 +1,2 @@ include("$(PORT_DIR)/boards/manifest.py") include("$(MPY_DIR)/extmod/asyncio") -require("onewire") -require("ds18x20") -require("dht") diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index a1ff208eb59..974a40f7aa7 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -21,10 +21,10 @@ unsigned long trng_random_u32(void); #endif // fatfs configuration used in ffconf.h -#define MICROPY_FATFS_ENABLE_LFN (1) -#define MICROPY_FATFS_RPATH (2) -#define MICROPY_FATFS_MAX_SS (4096) -#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define VFS_BLOCK_SIZE_BYTES (2048) // diff --git a/ports/samd/mcu/samd51/mpconfigmcu.mk b/ports/samd/mcu/samd51/mpconfigmcu.mk index 9bef5eca166..f10faec47f4 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.mk +++ b/ports/samd/mcu/samd51/mpconfigmcu.mk @@ -5,6 +5,7 @@ CFLAGS_MCU += -DCFG_TUSB_MCU=OPT_MCU_SAMD51 MPY_CROSS_MCU_ARCH = armv7m MICROPY_HW_CODESIZE ?= 368K +MICROPY_HW_ROMFS_BYTES ?= 64K MICROPY_VFS_LFS2 ?= 1 MICROPY_VFS_FAT ?= 1 diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 32009ae82f2..131861d2bd8 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -86,6 +86,9 @@ #define MICROPY_PY_OS_INCLUDEFILE "ports/samd/modos.c" #define MICROPY_READER_VFS (1) #define MICROPY_VFS (1) +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (1) +#endif #ifndef MICROPY_PY_MACHINE_ADC #define MICROPY_PY_MACHINE_ADC (1) #endif diff --git a/ports/samd/samd_flash.c b/ports/samd/samd_flash.c index f68bdf140f4..c99df9d0f89 100644 --- a/ports/samd/samd_flash.c +++ b/ports/samd/samd_flash.c @@ -26,11 +26,13 @@ #include +#include "py/objarray.h" #include "py/runtime.h" #include "extmod/vfs.h" +#include "py/mperrno.h" #include "samd_soc.h" -#if MICROPY_HW_MCUFLASH +#if MICROPY_HW_MCUFLASH || MICROPY_VFS_ROM // ASF 4 #include "hal_flash.h" @@ -45,7 +47,6 @@ #endif static struct flash_descriptor flash_desc; -static mp_int_t BLOCK_SIZE = VFS_BLOCK_SIZE_BYTES; // Board specific: mpconfigboard.h extern const mp_obj_type_t samd_flash_type; typedef struct _samd_flash_obj_t { @@ -54,8 +55,9 @@ typedef struct _samd_flash_obj_t { uint32_t flash_size; } samd_flash_obj_t; +#if MICROPY_HW_MCUFLASH +static mp_int_t BLOCK_SIZE = VFS_BLOCK_SIZE_BYTES; // Board specific: mpconfigboard.h extern uint8_t _oflash_fs, _sflash_fs; - // Build a Flash storage at top. static samd_flash_obj_t samd_flash_obj = { .base = { &samd_flash_type }, @@ -63,9 +65,31 @@ static samd_flash_obj_t samd_flash_obj = { .flash_size = (uint32_t)&_sflash_fs, // Get from MCU-Specific loader script. }; +static mp_obj_t samd_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // No args required. bdev=Flash(). Start Addr & Size defined in samd_flash_obj. + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // Return singleton object. + return MP_OBJ_FROM_PTR(&samd_flash_obj); +} + +static mp_int_t samd_flash_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + samd_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)((uintptr_t)self->flash_base); + bufinfo->len = self->flash_size; + bufinfo->typecode = 'B'; + return 0; + } else { + // Write unsupported. + return 1; + } +} +#endif // MICROPY_HW_MCUFLASH + // Flash init (from cctpy) // Method is needed for when MP starts up in _boot.py -static void samd_flash_init(void) { +void samd_flash_init(void) { #ifdef SAMD51 hri_mclk_set_AHBMASK_NVMCTRL_bit(MCLK); #endif @@ -76,23 +100,14 @@ static void samd_flash_init(void) { flash_init(&flash_desc, NVMCTRL); } -static mp_obj_t samd_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - // No args required. bdev=Flash(). Start Addr & Size defined in samd_flash_obj. - mp_arg_check_num(n_args, n_kw, 0, 0, false); - - samd_flash_init(); - - // Return singleton object. - return MP_OBJ_FROM_PTR(&samd_flash_obj); -} - +#if MICROPY_HW_MCUFLASH // Function for ioctl. static mp_obj_t eraseblock(uint32_t sector_in) { // Destination address aligned with page start to be erased. - uint32_t DEST_ADDR = sector_in; // Number of pages to be erased. - mp_int_t PAGE_SIZE = flash_get_page_size(&flash_desc); // adf4 API call + uint32_t dest_addr = sector_in; // Number of pages to be erased. + mp_int_t page_size = flash_get_page_size(&flash_desc); // adf4 API call - flash_erase(&flash_desc, DEST_ADDR, (BLOCK_SIZE / PAGE_SIZE)); + flash_erase(&flash_desc, dest_addr, (BLOCK_SIZE / page_size)); return mp_const_none; } @@ -131,7 +146,7 @@ static mp_obj_t samd_flash_writeblocks(size_t n_args, const mp_obj_t *args) { } // Write data to flash (adf4 API) flash_write(&flash_desc, offset, bufinfo.buf, bufinfo.len); - // TODO check return value + return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_flash_writeblocks_obj, 3, 4, samd_flash_writeblocks); @@ -176,7 +191,75 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_QSTR_Flash, MP_TYPE_FLAG_NONE, make_new, samd_flash_make_new, + buffer, samd_flash_get_buffer, locals_dict, &samd_flash_locals_dict ); +#endif -#endif // MICROPY_HW_MCUFLASH +#if MICROPY_VFS_ROM +// +// Uses object-based capabilities for devices using the internal flash +// for the regular file system and ioctl function for devices with +// external flash. +// +extern uint8_t _micropy_hw_romfs_part0_start, _micropy_hw_romfs_part0_size; + +#define MICROPY_HW_ROMFS_BASE ((uint32_t)&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_BYTES ((uint32_t)&_micropy_hw_romfs_part0_size) + +#if MICROPY_HW_MCUFLASH +static samd_flash_obj_t samd_flash_romfs_obj = { + .base = { &samd_flash_type }, + .flash_base = MICROPY_HW_ROMFS_BASE, // Get from MCU-Specific loader script. + .flash_size = MICROPY_HW_ROMFS_BYTES, // Get from MCU-Specific loader script. +}; +#else +static const MP_DEFINE_MEMORYVIEW_OBJ(samd_flash_romfs_obj, 'B', 0, MICROPY_HW_ROMFS_BYTES, (void *)MICROPY_HW_ROMFS_BASE); +#endif + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + if (MICROPY_HW_ROMFS_BYTES <= 0) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&samd_flash_romfs_obj); + + #if !MICROPY_HW_MCUFLASH + + case MP_VFS_ROM_IOCTL_WRITE_PREPARE: { + // Erase sectors in given range. + if (n_args < 3) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest_addr = MICROPY_HW_ROMFS_BASE; + uint32_t bytes_used = mp_obj_get_int(args[2]); + mp_int_t page_size = flash_get_page_size(&flash_desc); // adf4 API call + flash_erase(&flash_desc, dest_addr, (bytes_used + page_size - 1) / page_size); + return MP_OBJ_NEW_SMALL_INT(4); + } + + case MP_VFS_ROM_IOCTL_WRITE: { + // Write data to flash. + if (n_args < 4) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest_addr = MICROPY_HW_ROMFS_BASE + mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + flash_write(&flash_desc, dest_addr, bufinfo.buf, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(0); + } + + #endif + + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} +#endif + +#endif // MICROPY_HW_MCUFLASH || MICROPY_VFS_ROM diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index fb6eb2083a3..761eb8d1aae 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -39,6 +39,7 @@ #include "tusb.h" extern void machine_rtc_start(bool force); +extern void samd_flash_init(void); static void usb_init(void) { // Init USB clock @@ -120,6 +121,9 @@ void samd_init(void) { mp_hal_ticks_cpu_enable(); #endif machine_rtc_start(false); + #if MICROPY_HW_MCUFLASH || MICROPY_VFS_ROM + samd_flash_init(); + #endif } #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_I2C_TARGET || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART From cee950673d1f04ddd1b33bfb682c1c8623ea81a0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 11 Dec 2024 14:21:18 +0100 Subject: [PATCH 1615/2098] renesas-ra: Add support for VfsRom filesystem. The ROM partition is taken from the last part of the flash text section, so the existing writable filesystem is untouched. VfsRom file system sizes: - EK_RA4M1: 12K - RA4M1_CLICKER: 12k - EK_RA4W1: 64k - EK_RA6M1: 64k - EK_RA6M2: 256k - VK_RA6M5: 384k - ARDUINO_PORTENTA_C33: 256k Tested with Weact RA4M1 core board with EK_RA4M1 firmware and EK_RA6M2. Signed-off-by: robert-hh Signed-off-by: Damien George --- .../boards/ARDUINO_PORTENTA_C33/ra6m5.ld | 6 +- ports/renesas-ra/boards/EK_RA4M1/ra4m1_ek.ld | 10 ++- ports/renesas-ra/boards/EK_RA4W1/ra4w1_ek.ld | 10 ++- ports/renesas-ra/boards/EK_RA6M1/ra6m1_ek.ld | 6 +- ports/renesas-ra/boards/EK_RA6M2/ra6m2_ek.ld | 11 ++- .../boards/RA4M1_CLICKER/ra4m1_clicker.ld | 6 +- ports/renesas-ra/boards/VK_RA6M5/vk_ra6m5.ld | 6 +- ports/renesas-ra/mpconfigboard_common.h | 5 ++ ports/renesas-ra/storage.c | 68 +++++++++++++++++-- 9 files changed, 115 insertions(+), 13 deletions(-) diff --git a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/ra6m5.ld b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/ra6m5.ld index c1fac5106c3..a937e01cdf5 100644 --- a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/ra6m5.ld +++ b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/ra6m5.ld @@ -6,7 +6,8 @@ MEMORY { FLASH_BOOT (r) : ORIGIN = 0x00000000, LENGTH = 0x00010000 /* 64K */ - FLASH (rx) : ORIGIN = 0x00010000, LENGTH = 0x000f0000 /* 960KB */ + FLASH (rx) : ORIGIN = 0x00010000, LENGTH = 0x000B0000 /* 704KB/2MB */ + FLASH_ROMFS (r) : ORIGIN = 0x000C0000, LENGTH = 0x00040000 /* 256KB/2MB */ FLASH_FS (r) : ORIGIN = 0x00100000, LENGTH = 0x00100000 /* 1MB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00080000 /* 512KB */ OSPI_RAM (rwx) : ORIGIN = 0x68000000, LENGTH = 0x00800000 /* 8MB/8MB */ @@ -304,3 +305,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/EK_RA4M1/ra4m1_ek.ld b/ports/renesas-ra/boards/EK_RA4M1/ra4m1_ek.ld index 52f8acf93eb..e6e4cb3d315 100644 --- a/ports/renesas-ra/boards/EK_RA4M1/ra4m1_ek.ld +++ b/ports/renesas-ra/boards/EK_RA4M1/ra4m1_ek.ld @@ -3,9 +3,14 @@ */ /* Linker script to configure memory regions. */ +/* + * FLASH_ROMFS and FLASH_FS must start and length must be a multiple + * of the physical sector size of that region (2k). + */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00037000 /* 220KB/256KB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00034000 /* 208KB/256KB */ + FLASH_ROMFS (r) : ORIGIN = 0x00034000, LENGTH = 0x00003000 /* 12KB/256KB */ FLASH_FS (r) : ORIGIN = 0x00037000, LENGTH = 0x00009000 /* 36KB/256KB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 /* 32KB */ DATA_FLASH (rx) : ORIGIN = 0x40100000, LENGTH = 0x00002000 /* 8KB */ @@ -300,3 +305,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/EK_RA4W1/ra4w1_ek.ld b/ports/renesas-ra/boards/EK_RA4W1/ra4w1_ek.ld index 1241b5bc230..7c021207a40 100644 --- a/ports/renesas-ra/boards/EK_RA4W1/ra4w1_ek.ld +++ b/ports/renesas-ra/boards/EK_RA4W1/ra4w1_ek.ld @@ -3,9 +3,14 @@ */ /* Linker script to configure memory regions. */ +/* + * FLASH_ROMFS and FLASH_FS must start and length must be a multiple + * of the physical sector size of that region (2k). + */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00070000 /* 448KB/512KB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00060000 /* 384KB/512KB */ + FLASH_ROMFS (r) : ORIGIN = 0x00060000, LENGTH = 0x00010000 /* 64KB/512KB */ FLASH_FS (r) : ORIGIN = 0x00070000, LENGTH = 0x00010000 /* 64KB/512KB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00018000 /* 96KB */ DATA_FLASH (rx) : ORIGIN = 0x40100000, LENGTH = 0x00002000 /* 8KB */ @@ -300,3 +305,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/EK_RA6M1/ra6m1_ek.ld b/ports/renesas-ra/boards/EK_RA6M1/ra6m1_ek.ld index c7d85ed3db0..876525d717b 100644 --- a/ports/renesas-ra/boards/EK_RA6M1/ra6m1_ek.ld +++ b/ports/renesas-ra/boards/EK_RA6M1/ra6m1_ek.ld @@ -5,7 +5,8 @@ /* Linker script to configure memory regions. */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00070000 /* 448KB/512KB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00060000 /* 384KB/512KB */ + FLASH_ROMFS (r) : ORIGIN = 0x00060000, LENGTH = 0x00010000 /* 64KB/512KB */ FLASH_FS (r) : ORIGIN = 0x00070000, LENGTH = 0x00010000 /* 64KB/512KB */ RAM (rwx) : ORIGIN = 0x1FFE0000, LENGTH = 0x00040000 /* 256KB */ DATA_FLASH (rx) : ORIGIN = 0x40100000, LENGTH = 0x00002000 /* 8KB */ @@ -300,3 +301,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/EK_RA6M2/ra6m2_ek.ld b/ports/renesas-ra/boards/EK_RA6M2/ra6m2_ek.ld index 086f6630c11..423cf63b701 100644 --- a/ports/renesas-ra/boards/EK_RA6M2/ra6m2_ek.ld +++ b/ports/renesas-ra/boards/EK_RA6M2/ra6m2_ek.ld @@ -3,9 +3,15 @@ */ /* Linker script to configure memory regions. */ + +/* + * FLASH_ROMFS and FLASH_FS must start and length must be a multiple + * of the physical sector size of that region (32k). + */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x000e0000 /* 896KB/1MB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x000A0000 /* 768KB/1MB */ + FLASH_ROMFS (r) : ORIGIN = 0x000A0000, LENGTH = 0x00040000 /* 256KB/1MB */ FLASH_FS (r) : ORIGIN = 0x000e0000, LENGTH = 0x00020000 /* 128KB/1MB */ RAM (rwx) : ORIGIN = 0x1FFE0000, LENGTH = 0x00060000 /* 384KB */ DATA_FLASH (rx) : ORIGIN = 0x40100000, LENGTH = 0x00008000 /* 32KB */ @@ -300,3 +306,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/RA4M1_CLICKER/ra4m1_clicker.ld b/ports/renesas-ra/boards/RA4M1_CLICKER/ra4m1_clicker.ld index 52f8acf93eb..92d34ba3366 100644 --- a/ports/renesas-ra/boards/RA4M1_CLICKER/ra4m1_clicker.ld +++ b/ports/renesas-ra/boards/RA4M1_CLICKER/ra4m1_clicker.ld @@ -5,7 +5,8 @@ /* Linker script to configure memory regions. */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00037000 /* 220KB/256KB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00034000 /* 208KB/256KB */ + FLASH_ROMFS (r) : ORIGIN = 0x00034000, LENGTH = 0x00003000 /* 12KB/256KB */ FLASH_FS (r) : ORIGIN = 0x00037000, LENGTH = 0x00009000 /* 36KB/256KB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 /* 32KB */ DATA_FLASH (rx) : ORIGIN = 0x40100000, LENGTH = 0x00002000 /* 8KB */ @@ -300,3 +301,6 @@ _heap_end = __HeapLimit; /* tunable */ _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/boards/VK_RA6M5/vk_ra6m5.ld b/ports/renesas-ra/boards/VK_RA6M5/vk_ra6m5.ld index 8363d2e743f..82e9bbb4bd2 100644 --- a/ports/renesas-ra/boards/VK_RA6M5/vk_ra6m5.ld +++ b/ports/renesas-ra/boards/VK_RA6M5/vk_ra6m5.ld @@ -5,7 +5,8 @@ /* Linker script to configure memory regions. */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00100000 /* 1MB/2MB */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x000A0000 /* 640KB/2MB */ + FLASH_ROMFS (r) : ORIGIN = 0x000A0000, LENGTH = 0x00060000 /* 384KB/2MB */ FLASH_FS (r) : ORIGIN = 0x00100000, LENGTH = 0x00100000 /* 1MB/2MB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00080000 /* 512KB */ OSPI_RAM (rwx) : ORIGIN = 0x68000000, LENGTH = 0x00800000 /* 8MB/8MB */ @@ -306,3 +307,6 @@ _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); _micropy_hw_external_flash_storage_start = ORIGIN(QSPI_FLASH); _micropy_hw_external_flash_storage_end = ORIGIN(QSPI_FLASH) + LENGTH(QSPI_FLASH); + +_micropy_hw_romfs_part0_start = ORIGIN(FLASH_ROMFS); +_micropy_hw_romfs_part0_size = LENGTH(FLASH_ROMFS); diff --git a/ports/renesas-ra/mpconfigboard_common.h b/ports/renesas-ra/mpconfigboard_common.h index 479c9f61d1a..a1aa4c0539e 100644 --- a/ports/renesas-ra/mpconfigboard_common.h +++ b/ports/renesas-ra/mpconfigboard_common.h @@ -154,6 +154,11 @@ #define MICROPY_HW_UART_IS_RESERVED(uart_id) (false) #endif +// Whether to support the VFSROM file system +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (1) +#endif + /*****************************************************************************/ // General configuration diff --git a/ports/renesas-ra/storage.c b/ports/renesas-ra/storage.c index 12416e4dc7a..4262a5ab651 100644 --- a/ports/renesas-ra/storage.c +++ b/ports/renesas-ra/storage.c @@ -28,6 +28,7 @@ #include #include +#include "py/objarray.h" #include "py/runtime.h" #include "py/mperrno.h" #include "extmod/vfs_fat.h" @@ -36,6 +37,13 @@ #include "led.h" #include "storage.h" #include "irq.h" +#include "flash.h" + +typedef struct _pyb_flash_obj_t { + mp_obj_base_t base; + uint32_t start; // in bytes + uint32_t len; // in bytes +} pyb_flash_obj_t; #if MICROPY_HW_ENABLE_STORAGE @@ -236,12 +244,6 @@ int storage_readblocks_ext(uint8_t *dest, uint32_t block, uint32_t offset, uint3 } #endif -typedef struct _pyb_flash_obj_t { - mp_obj_base_t base; - uint32_t start; // in bytes - uint32_t len; // in bytes -} pyb_flash_obj_t; - // This Flash object represents the entire available flash, with emulated partition table at start const pyb_flash_obj_t pyb_flash_obj = { { &pyb_flash_type }, @@ -427,3 +429,57 @@ void pyb_flash_init_vfs(fs_user_mount_t *vfs) { } #endif + +#if MICROPY_VFS_ROM + +extern uint32_t _micropy_hw_romfs_part0_start, _micropy_hw_romfs_part0_size; + +#define MICROPY_HW_ROMFS_BASE ((uint32_t)&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_BYTES ((uint32_t)&_micropy_hw_romfs_part0_size) +#define VFSROM_BLOCK_SIZE (2048) + +static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, MICROPY_HW_ROMFS_BYTES, (void *)MICROPY_HW_ROMFS_BASE); + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + if (MICROPY_HW_ROMFS_BYTES <= 0) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&romfs_obj); + + case MP_VFS_ROM_IOCTL_WRITE_PREPARE: { + // Erase sectors in given range. + if (n_args < 3) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = MICROPY_HW_ROMFS_BASE; + uint32_t dest_max = dest + mp_obj_get_int(args[2]); + uint32_t sec_size = sector_size(dest); + for (; dest < dest_max; dest += sec_size) { + flash_erase(dest, sec_size); + } + return MP_OBJ_NEW_SMALL_INT(4); // minimum write size + } + + case MP_VFS_ROM_IOCTL_WRITE: { + // Write data to flash. + if (n_args < 4) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t dest = MICROPY_HW_ROMFS_BASE + mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + flash_write(dest, bufinfo.buf, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(0); + } + + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} + +#endif // MICROPY_VFS_ROM From 610552010dd7c2f728b2ea8f900ef969348a3a92 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 25 Nov 2024 21:49:04 +0100 Subject: [PATCH 1616/2098] nrf: Add support for VfsRom filesystem. The implementation uses the object based capabilities, which avoids complication about different flash block sizes. The ROM partition is placed between the text and writable filesystem sections, and the latter size is unchanged. VfsRom sizes are: - NRF51x22: 12K - NRF52832: 128K - NRF52840: 256K - NRF9160: 256K Use frozen `_boot.py` to set up and mount the filesystem, replacing a mix of C-Code and Python code. The mkfs part has been simplified to save code. Tested with Microbit and Arduino Nano Connect. Signed-off-by: Damien George Signed-off-by: robert-hh --- ports/nrf/boards/memory.ld | 6 ++-- ports/nrf/boards/nrf51x22_256k_16k.ld | 1 + ports/nrf/boards/nrf51x22_256k_32k.ld | 1 + ports/nrf/boards/nrf52832_512k_64k.ld | 1 + ports/nrf/boards/nrf52840_1M_256k.ld | 1 + ports/nrf/boards/nrf9160_1M_256k.ld | 1 + ports/nrf/main.c | 18 ++--------- ports/nrf/modules/manifest.py | 2 +- ports/nrf/modules/nrf/flashbdev.c | 44 +++++++++++++++++++++++++++ ports/nrf/modules/scripts/_boot.py | 28 +++++++++++++++++ ports/nrf/modules/scripts/_mkfs.py | 23 -------------- ports/nrf/mpconfigport.h | 5 +++ 12 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 ports/nrf/modules/scripts/_boot.py delete mode 100644 ports/nrf/modules/scripts/_mkfs.py diff --git a/ports/nrf/boards/memory.ld b/ports/nrf/boards/memory.ld index 9c4c9c8bd12..df95b951b07 100644 --- a/ports/nrf/boards/memory.ld +++ b/ports/nrf/boards/memory.ld @@ -8,10 +8,12 @@ _head_size = DEFINED(_sd_size) ? _sd_size : _bootloader_head_size; _head_ram = DEFINED(_sd_ram) ? _sd_ram : _bootloader_head_ram_size; _sd_size = DEFINED(_sd_size) ? _sd_size : 0; _sd_ram = DEFINED(_sd_ram) ? _sd_ram : 0; +_micropy_hw_romfs_part0_size = DEFINED(_micropy_hw_romfs_part0_size) ? _micropy_hw_romfs_part0_size : 0; _fs_size = DEFINED(_fs_size) ? _fs_size : 64K; /* TODO: set to 0 if not using the filesystem */ -_app_size = _flash_size - _head_size - _fs_size - _bootloader_tail_size; +_app_size = _flash_size - _head_size - _micropy_hw_romfs_part0_size - _fs_size - _bootloader_tail_size; _app_start = _head_size; -_fs_start = _head_size + _app_size; +_micropy_hw_romfs_part0_start = _head_size + _app_size; +_fs_start = _micropy_hw_romfs_part0_start + _micropy_hw_romfs_part0_size; _fs_end = _fs_start + _fs_size; _app_ram_start = 0x20000000 + _head_ram; _app_ram_size = _ram_size - _head_ram; diff --git a/ports/nrf/boards/nrf51x22_256k_16k.ld b/ports/nrf/boards/nrf51x22_256k_16k.ld index 8a40ae0f173..4c54def8e80 100644 --- a/ports/nrf/boards/nrf51x22_256k_16k.ld +++ b/ports/nrf/boards/nrf51x22_256k_16k.ld @@ -6,6 +6,7 @@ GROUP(-lgcc -lc -lnosys) _flash_size = 256K; _ram_size = 16K; +_micropy_hw_romfs_part0_size = 12K; /* Default stack size when there is no SoftDevice */ _stack_size = 4K; diff --git a/ports/nrf/boards/nrf51x22_256k_32k.ld b/ports/nrf/boards/nrf51x22_256k_32k.ld index 06c0914035b..b61c2a55056 100644 --- a/ports/nrf/boards/nrf51x22_256k_32k.ld +++ b/ports/nrf/boards/nrf51x22_256k_32k.ld @@ -6,6 +6,7 @@ GROUP(-lgcc -lc -lnosys) _flash_size = 256K; _ram_size = 32K; +_micropy_hw_romfs_part0_size = 12K; /* Default stack size when there is no SoftDevice */ _stack_size = 4K; diff --git a/ports/nrf/boards/nrf52832_512k_64k.ld b/ports/nrf/boards/nrf52832_512k_64k.ld index 22804df5cdb..0599c9bda3b 100644 --- a/ports/nrf/boards/nrf52832_512k_64k.ld +++ b/ports/nrf/boards/nrf52832_512k_64k.ld @@ -4,6 +4,7 @@ _flash_size = 512K; _ram_size = 64K; +_micropy_hw_romfs_part0_size = 128K; /* produce a link error if there is not this amount of RAM for these sections */ _stack_size = 8K; diff --git a/ports/nrf/boards/nrf52840_1M_256k.ld b/ports/nrf/boards/nrf52840_1M_256k.ld index 16d61af6a30..21d45182434 100644 --- a/ports/nrf/boards/nrf52840_1M_256k.ld +++ b/ports/nrf/boards/nrf52840_1M_256k.ld @@ -4,6 +4,7 @@ _flash_size = 1M; _ram_size = 256K; +_micropy_hw_romfs_part0_size = 256K; /* produce a link error if there is not this amount of RAM for these sections */ _stack_size = 8K; diff --git a/ports/nrf/boards/nrf9160_1M_256k.ld b/ports/nrf/boards/nrf9160_1M_256k.ld index 6347095a899..d8f0aa5cb01 100644 --- a/ports/nrf/boards/nrf9160_1M_256k.ld +++ b/ports/nrf/boards/nrf9160_1M_256k.ld @@ -7,6 +7,7 @@ _ram_size = 256K; _sd_size = 0x00008000; _sd_ram = 0x00020000; _fs_size = 80K; +_micropy_hw_romfs_part0_size = 256K; /* produce a link error if there is not this amount of RAM for these sections */ _stack_size = 32K; diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 7278a646ab6..1013005e198 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -177,22 +177,8 @@ void MP_NORETURN _start(void) { #if MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE flashbdev_init(); - - // Try to mount the flash on "/flash" and chdir to it for the boot-up directory. - mp_obj_t mount_point = MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash); - int ret = mp_vfs_mount_and_chdir_protected((mp_obj_t)&nrf_flash_obj, mount_point); - - if ((ret == -MP_ENODEV) || (ret == -MP_EIO)) { - pyexec_frozen_module("_mkfs.py", false); // Frozen script for formatting flash filesystem. - ret = mp_vfs_mount_and_chdir_protected((mp_obj_t)&nrf_flash_obj, mount_point); - } - - if (ret != 0) { - printf("MPY: can't mount flash\n"); - } else { - mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash)); - mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash_slash_lib)); - } + // Execute _boot.py to set up the filesystem. + pyexec_frozen_module("_boot.py", false); #endif #if MICROPY_MBFS diff --git a/ports/nrf/modules/manifest.py b/ports/nrf/modules/manifest.py index 7ba0f80d884..631ad19a8db 100644 --- a/ports/nrf/modules/manifest.py +++ b/ports/nrf/modules/manifest.py @@ -1,2 +1,2 @@ -module("_mkfs.py", base_path="$(PORT_DIR)/modules/scripts", opt=3) +module("_boot.py", base_path="$(PORT_DIR)/modules/scripts", opt=3) include("$(MPY_DIR)/extmod/asyncio") diff --git a/ports/nrf/modules/nrf/flashbdev.c b/ports/nrf/modules/nrf/flashbdev.c index 41833b228e2..b28314b4a62 100644 --- a/ports/nrf/modules/nrf/flashbdev.c +++ b/ports/nrf/modules/nrf/flashbdev.c @@ -183,12 +183,26 @@ static mp_obj_t nrf_flashbdev_make_new(const mp_obj_type_t *type, size_t n_args, return MP_OBJ_FROM_PTR(self); } +static mp_int_t nrf_flashbdev_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + nrf_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (flags == MP_BUFFER_READ) { + bufinfo->buf = (void *)self->start; + bufinfo->len = self->len; + bufinfo->typecode = 'B'; + return 0; + } else { + // Unsupported. + return 1; + } +} + MP_DEFINE_CONST_OBJ_TYPE( nrf_flashbdev_type, MP_QSTR_Flash, MP_TYPE_FLAG_NONE, make_new, nrf_flashbdev_make_new, print, nrf_flashbdev_print, + buffer, nrf_flashbdev_get_buffer, locals_dict, &nrf_flashbdev_locals_dict ); @@ -202,4 +216,34 @@ void flashbdev_init(void) { nrf_flash_obj.len = num_pages * FLASH_PAGESIZE; } +#if MICROPY_VFS_ROM + +extern byte _micropy_hw_romfs_part0_start[]; +extern byte _micropy_hw_romfs_part0_size[]; + +#define MICROPY_HW_ROMFS_BASE ((uint32_t)_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_BYTES ((uint32_t)_micropy_hw_romfs_part0_size) + +static nrf_flash_obj_t nrf_flash_romfs_obj = { + .base = { &nrf_flashbdev_type }, + .start = MICROPY_HW_ROMFS_BASE, // Get from MCU-Specific loader script. + .len = MICROPY_HW_ROMFS_BYTES, // Get from MCU-Specific loader script. +}; + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + if (MICROPY_HW_ROMFS_BYTES <= 0) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + case MP_VFS_ROM_IOCTL_GET_SEGMENT: + return MP_OBJ_FROM_PTR(&nrf_flash_romfs_obj); + default: + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } +} + +#endif // MICROPY_VFS_ROM + #endif // MICROPY_PY_NRF && MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE diff --git a/ports/nrf/modules/scripts/_boot.py b/ports/nrf/modules/scripts/_boot.py new file mode 100644 index 00000000000..793c56ee149 --- /dev/null +++ b/ports/nrf/modules/scripts/_boot.py @@ -0,0 +1,28 @@ +def setup_fs(): + import gc + import vfs + import sys + import nrf + import os + + fs_type = getattr(vfs, "VfsLfs2", getattr(vfs, "VfsLfs1", getattr(vfs, "VfsFat", None))) + try: + bdev = nrf.Flash() + vfs.mount(bdev, "/flash") + except: + if fs_type is not None: + try: + fs_type.mkfs(bdev) + vfs.mount(bdev, "/flash") + except: + return + + os.chdir("/flash") + sys.path.append("/flash") + sys.path.append("/flash/lib") + + gc.collect() + + +setup_fs() +del setup_fs diff --git a/ports/nrf/modules/scripts/_mkfs.py b/ports/nrf/modules/scripts/_mkfs.py deleted file mode 100644 index 601f9558eb7..00000000000 --- a/ports/nrf/modules/scripts/_mkfs.py +++ /dev/null @@ -1,23 +0,0 @@ -import vfs, nrf - -try: - from vfs import VfsLfs1 - - vfs.VfsLfs1.mkfs(nrf.Flash()) -except ImportError: - try: - from vfs import VfsLfs2 - - vfs.VfsLfs2.mkfs(nrf.Flash()) - except ImportError: - try: - from vfs import VfsFat - - vfs.VfsFat.mkfs(nrf.Flash()) - except ImportError: - pass - except OSError as e: - if e.args[0] == 5: # I/O Error - flashbdev_size = (nrf.Flash.ioctl(4, 0) * nrf.Flash.ioctl(5, 0)) // 1024 - print() - print("Is `FS_SIZE=%iK` enough for FAT filesystem?" % flashbdev_size) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index cce3f86cfd2..bb517c19c55 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -69,6 +69,11 @@ #define MICROPY_VFS (CORE_FEAT) #endif +// VfsROM filesystem +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (CORE_FEAT) +#endif + // micro:bit filesystem #ifndef MICROPY_MBFS #define MICROPY_MBFS (!MICROPY_VFS) From 049cdd0dd3d27e771907fff82967df78d88f0aa7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Nov 2025 03:33:47 +0100 Subject: [PATCH 1617/2098] cc3200/mods/pybpin: Reduce footprint of AF pin structures. This commit performs a minor change to the "pin_af_t" structure, changing the data type used to store alternate function pin name QSTRs in order to have a smaller impact on the .rodata section of the firmware. The data type used to store the QSTR name of the pin is changed from "qstr" to "qstr_short_t", that is able to store the same name string index but in half the size. Other pin-related structures in the port that also store QSTRs do not benefit from a narrower data type, as their members are mostly word-aligned and thus the linker is forced to insert padding bytes between entries. Signed-off-by: Alessandro Gatti --- ports/cc3200/mods/pybpin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/cc3200/mods/pybpin.h b/ports/cc3200/mods/pybpin.h index 74f0af2b3ca..5e55d2cf95a 100644 --- a/ports/cc3200/mods/pybpin.h +++ b/ports/cc3200/mods/pybpin.h @@ -88,7 +88,7 @@ enum { }; typedef struct { - qstr name; + qstr_short_t name; int8_t idx; uint8_t fn; uint8_t unit; From 8bda61a3afe3f2406d80de509d5a98b2f010fad4 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Nov 2025 03:46:01 +0100 Subject: [PATCH 1618/2098] mimxrt/pin: Reduce footprint of pin structures. This commit performs a few minor changes to the structures used to store board pin information, in order to reduce the impact on the .rodata section of the firmware of instances of those structures. The data type used to store the QSTR name of alternate function pins ("machine_pin_af_obj_t") is changed from "qstr" to "qstr_short_t", that is able to store the same name string index but in half the size. Regular pin objects structure ("machine_pin_obj_t") instead has its QSTR variable holder changed from "qstr" to "qstr_short_t", and some elements of that structure have been rearranged to remove enough padding bytes inside structure elements to let the linker allocate less data for instances of that structure. Signed-off-by: Alessandro Gatti --- ports/mimxrt/pin.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/mimxrt/pin.h b/ports/mimxrt/pin.h index 13b313c41f4..7ee28414659 100644 --- a/ports/mimxrt/pin.h +++ b/ports/mimxrt/pin.h @@ -106,7 +106,7 @@ enum { typedef struct { mp_obj_base_t base; - qstr name; // port name + qstr_short_t name; // port name uint8_t af_mode; // alternate function uint8_t input_daisy; void *instance; // pointer to peripheral instance for alternate function @@ -121,15 +121,15 @@ typedef struct { typedef struct { mp_obj_base_t base; - qstr name; // pad name GPIO_Type *gpio; // gpio instance for pin uint32_t pin; // pin number uint32_t muxRegister; uint32_t configRegister; - uint8_t af_list_len; // length of available alternate functions list - uint8_t adc_list_len; // length of available ADC options list const machine_pin_af_obj_t *af_list; // pointer to list with alternate functions const machine_pin_adc_obj_t *adc_list; // pointer to list with ADC options + qstr_short_t name; // pad name + uint8_t af_list_len; // length of available alternate functions list + uint8_t adc_list_len; // length of available ADC options list } machine_pin_obj_t; typedef struct _machine_pin_irq_obj_t { From fb8ecaecf56a2e393688a8907bd987cb1d7d763a Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Nov 2025 03:57:46 +0100 Subject: [PATCH 1619/2098] renesas-ra/pin: Reduce footprint of pin structures. This commit performs a few minor changes to the structures used to store board pin information, in order to reduce the impact on the .rodata section of the firmware of instances of those structures. The pin objects structure ("machine_pin_obj_t") instead has its QSTR variable holder changed from "qstr" to "qstr_short_t", and some elements of that structure have been rearranged to remove enough padding bytes inside structure elements to let the linker allocate less data for instances of that structure. Signed-off-by: Alessandro Gatti --- ports/renesas-ra/pin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/renesas-ra/pin.h b/ports/renesas-ra/pin.h index 76d6c0b1e43..649f1f2fcea 100644 --- a/ports/renesas-ra/pin.h +++ b/ports/renesas-ra/pin.h @@ -41,9 +41,9 @@ typedef struct { typedef struct { mp_obj_base_t base; - qstr name; - uint8_t pin; const machine_pin_adc_obj_t *ad; + qstr_short_t name; + uint8_t pin; } machine_pin_obj_t; // Include all of the individual pin objects From 31deee90608e76c0e0829db8e819f03ae85b1bfb Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Nov 2025 04:19:11 +0100 Subject: [PATCH 1620/2098] rp2/machine_pin: Reduce footprint of pin structures. This commit performs a few minor changes to the structures used to store board pin information, in order to reduce the impact on the .rodata section of the firmware of instances of those structures. The data type used to store the QSTR name of both regular and alternate function pins ("machine_pin_obj_t" and "machine_pin_af_obj_t") is changed from "qstr" to "qstr_short_t", that is able to store the same name string index but in half the size. Signed-off-by: Alessandro Gatti --- ports/rp2/machine_pin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_pin.h b/ports/rp2/machine_pin.h index 2d85bdcadc1..47c0d328e56 100644 --- a/ports/rp2/machine_pin.h +++ b/ports/rp2/machine_pin.h @@ -40,7 +40,7 @@ enum { typedef struct _machine_pin_af_obj_t { mp_obj_base_t base; - qstr name; + qstr_short_t name; uint8_t idx : 4; uint8_t fn : 4; uint8_t unit : 8; @@ -48,7 +48,7 @@ typedef struct _machine_pin_af_obj_t { typedef struct _machine_pin_obj_t { mp_obj_base_t base; - qstr name; + qstr_short_t name; uint8_t id : 6; #if MICROPY_HW_PIN_EXT_COUNT uint8_t is_ext : 1; From 6572ce84c95b72dac92cc587ca16babf92afe59e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 14 Nov 2025 04:27:17 +0100 Subject: [PATCH 1621/2098] samd/pin_af: Reduce footprint of pin structures. This commit performs a few minor changes to the structures used to store board pin information, in order to reduce the impact on the .rodata section of the firmware of instances of those structures. The pin objects structure ("machine_pin_obj_t") instead has its QSTR variable holder changed from "qstr" to "qstr_short_t", and some elements of that structure have been rearranged to remove enough padding bytes inside structure elements to let the linker allocate less data for instances of that structure. Signed-off-by: Alessandro Gatti --- ports/samd/boards/pins_prefix.c | 4 ++-- ports/samd/pin_af.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/samd/boards/pins_prefix.c b/ports/samd/boards/pins_prefix.c index 4f0095d3321..24378593393 100644 --- a/ports/samd/boards/pins_prefix.c +++ b/ports/samd/boards/pins_prefix.c @@ -11,11 +11,11 @@ #if defined(MCU_SAMD21) #define PIN(p_name, p_eic, p_adc0, p_sercom1, p_sercom2, p_tcc1, p_tcc2) \ - {{&machine_pin_type}, PIN_##p_name, MP_QSTR_##p_name, p_eic, p_adc0, p_sercom1, p_sercom2, p_tcc1, p_tcc2 } + {{&machine_pin_type}, MP_QSTR_##p_name, PIN_##p_name, p_eic, p_adc0, p_sercom1, p_sercom2, p_tcc1, p_tcc2 } #elif defined(MCU_SAMD51) #define PIN(p_name, p_eic, p_adc0, p_adc1, p_sercom1, p_sercom2, p_tc, p_tcc1, p_tcc2) \ - {{&machine_pin_type}, PIN_##p_name, MP_QSTR_##p_name, p_eic, p_adc0, p_adc1, p_sercom1, p_sercom2, p_tc, p_tcc1, p_tcc2 } + {{&machine_pin_type}, MP_QSTR_##p_name, PIN_##p_name, p_eic, p_adc0, p_adc1, p_sercom1, p_sercom2, p_tc, p_tcc1, p_tcc2 } #endif diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h index bc65e8ae23b..11ef75c63f5 100644 --- a/ports/samd/pin_af.h +++ b/ports/samd/pin_af.h @@ -32,8 +32,8 @@ typedef struct _machine_pin_obj_t { mp_obj_base_t base; + qstr_short_t name; uint8_t pin_id; - qstr name; uint8_t eic; uint8_t adc0; uint8_t sercom1; @@ -50,8 +50,8 @@ typedef struct _machine_pin_obj_t { typedef struct _machine_pin_obj_t { mp_obj_base_t base; + qstr_short_t name; uint8_t pin_id; - qstr name; uint8_t eic; uint8_t adc0; uint8_t adc1; From 11861e96a949a912d2a22c920191333da67452ed Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 15 Nov 2025 17:04:01 +0100 Subject: [PATCH 1622/2098] nrf/modules/machine/pin: Reduce footprint of pin structures. This commit performs a few minor changes to the structures used to store board pin information, in order to reduce the impact on the .rodata section of the firmware of instances of those structures. The "pin_obj_t" structure, holding pin information, had an unused word-sized field ("pull") that isn't used by any of the board definitions, yet it takes up space in each pin structure being compiled in the final firmware image. Images for all available boards were built successfully with this change, so there should be no unwanted issues arising from shrinking that structure. Signed-off-by: Alessandro Gatti --- ports/nrf/modules/machine/pin.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/nrf/modules/machine/pin.h b/ports/nrf/modules/machine/pin.h index 41579011b5d..a67c39d2848 100644 --- a/ports/nrf/modules/machine/pin.h +++ b/ports/nrf/modules/machine/pin.h @@ -56,7 +56,6 @@ typedef struct { uint32_t adc_channel : 5; // Some ARM processors use 32 bits/PORT uint32_t adc_num : 3; // 1 bit per ADC const pin_af_obj_t *af; - uint32_t pull; } pin_obj_t; extern const mp_obj_type_t pin_type; From d468ccce62e4d48fc726ef9f883159f2ca3baa47 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 30 Nov 2025 11:05:57 +0100 Subject: [PATCH 1623/2098] extmod/modopenamp: Rework trace buffer setup procedure. This commit reworks the setup procedure for the OpenAMP trace buffer, used by the libmetal framework to provide cross-core logging data if needed. Before these changes, the buffer was provided by MicroPython, as a fixed size 128 bytes chunk that was accidentally put into the .rodata section, making it not usable for its intended purpose. Now, a buffer placed in .bss with a default size of 128 bytes is provided by MicroPython unless chosen otherwise. A user-chosen buffer pointer can be provided to MicroPython using the MICROPY_PY_OPENAMP_TRACE_BUF preprocessor definition. If what MicroPython provides by default is fine, the buffer size can be overridden with a new value for the MICROPY_PY_OPENAMP_TRACE_BUF_LEN preprocessor definition instead. Signed-off-by: Alessandro Gatti --- extmod/modopenamp.c | 9 +++++---- extmod/modopenamp.h | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/extmod/modopenamp.c b/extmod/modopenamp.c index 7d5841c4000..b5e813495ba 100644 --- a/extmod/modopenamp.c +++ b/extmod/modopenamp.c @@ -80,11 +80,12 @@ #define VRING_BUFF_ADDR (METAL_SHM_ADDR + 0x2000) #define VRING_BUFF_SIZE (METAL_SHM_SIZE - 0x2000) -#if MICROPY_PY_OPENAMP_HOST -static const char openamp_trace_buf[128]; +#if MICROPY_PY_OPENAMP_HOST && MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE +#ifndef MICROPY_PY_OPENAMP_TRACE_BUF +static char openamp_trace_buf[MICROPY_PY_OPENAMP_TRACE_BUF_LEN]; #define MICROPY_PY_OPENAMP_TRACE_BUF ((uint32_t)openamp_trace_buf) -#define MICROPY_PY_OPENAMP_TRACE_BUF_LEN sizeof(MICROPY_PY_OPENAMP_TRACE_BUF) -#endif // MICROPY_PY_OPENAMP_HOST +#endif // MICROPY_PY_OPENAMP_TRACE_BUF +#endif // MICROPY_PY_OPENAMP_HOST && MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE #endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE diff --git a/extmod/modopenamp.h b/extmod/modopenamp.h index 8f677788f90..463399507b9 100644 --- a/extmod/modopenamp.h +++ b/extmod/modopenamp.h @@ -47,6 +47,12 @@ #define MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE (1) #endif +// Set the default trace buffer size, making it 128 bytes long unless +// marked otherwise. +#ifndef MICROPY_PY_OPENAMP_TRACE_BUF_LEN +#define MICROPY_PY_OPENAMP_TRACE_BUF_LEN (128) +#endif + // For ports that don't define a custom image store, this enables a generic // VFS-based image store that supports loading elf files from storage. #ifndef MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE From 3980a0c01f3157526bbe4b16fbf7ba018058d0e2 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 7 Dec 2025 01:06:16 +0100 Subject: [PATCH 1624/2098] py/misc: Add byte-swapping macros. This commit adds byte-swapping macros for 16-, 32-, and 64-bit values. Modern compilers are usually able to detect manual byte/endian swaps and will generate optimised code sequences if they know how to do that. However, in certain situations it may be helpful to let the compiler generate an optimised sequence if it doesn't recognise the pattern. Most compilers provide built-in byte swap operations for 16, 32, and 64 bit values, accessed via "__builtin_bswap", but if those are not available a fallback implementation is provided. Signed-off-by: Alessandro Gatti --- py/misc.h | 26 ++++++++++++++++++++++++++ py/mpconfig.h | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/py/misc.h b/py/misc.h index f3688c73e45..ab266084c8b 100644 --- a/py/misc.h +++ b/py/misc.h @@ -553,4 +553,30 @@ static inline bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long #define MP_SANITIZER_BUILD (MP_UBSAN || MP_ASAN) #endif +// halfword/word/longword swapping macros + +#if __has_builtin(__builtin_bswap16) +#define MP_BSWAP16(x) __builtin_bswap16(x) +#else +#define MP_BSWAP16(x) ((uint16_t)((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF))) +#endif + +#if __has_builtin(__builtin_bswap32) +#define MP_BSWAP32(x) __builtin_bswap32(x) +#else +#define MP_BSWAP32(x) \ + ((uint32_t)((((x) & 0xFF) << 24) | (((x) & 0xFF00) << 8) | \ + (((x) >> 8) & 0xFF00) | (((x) >> 24) & 0xFF))) +#endif + +#if __has_builtin(__builtin_bswap64) +#define MP_BSWAP64(x) __builtin_bswap64(x) +#else +#define MP_BSWAP64(x) \ + ((uint64_t)((((x) & 0xFF) << 56) | (((x) & 0xFF00) << 40) | \ + (((x) & 0xFF0000) << 24) | (((x) & 0xFF000000) << 8) | \ + (((x) >> 8) & 0xFF000000) | (((x) >> 24) & 0xFF0000) | \ + (((x) >> 40) & 0xFF00) | (((x) >> 56) & 0xFF))) +#endif + #endif // MICROPY_INCLUDED_PY_MISC_H diff --git a/py/mpconfig.h b/py/mpconfig.h index 3211d4d398e..2a71c731628 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2394,7 +2394,7 @@ typedef time_t mp_timestamp_t; #ifndef MP_HTOBE16 #if MP_ENDIANNESS_LITTLE -#define MP_HTOBE16(x) ((uint16_t)((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))) +#define MP_HTOBE16(x) MP_BSWAP16(x) #define MP_BE16TOH(x) MP_HTOBE16(x) #else #define MP_HTOBE16(x) (x) @@ -2404,7 +2404,7 @@ typedef time_t mp_timestamp_t; #ifndef MP_HTOBE32 #if MP_ENDIANNESS_LITTLE -#define MP_HTOBE32(x) ((uint32_t)((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) | (((x) >> 8) & 0xff00) | (((x) >> 24) & 0xff))) +#define MP_HTOBE32(x) MP_BSWAP32(x) #define MP_BE32TOH(x) MP_HTOBE32(x) #else #define MP_HTOBE32(x) (x) From 678dbd26f88928c403d6e55215bd3f560da649fb Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 30 Dec 2025 21:17:41 +0100 Subject: [PATCH 1625/2098] alif/Makefile: Do not hardcode the python interpreter name. This commit fixes a build issue occurring on systems where the Python 3.x interpreter isn't aliased to 'python' but has a different name (usually `python3'). The main Linux distribution where this occurs is Ubuntu LTS, where the Python 3.x interpreter is available as 'python3' since at least two major versions (so since about five years now). MicroPython's makefile environment setup file ('py/mkenv.mk') already defines a variable called '$(PYTHON)' that defaults to 'python3', so the Makefile was updated to use that instead of hardcoding the interpreter name. Without these changes, building a firmware image on Ubuntu LTS fails with this error: make: python: No such file or directory make: *** [Makefile:95: build-ALIF_ENSEMBLE/firmware.toc.bin] Error 127 This also allows overriding the interpreter name to something else if there's the need to perform tests with different interpreter versions or the like. Signed-off-by: Alessandro Gatti --- ports/alif/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/alif/Makefile b/ports/alif/Makefile index d258b27b1d5..5eff6ccd9ad 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -92,7 +92,7 @@ $(BUILD)/$(ALIF_TOC_CONFIG): mcu/$(ALIF_TOC_CONFIG).in | $(BUILD) $(Q)$(CPP) -P -E $(ALIF_TOC_CFLAGS) - < mcu/$(ALIF_TOC_CONFIG).in > $@ $(ALIF_TOC_BIN): $(ALIF_TOC_APPS) - $(Q)python $(ALIF_TOOLS)/app-gen-toc.py \ + $(Q)$(PYTHON) $(ALIF_TOOLS)/app-gen-toc.py \ --filename $(abspath $(BUILD)/$(ALIF_TOC_CONFIG)) \ --output-dir $(BUILD) \ --firmware-dir $(BUILD) \ @@ -105,7 +105,7 @@ $(BUILD)/firmware.zip: $(ALIF_TOC_BIN) $(ALIF_TOC_APPS) .PHONY: deploy deploy: $(ALIF_TOC_BIN) $(ECHO) "Writing $< to the board" - $(Q)python $(ALIF_TOOLS)/app-write-mram.py \ + $(Q)$(PYTHON) $(ALIF_TOOLS)/app-write-mram.py \ --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ --port $(PORT) \ --pad \ @@ -117,13 +117,13 @@ deploy-jlink: $(ALIF_TOC_APPS) .PHONY: maintenance maintenance: - $(Q)python $(ALIF_TOOLS)/maintenance.py \ + $(Q)$(PYTHON) $(ALIF_TOOLS)/maintenance.py \ --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ --port $(PORT) .PHONY: update-system-package update-system-package: - $(Q)python $(ALIF_TOOLS)/updateSystemPackage.py \ + $(Q)$(PYTHON) $(ALIF_TOOLS)/updateSystemPackage.py \ --cfg-part $(ALIF_TOOLKIT_CFG_PART) \ --port $(PORT) From 2554e4172fea2eb129e251d9c6ee54d3cd66f1b7 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 30 Dec 2025 21:21:54 +0100 Subject: [PATCH 1626/2098] alif/Makefile: Enable job server support on Ubuntu LTS. This commit modifies the Alif port's Makefile to propagate to child make tasks the number of parallel jobs to run. On Ubuntu LTS 24.04, building the Alif port passing a parallel jobs number to make's command line will result in the command emitting this warning: make[1]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule. And building the two cores' firmware images using one compile job at a time instead of the number provided to the command line. Signed-off-by: Alessandro Gatti --- ports/alif/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/alif/Makefile b/ports/alif/Makefile index 5eff6ccd9ad..e7a4d2f104c 100644 --- a/ports/alif/Makefile +++ b/ports/alif/Makefile @@ -82,10 +82,10 @@ $(BUILD): $(MKDIR) -p $@ $(BUILD)/M55_HP/firmware.bin: - make -f alif.mk BUILD=$(BUILD)/M55_HP MCU_CORE=M55_HP MICROPY_PY_OPENAMP_MODE=0 + $(MAKE) -f alif.mk BUILD=$(BUILD)/M55_HP MCU_CORE=M55_HP MICROPY_PY_OPENAMP_MODE=0 $(BUILD)/M55_HE/firmware.bin: - make -f alif.mk BUILD=$(BUILD)/M55_HE MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 + $(MAKE) -f alif.mk BUILD=$(BUILD)/M55_HE MCU_CORE=M55_HE MICROPY_PY_OPENAMP_MODE=1 $(BUILD)/$(ALIF_TOC_CONFIG): mcu/$(ALIF_TOC_CONFIG).in | $(BUILD) $(ECHO) "Preprocess toc config $@" From ab5f47c54d895ec77dabcad23a5c2aa5102e080c Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 6 Dec 2025 09:20:58 +0100 Subject: [PATCH 1627/2098] alif/ospi_flash: Remove workaround for P10_7 OSPI pinmux issue. P5_6 doesn't have that alt function, and only has 7 alt functions. This workaround was never really needed, it was introduced in DFP because the wrong P10_7 alt function was used, in original DFP sources. This has been removed from DFP starting with 1.3.4, so remove it here also. Signed-off-by: iabdalkader --- ports/alif/mcu/ensemble_pin_alt.csv | 3 +-- ports/alif/ospi_flash.c | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/ports/alif/mcu/ensemble_pin_alt.csv b/ports/alif/mcu/ensemble_pin_alt.csv index 34f13e86f6d..751f8e4b600 100644 --- a/ports/alif/mcu/ensemble_pin_alt.csv +++ b/ports/alif/mcu/ensemble_pin_alt.csv @@ -45,8 +45,7 @@ P5_2,GPIO,OSPI1_SCLKN,UART5_RX,PDM_C3,SPI0_SS0,LPI2C_SCL,UT1_T0,SD_D2 P5_3,GPIO,OSPI1_SCLK,UART5_TX,SPI0_SCLK,LPI2C_SDA,UT1_T1,SD_D3,CDC_PCLK P5_4,GPIO,OSPI1_SS1,UART3_CTS,PDM_D2,SPI0_SS3,UT2_T0,SD_D4,CDC_DE P5_5,GPIO,OSPI1_SCLK,UART3_RTS,PDM_D3,UT2_T1,SD_D5,ETH_RXD0,CDC_HSYNC -# P5_6 doesn't really have OSPI on AF1 but it's needed for P10_7 to be in OSPI1_RXDS mode -P5_6,GPIO,OSPI1_RXDS,UART1_CTS,I2C2_SCL,UT3_T0,SD_D6,ETH_RXD1,CDC_VSYNC +P5_6,GPIO,,UART1_CTS,I2C2_SCL,UT3_T0,SD_D6,ETH_RXD1,CDC_VSYNC P5_7,GPIO,OSPI1_SS0,UART1_RTS,I2C2_SDA,UT3_T1,SD_D7,ETH_RST, P6_0,GPIO,OSPI0_D0,UART4_DE,PDM_D0,UT4_T0,SD_D0,ETH_TXD0, P6_1,GPIO,OSPI0_D1,UART5_DE,PDM_C0,UT4_T1,SD_D1,ETH_TXD1, diff --git a/ports/alif/ospi_flash.c b/ports/alif/ospi_flash.c index f78002eb0ef..64c2438c2f1 100644 --- a/ports/alif/ospi_flash.c +++ b/ports/alif/ospi_flash.c @@ -274,11 +274,6 @@ int ospi_flash_init(void) { if (pin->pin_rwds != NULL) { mp_hal_pin_config(pin->pin_rwds, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_RXDS, unit), true); - if (pin->pin_rwds->port == PORT_10 && pin->pin_rwds->pin == PIN_7) { - // Alif: P5_6 is needed to support proper alt function selection of P10_7. - mp_hal_pin_config(pin_P5_6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, - MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_RXDS, unit), true); - } } mp_hal_pin_config(pin->pin_d0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_HIGH, MP_HAL_PIN_DRIVE_12MA, MP_HAL_PIN_ALT(OSPI_D0, unit), true); From 1174dda51923a2ed7c0fac4e9ffc4b8340129763 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sat, 20 Dec 2025 13:12:26 +0100 Subject: [PATCH 1628/2098] alif/boards/OPENMV_AE3: Update romfs partition size. Update romfs partition to reflect the latest layout. Signed-off-by: iabdalkader --- ports/alif/boards/OPENMV_AE3/board.ld.S | 4 ++-- ports/alif/boards/OPENMV_AE3/mpconfigboard.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/alif/boards/OPENMV_AE3/board.ld.S b/ports/alif/boards/OPENMV_AE3/board.ld.S index 0d09bb15f87..539e24b1de2 100644 --- a/ports/alif/boards/OPENMV_AE3/board.ld.S +++ b/ports/alif/boards/OPENMV_AE3/board.ld.S @@ -3,8 +3,8 @@ /* Define ROMFS partition locations. */ #if CORE_M55_HP /* The HP core has access to the external OSPI flash and MRAM ROMFS partitions. */ -_micropy_hw_romfs_part0_start = 0xa1000000; -_micropy_hw_romfs_part0_size = 16M; +_micropy_hw_romfs_part0_start = 0xa0800000; +_micropy_hw_romfs_part0_size = 24M; _micropy_hw_romfs_part1_start = ORIGIN(MRAM_FS); _micropy_hw_romfs_part1_size = LENGTH(MRAM_FS); #else diff --git a/ports/alif/boards/OPENMV_AE3/mpconfigboard.h b/ports/alif/boards/OPENMV_AE3/mpconfigboard.h index 0495bc81c8a..a4981ce167e 100644 --- a/ports/alif/boards/OPENMV_AE3/mpconfigboard.h +++ b/ports/alif/boards/OPENMV_AE3/mpconfigboard.h @@ -67,8 +67,8 @@ extern void board_exit_standby(void); // This is used for alif.Flash() and USB MSC. #define MICROPY_HW_FLASH_STORAGE_BASE_ADDR (0) #define MICROPY_HW_FLASH_STORAGE_BYTES (32 * 1024 * 1024) -#define MICROPY_HW_FLASH_STORAGE_FS_BYTES (16 * 1024 * 1024) -#define MICROPY_HW_FLASH_STORAGE_ROMFS_BYTES (16 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_FS_BYTES (8 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_ROMFS_BYTES (24 * 1024 * 1024) // Murata 1YN configuration #define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/w43439_sdio_1yn_7_95_59_combined.h" From 75eb197f9d1e6780e71ca0c6673461ad5a79f353 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sat, 27 Dec 2025 22:49:27 -0800 Subject: [PATCH 1629/2098] alif/boards/OPENMV_AE3: Make JTAG pins controllable as GPIOs. Allows user control of JTAG pins on the AE3 which are exposed via the B2B header. Signed-off-by: Kwabena W. Agyeman --- ports/alif/boards/OPENMV_AE3/pins.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/alif/boards/OPENMV_AE3/pins.csv b/ports/alif/boards/OPENMV_AE3/pins.csv index 360b27af813..9ebd3b726bd 100644 --- a/ports/alif/boards/OPENMV_AE3/pins.csv +++ b/ports/alif/boards/OPENMV_AE3/pins.csv @@ -55,6 +55,10 @@ P6,P7_2 P7,P7_3 P8,P1_2 P9,P1_3 +P10,P4_4 +P11,P4_7 +P13,P4_5 +P14,P4_6 # UART buses UART1_TX,P0_5 From 1947759c943176c88e35dc27ab3faf1680471f8b Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 23 Dec 2025 10:12:15 +0100 Subject: [PATCH 1630/2098] renesas-ra/boards/ARDUINO_PORTENTA_C33: Increase TinyUSB queue size. Default queue size is too small that it overflows easily if debugging printfs are enabled. Increase TinyUSB event queue size to 128. Signed-off-by: iabdalkader --- .../renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk index 9bb336d65e9..69333eab4ab 100644 --- a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk +++ b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk @@ -3,8 +3,9 @@ MCU_SERIES = m33 LD_FILES = boards/ARDUINO_PORTENTA_C33/ra6m5.ld CFLAGS += -DCFG_TUH_MAX_SPEED=OPT_MODE_FULL_SPEED \ -DCFG_TUD_MAX_SPEED=OPT_MODE_HIGH_SPEED \ - -DCFG_TUSB_RHPORT0_MODE=0\ - -DCFG_TUSB_RHPORT1_MODE=OPT_MODE_DEVICE + -DCFG_TUSB_RHPORT0_MODE=0 \ + -DCFG_TUSB_RHPORT1_MODE=OPT_MODE_DEVICE \ + -DCFG_TUD_TASK_QUEUE_SZ=128 # MicroPython settings MICROPY_VFS_FAT = 1 From 2cb41ff97b14011714b33a265995da2709ae3584 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:05:32 +0000 Subject: [PATCH 1631/2098] github/workflows: Bump actions/upload-artifact from 4 to 6. Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_size.yml | 2 +- .github/workflows/mpremote.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index aed41676788..7d3329613fa 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -41,7 +41,7 @@ jobs: run: echo $PR_NUMBER > pr_number - name: Upload diff if: github.event_name == 'pull_request' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: code-size-report path: | diff --git a/.github/workflows/mpremote.yml b/.github/workflows/mpremote.yml index 5b9f1ff130a..25cf56d587d 100644 --- a/.github/workflows/mpremote.yml +++ b/.github/workflows/mpremote.yml @@ -22,7 +22,7 @@ jobs: - name: Build mpremote wheel run: cd tools/mpremote && python -m build --wheel - name: Archive mpremote wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: mpremote path: | From 99c59fabdbd4f7aaadffd29f1d5aca9ebf341046 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:05:36 +0000 Subject: [PATCH 1632/2098] github/workflows: Bump actions/cache from 4 to 5. Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ports_esp32.yml | 2 +- .github/workflows/ports_zephyr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 6b3700ed5ee..95572eedfb6 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -41,7 +41,7 @@ jobs: - name: Cached ESP-IDF install id: cache_esp_idf - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ./esp-idf/ diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 812f4cbf47b..571a443e903 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -43,7 +43,7 @@ jobs: run: source tools/ci.sh && echo "ZEPHYR=$ZEPHYR_VERSION" | tee "$GITHUB_OUTPUT" - name: Cached Zephyr Workspace id: cache_workspace - uses: actions/cache@v4 + uses: actions/cache@v5 with: # note that the Zephyr CI docker image is 15GB. At time of writing # GitHub caches are limited to 10GB total for a project. So we only From 0aa6115aacf415da1f7039a341c0cd21799cdb0b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 9 Oct 2025 08:15:35 -0500 Subject: [PATCH 1633/2098] github/workflows: Introduce and use ci_esp32_idf_ver helper. Signed-off-by: Jeff Epler --- .github/workflows/ports_esp32.yml | 4 ++-- tools/ci.sh | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 95572eedfb6..92ac6df3b61 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -36,8 +36,8 @@ jobs: - uses: actions/checkout@v6 - id: idf_ver - name: Read the ESP-IDF version (including Python version) - run: source tools/ci.sh && echo "IDF_VER=${IDF_VER}-py${PYTHON_VER}" | tee "$GITHUB_OUTPUT" + name: Read the ESP-IDF version (including Python version) and set outputs.IDF_VER + run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" - name: Cached ESP-IDF install id: cache_esp_idf diff --git a/tools/ci.sh b/tools/ci.sh index ec4b57a8f56..8b122256601 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -206,6 +206,10 @@ PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) export IDF_CCACHE_ENABLE=1 +function ci_esp32_idf_ver { + echo "IDF_VER=${IDF_VER}-py${PYTHON_VER}" +} + function ci_esp32_idf_setup { echo "Using ESP-IDF version $IDF_VER" git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git From 6c7edce76cadb2c54dae5c9d6e48c78e50aa815d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 8 Oct 2025 08:39:24 -0500 Subject: [PATCH 1634/2098] github/workflows: Add esp32 to code size report. Add an esp32 build (specifically ESP32_GENERIC) to the CI code-size check. Multiple new steps must be done to prepare for building esp32, and caching is used to speed up both the install of the IDF and the build process. Signed-off-by: Jeff Epler --- .github/workflows/code_size.yml | 24 ++++++++++++++++++++++++ .github/workflows/ports_esp32.yml | 2 ++ tools/ci.sh | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 7d3329613fa..066f6806c48 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -30,6 +30,30 @@ jobs: fetch-depth: 100 - name: Install packages run: tools/ci.sh code_size_setup + + # Needs to be kept in synch with ports_esp32.yml + - id: idf_ver + name: Read the ESP-IDF version (including Python version) and set outputs.IDF_VER + run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" + - name: Cached ESP-IDF install + id: cache_esp_idf + uses: actions/cache@v4 + with: + path: | + ./esp-idf/ + ~/.espressif/ + !~/.espressif/dist/ + ~/.cache/pip/ + key: esp-idf-${{ steps.idf_ver.outputs.IDF_VER }} + - name: Install ESP-IDF packages + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: tools/ci.sh esp32_idf_setup + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: code_size + - name: Build run: tools/ci.sh code_size_build - name: Compute code size difference diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 92ac6df3b61..87ab6dbe355 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -35,6 +35,7 @@ jobs: steps: - uses: actions/checkout@v6 + # Needs to be kept in synch with code_size.yml - id: idf_ver name: Read the ESP-IDF version (including Python version) and set outputs.IDF_VER run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" @@ -54,6 +55,7 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: tools/ci.sh esp32_idf_setup + # Needs to be kept in synch with code_size.yml - name: ccache uses: hendrikmuhs/ccache-action@v1.2 with: diff --git a/tools/ci.sh b/tools/ci.sh index 8b122256601..85d736552a2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -87,7 +87,7 @@ function _ci_is_git_merge { function ci_code_size_build { # check the following ports for the change in their code size # Override the list by setting PORTS_TO_CHECK in the environment before invoking ci. - : ${PORTS_TO_CHECK:=bmusxpdv} + : ${PORTS_TO_CHECK:=bmus3xpdv} SUBMODULES="lib/asf4 lib/berkeley-db-1.xx lib/btstack lib/cyw43-driver lib/lwip lib/mbedtls lib/micropython-lib lib/nxp_driver lib/pico-sdk lib/stm32lib lib/tinyusb" From 569ebaa8283aa7ea28392943986413102c890b8c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 20:46:29 -0600 Subject: [PATCH 1635/2098] tools/ci.sh: Put embedding build rules in ci.sh. To make this CI step runnable locally by `ci.sh`. Signed-off-by: Jeff Epler --- .github/workflows/examples.yml | 4 +--- examples/embedding/.gitignore | 4 ++++ tools/ci.sh | 9 +++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 examples/embedding/.gitignore diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 4adeaae2e5e..4627247fb9f 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,6 +20,4 @@ jobs: steps: - uses: actions/checkout@v6 - name: Build - run: make -C examples/embedding -f micropython_embed.mk && make -C examples/embedding - - name: Run - run: ./examples/embedding/embed | grep "hello world" + run: tools/ci.sh embedding_build diff --git a/examples/embedding/.gitignore b/examples/embedding/.gitignore new file mode 100644 index 00000000000..78bd33eb13e --- /dev/null +++ b/examples/embedding/.gitignore @@ -0,0 +1,4 @@ +# Files created by ci.sh embed_build +embed +main.o +micropython_embed diff --git a/tools/ci.sh b/tools/ci.sh index 85d736552a2..82eb0942812 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -194,6 +194,15 @@ function ci_cc3200_build { make ${MAKEOPTS} -C ports/cc3200 BTARGET=bootloader BTYPE=release } +######################################################################################## +# ports/embed + +function ci_embedding_build { + make ${MAKEOPTS} -C examples/embedding -f micropython_embed.mk + make ${MAKEOPTS} -C examples/embedding + ./examples/embedding/embed | grep "hello world" +} + ######################################################################################## # ports/esp32 From 6dea53cb277c0cf37b2e749a21b56ea9ba6e0531 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 2 Jan 2026 10:27:33 +0100 Subject: [PATCH 1636/2098] tools/ci.sh: Update Unix/MIPS target to Ubuntu 24.04 LTS. This commit updates the Unix/MIPS target's environment to use the latest available LTS version of Ubuntu Linux (24.04). Since the new OS updated some key components used in the CI build process, a few modifications have been made to the setup and build procedure. Newer QEMU's sysroot location changed and the "static" variant of the QEMU emulators has to be used to make binfmt work, and updated autotools versions split some macros into an optional package that has to be manually installed. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_unix.yml | 8 ++++++-- tools/ci.sh | 14 +++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 6ef6c28d331..96fa1e76ec5 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -247,10 +247,14 @@ jobs: run: tests/run-tests.py --print-failures qemu_mips: - # ubuntu-22.04 is needed for older libffi. - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_qemu_mips_setup - name: Build diff --git a/tools/ci.sh b/tools/ci.sh index 82eb0942812..0e3111473e3 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -913,11 +913,11 @@ function ci_unix_macos_run_tests { function ci_unix_qemu_mips_setup { sudo apt-get update - sudo apt-get install gcc-mips-linux-gnu g++-mips-linux-gnu libc6-mips-cross - sudo apt-get install qemu-user - qemu-mips --version - sudo mkdir /etc/qemu-binfmt - sudo ln -s /usr/mips-linux-gnu/ /etc/qemu-binfmt/mips + sudo apt-get install gcc-mips-linux-gnu g++-mips-linux-gnu libc6-mips-cross libltdl-dev + sudo apt-get install qemu-user-static + qemu-mips-static --version + sudo mkdir -p /usr/gnemul + sudo ln -s /usr/mips-linux-gnu /usr/gnemul/qemu-mips } function ci_unix_qemu_mips_build { @@ -927,11 +927,11 @@ function ci_unix_qemu_mips_build { function ci_unix_qemu_mips_run_tests { # Issues with MIPS tests: - # - thread/stress_aes.py takes around 50 seconds + # - thread/stress_aes.py takes around 90 seconds # - thread/stress_recurse.py is flaky # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') } function ci_unix_qemu_arm_setup { From 16844bb027952aac701cb1dcb004dde7849de118 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 2 Jan 2026 10:33:13 +0100 Subject: [PATCH 1637/2098] tools/ci.sh: Update Unix/Arm target to Ubuntu 24.04 LTS. This commit updates the Unix/Arm target's environment to use the latest available LTS version of Ubuntu Linux (24.04). Since the new OS updated some key components used in the CI build process, a few modifications have been made to the setup and build procedure. Newer QEMU's sysroot location changed and the "static" variant of the QEMU emulators has to be used to make binfmt work, and updated autotools versions split some macros into an optional package that has to be manually installed. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_unix.yml | 8 ++++++-- tools/ci.sh | 13 ++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 96fa1e76ec5..f083a9d0f19 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -266,10 +266,14 @@ jobs: run: tests/run-tests.py --print-failures qemu_arm: - # ubuntu-22.04 is needed for older libffi. - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_qemu_arm_setup - name: Build diff --git a/tools/ci.sh b/tools/ci.sh index 0e3111473e3..f9f40e5dd4e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -936,11 +936,11 @@ function ci_unix_qemu_mips_run_tests { function ci_unix_qemu_arm_setup { sudo apt-get update - sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi - sudo apt-get install qemu-user - qemu-arm --version - sudo mkdir /etc/qemu-binfmt - sudo ln -s /usr/arm-linux-gnueabi/ /etc/qemu-binfmt/arm + sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libltdl-dev + sudo apt-get install qemu-user-static + qemu-arm-static --version + sudo mkdir -p /usr/gnemul + sudo ln -s /usr/arm-linux-gnueabi /usr/gnemul/qemu-arm } function ci_unix_qemu_arm_build { @@ -950,12 +950,11 @@ function ci_unix_qemu_arm_build { function ci_unix_qemu_arm_run_tests { # Issues with ARM tests: - # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) # - thread/stress_aes.py takes around 70 seconds # - thread/stress_recurse.py is flaky # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'vfs_posix.*\.py|thread/stress_recurse.py|thread/thread_gc1.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') } function ci_unix_qemu_riscv64_setup { From 31fe86546092b1fa07904c7f6ede6e2a29c0a0be Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 5 Jan 2026 14:25:55 +0100 Subject: [PATCH 1638/2098] tools/ci.sh: Use CPython 3.11 for Unix/RV64. This commit forces the installation of CPython 3.11 instead of CPython 3.12 in the OS image for Unix/RV64 CI jobs. CPython 3.12 is not compatible with settrace tests, but it is the CPython version that is installed by default in "ubuntu-latest" (which is Ubuntu 24.04 LTS right now). Updating the base image for the RV64 tests also disabled settrace tests to work around its incompatibility, however turns out there is a way to force CI to set up a base OS image with an arbitrary CPython version. Now the RV64 CI jobs are now going to be executed using CPython 3.11, and thus the settrace tests can be removed from the ignore list. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_unix.yml | 5 +++++ tools/ci.sh | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index f083a9d0f19..5f9aa26d1b9 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -288,6 +288,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_qemu_riscv64_setup - name: Build diff --git a/tools/ci.sh b/tools/ci.sh index f9f40e5dd4e..31ef77c2603 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -961,8 +961,8 @@ function ci_unix_qemu_riscv64_setup { ci_gcc_riscv_setup sudo apt-get update sudo apt-get install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libltdl-dev - sudo pip3 install pyelftools - sudo pip3 install ar + python3 -m pip install pyelftools + python3 -m pip install ar sudo apt-get install qemu-user-static qemu-riscv64-static --version sudo mkdir -p /usr/gnemul @@ -977,13 +977,12 @@ function ci_unix_qemu_riscv64_build { function ci_unix_qemu_riscv64_run_tests { # Issues with RISCV-64 tests: - # - misc/sys_settrace_features.py doesn't work with CPython 3.12 # - thread/stress_aes.py takes around 180 seconds # - thread/stress_recurse.py is flaky # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython pushd tests - MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=200 ./run-tests.py --exclude 'misc/sys_settrace_features.py|thread/stress_recurse.py|thread/thread_gc1.py' + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=200 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py' MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-natmodtests.py extmod/btree*.py extmod/deflate*.py extmod/framebuf*.py extmod/heapq*.py extmod/random_basic*.py extmod/re*.py popd } From 26c16969ab954db4d8d79bed154e3d45c12c087f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 6 Jan 2026 09:12:39 -0600 Subject: [PATCH 1639/2098] github/workflows: Use same Ubuntu for code_size as ports_esp32. Also make sure code_size runs when the esp32 port source changes, as per the other ports that are built as part of code_size. Signed-off-by: Jeff Epler Signed-off-by: Damien George --- .github/workflows/code_size.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 066f6806c48..20f47a8a62e 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -10,6 +10,7 @@ on: - 'shared/**' - 'lib/**' - 'ports/bare-arm/**' + - 'ports/esp32/**' - 'ports/mimxrt/**' - 'ports/minimal/**' - 'ports/rp2/**' @@ -23,7 +24,7 @@ concurrency: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: From 21e4c036afc2798871f1d61331e639140f6f5e95 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 Jan 2026 12:04:41 +0100 Subject: [PATCH 1640/2098] tools/ci.sh: Update Unix/x86 target to Ubuntu 24.04 LTS. This commit updates the Unix/x86 target's environment to use the latest available LTS version of Ubuntu Linux (24.04). Since the new OS updated some key components used in the CI build process, a few modifications have been made to the setup and build procedure. The new OS introduces a CPython version that is known to not be compatible with a subset of settrace tests, so even though the OS is updated, an older version of CPython is installed as part of the image provisioning process. Signed-off-by: Alessandro Gatti --- .github/workflows/ports_unix.yml | 28 ++++++++++++++++++++++++---- tools/ci.sh | 5 ++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 5f9aa26d1b9..bd04163ded8 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -103,9 +103,14 @@ jobs: run: tests/run-tests.py --print-failures coverage_32bit: - runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -121,9 +126,14 @@ jobs: run: tests/run-tests.py --print-failures nanbox: - runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -135,9 +145,14 @@ jobs: run: tests/run-tests.py --print-failures longlong: - runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build @@ -218,9 +233,14 @@ jobs: run: tests/run-tests.py --print-failures repr_b: - runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + # Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests. + # Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default. + with: + python-version: '3.11' - name: Install packages run: tools/ci.sh unix_32bit_setup - name: Build diff --git a/tools/ci.sh b/tools/ci.sh index 31ef77c2603..588bb31638c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -777,9 +777,8 @@ function ci_unix_32bit_setup { sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 - sudo pip3 install setuptools - sudo pip3 install pyelftools - sudo pip3 install ar + python -m pip install pyelftools + python -m pip install ar gcc --version python3 --version } From 0fd084363a81c369a1487acd62f41f69199a7dae Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 21 Nov 2025 09:26:23 +0100 Subject: [PATCH 1641/2098] tools/mpy_ld.py: Optimise MPY trampoline sizes if possible. This commit changes the way native modules' trampoline code sequence is emitted, generating an optimised code sequence to jump to the entry symbol. Turns out the address of the entry point is known even before the segments are built and the address of the entry point doesn't change when processing the module on anything but Xtensa. This means that the jump trampoline doesn't have to be a dummy fixed-size block to be filled in later, but it can be the final trampoline being used in the module. On Xtensa the address of the symbol is offset by the length of the literals pool, but since the trampoline being generated is always the shortest one said platform is left alone (handling distances greater than 128KiB would require more extensive changes). Signed-off-by: Alessandro Gatti --- tools/mpy_ld.py | 119 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 39 deletions(-) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index b363c6a25f7..f35a7f78622 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -131,66 +131,93 @@ # Architecture configuration +def fit_signed(bits, value): + return (value >> bits) == 0 or (value >> bits) == -1 + + +# Note: all trampoline jump function arguments are raw offsets calculated from +# the start of the text segment with no relocation applied beforehand. + + def asm_jump_x86(entry): - return struct.pack("> 11 == 0 or b_off >> 11 == -1: + if fit_signed(11, entry): # Signed value fits in 12 bits. - b0 = 0xE000 | (b_off >> 1 & 0x07FF) - b1 = 0 - b2 = 0 - b3 = 0 + b0 = 0xE000 | ((entry >> 1) & 0x07FF) + return struct.pack(" # pop {r0, pc} - b_off -= 2 # skip "push {r0, lr}" + entry += 2 # skip "push {r0, lr}" b0 = 0xB400 | 0x0100 | 0x0001 # push, lr, r0 - b1 = 0xF000 | (((b_off) >> 12) & 0x07FF) - b2 = 0xF800 | (((b_off) >> 1) & 0x07FF) + b1 = 0xF000 | ((entry >> 12) & 0x07FF) + b2 = 0xF800 | ((entry >> 1) & 0x07FF) b3 = 0xBC00 | 0x0100 | 0x0001 # pop, pc, r0 - return struct.pack("> 11 == 0 or b_off >> 11 == -1: + if fit_signed(11, entry): # Signed value fits in 12 bits - b0 = 0xE000 | (b_off >> 1 & 0x07FF) - b1 = 0 + b0 = 0xE000 | ((entry >> 1) & 0x07FF) + return struct.pack("> 12 & 0x07FF) - b1 = 0xB800 | (b_off >> 1 & 0x7FF) - return struct.pack("> 12) & 0x07FF) + b1 = 0xB800 | ((entry >> 1) & 0x07FF) + return struct.pack("> 8) + if fit_signed(17, entry): + jump_op = (entry - 4) << 6 | 6 + return struct.pack("> 8) + else: + raise LinkError("Large jumps are not yet supported on Xtensa") def asm_jump_riscv(entry): - # This could be 6 bytes shorter, but the code currently cannot - # support a trampoline with varying length depending on the offset. - - # auipc t6, HI(entry) - # jalr zero, t6, LO(entry) - upper, lower = split_riscv_address(entry) - return struct.pack( - "> 2) + | ((entry & 0x80) >> 1) + | ((entry & 0x40) << 1) + | ((entry & 0x20) >> 3) + | ((entry & 0x10) << 7), + ) + else: + # auipc t6, HI(entry) + # jalr zero, t6, LO(entry) + upper, lower = split_riscv_address(entry + 8) + return struct.pack( + " Date: Fri, 28 Nov 2025 06:25:57 +0100 Subject: [PATCH 1642/2098] tools/mpy_ld.py: Write architecture flags to output natmod if needed. This commit lets "tools/mpy_ld.py" store architecture flags in generated MPY files if explicitly requested, like "mpy-cross" does. To achieve this, a new command-line option ("--arch-flags") was added to receive the architecture flags value, accepting the same arguments' format as "mpy-cross", and performing the same input validation. The rest of the MPY toolchain was also modified to let the user pass the arch flags to standard native module makefiles. Given that there's already a well-established "ARCH" argument, "ARCH_FLAGS" was chosen to pass the optional flags to "mpy_ld.py". Finally, documentation was updated to mention the new variable. Signed-off-by: Alessandro Gatti --- docs/develop/natmod.rst | 16 +++++++++--- docs/reference/mpyfiles.rst | 3 ++- py/dynruntime.mk | 3 +++ tools/mpy_ld.py | 51 ++++++++++++++++++++++++++++++++----- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index 142e475366f..e0f7bdaaa89 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -43,9 +43,14 @@ options for the ``ARCH`` variable, see below): * ``rv32imc`` (RISC-V 32 bits with compressed instructions, eg ESP32C3, ESP32C6) * ``rv64imc`` (RISC-V 64 bits with compressed instructions) +If the chosen platform supports explicit architecture flags and you want to let +the output .mpy file carry those flags' value, you must pass them to the +``ARCH_FLAGS`` flags variable when building the .mpy file. + When compiling and linking the native .mpy file the architecture must be chosen -and the corresponding file can only be imported on that architecture. For more -details about .mpy files see :ref:`mpy_files`. +and the corresponding file can only be imported on that architecture (and if +architecture flags are present, only if they match the target's capabilities). +For more details about .mpy files see :ref:`mpy_files`. Native code must be compiled as position independent code (PIC) and use a global offset table (GOT), although the details of this varies from architecture to @@ -124,7 +129,8 @@ The filesystem layout consists of two main parts, the source files and the Makef location of the MicroPython repository (to find header files, the relevant Makefile fragment, and the ``mpy_ld.py`` tool), ``MOD`` as the name of the module, ``SRC`` as the list of source files, optionally specify the machine architecture via ``ARCH``, - and then include ``py/dynruntime.mk``. + along with optional machine architecture flags specified via ``ARCH_FLAGS``, and + then include ``py/dynruntime.mk``. Minimal example --------------- @@ -217,6 +223,10 @@ Without modifying the Makefile you can specify the target architecture via:: $ make ARCH=armv7m +Same applies for optional architecture flags via:: + + $ make ARCH=rv32imc ARCH_FLAGS=zba + Module usage in MicroPython --------------------------- diff --git a/docs/reference/mpyfiles.rst b/docs/reference/mpyfiles.rst index 58e45e15852..978a798350a 100644 --- a/docs/reference/mpyfiles.rst +++ b/docs/reference/mpyfiles.rst @@ -193,7 +193,8 @@ MPY files is used to indicate that no specific extensions are needed, and saves one byte in the final output binary. See also the ``-march-flags`` command-line option in both ``mpy-tool.py`` and -``mpy-cross`` to set this value when creating MPY files. +``mpy-cross``, and the ``--arch-flags`` command-line option in ``mpy_ld.py`` to +set this value when creating MPY files. The global qstr and constant tables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 866d5fae7b1..3902acbd0b3 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -194,6 +194,9 @@ endif ifneq ($(MPY_EXTERN_SYM_FILE),) MPY_LD_FLAGS += --externs "$(realpath $(MPY_EXTERN_SYM_FILE))" endif +ifneq ($(ARCH_FLAGS),) +MPY_LD_FLAGS += --arch-flags "$(ARCH_FLAGS)" +endif CFLAGS += $(CFLAGS_EXTRA) diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index f35a7f78622..02d2431c4ee 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -38,6 +38,7 @@ # MicroPython constants MPY_VERSION = 6 MPY_SUB_VERSION = 3 +MPY_ARCH_FLAGS = 0x40 MP_CODE_BYTECODE = 2 MP_CODE_NATIVE_VIPER = 4 MP_NATIVE_ARCH_X86 = 1 @@ -1379,7 +1380,7 @@ def write_reloc(self, base, offset, dest, n): self.write_uint(n) -def build_mpy(env, fmpy, native_qstr_vals): +def build_mpy(env, fmpy, native_qstr_vals, arch_flags): # Rewrite the entry trampoline if the proper value isn't known earlier, and # ensure the trampoline size remains the same. if env.arch.delayed_entry_offset: @@ -1399,12 +1400,16 @@ def build_mpy(env, fmpy, native_qstr_vals): out = MPYOutput() out.open(fmpy) + header_flags = env.arch.mpy_feature | MPY_SUB_VERSION + if arch_flags != 0: + header_flags |= MPY_ARCH_FLAGS + # MPY: header - out.write_bytes( - bytearray( - [ord("M"), MPY_VERSION, env.arch.mpy_feature | MPY_SUB_VERSION, MP_SMALL_INT_BITS] - ) - ) + out.write_bytes(bytearray([ord("M"), MPY_VERSION, header_flags, MP_SMALL_INT_BITS])) + + # MPY: arch flags + if arch_flags != 0: + out.write_uint(arch_flags) # MPY: n_qstr out.write_uint(1 + len(native_qstr_vals)) @@ -1553,7 +1558,7 @@ def do_link(args): load_object_file(env, f, obj_name) link_objects(env, len(native_qstr_vals)) - build_mpy(env, args.output, native_qstr_vals) + build_mpy(env, args.output, native_qstr_vals, args.arch_flags) except LinkError as er: print("LinkError:", er.args[0]) sys.exit(1) @@ -1603,6 +1608,35 @@ def parse_linkerscript(source): return symbols +RV32_EXTENSIONS = { + "zba": 1 << 0, + "zcmp": 1 << 1, +} + + +def validate_arch_flags(args): + if args.arch_flags is None: + args.arch_flags = 0 + return + if args.arch != "rv32imc": + raise ValueError('Architecture "{}" does not support extra flags'.format(args.arch)) + if (args.arch_flags.startswith("0") and len(args.arch_flags) > 2) or args.arch_flags.isdigit(): + if args.arch_flags[1] in "bB": + base = 2 + elif args.arch_flags[1] in "xX": + base = 16 + else: + base = 10 + args.arch_flags = int(args.arch_flags, base) + else: + flags_value = 0 + for flag in args.arch_flags.lower().split(","): + if flag not in RV32_EXTENSIONS: + raise ValueError('Invalid architecture flags value "{}"'.format(flag)) + flags_value |= RV32_EXTENSIONS[flag] + args.arch_flags = flags_value + + def main(): import argparse @@ -1611,6 +1645,7 @@ def main(): "--verbose", "-v", action="count", default=1, help="increase verbosity" ) cmd_parser.add_argument("--arch", default="x64", help="architecture") + cmd_parser.add_argument("--arch-flags", default=None, help="optional architecture flags") cmd_parser.add_argument("--preprocess", action="store_true", help="preprocess source files") cmd_parser.add_argument("--qstrs", default=None, help="file defining additional qstrs") cmd_parser.add_argument( @@ -1632,6 +1667,8 @@ def main(): global log_level log_level = args.verbose + validate_arch_flags(args) + if args.preprocess: do_preprocess(args) else: From 83996f057fecfdfdded66eeff2aa6193ee7a2f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:06:20 +0000 Subject: [PATCH 1643/2098] github/workflows: Bump actions/cache from 4 to 5. Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code_size.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 20f47a8a62e..cf6838b7ae1 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -38,7 +38,7 @@ jobs: run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" - name: Cached ESP-IDF install id: cache_esp_idf - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ./esp-idf/ From 3a487c32ba858278d3231b9c57529f1d4ba13810 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Sep 2025 12:01:19 +1000 Subject: [PATCH 1644/2098] all: Fix spelling of Micropython -> MicroPython. Signed-off-by: Damien George --- ports/mimxrt/machine_pwm.c | 2 +- ports/zephyr/README.md | 8 ++++---- ports/zephyr/src/usbd.c | 2 +- tools/mpremote/mpremote/mip.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/mimxrt/machine_pwm.c b/ports/mimxrt/machine_pwm.c index df76ed2b8bd..9a2e39fd827 100644 --- a/ports/mimxrt/machine_pwm.c +++ b/ports/mimxrt/machine_pwm.c @@ -375,7 +375,7 @@ static void configure_pwm(machine_pwm_obj_t *self) { } } -// Micropython API functions +// MicroPython API functions // static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index f3fcd8f70d3..be5e322ca55 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -211,8 +211,8 @@ run the following after you built an image with the previous command: File Systems ------------ -The Zephyr Micropython port provides 2 options for handling filesystems on the device: -The first is the Micropython filesystem management, which uses Micropython's filesystem code and +The Zephyr MicroPython port provides 2 options for handling filesystems on the device: +The first is the MicroPython filesystem management, which uses MicroPython's filesystem code and relies on zephyr's FlashArea API, this is enabled by default when `CONFIG_FLASH` and `CONFIG_FLASH_MAP` are turned on. The second option is using Zephyr's Filesystem management: @@ -241,13 +241,13 @@ Then, a fstab must be added to the dts overlay, for example: }; }; -It is then possible to use the FS like a normal Micropython filesystem: +It is then possible to use the FS like a normal MicroPython filesystem: import vfs, zephyr zfs = zephyr.FileSystem(zephyr.FileSystem.fstab()[0]) vfs.mount(zfs, "/zephyr") -You may disable Micropython's File system code to save space: +You may disable MicroPython's File system code to save space: CONFIG_MICROPY_VFS_FAT=n CONFIG_MICROPY_VFS_LFS1=n diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c index 36b07a8638f..118463a0756 100644 --- a/ports/zephyr/src/usbd.c +++ b/ports/zephyr/src/usbd.c @@ -56,7 +56,7 @@ USBD_DEVICE_DEFINE(mp_usbd, USBD_DESC_LANG_DEFINE(mp_lang); USBD_DESC_MANUFACTURER_DEFINE(mp_mfr, "Zephyr Project"); -USBD_DESC_PRODUCT_DEFINE(mp_product, "Micropython on Zephyr RTOS"); +USBD_DESC_PRODUCT_DEFINE(mp_product, "MicroPython on Zephyr RTOS"); USBD_DESC_SERIAL_NUMBER_DEFINE(mp_sn); USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration"); diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index fa7974053f4..0d12ae651ec 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -1,4 +1,4 @@ -# Micropython package installer +# MicroPython package installer # Ported from micropython-lib/micropython/mip/mip.py. # MIT license; Copyright (c) 2022 Jim Mussared From e52916daaa455cc338ad01ad541eb6dc81a19a0d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Sep 2025 11:41:24 +1000 Subject: [PATCH 1645/2098] github/workflows: Add check for misspelling of "MicroPython". "MicroPython" is sometimes misspelled as "Micropython". Add an explicit check for that as part of CI (note that codespell doesn't consider case when spelling, hence the need to do it this way). Signed-off-by: Damien George --- .github/workflows/codespell.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index c413a430a5b..e3a9c79bd5e 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -10,4 +10,9 @@ jobs: # codespell version should be kept in sync with .pre-commit-config.yml - run: pip install --user codespell==2.4.1 tomli - run: codespell - + # additionally check for misspelling of "MicroPython" + - run: | + if git grep -n Micropython -- ":(exclude).github/workflows/codespell.yml"; then + echo "Please correct capitalisation of MicroPython on the above lines" + exit 1 + fi From b105677ac49b1d240d9e735ff9616aa78edd71fe Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Dec 2025 11:58:11 +1100 Subject: [PATCH 1646/2098] esp32/machine_pin: Rename legacy CONFIG_ESP32_SPIRAM_SUPPORT option. Signed-off-by: Damien George --- ports/esp32/machine_pin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/machine_pin.h b/ports/esp32/machine_pin.h index 4e21b032e66..c63630eb85d 100644 --- a/ports/esp32/machine_pin.h +++ b/ports/esp32/machine_pin.h @@ -49,7 +49,7 @@ #define MICROPY_HW_ENABLE_GPIO13 (1) #define MICROPY_HW_ENABLE_GPIO14 (1) #define MICROPY_HW_ENABLE_GPIO15 (1) -#if !CONFIG_ESP32_SPIRAM_SUPPORT +#if !CONFIG_SPIRAM #define MICROPY_HW_ENABLE_GPIO16 (1) #define MICROPY_HW_ENABLE_GPIO17 (1) #endif From 85a15f995c661d6499b89aa8fdb21681db9f09b6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Dec 2025 11:58:42 +1100 Subject: [PATCH 1647/2098] esp32/machine_uart: Change default UART(1) pins on ESP32 with SPIRAM. SPIRAM usually uses GPIO 9 and 10 on ESP32, so don't use them as default UART pins. Fixes issue #18544. Signed-off-by: Damien George --- docs/esp32/quickref.rst | 3 +++ ports/esp32/machine_uart.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 0cf7d81a7ea..b4961fd4ed4 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -380,6 +380,9 @@ tx 1 10 17 rx 3 9 16 ===== ===== ===== ===== +On ESP32 with SPIRAM, the default pins for UART1 are ``tx=5`` and ``rx=4`` +to avoid possible conflicts with the SPIRAM pins. + PWM (pulse width modulation) ---------------------------- diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index a2034b74927..4a0782f7443 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -278,8 +278,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, self->tx = UART_PIN_NO_CHANGE; // GPIO 1 break; case UART_NUM_1: + #if CONFIG_IDF_TARGET_ESP32 && CONFIG_SPIRAM + // ESP32 usually uses pins 9 and 10 for SPIRAM bus, so avoid those pins as defaults. + self->rx = 4; + self->tx = 5; + #else self->rx = 9; self->tx = 10; + #endif break; #if SOC_UART_HP_NUM > 2 case UART_NUM_2: From da6a18ffe04ff7a9538ffaa915bca4712beb9d63 Mon Sep 17 00:00:00 2001 From: Elvis Pfutzenreuter Date: Sat, 27 Dec 2025 12:10:33 -0300 Subject: [PATCH 1648/2098] esp32/esp32_rmt: Update FIXME on RMT module. Signed-off-by: Elvis Pfutzenreuter --- ports/esp32/esp32_rmt.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 85a98c2910b..8b77e2ac6fa 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -193,14 +193,13 @@ static void esp32_rmt_deactivate(esp32_rmt_obj_t *self) { if (self->enabled) { // FIXME: panics in ESP32 if called while TX is ongoing and TX sequence is long (>300ms) // Does not panic in ESP32-S3, ESP32-C3 and ESP32-C6. - // Tested with ESP-IDF up to 5.5 - // ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17692 + // Happens with ESP-IDF up to 5.5.1. Fixed in ESP-IDF 5.5.2. + // ESP-IDF GitHub issue: https://github.com/espressif/esp-idf/issues/17692 // - // Cause is Interrupt WDT to trigger because ESP-IDF rmt_disable() disables - // interrupts and spinlocks until the ongoing TX sequence is finished. - // - // Workaround is never try to stop RMT sequences longer than 300ms (which are unusual - // anyway). Or apply the patch mentioned at the GitHub issue to ESP-IDF. + // Workarounds: + // - recompile with ESP-IDF 5.5.2 or better + // - never try to stop RMT sequences longer than 300ms + // - apply to ESP-IDF the patch mentioned at the GitHub issue rmt_disable(self->channel); self->enabled = false; } From 07540a8fd19a56106bc76dd8ab73c6efc7aaba07 Mon Sep 17 00:00:00 2001 From: FH Date: Thu, 1 Jan 2026 21:12:46 +0100 Subject: [PATCH 1649/2098] esp32/modesp32: Update available RTC pins for ESP32C6. According to https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/#demo1-deep-sleep-with-external-wake-up and https://docs.espressif.com/projects/esp-idf/en/stable/esp32c6/api-reference/peripherals/gpio.html ESP32C6 supports RTC/LP on Pins 0-7. Signed-off-by: FH --- ports/esp32/modesp32.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 60c386565b8..6b6a6578a75 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -32,6 +32,21 @@ ) #define RTC_LAST_EXT_PIN 21 +#elif CONFIG_IDF_TARGET_ESP32C6 + + #define RTC_VALID_EXT_PINS \ + ( \ + (1ll << 0) | \ + (1ll << 1) | \ + (1ll << 2) | \ + (1ll << 3) | \ + (1ll << 4) | \ + (1ll << 5) | \ + (1ll << 6) | \ + (1ll << 7) \ + ) + #define RTC_LAST_EXT_PIN 7 + #else #define RTC_VALID_EXT_PINS \ From e48b98537fdf0000c6ea3b03d0a6ce831431795c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 17 Dec 2025 14:39:06 +1100 Subject: [PATCH 1650/2098] stm32/main: Enable all AHB5 GRP1 clocks in low power mode. The main functional change here is to make sure that the SDMMC1/2 clocks are enabled in low power mode; they were not previously enabled for SD card use, only WLAN. It doesn't hurt to unconditionally enable the clocks in low power, like all the other peripherals. Signed-off-by: Damien George --- ports/stm32/eth.c | 5 ----- ports/stm32/main.c | 2 +- ports/stm32/sdio.c | 4 ---- ports/stm32/usbd_conf.c | 2 -- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 60f2a23deca..7baaa89c627 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -370,11 +370,6 @@ static int eth_mac_init(eth_t *self) { __HAL_RCC_ETH1RX_CLK_SLEEP_ENABLE(); #elif defined(STM32N6) __HAL_RCC_ETH1_RELEASE_RESET(); - - __HAL_RCC_ETH1_CLK_SLEEP_ENABLE(); - __HAL_RCC_ETH1MAC_CLK_SLEEP_ENABLE(); - __HAL_RCC_ETH1TX_CLK_SLEEP_ENABLE(); - __HAL_RCC_ETH1RX_CLK_SLEEP_ENABLE(); #else __HAL_RCC_ETHMAC_RELEASE_RESET(); diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 8085a5e2576..6ae8061c413 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -409,13 +409,13 @@ void stm32_main(uint32_t reset_mode) { LL_MEM_EnableClockLowPower(LL_MEM_AXISRAM1 | LL_MEM_AXISRAM2 | LL_MEM_AXISRAM3 | LL_MEM_AXISRAM4 | LL_MEM_AXISRAM5 | LL_MEM_AXISRAM6 | LL_MEM_AHBSRAM1 | LL_MEM_AHBSRAM2 | LL_MEM_BKPSRAM | LL_MEM_FLEXRAM | LL_MEM_CACHEAXIRAM | LL_MEM_VENCRAM | LL_MEM_BOOTROM); - LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_XSPI2 | LL_AHB5_GRP1_PERIPH_XSPIM); LL_APB4_GRP1_EnableClock(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); LL_APB4_GRP1_EnableClockLowPower(LL_APB4_GRP1_PERIPH_RTC | LL_APB4_GRP1_PERIPH_RTCAPB); // Enable some AHB peripherals during sleep. LL_AHB1_GRP1_EnableClockLowPower(LL_AHB1_GRP1_PERIPH_ALL); // GPDMA1, ADC12 LL_AHB4_GRP1_EnableClockLowPower(LL_AHB4_GRP1_PERIPH_ALL); // GPIOA-Q, PWR, CRC + LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_ALL); // DMA2D, ETH, FMC, GFXMMU, GPU2D, HPDMA, XSPI, JPEG, MCE, CACHEAXI, NPU, OTG, PSSI, SDMMC // Enable some APB peripherals during sleep. LL_APB1_GRP1_EnableClockLowPower(LL_APB1_GRP1_PERIPH_ALL); // I2C, I3C, LPTIM, SPI, TIM, UART, WWDG diff --git a/ports/stm32/sdio.c b/ports/stm32/sdio.c index de82ceadc5f..4d18102e154 100644 --- a/ports/stm32/sdio.c +++ b/ports/stm32/sdio.c @@ -131,10 +131,6 @@ void sdio_init(uint32_t irq_pri) { mp_hal_pin_config_alt_static(MICROPY_HW_SDIO_CMD, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, STATIC_AF_SDMMC_CMD); SDMMC_CLK_ENABLE(); // enable SDIO peripheral - #if defined(STM32N6) - LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_SDMMC1); - LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_SDMMC2); - #endif SDMMC_TypeDef *SDIO = SDMMC; #if defined(STM32F7) diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 46d7985253d..d481aec1e49 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -279,8 +279,6 @@ static void mp_usbd_ll_init_hs(void) { LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_OTG1); LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_OTGPHY1); - LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_OTG1); - LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_OTGPHY1); // Select 24MHz clock. MODIFY_REG(USB1_HS_PHYC->USBPHYC_CR, USB_USBPHYC_CR_FSEL, 2 << USB_USBPHYC_CR_FSEL_Pos); From bc7d264e869aa39d9789ea330614981c1f38e49e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 17 Dec 2025 14:40:24 +1100 Subject: [PATCH 1651/2098] stm32/sdcard: Use high speed mode for SD transfers on H5/H7/N6. This doubles the speed of SD card transfers. Signed-off-by: Damien George --- ports/stm32/sdcard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index b91fa3a9c28..3e755bf9f26 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -105,7 +105,7 @@ #define SDIO_HARDWARE_FLOW_CONTROL_ENABLE SDMMC_HARDWARE_FLOW_CONTROL_ENABLE #if defined(STM32H5) || defined(STM32H7) || defined(STM32N6) -#define SDIO_TRANSFER_CLK_DIV SDMMC_NSpeed_CLK_DIV +#define SDIO_TRANSFER_CLK_DIV SDMMC_HSPEED_CLK_DIV #define SDIO_USE_GPDMA 0 #else #define SDIO_TRANSFER_CLK_DIV SDMMC_TRANSFER_CLK_DIV From e9bd1e2e082b2fddc5b67574ecf88025203f86a4 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Thu, 19 Aug 2021 00:35:24 +0200 Subject: [PATCH 1652/2098] stm32: Add support for all STM32F412xx MCUs. And fix typo in the usage string of plli2svalues.py. Signed-off-by: Oliver Joos --- ports/stm32/adc.c | 4 +++- ports/stm32/boards/plli2svalues.py | 5 ++++- ports/stm32/timer.c | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index e9be7021322..a5163537e05 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -134,7 +134,9 @@ defined(STM32F407xx) || defined(STM32F417xx) || \ defined(STM32F401xC) || defined(STM32F401xE) #define VBAT_DIV (2) -#elif defined(STM32F411xE) || defined(STM32F412Zx) || \ +#elif defined(STM32F411xE) || \ + defined(STM32F412Cx) || defined(STM32F412Rx) || \ + defined(STM32F412Vx) || defined(STM32F412Zx) || \ defined(STM32F413xx) || defined(STM32F427xx) || \ defined(STM32F429xx) || defined(STM32F437xx) || \ defined(STM32F439xx) || defined(STM32F446xx) || \ diff --git a/ports/stm32/boards/plli2svalues.py b/ports/stm32/boards/plli2svalues.py index e5ea4e8dfd4..f872c60ebb5 100644 --- a/ports/stm32/boards/plli2svalues.py +++ b/ports/stm32/boards/plli2svalues.py @@ -24,6 +24,9 @@ def __init__(self, range_plli2sn, range_plli2sr): "stm32f401xe", "stm32f407xx", "stm32f411xe", + "stm32f412cx", + "stm32f412rx", + "stm32f412vx", "stm32f412zx", "stm32f413xx", "stm32f427xx", @@ -163,7 +166,7 @@ def main(): if mcu_series in mcu_support_plli2s: if len(argv) not in (1, 2): - print("usage: pllvalues.py [-c] [-m ] ") + print("usage: plli2svalues.py [-c] [-m ] ") sys.exit(1) if argv[0].startswith("hse:"): diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index aa410928298..53080617324 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -889,7 +889,7 @@ static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { #endif #if defined(TIM6) - #if defined(STM32F412Zx) || defined(STM32L1) + #if defined(STM32F412Cx) || defined(STM32F412Rx) || defined(STM32F412Vx) || defined(STM32F412Zx) || defined(STM32L1) TIM_ENTRY(6, TIM6_IRQn), #elif defined(STM32G0) TIM_ENTRY(6, TIM6_DAC_LPTIM1_IRQn), From 4e98486830adf0be003701ec2f2bae4424f9875d Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Thu, 19 Aug 2021 00:38:19 +0200 Subject: [PATCH 1653/2098] stm32/boards: Add linker script for STMF412xE with 512k flash. The existing linker script for F412 is renamed to separate F412xG (with 1MB RAM) from F412xE (with 512K). Signed-off-by: Oliver Joos --- .../boards/NUCLEO_F412ZG/mpconfigboard.mk | 2 +- ports/stm32/boards/stm32f412xe.ld | 35 +++++++++++++++++++ .../boards/{stm32f412zx.ld => stm32f412xg.ld} | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 ports/stm32/boards/stm32f412xe.ld rename ports/stm32/boards/{stm32f412zx.ld => stm32f412xg.ld} (96%) diff --git a/ports/stm32/boards/NUCLEO_F412ZG/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_F412ZG/mpconfigboard.mk index 2fbb4ddbe2d..1deebe6f1e9 100644 --- a/ports/stm32/boards/NUCLEO_F412ZG/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_F412ZG/mpconfigboard.mk @@ -1,7 +1,7 @@ MCU_SERIES = f4 CMSIS_MCU = STM32F412Zx AF_FILE = boards/stm32f412_af.csv -LD_FILES = boards/stm32f412zx.ld boards/common_ifs.ld +LD_FILES = boards/stm32f412xg.ld boards/common_ifs.ld TEXT0_ADDR = 0x08000000 TEXT1_ADDR = 0x08020000 diff --git a/ports/stm32/boards/stm32f412xe.ld b/ports/stm32/boards/stm32f412xe.ld new file mode 100644 index 00000000000..15b8d78f15f --- /dev/null +++ b/ports/stm32/boards/stm32f412xe.ld @@ -0,0 +1,35 @@ +/* + GNU linker script for STM32F412xe (512kB flash, 256kB RAM) +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* entire flash */ + FLASH_START (rx): ORIGIN = 0x08000000, LENGTH = 16K /* sector 0 */ + FLASH_FS (rx) : ORIGIN = 0x08004000, LENGTH = 64K /* sectors 1,2,3,4: 16k+16k+16k+16k(of 64k)=64k */ + FLASH_TEXT (rx) : ORIGIN = 0x08020000, LENGTH = 384K /* sectors 5,6,7 are 128K */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 240K + FS_CACHE (xrw) : ORIGIN = 0x2003c000, LENGTH = 16K +} + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM extents for the garbage collector */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; + +/* Filesystem cache in RAM, and storage in flash */ +_micropy_hw_internal_flash_storage_ram_cache_start = ORIGIN(FS_CACHE); +_micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(FS_CACHE) + LENGTH(FS_CACHE); +_micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); +_micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); diff --git a/ports/stm32/boards/stm32f412zx.ld b/ports/stm32/boards/stm32f412xg.ld similarity index 96% rename from ports/stm32/boards/stm32f412zx.ld rename to ports/stm32/boards/stm32f412xg.ld index b67f1c3e253..5b5ccb1d423 100644 --- a/ports/stm32/boards/stm32f412zx.ld +++ b/ports/stm32/boards/stm32f412xg.ld @@ -1,5 +1,5 @@ /* - GNU linker script for STM32F412zx (1MB flash, 256kB RAM) + GNU linker script for STM32F412xg (1MB flash, 256kB RAM) */ /* Specify the memory areas */ From ee0b5ab2dc4f39376b062d883739bc38c366a9c5 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Thu, 27 Nov 2025 14:33:40 +0100 Subject: [PATCH 1654/2098] stm32/boards/NUCLEO_H753ZI: Add NUCLEO_H753ZI board support. Configuration: - Clock is 8MHz from STLINK MCO, CPU runs at 400MHz - REPL on USB and on UART connected to ST-Link interface - Storage is configured for internal flash memory - 3x LEDs and 1x user button - Ethernet Product page: https://www.st.com/en/evaluation-tools/nucleo-h753zi.html Signed-off-by: Oliver Joos --- ports/stm32/adc.c | 2 +- ports/stm32/boards/NUCLEO_H753ZI/board.json | 15 ++ ports/stm32/boards/NUCLEO_H753ZI/board_init.c | 1 + .../boards/NUCLEO_H753ZI/mpconfigboard.h | 7 + .../boards/NUCLEO_H753ZI/mpconfigboard.mk | 5 + ports/stm32/boards/NUCLEO_H753ZI/pins.csv | 130 ++++++++++++++++++ .../boards/NUCLEO_H753ZI/stm32h7xx_hal_conf.h | 1 + ports/stm32/mboot/main.c | 2 +- ports/stm32/stm32.mk | 2 +- 9 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/board.json create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/board_init.c create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.h create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.mk create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/pins.csv create mode 100644 ports/stm32/boards/NUCLEO_H753ZI/stm32h7xx_hal_conf.h diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index a5163537e05..ec55175af73 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -156,7 +156,7 @@ defined(STM32H743xx) || defined(STM32H747xx) || \ defined(STM32H7A3xx) || defined(STM32H7A3xxQ) || \ defined(STM32H7B3xx) || defined(STM32H7B3xxQ) || \ - defined(STM32H750xx) + defined(STM32H750xx) || defined(STM32H753xx) #define VBAT_DIV (4) #elif defined(STM32L432xx) || \ defined(STM32L451xx) || defined(STM32L452xx) || \ diff --git a/ports/stm32/boards/NUCLEO_H753ZI/board.json b/ports/stm32/boards/NUCLEO_H753ZI/board.json new file mode 100644 index 00000000000..ec3e75c0de6 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/board.json @@ -0,0 +1,15 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [], + "images": [ + "nucleo_h753zi.jpg" + ], + "mcu": "stm32h7", + "product": "Nucleo H753ZI", + "thumbnail": "", + "url": "https://www.st.com/en/evaluation-tools/nucleo-h753zi.html", + "vendor": "ST Microelectronics" +} diff --git a/ports/stm32/boards/NUCLEO_H753ZI/board_init.c b/ports/stm32/boards/NUCLEO_H753ZI/board_init.c new file mode 100644 index 00000000000..04caaaca9e4 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/board_init.c @@ -0,0 +1 @@ +#include "boards/NUCLEO_H743ZI/board_init.c" diff --git a/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.h new file mode 100644 index 00000000000..a92fd6b0355 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.h @@ -0,0 +1,7 @@ +#include "boards/NUCLEO_H743ZI2/mpconfigboard.h" + +#undef MICROPY_HW_BOARD_NAME +#define MICROPY_HW_BOARD_NAME "NUCLEO_H753ZI" + +#undef MICROPY_HW_MCU_NAME +#define MICROPY_HW_MCU_NAME "STM32H753" diff --git a/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.mk new file mode 100644 index 00000000000..10c72088217 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/mpconfigboard.mk @@ -0,0 +1,5 @@ +FROZEN_MANIFEST ?= boards/NUCLEO_H743ZI/manifest.py + +include boards/NUCLEO_H743ZI/mpconfigboard.mk + +CMSIS_MCU = STM32H753xx diff --git a/ports/stm32/boards/NUCLEO_H753ZI/pins.csv b/ports/stm32/boards/NUCLEO_H753ZI/pins.csv new file mode 100644 index 00000000000..450d6e432ae --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/pins.csv @@ -0,0 +1,130 @@ +A0,PA3 +A1,PC0 +A2,PC3 +A3,PB1 +A4,PC2 +A5,PF10 +A6,PF4 +A7,PF5 +A8,PF6 +D0,PB7 +D1,PB6 +D2,PG14 +D3,PE13 +D4,PE14 +D5,PE11 +D6,PE9 +D7,PG12 +D8,PF3 +D9,PD15 +D10,PD14 +D11,PB5 +D12,PA6 +D13,PA7 +D14,PB9 +D15,PB8 +D16,PC6 +D17,PB15 +D18,PB13 +D19,PB12 +D20,PA15 +D21,PC7 +D22,PB5 +D23,PB3 +D24,PA4 +D25,PB4 +D26,PG6 +D27,PB2 +D28,PD13 +D29,PD12 +D30,PD11 +D31,PE2 +D32,PA0 +D33,PB0 +D34,PE0 +D35,PB11 +D36,PB10 +D37,PE15 +D38,PE6 +D39,PE12 +D40,PE10 +D41,PE7 +D42,PE8 +D43,PC8 +D44,PC9 +D45,PC10 +D46,PC11 +D47,PC12 +D48,PD2 +D49,PG2 +D50,PG3 +D51,PD7 +D52,PD6 +D53,PD5 +D54,PD4 +D55,PD3 +D56,PE2 +D57,PE4 +D58,PE5 +D59,PE6 +D60,PE3 +D61,PF8 +D62,PF7 +D63,PF9 +D64,PG1 +D65,PG0 +D66,PD1 +D67,PD0 +D68,PF0 +D69,PF1 +D70,PF2 +D71,PE9 +D72,PB2 +DAC1,PA4 +DAC2,PA5 +LED1,PB0 +LED2,PE1 +LED3,PB14 +SW,PC13 +I2C1_SDA,PB9 +I2C1_SCL,PB8 +I2C2_SDA,PF0 +I2C2_SCL,PF1 +I2C4_SCL,PF14 +I2C4_SDA,PF15 +SD_D0,PC8 +SD_D1,PC9 +SD_D2,PC10 +SD_D3,PC11 +SD_CMD,PD2 +SD_CK,PC12 +SD_SW,PG2 +OTG_FS_POWER,PD10 +OTG_FS_OVER_CURRENT,PG7 +USB_VBUS,PA9 +USB_ID,PA10 +USB_DM,PA11 +USB_DP,PA12 +UART2_TX,PD5 +UART2_RX,PD6 +UART2_RTS,PD4 +UART2_CTS,PD3 +UART3_TX,PD8 +UART3_RX,PD9 +UART5_TX,PB6 +UART5_RX,PB12 +UART6_TX,PC6 +UART6_RX,PC7 +UART7_TX,PF7 +UART7_RX,PF6 +UART8_TX,PE1 +UART8_RX,PE0 +ETH_MDC,PC1 +ETH_MDIO,PA2 +ETH_RMII_REF_CLK,PA1 +ETH_RMII_CRS_DV,PA7 +ETH_RMII_RXD0,PC4 +ETH_RMII_RXD1,PC5 +ETH_RMII_TX_EN,PG11 +ETH_RMII_TXD0,PG13 +ETH_RMII_TXD1,PB13 diff --git a/ports/stm32/boards/NUCLEO_H753ZI/stm32h7xx_hal_conf.h b/ports/stm32/boards/NUCLEO_H753ZI/stm32h7xx_hal_conf.h new file mode 100644 index 00000000000..61f202e6298 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_H753ZI/stm32h7xx_hal_conf.h @@ -0,0 +1 @@ +#include "boards/NUCLEO_H743ZI/stm32h7xx_hal_conf.h" diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index e40413e4e7a..7251bc1562e 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -443,7 +443,7 @@ void mp_hal_pin_config_speed(uint32_t port_pin, uint32_t speed) { #elif defined(STM32H5) #define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/???*08Kg" #define INTERNAL_FLASH_LAYOUT_HAS_TEMPLATE (1) -#elif defined(STM32H743xx) +#elif defined(STM32H743xx) || defined(STM32H753xx) #define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/16*128Kg" #elif defined(STM32H750xx) #define INTERNAL_FLASH_LAYOUT "@Internal Flash /0x08000000/01*128Kg" diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index b63cb0cc51b..a1532d2776d 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -43,7 +43,7 @@ ifneq ($(BUILDING_MBOOT),1) # Select hardware floating-point support. SUPPORTS_HARDWARE_FP_SINGLE = 0 SUPPORTS_HARDWARE_FP_DOUBLE = 0 -ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ STM32N657xx)) +ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H753xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ STM32N657xx)) CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 SUPPORTS_HARDWARE_FP_DOUBLE = 1 From 6b13e6c4987c6fd103847e7b36a3f0fcd0ff42d9 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Mon, 15 Dec 2025 00:03:31 +0100 Subject: [PATCH 1655/2098] stm32/boards/NUCLEO_H7x3: Fix st-flash and add openocd configuration. To connect STM32H7 that is in SLEEPD1 state (e.g. during REPL) debuggers like st-flash or openocd must assert SRST. Signed-off-by: Oliver Joos --- .../boards/NUCLEO_H723ZG/mpconfigboard.mk | 4 ++ .../boards/NUCLEO_H743ZI/mpconfigboard.mk | 4 ++ .../boards/openocd_stm32h7_dual_bank.cfg | 46 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 ports/stm32/boards/openocd_stm32h7_dual_bank.cfg diff --git a/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk index b05da481f65..56f30c42bd3 100644 --- a/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk @@ -24,3 +24,7 @@ MICROPY_VFS_LFS2 = 1 MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM = 1 FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py + +# Flash tool configuration +STFLASH = st-flash --connect-under-reset +OPENOCD_CONFIG = boards/openocd_stm32h7_dual_bank.cfg diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk index 35e62c470ac..da991c5879e 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk @@ -24,3 +24,7 @@ MICROPY_VFS_LFS2 = 1 MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM = 1 FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py + +# Flash tool configuration +STFLASH = st-flash --connect-under-reset +OPENOCD_CONFIG = boards/openocd_stm32h7_dual_bank.cfg diff --git a/ports/stm32/boards/openocd_stm32h7_dual_bank.cfg b/ports/stm32/boards/openocd_stm32h7_dual_bank.cfg new file mode 100644 index 00000000000..8bb3116990b --- /dev/null +++ b/ports/stm32/boards/openocd_stm32h7_dual_bank.cfg @@ -0,0 +1,46 @@ +# This script configures OpenOCD for use with an ST-Link V3 programmer/debugger +# and an STM32H7 target microcontroller with 2MB RAM (dual bank). +# +# To flash your firmware: +# +# $ openocd -f boards/openocd_stm32h7_dual_bank.cfg \ +# -c "stm_flash build-BOARD/firmware.bin 0x08000000 +# +# For a gdb server on port 3333: +# +# $ openocd -f boards/openocd_stm32h7_dual_bank.cfg + + +source [find interface/stlink-dap.cfg] +transport select dapdirect_swd +source [find target/stm32h7x_dual_bank.cfg] +reset_config srst_only connect_assert_srst +init + +proc stm_flash { BIN0 ADDR0 {BIN1 ""} {ADDR1 ""} } { + reset halt + sleep 100 + wait_halt 2 + flash write_image erase $BIN0 $ADDR0 + sleep 100 + verify_image $BIN0 $ADDR0 + sleep 100 + if {$BIN1 ne ""} { + flash write_image erase $BIN1 $ADDR1 + sleep 100 + verify_image $BIN1 $ADDR1 + sleep 100 + } + reset run + shutdown +} + +proc stm_erase {} { + reset halt + sleep 100 + stm32h7x mass_erase 0 + sleep 100 + stm32h7x mass_erase 1 + sleep 100 + shutdown +} From 230bbbbdf5793008d2cdfa40b33f7473312c1d55 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Thu, 18 Dec 2025 13:47:37 +0100 Subject: [PATCH 1656/2098] stm32/boards/NUCLEO_H7x3: Add UART1, remove UART5, slow down PLL1Q. STM32CubeMX shows a conflict of UART5 with ETH MII. However UART1 can be used with TX and RX on the pin headers. PLL1Q is reduced from 200 to 100 MHz because it may be used for FDCAN or SDMMC. FDCAN needs <= 150 MHz, and 100 MHz is enough for an SDCard connected to pin headers to work reliably. Signed-off-by: Oliver Joos --- ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h | 8 +++++--- ports/stm32/boards/NUCLEO_H743ZI/pins.csv | 2 ++ ports/stm32/boards/NUCLEO_H743ZI2/pins.csv | 2 ++ ports/stm32/boards/NUCLEO_H753ZI/pins.csv | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h index 4c053828fab..9078a3ef77f 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h @@ -17,7 +17,7 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_CLK_PLLM (4) #define MICROPY_HW_CLK_PLLN (400) #define MICROPY_HW_CLK_PLLP (2) -#define MICROPY_HW_CLK_PLLQ (4) +#define MICROPY_HW_CLK_PLLQ (8) #define MICROPY_HW_CLK_PLLR (2) #define MICROPY_HW_CLK_PLLVCI (RCC_PLL1VCIRANGE_1) #define MICROPY_HW_CLK_PLLVCO (RCC_PLL1VCOWIDE) @@ -37,14 +37,16 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_4 // UART config +#define MICROPY_HW_UART1_TX (pin_B6) +#define MICROPY_HW_UART1_RX (pin_B15) #define MICROPY_HW_UART2_TX (pin_D5) #define MICROPY_HW_UART2_RX (pin_D6) #define MICROPY_HW_UART2_RTS (pin_D4) #define MICROPY_HW_UART2_CTS (pin_D3) #define MICROPY_HW_UART3_TX (pin_D8) #define MICROPY_HW_UART3_RX (pin_D9) -#define MICROPY_HW_UART5_TX (pin_B6) -#define MICROPY_HW_UART5_RX (pin_B12) +// #define MICROPY_HW_UART5_TX (pin_B6) // conflict with UART1_TX +// #define MICROPY_HW_UART5_RX (pin_B12) // conflict with Ethernet MII mode #define MICROPY_HW_UART6_TX (pin_C6) #define MICROPY_HW_UART6_RX (pin_C7) #define MICROPY_HW_UART7_TX (pin_F7) diff --git a/ports/stm32/boards/NUCLEO_H743ZI/pins.csv b/ports/stm32/boards/NUCLEO_H743ZI/pins.csv index d3647ca42a3..9c1ad7d7103 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_H743ZI/pins.csv @@ -105,6 +105,8 @@ USB_VBUS,PA9 USB_ID,PA10 USB_DM,PA11 USB_DP,PA12 +UART1_TX,PB6 +UART1_RX,PB15 UART2_TX,PD5 UART2_RX,PD6 UART2_RTS,PD4 diff --git a/ports/stm32/boards/NUCLEO_H743ZI2/pins.csv b/ports/stm32/boards/NUCLEO_H743ZI2/pins.csv index 450d6e432ae..7d964c1ab68 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI2/pins.csv +++ b/ports/stm32/boards/NUCLEO_H743ZI2/pins.csv @@ -105,6 +105,8 @@ USB_VBUS,PA9 USB_ID,PA10 USB_DM,PA11 USB_DP,PA12 +UART1_TX,PB6 +UART1_RX,PB15 UART2_TX,PD5 UART2_RX,PD6 UART2_RTS,PD4 diff --git a/ports/stm32/boards/NUCLEO_H753ZI/pins.csv b/ports/stm32/boards/NUCLEO_H753ZI/pins.csv index 450d6e432ae..7d964c1ab68 100644 --- a/ports/stm32/boards/NUCLEO_H753ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_H753ZI/pins.csv @@ -105,6 +105,8 @@ USB_VBUS,PA9 USB_ID,PA10 USB_DM,PA11 USB_DP,PA12 +UART1_TX,PB6 +UART1_RX,PB15 UART2_TX,PD5 UART2_RX,PD6 UART2_RTS,PD4 From e66a1a3f4aa6b0e976f6fe9ac17679407cd27080 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 12 Jan 2026 13:44:37 +0100 Subject: [PATCH 1657/2098] tests/basics/try_finally: Fix try/finally flow tests under CPython 3.14. This commit fixes tests that will always fail when using CPython 3.14 to get a known-good output to compare MicroPython's behaviour against. Starting from CPython 3.14, statements inside a `finally` block that will prevent the execution flow to reach the block's last statement will raise a SyntaxWarning. That text would end up in the comparison data and thus make known-good tests fail. Given that those tests explicitly exercise flow control interruptions in finally blocks, there is no real workaround that can be applied to the tests themselves. Therefore those tests will now check MicroPython's behaviour against expected output files recorded from the tests' output with CPython 3.11. Signed-off-by: Alessandro Gatti --- tests/basics/try_finally_break.py.exp | 25 ++++++++++++++++ tests/basics/try_finally_break2.py.exp | 17 +++++++++++ tests/basics/try_finally_continue.py.exp | 9 ++++++ tests/basics/try_finally_return2.py.exp | 19 ++++++++++++ tests/basics/try_finally_return3.py.exp | 23 ++++++++++++++ tests/basics/try_finally_return4.py.exp | 38 ++++++++++++++++++++++++ tests/basics/try_finally_return5.py.exp | 3 ++ 7 files changed, 134 insertions(+) create mode 100644 tests/basics/try_finally_break.py.exp create mode 100644 tests/basics/try_finally_break2.py.exp create mode 100644 tests/basics/try_finally_continue.py.exp create mode 100644 tests/basics/try_finally_return2.py.exp create mode 100644 tests/basics/try_finally_return3.py.exp create mode 100644 tests/basics/try_finally_return4.py.exp create mode 100644 tests/basics/try_finally_return5.py.exp diff --git a/tests/basics/try_finally_break.py.exp b/tests/basics/try_finally_break.py.exp new file mode 100644 index 00000000000..d0b36985489 --- /dev/null +++ b/tests/basics/try_finally_break.py.exp @@ -0,0 +1,25 @@ +1 +2 +5 +a 1 +1 +iter 0 +1 +None +1 +2 +None +1 +2 +None +1 +3 +4 +7 +1 +2 +4 +7 +1 +4 +7 diff --git a/tests/basics/try_finally_break2.py.exp b/tests/basics/try_finally_break2.py.exp new file mode 100644 index 00000000000..bc5e931fdb4 --- /dev/null +++ b/tests/basics/try_finally_break2.py.exp @@ -0,0 +1,17 @@ +4 0 0 1 +4 0 0 2 +4 0 0 3 +4 0 0 4 +4 1 0 1 +4 1 0 2 +4 1 0 3 +4 1 0 4 +4 2 0 1 +4 2 0 2 +4 2 0 3 +4 2 0 4 +4 3 0 1 +4 3 0 2 +4 3 0 3 +4 3 0 4 +None diff --git a/tests/basics/try_finally_continue.py.exp b/tests/basics/try_finally_continue.py.exp new file mode 100644 index 00000000000..2901997b1aa --- /dev/null +++ b/tests/basics/try_finally_continue.py.exp @@ -0,0 +1,9 @@ +4 0 +continue +4 1 +continue +4 2 +continue +4 3 +continue +None diff --git a/tests/basics/try_finally_return2.py.exp b/tests/basics/try_finally_return2.py.exp new file mode 100644 index 00000000000..b4c364f81d9 --- /dev/null +++ b/tests/basics/try_finally_return2.py.exp @@ -0,0 +1,19 @@ +finally +0 +finally 1 +finally 2 +2 +finally 1 +finally 2 +1 +finally 1 +finally 2 +2 +finally +0 +finally +0 +finally +0 +finally +0 diff --git a/tests/basics/try_finally_return3.py.exp b/tests/basics/try_finally_return3.py.exp new file mode 100644 index 00000000000..5fef648ea67 --- /dev/null +++ b/tests/basics/try_finally_return3.py.exp @@ -0,0 +1,23 @@ +1 +42 +1 +2 +42 +1 +2 +3 +42 +2 +1 +42 +2 +1 +42 +1 +3 +2 +42 +1 +3 +2 +42 diff --git a/tests/basics/try_finally_return4.py.exp b/tests/basics/try_finally_return4.py.exp new file mode 100644 index 00000000000..f92572ff611 --- /dev/null +++ b/tests/basics/try_finally_return4.py.exp @@ -0,0 +1,38 @@ +1 +2 +3 +4 +5 +None +1 +2 +3 +5 +42 +1 +2 +5 +43 +1 +2 +5 +43 +1 +2 +5 +caught +1 +2 +5 +caught +1 +2 +3 +4 +5 +None +1 +2 +3 +5 +42 diff --git a/tests/basics/try_finally_return5.py.exp b/tests/basics/try_finally_return5.py.exp new file mode 100644 index 00000000000..dfa403cfd47 --- /dev/null +++ b/tests/basics/try_finally_return5.py.exp @@ -0,0 +1,3 @@ +4 0 +return +43 From eae83df201f8bbf4c97a8bd05ee7e930ad5d2233 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 15 Jan 2026 20:33:58 +0100 Subject: [PATCH 1658/2098] tests/float/math_fun: Fix domain error tests with CPython 3.14. This commit makes tests exercising certain math functions' limit work when using CPython 3.14 to validate the tests' output. CPython 3.14 introduced more descriptive messages when math domain error conditions are encountered rather than a single generic message. This breaks the tests in question as MicroPython uses a single error message when reporting these conditions (both to closely follow CPython and to save firmware space). The math domain tests now look for an error pattern that is compatible with both CPython 3.14 and previous versions, converting messages in the newer format into the previous one. This makes the tests' behaviour under MicroPython comparable with CPython for the foreseeable future. Signed-off-by: Alessandro Gatti --- tests/float/math_fun.py | 3 +++ tests/float/math_fun_special.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/float/math_fun.py b/tests/float/math_fun.py index 789158c0e2b..05f8be08fa0 100644 --- a/tests/float/math_fun.py +++ b/tests/float/math_fun.py @@ -43,6 +43,9 @@ ans = "{:.5g}".format(function(value)) except ValueError as e: ans = str(e) + if ans.startswith("expected a "): + # CPython 3.14 changed messages to be more detailed; convert them back to simple ones + ans = "math domain error" print("{}({:.5g}) = {}".format(function_name, value, ans)) tuple_functions = [ diff --git a/tests/float/math_fun_special.py b/tests/float/math_fun_special.py index ecacedec552..fcf6175af73 100644 --- a/tests/float/math_fun_special.py +++ b/tests/float/math_fun_special.py @@ -51,6 +51,9 @@ ans = "{:.4g}".format(function(value)) except ValueError as e: ans = str(e) + if ans.startswith("expected a "): + # CPython 3.14 changed messages to be more detailed; convert them back to simple ones + ans = "math domain error" # a tiny error in REPR_C value for 1.5204998778 causes a wrong rounded value if is_REPR_C and function_name == "erfc" and ans == "1.521": ans = "1.52" From b14d129a163d980ab4ae3e3e509bcf6b1232b663 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 15 Jan 2026 20:47:38 +0100 Subject: [PATCH 1659/2098] tests/float/complex1.py: Fix CPython 3.14 deprecation. This commit fixes a test exercising complex numbers creation, working around a deprecation introduced in CPython 3.14. Creating complex numbers using a complex number as the real part in the constructor is deprecated in CPython 3.14, but that construction method is still supported by MicroPython and covered by tests. To work around this, the specific constructor is extracted into its own test, providing an expected output file recorded using CPython 3.11. Signed-off-by: Alessandro Gatti --- tests/float/complex1.py | 1 - tests/float/complex1_micropython.py | 6 ++++++ tests/float/complex1_micropython.py.exp | 1 + tests/run-tests.py | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/float/complex1_micropython.py create mode 100644 tests/float/complex1_micropython.py.exp diff --git a/tests/float/complex1.py b/tests/float/complex1.py index 0a1d98b9af3..4decea2ac6b 100644 --- a/tests/float/complex1.py +++ b/tests/float/complex1.py @@ -20,7 +20,6 @@ print(complex("nanj")) print(complex("nan-infj")) print(complex(1, 2)) -print(complex(1j, 2j)) # unary ops print(bool(1j)) diff --git a/tests/float/complex1_micropython.py b/tests/float/complex1_micropython.py new file mode 100644 index 00000000000..6b92f593ef2 --- /dev/null +++ b/tests/float/complex1_micropython.py @@ -0,0 +1,6 @@ +# test basic complex number functionality + +# CPython 3.14 marks this constructor as deprecated, but it is still currently +# supported by MicroPython. + +print(complex(1j, 2j)) diff --git a/tests/float/complex1_micropython.py.exp b/tests/float/complex1_micropython.py.exp new file mode 100644 index 00000000000..1defdb822cd --- /dev/null +++ b/tests/float/complex1_micropython.py.exp @@ -0,0 +1 @@ +(-2+1j) diff --git a/tests/run-tests.py b/tests/run-tests.py index 7f666e09b56..ecf9736fa3b 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -1019,6 +1019,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if not has_complex: skip_tests.add("float/complex1.py") skip_tests.add("float/complex1_intbig.py") + skip_tests.add("float/complex1_micropython.py") skip_tests.add("float/complex_reverse_op.py") skip_tests.add("float/complex_special_methods.py") skip_tests.add("float/int_big_float.py") From f1363d85406db17c63d54df3d2612bf854148105 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Jan 2026 11:36:55 +1100 Subject: [PATCH 1660/2098] github: Install Python 3.11 for Windows CI runs. As of January 12 the default Python version changed from 3.9 to 3.12, and 3.12 has issues running the settrace tests. 3.13 seems to also have some issues, so setting 3.11 for now. See https://github.com/actions/runner-images/issues/13468 Signed-off-by: Angus Gratton --- .github/workflows/ports_windows.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 793eba36a7b..5318a851981 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -45,6 +45,12 @@ jobs: env: CI_BUILD_CONFIGURATION: ${{ matrix.configuration }} steps: + - name: Install Python 3.11 + # As of 20260112 the default Python version in Windows image is 3.12, which breaks settrace tests + # Use 3.11 for now + uses: actions/setup-python@v6 + with: + python-version: '3.11' - name: Install Visual Studio ${{ matrix.visualstudio }} if: matrix.custom_vs_install shell: bash From b792efc341be16da31e1b5d855e61307e1047310 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 15 Nov 2025 08:31:44 -0600 Subject: [PATCH 1661/2098] tests/run-tests.py: Re-add stress_schedule.py for GitHub Actions. This test was ignored since 2020, which hid a new bug in the test for the native emitter added in 2024. Hopefully other changes to the test will make it more reliable. Signed-off-by: Jeff Epler --- tests/run-tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index ecf9736fa3b..abf5752fb85 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -985,8 +985,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Some tests shouldn't be run on GitHub Actions if os.getenv("GITHUB_ACTIONS") == "true": - skip_tests.add("thread/stress_schedule.py") # has reliability issues - if os.getenv("RUNNER_OS") == "Windows" and os.getenv("CI_BUILD_CONFIGURATION") == "Debug": # fails with stack overflow on Debug builds skip_tests.add("misc/sys_settrace_features.py") From cfaba3211cf37efd2f6deb08deeedfd6390193ef Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 15 Nov 2025 08:30:56 -0600 Subject: [PATCH 1662/2098] tests/thread/stress_schedule.py: Decrease backoff time. Locally, this changes the duration of the test from about 2.7s to 0.3s. Signed-off-by: Jeff Epler --- tests/thread/stress_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index 362d71aa12e..10ba67d0bb3 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -35,7 +35,7 @@ def thread(): micropython.schedule(task, None) except RuntimeError: # Queue full, back off. - time.sleep_ms(10) + time.sleep_ms(1) for i in range(8): From 16ccf55fd8eead8a5fdae5c4b9da9ad9af6c92f4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 15 Nov 2025 17:25:11 -0600 Subject: [PATCH 1663/2098] unix/modtime: Handle pending events during sleep(0). This ensures that zero-duration sleeps run scheduled callbacks. Signed-off-by: Jeff Epler --- ports/unix/modtime.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 4f0550dbea7..1a9bfcd18e9 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -95,6 +95,7 @@ static mp_obj_t mp_time_sleep(mp_obj_t arg) { tv.tv_sec = (suseconds_t)ipart; int res; while (1) { + mp_handle_pending(true); MP_THREAD_GIL_EXIT(); res = sleep_select(0, NULL, NULL, NULL, &tv); MP_THREAD_GIL_ENTER(); @@ -104,7 +105,6 @@ static mp_obj_t mp_time_sleep(mp_obj_t arg) { if (res != -1 || errno != EINTR) { break; } - mp_handle_pending(true); // printf("select: EINTR: %ld:%ld\n", tv.tv_sec, tv.tv_usec); #else break; @@ -114,13 +114,13 @@ static mp_obj_t mp_time_sleep(mp_obj_t arg) { #else int seconds = mp_obj_get_int(arg); for (;;) { + mp_handle_pending(true); MP_THREAD_GIL_EXIT(); seconds = sleep(seconds); MP_THREAD_GIL_ENTER(); if (seconds == 0) { break; } - mp_handle_pending(true); } #endif return mp_const_none; From 8e3ee303ce8bb05b784a3e100f2fac77dec10a49 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 18 Nov 2025 21:50:02 -0600 Subject: [PATCH 1664/2098] unix/unix_mphal: Ensure mp_hal_delay_ms handles pending tasks. This ensures that zero-duration delays run scheduled callbacks. Signed-off-by: Jeff Epler --- ports/unix/unix_mphal.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 5f5abc2e056..39311c98729 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -242,9 +242,13 @@ uint64_t mp_hal_time_ns(void) { #ifndef mp_hal_delay_ms void mp_hal_delay_ms(mp_uint_t ms) { - mp_uint_t start = mp_hal_ticks_ms(); - while (mp_hal_ticks_ms() - start < ms) { - mp_event_wait_ms(1); + if (ms) { + mp_uint_t start = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - start < ms) { + mp_event_wait_ms(1); + } + } else { + mp_handle_pending(true); } } #endif From baf75e21d30d38c4ebad16ff1f7d83807431b57c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 19:40:51 -0600 Subject: [PATCH 1665/2098] docs/library: Document that sleep(0)/sleep_ms(0) run the scheduler. Signed-off-by: Jeff Epler --- docs/library/micropython.rst | 4 ++++ docs/library/time.rst | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index 4d5a064a7a7..b9e910ca261 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -130,6 +130,10 @@ Functions a critical region but they will not be executed until that region is exited. An example of a critical region is a preempting interrupt handler (an IRQ). + - Inside native code functions, scheduled functions are not called unless + the native code calls a function that specifically does so. + - ``time.sleep`` and ``time.sleep_ms``, including zero-duration sleeps, + will call scheduled functions. A use for this function is to schedule a callback from a preempting IRQ. Such an IRQ puts restrictions on the code that runs in the IRQ (for example diff --git a/docs/library/time.rst b/docs/library/time.rst index b53bb133ec4..8f413fca92b 100644 --- a/docs/library/time.rst +++ b/docs/library/time.rst @@ -71,6 +71,9 @@ Functions other boards may not accept a floating-point argument, for compatibility with them use `sleep_ms()` and `sleep_us()` functions. + Calling ``sleep``, including ``sleep(0)`` is guaranteed to call pending callback + functions. + .. function:: sleep_ms(ms) Delay for given number of milliseconds, should be positive or 0. @@ -80,6 +83,9 @@ Functions interrupt handlers or other threads. Passing in 0 for *ms* will still allow this other processing to occur. Use `sleep_us()` for more precise delays. + Calling ``sleep_ms``, including ``sleep_ms(0)`` is guaranteed to call + pending callback functions. + .. function:: sleep_us(us) Delay for given number of microseconds, should be positive or 0. From 8248d1180664a999068d744b8e66d8c4d0fca969 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 20:17:28 -0600 Subject: [PATCH 1666/2098] qemu/mphalport: Ensure mp_hal_delay_ms(0) handles pending tasks. This ensures that zero-duration delays run scheduled callbacks. Signed-off-by: Jeff Epler --- ports/qemu/mphalport.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/qemu/mphalport.c b/ports/qemu/mphalport.c index bd3d653eed5..a4fa32e297f 100644 --- a/ports/qemu/mphalport.c +++ b/ports/qemu/mphalport.c @@ -25,6 +25,7 @@ */ #include "py/mphal.h" +#include "py/runtime.h" #include "py/stream.h" #include "shared/runtime/semihosting_arm.h" #include "uart.h" @@ -82,8 +83,12 @@ mp_uint_t mp_hal_ticks_us(void) { } void mp_hal_delay_ms(mp_uint_t ms) { - mp_uint_t start = mp_hal_ticks_ms(); - while (mp_hal_ticks_ms() - start < ms) { + if (ms) { + mp_uint_t start = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - start < ms) { + } + } else { + mp_handle_pending(true); } } From 2782d4598de38d69878b121a0ec33c1820396599 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 20:40:47 -0600 Subject: [PATCH 1667/2098] extmod/modselect: Handle pending events before entering poll. This allows tests like `asyncio_event_queue.py` to succeed under the native emitter when poll is enabled. Signed-off-by: Jeff Epler --- docs/library/micropython.rst | 3 ++- docs/library/select.rst | 6 ++++++ extmod/modselect.c | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index b9e910ca261..2da9a6e3fd7 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -132,7 +132,8 @@ Functions handler (an IRQ). - Inside native code functions, scheduled functions are not called unless the native code calls a function that specifically does so. - - ``time.sleep`` and ``time.sleep_ms``, including zero-duration sleeps, + - Certain functions including ``poll.poll``, ``poll.ipoll``, + ``time.sleep`` and ``time.sleep_ms`` (including zero-duration sleeps) will call scheduled functions. A use for this function is to schedule a callback from a preempting IRQ. diff --git a/docs/library/select.rst b/docs/library/select.rst index 57adbb49afd..6df1ff9c230 100644 --- a/docs/library/select.rst +++ b/docs/library/select.rst @@ -76,6 +76,9 @@ Methods In case of timeout, an empty list is returned. + Calling ``poll.poll`` is guaranteed to call pending callback functions + before entering the polling loop. + .. admonition:: Difference to CPython :class: attention @@ -93,6 +96,9 @@ Methods won't be processed until new mask is set with `poll.modify()`. This behaviour is useful for asynchronous I/O schedulers. + Calling ``poll.ipoll`` is guaranteed to call pending callback functions + before entering the polling loop. + .. admonition:: Difference to CPython :class: attention diff --git a/extmod/modselect.c b/extmod/modselect.c index d06157e585a..229f8f737b5 100644 --- a/extmod/modselect.c +++ b/extmod/modselect.c @@ -342,6 +342,8 @@ static mp_uint_t poll_set_poll_until_ready_or_timeout(poll_set_t *poll_set, size mp_uint_t start_ticks = mp_hal_ticks_ms(); bool has_timeout = timeout != (mp_uint_t)-1; + mp_handle_pending(true); + #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS for (;;) { From f3874842ab9d568fc1eca813ff8e368a69aa3a84 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Nov 2025 19:40:06 -0600 Subject: [PATCH 1668/2098] tests/micropython: Add new schedule_sleep.py test. This variant of `schedule.py` explicitly calls a zero sleep. The existing variant is kept to ensure the scheduler is called between bytecodes. Signed-off-by: Jeff Epler --- tests/micropython/schedule.py | 2 + tests/micropython/schedule_sleep.py | 72 +++++++++++++++++++++++++ tests/micropython/schedule_sleep.py.exp | 4 ++ tests/run-tests.py | 2 +- 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/micropython/schedule_sleep.py create mode 100644 tests/micropython/schedule_sleep.py.exp diff --git a/tests/micropython/schedule.py b/tests/micropython/schedule.py index f3dd3266126..e629edb3eb3 100644 --- a/tests/micropython/schedule.py +++ b/tests/micropython/schedule.py @@ -1,4 +1,6 @@ # test micropython.schedule() function +# this test should be manually kept in synch with +# tests/micrpython/schedule_sleep.py. try: import micropython diff --git a/tests/micropython/schedule_sleep.py b/tests/micropython/schedule_sleep.py new file mode 100644 index 00000000000..9aadde7b084 --- /dev/null +++ b/tests/micropython/schedule_sleep.py @@ -0,0 +1,72 @@ +# test micropython.schedule() function +# this is the same as tests/micropython/schedule.py but the busy loops are +# replaced with sleep/sleep_ms which allows the test to succeed when run under +# the native emitter. + +try: + import micropython + import time + + micropython.schedule +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +# Basic test of scheduling a function. + + +def callback(arg): + global done + print(arg) + done = True + + +done = False +micropython.schedule(callback, 1) +while not done: + time.sleep(0) + +# Test that callbacks can be scheduled from within a callback, but +# that they don't execute until the outer callback is finished. + + +def callback_inner(arg): + global done + print("inner") + done += 1 + + +def callback_outer(arg): + global done + micropython.schedule(callback_inner, 0) + # need a loop so that the VM can check for pending events + for i in range(2): + pass + print("outer") + done += 1 + + +done = 0 +micropython.schedule(callback_outer, 0) +while done != 2: + time.sleep(0) + +# Test that scheduling too many callbacks leads to an exception. To do this we +# must schedule from within a callback to guarantee that the scheduler is locked. + + +def callback(arg): + global done + try: + for i in range(100): + micropython.schedule(lambda x: x, None) + except RuntimeError: + print("RuntimeError") + done = True + + +done = False +micropython.schedule(callback, None) +while not done: + time.sleep_ms(0) diff --git a/tests/micropython/schedule_sleep.py.exp b/tests/micropython/schedule_sleep.py.exp new file mode 100644 index 00000000000..c4a3e1227e2 --- /dev/null +++ b/tests/micropython/schedule_sleep.py.exp @@ -0,0 +1,4 @@ +1 +outer +inner +RuntimeError diff --git a/tests/run-tests.py b/tests/run-tests.py index abf5752fb85..5aaa680ecd4 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -164,7 +164,7 @@ def open(self, path, mode): "basics/exception_chain.py", # These require stack-allocated slice optimisation. "micropython/heapalloc_slice.py", - # These require running the scheduler. + # These require implicitly running the scheduler between bytecodes. "micropython/schedule.py", "extmod/asyncio_event_queue.py", "extmod/asyncio_iterator_event.py", From 78ec31b1751a9abfaee31590ce5814b45e56cbfb Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 1 Dec 2025 08:46:07 -0600 Subject: [PATCH 1669/2098] tests/run-tests.py: Re-enable some asyncio tests for the native emitter. These tests no longer fail under the native emitter. Signed-off-by: Jeff Epler --- tests/run-tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 5aaa680ecd4..6dfba2c540a 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -166,8 +166,6 @@ def open(self, path, mode): "micropython/heapalloc_slice.py", # These require implicitly running the scheduler between bytecodes. "micropython/schedule.py", - "extmod/asyncio_event_queue.py", - "extmod/asyncio_iterator_event.py", # These require sys.exc_info(). "misc/sys_exc_info.py", # These require sys.settrace(). From f918e336452b8506ca30887fc37e92733718e3bb Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 1 Dec 2025 08:46:19 -0600 Subject: [PATCH 1670/2098] tests/thread/stress_schedule.py: Remove decorator/inaccurate comment. This test can now run correctly with the native emitter because the `thread()` function will run the scheduler during the call to `time.sleep_ms(1)`. Signed-off-by: Jeff Epler --- tests/thread/stress_schedule.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index 10ba67d0bb3..fca9b38df89 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -27,8 +27,6 @@ def task(x): n += 1 -# This function must always use the bytecode emitter so it bounces the GIL when running. -@micropython.bytecode def thread(): while thread_run: try: From 894d7a54058610ebcfbd32339ea4bd116c4ec8ba Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 20 Aug 2025 17:23:31 +1000 Subject: [PATCH 1671/2098] extmod/modlwip: Fix latent bug with partially created socket object. Sockets have a finaliser, so they must be created carefully. Signed-off-by: Damien George --- extmod/modlwip.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 4b1c1b8f3a5..2a43256f3c8 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -1091,15 +1091,10 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { mp_raise_OSError(MP_EOPNOTSUPP); } - // Create new socket object, do it here because we must not raise an out-of-memory - // exception when the LWIP concurrency lock is held - lwip_socket_obj_t *socket2 = mp_obj_malloc_with_finaliser(lwip_socket_obj_t, &lwip_socket_type); - MICROPY_PY_LWIP_ENTER if (socket->pcb.tcp == NULL) { MICROPY_PY_LWIP_EXIT - m_del_obj(lwip_socket_obj_t, socket2); mp_raise_OSError(MP_EBADF); } @@ -1107,7 +1102,6 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { struct tcp_pcb *listener = socket->pcb.tcp; if (listener->state != LISTEN) { MICROPY_PY_LWIP_EXIT - m_del_obj(lwip_socket_obj_t, socket2); mp_raise_OSError(MP_EINVAL); } @@ -1124,7 +1118,6 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { } if (socket_is_timedout(socket, ticks_start)) { MICROPY_PY_LWIP_EXIT - m_del_obj(lwip_socket_obj_t, socket2); if (socket->timeout == 0) { mp_raise_OSError(MP_EAGAIN); } else { @@ -1135,13 +1128,20 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { } // We get a new pcb handle... - socket2->pcb.tcp = *incoming_connection; + struct tcp_pcb *pcb_new = *incoming_connection; if (++socket->incoming.connection.iget >= socket->incoming.connection.alloc) { socket->incoming.connection.iget = 0; } *incoming_connection = NULL; + MICROPY_PY_LWIP_EXIT + // ...and set up the new socket for it. + // + // Creating the new socket object must be done in one step due to the finaliser, and + // outside the lwIP concurrency lock in case it raises an out-of-memory exception. + lwip_socket_obj_t *socket2 = mp_obj_malloc_with_finaliser(lwip_socket_obj_t, &lwip_socket_type); + socket2->pcb.tcp = pcb_new; socket2->domain = MOD_NETWORK_AF_INET; socket2->type = MOD_NETWORK_SOCK_STREAM; socket2->incoming.tcp.pbuf = NULL; @@ -1149,10 +1149,12 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { socket2->state = STATE_CONNECTED; socket2->recv_offset = 0; socket2->callback = MP_OBJ_NULL; + + MICROPY_PY_LWIP_REENTER + tcp_arg(socket2->pcb.tcp, (void *)socket2); tcp_err(socket2->pcb.tcp, _lwip_tcp_error); tcp_recv(socket2->pcb.tcp, _lwip_tcp_recv); - tcp_accepted(listener); MICROPY_PY_LWIP_EXIT From 33474a6fdb3ce9853dd105e69c9c5a1484909a99 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 20 Aug 2025 18:24:57 +1000 Subject: [PATCH 1672/2098] extmod/modlwip: Adjust logic for determining a listening socket. This should be equivalent logic, and is a bit simpler and clearer now. Signed-off-by: Damien George --- extmod/modlwip.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 2a43256f3c8..2d9cda8f170 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -371,11 +371,7 @@ static struct tcp_pcb *volatile *lwip_socket_incoming_array(lwip_socket_obj_t *s } static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { - bool socket_is_listener = - socket->type == MOD_NETWORK_SOCK_STREAM - && socket->pcb.tcp->state == LISTEN; - - if (!socket_is_listener) { + if (socket->state != STATE_LISTENING) { if (socket->type == MOD_NETWORK_SOCK_STREAM) { if (socket->incoming.tcp.pbuf != NULL) { pbuf_free(socket->incoming.tcp.pbuf); @@ -1648,7 +1644,7 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ tcp_err(socket->pcb.tcp, NULL); tcp_recv(socket->pcb.tcp, NULL); - if (socket->pcb.tcp->state != LISTEN) { + if (socket->state != STATE_LISTENING) { // Schedule a callback to abort the connection if it's not cleanly closed after // the given timeout. The callback must be set before calling tcp_close since // the latter may free the pcb; if it doesn't then the callback will be active. From c696ca91e06dabd756694a160671a3e8930391e9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Dec 2025 11:06:09 +1100 Subject: [PATCH 1673/2098] extmod/modlwip: Narrow error_lookup_table type to int8_t. Reduces code size by about 60 bytes. Signed-off-by: Damien George --- extmod/modlwip.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 2d9cda8f170..e4bb720dbac 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -213,7 +213,7 @@ static MP_DEFINE_CONST_OBJ_TYPE( // TODO: We just know that change happened somewhere between 1.4.0 and 1.4.1, // investigate in more detail. #if LWIP_VERSION_MACRO < 0x01040100 -static const int error_lookup_table[] = { +static const int8_t error_lookup_table[] = { 0, /* ERR_OK 0 No error, everything OK. */ MP_ENOMEM, /* ERR_MEM -1 Out of memory error. */ MP_ENOBUFS, /* ERR_BUF -2 Buffer error. */ @@ -234,7 +234,7 @@ static const int error_lookup_table[] = { MP_EBADF, /* _ERR_BADF -16 Closed socket (null pcb) */ }; #elif LWIP_VERSION_MACRO < 0x02000000 -static const int error_lookup_table[] = { +static const int8_t error_lookup_table[] = { 0, /* ERR_OK 0 No error, everything OK. */ MP_ENOMEM, /* ERR_MEM -1 Out of memory error. */ MP_ENOBUFS, /* ERR_BUF -2 Buffer error. */ @@ -258,7 +258,7 @@ static const int error_lookup_table[] = { // Matches lwIP 2.0.3 #undef _ERR_BADF #define _ERR_BADF -17 -static const int error_lookup_table[] = { +static const int8_t error_lookup_table[] = { 0, /* ERR_OK 0 No error, everything OK */ MP_ENOMEM, /* ERR_MEM -1 Out of memory error */ MP_ENOBUFS, /* ERR_BUF -2 Buffer error */ From a7cd41847d8d96716eeafca6006ccd402a04e9d3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 20 Aug 2025 00:45:10 +1000 Subject: [PATCH 1674/2098] extmod/modlwip: Keep TCP data on remote RST. This commit fixes a long standing bug/deficiency in the lwIP socket code, whereby it would abandon all incoming TCP data if the remote sent a TCP RST. This behaviour it tested by the existing `tests/multi_net/tcp_client_rst.py` and `tests/multi_net/asyncio_tcp_client_rst.py` tests, and they both fail on boards like PYBD_SFx and RPI_PICO_W due to the deficiency. With the fix here, both of those tests now pass on lwIP targets, along with all existing socket tests. Signed-off-by: Damien George --- extmod/modlwip.c | 56 +++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index e4bb720dbac..bcec34bca3b 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -348,8 +348,9 @@ typedef struct _lwip_socket_obj_t { #define STATE_LISTENING 1 #define STATE_CONNECTING 2 #define STATE_CONNECTED 3 - #define STATE_PEER_CLOSED 4 - #define STATE_ACTIVE_UDP 5 + #define STATE_ACTIVE_UDP 4 + #define STATE_PEER_CLOSED 5 // Values higher than this must also be closed by peer + #define STATE_PEER_RST_HANDLED 6 // Negative value is lwIP error int8_t state; } lwip_socket_obj_t; @@ -370,10 +371,10 @@ static struct tcp_pcb *volatile *lwip_socket_incoming_array(lwip_socket_obj_t *s } } -static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { +static void lwip_socket_free_incoming(lwip_socket_obj_t *socket, bool free_queued_stream_data) { if (socket->state != STATE_LISTENING) { if (socket->type == MOD_NETWORK_SOCK_STREAM) { - if (socket->incoming.tcp.pbuf != NULL) { + if (free_queued_stream_data && socket->incoming.tcp.pbuf != NULL) { pbuf_free(socket->incoming.tcp.pbuf); socket->incoming.tcp.pbuf = NULL; } @@ -399,6 +400,8 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { tcp_array[i] = NULL; } } + // This socket is now a non-listening stream, so clear the relevant state. + socket->incoming.tcp.pbuf = NULL; } } @@ -487,8 +490,9 @@ static void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p, static void _lwip_tcp_error(void *arg, err_t err) { lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg; - // Free any incoming buffers or connections that are stored - lwip_socket_free_incoming(socket); + // Free any incoming buffers or connections that are stored, but keep potential + // queued TCP data in case it's read later. Will be freed by MP_STREAM_CLOSE. + lwip_socket_free_incoming(socket, false); // Pass the error code back via the connection variable. socket->state = err; // If we got here, the lwIP stack either has deallocated or will deallocate the pcb. @@ -818,9 +822,6 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // Helper function for recv/recvfrom to handle TCP packets static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, mp_int_t flags, int *_errno) { - // Check for any pending errors - STREAM_ERROR_CHECK(socket); - if (socket->state == STATE_LISTENING) { // original socket in listening state, not the accepted connection. *_errno = MP_ENOTCONN; @@ -828,10 +829,20 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } if (socket->incoming.tcp.pbuf == NULL) { + // Check for any pending errors that should propagate out on socket read. + if (socket->state < 0) { + *_errno = error_lookup_table[-socket->state]; + if (*_errno == MP_ECONNRESET) { + socket->state = STATE_PEER_RST_HANDLED; + } else { + socket->state = _ERR_BADF; + } + return MP_STREAM_ERROR; + } // Non-blocking socket or flag if (socket->timeout == 0 || (flags & MSG_DONTWAIT)) { - if (socket->state == STATE_PEER_CLOSED) { + if (socket->state >= STATE_PEER_CLOSED) { return 0; } *_errno = MP_EAGAIN; @@ -847,7 +858,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ poll_sockets(); } - if (socket->state == STATE_PEER_CLOSED) { + if (socket->state >= STATE_PEER_CLOSED) { if (socket->incoming.tcp.pbuf == NULL) { // socket closed and no data left in buffer return 0; @@ -864,8 +875,6 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ MICROPY_PY_LWIP_ENTER - assert(socket->pcb.tcp != NULL); - struct pbuf *p = socket->incoming.tcp.pbuf; mp_uint_t remaining = p->len - socket->recv_offset; @@ -888,7 +897,9 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } else { socket->recv_offset += len; } - tcp_recved(socket->pcb.tcp, len); + if (socket->pcb.tcp != NULL) { + tcp_recved(socket->pcb.tcp, len); + } } MICROPY_PY_LWIP_EXIT @@ -1292,8 +1303,6 @@ static mp_obj_t lwip_socket_recv_common(size_t n_args, const mp_obj_t *args, ip_ vstr_t vstr; mp_uint_t ret = 0; - lwip_socket_check_connected(socket); - vstr_init_len(&vstr, len); switch (socket->type) { @@ -1308,6 +1317,7 @@ static mp_obj_t lwip_socket_recv_common(size_t n_args, const mp_obj_t *args, ip_ #if MICROPY_PY_LWIP_SOCK_RAW case MOD_NETWORK_SOCK_RAW: #endif + lwip_socket_check_connected(socket); ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, flags, ip, port, &_errno); break; } @@ -1580,7 +1590,10 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ } } else if (socket->type == MOD_NETWORK_SOCK_STREAM) { // For TCP sockets there is just one slot for incoming data - if (socket->incoming.tcp.pbuf != NULL) { + // The socket is also readable when in RST state + if (socket->incoming.tcp.pbuf != NULL + || socket->state == ERR_RST + || socket->state == STATE_PEER_RST_HANDLED) { ret |= MP_STREAM_POLL_RD; } } else { @@ -1619,6 +1632,8 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ } else if (socket->state == ERR_RST) { // Socket was reset by peer, a write will return an error ret |= flags & MP_STREAM_POLL_WR; + ret |= MP_STREAM_POLL_ERR | MP_STREAM_POLL_HUP; + } else if (socket->state == STATE_PEER_RST_HANDLED) { ret |= MP_STREAM_POLL_HUP; } else if (socket->state == _ERR_BADF) { ret |= MP_STREAM_POLL_NVAL; @@ -1629,14 +1644,15 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ } } else if (request == MP_STREAM_CLOSE) { + // Free any incoming buffers or connections that are stored + lwip_socket_free_incoming(socket, true); + if (socket->pcb.tcp == NULL) { + socket->state = _ERR_BADF; MICROPY_PY_LWIP_EXIT return 0; } - // Free any incoming buffers or connections that are stored - lwip_socket_free_incoming(socket); - switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: { // Deregister callback (pcb.tcp is set to NULL below so must deregister now) From 25b400f6888a098a15c4a2811c1fd78af18032a1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 11 Dec 2025 11:39:17 +1100 Subject: [PATCH 1675/2098] tests/multi_net/tcp_client_rst.py: Improve and extend test. These changes test a few more things related to TCP RST: - add a second iteration to drain incoming data after TCP RST - read data after closing socket Signed-off-by: Damien George --- tests/multi_net/tcp_client_rst.py | 82 +++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/tests/multi_net/tcp_client_rst.py b/tests/multi_net/tcp_client_rst.py index 1fe994f36ce..6e74edc194d 100644 --- a/tests/multi_net/tcp_client_rst.py +++ b/tests/multi_net/tcp_client_rst.py @@ -2,6 +2,12 @@ import struct, time, socket, select +try: + import errno +except ImportError: + print("SKIP") + raise SystemExit + PORT = 8000 @@ -18,27 +24,49 @@ def instance0(): s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) s.listen(1) multitest.next() - s2, _ = s.accept() - s2.setblocking(False) - poll = select.poll() - poll.register(s2, select.POLLIN) - time.sleep(0.4) - print(convert_poll_list(poll.poll(1000))) - # TODO: the following recv don't work with lwip, it abandons data upon TCP RST - try: - print(s2.recv(10)) + + for iteration in range(2): + print("server iteration", iteration) + + # Accept a connection from the client. + s2, _ = s.accept() + s2.setblocking(False) + + # Create a poller for the connected socket. + poll = select.poll() + poll.register(s2, select.POLLIN) + + # Wait for the client to send data and issue a TCP RST. + for _ in range(10): + if select.POLLIN | select.POLLERR | select.POLLHUP in convert_poll_list( + poll.poll(1000) + ): + break + time.sleep(0.1) print(convert_poll_list(poll.poll(1000))) - print(s2.recv(10)) + + # On the second test, drain the incoming data. + if iteration == 1: + for _ in range(5): + try: + print(s2.recv(10)) + except OSError as er: + # This error should only happen on the 3rd recv. + print("ECONNRESET:", er.errno == errno.ECONNRESET) + print(convert_poll_list(poll.poll(1000))) + + # Close the connection to the client. + s2.close() print(convert_poll_list(poll.poll(1000))) - print(s2.recv(10)) + + # Try to read more data from the closed socket, to make sure the error code is correct. + try: + print(s2.recv(10)) + except OSError as er: + print("EBADF:", er.errno == errno.EBADF) print(convert_poll_list(poll.poll(1000))) - except OSError as er: - print(er.errno) - print(convert_poll_list(poll.poll(1000))) - # TODO lwip raises here but apparently it shouldn't - print(s2.recv(10)) - print(convert_poll_list(poll.poll(1000))) - s2.close() + + # Close the listening socket. s.close() @@ -47,11 +75,13 @@ def instance1(): if not hasattr(socket, "SO_LINGER"): multitest.skip() multitest.next() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) - lgr_onoff = 1 - lgr_linger = 0 - s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", lgr_onoff, lgr_linger)) - s.send(b"GET / HTTP/1.0\r\n\r\n") - time.sleep(0.2) - s.close() # This issues a TCP RST since we've set the linger option + for iteration in range(2): + print("client iteration", iteration) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + lgr_onoff = 1 + lgr_linger = 0 + s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", lgr_onoff, lgr_linger)) + s.send(b"GET / HTTP/1.0\r\n\r\n") + time.sleep(0.2) + s.close() # This issues a TCP RST since we've set the linger option From a5bda23aaa833245362cee3a5e8058540440f2c2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 18:29:30 +1100 Subject: [PATCH 1676/2098] py/emitglue: Check for bytecode with MICROPY_PY_FUNCTION_ATTRS_CODE. When `MICROPY_PY_FUNCTION_ATTRS_CODE` is enabled, constructing a function instance through `fun_bc_make_new()` can call `mp_make_function_from_proto_fun()` with pure bytecode as the proto-fun. Signed-off-by: Damien George --- py/emitglue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/emitglue.c b/py/emitglue.c index a72f94c88bd..6a1816c2c79 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -183,7 +183,7 @@ mp_obj_t mp_make_function_from_proto_fun(mp_proto_fun_t proto_fun, const mp_modu // def_kw_args must be MP_OBJ_NULL or a dict assert(def_args == NULL || def_args[1] == MP_OBJ_NULL || mp_obj_is_type(def_args[1], &mp_type_dict)); - #if MICROPY_MODULE_FROZEN_MPY + #if MICROPY_MODULE_FROZEN_MPY || MICROPY_PY_FUNCTION_ATTRS_CODE if (mp_proto_fun_is_bytecode(proto_fun)) { const uint8_t *bc = proto_fun; mp_obj_t fun = mp_obj_new_fun_bc(def_args, bc, context, NULL); From 03f50d39dad8624bc3cbf5c7c8b49e9ef9686464 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 23:29:52 +1100 Subject: [PATCH 1677/2098] py/mpconfig: Enable MICROPY_PY_FUNCTION_ATTRS_CODE when marshal enabled. In case `MICROPY_PY_MARSHAL` is enabled manually. Otherwise the marshal module is not very usable. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 2a71c731628..9a2c753462e 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1281,7 +1281,7 @@ typedef time_t mp_timestamp_t; // Whether to implement the __code__ attribute on functions, and function constructor #ifndef MICROPY_PY_FUNCTION_ATTRS_CODE -#define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES) +#define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_PY_MARSHAL || MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES) #endif // Whether bound_method can just use == (feature disabled), or requires a call to From 384cc627bc633af3bb092f173d447c33c1b71952 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 11:42:04 +1100 Subject: [PATCH 1678/2098] py/persistentcode: Support saving functions with children. This adds support to `mp_raw_code_save_fun_to_bytes()` so that it can handle saving functions that have children. It does this by inspecting the MP_BC_MAKE_FUNCTION/etc opcodes to work out how many children there are, and creating a tree of simplified raw code information. Signed-off-by: Damien George --- py/persistentcode.c | 105 +++++++++++++++++++++++++++++++++++--------- py/persistentcode.h | 2 +- 2 files changed, 85 insertions(+), 22 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index d83386736b2..2c7a4256566 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -844,8 +844,28 @@ static mp_opcode_t mp_opcode_decode(const uint8_t *ip) { return op; } -mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, const uint8_t *bytecode) { - const uint8_t *fun_data = bytecode; +typedef struct _mp_raw_code_simplified_t { + const uint8_t *fun_data; + struct _mp_raw_code_simplified_t *children; + size_t fun_data_len; + size_t n_children; +} mp_raw_code_simplified_t; + +static void proto_fun_to_raw_code_simplified(const void *proto_fun, bit_vector_t *qstr_table_used, bit_vector_t *obj_table_used, mp_raw_code_simplified_t *rcs) { + const uint8_t *fun_data; + mp_raw_code_t **children; + if (mp_proto_fun_is_bytecode(proto_fun)) { + fun_data = proto_fun; + children = NULL; + } else { + const mp_raw_code_t *rc = proto_fun; + if (rc->kind != MP_CODE_BYTECODE) { + mp_raise_ValueError(MP_ERROR_TEXT("function must be bytecode")); + } + fun_data = rc->fun_data; + children = rc->children; + } + const uint8_t *fun_data_top = fun_data + gc_nbytes(fun_data); // Extract function information. @@ -853,20 +873,12 @@ mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, cons MP_BC_PRELUDE_SIG_DECODE(ip); MP_BC_PRELUDE_SIZE_DECODE(ip); - // Track the qstrs used by the function. - bit_vector_t qstr_table_used; - bit_vector_init(&qstr_table_used); - - // Track the objects used by the function. - bit_vector_t obj_table_used; - bit_vector_init(&obj_table_used); - const byte *ip_names = ip; mp_uint_t simple_name = mp_decode_uint(&ip_names); - bit_vector_set(&qstr_table_used, simple_name); + bit_vector_set(qstr_table_used, simple_name); for (size_t i = 0; i < n_pos_args + n_kwonly_args; ++i) { mp_uint_t arg_name = mp_decode_uint(&ip_names); - bit_vector_set(&qstr_table_used, arg_name); + bit_vector_set(qstr_table_used, arg_name); } // Skip pass source code info and cell info. @@ -874,20 +886,74 @@ mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, cons ip += n_info + n_cell; // Decode bytecode. + size_t n_children = 0; while (ip < fun_data_top) { mp_opcode_t op = mp_opcode_decode(ip); if (op.opcode == MP_BC_BASE_RESERVED) { // End of opcodes. fun_data_top = ip; - } else if (op.opcode == MP_BC_LOAD_CONST_OBJ) { - bit_vector_set(&obj_table_used, op.arg); } else if (op.format == MP_BC_FORMAT_QSTR) { - bit_vector_set(&qstr_table_used, op.arg); + bit_vector_set(qstr_table_used, op.arg); + } else if (op.opcode == MP_BC_LOAD_CONST_OBJ) { + bit_vector_set(obj_table_used, op.arg); + } else if (op.opcode == MP_BC_MAKE_FUNCTION + || op.opcode == MP_BC_MAKE_FUNCTION_DEFARGS + || op.opcode == MP_BC_MAKE_CLOSURE + || op.opcode == MP_BC_MAKE_CLOSURE_DEFARGS) { + if ((mp_uint_t)op.arg + 1 > n_children) { + n_children = (mp_uint_t)op.arg + 1; + } } ip += op.size; } - mp_uint_t fun_data_len = fun_data_top - fun_data; + rcs->fun_data = fun_data; + rcs->fun_data_len = fun_data_top - fun_data; + rcs->n_children = n_children; + rcs->children = NULL; + + if (n_children) { + rcs->children = m_new(mp_raw_code_simplified_t, n_children); + for (size_t i = 0; i < n_children; ++i) { + proto_fun_to_raw_code_simplified(children[i], qstr_table_used, obj_table_used, &rcs->children[i]); + } + } +} + +static void save_raw_code_simplified(mp_print_t *print, const mp_raw_code_simplified_t *rcs) { + // Save function kind and data length. + mp_print_uint(print, rcs->fun_data_len << 3 | (rcs->n_children != 0) << 2); + + // Save function code. + mp_print_bytes(print, rcs->fun_data, rcs->fun_data_len); + + // Save (and free) children. + if (rcs->n_children) { + mp_print_uint(print, rcs->n_children); + for (size_t i = 0; i < rcs->n_children; ++i) { + save_raw_code_simplified(print, &rcs->children[i]); + } + m_del(mp_raw_code_simplified_t, rcs->children, rcs->n_children); + } +} + +mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, mp_proto_fun_t proto_fun) { + // Track the qstrs used by the function. + bit_vector_t qstr_table_used; + bit_vector_init(&qstr_table_used); + + // Track the objects used by the function. + bit_vector_t obj_table_used; + bit_vector_init(&obj_table_used); + + #if MICROPY_PY_BUILTINS_CODE >= MICROPY_PY_BUILTINS_CODE_FULL + // Make sure the filename appears in the qstr table. + bit_vector_set(&qstr_table_used, 0); + #endif + + // Convert function into a simplified raw code tree. + mp_raw_code_simplified_t rcs; + proto_fun_to_raw_code_simplified(proto_fun, &qstr_table_used, &obj_table_used, &rcs); mp_print_t print; vstr_t vstr; @@ -922,11 +988,8 @@ mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, cons bit_vector_clear(&qstr_table_used); bit_vector_clear(&obj_table_used); - // Save function kind and data length. - mp_print_uint(&print, fun_data_len << 3); - - // Save function code. - mp_print_bytes(&print, fun_data, fun_data_len); + // Save the bytecode data (also free the simplified raw code tree at the same time). + save_raw_code_simplified(&print, &rcs); // Create and return bytes representing the .mpy data. return mp_obj_new_bytes_from_vstr(&vstr); diff --git a/py/persistentcode.h b/py/persistentcode.h index 1e0bbbd272f..23cb0936f2f 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -139,7 +139,7 @@ void mp_raw_code_load_file(qstr filename, mp_compiled_module_t *ctx); void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print); void mp_raw_code_save_file(mp_compiled_module_t *cm, qstr filename); -mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, const uint8_t *bytecode); +mp_obj_t mp_raw_code_save_fun_to_bytes(const mp_module_constants_t *consts, mp_proto_fun_t proto_fun); void mp_native_relocate(void *reloc, uint8_t *text, uintptr_t reloc_text); From 878f8004fd311f10e595ed1f7b7160b4b2b64516 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 11:42:49 +1100 Subject: [PATCH 1679/2098] py/objfun: Support __code__ on functions with children. If a function has children then the code object returned from __code__ must contain an `mp_raw_code_t` (actually `mp_raw_code_truncated_t` is enough) that points to the child table. Signed-off-by: Damien George --- py/objfun.c | 21 +++++++++++++++++---- tests/basics/fun_code.py | 5 +++++ tests/basics/fun_code_micropython.py | 19 ------------------- tests/basics/fun_code_micropython.py.exp | 1 - 4 files changed, 22 insertions(+), 24 deletions(-) delete mode 100644 tests/basics/fun_code_micropython.py delete mode 100644 tests/basics/fun_code_micropython.py.exp diff --git a/py/objfun.c b/py/objfun.c index 933da1e494d..996ac861600 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -369,11 +369,24 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { #if MICROPY_PY_FUNCTION_ATTRS_CODE if (attr == MP_QSTR___code__) { const mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); - if ((self->base.type == &mp_type_fun_bc - || self->base.type == &mp_type_gen_wrap) - && self->child_table == NULL) { + if (self->base.type == &mp_type_fun_bc + || self->base.type == &mp_type_gen_wrap) { #if MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC - dest[0] = mp_obj_new_code(self->context->constants, self->bytecode); + #if MICROPY_PERSISTENT_CODE_SAVE + #error "MICROPY_PERSISTENT_CODE_SAVE can't be enabled at this level of MICROPY_PY_BUILTINS_CODE" + #endif + mp_proto_fun_t proto_fun; + if (self->child_table != NULL) { + mp_raw_code_truncated_t *rc = m_new0(mp_raw_code_truncated_t, 1); + rc->kind = MP_CODE_BYTECODE; + rc->is_generator = self->base.type == &mp_type_gen_wrap; + rc->fun_data = self->bytecode; + rc->children = (mp_raw_code_t **)self->child_table; + proto_fun = rc; + } else { + proto_fun = self->bytecode; + } + dest[0] = mp_obj_new_code(self->context->constants, proto_fun); #else dest[0] = mp_obj_new_code(self->context, self->rc, true); #endif diff --git a/tests/basics/fun_code.py b/tests/basics/fun_code.py index 59e1f7ec048..841357e8aad 100644 --- a/tests/basics/fun_code.py +++ b/tests/basics/fun_code.py @@ -34,3 +34,8 @@ def f(): ftype(f.__code__, None) except TypeError: print("TypeError") + +# Test __code__ on functions with children functions. +code = (lambda: (lambda: a)).__code__ +print(ftype(code, {"a": 1})()()) +print(ftype(code, {"a": 2})()()) diff --git a/tests/basics/fun_code_micropython.py b/tests/basics/fun_code_micropython.py deleted file mode 100644 index 2c319a2db8c..00000000000 --- a/tests/basics/fun_code_micropython.py +++ /dev/null @@ -1,19 +0,0 @@ -# Test MicroPython-specific restrictions of function.__code__ attribute. - -try: - (lambda: 0).__code__ -except AttributeError: - print("SKIP") - raise SystemExit - - -def f_with_children(): - def g(): - pass - - -# Can't access __code__ when function has children. -try: - f_with_children.__code__ -except AttributeError: - print("AttributeError") diff --git a/tests/basics/fun_code_micropython.py.exp b/tests/basics/fun_code_micropython.py.exp deleted file mode 100644 index d169edffb4c..00000000000 --- a/tests/basics/fun_code_micropython.py.exp +++ /dev/null @@ -1 +0,0 @@ -AttributeError From 93692caefa91d7aac9101a0d5724e1418ba9e41a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 11:42:59 +1100 Subject: [PATCH 1680/2098] extmod/modmarshal: Support marshal.dumps of functions with children. This commit adds support to the `marshal` module to be able to dump functions that have child functions. For example: import marshal def f(): def child(): return 1 return child marshal.dumps(f.__code__) It also covers the case of marshalling functions that use list comprehensions, because a list comprehension uses a child function. This is made possible by the newly enhanced `mp_raw_code_save_fun_to_bytes()` that can now handle nested functions. Unmarshalling via `marshal.loads()` already supports nested functions because it uses the standard `mp_raw_code_load_mem()` function which is used to import mpy files (and hence can handle all possibilities). Signed-off-by: Damien George --- extmod/modmarshal.c | 12 +---- tests/extmod/marshal_fun_nested.py | 79 +++++++++++++++++++++++++++++ tests/extmod/marshal_micropython.py | 21 -------- tests/micropython/native_marshal.py | 45 ++++++++++++++++ 4 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 tests/extmod/marshal_fun_nested.py delete mode 100644 tests/extmod/marshal_micropython.py create mode 100644 tests/micropython/native_marshal.py diff --git a/extmod/modmarshal.c b/extmod/modmarshal.c index 93d2bcf1150..cc7878e864d 100644 --- a/extmod/modmarshal.c +++ b/extmod/modmarshal.c @@ -36,17 +36,7 @@ static mp_obj_t marshal_dumps(mp_obj_t value_in) { if (mp_obj_is_type(value_in, &mp_type_code)) { mp_obj_code_t *code = MP_OBJ_TO_PTR(value_in); const void *proto_fun = mp_code_get_proto_fun(code); - const uint8_t *bytecode; - if (mp_proto_fun_is_bytecode(proto_fun)) { - bytecode = proto_fun; - } else { - const mp_raw_code_t *rc = proto_fun; - if (!(rc->kind == MP_CODE_BYTECODE && rc->children == NULL)) { - mp_raise_ValueError(MP_ERROR_TEXT("function must be bytecode with no children")); - } - bytecode = rc->fun_data; - } - return mp_raw_code_save_fun_to_bytes(mp_code_get_constants(code), bytecode); + return mp_raw_code_save_fun_to_bytes(mp_code_get_constants(code), proto_fun); } else { mp_raise_ValueError(MP_ERROR_TEXT("unmarshallable object")); } diff --git a/tests/extmod/marshal_fun_nested.py b/tests/extmod/marshal_fun_nested.py new file mode 100644 index 00000000000..fcf8f9a0fa4 --- /dev/null +++ b/tests/extmod/marshal_fun_nested.py @@ -0,0 +1,79 @@ +# Test the marshal module, with functions that have children. + +try: + import marshal + + (lambda: 0).__code__ +except (AttributeError, ImportError): + print("SKIP") + raise SystemExit + + +def f_with_child(): + def child(): + return a + + return child + + +def f_with_child_defargs(): + def child(a="default"): + return a + + return child + + +def f_with_child_closure(): + a = "closure 1" + + def child(): + return a + + a = "closure 2" + return child + + +def f_with_child_closure_defargs(): + a = "closure defargs 1" + + def child(b="defargs default"): + return (a, b) + + a = "closure defargs 1" + return child + + +def f_with_list_comprehension(a): + return [i + a for i in range(4)] + + +ftype = type(lambda: 0) + +# Test function with a child. +f = ftype(marshal.loads(marshal.dumps(f_with_child.__code__)), {"a": "global"}) +print(f()()) + +# Test function with a child that has default arguments. +f = ftype(marshal.loads(marshal.dumps(f_with_child_defargs.__code__)), {}) +print(f()()) +print(f()("non-default")) + +# Test function with a child that is a closure. +f = ftype(marshal.loads(marshal.dumps(f_with_child_closure.__code__)), {}) +print(f()()) + +# Test function with a child that is a closure and has default arguments. +f = ftype(marshal.loads(marshal.dumps(f_with_child_closure_defargs.__code__)), {}) +print(f()()) +print(f()("defargs non-default")) + +# Test function with a list comprehension (which will be an anonymous child). +f = ftype(marshal.loads(marshal.dumps(f_with_list_comprehension.__code__)), {}) +print(f(10)) + +# Test child within a module (the outer scope). +code = compile("def child(a): return a", "", "exec") +f = marshal.loads(marshal.dumps(code)) +ctx = {} +exec(f, ctx) +print(ctx["child"]("arg")) diff --git a/tests/extmod/marshal_micropython.py b/tests/extmod/marshal_micropython.py deleted file mode 100644 index 213b3bf3189..00000000000 --- a/tests/extmod/marshal_micropython.py +++ /dev/null @@ -1,21 +0,0 @@ -# Test the marshal module, MicroPython-specific functionality. - -try: - import marshal -except ImportError: - print("SKIP") - raise SystemExit - -import unittest - - -class Test(unittest.TestCase): - def test_function_with_children(self): - # Can't marshal a function with children (in this case the module has a child function f). - code = compile("def f(): pass", "", "exec") - with self.assertRaises(ValueError): - marshal.dumps(code) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/micropython/native_marshal.py b/tests/micropython/native_marshal.py new file mode 100644 index 00000000000..09a27a374b8 --- /dev/null +++ b/tests/micropython/native_marshal.py @@ -0,0 +1,45 @@ +# Test the marshal module in combination with native/viper functions. + +try: + import marshal + + (lambda: 0).__code__ +except (AttributeError, ImportError): + print("SKIP") + raise SystemExit + +import unittest + + +def f_native(): + @micropython.native + def g(): + pass + + return g + + +def f_viper(): + @micropython.viper + def g(): + pass + + return g + + +class Test(unittest.TestCase): + def test_native_function(self): + # Can't marshal a function with native code. + code = f_native.__code__ + with self.assertRaises(ValueError): + marshal.dumps(code) + + def test_viper_function(self): + # Can't marshal a function with viper code. + code = f_viper.__code__ + with self.assertRaises(ValueError): + marshal.dumps(code) + + +if __name__ == "__main__": + unittest.main() From 85e9d7596ac077c4c46b57f578865c51159e7777 Mon Sep 17 00:00:00 2001 From: Alon Bar-Lev Date: Sat, 9 Aug 2025 19:29:17 +0300 Subject: [PATCH 1681/2098] mimxrt/machine_adc: Initialize LPADC2 for rt117x. Pins may be on the LPADC2 peripheral. Signed-off-by: Alon Bar-Lev --- ports/mimxrt/machine_adc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/mimxrt/machine_adc.c b/ports/mimxrt/machine_adc.c index c332bd70312..da5b20a8c75 100644 --- a/ports/mimxrt/machine_adc.c +++ b/ports/mimxrt/machine_adc.c @@ -126,6 +126,7 @@ void machine_adc_init(void) { adc_config.enableAnalogPreliminary = true; adc_config.referenceVoltageSource = kLPADC_ReferenceVoltageAlt1; LPADC_Init(LPADC1, &adc_config); + LPADC_Init(LPADC2, &adc_config); } #else From 2b07661ee64ef3ad40d9faac51403e0a23352662 Mon Sep 17 00:00:00 2001 From: Alon Bar-Lev Date: Sat, 9 Aug 2025 19:29:07 +0300 Subject: [PATCH 1682/2098] mimxrt/machine_adc: Support ADC channel groups on rt117x. The rt117x uses the LPADC peripheral which supports both A-side and B-side channel inputs, e.g. ADC1_CH1A and ADC1_CH1B. Previously, only `kLPADC_SampleChannelSingleEndSideA` was being used during capture, while the pin may be in side B. The fix in this commit detects the side based on the pin configuration and sets `sampleChannelMode` appropriately. Signed-off-by: Alon Bar-Lev --- ports/mimxrt/boards/make-pins.py | 17 ++++++++++++----- ports/mimxrt/boards/mimxrt_prefix.c | 5 +++-- ports/mimxrt/machine_adc.c | 22 ++++++++++++++++++++-- ports/mimxrt/pin.h | 1 + 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/boards/make-pins.py b/ports/mimxrt/boards/make-pins.py index 55c7f5bd027..6ad60f0545b 100644 --- a/ports/mimxrt/boards/make-pins.py +++ b/ports/mimxrt/boards/make-pins.py @@ -61,18 +61,23 @@ def add_af(self, af_idx, af_name, af): elif af_name == "ADC": adc_regex = r"ADC(?P\d*)_IN(?P\d*)" - lpadc_regex = r"ADC(?P\d*)_CH(?P\d*)" # LPADC for MIMXRT11xx chips + lpadc_regex = r"ADC(?P\d*)_CH(?P\d*)(?P.*)" # LPADC for MIMXRT11xx chips matches = re.finditer(adc_regex, af, re.MULTILINE) for match in matches: self._adc_fns.append( - (int(match.group("instance")), int(match.group("channel")), "ADC") + (int(match.group("instance")), int(match.group("channel")), 0, "ADC") ) matches = re.finditer(lpadc_regex, af, re.MULTILINE) for match in matches: self._adc_fns.append( - (int(match.group("instance")), int(match.group("channel")), "LPADC") + ( + int(match.group("instance")), + int(match.group("channel")), + ord(match.group("channel_group")[0]) - ord("A"), + "LPADC", + ) ) # Use the PIN() macro defined in samd_prefix.c for defining the pin @@ -122,9 +127,11 @@ def print_source(self, out_source): "static const machine_pin_adc_obj_t pin_{:s}_adc[] = {{".format(self.name()), file=out_source, ) - for instance, channel, peripheral in self._adc_fns: + for instance, channel, channel_group, peripheral in self._adc_fns: print( - " PIN_ADC({:s}{:d}, {:d}),".format(peripheral, instance, channel), + " PIN_ADC({:s}{:d}, {:d}, {:d}),".format( + peripheral, instance, channel, channel_group + ), file=out_source, ) print("};", file=out_source) diff --git a/ports/mimxrt/boards/mimxrt_prefix.c b/ports/mimxrt/boards/mimxrt_prefix.c index 49d6e67dc40..e6bba18b9f1 100644 --- a/ports/mimxrt/boards/mimxrt_prefix.c +++ b/ports/mimxrt/boards/mimxrt_prefix.c @@ -16,10 +16,11 @@ .pad_config = (uint32_t)(_pad_config), \ } \ -#define PIN_ADC(_instance, _channel) \ +#define PIN_ADC(_instance, _channel, _channel_group) \ { \ .instance = (_instance), \ - .channel = (_channel) \ + .channel = (_channel), \ + .channel_group = (_channel_group), \ } \ #define PIN(_name, _gpio, _pin, _af_list, _adc_list_len, _adc_list) \ diff --git a/ports/mimxrt/machine_adc.c b/ports/mimxrt/machine_adc.c index da5b20a8c75..1ebfe528612 100644 --- a/ports/mimxrt/machine_adc.c +++ b/ports/mimxrt/machine_adc.c @@ -64,7 +64,19 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p // Get ADC adc id for (int i = 1; i < sizeof(adc_bases) / sizeof(ADC_Type *); ++i) { if (adc_bases[i] == self->adc) { - mp_printf(print, "ADC(%u, channel=%u)", i, self->channel); + mp_printf( + print, + "ADC(%u, channel=%u" + #if defined(MIMXRT117x_SERIES) + ", channel_input=%u" + #endif + ")", + i, + self->channel + #if defined(MIMXRT117x_SERIES) + , self->channel_group + #endif + ); break; } } @@ -83,12 +95,13 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args // Extract arguments ADC_Type *adc_instance = pin->adc_list[0].instance; // NOTE: we only use the first ADC assignment - multiple assignments are not supported for now uint8_t channel = pin->adc_list[0].channel; + uint8_t channel_group = pin->adc_list[0].channel_group; // Create ADC Instance machine_adc_obj_t *o = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); o->adc = adc_instance; o->channel = (uint8_t)channel; - o->channel_group = 0; + o->channel_group = channel_group; o->resolution = 4096; // NOTE: currently only 12bit resolution supported return MP_OBJ_FROM_PTR(o); @@ -104,6 +117,11 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { LPADC_GetDefaultConvCommandConfig(&adc_config); adc_config.channelNumber = self->channel; adc_config.sampleScaleMode = kLPADC_SamplePartScale; + if (self->channel_group == 0) { + adc_config.sampleChannelMode = kLPADC_SampleChannelSingleEndSideA; + } else { + adc_config.sampleChannelMode = kLPADC_SampleChannelSingleEndSideB; + } LPADC_SetConvCommandConfig(self->adc, 1, &adc_config); // Set Trigger mode diff --git a/ports/mimxrt/pin.h b/ports/mimxrt/pin.h index 7ee28414659..94cd3cad4d7 100644 --- a/ports/mimxrt/pin.h +++ b/ports/mimxrt/pin.h @@ -117,6 +117,7 @@ typedef struct { typedef struct { ADC_Type *instance; uint8_t channel; + uint8_t channel_group; } machine_pin_adc_obj_t; typedef struct { From 2a5a8cc16edc0ac6a6e0abc88ff5c95a28998d7e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 14 Aug 2025 13:50:53 +1000 Subject: [PATCH 1683/2098] mimxrt/eth: Add DP83867 PHY driver support. Adds new PHY driver for TI DP83867 Gigabit Ethernet PHY. Signed-off-by: Andrew Leech --- ports/mimxrt/Makefile | 1 + ports/mimxrt/eth.c | 3 +- ports/mimxrt/eth.h | 1 + .../phy/device/phydp83867/fsl_phydp83867.c | 302 ++++++++++++++++++ .../phy/device/phydp83867/fsl_phydp83867.h | 165 ++++++++++ ports/mimxrt/network_lan.c | 4 + 6 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.c create mode 100644 ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.h diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 52003ed4a4d..08f99d68b53 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -113,6 +113,7 @@ SRC_ETH_C += \ $(MCUX_SDK_DIR)/drivers/enet/fsl_enet.c \ hal/phy/device/phydp83825/fsl_phydp83825.c \ hal/phy/device/phydp83848/fsl_phydp83848.c \ + hal/phy/device/phydp83867/fsl_phydp83867.c \ hal/phy/device/phyksz8081/fsl_phyksz8081.c \ hal/phy/device/phylan8720/fsl_phylan8720.c \ hal/phy/device/phyrtl8211f/fsl_phyrtl8211f.c \ diff --git a/ports/mimxrt/eth.c b/ports/mimxrt/eth.c index 1fbd9d3895f..bbca2f66465 100644 --- a/ports/mimxrt/eth.c +++ b/ports/mimxrt/eth.c @@ -44,6 +44,7 @@ #include "hal/phy/device/phyksz8081/fsl_phyksz8081.h" #include "hal/phy/device/phydp83825/fsl_phydp83825.h" #include "hal/phy/device/phydp83848/fsl_phydp83848.h" +#include "hal/phy/device/phydp83867/fsl_phydp83867.h" #include "hal/phy/device/phylan8720/fsl_phylan8720.h" #include "hal/phy/device/phyrtl8211f/fsl_phyrtl8211f.h" @@ -422,7 +423,7 @@ void eth_init_1(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int ph uint32_t source_clock = eth_clock_init(eth_id, phy_clock); const machine_pin_obj_t *reset_pin = NULL; - #if defined(pin_ENET_1_INT) + #if defined(pin_ENET_1_RESET) reset_pin = pin_ENET_1_RESET; #endif const machine_pin_obj_t *int_pin = NULL; diff --git a/ports/mimxrt/eth.h b/ports/mimxrt/eth.h index afb5d5edf37..0d9ed599bc4 100644 --- a/ports/mimxrt/eth.h +++ b/ports/mimxrt/eth.h @@ -47,6 +47,7 @@ enum { PHY_KSZ8081 = 0, PHY_DP83825, PHY_DP83848, + PHY_DP83867, PHY_LAN8720, PHY_RTL8211F, }; diff --git a/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.c b/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.c new file mode 100644 index 00000000000..87943881d8e --- /dev/null +++ b/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.c @@ -0,0 +1,302 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * 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 "fsl_phydp83867.h" + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/*! @brief Defines the PHY DP83867 vendor defined registers. */ +#define PHY_PHYSTS_REG 0x11U /*!< The PHY Status register. */ + +/*! @brief Defines the PHY DP83867 ID number. */ +#define PHY_CONTROL_ID1 0x2000U /*!< The PHY ID1 (upper 16 bits). */ +#define PHY_CONTROL_ID2 0xA231U /*!< The PHY ID2 (lower 16 bits). */ +#define PHY_FULL_ID 0x2000A231U /*!< Full PHY ID. */ + +/*! @brief Defines the mask flag in PHYSTS register. */ +#define PHY_PHYSTS_LINKSTATUS_MASK 0x0400U /*!< The PHY link status mask. */ +#define PHY_PHYSTS_LINKSPEED_MASK 0xC000U /*!< The PHY link speed mask. */ +#define PHY_PHYSTS_LINKDUPLEX_MASK 0x2000U /*!< The PHY link duplex mask. */ +#define PHY_PHYSTS_LINKSPEED_SHIFT 14U /*!< The link speed shift */ + +/*! @brief Link speed values from PHYSTS register. */ +#define PHY_PHYSTS_LINKSPEED_10M 0U /*!< 10M link speed. */ +#define PHY_PHYSTS_LINKSPEED_100M 1U /*!< 100M link speed. */ +#define PHY_PHYSTS_LINKSPEED_1000M 2U /*!< 1000M link speed. */ + +/*! @brief Defines the timeout macro. */ +#define PHY_READID_TIMEOUT_COUNT 1000U + +/******************************************************************************* + * Prototypes + ******************************************************************************/ + +/******************************************************************************* + * Variables + ******************************************************************************/ + +const phy_operations_t phydp83867_ops = {.phyInit = PHY_DP83867_Init, + .phyWrite = PHY_DP83867_Write, + .phyRead = PHY_DP83867_Read, + .getAutoNegoStatus = PHY_DP83867_GetAutoNegotiationStatus, + .getLinkStatus = PHY_DP83867_GetLinkStatus, + .getLinkSpeedDuplex = PHY_DP83867_GetLinkSpeedDuplex, + .setLinkSpeedDuplex = PHY_DP83867_SetLinkSpeedDuplex, + .enableLoopback = PHY_DP83867_EnableLoopback}; + +/******************************************************************************* + * Code + ******************************************************************************/ + +status_t PHY_DP83867_Init(phy_handle_t *handle, const phy_config_t *config) { + uint32_t counter = PHY_READID_TIMEOUT_COUNT; + status_t result; + uint32_t regValue = 0U; + + + /* Init MDIO interface. */ + MDIO_Init(handle->mdioHandle); + + /* Assign phy address. */ + handle->phyAddr = config->phyAddr; + + + /* Check PHY ID. */ + do + { + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_ID1_REG, ®Value); + if (result != kStatus_Success) { + return result; + } + counter--; + } while ((regValue != PHY_CONTROL_ID1) && (counter != 0U)); + + if (counter == 0U) { + return kStatus_Fail; + } + + + /* Reset PHY. */ + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, PHY_BCTL_RESET_MASK); + if (result != kStatus_Success) { + return result; + } + + + /* Wait for reset to complete */ + counter = PHY_READID_TIMEOUT_COUNT; + do { + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, ®Value); + if (result != kStatus_Success) { + return result; + } + counter--; + } while ((regValue & PHY_BCTL_RESET_MASK) && (counter != 0U)); + + if (counter == 0U) { + return kStatus_Fail; + } + + + if (config->autoNeg) { + /* Set the auto-negotiation. */ + result = + MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_AUTONEG_ADVERTISE_REG, + PHY_100BASETX_FULLDUPLEX_MASK | PHY_100BASETX_HALFDUPLEX_MASK | PHY_10BASETX_FULLDUPLEX_MASK | + PHY_10BASETX_HALFDUPLEX_MASK | PHY_IEEE802_3_SELECTOR_MASK); + if (result == kStatus_Success) { + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_1000BASET_CONTROL_REG, + PHY_1000BASET_FULLDUPLEX_MASK); + if (result == kStatus_Success) { + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, ®Value); + if (result == kStatus_Success) { + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, + (regValue | PHY_BCTL_AUTONEG_MASK | PHY_BCTL_RESTART_AUTONEG_MASK)); + } + } + } + } else { + /* Disable isolate mode */ + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, ®Value); + if (result != kStatus_Success) { + return result; + } + regValue &= ~PHY_BCTL_ISOLATE_MASK; + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, regValue); + if (result != kStatus_Success) { + return result; + } + + /* Disable the auto-negotiation and set user-defined speed/duplex configuration. */ + result = PHY_DP83867_SetLinkSpeedDuplex(handle, config->speed, config->duplex); + } + + + return result; +} + +status_t PHY_DP83867_Write(phy_handle_t *handle, uint32_t phyReg, uint32_t data) { + return MDIO_Write(handle->mdioHandle, handle->phyAddr, phyReg, data); +} + +status_t PHY_DP83867_Read(phy_handle_t *handle, uint32_t phyReg, uint32_t *dataPtr) { + return MDIO_Read(handle->mdioHandle, handle->phyAddr, phyReg, dataPtr); +} + +status_t PHY_DP83867_GetAutoNegotiationStatus(phy_handle_t *handle, bool *status) { + assert(status); + + status_t result; + uint32_t regValue; + + *status = false; + + /* Check auto negotiation complete. */ + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICSTATUS_REG, ®Value); + if (result == kStatus_Success) { + if ((regValue & PHY_BSTATUS_AUTONEGCOMP_MASK) != 0U) { + *status = true; + } + } + return result; +} + +status_t PHY_DP83867_GetLinkStatus(phy_handle_t *handle, bool *status) { + assert(status); + + status_t result; + uint32_t regValue; + + /* Read the PHY Status register. */ + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_PHYSTS_REG, ®Value); + if (result == kStatus_Success) { + if ((PHY_PHYSTS_LINKSTATUS_MASK & regValue) != 0U) { + /* Link up. */ + *status = true; + } else { + /* Link down. */ + *status = false; + } + } + return result; +} + +status_t PHY_DP83867_GetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t *speed, phy_duplex_t *duplex) { + assert(!((speed == NULL) && (duplex == NULL))); + + status_t result; + uint32_t regValue; + + /* Read the status register. */ + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_PHYSTS_REG, ®Value); + if (result == kStatus_Success) { + if (speed != NULL) { + switch ((regValue & PHY_PHYSTS_LINKSPEED_MASK) >> PHY_PHYSTS_LINKSPEED_SHIFT) + { + case PHY_PHYSTS_LINKSPEED_10M: + *speed = kPHY_Speed10M; + break; + case PHY_PHYSTS_LINKSPEED_100M: + *speed = kPHY_Speed100M; + break; + case PHY_PHYSTS_LINKSPEED_1000M: + *speed = kPHY_Speed1000M; + break; + default: + *speed = kPHY_Speed10M; + break; + } + } + + if (duplex != NULL) { + if ((regValue & PHY_PHYSTS_LINKDUPLEX_MASK) != 0U) { + *duplex = kPHY_FullDuplex; + } else { + *duplex = kPHY_HalfDuplex; + } + } + } + return result; +} + +status_t PHY_DP83867_SetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t speed, phy_duplex_t duplex) { + status_t result; + uint32_t regValue; + + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, ®Value); + if (result == kStatus_Success) { + /* Disable the auto-negotiation and set according to user-defined configuration. */ + regValue &= ~PHY_BCTL_AUTONEG_MASK; + if (speed == kPHY_Speed1000M) { + regValue &= ~PHY_BCTL_SPEED0_MASK; + regValue |= PHY_BCTL_SPEED1_MASK; + } else if (speed == kPHY_Speed100M) { + regValue |= PHY_BCTL_SPEED0_MASK; + regValue &= ~PHY_BCTL_SPEED1_MASK; + } else { + regValue &= ~PHY_BCTL_SPEED0_MASK; + regValue &= ~PHY_BCTL_SPEED1_MASK; + } + if (duplex == kPHY_FullDuplex) { + regValue |= PHY_BCTL_DUPLEX_MASK; + } else { + regValue &= ~PHY_BCTL_DUPLEX_MASK; + } + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, regValue); + } + return result; +} + +status_t PHY_DP83867_EnableLoopback(phy_handle_t *handle, phy_loop_t mode, phy_speed_t speed, bool enable) { + /* This PHY only supports local loopback. */ + assert(mode == kPHY_LocalLoop); + + status_t result; + uint32_t regValue; + + /* Set the loop mode. */ + if (enable) { + if (speed == kPHY_Speed1000M) { + regValue = PHY_BCTL_SPEED1_MASK | PHY_BCTL_DUPLEX_MASK | PHY_BCTL_LOOP_MASK; + } else if (speed == kPHY_Speed100M) { + regValue = PHY_BCTL_SPEED0_MASK | PHY_BCTL_DUPLEX_MASK | PHY_BCTL_LOOP_MASK; + } else { + regValue = PHY_BCTL_DUPLEX_MASK | PHY_BCTL_LOOP_MASK; + } + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, regValue); + } else { + /* First read the current status in control register. */ + result = MDIO_Read(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, ®Value); + if (result == kStatus_Success) { + regValue &= ~PHY_BCTL_LOOP_MASK; + result = MDIO_Write(handle->mdioHandle, handle->phyAddr, PHY_BASICCONTROL_REG, + (regValue | PHY_BCTL_RESTART_AUTONEG_MASK)); + } + } + return result; +} diff --git a/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.h b/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.h new file mode 100644 index 00000000000..ba0f27daa9a --- /dev/null +++ b/ports/mimxrt/hal/phy/device/phydp83867/fsl_phydp83867.h @@ -0,0 +1,165 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * 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 _FSL_PHYDP83867_H_ +#define _FSL_PHYDP83867_H_ + +#include "fsl_phy.h" + +/*! + * @addtogroup phy_driver + * @{ + */ + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/*! @brief PHY operations structure. */ +extern const phy_operations_t phydp83867_ops; + +/******************************************************************************* + * API + ******************************************************************************/ + +#if defined(__cplusplus) +extern "C" { +#endif + +/*! + * @name PHY Driver + * @{ + */ + +/*! + * @brief Initializes PHY. + * + * This function initialize PHY. + * + * @param handle PHY device handle. + * @param config Pointer to structure of phy_config_t. + * @retval kStatus_Success PHY initialization succeeds + * @retval kStatus_Fail PHY initialization fails + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_Init(phy_handle_t *handle, const phy_config_t *config); + +/*! + * @brief PHY Write function. This function writes data over the SMI to + * the specified PHY register. This function is called by all PHY interfaces. + * + * @param handle PHY device handle. + * @param phyReg The PHY register. + * @param data The data written to the PHY register. + * @retval kStatus_Success PHY write success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_Write(phy_handle_t *handle, uint32_t phyReg, uint32_t data); + +/*! + * @brief PHY Read function. This interface reads data over the SMI from the + * specified PHY register. This function is called by all PHY interfaces. + * + * @param handle PHY device handle. + * @param phyReg The PHY register. + * @param dataPtr The address to store the data read from the PHY register. + * @retval kStatus_Success PHY read success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_Read(phy_handle_t *handle, uint32_t phyReg, uint32_t *dataPtr); + +/*! + * @brief Gets the PHY auto-negotiation status. + * + * @param handle PHY device handle. + * @param status The auto-negotiation status of the PHY. + * - true the auto-negotiation is over. + * - false the auto-negotiation is on-going or not started. + * @retval kStatus_Success PHY gets status success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_GetAutoNegotiationStatus(phy_handle_t *handle, bool *status); + +/*! + * @brief Gets the PHY link status. + * + * @param handle PHY device handle. + * @param status The link up or down status of the PHY. + * - true the link is up. + * - false the link is down. + * @retval kStatus_Success PHY gets link status success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_GetLinkStatus(phy_handle_t *handle, bool *status); + +/*! + * @brief Gets the PHY link speed and duplex. + * + * @brief This function gets the speed and duplex mode of PHY. User can give one of speed + * and duplex address parameter and set the other as NULL if only wants to get one of them. + * + * @param handle PHY device handle. + * @param speed The address of PHY link speed. + * @param duplex The link duplex of PHY. + * @retval kStatus_Success PHY gets link speed and duplex success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_GetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t *speed, phy_duplex_t *duplex); + +/*! + * @brief Sets the PHY link speed and duplex. + * + * @param handle PHY device handle. + * @param speed Specified PHY link speed. + * @param duplex Specified PHY link duplex. + * @retval kStatus_Success PHY gets status success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_SetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t speed, phy_duplex_t duplex); + +/*! + * @brief Enables/disables PHY loopback. + * + * @param handle PHY device handle. + * @param mode The loopback mode to be enabled, please see "phy_loop_t". + * All loopback modes should not be set together, when one loopback mode is set + * another should be disabled. + * @param speed PHY speed for loopback mode. + * @param enable True to enable, false to disable. + * @retval kStatus_Success PHY loopback success + * @retval kStatus_PHY_SMIVisitTimeout PHY SMI visit time out + */ +status_t PHY_DP83867_EnableLoopback(phy_handle_t *handle, phy_loop_t mode, phy_speed_t speed, bool enable); + +/* @} */ + +#if defined(__cplusplus) +} +#endif + +/*! @}*/ + +#endif /* _FSL_PHYDP83867_H_ */ diff --git a/ports/mimxrt/network_lan.c b/ports/mimxrt/network_lan.c index 7d6ae2d7277..46f1bfa186d 100644 --- a/ports/mimxrt/network_lan.c +++ b/ports/mimxrt/network_lan.c @@ -35,6 +35,7 @@ #include "hal/phy/device/phyksz8081/fsl_phyksz8081.h" #include "hal/phy/device/phydp83825/fsl_phydp83825.h" #include "hal/phy/device/phydp83848/fsl_phydp83848.h" +#include "hal/phy/device/phydp83867/fsl_phydp83867.h" #include "hal/phy/device/phylan8720/fsl_phylan8720.h" #include "hal/phy/device/phyrtl8211f/fsl_phyrtl8211f.h" @@ -113,6 +114,8 @@ static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, s phy_ops = &phydp83825_ops; } else if (phy_type == PHY_DP83848) { phy_ops = &phydp83848_ops; + } else if (phy_type == PHY_DP83867) { + phy_ops = &phydp83867_ops; } else if (phy_type == PHY_LAN8720) { phy_ops = &phylan8720_ops; } else if (phy_type == PHY_RTL8211F) { @@ -254,6 +257,7 @@ static const mp_rom_map_elem_t network_lan_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8081), MP_ROM_INT(PHY_KSZ8081) }, { MP_ROM_QSTR(MP_QSTR_PHY_DP83825), MP_ROM_INT(PHY_DP83825) }, { MP_ROM_QSTR(MP_QSTR_PHY_DP83848), MP_ROM_INT(PHY_DP83848) }, + { MP_ROM_QSTR(MP_QSTR_PHY_DP83867), MP_ROM_INT(PHY_DP83867) }, { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, { MP_ROM_QSTR(MP_QSTR_PHY_RTL8211F), MP_ROM_INT(PHY_RTL8211F) }, { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(PHY_TX_CLK_IN) }, From 5021137f32d77229f38ae2ab39aa30b5b54dcc1c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 11 Nov 2025 13:31:25 +1100 Subject: [PATCH 1684/2098] mimxrt/boards/MIMXRT1170_EVK: Remove obsolete pin defines. The definitions from pins.csv are used directly in mimxrt/eth.c Signed-off-by: Andrew Leech --- ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h index 131c5e17254..5982326ed28 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h @@ -175,10 +175,6 @@ #define ENET_1_PHY_OPS phyrtl8211f_ops // 1G Ethernet PIN definitions -// No INT pin for ENET_1G -#define ENET_1_RESET_PIN &pin_GPIO_DISP_B2_13 -#define ENET_1_INT_PIN NULL - #define IOMUX_TABLE_ENET_1 \ { IOMUXC_GPIO_DISP_B1_00_ENET_1G_RX_EN, 0, 0x08U }, \ { IOMUXC_GPIO_DISP_B1_01_ENET_1G_RX_CLK, 0, 0x08U }, \ From 77e62f627ac7edac7373c34d70f9dc158e1ef7f5 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 20 Nov 2025 13:52:09 +1100 Subject: [PATCH 1685/2098] mimxrt/eth: Improve Dual Ethernet configuration. Signed-off-by: Andrew Leech --- .../boards/MIMXRT1170_EVK/mpconfigboard.h | 4 +- ports/mimxrt/eth.c | 67 +++++++++++++------ ports/mimxrt/eth.h | 7 +- ports/mimxrt/mpconfigport.h | 2 +- ports/mimxrt/network_lan.c | 60 +++++++++++++---- 5 files changed, 101 insertions(+), 39 deletions(-) diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h index 5982326ed28..1154aac8300 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h @@ -167,9 +167,7 @@ { IOMUXC_GPIO_AD_33_ENET_MDIO, 0, 0x06u }, \ { IOMUXC_GPIO_AD_32_ENET_MDC, 0, 0x06u }, -// A second ETH port is present. -#define ENET_DUAL_PORT (1) -// 1G Transceiver Phy Parameters +// 1G Transceiver Phy Parameters (second ETH port) #define ENET_1_PHY_ADDRESS (1) #define ENET_1_PHY RTL8211F #define ENET_1_PHY_OPS phyrtl8211f_ops diff --git a/ports/mimxrt/eth.c b/ports/mimxrt/eth.c index bbca2f66465..308f5407a29 100644 --- a/ports/mimxrt/eth.c +++ b/ports/mimxrt/eth.c @@ -31,7 +31,7 @@ #include "py/mperrno.h" #include "ticks.h" -#if defined(IOMUX_TABLE_ENET) +#if defined(ENET_PHY_ADDRESS) || defined(ENET_1_PHY_ADDRESS) #include "pin.h" #include "shared/netutils/netutils.h" @@ -75,6 +75,8 @@ typedef struct _iomux_table_t { uint32_t configValue; } iomux_table_t; +#if defined(ENET_PHY_ADDRESS) + // ETH0 buffers and handles static AT_NONCACHEABLE_SECTION_ALIGN(enet_rx_bd_struct_t g_rxBuffDescrip[ENET_RXBD_NUM], ENET_BUFF_ALIGNMENT); static AT_NONCACHEABLE_SECTION_ALIGN(enet_tx_bd_struct_t g_txBuffDescrip[ENET_TXBD_NUM], ENET_BUFF_ALIGNMENT); @@ -111,7 +113,9 @@ static const iomux_table_t iomux_table_enet[] = { static uint8_t hw_addr[6]; // The MAC address field -#if defined(ENET_DUAL_PORT) +#endif // defined(ENET_PHY_ADDRESS) + +#if defined(ENET_1_PHY_ADDRESS) // ETH1 buffers and handles static AT_NONCACHEABLE_SECTION_ALIGN(enet_rx_bd_struct_t g_rxBuffDescrip_1[ENET_RXBD_NUM], ENET_BUFF_ALIGNMENT); @@ -148,17 +152,14 @@ static const iomux_table_t iomux_table_enet_1[] = { static uint8_t hw_addr_1[6]; // The MAC address field -#endif - -#if defined(ENET_DUAL_PORT) +// Define ENET_1 to the appropriate controller for this hardware #if defined MIMXRT117x_SERIES #define ENET_1 ENET_1G #else #define ENET_1 ENET2 #endif -#else -#define ENET_1 ENET -#endif + +#endif // defined(ENET_1_PHY_ADDRESS) #define PHY_AUTONEGO_TIMEOUT_US (5000000) #define PHY_SETTLE_TIME_US (1000) @@ -357,6 +358,8 @@ static void eth_phy_init(phy_handle_t *phyHandle, phy_config_t *phy_config, mp_hal_delay_us(phy_settle_time); } +#if defined(ENET_PHY_ADDRESS) + // eth_init: Set up GPIO and the transceiver void eth_init_0(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int phy_addr, bool phy_clock) { // Configuration values @@ -411,7 +414,9 @@ void eth_init_0(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int ph ENET_ActiveRead(ENET); } -#if defined(ENET_DUAL_PORT) +#endif // defined(ENET_PHY_ADDRESS) + +#if defined(ENET_1_PHY_ADDRESS) // eth_init: Set up GPIO and the transceiver void eth_init_1(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int phy_addr, bool phy_clock) { @@ -474,7 +479,8 @@ void eth_init_1(eth_t *self, int eth_id, const phy_operations_t *phy_ops, int ph ENET_ActiveRead(ENET_1); } -#endif +#endif // defined(ENET_1_PHY_ADDRESS) + // Initialize the phy interface static int eth_mac_init(eth_t *self) { return 0; @@ -512,14 +518,26 @@ static err_t eth_send_frame_blocking(ENET_Type *base, enet_handle_t *handle, uin static err_t eth_netif_output(struct netif *netif, struct pbuf *p) { // This function should always be called from a context where PendSV-level IRQs are disabled status_t status; - ENET_Type *enet = ENET; - enet_handle_t *handle = &g_handle; + ENET_Type *enet; + enet_handle_t *handle; - #if defined ENET_DUAL_PORT + #if defined(ENET_PHY_ADDRESS) && defined(ENET_1_PHY_ADDRESS) + // Dual port: select based on netif->state if (netif->state == ð_instance1) { enet = ENET_1; handle = &g_handle_1; + } else { + enet = ENET; + handle = &g_handle; } + #elif defined(ENET_1_PHY_ADDRESS) + // Only ENET_1 available + enet = ENET_1; + handle = &g_handle_1; + #else + // Only ENET available + enet = ENET; + handle = &g_handle; #endif eth_trace(netif->state, (size_t)-1, p, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE); @@ -562,15 +580,18 @@ static void eth_lwip_init(eth_t *self) { ip_addr_t ipconfig[4]; self->netif.hwaddr_len = 6; + #if defined(ENET_PHY_ADDRESS) if (self == ð_instance0) { memcpy(self->netif.hwaddr, hw_addr, 6); IP4_ADDR(&ipconfig[0], 192, 168, 0, 2); - #if defined ENET_DUAL_PORT - } else { + } + #endif + #if defined(ENET_1_PHY_ADDRESS) + if (self == ð_instance1) { memcpy(self->netif.hwaddr, hw_addr_1, 6); IP4_ADDR(&ipconfig[0], 192, 168, 0, 3); - #endif } + #endif IP4_ADDR(&ipconfig[1], 255, 255, 255, 0); IP4_ADDR(&ipconfig[2], 192, 168, 0, 1); IP4_ADDR(&ipconfig[3], 8, 8, 8, 8); @@ -578,7 +599,11 @@ static void eth_lwip_init(eth_t *self) { MICROPY_PY_LWIP_ENTER n->name[0] = 'e'; + #if defined(ENET_PHY_ADDRESS) n->name[1] = (self == ð_instance0 ? '0' : '1'); + #else + n->name[1] = '1'; + #endif netif_add(n, &ipconfig[0], &ipconfig[1], &ipconfig[2], self, eth_netif_init, ethernet_input); netif_set_hostname(n, mod_network_hostname_data); netif_set_default(n); @@ -620,8 +645,10 @@ int eth_link_status(eth_t *self) { } } else { bool link; - #if defined ENET_DUAL_PORT + #if defined(ENET_PHY_ADDRESS) && defined(ENET_1_PHY_ADDRESS) PHY_GetLinkStatus(self == ð_instance0 ? &phyHandle : &phyHandle_1, &link); + #elif defined(ENET_1_PHY_ADDRESS) + PHY_GetLinkStatus(&phyHandle_1, &link); #else PHY_GetLinkStatus(&phyHandle, &link); #endif @@ -654,10 +681,12 @@ int eth_stop(eth_t *self) { } void eth_low_power_mode(eth_t *self, bool enable) { - #if defined ENET_DUAL_PORT + #if defined(ENET_PHY_ADDRESS) && defined(ENET_1_PHY_ADDRESS) ENET_EnableSleepMode(self == ð_instance0 ? ENET : ENET_1, enable); + #elif defined(ENET_1_PHY_ADDRESS) + ENET_EnableSleepMode(ENET_1, enable); #else ENET_EnableSleepMode(ENET, enable); #endif } -#endif // defined(IOMUX_TABLE_ENET) +#endif // defined(ENET_PHY_ADDRESS) || defined(ENET_1_PHY_ADDRESS) diff --git a/ports/mimxrt/eth.h b/ports/mimxrt/eth.h index 0d9ed599bc4..4c87d1b5178 100644 --- a/ports/mimxrt/eth.h +++ b/ports/mimxrt/eth.h @@ -28,11 +28,14 @@ #define MICROPY_INCLUDED_MIMXRT_ETH_H typedef struct _eth_t eth_t; + +#if defined(ENET_PHY_ADDRESS) extern eth_t eth_instance0; -extern eth_t eth_instance1; void eth_init_0(eth_t *self, int mac_idx, const phy_operations_t *phy_ops, int phy_addr, bool phy_clock); +#endif -#if defined(ENET_DUAL_PORT) +#if defined(ENET_1_PHY_ADDRESS) +extern eth_t eth_instance1; void eth_init_1(eth_t *self, int mac_idx, const phy_operations_t *phy_ops, int phy_addr, bool phy_clock); #endif diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 45316b904b6..52289549d7c 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -179,7 +179,7 @@ uint32_t trng_random_u32(void); // Hooks to add builtins -#if defined(IOMUX_TABLE_ENET) +#if defined(ENET_PHY_ADDRESS) || defined(ENET_1_PHY_ADDRESS) extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_NIC_ETH { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&network_lan_type) }, #else diff --git a/ports/mimxrt/network_lan.c b/ports/mimxrt/network_lan.c index 46f1bfa186d..b020d53e6ef 100644 --- a/ports/mimxrt/network_lan.c +++ b/ports/mimxrt/network_lan.c @@ -28,7 +28,7 @@ #include "py/mphal.h" #include "extmod/modnetwork.h" -#if defined(IOMUX_TABLE_ENET) +#if defined(ENET_PHY_ADDRESS) || defined(ENET_1_PHY_ADDRESS) #include "fsl_phy.h" #include "eth.h" @@ -55,9 +55,13 @@ typedef struct _network_lan_obj_t { eth_t *eth; } network_lan_obj_t; +// Forward declaration of the type +extern const mp_obj_type_t network_lan_type; +#if defined(ENET_PHY_ADDRESS) static const network_lan_obj_t network_lan_eth0 = { { &network_lan_type }, ð_instance0 }; -#if defined(ENET_DUAL_PORT) +#endif +#if defined(ENET_1_PHY_ADDRESS) static const network_lan_obj_t network_lan_eth1 = { { &network_lan_type }, ð_instance1 }; #endif @@ -65,8 +69,19 @@ static void network_lan_print(const mp_print_t *print, mp_obj_t self_in, mp_prin network_lan_obj_t *self = MP_OBJ_TO_PTR(self_in); struct netif *netif = eth_netif(self->eth); int status = eth_link_status(self->eth); + int eth_id = 0; + #if defined(ENET_PHY_ADDRESS) + if (self->eth == ð_instance0) { + eth_id = 0; + } + #endif + #if defined(ENET_1_PHY_ADDRESS) + if (self->eth == ð_instance1) { + eth_id = 1; + } + #endif mp_printf(print, "", - self->eth == ð_instance0 ? 0 : 1, + eth_id, status, netif->ip_addr.addr & 0xff, netif->ip_addr.addr >> 8 & 0xff, @@ -77,8 +92,18 @@ static void network_lan_print(const mp_print_t *print, mp_obj_t self_in, mp_prin static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_phy_type, ARG_phy_addr, ARG_ref_clk_mode}; + + // Default to port 0 if ENET available, else port 1 if only ENET_1 available + #if defined(ENET_PHY_ADDRESS) + #define DEFAULT_ETH_ID 0 + #elif defined(ENET_1_PHY_ADDRESS) + #define DEFAULT_ETH_ID 1 + #else + #error "No Ethernet PHY configured" + #endif + static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = DEFAULT_ETH_ID} }, { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_phy_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_ref_clk_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, @@ -92,18 +117,21 @@ static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, s bool phy_clock; int mac_id = args[ARG_id].u_int; - // set default + // set default based on which interface is being used + #if defined(ENET_PHY_ADDRESS) if (mac_id == 0) { phy_ops = &ENET_PHY_OPS; phy_addr = ENET_PHY_ADDRESS; phy_clock = ENET_TX_CLK_OUTPUT; - #if defined(ENET_DUAL_PORT) - } else { + } + #endif + #if defined(ENET_1_PHY_ADDRESS) + if (mac_id == 1) { phy_ops = &ENET_1_PHY_OPS; phy_addr = ENET_1_PHY_ADDRESS; phy_clock = ENET_1_TX_CLK_OUTPUT; - #endif } + #endif // Select PHY driver int phy_type = args[ARG_phy_type].u_int; @@ -132,17 +160,21 @@ static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, s phy_clock = args[ARG_ref_clk_mode].u_int; } - // Prepare for two ETH interfaces. - const network_lan_obj_t *self; + // Initialize the appropriate ETH interface + const network_lan_obj_t *self = NULL; + #if defined(ENET_PHY_ADDRESS) if (mac_id == 0) { self = &network_lan_eth0; eth_init_0(self->eth, MP_HAL_MAC_ETH0, phy_ops, phy_addr, phy_clock); - #if defined(ENET_DUAL_PORT) - } else if (mac_id == 1) { + } + #endif + #if defined(ENET_1_PHY_ADDRESS) + if (mac_id == 1) { self = &network_lan_eth1; eth_init_1(self->eth, MP_HAL_MAC_ETH1, phy_ops, phy_addr, phy_clock); + } #endif - } else { + if (self == NULL) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid LAN interface %d"), mac_id); } @@ -275,4 +307,4 @@ MP_DEFINE_CONST_OBJ_TYPE( ); -#endif // defined(IOMUX_TABLE_ENET) +#endif // defined(ENET_PHY_ADDRESS) || defined(ENET_1_PHY_ADDRESS) From af88d65e8661b9c0a3ad9d6b4f61cde6f7948597 Mon Sep 17 00:00:00 2001 From: Algy Tynan Date: Mon, 8 Dec 2025 16:07:52 +1100 Subject: [PATCH 1686/2098] mimxrt: Add ALT11 pin mode support for MIMXRT1176. This adds support for the ALT11 alternate function mode on MIMXRT1176 MCUs, enabling FLEXPWM channels on GPIO_AD pins that were previously unavailable for PWM use. Changes: - Add PIN_AF_MODE_ALT11 enum to pin.h - Add ALT11 column to MIMXRT1176_af.csv with FLEXPWM mappings - Enables FLEXPWM1-4 on GPIO_AD_06 through GPIO_AD_21 This change only affects MIMXRT1176-based boards (MIMXRT1170_EVK and PHYBOARD_RT1170). Other MIMXRT family chips use alternate function modes ALT0-ALT9 and are not affected. Tested on MIMXRT1176 hardware with PWM output on GPIO_AD_14 (FLEXPWM3_PWM0_X). Signed-off-by: Algy Tynan --- docs/mimxrt/pinout.rst | 70 +++--- ports/mimxrt/boards/MIMXRT1176_af.csv | 350 +++++++++++++------------- ports/mimxrt/pin.h | 1 + 3 files changed, 214 insertions(+), 207 deletions(-) diff --git a/docs/mimxrt/pinout.rst b/docs/mimxrt/pinout.rst index d2b62d56d01..09953164bde 100644 --- a/docs/mimxrt/pinout.rst +++ b/docs/mimxrt/pinout.rst @@ -67,41 +67,43 @@ PWM pin assignment Pins are specified in the same way as for the Pin class. The following tables show the assignment of the board Pins to PWM modules: -=========== ========== ========== ====== ========== ====== ======== -Pin/ MIMXRT 1010 1015 1020 1050/60/64 1170 Metro M7 -=========== ========== ========== ====== ========== ====== ======== -D0 - Q1/1 F1/1/B - - - -D1 - Q1/0 F1/1/A - - - -D2 F1/3/B F1/3/A - F1/3/B - - -D3 F1/3/A F1/0/A F2/3/B F4/0/A F1/2/A - -D4 F1/3/A (*) Q1/2 Q2/1 F2/3/A Q4/2 F1/0/B -D5 F1/0/B (*) F1/0/B F2/3/A F1/3/A F1/2/B F1/0/A -D6 - F1/2/B F2/0/A Q3/2 F1/0/A - -D7 - - F1/0/A Q3/3 - - -D8 F1/0/A F1/1/B F1/0/B F1/1/X Q4/3 F1/3/A -D9 F1/1/B (*) F1/2/A F2/0/B F1/0/X F1/0/B F1/3/B -D10 F1/3/B - F2/2/B F1/0/B (*) F2/2/B F1/2/A -D11 F1/2/A - F2/1/A F1/1/A (*) - F1/2/B -D12 F1/2/B - F2/1/B F1/1/B (*) - F1/1/A -D13 F1/3/A - F2/2/A F1/0/A (*) F2/2/A F1/1/B -D14 F1/0/B - - F2/3/B - F1/0/B -D15 F1/0/A - - F2/3/A - F1/0/A -A0 - - F1/2/A - - - -A1 F1/3/X F1/3/B F1/2/B - - - -A2 F1/2/X F1/3/A F1/3/A - - - -A3 - F1/2/A F1/3/B - - F1/3/B -A4 - - - Q3/1 - F1/2/X -A5 - - - Q3/0 - - -D31 - - - - F1/2/B - -D32 - - - - F1/2/A - -D33 - - - - F1/1/B - -D34 - - - - F1/1/A - -D35 - - - - F1/0/B - -D36 - - - - F1/0/A - -=========== ========== ========== ====== ========== ====== ======== +=========== ========== ========== ====== ========== =========== ======== +Pin/ MIMXRT 1010 1015 1020 1050/60/64 1170 Metro M7 +=========== ========== ========== ====== ========== =========== ======== +D0 - Q1/1 F1/1/B - - - +D1 - Q1/0 F1/1/A - - - +D2 F1/3/B F1/3/A - F1/3/B - - +D3 F1/3/A F1/0/A F2/3/B F4/0/A F1/2/A (&) - +D4 F1/3/A (*) Q1/2 Q2/1 F2/3/A F1/0/X (&) F1/0/B +D5 F1/0/B (*) F1/0/B F2/3/A F1/3/A F1/2/B (&) F1/0/A +D6 - F1/2/B F2/0/A Q3/2 F1/0/A - +D7 - - F1/0/A Q3/3 F3/0/X - +D8 F1/0/A F1/1/B F1/0/B F1/1/X F1/1/X (&) F1/3/A +D9 F1/1/B (*) F1/2/A F2/0/B F1/0/X F1/0/B F1/3/B +D10 F1/3/B - F2/2/B F1/0/B (*) F2/2/B F1/2/A +D11 F1/2/A - F2/1/A F1/1/A (*) - F1/2/B +D12 F1/2/B - F2/1/B F1/1/B (*) - F1/1/A +D13 F1/3/A - F2/2/A F1/0/A (*) F2/2/A F1/1/B +D14 F1/0/B - - F2/3/B - F1/0/B +D15 F1/0/A - - F2/3/A - F1/0/A +A0 - - F1/2/A - F2/0/X - +A1 F1/3/X F1/3/B F1/2/B - F2/1/X - +A2 F1/2/X F1/3/A F1/3/A - F2/2/X - +A3 - F1/2/A F1/3/B - F2/3/X F1/3/B +A4 - - - Q3/1 F1/3/X F1/2/X +A5 - - - Q3/0 F1/2/X - +D31 - - - - F1/2/B (&) - +D32 - - - - F1/2/A (&) - +D33 - - - - F1/1/B - +D34 - - - - F1/1/A - +D35 - - - - F1/0/B - +D36 - - - - F1/0/A - +=========== ========== ========== ====== ========== =========== ======== Pins denoted with (*) are by default not wired at the board. +Pins denoted with (&) have alternative PWM options available. + ==== ========== ==== ========== Pin Teensy 4.0 Pin Teensy 4.1 ==== ========== ==== ========== @@ -317,6 +319,10 @@ Pin.cpu.GPIO_EMC_B1_29 FLEXPWM3 Channel A (*) Pin.cpu.GPIO_EMC_B1_30 FLEXPWM3 Channel B (*) Pin.cpu.GPIO_AD_00 FLEXPWM1 Channel A Pin.cpu.GPIO_AD_01 FLEXPWM1 Channel B +Pin.cpu.GPIO_AD_06 FLEXPWM1 Channel X +Pin.cpu.GPIO_AD_10 FLEXPWM2 Channel X +Pin.cpu.GPIO_AD_14 FLEXPWM3 Channel X +Pin.cpu.GPIO_AD_18 FLEXPWM4 Channel X Pin.cpu.GPIO_AD_24 FLEXPWM2 Channel A Pin.cpu.GPIO_AD_25 FLEXPWM2 Channel B ====================== ====================== diff --git a/ports/mimxrt/boards/MIMXRT1176_af.csv b/ports/mimxrt/boards/MIMXRT1176_af.csv index 5e99a49003a..0b13e509aa2 100644 --- a/ports/mimxrt/boards/MIMXRT1176_af.csv +++ b/ports/mimxrt/boards/MIMXRT1176_af.csv @@ -1,175 +1,175 @@ -Pad,ALT0,ALT1,ALT2,ALT3,ALT4,ALT5,ALT6,ALT7,ALT8,ALT9,ALT10,ADC,ACMP,Default -GPIO_SD_B1_00,USDHC1_CMD,,XBAR1_INOUT20,GPT4_CAPTURE1,,GPIO4_IO03,FLEXSPI2_A_SS0_B,,KPP_ROW07,,GPIO10_IO03,,, -GPIO_SD_B1_01,USDHC1_CLK,,XBAR1_INOUT21,GPT4_CAPTURE2,,GPIO4_IO04,FLEXSPI2_A_SCLK,,KPP_COL07,,GPIO10_IO04,,, -GPIO_SD_B1_02,USDHC1_DATA0,,XBAR1_INOUT22,GPT4_COMPARE1,,GPIO4_IO05,FLEXSPI2_A_DATA00,,KPP_ROW06,FLEXSPI1_A_SS1_B,GPIO10_IO05,,, -GPIO_SD_B1_03,USDHC1_DATA1,,XBAR1_INOUT23,GPT4_COMPARE2,,GPIO4_IO06,FLEXSPI2_A_DATA01,,KPP_COL06,FLEXSPI1_B_SS1_B,GPIO10_IO06,,, -GPIO_SD_B1_04,USDHC1_DATA2,,XBAR1_INOUT24,GPT4_COMPARE3,,GPIO4_IO07,FLEXSPI2_A_DATA02,,FLEXSPI1_B_SS0_B,ENET_QOS_1588_EVENT2_AUX_IN,GPIO10_IO07,,, -GPIO_SD_B1_05,USDHC1_DATA3,,XBAR1_INOUT25,GPT4_CLK,,GPIO4_IO08,FLEXSPI2_A_DATA03,,FLEXSPI1_B_DQS,ENET_QOS_1588_EVENT3_AUX_IN,GPIO10_IO08,,, -GPIO_SD_B2_00,USDHC2_DATA3,FLEXSPI1_B_DATA03,ENET_1G_RX_EN,LPUART9_TXD,LPSPI4_SCK,GPIO4_IO09,,,,,GPIO10_IO09,,, -GPIO_SD_B2_01,USDHC2_DATA2,FLEXSPI1_B_DATA02,ENET_1G_RX_CLK,LPUART9_RXD,LPSPI4_PCS0,GPIO4_IO10,,,,,GPIO10_IO10,,, -GPIO_SD_B2_02,USDHC2_DATA1,FLEXSPI1_B_DATA01,ENET_1G_RX_DATA00,LPUART9_CTS_B,LPSPI4_SOUT,GPIO4_IO11,,,,,GPIO10_IO11,,, -GPIO_SD_B2_03,USDHC2_DATA0,FLEXSPI1_B_DATA00,ENET_1G_RX_DATA01,LPUART9_RTS_B,LPSPI4_SIN,GPIO4_IO12,,,,,GPIO10_IO12,,, -GPIO_SD_B2_04,USDHC2_CLK,FLEXSPI1_B_SCLK,ENET_1G_RX_DATA02,FLEXSPI1_A_SS1_B,LPSPI4_PCS1,GPIO4_IO13,,,,,GPIO10_IO13,,, -GPIO_SD_B2_05,USDHC2_CMD,FLEXSPI1_A_DQS,ENET_1G_RX_DATA03,FLEXSPI1_B_SS0_B,LPSPI4_PCS2,GPIO4_IO14,,,,,GPIO10_IO14,,, -GPIO_SD_B2_06,USDHC2_RESET_B,FLEXSPI1_A_SS0_B,ENET_1G_TX_DATA03,LPSPI4_PCS3,GPT6_CAPTURE1,GPIO4_IO15,,,,,GPIO10_IO15,,, -GPIO_SD_B2_07,USDHC2_STROBE,FLEXSPI1_A_SCLK,ENET_1G_TX_DATA02,LPUART3_CTS_B,GPT6_CAPTURE2,GPIO4_IO16,LPSPI2_SCK,,ENET_TX_ER,ENET_QOS_REF_CLK,GPIO10_IO16,,, -GPIO_SD_B2_08,USDHC2_DATA4,FLEXSPI1_A_DATA00,ENET_1G_TX_DATA01,LPUART3_RTS_B,GPT6_COMPARE1,GPIO4_IO17,LPSPI2_PCS0,,,,GPIO10_IO17,,, -GPIO_SD_B2_09,USDHC2_DATA5,FLEXSPI1_A_DATA01,ENET_1G_TX_DATA00,LPUART5_CTS_B,GPT6_COMPARE2,GPIO4_IO18,LPSPI2_SOUT,,,,GPIO10_IO18,,, -GPIO_SD_B2_10,USDHC2_DATA6,FLEXSPI1_A_DATA02,ENET_1G_TX_EN,LPUART5_RTS_B,GPT6_COMPARE3,GPIO4_IO19,LPSPI2_SIN,,,,GPIO10_IO19,,, -GPIO_SD_B2_11,USDHC2_DATA7,FLEXSPI1_A_DATA03,ENET_1G_TX_CLK_IO,ENET_1G_REF_CLK,GPT6_CLK,GPIO4_IO20,LPSPI2_PCS1,,,,GPIO10_IO20,,, -GPIO_EMC_B1_00,SEMC_DATA00,FLEXPWM4_PWM0_A,,,,GPIO1_IO00,,,FLEXIO1_D00,,GPIO7_IO00,,, -GPIO_EMC_B1_01,SEMC_DATA01,FLEXPWM4_PWM0_B,,,,GPIO1_IO01,,,FLEXIO1_D01,,GPIO7_IO01,,, -GPIO_EMC_B1_02,SEMC_DATA02,FLEXPWM4_PWM1_A,,,,GPIO1_IO02,,,FLEXIO1_D02,,GPIO7_IO02,,, -GPIO_EMC_B1_03,SEMC_DATA03,FLEXPWM4_PWM1_B,,,,GPIO1_IO03,,,FLEXIO1_D03,,GPIO7_IO03,,, -GPIO_EMC_B1_04,SEMC_DATA04,FLEXPWM4_PWM2_A,,,,GPIO1_IO04,,,FLEXIO1_D04,,GPIO7_IO04,,, -GPIO_EMC_B1_05,SEMC_DATA05,FLEXPWM4_PWM2_B,,,,GPIO1_IO05,,,FLEXIO1_D05,,GPIO7_IO05,,, -GPIO_EMC_B1_06,SEMC_DATA06,FLEXPWM2_PWM0_A,,,,GPIO1_IO06,,,FLEXIO1_D06,,GPIO7_IO06,,, -GPIO_EMC_B1_07,SEMC_DATA07,FLEXPWM2_PWM0_B,,,,GPIO1_IO07,,,FLEXIO1_D07,,GPIO7_IO07,,, -GPIO_EMC_B1_08,SEMC_DM00,FLEXPWM2_PWM1_A,,,,GPIO1_IO08,,,FLEXIO1_D08,,GPIO7_IO08,,, -GPIO_EMC_B1_09,SEMC_ADDR00,FLEXPWM2_PWM1_B,GPT5_CAPTURE1,,,GPIO1_IO09,,,FLEXIO1_D09,,GPIO7_IO09,,, -GPIO_EMC_B1_10,SEMC_ADDR01,FLEXPWM2_PWM2_A,GPT5_CAPTURE2,,,GPIO1_IO10,,,FLEXIO1_D10,,GPIO7_IO10,,, -GPIO_EMC_B1_11,SEMC_ADDR02,FLEXPWM2_PWM2_B,GPT5_COMPARE1,,,GPIO1_IO11,,,FLEXIO1_D11,,GPIO7_IO11,,, -GPIO_EMC_B1_12,SEMC_ADDR03,XBAR1_INOUT04,GPT5_COMPARE2,,,GPIO1_IO12,,,FLEXIO1_D12,,GPIO7_IO12,,, -GPIO_EMC_B1_13,SEMC_ADDR04,XBAR1_INOUT05,GPT5_COMPARE3,,,GPIO1_IO13,,,FLEXIO1_D13,,GPIO7_IO13,,, -GPIO_EMC_B1_14,SEMC_ADDR05,XBAR1_INOUT06,GPT5_CLK,,,GPIO1_IO14,,,FLEXIO1_D14,,GPIO7_IO14,,, -GPIO_EMC_B1_15,SEMC_ADDR06,XBAR1_INOUT07,,,,GPIO1_IO15,,,FLEXIO1_D15,,GPIO7_IO15,,, -GPIO_EMC_B1_16,SEMC_ADDR07,XBAR1_INOUT08,,,,GPIO1_IO16,,,FLEXIO1_D16,,GPIO7_IO16,,, -GPIO_EMC_B1_17,SEMC_ADDR08,FLEXPWM4_PWM3_A,TMR1_TIMER0,,,GPIO1_IO17,,,FLEXIO1_D17,,GPIO7_IO17,,, -GPIO_EMC_B1_18,SEMC_ADDR09,FLEXPWM4_PWM3_B,TMR2_TIMER0,,,GPIO1_IO18,,,FLEXIO1_D18,,GPIO7_IO18,,, -GPIO_EMC_B1_19,SEMC_ADDR11,FLEXPWM2_PWM3_A,TMR3_TIMER0,,,GPIO1_IO19,,,FLEXIO1_D19,,GPIO7_IO19,,, -GPIO_EMC_B1_20,SEMC_ADDR12,FLEXPWM2_PWM3_B,TMR4_TIMER0,,,GPIO1_IO20,,,FLEXIO1_D20,,GPIO7_IO20,,, -GPIO_EMC_B1_21,SEMC_BA0,FLEXPWM3_PWM3_A,,,,GPIO1_IO21,,,FLEXIO1_D21,,GPIO7_IO21,,, -GPIO_EMC_B1_22,SEMC_BA1,FLEXPWM3_PWM3_B,,,,GPIO1_IO22,,,FLEXIO1_D22,,GPIO7_IO22,,, -GPIO_EMC_B1_23,SEMC_ADDR10,FLEXPWM1_PWM0_A,,,,GPIO1_IO23,,,FLEXIO1_D23,,GPIO7_IO23,,, -GPIO_EMC_B1_24,SEMC_CAS,FLEXPWM1_PWM0_B,,,,GPIO1_IO24,,,FLEXIO1_D24,,GPIO7_IO24,,, -GPIO_EMC_B1_25,SEMC_RAS,FLEXPWM1_PWM1_A,,,,GPIO1_IO25,,,FLEXIO1_D25,,GPIO7_IO25,,, -GPIO_EMC_B1_26,SEMC_CLK,FLEXPWM1_PWM1_B,,,,GPIO1_IO26,,,FLEXIO1_D26,,GPIO7_IO26,,, -GPIO_EMC_B1_27,SEMC_CKE,FLEXPWM1_PWM2_A,,,,GPIO1_IO27,,,FLEXIO1_D27,,GPIO7_IO27,,, -GPIO_EMC_B1_28,SEMC_WE,FLEXPWM1_PWM2_B,,,,GPIO1_IO28,,,FLEXIO1_D28,,GPIO7_IO28,,, -GPIO_EMC_B1_29,SEMC_CS0,FLEXPWM3_PWM0_A,,,,GPIO1_IO29,,,FLEXIO1_D29,,GPIO7_IO29,,, -GPIO_EMC_B1_30,SEMC_DATA08,FLEXPWM3_PWM0_B,,,,GPIO1_IO30,,,FLEXIO1_D30,,GPIO7_IO30,,, -GPIO_EMC_B1_31,SEMC_DATA09,FLEXPWM3_PWM1_A,,,,GPIO1_IO31,,,FLEXIO1_D31,,GPIO7_IO31,,, -GPIO_EMC_B1_32,SEMC_DATA10,FLEXPWM3_PWM1_B,,,,GPIO2_IO00,,,,,GPIO8_IO00,,, -GPIO_EMC_B1_33,SEMC_DATA11,FLEXPWM3_PWM2_A,,,,GPIO2_IO01,,,,,GPIO8_IO01,,, -GPIO_EMC_B1_34,SEMC_DATA12,FLEXPWM3_PWM2_B,,,,GPIO2_IO02,,,,,GPIO8_IO02,,, -GPIO_EMC_B1_35,SEMC_DATA13,XBAR1_INOUT09,,,,GPIO2_IO03,,,,,GPIO8_IO03,,, -GPIO_EMC_B1_36,SEMC_DATA14,XBAR1_INOUT10,,,,GPIO2_IO04,,,,,GPIO8_IO04,,, -GPIO_EMC_B1_37,SEMC_DATA15,XBAR1_INOUT11,,,,GPIO2_IO05,,,,,GPIO8_IO05,,, -GPIO_EMC_B1_38,SEMC_DM01,FLEXPWM1_PWM3_A,TMR1_TIMER1,,,GPIO2_IO06,,,,,GPIO8_IO06,,, -GPIO_EMC_B1_39,SEMC_DQS,FLEXPWM1_PWM3_B,TMR2_TIMER1,,,GPIO2_IO07,,,,,GPIO8_IO07,,, -GPIO_EMC_B1_40,SEMC_RDY,XBAR1_INOUT12,MQS_RIGHT,LPUART6_TXD,,GPIO2_IO08,,ENET_1G_MDC,,CCM_CLKO1,GPIO8_IO08,,, -GPIO_EMC_B1_41,SEMC_CSX00,XBAR1_INOUT13,MQS_LEFT,LPUART6_RXD,FLEXSPI2_B_DATA07,GPIO2_IO09,,ENET_1G_MDIO,,CCM_CLKO2,GPIO8_IO09,,, -GPIO_EMC_B2_00,SEMC_DATA16,CCM_ENET_REF_CLK_25M,TMR3_TIMER1,LPUART6_CTS_B,FLEXSPI2_B_DATA06,GPIO2_IO10,XBAR1_INOUT20,ENET_QOS_1588_EVENT1_OUT,LPSPI1_SCK,LPI2C2_SCL,GPIO8_IO10,,, -GPIO_EMC_B2_01,SEMC_DATA17,USDHC2_CD_B,TMR4_TIMER1,LPUART6_RTS_B,FLEXSPI2_B_DATA05,GPIO2_IO11,XBAR1_INOUT21,ENET_QOS_1588_EVENT1_IN,LPSPI1_PCS0,LPI2C2_SDA,GPIO8_IO11,,, -GPIO_EMC_B2_02,SEMC_DATA18,USDHC2_WP,,VIDEO_MUX_CSI_DATA23,FLEXSPI2_B_DATA04,GPIO2_IO12,XBAR1_INOUT22,ENET_QOS_1588_EVENT1_AUX_IN,LPSPI1_SOUT,,GPIO8_IO12,,, -GPIO_EMC_B2_03,SEMC_DATA19,USDHC2_VSELECT,,VIDEO_MUX_CSI_DATA22,FLEXSPI2_B_DATA03,GPIO2_IO13,XBAR1_INOUT23,ENET_1G_TX_DATA03,LPSPI1_SIN,,GPIO8_IO13,,, -GPIO_EMC_B2_04,SEMC_DATA20,USDHC2_RESET_B,SAI2_MCLK,VIDEO_MUX_CSI_DATA21,FLEXSPI2_B_DATA02,GPIO2_IO14,XBAR1_INOUT24,ENET_1G_TX_DATA02,LPSPI3_SCK,,GPIO8_IO14,,, -GPIO_EMC_B2_05,SEMC_DATA21,GPT3_CLK,SAI2_RX_SYNC,VIDEO_MUX_CSI_DATA20,FLEXSPI2_B_DATA01,GPIO2_IO15,XBAR1_INOUT25,ENET_1G_RX_CLK,LPSPI3_PCS0,PIT1_TRIGGER0,GPIO8_IO15,,, -GPIO_EMC_B2_06,SEMC_DATA22,GPT3_CAPTURE1,SAI2_RX_BCLK,VIDEO_MUX_CSI_DATA19,FLEXSPI2_B_DATA00,GPIO2_IO16,XBAR1_INOUT26,ENET_1G_TX_ER,LPSPI3_SOUT,PIT1_TRIGGER1,GPIO8_IO16,,, -GPIO_EMC_B2_07,SEMC_DATA23,GPT3_CAPTURE2,SAI2_RX_DATA,VIDEO_MUX_CSI_DATA18,FLEXSPI2_B_DQS,GPIO2_IO17,XBAR1_INOUT27,ENET_1G_RX_DATA03,LPSPI3_SIN,PIT1_TRIGGER2,GPIO8_IO17,,, -GPIO_EMC_B2_08,SEMC_DM02,GPT3_COMPARE1,SAI2_TX_DATA,VIDEO_MUX_CSI_DATA17,FLEXSPI2_B_SS0_B,GPIO2_IO18,XBAR1_INOUT28,ENET_1G_RX_DATA02,LPSPI3_PCS1,PIT1_TRIGGER3,GPIO8_IO18,,, -GPIO_EMC_B2_09,SEMC_DATA24,GPT3_COMPARE2,SAI2_TX_BCLK,VIDEO_MUX_CSI_DATA16,FLEXSPI2_B_SCLK,GPIO2_IO19,XBAR1_INOUT29,ENET_1G_CRS,LPSPI3_PCS2,TMR1_TIMER0,GPIO8_IO19,,, -GPIO_EMC_B2_10,SEMC_DATA25,GPT3_COMPARE3,SAI2_TX_SYNC,VIDEO_MUX_CSI_FIELD,FLEXSPI2_A_SCLK,GPIO2_IO20,XBAR1_INOUT30,ENET_1G_COL,LPSPI3_PCS3,TMR1_TIMER1,GPIO8_IO20,,, -GPIO_EMC_B2_11,SEMC_DATA26,SPDIF_IN,ENET_1G_TX_DATA00,SAI3_RX_SYNC,FLEXSPI2_A_SS0_B,GPIO2_IO21,XBAR1_INOUT31,,EMVSIM1_IO,TMR1_TIMER2,GPIO8_IO21,,, -GPIO_EMC_B2_12,SEMC_DATA27,SPDIF_OUT,ENET_1G_TX_DATA01,SAI3_RX_BCLK,FLEXSPI2_A_DQS,GPIO2_IO22,XBAR1_INOUT32,,EMVSIM1_CLK,TMR1_TIMER3,GPIO8_IO22,,, -GPIO_EMC_B2_13,SEMC_DATA28,,ENET_1G_TX_EN,SAI3_RX_DATA,FLEXSPI2_A_DATA00,GPIO2_IO23,XBAR1_INOUT33,,EMVSIM1_RST,TMR2_TIMER0,GPIO8_IO23,,, -GPIO_EMC_B2_14,SEMC_DATA29,,ENET_1G_TX_CLK_IO,SAI3_TX_DATA,FLEXSPI2_A_DATA01,GPIO2_IO24,XBAR1_INOUT34,SFA_ipp_do_atx_clk_under_test,EMVSIM1_SVEN,TMR2_TIMER1,GPIO8_IO24,,, -GPIO_EMC_B2_15,SEMC_DATA30,,ENET_1G_RX_DATA00,SAI3_TX_BCLK,FLEXSPI2_A_DATA02,GPIO2_IO25,XBAR1_INOUT35,,EMVSIM1_PD,TMR2_TIMER2,GPIO8_IO25,,, -GPIO_EMC_B2_16,SEMC_DATA31,XBAR1_INOUT14,ENET_1G_RX_DATA01,SAI3_TX_SYNC,FLEXSPI2_A_DATA03,GPIO2_IO26,,,EMVSIM1_POWER_FAIL,TMR2_TIMER3,GPIO8_IO26,,, -GPIO_EMC_B2_17,SEMC_DM03,XBAR1_INOUT15,ENET_1G_RX_EN,SAI3_MCLK,FLEXSPI2_A_DATA04,GPIO2_IO27,,,WDOG1_ANY,TMR3_TIMER0,GPIO8_IO27,,, -GPIO_EMC_B2_18,SEMC_DQS4,XBAR1_INOUT16,ENET_1G_RX_ER,EWM_OUT_B,FLEXSPI2_A_DATA05,GPIO2_IO28,FLEXSPI1_A_DQS,,WDOG1_B,TMR3_TIMER1,GPIO8_IO28,,, -GPIO_EMC_B2_19,SEMC_CLKX00,ENET_MDC,ENET_1G_MDC,ENET_1G_REF_CLK,FLEXSPI2_A_DATA06,GPIO2_IO29,,,ENET_QOS_MDC,TMR3_TIMER2,GPIO8_IO29,,, -GPIO_EMC_B2_20,SEMC_CLKX01,ENET_MDIO,ENET_1G_MDIO,ENET_QOS_REF_CLK,FLEXSPI2_A_DATA07,GPIO2_IO30,,,ENET_QOS_MDIO,TMR3_TIMER3,GPIO8_IO30,,, -GPIO_SNVS_00_DIG,SNVS_TAMPER0,,,,,GPIO13_IO03,,,,,,,, -GPIO_SNVS_01_DIG,SNVS_TAMPER1,,,,,GPIO13_IO04,,,,,,,, -GPIO_SNVS_02_DIG,SNVS_TAMPER2,,,,,GPIO13_IO05,,,,,,,, -GPIO_SNVS_03_DIG,SNVS_TAMPER3,,,,,GPIO13_IO06,,,,,,,, -GPIO_SNVS_04_DIG,SNVS_TAMPER4,,,,,GPIO13_IO07,,,,,,,, -GPIO_SNVS_05_DIG,SNVS_TAMPER5,,,,,GPIO13_IO08,,,,,,,, -GPIO_SNVS_06_DIG,SNVS_TAMPER6,,,,,GPIO13_IO09,,,,,,,, -GPIO_SNVS_07_DIG,SNVS_TAMPER7,,,,,GPIO13_IO10,,,,,,,, -GPIO_SNVS_08_DIG,SNVS_TAMPER8,,,,,GPIO13_IO11,,,,,,,, -GPIO_SNVS_09_DIG,SNVS_TAMPER9,,,,,GPIO13_IO12,,,,,,,, -GPIO_LPSR_00,FLEXCAN3_TX,MIC_CLK,MQS_RIGHT,ARM_CM4_EVENTO,,GPIO6_IO00,LPUART12_TXD,SAI4_MCLK,,,GPIO12_IO00,,, -GPIO_LPSR_01,FLEXCAN3_RX,MIC_BITSTREAM0,MQS_LEFT,ARM_CM4_EVENTI,,GPIO6_IO01,LPUART12_RXD,,,,GPIO12_IO01,,, -GPIO_LPSR_02,SRC_BOOT_MODE00,LPSPI5_SCK,SAI4_TX_DATA,MQS_RIGHT,,GPIO6_IO02,,,,,GPIO12_IO02,,, -GPIO_LPSR_03,SRC_BOOT_MODE01,LPSPI5_PCS0,SAI4_TX_SYNC,MQS_LEFT,,GPIO6_IO03,,,,,GPIO12_IO03,,, -GPIO_LPSR_04,LPI2C5_SDA,LPSPI5_SOUT,SAI4_TX_BCLK,LPUART12_RTS_B,,GPIO6_IO04,LPUART11_TXD,,,,GPIO12_IO04,,, -GPIO_LPSR_05,LPI2C5_SCL,LPSPI5_SIN,SAI4_MCLK,LPUART12_CTS_B,,GPIO6_IO05,LPUART11_RXD,NMI_GLUE_NMI,,,GPIO12_IO05,,, -GPIO_LPSR_06,LPI2C6_SDA,,SAI4_RX_DATA,LPUART12_TXD,LPSPI6_PCS3,GPIO6_IO06,FLEXCAN3_TX,PIT2_TRIGGER3,LPSPI5_PCS1,,GPIO12_IO06,,, -GPIO_LPSR_07,LPI2C6_SCL,,SAI4_RX_BCLK,LPUART12_RXD,LPSPI6_PCS2,GPIO6_IO07,FLEXCAN3_RX,PIT2_TRIGGER2,LPSPI5_PCS2,,GPIO12_IO07,,, -GPIO_LPSR_08,LPUART11_TXD,FLEXCAN3_TX,SAI4_RX_SYNC,MIC_CLK,LPSPI6_PCS1,GPIO6_IO08,LPI2C5_SDA,PIT2_TRIGGER1,LPSPI5_PCS3,,GPIO12_IO08,,, -GPIO_LPSR_09,LPUART11_RXD,FLEXCAN3_RX,PIT2_TRIGGER0,MIC_BITSTREAM0,LPSPI6_PCS0,GPIO6_IO09,LPI2C5_SCL,SAI4_TX_DATA,,,GPIO12_IO09,,, -GPIO_LPSR_10,JTAG_MUX_TRSTB,LPUART11_CTS_B,LPI2C6_SDA,MIC_BITSTREAM1,LPSPI6_SCK,GPIO6_IO10,LPI2C5_SCLS,SAI4_TX_SYNC,LPUART12_TXD,,GPIO12_IO10,,, -GPIO_LPSR_11,JTAG_MUX_TDO,LPUART11_RTS_B,LPI2C6_SCL,MIC_BITSTREAM2,LPSPI6_SOUT,GPIO6_IO11,LPI2C5_SDAS,ARM_TRACE_SWO,LPUART12_RXD,,GPIO12_IO11,,, -GPIO_LPSR_12,JTAG_MUX_TDI,PIT2_TRIGGER0,,MIC_BITSTREAM3,LPSPI6_SIN,GPIO6_IO12,LPI2C5_HREQ,SAI4_TX_BCLK,LPSPI5_SCK,,GPIO12_IO12,,, -GPIO_LPSR_13,JTAG_MUX_MOD,MIC_BITSTREAM1,PIT2_TRIGGER1,,,GPIO6_IO13,,SAI4_RX_DATA,LPSPI5_PCS0,,GPIO12_IO13,,, -GPIO_LPSR_14,JTAG_MUX_TCK,MIC_BITSTREAM2,PIT2_TRIGGER2,,,GPIO6_IO14,,SAI4_RX_BCLK,LPSPI5_SOUT,,GPIO12_IO14,,, -GPIO_LPSR_15,JTAG_MUX_TMS,MIC_BITSTREAM3,PIT2_TRIGGER3,,,GPIO6_IO15,,SAI4_RX_SYNC,LPSPI5_SIN,,GPIO12_IO15,,, -WAKEUP_DIG,,,,,,GPIO13_IO00,,NMI_GLUE_NMI,,,,,, -PMIC_ON_REQ_DIG,SNVS_LP_PMIC_ON_REQ,,,,,GPIO13_IO01,,,,,,,, -PMIC_STBY_REQ_DIG,CCM_PMIC_VSTBY_REQ,,,,,GPIO13_IO02,,,,,,,, -GPIO_DISP_B1_00,VIDEO_MUX_LCDIF_CLK,ENET_1G_RX_EN,,TMR1_TIMER0,XBAR1_INOUT26,GPIO4_IO21,,,ENET_QOS_RX_EN,,GPIO10_IO21,,, -GPIO_DISP_B1_01,VIDEO_MUX_LCDIF_ENABLE,ENET_1G_RX_CLK,ENET_1G_RX_ER,TMR1_TIMER1,XBAR1_INOUT27,GPIO4_IO22,,,ENET_QOS_RX_CLK,ENET_QOS_RX_ER,GPIO10_IO22,,, -GPIO_DISP_B1_02,VIDEO_MUX_LCDIF_HSYNC,ENET_1G_RX_DATA00,LPI2C3_SCL,TMR1_TIMER2,XBAR1_INOUT28,GPIO4_IO23,,,ENET_QOS_RX_DATA00,LPUART1_TXD,GPIO10_IO23,,, -GPIO_DISP_B1_03,VIDEO_MUX_LCDIF_VSYNC,ENET_1G_RX_DATA01,LPI2C3_SDA,TMR2_TIMER0,XBAR1_INOUT29,GPIO4_IO24,,,ENET_QOS_RX_DATA01,LPUART1_RXD,GPIO10_IO24,,, -GPIO_DISP_B1_04,VIDEO_MUX_LCDIF_DATA00,ENET_1G_RX_DATA02,LPUART4_RXD,TMR2_TIMER1,XBAR1_INOUT30,GPIO4_IO25,,,ENET_QOS_RX_DATA02,LPSPI3_SCK,GPIO10_IO25,,, -GPIO_DISP_B1_05,VIDEO_MUX_LCDIF_DATA01,ENET_1G_RX_DATA03,LPUART4_CTS_B,TMR2_TIMER2,XBAR1_INOUT31,GPIO4_IO26,,,ENET_QOS_RX_DATA03,LPSPI3_SIN,GPIO10_IO26,,, -GPIO_DISP_B1_06,VIDEO_MUX_LCDIF_DATA02,ENET_1G_TX_DATA03,LPUART4_TXD,TMR3_TIMER0,XBAR1_INOUT32,GPIO4_IO27,SRC_BT_CFG00,,ENET_QOS_TX_DATA03,LPSPI3_SOUT,GPIO10_IO27,,, -GPIO_DISP_B1_07,VIDEO_MUX_LCDIF_DATA03,ENET_1G_TX_DATA02,LPUART4_RTS_B,TMR3_TIMER1,XBAR1_INOUT33,GPIO4_IO28,SRC_BT_CFG01,,ENET_QOS_TX_DATA02,LPSPI3_PCS0,GPIO10_IO28,,, -GPIO_DISP_B1_08,VIDEO_MUX_LCDIF_DATA04,ENET_1G_TX_DATA01,USDHC1_CD_B,TMR3_TIMER2,XBAR1_INOUT34,GPIO4_IO29,SRC_BT_CFG02,,ENET_QOS_TX_DATA01,LPSPI3_PCS1,GPIO10_IO29,,, -GPIO_DISP_B1_09,VIDEO_MUX_LCDIF_DATA05,ENET_1G_TX_DATA00,USDHC1_WP,TMR4_TIMER0,XBAR1_INOUT35,GPIO4_IO30,SRC_BT_CFG03,,ENET_QOS_TX_DATA00,LPSPI3_PCS2,GPIO10_IO30,,, -GPIO_DISP_B1_10,VIDEO_MUX_LCDIF_DATA06,ENET_1G_TX_EN,USDHC1_RESET_B,TMR4_TIMER1,XBAR1_INOUT36,GPIO4_IO31,SRC_BT_CFG04,,ENET_QOS_TX_EN,LPSPI3_PCS3,GPIO10_IO31,,, -GPIO_DISP_B1_11,VIDEO_MUX_LCDIF_DATA07,ENET_1G_TX_CLK_IO,ENET_1G_REF_CLK,TMR4_TIMER2,XBAR1_INOUT37,GPIO5_IO00,SRC_BT_CFG05,,ENET_QOS_TX_CLK,ENET_QOS_REF_CLK,GPIO11_IO00,,, -GPIO_DISP_B2_00,VIDEO_MUX_LCDIF_DATA08,WDOG1_B,MQS_RIGHT,ENET_1G_TX_ER,SAI1_TX_DATA03,GPIO5_IO01,SRC_BT_CFG06,,ENET_QOS_TX_ER,,GPIO11_IO01,,, -GPIO_DISP_B2_01,VIDEO_MUX_LCDIF_DATA09,USDHC1_VSELECT,MQS_LEFT,WDOG2_B,SAI1_TX_DATA02,GPIO5_IO02,SRC_BT_CFG07,,EWM_OUT_B,CCM_ENET_REF_CLK_25M,GPIO11_IO02,,, -GPIO_DISP_B2_02,VIDEO_MUX_LCDIF_DATA10,ENET_TX_DATA00,PIT1_TRIGGER3,ARM_TRACE00,SAI1_TX_DATA01,GPIO5_IO03,SRC_BT_CFG08,,ENET_QOS_TX_DATA00,,GPIO11_IO03,,, -GPIO_DISP_B2_03,VIDEO_MUX_LCDIF_DATA11,ENET_TX_DATA01,PIT1_TRIGGER2,ARM_TRACE01,SAI1_MCLK,GPIO5_IO04,SRC_BT_CFG09,,ENET_QOS_TX_DATA01,,GPIO11_IO04,,, -GPIO_DISP_B2_04,VIDEO_MUX_LCDIF_DATA12,ENET_TX_EN,PIT1_TRIGGER1,ARM_TRACE02,SAI1_RX_SYNC,GPIO5_IO05,SRC_BT_CFG10,,ENET_QOS_TX_EN,,GPIO11_IO05,,, -GPIO_DISP_B2_05,VIDEO_MUX_LCDIF_DATA13,ENET_TX_CLK,ENET_REF_CLK,ARM_TRACE03,SAI1_RX_BCLK,GPIO5_IO06,SRC_BT_CFG11,,ENET_QOS_TX_CLK,,GPIO11_IO06,,, -GPIO_DISP_B2_06,VIDEO_MUX_LCDIF_DATA14,ENET_RX_DATA00,LPUART7_TXD,ARM_TRACE_CLK,SAI1_RX_DATA00,GPIO5_IO07,,,ENET_QOS_RX_DATA00,,GPIO11_IO07,,, -GPIO_DISP_B2_07,VIDEO_MUX_LCDIF_DATA15,ENET_RX_DATA01,LPUART7_RXD,ARM_TRACE_SWO,SAI1_TX_DATA00,GPIO5_IO08,,,ENET_QOS_RX_DATA01,,GPIO11_IO08,,, -GPIO_DISP_B2_08,VIDEO_MUX_LCDIF_DATA16,ENET_RX_EN,LPUART8_TXD,ARM_CM7_EVENTO,SAI1_TX_BCLK,GPIO5_IO09,,,ENET_QOS_RX_EN,LPUART1_TXD,GPIO11_IO09,,, -GPIO_DISP_B2_09,VIDEO_MUX_LCDIF_DATA17,ENET_RX_ER,LPUART8_RXD,ARM_CM7_EVENTI,SAI1_TX_SYNC,GPIO5_IO10,,,ENET_QOS_RX_ER,LPUART1_RXD,GPIO11_IO10,,, -GPIO_DISP_B2_10,VIDEO_MUX_LCDIF_DATA18,EMVSIM2_IO,LPUART2_TXD,WDOG2_RESET_B_DEB,XBAR1_INOUT38,GPIO5_IO11,LPI2C3_SCL,,ENET_QOS_RX_ER,SPDIF_IN,GPIO11_IO11,,, -GPIO_DISP_B2_11,VIDEO_MUX_LCDIF_DATA19,EMVSIM2_CLK,LPUART2_RXD,WDOG1_RESET_B_DEB,XBAR1_INOUT39,GPIO5_IO12,LPI2C3_SDA,,ENET_QOS_CRS,SPDIF_OUT,GPIO11_IO12,,, -GPIO_DISP_B2_12,VIDEO_MUX_LCDIF_DATA20,EMVSIM2_RST,FLEXCAN1_TX,LPUART2_CTS_B,XBAR1_INOUT40,GPIO5_IO13,LPI2C4_SCL,,ENET_QOS_COL,LPSPI4_SCK,GPIO11_IO13,,, -GPIO_DISP_B2_13,VIDEO_MUX_LCDIF_DATA21,EMVSIM2_SVEN,FLEXCAN1_RX,LPUART2_RTS_B,ENET_REF_CLK,GPIO5_IO14,LPI2C4_SDA,,ENET_QOS_1588_EVENT0_OUT,LPSPI4_SIN,GPIO11_IO14,,, -GPIO_DISP_B2_14,VIDEO_MUX_LCDIF_DATA22,EMVSIM2_PD,WDOG2_B,VIDEO_MUX_EXT_DCIC1,ENET_1G_REF_CLK,GPIO5_IO15,FLEXCAN1_TX,,ENET_QOS_1588_EVENT0_IN,LPSPI4_SOUT,GPIO11_IO15,,, -GPIO_DISP_B2_15,VIDEO_MUX_LCDIF_DATA23,EMVSIM2_POWER_FAIL,WDOG1_B,VIDEO_MUX_EXT_DCIC2,PIT1_TRIGGER0,GPIO5_IO16,FLEXCAN1_RX,,ENET_QOS_1588_EVENT0_AUX_IN,LPSPI4_PCS0,GPIO11_IO16,,, -GPIO_AD_00,EMVSIM1_IO,FLEXCAN2_TX,ENET_1G_1588_EVENT1_IN,GPT2_CAPTURE1,FLEXPWM1_PWM0_A,GPIO2_IO31,LPUART7_TXD,,FLEXIO2_D00,FLEXSPI2_B_SS1_B,GPIO8_IO31,,ACMP1_IN1, -GPIO_AD_01,EMVSIM1_CLK,FLEXCAN2_RX,ENET_1G_1588_EVENT1_OUT,GPT2_CAPTURE2,FLEXPWM1_PWM0_B,GPIO3_IO00,LPUART7_RXD,,FLEXIO2_D01,FLEXSPI2_A_SS1_B,GPIO9_IO00,,ACMP1_IN2, -GPIO_AD_02,EMVSIM1_RST,LPUART7_CTS_B,ENET_1G_1588_EVENT2_IN,GPT2_COMPARE1,FLEXPWM1_PWM1_A,GPIO3_IO01,LPUART8_TXD,,FLEXIO2_D02,VIDEO_MUX_EXT_DCIC1,GPIO9_IO01,,ACMP1_IN3, -GPIO_AD_03,EMVSIM1_SVEN,LPUART7_RTS_B,ENET_1G_1588_EVENT2_OUT,GPT2_COMPARE2,FLEXPWM1_PWM1_B,GPIO3_IO02,LPUART8_RXD,,FLEXIO2_D03,VIDEO_MUX_EXT_DCIC2,GPIO9_IO02,,ACMP1_IN4, -GPIO_AD_04,EMVSIM1_PD,LPUART8_CTS_B,ENET_1G_1588_EVENT3_IN,GPT2_COMPARE3,FLEXPWM1_PWM2_A,GPIO3_IO03,WDOG1_B,,FLEXIO2_D04,TMR4_TIMER0,GPIO9_IO03,,ACMP2_IN1, -GPIO_AD_05,EMVSIM1_POWER_FAIL,LPUART8_RTS_B,ENET_1G_1588_EVENT3_OUT,GPT2_CLK,FLEXPWM1_PWM2_B,GPIO3_IO04,WDOG2_B,,FLEXIO2_D05,TMR4_TIMER1,GPIO9_IO04,,ACMP2_IN2, -GPIO_AD_06,USB_OTG2_OC,FLEXCAN1_TX,EMVSIM2_IO,GPT3_CAPTURE1,VIDEO_MUX_CSI_DATA15,GPIO3_IO05,ENET_1588_EVENT1_IN,,FLEXIO2_D06,TMR4_TIMER2,GPIO9_IO05,ADC1_CH0A,, -GPIO_AD_07,USB_OTG2_PWR,FLEXCAN1_RX,EMVSIM2_CLK,GPT3_CAPTURE2,VIDEO_MUX_CSI_DATA14,GPIO3_IO06,ENET_1588_EVENT1_OUT,,FLEXIO2_D07,TMR4_TIMER3,GPIO9_IO06,ADC1_CH0B,, -GPIO_AD_08,USBPHY2_OTG_ID,LPI2C1_SCL,EMVSIM2_RST,GPT3_COMPARE1,VIDEO_MUX_CSI_DATA13,GPIO3_IO07,ENET_1588_EVENT2_IN,,FLEXIO2_D08,,GPIO9_IO07,ADC1_CH1A,, -GPIO_AD_09,USBPHY1_OTG_ID,LPI2C1_SDA,EMVSIM2_SVEN,GPT3_COMPARE2,VIDEO_MUX_CSI_DATA12,GPIO3_IO08,ENET_1588_EVENT2_OUT,,FLEXIO2_D09,,GPIO9_IO08,ADC1_CH1B,, -GPIO_AD_10,USB_OTG1_PWR,LPI2C1_SCLS,EMVSIM2_PD,GPT3_COMPARE3,VIDEO_MUX_CSI_DATA11,GPIO3_IO09,ENET_1588_EVENT3_IN,,FLEXIO2_D10,,GPIO9_IO09,ADC1_CH2A,, -GPIO_AD_11,USB_OTG1_OC,LPI2C1_SDAS,EMVSIM2_POWER_FAIL,GPT3_CLK,VIDEO_MUX_CSI_DATA10,GPIO3_IO10,ENET_1588_EVENT3_OUT,,FLEXIO2_D11,,GPIO9_IO10,ADC1_CH2B,, -GPIO_AD_12,SPDIF_LOCK,LPI2C1_HREQ,GPT1_CAPTURE1,FLEXSPI1_B_DATA03,VIDEO_MUX_CSI_PIXCLK,GPIO3_IO11,ENET_TX_DATA03,,FLEXIO2_D12,EWM_OUT_B,GPIO9_IO11,"ADC1_CH3A,ADC2_CH3A",, -GPIO_AD_13,SPDIF_SR_CLK,PIT1_TRIGGER0,GPT1_CAPTURE2,FLEXSPI1_B_DATA02,VIDEO_MUX_CSI_MCLK,GPIO3_IO12,ENET_TX_DATA02,,FLEXIO2_D13,REF_CLK_32K,GPIO9_IO12,"ADC1_CH3B,ADC2_CH3B",, -GPIO_AD_14,SPDIF_EXT_CLK,REF_CLK_24M,GPT1_COMPARE1,FLEXSPI1_B_DATA01,VIDEO_MUX_CSI_VSYNC,GPIO3_IO13,ENET_RX_CLK,,FLEXIO2_D14,CCM_ENET_REF_CLK_25M,GPIO9_IO13,"ADC1_CH4A,ADC2_CH4A",, -GPIO_AD_15,SPDIF_IN,LPUART10_TXD,GPT1_COMPARE2,FLEXSPI1_B_DATA00,VIDEO_MUX_CSI_HSYNC,GPIO3_IO14,ENET_TX_ER,,FLEXIO2_D15,,GPIO9_IO14,"ADC1_CH4B,ADC2_CH4B",, -GPIO_AD_16,SPDIF_OUT,LPUART10_RXD,GPT1_COMPARE3,FLEXSPI1_B_SCLK,VIDEO_MUX_CSI_DATA09,GPIO3_IO15,ENET_RX_DATA03,,FLEXIO2_D16,ENET_1G_MDC,GPIO9_IO15,"ADC1_CH5A,ADC2_CH5A",, -GPIO_AD_17,SAI1_MCLK,ACMP1_OUT,GPT1_CLK,FLEXSPI1_A_DQS,VIDEO_MUX_CSI_DATA08,GPIO3_IO16,ENET_RX_DATA02,,FLEXIO2_D17,ENET_1G_MDIO,GPIO9_IO16,"ADC1_CH5B,ADC2_CH5B",ACMP1_OUT, -GPIO_AD_18,SAI1_RX_SYNC,ACMP2_OUT,LPSPI1_PCS1,FLEXSPI1_A_SS0_B,VIDEO_MUX_CSI_DATA07,GPIO3_IO17,ENET_CRS,,FLEXIO2_D18,LPI2C2_SCL,GPIO9_IO17,ADC2_CH0A,ACMP2_OUT, -GPIO_AD_19,SAI1_RX_BCLK,ACMP3_OUT,LPSPI1_PCS2,FLEXSPI1_A_SCLK,VIDEO_MUX_CSI_DATA06,GPIO3_IO18,ENET_COL,,FLEXIO2_D19,LPI2C2_SDA,GPIO9_IO18,ADC2_CH0B,ACMP3_OUT, -GPIO_AD_20,SAI1_RX_DATA00,ACMP4_OUT,LPSPI1_PCS3,FLEXSPI1_A_DATA00,VIDEO_MUX_CSI_DATA05,GPIO3_IO19,KPP_ROW07,,FLEXIO2_D20,ENET_QOS_1588_EVENT2_OUT,GPIO9_IO19,ADC2_CH1A,ACMP4_OUT, -GPIO_AD_21,SAI1_TX_DATA00,,LPSPI2_PCS1,FLEXSPI1_A_DATA01,VIDEO_MUX_CSI_DATA04,GPIO3_IO20,KPP_COL07,,FLEXIO2_D21,ENET_QOS_1588_EVENT2_IN,GPIO9_IO20,ADC2_CH1B,, -GPIO_AD_22,SAI1_TX_BCLK,,LPSPI2_PCS2,FLEXSPI1_A_DATA02,VIDEO_MUX_CSI_DATA03,GPIO3_IO21,KPP_ROW06,,FLEXIO2_D22,ENET_QOS_1588_EVENT3_OUT,GPIO9_IO21,ADC2_CH2A,, -GPIO_AD_23,SAI1_TX_SYNC,,LPSPI2_PCS3,FLEXSPI1_A_DATA03,VIDEO_MUX_CSI_DATA02,GPIO3_IO22,KPP_COL06,,FLEXIO2_D23,ENET_QOS_1588_EVENT3_IN,GPIO9_IO22,ADC2_CH2B,, -GPIO_AD_24,LPUART1_TXD,LPSPI2_SCK,VIDEO_MUX_CSI_DATA00,ENET_RX_EN,FLEXPWM2_PWM0_A,GPIO3_IO23,KPP_ROW05,,FLEXIO2_D24,LPI2C4_SCL,GPIO9_IO23,ADC2_CH6A,, -GPIO_AD_25,LPUART1_RXD,LPSPI2_PCS0,VIDEO_MUX_CSI_DATA01,ENET_RX_ER,FLEXPWM2_PWM0_B,GPIO3_IO24,KPP_COL05,,FLEXIO2_D25,LPI2C4_SDA,GPIO9_IO24,ADC2_CH6B,, -GPIO_AD_26,LPUART1_CTS_B,LPSPI2_SOUT,SEMC_CSX01,ENET_RX_DATA00,FLEXPWM2_PWM1_A,GPIO3_IO25,KPP_ROW04,,FLEXIO2_D26,ENET_QOS_MDC,GPIO9_IO25,,ACMP2_IN3, -GPIO_AD_27,LPUART1_RTS_B,LPSPI2_SIN,SEMC_CSX02,ENET_RX_DATA01,FLEXPWM2_PWM1_B,GPIO3_IO26,KPP_COL04,,FLEXIO2_D27,ENET_QOS_MDIO,GPIO9_IO26,,ACMP2_IN4, -GPIO_AD_28,LPSPI1_SCK,LPUART5_TXD,SEMC_CSX03,ENET_TX_EN,FLEXPWM2_PWM2_A,GPIO3_IO27,KPP_ROW03,,FLEXIO2_D28,VIDEO_MUX_EXT_DCIC1,GPIO9_IO27,,ACMP3_IN1, -GPIO_AD_29,LPSPI1_PCS0,LPUART5_RXD,ENET_REF_CLK,ENET_TX_CLK,FLEXPWM2_PWM2_B,GPIO3_IO28,KPP_COL03,,FLEXIO2_D29,VIDEO_MUX_EXT_DCIC2,GPIO9_IO28,,ACMP3_IN2, -GPIO_AD_30,LPSPI1_SOUT,USB_OTG2_OC,FLEXCAN2_TX,ENET_TX_DATA00,LPUART3_TXD,GPIO3_IO29,KPP_ROW02,,FLEXIO2_D30,WDOG2_RESET_B_DEB,GPIO9_IO29,,ACMP3_IN3, -GPIO_AD_31,LPSPI1_SIN,USB_OTG2_PWR,FLEXCAN2_RX,ENET_TX_DATA01,LPUART3_RXD,GPIO3_IO30,KPP_COL02,,FLEXIO2_D31,WDOG1_RESET_B_DEB,GPIO9_IO30,,ACMP3_IN4, -GPIO_AD_32,LPI2C1_SCL,USBPHY2_OTG_ID,PGMC_PMIC_RDY,ENET_MDC,USDHC1_CD_B,GPIO3_IO31,KPP_ROW01,,LPUART10_TXD,ENET_1G_MDC,GPIO9_IO31,,ACMP4_IN1, -GPIO_AD_33,LPI2C1_SDA,USBPHY1_OTG_ID,XBAR1_INOUT17,ENET_MDIO,USDHC1_WP,GPIO4_IO00,KPP_COL01,,LPUART10_RXD,ENET_1G_MDIO,GPIO10_IO00,,ACMP4_IN2, -GPIO_AD_34,ENET_1G_1588_EVENT0_IN,USB_OTG1_PWR,XBAR1_INOUT18,ENET_1588_EVENT0_IN,USDHC1_VSELECT,GPIO4_IO01,KPP_ROW00,,LPUART10_CTS_B,WDOG1_ANY,GPIO10_IO01,,ACMP4_IN3, -GPIO_AD_35,ENET_1G_1588_EVENT0_OUT,USB_OTG1_OC,XBAR1_INOUT19,ENET_1588_EVENT0_OUT,USDHC1_RESET_B,GPIO4_IO02,KPP_COL00,,LPUART10_RTS_B,FLEXSPI1_B_SS1_B,GPIO10_IO02,,ACMP4_IN4, +Pad,ALT0,ALT1,ALT2,ALT3,ALT4,ALT5,ALT6,ALT7,ALT8,ALT9,ALT10,ALT11,ADC,ACMP,Default +GPIO_SD_B1_00,USDHC1_CMD,,XBAR1_INOUT20,GPT4_CAPTURE1,,GPIO4_IO03,FLEXSPI2_A_SS0_B,,KPP_ROW07,,GPIO10_IO03,,,, +GPIO_SD_B1_01,USDHC1_CLK,,XBAR1_INOUT21,GPT4_CAPTURE2,,GPIO4_IO04,FLEXSPI2_A_SCLK,,KPP_COL07,,GPIO10_IO04,,,, +GPIO_SD_B1_02,USDHC1_DATA0,,XBAR1_INOUT22,GPT4_COMPARE1,,GPIO4_IO05,FLEXSPI2_A_DATA00,,KPP_ROW06,FLEXSPI1_A_SS1_B,GPIO10_IO05,,,, +GPIO_SD_B1_03,USDHC1_DATA1,,XBAR1_INOUT23,GPT4_COMPARE2,,GPIO4_IO06,FLEXSPI2_A_DATA01,,KPP_COL06,FLEXSPI1_B_SS1_B,GPIO10_IO06,,,, +GPIO_SD_B1_04,USDHC1_DATA2,,XBAR1_INOUT24,GPT4_COMPARE3,,GPIO4_IO07,FLEXSPI2_A_DATA02,,FLEXSPI1_B_SS0_B,ENET_QOS_1588_EVENT2_AUX_IN,GPIO10_IO07,,,, +GPIO_SD_B1_05,USDHC1_DATA3,,XBAR1_INOUT25,GPT4_CLK,,GPIO4_IO08,FLEXSPI2_A_DATA03,,FLEXSPI1_B_DQS,ENET_QOS_1588_EVENT3_AUX_IN,GPIO10_IO08,,,, +GPIO_SD_B2_00,USDHC2_DATA3,FLEXSPI1_B_DATA03,ENET_1G_RX_EN,LPUART9_TXD,LPSPI4_SCK,GPIO4_IO09,,,,,GPIO10_IO09,,,, +GPIO_SD_B2_01,USDHC2_DATA2,FLEXSPI1_B_DATA02,ENET_1G_RX_CLK,LPUART9_RXD,LPSPI4_PCS0,GPIO4_IO10,,,,,GPIO10_IO10,,,, +GPIO_SD_B2_02,USDHC2_DATA1,FLEXSPI1_B_DATA01,ENET_1G_RX_DATA00,LPUART9_CTS_B,LPSPI4_SOUT,GPIO4_IO11,,,,,GPIO10_IO11,,,, +GPIO_SD_B2_03,USDHC2_DATA0,FLEXSPI1_B_DATA00,ENET_1G_RX_DATA01,LPUART9_RTS_B,LPSPI4_SIN,GPIO4_IO12,,,,,GPIO10_IO12,,,, +GPIO_SD_B2_04,USDHC2_CLK,FLEXSPI1_B_SCLK,ENET_1G_RX_DATA02,FLEXSPI1_A_SS1_B,LPSPI4_PCS1,GPIO4_IO13,,,,,GPIO10_IO13,,,, +GPIO_SD_B2_05,USDHC2_CMD,FLEXSPI1_A_DQS,ENET_1G_RX_DATA03,FLEXSPI1_B_SS0_B,LPSPI4_PCS2,GPIO4_IO14,,,,,GPIO10_IO14,,,, +GPIO_SD_B2_06,USDHC2_RESET_B,FLEXSPI1_A_SS0_B,ENET_1G_TX_DATA03,LPSPI4_PCS3,GPT6_CAPTURE1,GPIO4_IO15,,,,,GPIO10_IO15,,,, +GPIO_SD_B2_07,USDHC2_STROBE,FLEXSPI1_A_SCLK,ENET_1G_TX_DATA02,LPUART3_CTS_B,GPT6_CAPTURE2,GPIO4_IO16,LPSPI2_SCK,,ENET_TX_ER,ENET_QOS_REF_CLK,GPIO10_IO16,,,, +GPIO_SD_B2_08,USDHC2_DATA4,FLEXSPI1_A_DATA00,ENET_1G_TX_DATA01,LPUART3_RTS_B,GPT6_COMPARE1,GPIO4_IO17,LPSPI2_PCS0,,,,GPIO10_IO17,,,, +GPIO_SD_B2_09,USDHC2_DATA5,FLEXSPI1_A_DATA01,ENET_1G_TX_DATA00,LPUART5_CTS_B,GPT6_COMPARE2,GPIO4_IO18,LPSPI2_SOUT,,,,GPIO10_IO18,,,, +GPIO_SD_B2_10,USDHC2_DATA6,FLEXSPI1_A_DATA02,ENET_1G_TX_EN,LPUART5_RTS_B,GPT6_COMPARE3,GPIO4_IO19,LPSPI2_SIN,,,,GPIO10_IO19,,,, +GPIO_SD_B2_11,USDHC2_DATA7,FLEXSPI1_A_DATA03,ENET_1G_TX_CLK_IO,ENET_1G_REF_CLK,GPT6_CLK,GPIO4_IO20,LPSPI2_PCS1,,,,GPIO10_IO20,,,, +GPIO_EMC_B1_00,SEMC_DATA00,FLEXPWM4_PWM0_A,,,,GPIO1_IO00,,,FLEXIO1_D00,,GPIO7_IO00,,,, +GPIO_EMC_B1_01,SEMC_DATA01,FLEXPWM4_PWM0_B,,,,GPIO1_IO01,,,FLEXIO1_D01,,GPIO7_IO01,,,, +GPIO_EMC_B1_02,SEMC_DATA02,FLEXPWM4_PWM1_A,,,,GPIO1_IO02,,,FLEXIO1_D02,,GPIO7_IO02,,,, +GPIO_EMC_B1_03,SEMC_DATA03,FLEXPWM4_PWM1_B,,,,GPIO1_IO03,,,FLEXIO1_D03,,GPIO7_IO03,,,, +GPIO_EMC_B1_04,SEMC_DATA04,FLEXPWM4_PWM2_A,,,,GPIO1_IO04,,,FLEXIO1_D04,,GPIO7_IO04,,,, +GPIO_EMC_B1_05,SEMC_DATA05,FLEXPWM4_PWM2_B,,,,GPIO1_IO05,,,FLEXIO1_D05,,GPIO7_IO05,,,, +GPIO_EMC_B1_06,SEMC_DATA06,FLEXPWM2_PWM0_A,,,,GPIO1_IO06,,,FLEXIO1_D06,,GPIO7_IO06,,,, +GPIO_EMC_B1_07,SEMC_DATA07,FLEXPWM2_PWM0_B,,,,GPIO1_IO07,,,FLEXIO1_D07,,GPIO7_IO07,,,, +GPIO_EMC_B1_08,SEMC_DM00,FLEXPWM2_PWM1_A,,,,GPIO1_IO08,,,FLEXIO1_D08,,GPIO7_IO08,,,, +GPIO_EMC_B1_09,SEMC_ADDR00,FLEXPWM2_PWM1_B,GPT5_CAPTURE1,,,GPIO1_IO09,,,FLEXIO1_D09,,GPIO7_IO09,,,, +GPIO_EMC_B1_10,SEMC_ADDR01,FLEXPWM2_PWM2_A,GPT5_CAPTURE2,,,GPIO1_IO10,,,FLEXIO1_D10,,GPIO7_IO10,,,, +GPIO_EMC_B1_11,SEMC_ADDR02,FLEXPWM2_PWM2_B,GPT5_COMPARE1,,,GPIO1_IO11,,,FLEXIO1_D11,,GPIO7_IO11,,,, +GPIO_EMC_B1_12,SEMC_ADDR03,XBAR1_INOUT04,GPT5_COMPARE2,,,GPIO1_IO12,,,FLEXIO1_D12,,GPIO7_IO12,,,, +GPIO_EMC_B1_13,SEMC_ADDR04,XBAR1_INOUT05,GPT5_COMPARE3,,,GPIO1_IO13,,,FLEXIO1_D13,,GPIO7_IO13,,,, +GPIO_EMC_B1_14,SEMC_ADDR05,XBAR1_INOUT06,GPT5_CLK,,,GPIO1_IO14,,,FLEXIO1_D14,,GPIO7_IO14,,,, +GPIO_EMC_B1_15,SEMC_ADDR06,XBAR1_INOUT07,,,,GPIO1_IO15,,,FLEXIO1_D15,,GPIO7_IO15,,,, +GPIO_EMC_B1_16,SEMC_ADDR07,XBAR1_INOUT08,,,,GPIO1_IO16,,,FLEXIO1_D16,,GPIO7_IO16,,,, +GPIO_EMC_B1_17,SEMC_ADDR08,FLEXPWM4_PWM3_A,TMR1_TIMER0,,,GPIO1_IO17,,,FLEXIO1_D17,,GPIO7_IO17,,,, +GPIO_EMC_B1_18,SEMC_ADDR09,FLEXPWM4_PWM3_B,TMR2_TIMER0,,,GPIO1_IO18,,,FLEXIO1_D18,,GPIO7_IO18,,,, +GPIO_EMC_B1_19,SEMC_ADDR11,FLEXPWM2_PWM3_A,TMR3_TIMER0,,,GPIO1_IO19,,,FLEXIO1_D19,,GPIO7_IO19,,,, +GPIO_EMC_B1_20,SEMC_ADDR12,FLEXPWM2_PWM3_B,TMR4_TIMER0,,,GPIO1_IO20,,,FLEXIO1_D20,,GPIO7_IO20,,,, +GPIO_EMC_B1_21,SEMC_BA0,FLEXPWM3_PWM3_A,,,,GPIO1_IO21,,,FLEXIO1_D21,,GPIO7_IO21,,,, +GPIO_EMC_B1_22,SEMC_BA1,FLEXPWM3_PWM3_B,,,,GPIO1_IO22,,,FLEXIO1_D22,,GPIO7_IO22,,,, +GPIO_EMC_B1_23,SEMC_ADDR10,FLEXPWM1_PWM0_A,,,,GPIO1_IO23,,,FLEXIO1_D23,,GPIO7_IO23,,,, +GPIO_EMC_B1_24,SEMC_CAS,FLEXPWM1_PWM0_B,,,,GPIO1_IO24,,,FLEXIO1_D24,,GPIO7_IO24,,,, +GPIO_EMC_B1_25,SEMC_RAS,FLEXPWM1_PWM1_A,,,,GPIO1_IO25,,,FLEXIO1_D25,,GPIO7_IO25,,,, +GPIO_EMC_B1_26,SEMC_CLK,FLEXPWM1_PWM1_B,,,,GPIO1_IO26,,,FLEXIO1_D26,,GPIO7_IO26,,,, +GPIO_EMC_B1_27,SEMC_CKE,FLEXPWM1_PWM2_A,,,,GPIO1_IO27,,,FLEXIO1_D27,,GPIO7_IO27,,,, +GPIO_EMC_B1_28,SEMC_WE,FLEXPWM1_PWM2_B,,,,GPIO1_IO28,,,FLEXIO1_D28,,GPIO7_IO28,,,, +GPIO_EMC_B1_29,SEMC_CS0,FLEXPWM3_PWM0_A,,,,GPIO1_IO29,,,FLEXIO1_D29,,GPIO7_IO29,,,, +GPIO_EMC_B1_30,SEMC_DATA08,FLEXPWM3_PWM0_B,,,,GPIO1_IO30,,,FLEXIO1_D30,,GPIO7_IO30,,,, +GPIO_EMC_B1_31,SEMC_DATA09,FLEXPWM3_PWM1_A,,,,GPIO1_IO31,,,FLEXIO1_D31,,GPIO7_IO31,,,, +GPIO_EMC_B1_32,SEMC_DATA10,FLEXPWM3_PWM1_B,,,,GPIO2_IO00,,,,,GPIO8_IO00,,,, +GPIO_EMC_B1_33,SEMC_DATA11,FLEXPWM3_PWM2_A,,,,GPIO2_IO01,,,,,GPIO8_IO01,,,, +GPIO_EMC_B1_34,SEMC_DATA12,FLEXPWM3_PWM2_B,,,,GPIO2_IO02,,,,,GPIO8_IO02,,,, +GPIO_EMC_B1_35,SEMC_DATA13,XBAR1_INOUT09,,,,GPIO2_IO03,,,,,GPIO8_IO03,,,, +GPIO_EMC_B1_36,SEMC_DATA14,XBAR1_INOUT10,,,,GPIO2_IO04,,,,,GPIO8_IO04,,,, +GPIO_EMC_B1_37,SEMC_DATA15,XBAR1_INOUT11,,,,GPIO2_IO05,,,,,GPIO8_IO05,,,, +GPIO_EMC_B1_38,SEMC_DM01,FLEXPWM1_PWM3_A,TMR1_TIMER1,,,GPIO2_IO06,,,,,GPIO8_IO06,,,, +GPIO_EMC_B1_39,SEMC_DQS,FLEXPWM1_PWM3_B,TMR2_TIMER1,,,GPIO2_IO07,,,,,GPIO8_IO07,,,, +GPIO_EMC_B1_40,SEMC_RDY,XBAR1_INOUT12,MQS_RIGHT,LPUART6_TXD,,GPIO2_IO08,,ENET_1G_MDC,,CCM_CLKO1,GPIO8_IO08,,,, +GPIO_EMC_B1_41,SEMC_CSX00,XBAR1_INOUT13,MQS_LEFT,LPUART6_RXD,FLEXSPI2_B_DATA07,GPIO2_IO09,,ENET_1G_MDIO,,CCM_CLKO2,GPIO8_IO09,,,, +GPIO_EMC_B2_00,SEMC_DATA16,CCM_ENET_REF_CLK_25M,TMR3_TIMER1,LPUART6_CTS_B,FLEXSPI2_B_DATA06,GPIO2_IO10,XBAR1_INOUT20,ENET_QOS_1588_EVENT1_OUT,LPSPI1_SCK,LPI2C2_SCL,GPIO8_IO10,FLEXPWM3_PWM0_A,,, +GPIO_EMC_B2_01,SEMC_DATA17,USDHC2_CD_B,TMR4_TIMER1,LPUART6_RTS_B,FLEXSPI2_B_DATA05,GPIO2_IO11,XBAR1_INOUT21,ENET_QOS_1588_EVENT1_IN,LPSPI1_PCS0,LPI2C2_SDA,GPIO8_IO11,FLEXPWM3_PWM0_B,,, +GPIO_EMC_B2_02,SEMC_DATA18,USDHC2_WP,,VIDEO_MUX_CSI_DATA23,FLEXSPI2_B_DATA04,GPIO2_IO12,XBAR1_INOUT22,ENET_QOS_1588_EVENT1_AUX_IN,LPSPI1_SOUT,,GPIO8_IO12,FLEXPWM3_PWM1_A,,, +GPIO_EMC_B2_03,SEMC_DATA19,USDHC2_VSELECT,,VIDEO_MUX_CSI_DATA22,FLEXSPI2_B_DATA03,GPIO2_IO13,XBAR1_INOUT23,ENET_1G_TX_DATA03,LPSPI1_SIN,,GPIO8_IO13,FLEXPWM3_PWM1_B,,, +GPIO_EMC_B2_04,SEMC_DATA20,USDHC2_RESET_B,SAI2_MCLK,VIDEO_MUX_CSI_DATA21,FLEXSPI2_B_DATA02,GPIO2_IO14,XBAR1_INOUT24,ENET_1G_TX_DATA02,LPSPI3_SCK,,GPIO8_IO14,FLEXPWM3_PWM2_A,,, +GPIO_EMC_B2_05,SEMC_DATA21,GPT3_CLK,SAI2_RX_SYNC,VIDEO_MUX_CSI_DATA20,FLEXSPI2_B_DATA01,GPIO2_IO15,XBAR1_INOUT25,ENET_1G_RX_CLK,LPSPI3_PCS0,PIT1_TRIGGER0,GPIO8_IO15,FLEXPWM3_PWM2_B,,, +GPIO_EMC_B2_06,SEMC_DATA22,GPT3_CAPTURE1,SAI2_RX_BCLK,VIDEO_MUX_CSI_DATA19,FLEXSPI2_B_DATA00,GPIO2_IO16,XBAR1_INOUT26,ENET_1G_TX_ER,LPSPI3_SOUT,PIT1_TRIGGER1,GPIO8_IO16,FLEXPWM3_PWM3_A,,, +GPIO_EMC_B2_07,SEMC_DATA23,GPT3_CAPTURE2,SAI2_RX_DATA,VIDEO_MUX_CSI_DATA18,FLEXSPI2_B_DQS,GPIO2_IO17,XBAR1_INOUT27,ENET_1G_RX_DATA03,LPSPI3_SIN,PIT1_TRIGGER2,GPIO8_IO17,FLEXPWM3_PWM3_B,,, +GPIO_EMC_B2_08,SEMC_DM02,GPT3_COMPARE1,SAI2_TX_DATA,VIDEO_MUX_CSI_DATA17,FLEXSPI2_B_SS0_B,GPIO2_IO18,XBAR1_INOUT28,ENET_1G_RX_DATA02,LPSPI3_PCS1,PIT1_TRIGGER3,GPIO8_IO18,,,, +GPIO_EMC_B2_09,SEMC_DATA24,GPT3_COMPARE2,SAI2_TX_BCLK,VIDEO_MUX_CSI_DATA16,FLEXSPI2_B_SCLK,GPIO2_IO19,XBAR1_INOUT29,ENET_1G_CRS,LPSPI3_PCS2,TMR1_TIMER0,GPIO8_IO19,,,, +GPIO_EMC_B2_10,SEMC_DATA25,GPT3_COMPARE3,SAI2_TX_SYNC,VIDEO_MUX_CSI_FIELD,FLEXSPI2_A_SCLK,GPIO2_IO20,XBAR1_INOUT30,ENET_1G_COL,LPSPI3_PCS3,TMR1_TIMER1,GPIO8_IO20,,,, +GPIO_EMC_B2_11,SEMC_DATA26,SPDIF_IN,ENET_1G_TX_DATA00,SAI3_RX_SYNC,FLEXSPI2_A_SS0_B,GPIO2_IO21,XBAR1_INOUT31,,EMVSIM1_IO,TMR1_TIMER2,GPIO8_IO21,,,, +GPIO_EMC_B2_12,SEMC_DATA27,SPDIF_OUT,ENET_1G_TX_DATA01,SAI3_RX_BCLK,FLEXSPI2_A_DQS,GPIO2_IO22,XBAR1_INOUT32,,EMVSIM1_CLK,TMR1_TIMER3,GPIO8_IO22,,,, +GPIO_EMC_B2_13,SEMC_DATA28,,ENET_1G_TX_EN,SAI3_RX_DATA,FLEXSPI2_A_DATA00,GPIO2_IO23,XBAR1_INOUT33,,EMVSIM1_RST,TMR2_TIMER0,GPIO8_IO23,,,, +GPIO_EMC_B2_14,SEMC_DATA29,,ENET_1G_TX_CLK_IO,SAI3_TX_DATA,FLEXSPI2_A_DATA01,GPIO2_IO24,XBAR1_INOUT34,SFA_ipp_do_atx_clk_under_test,EMVSIM1_SVEN,TMR2_TIMER1,GPIO8_IO24,,,, +GPIO_EMC_B2_15,SEMC_DATA30,,ENET_1G_RX_DATA00,SAI3_TX_BCLK,FLEXSPI2_A_DATA02,GPIO2_IO25,XBAR1_INOUT35,,EMVSIM1_PD,TMR2_TIMER2,GPIO8_IO25,,,, +GPIO_EMC_B2_16,SEMC_DATA31,XBAR1_INOUT14,ENET_1G_RX_DATA01,SAI3_TX_SYNC,FLEXSPI2_A_DATA03,GPIO2_IO26,,,EMVSIM1_POWER_FAIL,TMR2_TIMER3,GPIO8_IO26,,,, +GPIO_EMC_B2_17,SEMC_DM03,XBAR1_INOUT15,ENET_1G_RX_EN,SAI3_MCLK,FLEXSPI2_A_DATA04,GPIO2_IO27,,,WDOG1_ANY,TMR3_TIMER0,GPIO8_IO27,,,, +GPIO_EMC_B2_18,SEMC_DQS4,XBAR1_INOUT16,ENET_1G_RX_ER,EWM_OUT_B,FLEXSPI2_A_DATA05,GPIO2_IO28,FLEXSPI1_A_DQS,,WDOG1_B,TMR3_TIMER1,GPIO8_IO28,,,, +GPIO_EMC_B2_19,SEMC_CLKX00,ENET_MDC,ENET_1G_MDC,ENET_1G_REF_CLK,FLEXSPI2_A_DATA06,GPIO2_IO29,,,ENET_QOS_MDC,TMR3_TIMER2,GPIO8_IO29,,,, +GPIO_EMC_B2_20,SEMC_CLKX01,ENET_MDIO,ENET_1G_MDIO,ENET_QOS_REF_CLK,FLEXSPI2_A_DATA07,GPIO2_IO30,,,ENET_QOS_MDIO,TMR3_TIMER3,GPIO8_IO30,,,, +GPIO_SNVS_00_DIG,SNVS_TAMPER0,,,,,GPIO13_IO03,,,,,,,,, +GPIO_SNVS_01_DIG,SNVS_TAMPER1,,,,,GPIO13_IO04,,,,,,,,, +GPIO_SNVS_02_DIG,SNVS_TAMPER2,,,,,GPIO13_IO05,,,,,,,,, +GPIO_SNVS_03_DIG,SNVS_TAMPER3,,,,,GPIO13_IO06,,,,,,,,, +GPIO_SNVS_04_DIG,SNVS_TAMPER4,,,,,GPIO13_IO07,,,,,,,,, +GPIO_SNVS_05_DIG,SNVS_TAMPER5,,,,,GPIO13_IO08,,,,,,,,, +GPIO_SNVS_06_DIG,SNVS_TAMPER6,,,,,GPIO13_IO09,,,,,,,,, +GPIO_SNVS_07_DIG,SNVS_TAMPER7,,,,,GPIO13_IO10,,,,,,,,, +GPIO_SNVS_08_DIG,SNVS_TAMPER8,,,,,GPIO13_IO11,,,,,,,,, +GPIO_SNVS_09_DIG,SNVS_TAMPER9,,,,,GPIO13_IO12,,,,,,,,, +GPIO_LPSR_00,FLEXCAN3_TX,MIC_CLK,MQS_RIGHT,ARM_CM4_EVENTO,,GPIO6_IO00,LPUART12_TXD,SAI4_MCLK,,,GPIO12_IO00,,,, +GPIO_LPSR_01,FLEXCAN3_RX,MIC_BITSTREAM0,MQS_LEFT,ARM_CM4_EVENTI,,GPIO6_IO01,LPUART12_RXD,,,,GPIO12_IO01,,,, +GPIO_LPSR_02,SRC_BOOT_MODE00,LPSPI5_SCK,SAI4_TX_DATA,MQS_RIGHT,,GPIO6_IO02,,,,,GPIO12_IO02,,,, +GPIO_LPSR_03,SRC_BOOT_MODE01,LPSPI5_PCS0,SAI4_TX_SYNC,MQS_LEFT,,GPIO6_IO03,,,,,GPIO12_IO03,,,, +GPIO_LPSR_04,LPI2C5_SDA,LPSPI5_SOUT,SAI4_TX_BCLK,LPUART12_RTS_B,,GPIO6_IO04,LPUART11_TXD,,,,GPIO12_IO04,,,, +GPIO_LPSR_05,LPI2C5_SCL,LPSPI5_SIN,SAI4_MCLK,LPUART12_CTS_B,,GPIO6_IO05,LPUART11_RXD,NMI_GLUE_NMI,,,GPIO12_IO05,,,, +GPIO_LPSR_06,LPI2C6_SDA,,SAI4_RX_DATA,LPUART12_TXD,LPSPI6_PCS3,GPIO6_IO06,FLEXCAN3_TX,PIT2_TRIGGER3,LPSPI5_PCS1,,GPIO12_IO06,,,, +GPIO_LPSR_07,LPI2C6_SCL,,SAI4_RX_BCLK,LPUART12_RXD,LPSPI6_PCS2,GPIO6_IO07,FLEXCAN3_RX,PIT2_TRIGGER2,LPSPI5_PCS2,,GPIO12_IO07,,,, +GPIO_LPSR_08,LPUART11_TXD,FLEXCAN3_TX,SAI4_RX_SYNC,MIC_CLK,LPSPI6_PCS1,GPIO6_IO08,LPI2C5_SDA,PIT2_TRIGGER1,LPSPI5_PCS3,,GPIO12_IO08,,,, +GPIO_LPSR_09,LPUART11_RXD,FLEXCAN3_RX,PIT2_TRIGGER0,MIC_BITSTREAM0,LPSPI6_PCS0,GPIO6_IO09,LPI2C5_SCL,SAI4_TX_DATA,,,GPIO12_IO09,,,, +GPIO_LPSR_10,JTAG_MUX_TRSTB,LPUART11_CTS_B,LPI2C6_SDA,MIC_BITSTREAM1,LPSPI6_SCK,GPIO6_IO10,LPI2C5_SCLS,SAI4_TX_SYNC,LPUART12_TXD,,GPIO12_IO10,,,, +GPIO_LPSR_11,JTAG_MUX_TDO,LPUART11_RTS_B,LPI2C6_SCL,MIC_BITSTREAM2,LPSPI6_SOUT,GPIO6_IO11,LPI2C5_SDAS,ARM_TRACE_SWO,LPUART12_RXD,,GPIO12_IO11,,,, +GPIO_LPSR_12,JTAG_MUX_TDI,PIT2_TRIGGER0,,MIC_BITSTREAM3,LPSPI6_SIN,GPIO6_IO12,LPI2C5_HREQ,SAI4_TX_BCLK,LPSPI5_SCK,,GPIO12_IO12,,,, +GPIO_LPSR_13,JTAG_MUX_MOD,MIC_BITSTREAM1,PIT2_TRIGGER1,,,GPIO6_IO13,,SAI4_RX_DATA,LPSPI5_PCS0,,GPIO12_IO13,,,, +GPIO_LPSR_14,JTAG_MUX_TCK,MIC_BITSTREAM2,PIT2_TRIGGER2,,,GPIO6_IO14,,SAI4_RX_BCLK,LPSPI5_SOUT,,GPIO12_IO14,,,, +GPIO_LPSR_15,JTAG_MUX_TMS,MIC_BITSTREAM3,PIT2_TRIGGER3,,,GPIO6_IO15,,SAI4_RX_SYNC,LPSPI5_SIN,,GPIO12_IO15,,,, +WAKEUP_DIG,,,,,,GPIO13_IO00,,NMI_GLUE_NMI,,,,,,, +PMIC_ON_REQ_DIG,SNVS_LP_PMIC_ON_REQ,,,,,GPIO13_IO01,,,,,,,,, +PMIC_STBY_REQ_DIG,CCM_PMIC_VSTBY_REQ,,,,,GPIO13_IO02,,,,,,,,, +GPIO_DISP_B1_00,VIDEO_MUX_LCDIF_CLK,ENET_1G_RX_EN,,TMR1_TIMER0,XBAR1_INOUT26,GPIO4_IO21,,,ENET_QOS_RX_EN,,GPIO10_IO21,,,, +GPIO_DISP_B1_01,VIDEO_MUX_LCDIF_ENABLE,ENET_1G_RX_CLK,ENET_1G_RX_ER,TMR1_TIMER1,XBAR1_INOUT27,GPIO4_IO22,,,ENET_QOS_RX_CLK,ENET_QOS_RX_ER,GPIO10_IO22,,,, +GPIO_DISP_B1_02,VIDEO_MUX_LCDIF_HSYNC,ENET_1G_RX_DATA00,LPI2C3_SCL,TMR1_TIMER2,XBAR1_INOUT28,GPIO4_IO23,,,ENET_QOS_RX_DATA00,LPUART1_TXD,GPIO10_IO23,,,, +GPIO_DISP_B1_03,VIDEO_MUX_LCDIF_VSYNC,ENET_1G_RX_DATA01,LPI2C3_SDA,TMR2_TIMER0,XBAR1_INOUT29,GPIO4_IO24,,,ENET_QOS_RX_DATA01,LPUART1_RXD,GPIO10_IO24,,,, +GPIO_DISP_B1_04,VIDEO_MUX_LCDIF_DATA00,ENET_1G_RX_DATA02,LPUART4_RXD,TMR2_TIMER1,XBAR1_INOUT30,GPIO4_IO25,,,ENET_QOS_RX_DATA02,LPSPI3_SCK,GPIO10_IO25,,,, +GPIO_DISP_B1_05,VIDEO_MUX_LCDIF_DATA01,ENET_1G_RX_DATA03,LPUART4_CTS_B,TMR2_TIMER2,XBAR1_INOUT31,GPIO4_IO26,,,ENET_QOS_RX_DATA03,LPSPI3_SIN,GPIO10_IO26,,,, +GPIO_DISP_B1_06,VIDEO_MUX_LCDIF_DATA02,ENET_1G_TX_DATA03,LPUART4_TXD,TMR3_TIMER0,XBAR1_INOUT32,GPIO4_IO27,SRC_BT_CFG00,,ENET_QOS_TX_DATA03,LPSPI3_SOUT,GPIO10_IO27,,,, +GPIO_DISP_B1_07,VIDEO_MUX_LCDIF_DATA03,ENET_1G_TX_DATA02,LPUART4_RTS_B,TMR3_TIMER1,XBAR1_INOUT33,GPIO4_IO28,SRC_BT_CFG01,,ENET_QOS_TX_DATA02,LPSPI3_PCS0,GPIO10_IO28,,,, +GPIO_DISP_B1_08,VIDEO_MUX_LCDIF_DATA04,ENET_1G_TX_DATA01,USDHC1_CD_B,TMR3_TIMER2,XBAR1_INOUT34,GPIO4_IO29,SRC_BT_CFG02,,ENET_QOS_TX_DATA01,LPSPI3_PCS1,GPIO10_IO29,,,, +GPIO_DISP_B1_09,VIDEO_MUX_LCDIF_DATA05,ENET_1G_TX_DATA00,USDHC1_WP,TMR4_TIMER0,XBAR1_INOUT35,GPIO4_IO30,SRC_BT_CFG03,,ENET_QOS_TX_DATA00,LPSPI3_PCS2,GPIO10_IO30,,,, +GPIO_DISP_B1_10,VIDEO_MUX_LCDIF_DATA06,ENET_1G_TX_EN,USDHC1_RESET_B,TMR4_TIMER1,XBAR1_INOUT36,GPIO4_IO31,SRC_BT_CFG04,,ENET_QOS_TX_EN,LPSPI3_PCS3,GPIO10_IO31,,,, +GPIO_DISP_B1_11,VIDEO_MUX_LCDIF_DATA07,ENET_1G_TX_CLK_IO,ENET_1G_REF_CLK,TMR4_TIMER2,XBAR1_INOUT37,GPIO5_IO00,SRC_BT_CFG05,,ENET_QOS_TX_CLK,ENET_QOS_REF_CLK,GPIO11_IO00,,,, +GPIO_DISP_B2_00,VIDEO_MUX_LCDIF_DATA08,WDOG1_B,MQS_RIGHT,ENET_1G_TX_ER,SAI1_TX_DATA03,GPIO5_IO01,SRC_BT_CFG06,,ENET_QOS_TX_ER,,GPIO11_IO01,,,, +GPIO_DISP_B2_01,VIDEO_MUX_LCDIF_DATA09,USDHC1_VSELECT,MQS_LEFT,WDOG2_B,SAI1_TX_DATA02,GPIO5_IO02,SRC_BT_CFG07,,EWM_OUT_B,CCM_ENET_REF_CLK_25M,GPIO11_IO02,,,, +GPIO_DISP_B2_02,VIDEO_MUX_LCDIF_DATA10,ENET_TX_DATA00,PIT1_TRIGGER3,ARM_TRACE00,SAI1_TX_DATA01,GPIO5_IO03,SRC_BT_CFG08,,ENET_QOS_TX_DATA00,,GPIO11_IO03,,,, +GPIO_DISP_B2_03,VIDEO_MUX_LCDIF_DATA11,ENET_TX_DATA01,PIT1_TRIGGER2,ARM_TRACE01,SAI1_MCLK,GPIO5_IO04,SRC_BT_CFG09,,ENET_QOS_TX_DATA01,,GPIO11_IO04,,,, +GPIO_DISP_B2_04,VIDEO_MUX_LCDIF_DATA12,ENET_TX_EN,PIT1_TRIGGER1,ARM_TRACE02,SAI1_RX_SYNC,GPIO5_IO05,SRC_BT_CFG10,,ENET_QOS_TX_EN,,GPIO11_IO05,,,, +GPIO_DISP_B2_05,VIDEO_MUX_LCDIF_DATA13,ENET_TX_CLK,ENET_REF_CLK,ARM_TRACE03,SAI1_RX_BCLK,GPIO5_IO06,SRC_BT_CFG11,,ENET_QOS_TX_CLK,,GPIO11_IO06,,,, +GPIO_DISP_B2_06,VIDEO_MUX_LCDIF_DATA14,ENET_RX_DATA00,LPUART7_TXD,ARM_TRACE_CLK,SAI1_RX_DATA00,GPIO5_IO07,,,ENET_QOS_RX_DATA00,,GPIO11_IO07,,,, +GPIO_DISP_B2_07,VIDEO_MUX_LCDIF_DATA15,ENET_RX_DATA01,LPUART7_RXD,ARM_TRACE_SWO,SAI1_TX_DATA00,GPIO5_IO08,,,ENET_QOS_RX_DATA01,,GPIO11_IO08,,,, +GPIO_DISP_B2_08,VIDEO_MUX_LCDIF_DATA16,ENET_RX_EN,LPUART8_TXD,ARM_CM7_EVENTO,SAI1_TX_BCLK,GPIO5_IO09,,,ENET_QOS_RX_EN,LPUART1_TXD,GPIO11_IO09,,,, +GPIO_DISP_B2_09,VIDEO_MUX_LCDIF_DATA17,ENET_RX_ER,LPUART8_RXD,ARM_CM7_EVENTI,SAI1_TX_SYNC,GPIO5_IO10,,,ENET_QOS_RX_ER,LPUART1_RXD,GPIO11_IO10,,,, +GPIO_DISP_B2_10,VIDEO_MUX_LCDIF_DATA18,EMVSIM2_IO,LPUART2_TXD,WDOG2_RESET_B_DEB,XBAR1_INOUT38,GPIO5_IO11,LPI2C3_SCL,,ENET_QOS_RX_ER,SPDIF_IN,GPIO11_IO11,,,, +GPIO_DISP_B2_11,VIDEO_MUX_LCDIF_DATA19,EMVSIM2_CLK,LPUART2_RXD,WDOG1_RESET_B_DEB,XBAR1_INOUT39,GPIO5_IO12,LPI2C3_SDA,,ENET_QOS_CRS,SPDIF_OUT,GPIO11_IO12,,,, +GPIO_DISP_B2_12,VIDEO_MUX_LCDIF_DATA20,EMVSIM2_RST,FLEXCAN1_TX,LPUART2_CTS_B,XBAR1_INOUT40,GPIO5_IO13,LPI2C4_SCL,,ENET_QOS_COL,LPSPI4_SCK,GPIO11_IO13,,,, +GPIO_DISP_B2_13,VIDEO_MUX_LCDIF_DATA21,EMVSIM2_SVEN,FLEXCAN1_RX,LPUART2_RTS_B,ENET_REF_CLK,GPIO5_IO14,LPI2C4_SDA,,ENET_QOS_1588_EVENT0_OUT,LPSPI4_SIN,GPIO11_IO14,,,, +GPIO_DISP_B2_14,VIDEO_MUX_LCDIF_DATA22,EMVSIM2_PD,WDOG2_B,VIDEO_MUX_EXT_DCIC1,ENET_1G_REF_CLK,GPIO5_IO15,FLEXCAN1_TX,,ENET_QOS_1588_EVENT0_IN,LPSPI4_SOUT,GPIO11_IO15,,,, +GPIO_DISP_B2_15,VIDEO_MUX_LCDIF_DATA23,EMVSIM2_POWER_FAIL,WDOG1_B,VIDEO_MUX_EXT_DCIC2,PIT1_TRIGGER0,GPIO5_IO16,FLEXCAN1_RX,,ENET_QOS_1588_EVENT0_AUX_IN,LPSPI4_PCS0,GPIO11_IO16,,,, +GPIO_AD_00,EMVSIM1_IO,FLEXCAN2_TX,ENET_1G_1588_EVENT1_IN,GPT2_CAPTURE1,FLEXPWM1_PWM0_A,GPIO2_IO31,LPUART7_TXD,,FLEXIO2_D00,FLEXSPI2_B_SS1_B,GPIO8_IO31,,,ACMP1_IN1, +GPIO_AD_01,EMVSIM1_CLK,FLEXCAN2_RX,ENET_1G_1588_EVENT1_OUT,GPT2_CAPTURE2,FLEXPWM1_PWM0_B,GPIO3_IO00,LPUART7_RXD,,FLEXIO2_D01,FLEXSPI2_A_SS1_B,GPIO9_IO00,,,ACMP1_IN2, +GPIO_AD_02,EMVSIM1_RST,LPUART7_CTS_B,ENET_1G_1588_EVENT2_IN,GPT2_COMPARE1,FLEXPWM1_PWM1_A,GPIO3_IO01,LPUART8_TXD,,FLEXIO2_D02,VIDEO_MUX_EXT_DCIC1,GPIO9_IO01,,,ACMP1_IN3, +GPIO_AD_03,EMVSIM1_SVEN,LPUART7_RTS_B,ENET_1G_1588_EVENT2_OUT,GPT2_COMPARE2,FLEXPWM1_PWM1_B,GPIO3_IO02,LPUART8_RXD,,FLEXIO2_D03,VIDEO_MUX_EXT_DCIC2,GPIO9_IO02,,,ACMP1_IN4, +GPIO_AD_04,EMVSIM1_PD,LPUART8_CTS_B,ENET_1G_1588_EVENT3_IN,GPT2_COMPARE3,FLEXPWM1_PWM2_A,GPIO3_IO03,WDOG1_B,,FLEXIO2_D04,TMR4_TIMER0,GPIO9_IO03,,,ACMP2_IN1, +GPIO_AD_05,EMVSIM1_POWER_FAIL,LPUART8_RTS_B,ENET_1G_1588_EVENT3_OUT,GPT2_CLK,FLEXPWM1_PWM2_B,GPIO3_IO04,WDOG2_B,,FLEXIO2_D05,TMR4_TIMER1,GPIO9_IO04,,,ACMP2_IN2, +GPIO_AD_06,USB_OTG2_OC,FLEXCAN1_TX,EMVSIM2_IO,GPT3_CAPTURE1,VIDEO_MUX_CSI_DATA15,GPIO3_IO05,ENET_1588_EVENT1_IN,,FLEXIO2_D06,TMR4_TIMER2,GPIO9_IO05,FLEXPWM1_PWM0_X,ADC1_CH0A,, +GPIO_AD_07,USB_OTG2_PWR,FLEXCAN1_RX,EMVSIM2_CLK,GPT3_CAPTURE2,VIDEO_MUX_CSI_DATA14,GPIO3_IO06,ENET_1588_EVENT1_OUT,,FLEXIO2_D07,TMR4_TIMER3,GPIO9_IO06,FLEXPWM1_PWM1_X,ADC1_CH0B,, +GPIO_AD_08,USBPHY2_OTG_ID,LPI2C1_SCL,EMVSIM2_RST,GPT3_COMPARE1,VIDEO_MUX_CSI_DATA13,GPIO3_IO07,ENET_1588_EVENT2_IN,,FLEXIO2_D08,,GPIO9_IO07,FLEXPWM1_PWM2_X,ADC1_CH1A,, +GPIO_AD_09,USBPHY1_OTG_ID,LPI2C1_SDA,EMVSIM2_SVEN,GPT3_COMPARE2,VIDEO_MUX_CSI_DATA12,GPIO3_IO08,ENET_1588_EVENT2_OUT,,FLEXIO2_D09,,GPIO9_IO08,FLEXPWM1_PWM3_X,ADC1_CH1B,, +GPIO_AD_10,USB_OTG1_PWR,LPI2C1_SCLS,EMVSIM2_PD,GPT3_COMPARE3,VIDEO_MUX_CSI_DATA11,GPIO3_IO09,ENET_1588_EVENT3_IN,,FLEXIO2_D10,,GPIO9_IO09,FLEXPWM2_PWM0_X,ADC1_CH2A,, +GPIO_AD_11,USB_OTG1_OC,LPI2C1_SDAS,EMVSIM2_POWER_FAIL,GPT3_CLK,VIDEO_MUX_CSI_DATA10,GPIO3_IO10,ENET_1588_EVENT3_OUT,,FLEXIO2_D11,,GPIO9_IO10,FLEXPWM2_PWM1_X,ADC1_CH2B,, +GPIO_AD_12,SPDIF_LOCK,LPI2C1_HREQ,GPT1_CAPTURE1,FLEXSPI1_B_DATA03,VIDEO_MUX_CSI_PIXCLK,GPIO3_IO11,ENET_TX_DATA03,,FLEXIO2_D12,EWM_OUT_B,GPIO9_IO11,FLEXPWM2_PWM2_X,"ADC1_CH3A,ADC2_CH3A",, +GPIO_AD_13,SPDIF_SR_CLK,PIT1_TRIGGER0,GPT1_CAPTURE2,FLEXSPI1_B_DATA02,VIDEO_MUX_CSI_MCLK,GPIO3_IO12,ENET_TX_DATA02,,FLEXIO2_D13,REF_CLK_32K,GPIO9_IO12,FLEXPWM2_PWM3_X,"ADC1_CH3B,ADC2_CH3B",, +GPIO_AD_14,SPDIF_EXT_CLK,REF_CLK_24M,GPT1_COMPARE1,FLEXSPI1_B_DATA01,VIDEO_MUX_CSI_VSYNC,GPIO3_IO13,ENET_RX_CLK,,FLEXIO2_D14,CCM_ENET_REF_CLK_25M,GPIO9_IO13,FLEXPWM3_PWM0_X,"ADC1_CH4A,ADC2_CH4A",, +GPIO_AD_15,SPDIF_IN,LPUART10_TXD,GPT1_COMPARE2,FLEXSPI1_B_DATA00,VIDEO_MUX_CSI_HSYNC,GPIO3_IO14,ENET_TX_ER,,FLEXIO2_D15,,GPIO9_IO14,FLEXPWM3_PWM1_X,"ADC1_CH4B,ADC2_CH4B",, +GPIO_AD_16,SPDIF_OUT,LPUART10_RXD,GPT1_COMPARE3,FLEXSPI1_B_SCLK,VIDEO_MUX_CSI_DATA09,GPIO3_IO15,ENET_RX_DATA03,,FLEXIO2_D16,ENET_1G_MDC,GPIO9_IO15,FLEXPWM3_PWM2_X,"ADC1_CH5A,ADC2_CH5A",, +GPIO_AD_17,SAI1_MCLK,ACMP1_OUT,GPT1_CLK,FLEXSPI1_A_DQS,VIDEO_MUX_CSI_DATA08,GPIO3_IO16,ENET_RX_DATA02,,FLEXIO2_D17,ENET_1G_MDIO,GPIO9_IO16,FLEXPWM3_PWM3_X,"ADC1_CH5B,ADC2_CH5B",ACMP1_OUT, +GPIO_AD_18,SAI1_RX_SYNC,ACMP2_OUT,LPSPI1_PCS1,FLEXSPI1_A_SS0_B,VIDEO_MUX_CSI_DATA07,GPIO3_IO17,ENET_CRS,,FLEXIO2_D18,LPI2C2_SCL,GPIO9_IO17,FLEXPWM4_PWM0_X,ADC2_CH0A,ACMP2_OUT, +GPIO_AD_19,SAI1_RX_BCLK,ACMP3_OUT,LPSPI1_PCS2,FLEXSPI1_A_SCLK,VIDEO_MUX_CSI_DATA06,GPIO3_IO18,ENET_COL,,FLEXIO2_D19,LPI2C2_SDA,GPIO9_IO18,FLEXPWM4_PWM1_X,ADC2_CH0B,ACMP3_OUT, +GPIO_AD_20,SAI1_RX_DATA00,ACMP4_OUT,LPSPI1_PCS3,FLEXSPI1_A_DATA00,VIDEO_MUX_CSI_DATA05,GPIO3_IO19,KPP_ROW07,,FLEXIO2_D20,ENET_QOS_1588_EVENT2_OUT,GPIO9_IO19,FLEXPWM4_PWM2_X,ADC2_CH1A,ACMP4_OUT, +GPIO_AD_21,SAI1_TX_DATA00,,LPSPI2_PCS1,FLEXSPI1_A_DATA01,VIDEO_MUX_CSI_DATA04,GPIO3_IO20,KPP_COL07,,FLEXIO2_D21,ENET_QOS_1588_EVENT2_IN,GPIO9_IO20,FLEXPWM4_PWM3_X,ADC2_CH1B,, +GPIO_AD_22,SAI1_TX_BCLK,,LPSPI2_PCS2,FLEXSPI1_A_DATA02,VIDEO_MUX_CSI_DATA03,GPIO3_IO21,KPP_ROW06,,FLEXIO2_D22,ENET_QOS_1588_EVENT3_OUT,GPIO9_IO21,,ADC2_CH2A,, +GPIO_AD_23,SAI1_TX_SYNC,,LPSPI2_PCS3,FLEXSPI1_A_DATA03,VIDEO_MUX_CSI_DATA02,GPIO3_IO22,KPP_COL06,,FLEXIO2_D23,ENET_QOS_1588_EVENT3_IN,GPIO9_IO22,,ADC2_CH2B,, +GPIO_AD_24,LPUART1_TXD,LPSPI2_SCK,VIDEO_MUX_CSI_DATA00,ENET_RX_EN,FLEXPWM2_PWM0_A,GPIO3_IO23,KPP_ROW05,,FLEXIO2_D24,LPI2C4_SCL,GPIO9_IO23,,ADC2_CH6A,, +GPIO_AD_25,LPUART1_RXD,LPSPI2_PCS0,VIDEO_MUX_CSI_DATA01,ENET_RX_ER,FLEXPWM2_PWM0_B,GPIO3_IO24,KPP_COL05,,FLEXIO2_D25,LPI2C4_SDA,GPIO9_IO24,,ADC2_CH6B,, +GPIO_AD_26,LPUART1_CTS_B,LPSPI2_SOUT,SEMC_CSX01,ENET_RX_DATA00,FLEXPWM2_PWM1_A,GPIO3_IO25,KPP_ROW04,,FLEXIO2_D26,ENET_QOS_MDC,GPIO9_IO25,USDHC2_CD_B,,ACMP2_IN3, +GPIO_AD_27,LPUART1_RTS_B,LPSPI2_SIN,SEMC_CSX02,ENET_RX_DATA01,FLEXPWM2_PWM1_B,GPIO3_IO26,KPP_COL04,,FLEXIO2_D27,ENET_QOS_MDIO,GPIO9_IO26,USDHC2_WP,,ACMP2_IN4, +GPIO_AD_28,LPSPI1_SCK,LPUART5_TXD,SEMC_CSX03,ENET_TX_EN,FLEXPWM2_PWM2_A,GPIO3_IO27,KPP_ROW03,,FLEXIO2_D28,VIDEO_MUX_EXT_DCIC1,GPIO9_IO27,USDHC2_VSELECT,,ACMP3_IN1, +GPIO_AD_29,LPSPI1_PCS0,LPUART5_RXD,ENET_REF_CLK,ENET_TX_CLK,FLEXPWM2_PWM2_B,GPIO3_IO28,KPP_COL03,,FLEXIO2_D29,VIDEO_MUX_EXT_DCIC2,GPIO9_IO28,USDHC2_RESET_B,,ACMP3_IN2, +GPIO_AD_30,LPSPI1_SOUT,USB_OTG2_OC,FLEXCAN2_TX,ENET_TX_DATA00,LPUART3_TXD,GPIO3_IO29,KPP_ROW02,,FLEXIO2_D30,WDOG2_RESET_B_DEB,GPIO9_IO29,,,ACMP3_IN3, +GPIO_AD_31,LPSPI1_SIN,USB_OTG2_PWR,FLEXCAN2_RX,ENET_TX_DATA01,LPUART3_RXD,GPIO3_IO30,KPP_COL02,,FLEXIO2_D31,WDOG1_RESET_B_DEB,GPIO9_IO30,,,ACMP3_IN4, +GPIO_AD_32,LPI2C1_SCL,USBPHY2_OTG_ID,PGMC_PMIC_RDY,ENET_MDC,USDHC1_CD_B,GPIO3_IO31,KPP_ROW01,,LPUART10_TXD,ENET_1G_MDC,GPIO9_IO31,,,ACMP4_IN1, +GPIO_AD_33,LPI2C1_SDA,USBPHY1_OTG_ID,XBAR1_INOUT17,ENET_MDIO,USDHC1_WP,GPIO4_IO00,KPP_COL01,,LPUART10_RXD,ENET_1G_MDIO,GPIO10_IO00,,,ACMP4_IN2, +GPIO_AD_34,ENET_1G_1588_EVENT0_IN,USB_OTG1_PWR,XBAR1_INOUT18,ENET_1588_EVENT0_IN,USDHC1_VSELECT,GPIO4_IO01,KPP_ROW00,,LPUART10_CTS_B,WDOG1_ANY,GPIO10_IO01,,,ACMP4_IN3, +GPIO_AD_35,ENET_1G_1588_EVENT0_OUT,USB_OTG1_OC,XBAR1_INOUT19,ENET_1588_EVENT0_OUT,USDHC1_RESET_B,GPIO4_IO02,KPP_COL00,,LPUART10_RTS_B,FLEXSPI1_B_SS1_B,GPIO10_IO02,,,ACMP4_IN4, diff --git a/ports/mimxrt/pin.h b/ports/mimxrt/pin.h index 94cd3cad4d7..3ed454c54bc 100644 --- a/ports/mimxrt/pin.h +++ b/ports/mimxrt/pin.h @@ -77,6 +77,7 @@ enum { PIN_AF_MODE_ALT8, PIN_AF_MODE_ALT9, PIN_AF_MODE_ALT10, + PIN_AF_MODE_ALT11, }; enum { From 1b2f6e3d1f68f2eb1dc4d1a63c5af7fed2628577 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 15 Jan 2026 15:12:50 +1100 Subject: [PATCH 1687/2098] mimxrt: Increase resolution of RTC to 1/32768 seconds. Currently the mimxrt port has a resolution of only 1 second for `machine.RTC().datetime()` and `time.time_ns()`. This means (among other things) that it fails the `tests/extmod/time_time_ns.py` test, which requires requires at least 5ms of resolution. The underlying RTC hardware is just a 64-bit counter, and the HAL functions `SNVS_LP_SRTC_GetDatetime()` and `SNVS_LP_SRTC_SetDatetime()` do conversions between y/m/d/h/m/s and this 64-bit value, which counts at a rate of 32kHz. This commit changes the RTC code to access the 64-bit counter directly and therefore improve resolution of all RTC functions to 1/32768 seconds. That makes things much simpler because it a lot of places the code wants to know the number of seconds since the Epoch. Currently it uses a combination of `SNVS_LP_SRTC_GetDatetime()` and `timeutils_seconds_since_epoch()` which converts the 64-bit counter to date-time and then back to seconds. Those operations are computationally expensive. With this commit, getting the number of seconds since the Epoch is as simple as reading the 64-bit counter and dividing by 32768. We can leverage a lot of the timeutils functions to simplify everything, and make it similar to other ports like rp2. Benefits of this change: - simpler, more efficient code to get/set RTC - `machine.RTC().datetime()` now has a non-zero value in the last slot, being the number of microseconds, and has a resolution of 1/32768 seconds - `time.time_ns()` now has a resolution of 1/32768 seconds - the `test/extmod/time_time_ns.py` test now passes Signed-off-by: Damien George --- ports/mimxrt/fatfs_port.c | 14 ++--- ports/mimxrt/machine_rtc.c | 93 +++++++++++++++-------------- ports/mimxrt/mbedtls/mbedtls_port.c | 6 +- ports/mimxrt/modmachine.h | 2 + ports/mimxrt/modtime.c | 21 ++----- ports/mimxrt/mphalport.c | 12 ++-- 6 files changed, 68 insertions(+), 80 deletions(-) diff --git a/ports/mimxrt/fatfs_port.c b/ports/mimxrt/fatfs_port.c index 7b7008667ff..5d3b90c28d7 100644 --- a/ports/mimxrt/fatfs_port.c +++ b/ports/mimxrt/fatfs_port.c @@ -26,14 +26,12 @@ */ #include "lib/oofatfs/ff.h" -#include "fsl_snvs_lp.h" - +#include "shared/timeutils/timeutils.h" +#include "modmachine.h" MP_WEAK DWORD get_fattime(void) { - snvs_lp_srtc_datetime_t srtcDate; - - SNVS_LP_SRTC_GetDatetime(SNVS, &srtcDate); - - return ((srtcDate.year - 1980) << 25) | (srtcDate.month << 21) | (srtcDate.day << 16) | - (srtcDate.hour << 11) | ((srtcDate.minute << 5) | (srtcDate.second / 2)); + uint64_t seconds = machine_rtc_get_seconds(); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(seconds, &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/mimxrt/machine_rtc.c b/ports/mimxrt/machine_rtc.c index b025e392612..3b7aa6ed86c 100644 --- a/ports/mimxrt/machine_rtc.c +++ b/ports/mimxrt/machine_rtc.c @@ -73,18 +73,33 @@ void machine_rtc_alarm_on() { machine_rtc_alarm_set_en(); } -uint32_t machine_rtc_get_seconds() { - uint32_t seconds = 0; - uint32_t tmp = 0; +// Returned ticks are in units of 1/32768 seconds. +uint64_t machine_rtc_get_ticks(void) { + uint64_t ticks = 0; + uint64_t tmp = 0; // Do consecutive reads until value is correct + uint32_t state = disable_irq(); do { - seconds = tmp; - tmp = (SNVS->LPSRTCMR << 17U); - tmp |= (SNVS->LPSRTCLR >> 15U); - } while (tmp != seconds); + ticks = tmp; + tmp = (uint64_t)SNVS->LPSRTCMR << 32U; + tmp |= SNVS->LPSRTCLR; + } while (tmp != ticks); + enable_irq(state); - return seconds; + return ticks; +} + +uint64_t machine_rtc_get_seconds(void) { + return machine_rtc_get_ticks() / 32768U; +} + +// Input ticks are in units of 1/32768 seconds. +static void machine_rtc_set_ticks(uint64_t ticks) { + SNVS_LP_SRTC_StopTimer(SNVS); + SNVS->LPSRTCMR = (uint32_t)(ticks >> 32U); + SNVS->LPSRTCLR = (uint32_t)ticks; + SNVS_LP_SRTC_StartTimer(SNVS); } void machine_rtc_alarm_helper(int seconds, bool repeat) { @@ -164,18 +179,9 @@ void machine_rtc_start(void) { SNVS_LP_SRTC_StartTimer(SNVS); // If the date is not set, set it to a more recent start date, // MicroPython's first commit. - snvs_lp_srtc_datetime_t srtc_date; - SNVS_LP_SRTC_GetDatetime(SNVS, &srtc_date); - if (srtc_date.year <= 1970) { - srtc_date = (snvs_lp_srtc_datetime_t) { - .year = 2013, - .month = 10, - .day = 14, - .hour = 19, - .minute = 53, - .second = 11, - }; - SNVS_LP_SRTC_SetDatetime(SNVS, &srtc_date); + if (machine_rtc_get_ticks() < 10) { + mp_timestamp_t seconds = timeutils_seconds_since_epoch(2013, 10, 14, 19, 53, 11); + machine_rtc_set_ticks((uint64_t)seconds * 32768ULL); } } @@ -190,39 +196,34 @@ 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_helper(size_t n_args, const mp_obj_t *args, int hour_index) { if (n_args == 1) { // Get date and time. - snvs_lp_srtc_datetime_t srtc_date; - SNVS_LP_SRTC_GetDatetime(SNVS, &srtc_date); - + uint64_t ticks = machine_rtc_get_ticks(); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(ticks / 32768U, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(srtc_date.year), - mp_obj_new_int(srtc_date.month), - mp_obj_new_int(srtc_date.day), - mp_obj_new_int(timeutils_calc_weekday(srtc_date.year, srtc_date.month, srtc_date.day)), - mp_obj_new_int(srtc_date.hour), - mp_obj_new_int(srtc_date.minute), - mp_obj_new_int(srtc_date.second), - 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((ticks % 32768) * 15625U / 512U), }; return mp_obj_new_tuple(8, tuple); } else { // Set date and time. mp_obj_t *items; - mp_int_t year; mp_obj_get_array_fixed_n(args[1], 8, &items); - - snvs_lp_srtc_datetime_t srtc_date; - year = mp_obj_get_int(items[0]); - srtc_date.year = year >= 100 ? year : year + 2000; // allow 21 for 2021 - srtc_date.month = mp_obj_get_int(items[1]); - srtc_date.day = mp_obj_get_int(items[2]); - // Ignore weekday at items[3] - srtc_date.hour = mp_obj_get_int(items[hour_index]); - srtc_date.minute = mp_obj_get_int(items[hour_index + 1]); - srtc_date.second = mp_obj_get_int(items[hour_index + 2]); - if (SNVS_LP_SRTC_SetDatetime(SNVS, &srtc_date) != kStatus_Success) { - mp_raise_ValueError(NULL); - } - + 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]), + }; + uint32_t seconds = timeutils_seconds_since_epoch(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + machine_rtc_set_ticks((uint64_t)seconds * 32768ULL); return mp_const_none; } } diff --git a/ports/mimxrt/mbedtls/mbedtls_port.c b/ports/mimxrt/mbedtls/mbedtls_port.c index 230e264bf98..fa2d81001b8 100644 --- a/ports/mimxrt/mbedtls/mbedtls_port.c +++ b/ports/mimxrt/mbedtls/mbedtls_port.c @@ -30,8 +30,8 @@ #include "mbedtls_config_port.h" #if defined(MBEDTLS_HAVE_TIME) || defined(MBEDTLS_HAVE_TIME_DATE) -#include "fsl_snvs_lp.h" #include "shared/timeutils/timeutils.h" +#include "modmachine.h" #include "mbedtls/platform_time.h" #endif @@ -49,9 +49,7 @@ int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t #if defined(MBEDTLS_HAVE_TIME) time_t mimxrt_rtctime_seconds(time_t *timer) { // Get date and date in CPython order. - snvs_lp_srtc_datetime_t date; - SNVS_LP_SRTC_GetDatetime(SNVS, &date); - return timeutils_seconds_since_epoch(date.year, date.month, date.day, date.hour, date.minute, date.second); + return machine_rtc_get_seconds(); } mbedtls_ms_time_t mbedtls_ms_time(void) { diff --git a/ports/mimxrt/modmachine.h b/ports/mimxrt/modmachine.h index 34e93260f2d..398dfb22609 100644 --- a/ports/mimxrt/modmachine.h +++ b/ports/mimxrt/modmachine.h @@ -42,6 +42,8 @@ void mimxrt_sdram_init(void); void machine_i2s_init0(); void machine_i2s_deinit_all(void); void machine_rtc_start(void); +uint64_t machine_rtc_get_ticks(void); +uint64_t machine_rtc_get_seconds(void); void machine_rtc_alarm_helper(int seconds, bool repeat); void machine_uart_set_baudrate(mp_obj_t uart, uint32_t baudrate); diff --git a/ports/mimxrt/modtime.c b/ports/mimxrt/modtime.c index fe77b8a733b..4b439bb17d1 100644 --- a/ports/mimxrt/modtime.c +++ b/ports/mimxrt/modtime.c @@ -25,30 +25,17 @@ * THE SOFTWARE. */ -#include "py/obj.h" #include "shared/timeutils/timeutils.h" -#include "fsl_snvs_lp.h" +#include "modmachine.h" // Get the localtime. static void mp_time_localtime_get(timeutils_struct_time_t *tm) { // Get current date and time. - snvs_lp_srtc_datetime_t t; - SNVS_LP_SRTC_GetDatetime(SNVS, &t); - tm->tm_year = t.year; - tm->tm_mon = t.month; - tm->tm_mday = t.day; - tm->tm_hour = t.hour; - tm->tm_min = t.minute; - tm->tm_sec = t.second; - tm->tm_wday = timeutils_calc_weekday(t.year, t.month, t.day); - tm->tm_yday = timeutils_year_day(t.year, t.month, t.day); + uint64_t seconds = machine_rtc_get_seconds(); + timeutils_seconds_since_epoch_to_struct_time(seconds, tm); } // Return the number of seconds since the Epoch. static mp_obj_t mp_time_time_get(void) { - snvs_lp_srtc_datetime_t t; - SNVS_LP_SRTC_GetDatetime(SNVS, &t); - return timeutils_obj_from_timestamp( - timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.minute, t.second) - ); + return timeutils_obj_from_timestamp(machine_rtc_get_seconds()); } diff --git a/ports/mimxrt/mphalport.c b/ports/mimxrt/mphalport.c index c3802be30f5..67612229aeb 100644 --- a/ports/mimxrt/mphalport.c +++ b/ports/mimxrt/mphalport.c @@ -34,7 +34,7 @@ #include "extmod/misc.h" #include "ticks.h" #include "tusb.h" -#include "fsl_snvs_lp.h" +#include "modmachine.h" #ifndef MICROPY_HW_STDIN_BUFFER_LEN #define MICROPY_HW_STDIN_BUFFER_LEN 512 @@ -96,10 +96,12 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { } uint64_t mp_hal_time_ns(void) { - snvs_lp_srtc_datetime_t t; - SNVS_LP_SRTC_GetDatetime(SNVS, &t); - uint64_t s = timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.minute, t.second); - return s * 1000000000ULL; + uint64_t ticks = machine_rtc_get_ticks(); + // Need to compute: + // nanoseconds = ticks * 1_000_000_000 / 32768 + // = ticks * 5**9 / 64 + // but split it into upper and lower 32-bit values so the multiplication doesn't overflow. + return (((ticks >> 32U) * 1953125ULL) << 26U) + (((ticks & 0xffffffff) * 1953125ULL) >> 6U); } /*******************************************************************************/ From be15be3ab56641b532f111fd1ce4389d9bd0a539 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 24 Nov 2025 14:50:46 +1100 Subject: [PATCH 1688/2098] docs/mimxrt: Add docs for mimxrt.Flash. This adds docs for the `mimxrt` module and the `mimxrt.Flash` class, the implementation for which was first added in dfd4324eb1b758e37cfc77da8467e7f5d72f519c The docs are simple, following the conventions used for similar classes in the rp2 and stm32 ports. Signed-off-by: Andrew Leech --- docs/library/index.rst | 10 +++++++++ docs/library/mimxrt.Flash.rst | 39 +++++++++++++++++++++++++++++++++++ docs/library/mimxrt.rst | 19 +++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 docs/library/mimxrt.Flash.rst create mode 100644 docs/library/mimxrt.rst diff --git a/docs/library/index.rst b/docs/library/index.rst index 2919378ce13..e64809bfa84 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -172,6 +172,16 @@ The following libraries are specific to the ESP8266 and ESP32. espnow.rst +Libraries specific to NXP i.MXRT +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following libraries are specific to the NXP i.MXRT family of microcontrollers. + +.. toctree:: + :maxdepth: 2 + + mimxrt.rst + Libraries specific to the RP2040 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/library/mimxrt.Flash.rst b/docs/library/mimxrt.Flash.rst new file mode 100644 index 00000000000..161ef4ff2a4 --- /dev/null +++ b/docs/library/mimxrt.Flash.rst @@ -0,0 +1,39 @@ +.. currentmodule:: mimxrt +.. _mimxrt.Flash: + +class Flash -- access to built-in flash storage +=============================================== + +This class gives access to the SPI flash memory. + +In most cases, to store persistent data on the device, you'll want to use a +higher-level abstraction, for example the filesystem via Python's standard file +API, but this interface is useful to :ref:`customise the filesystem +configuration ` or implement a low-level storage system for your +application. + + +Constructors +------------ + +.. class:: Flash() + + Gets the singleton object for accessing the SPI flash memory. + + +Methods +------- + +.. method:: Flash.readblocks(block_num, buf) + Flash.readblocks(block_num, buf, offset) +.. method:: Flash.writeblocks(block_num, buf) + Flash.writeblocks(block_num, buf, offset) +.. method:: Flash.ioctl(cmd, arg) + + These methods implement the simple and extended + :ref:`block protocol ` defined by + :class:`vfs.AbstractBlockDev`. + + The block size can be queried by calling ``ioctl(5, 0)``. Block numbers + are relative to the start of the user flash storage area, not the physical + start of flash memory. diff --git a/docs/library/mimxrt.rst b/docs/library/mimxrt.rst new file mode 100644 index 00000000000..fdb4c1a1da6 --- /dev/null +++ b/docs/library/mimxrt.rst @@ -0,0 +1,19 @@ +.. currentmodule:: mimxrt + +:mod:`mimxrt` --- functionality specific to NXP i.MXRT +====================================================== + +.. module:: mimxrt + :synopsis: functionality specific to NXP i.MXRT + +The ``mimxrt`` module contains functions and classes specific to the NXP i.MXRT +family of microcontrollers. + + +Classes +------- + +.. toctree:: + :maxdepth: 1 + + mimxrt.Flash.rst From af76da96a3a68946a46ee6927f6b4570de00d1ac Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 18 Dec 2025 14:59:36 +1100 Subject: [PATCH 1689/2098] stm32: Default to --connect-under-reset if flashing via deploy-stlink. (Can be overridden by setting STFLASH variable.) Generalises the change from 6b13e6c4987c6fd103847e7b36a3f0fcd0ff42d9 to all stm32 boards. As we're resetting the target to flash it anyway, it shouldn't hurt to put the target into reset before trying to connect. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/Makefile | 2 +- ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk | 1 - ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk | 1 - ports/stm32/mboot/Makefile | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index b43f284b1ab..c0bf4be32e9 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -72,7 +72,7 @@ PYDFU ?= $(TOP)/tools/pydfu.py DFU_UTIL ?= dfu-util BOOTLOADER_DFU_USB_VID ?= 0x0483 BOOTLOADER_DFU_USB_PID ?= 0xDF11 -STFLASH ?= st-flash +STFLASH ?= st-flash --connect-under-reset OPENOCD ?= openocd OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg diff --git a/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk index 56f30c42bd3..fcb235c8688 100644 --- a/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.mk @@ -26,5 +26,4 @@ MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM = 1 FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py # Flash tool configuration -STFLASH = st-flash --connect-under-reset OPENOCD_CONFIG = boards/openocd_stm32h7_dual_bank.cfg diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk index da991c5879e..0f528939c9d 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk @@ -26,5 +26,4 @@ MICROPY_HW_ENABLE_ISR_UART_FLASH_FUNCS_IN_RAM = 1 FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py # Flash tool configuration -STFLASH = st-flash --connect-under-reset OPENOCD_CONFIG = boards/openocd_stm32h7_dual_bank.cfg diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 7226dd353f3..c049017734f 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -54,7 +54,7 @@ DFU=$(TOP)/tools/dfu.py PYDFU ?= $(TOP)/tools/pydfu.py BOOTLOADER_DFU_USB_VID ?= 0x0483 BOOTLOADER_DFU_USB_PID ?= 0xDF11 -STFLASH ?= st-flash +STFLASH ?= st-flash --connect-under-reset OPENOCD ?= openocd OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg From 96bce47762503839e554aa0bb8549fdb617361f0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 21 Oct 2025 22:25:12 +1100 Subject: [PATCH 1690/2098] tests: Factor out common helper functions to separate Python module. The test runners have evolved over time and become more and more complex. In particular `tests/run-tests.py` is rather large now. The test runners also duplicate some functionality amongst themselves. As a start to improving this situation, this commit factors out the helper functions from `run-tests.py` into a new `test_utils.py` file, and uses that new module in all test runners. There should be no functional change here. Signed-off-by: Damien George --- tests/run-internalbench.py | 21 ++- tests/run-multitests.py | 19 +- tests/run-natmodtests.py | 20 +- tests/run-perfbench.py | 20 +- tests/run-tests.py | 366 +++---------------------------------- tests/serial_test.py | 6 +- tests/test_utils.py | 358 ++++++++++++++++++++++++++++++++++++ 7 files changed, 429 insertions(+), 381 deletions(-) create mode 100644 tests/test_utils.py diff --git a/tests/run-internalbench.py b/tests/run-internalbench.py index 99c6304afe9..4f2d29525de 100755 --- a/tests/run-internalbench.py +++ b/tests/run-internalbench.py @@ -8,9 +8,14 @@ from glob import glob from collections import defaultdict -run_tests_module = __import__("run-tests") -sys.path.append(run_tests_module.base_path("../tools")) -import pyboard +from test_utils import ( + base_path, + pyboard, + test_instance_description, + test_instance_epilog, + test_directory_description, + get_test_instance, +) if os.name == "nt": MICROPYTHON = os.getenv( @@ -97,10 +102,10 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, description=f"""Run and manage tests for MicroPython. -{run_tests_module.test_instance_description} -{run_tests_module.test_directory_description} +{test_instance_description} +{test_directory_description} """, - epilog=run_tests_module.test_instance_epilog, + epilog=test_instance_epilog, ) cmd_parser.add_argument( "-t", "--test-instance", default="unix", help="the MicroPython instance to test" @@ -124,9 +129,7 @@ def main(): args = cmd_parser.parse_args() # Note pyboard support is copied over from run-tests.py, not tests, and likely needs revamping - pyb = run_tests_module.get_test_instance( - args.test_instance, args.baudrate, args.user, args.password - ) + pyb = get_test_instance(args.test_instance, args.baudrate, args.user, args.password) if len(args.files) == 0: if args.test_dirs: diff --git a/tests/run-multitests.py b/tests/run-multitests.py index e5458ffe0d0..c5bb8011e76 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -15,7 +15,13 @@ import subprocess import tempfile -run_tests_module = __import__("run-tests") +from test_utils import ( + base_path, + pyboard, + test_instance_epilog, + convert_device_shortcut_to_real_device, + create_test_report, +) test_dir = os.path.abspath(os.path.dirname(__file__)) @@ -24,9 +30,6 @@ # accidentally importing tests like micropython/const.py sys.path.pop(0) -sys.path.insert(0, test_dir + "/../tools") -import pyboard - if os.name == "nt": CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3.exe") MICROPYTHON = os.path.abspath( @@ -554,7 +557,7 @@ def main(): cmd_parser = argparse.ArgumentParser( description="Run network tests for MicroPython", epilog=( - run_tests_module.test_instance_epilog + test_instance_epilog + "Each instance arg can optionally have custom env provided, eg. ,ENV=VAR,ENV=VAR...\n" ), formatter_class=argparse.RawTextHelpFormatter, @@ -582,7 +585,7 @@ def main(): cmd_parser.add_argument( "-r", "--result-dir", - default=run_tests_module.base_path("results"), + default=base_path("results"), help="directory for test results", ) cmd_parser.add_argument("files", nargs="+", help="input test files") @@ -612,7 +615,7 @@ def main(): print("unsupported instance string: {}".format(cmd), file=sys.stderr) sys.exit(2) else: - device = run_tests_module.convert_device_shortcut_to_real_device(cmd) + device = convert_device_shortcut_to_real_device(cmd) instances_test.append(PyInstancePyboard(device)) for _ in range(max_instances - len(instances_test)): @@ -626,7 +629,7 @@ def main(): break test_results = run_tests(test_files, instances_truth, instances_test_permutation) - all_pass &= run_tests_module.create_test_report(cmd_args, test_results) + all_pass &= create_test_report(cmd_args, test_results) finally: for i in instances_truth: diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index cd6c643bf9b..cb0877e2cb0 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -9,7 +9,13 @@ import sys import argparse -run_tests_module = __import__("run-tests") +from test_utils import ( + base_path, + pyboard, + test_instance_epilog, + get_test_instance, + create_test_report, +) # Paths for host executables CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") @@ -110,7 +116,7 @@ def run_script(self, script): output = self.pyb.exec_(script) output = output.replace(b"\r\n", b"\n") return output, None - except run_tests_module.pyboard.PyboardError as er: + except pyboard.PyboardError as er: return b"", er @@ -221,7 +227,7 @@ def main(): cmd_parser = argparse.ArgumentParser( description="Run dynamic-native-module tests under MicroPython", - epilog=run_tests_module.test_instance_epilog, + epilog=test_instance_epilog, formatter_class=argparse.RawDescriptionHelpFormatter, ) cmd_parser.add_argument( @@ -243,7 +249,7 @@ def main(): cmd_parser.add_argument( "-r", "--result-dir", - default=run_tests_module.base_path("results"), + default=base_path("results"), help="directory for test results", ) cmd_parser.add_argument("files", nargs="*", help="input test files") @@ -257,9 +263,7 @@ def main(): target_truth = TargetSubprocess([CPYTHON3]) - target = run_tests_module.get_test_instance( - args.test_instance, args.baudrate, args.user, args.password - ) + target = get_test_instance(args.test_instance, args.baudrate, args.user, args.password) if target is None: # Use the unix port of MicroPython. target = TargetSubprocess([MICROPYTHON]) @@ -283,7 +287,7 @@ def main(): os.makedirs(args.result_dir, exist_ok=True) test_results = run_tests(target_truth, target, args, target_arch) - res = run_tests_module.create_test_report(args, test_results) + res = create_test_report(args, test_results) target.close() target_truth.close() diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 039d11a3611..16182bc8a9f 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -10,9 +10,13 @@ import argparse from glob import glob -run_tests_module = __import__("run-tests") - -prepare_script_for_target = run_tests_module.prepare_script_for_target +from test_utils import ( + base_path, + pyboard, + get_test_instance, + prepare_script_for_target, + create_test_report, +) # Paths for host executables if os.name == "nt": @@ -49,7 +53,7 @@ def run_script_on_target(target, script): try: target.enter_raw_repl() output = target.exec_(script) - except run_tests_module.pyboard.PyboardError as er: + except pyboard.PyboardError as er: err = er else: # Run local executable @@ -277,7 +281,7 @@ def main(): cmd_parser.add_argument( "-r", "--result-dir", - default=run_tests_module.base_path("results"), + default=base_path("results"), help="directory for test results", ) cmd_parser.add_argument( @@ -298,9 +302,7 @@ def main(): M = int(args.M[0]) n_average = int(args.average) - target = run_tests_module.get_test_instance( - args.test_instance, args.baudrate, args.user, args.password - ) + target = get_test_instance(args.test_instance, args.baudrate, args.user, args.password) if target is None: # Use the unix port of MicroPython. target = [MICROPYTHON, "-X", "emit=" + args.emit] @@ -328,7 +330,7 @@ def main(): os.makedirs(args.result_dir, exist_ok=True) test_results = run_benchmarks(args, target, N, M, n_average, tests) - res = run_tests_module.create_test_report(args, test_results) + res = create_test_report(args, test_results) if hasattr(target, "exit_raw_repl"): target.exit_raw_repl() diff --git a/tests/run-tests.py b/tests/run-tests.py index 6dfba2c540a..0ff358c06e2 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -6,7 +6,6 @@ import sysconfig import platform import argparse -import inspect import json import re from glob import glob @@ -15,23 +14,29 @@ import threading import tempfile -# Maximum time to run a single test, in seconds. -TEST_TIMEOUT = float(os.environ.get("MICROPY_TEST_TIMEOUT", 30)) - -# See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] -# are guaranteed to always work, this one should though. -BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) +from test_utils import ( + base_path, + pyboard, + TEST_TIMEOUT, + MPYCROSS, + test_instance_description, + test_instance_epilog, + test_directory_description, + rm_f, + normalize_newlines, + set_injected_prologue, + get_results_filename, + convert_device_shortcut_to_real_device, + get_test_instance, + prepare_script_for_target, + create_test_report, +) RV32_ARCH_FLAGS = { "zba": 1 << 0, "zcmp": 1 << 1, } - -def base_path(*p): - return os.path.abspath(os.path.join(BASEPATH, *p)).replace("\\", "/") - - # Tests require at least CPython 3.3. If your default python3 executable # is of lower version, you can point MICROPY_CPYTHON3 environment var # to the correct executable. @@ -40,88 +45,22 @@ def base_path(*p): MICROPYTHON = os.getenv( "MICROPY_MICROPYTHON", base_path("../ports/windows/build-standard/micropython.exe") ) - # mpy-cross is only needed if --via-mpy command-line arg is passed - MPYCROSS = os.getenv("MICROPY_MPYCROSS", base_path("../mpy-cross/build/mpy-cross.exe")) else: CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv( "MICROPY_MICROPYTHON", base_path("../ports/unix/build-standard/micropython") ) - # mpy-cross is only needed if --via-mpy command-line arg is passed - MPYCROSS = os.getenv("MICROPY_MPYCROSS", base_path("../mpy-cross/build/mpy-cross")) # Use CPython options to not save .pyc files, to only access the core standard library # (not site packages which may clash with u-module names), and improve start up time. CPYTHON3_CMD = [CPYTHON3, "-BS"] -# File with the test results. -RESULTS_FILE = "_results.json" - # For diff'ing test output DIFF = os.getenv("MICROPY_DIFF", "diff -u") # Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale os.environ["PYTHONIOENCODING"] = "utf-8" - -def normalize_newlines(data): - """Normalize newline variations to \\n. - - Only normalizes actual line endings, not literal \\r characters in strings. - Handles \\r\\r\\n and \\r\\n cases to ensure consistent comparison - across different platforms and terminals. - """ - if isinstance(data, bytes): - # Handle PTY double-newline issue first - data = data.replace(b"\r\r\n", b"\n") - # Then handle standard Windows line endings - data = data.replace(b"\r\n", b"\n") - # Don't convert standalone \r as it might be literal content - return data - - -# Code to allow a target MicroPython to import an .mpy from RAM -# Note: the module is named `__injected_test` but it needs to have `__name__` set to -# `__main__` so that the test sees itself as the main module, eg so unittest works. -injected_import_hook_code = """\ -import sys, os, io, vfs -class __File(io.IOBase): - def __init__(self): - module = sys.modules['__injected_test'] - module.__name__ = '__main__' - sys.modules['__main__'] = module - self.off = 0 - def ioctl(self, request, arg): - if request == 4: # MP_STREAM_CLOSE - return 0 - return -1 - def readinto(self, buf): - buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] - self.off += len(buf) - return len(buf) -class __FS: - def mount(self, readonly, mkfs): - pass - def umount(self): - pass - def chdir(self, path): - pass - def getcwd(self): - return "" - def stat(self, path): - if path == '__injected_test.mpy': - return (0,0,0,0,0,0,0,0,0,0) - else: - raise OSError(2) # ENOENT - def open(self, path, mode): - self.stat(path) - return __File() -vfs.mount(__FS(), '/__vfstest') -os.chdir('/__vfstest') -{import_prologue} -__import__('__injected_test') -""" - # Platforms associated with the unix port, values of `sys.platform`. PC_PLATFORMS = ("darwin", "linux", "win32") @@ -337,11 +276,6 @@ def open(self, path, mode): ) -def rm_f(fname): - if os.path.exists(fname): - os.remove(fname) - - # unescape wanted regex chars and escape unwanted ones def convert_regex_escapes(line): cs = [] @@ -366,38 +300,6 @@ def platform_to_port(platform): return platform_to_port_map.get(platform, platform) -def convert_device_shortcut_to_real_device(device): - if device.startswith("port:"): - return device.split(":", 1)[1] - elif device.startswith("a") and device[1:].isdigit(): - return "/dev/ttyACM" + device[1:] - elif device.startswith("u") and device[1:].isdigit(): - return "/dev/ttyUSB" + device[1:] - elif device.startswith("c") and device[1:].isdigit(): - return "COM" + device[1:] - else: - return device - - -def get_test_instance(test_instance, baudrate, user, password): - if test_instance == "unix": - return None - elif test_instance == "webassembly": - return PyboardNodeRunner() - else: - # Assume it's a device path. - port = convert_device_shortcut_to_real_device(test_instance) - - global pyboard - sys.path.append(base_path("../tools")) - import pyboard - - pyb = pyboard.Pyboard(port, baudrate, user, password) - pyboard.Pyboard.run_script_on_remote_target = run_script_on_remote_target - pyb.enter_raw_repl() - return pyb - - def detect_inline_asm_arch(pyb, args): for arch in ("rv32", "thumb", "xtensa"): output = run_feature_check(pyb, args, "inlineasm_{}.py".format(arch)) @@ -503,90 +405,6 @@ def detect_target_wiring_script(pyb, args): pyb.target_wiring_script = tw_data -def prepare_script_for_target(args, *, script_text=None, force_plain=False): - if force_plain or (not args.via_mpy and args.emit == "bytecode"): - # A plain test to run as-is, no processing needed. - pass - elif args.via_mpy: - tempname = tempfile.mktemp(dir="") - mpy_filename = tempname + ".mpy" - - script_filename = tempname + ".py" - with open(script_filename, "wb") as f: - f.write(script_text) - - try: - subprocess.check_output( - [MPYCROSS] - + args.mpy_cross_flags.split() - + ["-o", mpy_filename, "-X", "emit=" + args.emit, script_filename], - stderr=subprocess.STDOUT, - ) - except subprocess.CalledProcessError as er: - return True, b"mpy-cross crash\n" + er.output - - with open(mpy_filename, "rb") as f: - script_text = b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" - - rm_f(mpy_filename) - rm_f(script_filename) - - script_text += bytes(injected_import_hook_code, "ascii") - else: - print("error: using emit={} must go via .mpy".format(args.emit)) - sys.exit(1) - - return False, script_text - - -def run_script_on_remote_target(pyb, args, test_file, is_special): - with open(test_file, "rb") as f: - script = f.read() - - # If the test is not a special test, prepend it with a print to indicate that it started. - # If the print does not execute this means that the test did not even start, eg it was - # too large for the target. - prepend_start_test = not is_special - if prepend_start_test: - if script.startswith(b"#"): - script = b"print('START TEST')" + script - else: - script = b"print('START TEST')\n" + script - - had_crash, script = prepare_script_for_target(args, script_text=script, force_plain=is_special) - - if had_crash: - return True, script - - try: - had_crash = False - pyb.enter_raw_repl() - if test_file.endswith(tests_requiring_target_wiring) and pyb.target_wiring_script: - pyb.exec_( - "import sys;sys.modules['target_wiring']=__build_class__(lambda:exec(" - + repr(pyb.target_wiring_script) - + "),'target_wiring')" - ) - output_mupy = pyb.exec_(script, timeout=TEST_TIMEOUT) - except pyboard.PyboardError as e: - had_crash = True - if not is_special and e.args[0] == "exception": - if prepend_start_test and e.args[1] == b"" and b"MemoryError" in e.args[2]: - output_mupy = b"SKIP-TOO-LARGE\n" - else: - output_mupy = e.args[1] + e.args[2] + b"CRASH" - else: - output_mupy = bytes(e.args[0], "ascii") + b"\nCRASH" - - if prepend_start_test: - if output_mupy.startswith(b"START TEST\r\n"): - output_mupy = output_mupy.removeprefix(b"START TEST\r\n") - else: - had_crash = True - - return had_crash, output_mupy - - tests_with_regex_output = [ base_path(file) for file in ( @@ -722,8 +540,9 @@ def send_get(what): else: # run via pyboard interface + requires_target_wiring = test_file.endswith(tests_requiring_target_wiring) had_crash, output_mupy = pyb.run_script_on_remote_target( - args, test_file_abspath, is_special + args, test_file_abspath, is_special, requires_target_wiring ) # canonical form for all ports/platforms is to use \n for end-of-line @@ -813,51 +632,6 @@ def value(self): return self._value -class PyboardNodeRunner: - def __init__(self): - mjs = os.getenv("MICROPY_MICROPYTHON_MJS") - if mjs is None: - mjs = base_path("../ports/webassembly/build-standard/micropython.mjs") - else: - mjs = os.path.abspath(mjs) - self.micropython_mjs = mjs - - def close(self): - pass - - def run_script_on_remote_target(self, args, test_file, is_special): - cwd = os.path.dirname(test_file) - - # Create system command list. - cmdlist = ["node"] - if test_file.endswith(".py"): - # Run a Python script indirectly via "node micropython.mjs ". - cmdlist.append(self.micropython_mjs) - if args.heapsize is not None: - cmdlist.extend(["-X", "heapsize=" + args.heapsize]) - cmdlist.append(test_file) - else: - # Run a js/mjs script directly with Node, passing in the path to micropython.mjs. - cmdlist.append(test_file) - cmdlist.append(self.micropython_mjs) - - # Run the script. - try: - had_crash = False - output_mupy = subprocess.check_output( - cmdlist, stderr=subprocess.STDOUT, timeout=TEST_TIMEOUT, cwd=cwd - ) - except subprocess.CalledProcessError as er: - had_crash = True - output_mupy = er.output + b"CRASH" - except subprocess.TimeoutExpired as er: - had_crash = True - output_mupy = (er.output or b"") + b"TIMEOUT" - - # Return the results. - return had_crash, output_mupy - - def run_tests(pyb, tests, args, result_dir, num_threads=1): testcase_count = ThreadSafeCounter() test_results = ThreadSafeCounter([]) @@ -1257,70 +1031,6 @@ def run_one_test(test_file): return test_results.value, testcase_count.value -# Print a summary of the results and save them to a JSON file. -# Returns True if everything succeeded, False otherwise. -def create_test_report(args, test_results, testcase_count=None): - passed_tests = list(r for r in test_results if r[1] == "pass") - skipped_tests = list(r for r in test_results if r[1] == "skip" and r[2] != "too large") - skipped_tests_too_large = list( - r for r in test_results if r[1] == "skip" and r[2] == "too large" - ) - failed_tests = list(r for r in test_results if r[1] == "fail") - - num_tests_performed = len(passed_tests) + len(failed_tests) - - testcase_count_info = "" - if testcase_count is not None: - testcase_count_info = " ({} individual testcases)".format(testcase_count) - print("{} tests performed{}".format(num_tests_performed, testcase_count_info)) - - print("{} tests passed".format(len(passed_tests))) - - if len(skipped_tests) > 0: - print( - "{} tests skipped: {}".format( - len(skipped_tests), " ".join(test[0] for test in skipped_tests) - ) - ) - - if len(skipped_tests_too_large) > 0: - print( - "{} tests skipped because they are too large: {}".format( - len(skipped_tests_too_large), " ".join(test[0] for test in skipped_tests_too_large) - ) - ) - - if len(failed_tests) > 0: - print( - "{} tests failed: {}".format( - len(failed_tests), " ".join(test[0] for test in failed_tests) - ) - ) - - # Serialize regex added by append_filter. - def to_json(obj): - if isinstance(obj, re.Pattern): - return obj.pattern - return obj - - with open(os.path.join(args.result_dir, RESULTS_FILE), "w") as f: - json.dump( - { - # The arguments passed on the command-line. - "args": vars(args), - # A list of all results of the form [(test, result, reason), ...]. - "results": list(test for test in test_results), - # A list of failed tests. This is deprecated, use the "results" above instead. - "failed_tests": [test[0] for test in failed_tests], - }, - f, - default=to_json, - ) - - # Return True only if all tests succeeded. - return len(failed_tests) == 0 - - class append_filter(argparse.Action): def __init__(self, option_strings, dest, **kwargs): super().__init__(option_strings, dest, default=[], **kwargs) @@ -1335,39 +1045,7 @@ def __call__(self, parser, args, value, option): args.filters.append((option, re.compile(value))) -test_instance_description = """\ -By default the tests are run against the unix port of MicroPython. To run it -against something else, use the -t option. See below for details. -""" -test_instance_epilog = """\ -The -t option accepts the following for the test instance: -- unix - use the unix port of MicroPython, specified by the MICROPY_MICROPYTHON - environment variable (which defaults to the standard variant of either the unix - or windows ports, depending on the host platform) -- webassembly - use the webassembly port of MicroPython, specified by the - MICROPY_MICROPYTHON_MJS environment variable (which defaults to the standard - variant of the webassembly port) -- port: - connect to and use the given serial port device -- a - connect to and use /dev/ttyACM -- u - connect to and use /dev/ttyUSB -- c - connect to and use COM -- exec: - execute a command and attach to its stdin/stdout -- execpty: - execute a command and attach to the printed /dev/pts/ device -- ... - connect to the given IPv4 address -- anything else specifies a serial port -""" - -test_directory_description = """\ -Tests are discovered by scanning test directories for .py files or using the -specified test files. If test files nor directories are specified, the script -expects to be ran in the tests directory (where this file is located) and the -builtin tests suitable for the target platform are ran. -""" - - def main(): - global injected_import_hook_code - cmd_parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=f"""Run and manage tests for MicroPython. @@ -1474,7 +1152,7 @@ def main(): if args.begin: with open(args.begin, "rt") as source: prologue = source.read() - injected_import_hook_code = injected_import_hook_code.replace("{import_prologue}", prologue) + set_injected_prologue(prologue) if args.print_failures: for out in glob(os.path.join(args.result_dir, "*.out")): @@ -1497,7 +1175,7 @@ def main(): os.path.join(args.result_dir, "*.out") ): os.remove(f) - rm_f(os.path.join(args.result_dir, RESULTS_FILE)) + rm_f(get_results_filename(args)) sys.exit(0) @@ -1513,7 +1191,7 @@ def main(): ) if args.run_failures: - results_file = os.path.join(args.result_dir, RESULTS_FILE) + results_file = get_results_filename(args) if os.path.exists(results_file): with open(results_file, "r") as f: tests = list(test[0] for test in json.load(f)["results"] if test[1] == "fail") diff --git a/tests/serial_test.py b/tests/serial_test.py index 3b5940d91a9..eebea402fa4 100755 --- a/tests/serial_test.py +++ b/tests/serial_test.py @@ -13,7 +13,7 @@ import sys import time -run_tests_module = __import__("run-tests") +from test_utils import test_instance_epilog, convert_device_shortcut_to_real_device echo_test_script = """ import sys @@ -307,7 +307,7 @@ def main(): cmd_parser = argparse.ArgumentParser( description="Test performance and reliability of serial port communication.", - epilog=run_tests_module.test_instance_epilog, + epilog=test_instance_epilog, formatter_class=argparse.RawTextHelpFormatter, ) cmd_parser.add_argument( @@ -321,7 +321,7 @@ def main(): ) args = cmd_parser.parse_args() - dev_repl = run_tests_module.convert_device_shortcut_to_real_device(args.test_instance) + dev_repl = convert_device_shortcut_to_real_device(args.test_instance) test_passed = True try: diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000000..abbc670c125 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,358 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# The MIT License (MIT) +# Copyright (c) 2019-2025 Damien P. George + +import inspect +import json +import os +import re +import subprocess +import sys +import tempfile + +# See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] +# are guaranteed to always work, this one should though. +_BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) + + +def base_path(*p): + return os.path.abspath(os.path.join(_BASEPATH, *p)).replace("\\", "/") + + +sys.path.append(base_path("../tools")) +import pyboard + +# File with the test results. +_RESULTS_FILE = "_results.json" + +# Maximum time to run a single test, in seconds. +TEST_TIMEOUT = float(os.environ.get("MICROPY_TEST_TIMEOUT", 30)) + +# mpy-cross is only needed if --via-mpy command-line arg is passed +if os.name == "nt": + MPYCROSS = os.getenv("MICROPY_MPYCROSS", base_path("../mpy-cross/build/mpy-cross.exe")) +else: + MPYCROSS = os.getenv("MICROPY_MPYCROSS", base_path("../mpy-cross/build/mpy-cross")) + +test_instance_description = """\ +By default the tests are run against the unix port of MicroPython. To run it +against something else, use the -t option. See below for details. +""" + +test_instance_epilog = """\ +The -t option accepts the following for the test instance: +- unix - use the unix port of MicroPython, specified by the MICROPY_MICROPYTHON + environment variable (which defaults to the standard variant of either the unix + or windows ports, depending on the host platform) +- webassembly - use the webassembly port of MicroPython, specified by the + MICROPY_MICROPYTHON_MJS environment variable (which defaults to the standard + variant of the webassembly port) +- port: - connect to and use the given serial port device +- a - connect to and use /dev/ttyACM +- u - connect to and use /dev/ttyUSB +- c - connect to and use COM +- exec: - execute a command and attach to its stdin/stdout +- execpty: - execute a command and attach to the printed /dev/pts/ device +- ... - connect to the given IPv4 address +- anything else specifies a serial port +""" + +test_directory_description = """\ +Tests are discovered by scanning test directories for .py files or using the +specified test files. If test files nor directories are specified, the script +expects to be ran in the tests directory (where this file is located) and the +builtin tests suitable for the target platform are ran. +""" + +# Code to allow a target MicroPython to import an .mpy from RAM +# Note: the module is named `__injected_test` but it needs to have `__name__` set to +# `__main__` so that the test sees itself as the main module, eg so unittest works. +_injected_import_hook_code = """\ +import sys, os, io, vfs +class __File(io.IOBase): + def __init__(self): + module = sys.modules['__injected_test'] + module.__name__ = '__main__' + sys.modules['__main__'] = module + self.off = 0 + def ioctl(self, request, arg): + if request == 4: # MP_STREAM_CLOSE + return 0 + return -1 + def readinto(self, buf): + buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] + self.off += len(buf) + return len(buf) +class __FS: + def mount(self, readonly, mkfs): + pass + def umount(self): + pass + def chdir(self, path): + pass + def getcwd(self): + return "" + def stat(self, path): + if path == '__injected_test.mpy': + return (0,0,0,0,0,0,0,0,0,0) + else: + raise OSError(2) # ENOENT + def open(self, path, mode): + self.stat(path) + return __File() +vfs.mount(__FS(), '/__vfstest') +os.chdir('/__vfstest') +{import_prologue} +__import__('__injected_test') +""" + + +class PyboardNodeRunner: + def __init__(self): + mjs = os.getenv("MICROPY_MICROPYTHON_MJS") + if mjs is None: + mjs = base_path("../ports/webassembly/build-standard/micropython.mjs") + else: + mjs = os.path.abspath(mjs) + self.micropython_mjs = mjs + + def close(self): + pass + + def run_script_on_remote_target(self, args, test_file, is_special, requires_target_wiring): + cwd = os.path.dirname(test_file) + + # Create system command list. + cmdlist = ["node"] + if test_file.endswith(".py"): + # Run a Python script indirectly via "node micropython.mjs ". + cmdlist.append(self.micropython_mjs) + if args.heapsize is not None: + cmdlist.extend(["-X", "heapsize=" + args.heapsize]) + cmdlist.append(test_file) + else: + # Run a js/mjs script directly with Node, passing in the path to micropython.mjs. + cmdlist.append(test_file) + cmdlist.append(self.micropython_mjs) + + # Run the script. + try: + had_crash = False + output_mupy = subprocess.check_output( + cmdlist, stderr=subprocess.STDOUT, timeout=TEST_TIMEOUT, cwd=cwd + ) + except subprocess.CalledProcessError as er: + had_crash = True + output_mupy = er.output + b"CRASH" + except subprocess.TimeoutExpired as er: + had_crash = True + output_mupy = (er.output or b"") + b"TIMEOUT" + + # Return the results. + return had_crash, output_mupy + + +def rm_f(fname): + if os.path.exists(fname): + os.remove(fname) + + +def normalize_newlines(data): + """Normalize newline variations to \\n. + + Only normalizes actual line endings, not literal \\r characters in strings. + Handles \\r\\r\\n and \\r\\n cases to ensure consistent comparison + across different platforms and terminals. + """ + if isinstance(data, bytes): + # Handle PTY double-newline issue first + data = data.replace(b"\r\r\n", b"\n") + # Then handle standard Windows line endings + data = data.replace(b"\r\n", b"\n") + # Don't convert standalone \r as it might be literal content + return data + + +def set_injected_prologue(prologue): + global _injected_import_hook_code + _injected_import_hook_code = _injected_import_hook_code.replace("{import_prologue}", prologue) + + +def get_results_filename(args): + return os.path.join(args.result_dir, _RESULTS_FILE) + + +def convert_device_shortcut_to_real_device(device): + if device.startswith("port:"): + return device.split(":", 1)[1] + elif device.startswith("a") and device[1:].isdigit(): + return "/dev/ttyACM" + device[1:] + elif device.startswith("u") and device[1:].isdigit(): + return "/dev/ttyUSB" + device[1:] + elif device.startswith("c") and device[1:].isdigit(): + return "COM" + device[1:] + else: + return device + + +def get_test_instance(test_instance, baudrate, user, password): + if test_instance == "unix": + return None + elif test_instance == "webassembly": + return PyboardNodeRunner() + else: + # Assume it's a device path. + port = convert_device_shortcut_to_real_device(test_instance) + + pyb = pyboard.Pyboard(port, baudrate, user, password) + pyboard.Pyboard.run_script_on_remote_target = run_script_on_remote_target + pyb.enter_raw_repl() + return pyb + + +def prepare_script_for_target(args, *, script_text=None, force_plain=False): + if force_plain or (not args.via_mpy and args.emit == "bytecode"): + # A plain test to run as-is, no processing needed. + pass + elif args.via_mpy: + tempname = tempfile.mktemp(dir="") + mpy_filename = tempname + ".mpy" + + script_filename = tempname + ".py" + with open(script_filename, "wb") as f: + f.write(script_text) + + try: + subprocess.check_output( + [MPYCROSS] + + args.mpy_cross_flags.split() + + ["-o", mpy_filename, "-X", "emit=" + args.emit, script_filename], + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as er: + return True, b"mpy-cross crash\n" + er.output + + with open(mpy_filename, "rb") as f: + script_text = b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" + + rm_f(mpy_filename) + rm_f(script_filename) + + script_text += bytes(_injected_import_hook_code, "ascii") + else: + print("error: using emit={} must go via .mpy".format(args.emit)) + sys.exit(1) + + return False, script_text + + +def run_script_on_remote_target(pyb, args, test_file, is_special, requires_target_wiring): + with open(test_file, "rb") as f: + script = f.read() + + # If the test is not a special test, prepend it with a print to indicate that it started. + # If the print does not execute this means that the test did not even start, eg it was + # too large for the target. + prepend_start_test = not is_special + if prepend_start_test: + if script.startswith(b"#"): + script = b"print('START TEST')" + script + else: + script = b"print('START TEST')\n" + script + + had_crash, script = prepare_script_for_target(args, script_text=script, force_plain=is_special) + + if had_crash: + return True, script + + try: + had_crash = False + pyb.enter_raw_repl() + if requires_target_wiring and pyb.target_wiring_script: + pyb.exec_( + "import sys;sys.modules['target_wiring']=__build_class__(lambda:exec(" + + repr(pyb.target_wiring_script) + + "),'target_wiring')" + ) + output_mupy = pyb.exec_(script, timeout=TEST_TIMEOUT) + except pyboard.PyboardError as e: + had_crash = True + if not is_special and e.args[0] == "exception": + if prepend_start_test and e.args[1] == b"" and b"MemoryError" in e.args[2]: + output_mupy = b"SKIP-TOO-LARGE\n" + else: + output_mupy = e.args[1] + e.args[2] + b"CRASH" + else: + output_mupy = bytes(e.args[0], "ascii") + b"\nCRASH" + + if prepend_start_test: + if output_mupy.startswith(b"START TEST\r\n"): + output_mupy = output_mupy.removeprefix(b"START TEST\r\n") + else: + had_crash = True + + return had_crash, output_mupy + + +# Print a summary of the results and save them to a JSON file. +# Returns True if everything succeeded, False otherwise. +def create_test_report(args, test_results, testcase_count=None): + passed_tests = list(r for r in test_results if r[1] == "pass") + skipped_tests = list(r for r in test_results if r[1] == "skip" and r[2] != "too large") + skipped_tests_too_large = list( + r for r in test_results if r[1] == "skip" and r[2] == "too large" + ) + failed_tests = list(r for r in test_results if r[1] == "fail") + + num_tests_performed = len(passed_tests) + len(failed_tests) + + testcase_count_info = "" + if testcase_count is not None: + testcase_count_info = " ({} individual testcases)".format(testcase_count) + print("{} tests performed{}".format(num_tests_performed, testcase_count_info)) + + print("{} tests passed".format(len(passed_tests))) + + if len(skipped_tests) > 0: + print( + "{} tests skipped: {}".format( + len(skipped_tests), " ".join(test[0] for test in skipped_tests) + ) + ) + + if len(skipped_tests_too_large) > 0: + print( + "{} tests skipped because they are too large: {}".format( + len(skipped_tests_too_large), " ".join(test[0] for test in skipped_tests_too_large) + ) + ) + + if len(failed_tests) > 0: + print( + "{} tests failed: {}".format( + len(failed_tests), " ".join(test[0] for test in failed_tests) + ) + ) + + # Serialize regex added by append_filter. + def to_json(obj): + if isinstance(obj, re.Pattern): + return obj.pattern + return obj + + with open(get_results_filename(args), "w") as f: + json.dump( + { + # The arguments passed on the command-line. + "args": vars(args), + # A list of all results of the form [(test, result, reason), ...]. + "results": list(test for test in test_results), + # A list of failed tests. This is deprecated, use the "results" above instead. + "failed_tests": [test[0] for test in failed_tests], + }, + f, + default=to_json, + ) + + # Return True only if all tests succeeded. + return len(failed_tests) == 0 From 0e3cc2910d250b51f2c22ef32c2be73f049696da Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 16 Oct 2025 15:21:28 -0600 Subject: [PATCH 1691/2098] mimxrt: Add PSRAM implementation. This commit adds PSRAM support for Teensy 4.1 and other mimxrt boards. It's enabled by default for Teensy 4.1. This implementation is based on the Teensy Arduino core PSRAM code, and Paul Stoffregen has agreed for it to be published here under the MIT license, see https://github.com/micropython/micropython/pull/18288#issuecomment-3806784632 This addresses issue #18281. Signed-off-by: Dryw Wade --- ports/mimxrt/Makefile | 1 + ports/mimxrt/boards/TEENSY41/mpconfigboard.h | 3 + ports/mimxrt/main.c | 18 ++ ports/mimxrt/mpconfigport.h | 9 +- ports/mimxrt/psram.c | 265 +++++++++++++++++++ ports/mimxrt/psram.h | 36 +++ 6 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 ports/mimxrt/psram.c create mode 100644 ports/mimxrt/psram.h diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 08f99d68b53..576f6e64ac1 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -266,6 +266,7 @@ SRC_C += \ network_lan.c \ pendsv.c \ pin.c \ + psram.c \ sdcard.c \ sdio.c \ systick.c \ diff --git a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h index bff319c6d15..2eeac0dab00 100644 --- a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h +++ b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h @@ -136,3 +136,6 @@ { IOMUXC_GPIO_B1_11_ENET_RX_ER, 0, 0xB0E9u }, \ { IOMUXC_GPIO_B1_15_ENET_MDIO, 0, 0xB0E9u }, \ { IOMUXC_GPIO_B1_14_ENET_MDC, 0, 0xB0E9u }, + +// Enable PSRAM support +#define MICROPY_HW_ENABLE_PSRAM (1) diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index dcb1ede1670..9f3d47f8cc2 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -37,6 +37,7 @@ #include "ticks.h" #include "led.h" #include "pendsv.h" +#include "psram.h" #include "modmachine.h" #include "modmimxrt.h" @@ -67,6 +68,10 @@ int main(void) { ticks_init(); pendsv_init(); + #if MICROPY_HW_ENABLE_PSRAM + size_t psram_size = configure_external_ram(); + #endif + #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function // because the system timeout list (next_timeout) is only ever reset by BSS clearing. @@ -101,7 +106,20 @@ int main(void) { mp_cstack_init_with_top(&_estack, &_estack - &_sstack); + #if MICROPY_HW_ENABLE_PSRAM + if (psram_size) { + #if MICROPY_GC_SPLIT_HEAP + gc_init(&_gc_heap_start, &_gc_heap_end); + gc_add((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #else + gc_init((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #endif + } else { + gc_init(&_gc_heap_start, &_gc_heap_end); + } + #else gc_init(&_gc_heap_start, &_gc_heap_end); + #endif mp_init(); #if MICROPY_PY_NETWORK diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 52289549d7c..871713e1cb2 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -35,13 +35,20 @@ uint32_t trng_random_u32(void); // Config level #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) +#ifndef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_HW_ENABLE_PSRAM (0) +#endif + // Memory allocation policies -#if MICROPY_HW_SDRAM_AVAIL +#if MICROPY_HW_SDRAM_AVAIL || MICROPY_HW_ENABLE_PSRAM #define MICROPY_GC_STACK_ENTRY_TYPE uint32_t #else #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #endif #define MICROPY_ALLOC_PATH_MAX (256) +#ifndef MICROPY_GC_SPLIT_HEAP +#define MICROPY_GC_SPLIT_HEAP MICROPY_HW_ENABLE_PSRAM +#endif // MicroPython emitters #define MICROPY_PERSISTENT_CODE_LOAD (1) diff --git a/ports/mimxrt/psram.c b/ports/mimxrt/psram.c new file mode 100644 index 00000000000..f19d4f1d265 --- /dev/null +++ b/ports/mimxrt/psram.c @@ -0,0 +1,265 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Paul Stoffregen + * 2026 Dryw Wade + * + * 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. + */ + +// This implementation is adapted from here: +// https://github.com/PaulStoffregen/cores/blob/10025393e83ca9f4dc5646643a41cb2f32022ae4/teensy4/startup.c#L421-L615 +#include "py/mphal.h" +#include "fsl_flexspi.h" + +#if MICROPY_HW_ENABLE_PSRAM + +/*! + * @brief Clock divider value. + * + * See https://www.pjrc.com/teensy/IMXRT1060RM_rev3_annotations.pdf (p1010 and p1050) + */ +typedef enum _clock_mux_value { + kCLOCK_Flexspi2Mux_396MHz = 0U, /*!< FLEXSPI2 clock source is PLL2 PFD2. */ + kCLOCK_Flexspi2Mux_720MHz = 1U, /*!< FLEXSPI2 clock source is PLL3 PFD0. */ + kCLOCK_Flexspi2Mux_664_62MHz = 2U, /*!< FLEXSPI2 clock source is PLL3 PFD1. */ + kCLOCK_Flexspi2Mux_528MHz = 3U, /*!< FLEXSPI2 clock source is PLL2 (pll2_main_clk). */ +} clock_mux_value_t; + +static void flexspi2_command(uint32_t index, uint32_t addr, flexspi_port_t port) { + flexspi_transfer_t xfer = { + .deviceAddress = addr, + .port = port, + .cmdType = kFLEXSPI_Command, + .seqIndex = index, + .SeqNumber = 1, + .data = NULL, + .dataSize = 0, + }; + FLEXSPI_TransferBlocking(FLEXSPI2, &xfer); + FLEXSPI_ClearInterruptStatusFlags(FLEXSPI2, kFLEXSPI_IpCommandExecutionDoneFlag); +} + +static uint32_t flexspi2_psram_id(uint32_t addr, flexspi_port_t port) { + uint32_t id = 0; + flexspi_transfer_t xfer = { + .deviceAddress = addr, + .port = port, + .cmdType = kFLEXSPI_Read, + .seqIndex = 3, + .SeqNumber = 1, + .data = &id, + .dataSize = 4, + }; + FLEXSPI_TransferBlocking(FLEXSPI2, &xfer); + FLEXSPI_ClearInterruptStatusFlags(FLEXSPI2, + kFLEXSPI_IpCommandExecutionDoneFlag | kFLEXSPI_IpRxFifoWatermarkAvailableFlag); + return id; +} + +/** + * \return size of PSRAM in MBytes, or 0 if not present + */ +static uint8_t flexspi2_psram_size(uint32_t addr, flexspi_port_t port) { + uint8_t result = 0; // assume we don't have PSRAM at this address + flexspi2_command(0, addr, port); // exit quad mode + flexspi2_command(1, addr, port); // reset enable + flexspi2_command(2, addr, port); // reset (is this really necessary?) + uint32_t id = flexspi2_psram_id(addr, port); + + switch (id & 0xFFFF) + { + default: + break; + + case 0x5D0D: // AP / Ipus / ESP / Lyontek + result = 8; + break; + + case 0x5D9D: // ISSI + switch ((id >> 21) & 0x7) // get size (Datasheet Table 6.2) + { + case 0b011: + result = 8; + break; + case 0b100: + result = 16; + break; + } + break; + } + + return result; +} + +size_t configure_external_ram() { + // initialize pins + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_22] = 0x1B0F9; // 100K pullup, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_23] = 0x110F9; // keeper, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_24] = 0x1B0F9; // 100K pullup, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_25] = 0x100F9; // strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_26] = 0x170F9; // 47K pullup, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_27] = 0x170F9; // 47K pullup, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_28] = 0x170F9; // 47K pullup, strong drive, max speed, hyst + IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_29] = 0x170F9; // 47K pullup, strong drive, max speed, hyst + + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_22] = 8 | 0x10; // ALT1 = FLEXSPI2_A_SS1_B (Flash) + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_23] = 8 | 0x10; // ALT1 = FLEXSPI2_A_DQS + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_24] = 8 | 0x10; // ALT1 = FLEXSPI2_A_SS0_B (RAM) + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_25] = 8 | 0x10; // ALT1 = FLEXSPI2_A_SCLK + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_26] = 8 | 0x10; // ALT1 = FLEXSPI2_A_DATA0 + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_27] = 8 | 0x10; // ALT1 = FLEXSPI2_A_DATA1 + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_28] = 8 | 0x10; // ALT1 = FLEXSPI2_A_DATA2 + IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_29] = 8 | 0x10; // ALT1 = FLEXSPI2_A_DATA3 + + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_DQS_FA_SELECT_INPUT] = 1; // GPIO_EMC_23 for Mode: ALT8, pg 986 + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_IO_FA_BIT0_SELECT_INPUT] = 1; // GPIO_EMC_26 for Mode: ALT8 + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_IO_FA_BIT1_SELECT_INPUT] = 1; // GPIO_EMC_27 for Mode: ALT8 + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_IO_FA_BIT2_SELECT_INPUT] = 1; // GPIO_EMC_28 for Mode: ALT8 + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_IO_FA_BIT3_SELECT_INPUT] = 1; // GPIO_EMC_29 for Mode: ALT8 + IOMUXC->SELECT_INPUT_1[kIOMUXC_FLEXSPI2_IPP_IND_SCK_FA_SELECT_INPUT] = 1; // GPIO_EMC_25 for Mode: ALT8 + + // turn on clock (QSPI flash & PSRAM chips usually spec max clock 100 to 133 MHz) + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy6); // 88.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_528MHz); // 88.0 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy4); // 99.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_396MHz); // 99.0 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy7); // 102.9 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_720MHz); // 102.9 MHz + CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy5); // 105.6 MHz + CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_528MHz); // 105.6 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy6); // 110.8 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_664_62MHz); // 110.8 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy6); // 120.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_720MHz); // 120.0 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy4); // 132.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_528MHz); // 132.0 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy5); // 144.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_720MHz); // 144.0 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy4); // 166.2 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_664_62MHz); // 166.2 MHz + // CLOCK_SetDiv(kCLOCK_Flexspi2Div, kCLOCK_Flexspi2DivBy3); // 176.0 MHz + // CLOCK_SetMux(kCLOCK_Flexspi2Mux, kCLOCK_Flexspi2Mux_528MHz); // 176.0 MHz + + flexspi_config_t flexspi_config = { + .rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad, + .enableSckFreeRunning = false, + .enableCombination = false, + .enableDoze = false, + .enableHalfSpeedAccess = false, + .enableSckBDiffOpt = false, + .enableSameConfigForAll = false, + .seqTimeoutCycle = 0xFFFF, + .ipGrantTimeoutCycle = 0xFF, + .txWatermark = 0, + .rxWatermark = 0, + .ahbConfig = { + .enableAHBWriteIpTxFifo = false, + .enableAHBWriteIpRxFifo = false, + .ahbGrantTimeoutCycle = 0xFF, + .ahbBusTimeoutCycle = 0xFFFF, + .resumeWaitCycle = 0x20, + .buffer = { + {.priority = 0, .masterIndex = 0, .bufferSize = 512, .enablePrefetch = true}, + {.priority = 0, .masterIndex = 0, .bufferSize = 512, .enablePrefetch = true}, + {.priority = 0, .masterIndex = 0, .bufferSize = 0, .enablePrefetch = false}, + {.priority = 0, .masterIndex = 0, .bufferSize = 0, .enablePrefetch = false}, + }, + .enableClearAHBBufferOpt = false, + .enableReadAddressOpt = false, + .enableAHBPrefetch = false, + .enableAHBBufferable = false, + .enableAHBCachable = false, + }, + }; + FLEXSPI_Init(FLEXSPI2, &flexspi_config); + + FLEXSPI_DisableInterrupts(FLEXSPI2, kFLEXSPI_AllInterruptFlags); + + flexspi_device_config_t flexspi_device_config = { + .flexspiRootClk = 0, + .isSck2Enabled = false, + .flashSize = 1 << 16, // Default value, will be updated later + .CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle, + .CSInterval = 0, + .CSHoldTime = 1, + .CSSetupTime = 1, + .dataValidTime = 0, + .columnspace = 0, + .enableWordAddress = false, + .AWRSeqIndex = 6, + .AWRSeqNumber = 1, + .ARDSeqIndex = 5, + .ARDSeqNumber = 1, + .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle, + .AHBWriteWaitInterval = 0, + .enableWriteMask = false, + }; + FLEXSPI_SetFlashConfig(FLEXSPI2, &flexspi_device_config, kFLEXSPI_PortA1); + FLEXSPI_SetFlashConfig(FLEXSPI2, &flexspi_device_config, kFLEXSPI_PortA2); + + uint32_t cmd[64] = {0}; + // cmd index 0 = exit QPI mode + cmd[0] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0xF5, 0, 0, 0); + // cmd index 1 = reset enable + cmd[4] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x66, 0, 0, 0); + // cmd index 2 = reset + cmd[8] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x99, 0, 0, 0); + // cmd index 3 = read ID bytes + cmd[12] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x9F, kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 24); + cmd[13] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 1, 0, 0, 0); + // cmd index 4 = enter QPI mode + cmd[16] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35, 0, 0, 0); + // cmd index 5 = read QPI + cmd[20] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 24); + cmd[21] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 6, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 1); + // cmd index 6 = write QPI + cmd[24] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0x38, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 24); + cmd[25] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 1, 0, 0, 0); + FLEXSPI_UpdateLUT(FLEXSPI2, 0, cmd, 64); + + // Detected PSRAM size in MB + uint8_t external_psram_size = 0; + + // look for the first PSRAM chip + uint8_t size1 = flexspi2_psram_size(0, kFLEXSPI_PortA1); + if (size1 > 0) { + flexspi_device_config.flashSize = size1 << 10; + FLEXSPI_SetFlashConfig(FLEXSPI2, &flexspi_device_config, kFLEXSPI_PortA1); + flexspi2_command(4, 0, kFLEXSPI_PortA1); // enter QPI mode + // look for a second PSRAM chip + uint8_t size2 = flexspi2_psram_size(size1 << 20, kFLEXSPI_PortA2); + external_psram_size = size1 + size2; + if (size2 > 0) { + flexspi_device_config.flashSize = size2 << 10; + FLEXSPI_SetFlashConfig(FLEXSPI2, &flexspi_device_config, kFLEXSPI_PortA2); + flexspi2_command(4, size1 << 20, kFLEXSPI_PortA2); // enter QPI mode + } + } else { + // No PSRAM + external_psram_size = 0; + } + + // Return the size of the PSRAM in bytes + return external_psram_size * 0x100000; +} + +#endif diff --git a/ports/mimxrt/psram.h b/ports/mimxrt/psram.h new file mode 100644 index 00000000000..e5ecd5aa164 --- /dev/null +++ b/ports/mimxrt/psram.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Paul Stoffregen + * 2026 Dryw Wade + * + * 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_MIMXRT_PSRAM_H +#define MICROPY_INCLUDED_MIMXRT_PSRAM_H + +#define PSRAM_BASE (0x70000000) + +// Configures external PSRAM if available, returns size in bytes (0 if none). +size_t configure_external_ram(); + +#endif // MICROPY_INCLUDED_MIMXRT_PSRAM_H From b0feb9c7ee54cdf87d69a205fd804d76e65f9bb4 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 17 Oct 2025 15:44:37 -0600 Subject: [PATCH 1692/2098] mimxrt/Makefile: Add CXXFLAGS, and libstdc++ to LDFLAGS. This allows user C modules to be built into the mimxrt port. The change is copied from `samd/Makefile`. Fixes issue #18292. Signed-off-by: Dryw Wade --- ports/mimxrt/Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 576f6e64ac1..2c1b41fcde9 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -437,6 +437,15 @@ CFLAGS += \ -Wfloat-conversion \ -Wno-error=unused-parameter +# Flags for optional C++ source code +CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) + +# TODO make this common -- shouldn't be using these "private" vars from py.mk +ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) +LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" +LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" +endif + # Configure respective board flash type # Add hal/flexspi_nor_flash.h or hal/flexspi_hyper_flash.h respectively CFLAGS += -DBOARD_FLASH_OPS_HEADER_H=\"hal/flexspi_$(subst qspi_,,$(FLEXSPI_FLASH_TYPE)).h\" From cf048e0e8ed4ccdd54925d09a9d74cf9d1ec0703 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 1 Apr 2024 16:13:15 +0800 Subject: [PATCH 1693/2098] lib/nxp_driver: Update mimxrt SDK to MCUX_2.16.100. Signed-off-by: Andrew Leech --- lib/nxp_driver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nxp_driver b/lib/nxp_driver index 91b04b34a59..a298e8a3cad 160000 --- a/lib/nxp_driver +++ b/lib/nxp_driver @@ -1 +1 @@ -Subproject commit 91b04b34a59f6d81661cec6f84611afe6330ce92 +Subproject commit a298e8a3cad2df737de020bbeac4ee2147d189ca From 0e9e66100e99c74caf4aa65f95bc09ba1e77b45c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 14 Nov 2024 21:55:01 +1100 Subject: [PATCH 1694/2098] mimxrt/machine_uart: Use a wrapper function to handle IRQ idle support. MicroPython needs to handle the UART idle interrupt, which is not something that the standard SDK provides. This was previously fixed by including in the mimxrt port a modified copy of `fsl_lpuart.c`. This commit changes how that's done by building the unmodified `fsl_lpuart.c` directly from the SDK, and using linker wrapping to override the necessary functions to get at the idle IRQ. As part of this, the code is updated to work with the latest SDK. Signed-off-by: Andrew Leech --- ports/mimxrt/Makefile | 9 +- ports/mimxrt/hal/fsl_lpuart.c | 2013 --------------------------------- ports/mimxrt/machine_uart.c | 72 ++ 3 files changed, 79 insertions(+), 2015 deletions(-) delete mode 100644 ports/mimxrt/hal/fsl_lpuart.c diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 2c1b41fcde9..d2109e2a3be 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -137,6 +137,7 @@ SRC_HAL_IMX_C += \ $(MCUX_SDK_DIR)/drivers/lpi2c/fsl_lpi2c.c \ $(MCUX_SDK_DIR)/drivers/lpspi/fsl_lpspi.c \ $(MCUX_SDK_DIR)/drivers/lpspi/fsl_lpspi_edma.c \ + $(MCUX_SDK_DIR)/drivers/lpuart/fsl_lpuart.c \ $(MCUX_SDK_DIR)/drivers/pit/fsl_pit.c \ $(MCUX_SDK_DIR)/drivers/pwm/fsl_pwm.c \ $(MCUX_SDK_DIR)/drivers/sai/fsl_sai.c \ @@ -152,6 +153,10 @@ else SRC_HAL_IMX_C += $(MCU_DIR)/xip/fsl_flexspi_nor_boot.c endif +# UART IRQ wrapper for UART.IRQ_RXIDLE support (see machine_uart.c for implementation details) +# Double wrapping is required because SDK stores function pointers in s_lpuartIsr[] dispatch table +LDFLAGS += --wrap=LPUART_TransferCreateHandle --wrap=LPUART_TransferHandleIRQ + INC_HAL_IMX += \ -I$(TOP)/$(MCU_DIR) \ -I$(TOP)/$(MCU_DIR)/drivers \ @@ -244,7 +249,6 @@ SRC_C += \ eth.c \ fatfs_port.c \ flash.c \ - hal/fsl_lpuart.c \ hal/pwm_backport.c \ help.c \ led.c \ @@ -505,7 +509,8 @@ LDFLAGS += \ --cref \ --gc-sections \ --print-memory-usage \ - -Map=$@.map + -Map=$@.map \ + --wrap=LPUART_TransferCreateHandle --wrap=LPUART_TransferHandleIRQ # LDDEFINES are used for link time adaptation of linker scripts, utilizing # the C preprocessor. Therefore keep LDDEFINES separated from LDFLAGS! diff --git a/ports/mimxrt/hal/fsl_lpuart.c b/ports/mimxrt/hal/fsl_lpuart.c deleted file mode 100644 index 54651c3efbc..00000000000 --- a/ports/mimxrt/hal/fsl_lpuart.c +++ /dev/null @@ -1,2013 +0,0 @@ -/* - * 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 107af72297d..ab5f9b76aa0 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -638,3 +638,75 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint } return ret; } + +// ============================================================================= +// LPUART IRQ Handler Wrapper for UART.IRQ_RXIDLE Support +// ============================================================================= +// +// Problem: SDK 2.16's LPUART_TransferHandleIDLEReady() only invokes the idle line callback +// when rxDataSize != 0. MicroPython uses ring buffer mode where rxDataSize is always 0, +// preventing IRQ_RXIDLE from ever firing. +// +// Solution: Use linker wrapping (see Makefile LDFLAGS) to intercept the IRQ handler and +// handle the idle line interrupt before the SDK processes it. +// +// Why double wrapping is needed: +// - The SDK's LPUART_TransferCreateHandle stores the address of LPUART_TransferHandleIRQ +// into the s_lpuartIsr[] dispatch table (not a direct call). +// - GCC's --wrap flag only intercepts direct function calls, not address-of operations. +// - Therefore we must also wrap CreateHandle to inject our IRQ wrapper's address. +// +// See Makefile: LDFLAGS += --wrap=LPUART_TransferCreateHandle --wrap=LPUART_TransferHandleIRQ +// + +// Linker wrapper declarations - these are provided by --wrap linkage +extern void __real_LPUART_TransferCreateHandle(LPUART_Type *base, lpuart_handle_t *handle, + lpuart_transfer_callback_t callback, void *userData); +extern void __real_LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle); + +// Forward declaration of our IRQ wrapper (defined below) +void __wrap_LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle); + +// SDK's ISR dispatch table - defined in lib/nxp_driver/sdk/drivers/lpuart/fsl_lpuart.c +extern lpuart_isr_t s_lpuartIsr[]; + +// Wrapper for LPUART_TransferCreateHandle to inject our IRQ wrapper into SDK's dispatch table. +// This is called instead of the SDK's function due to --wrap=LPUART_TransferCreateHandle in Makefile. +// After the SDK initializes, we replace s_lpuartIsr[instance] with our wrapper's address. +void __wrap_LPUART_TransferCreateHandle(LPUART_Type *base, lpuart_handle_t *handle, + lpuart_transfer_callback_t callback, void *userData) { + // Call the real SDK function to perform normal initialization + __real_LPUART_TransferCreateHandle(base, handle, callback, userData); + + // Override the ISR dispatch table entry with our wrapper's address + // (SDK stored __real_LPUART_TransferHandleIRQ, we want __wrap_LPUART_TransferHandleIRQ) + uint32_t instance = LPUART_GetInstance(base); + s_lpuartIsr[instance] = __wrap_LPUART_TransferHandleIRQ; +} + +// Wrapper for LPUART_TransferHandleIRQ to handle UART.IRQ_RXIDLE in ring buffer mode. +// This is installed into s_lpuartIsr[] by __wrap_LPUART_TransferCreateHandle above. +// Processes the IDLE line interrupt and invokes the MicroPython callback unconditionally, +// then calls the SDK's handler for remaining interrupt processing. +void __wrap_LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle) { + uint32_t status = LPUART_GetStatusFlags(base); + uint32_t enabledInterrupts = LPUART_GetEnabledInterrupts(base); + lpuart_handle_t *handle = (lpuart_handle_t *)irqHandle; + + // Check if IDLE flag is set and IDLE interrupt is enabled + if ((0U != ((uint32_t)kLPUART_IdleLineFlag & status)) && + (0U != ((uint32_t)kLPUART_IdleLineInterruptEnable & enabledInterrupts))) { + // Clear IDLE flag to prevent SDK's handler from seeing it + // (SDK would disable the interrupt due to rxDataSize == 0) + LPUART_ClearStatusFlags(base, kLPUART_IdleLineFlag); + // Invoke MicroPython's idle handler callback + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_IdleLineDetected, handle->userData); + } else { + /* Avoid MISRA 15.7 */ + } + } + + // Call SDK's handler for all other interrupt processing + __real_LPUART_TransferHandleIRQ(base, irqHandle); +} From 1046b5dc9901945c2fa42eb65cdef2ad75ae0110 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 30 Jan 2026 09:57:36 -0700 Subject: [PATCH 1695/2098] mimxrt/Makefile: Make board linker scripts configurable. With this change, can simply add `LD_FILES` to a board's `mpconfigboard.mk` to change the linker scripts, such as to change the section sizes, or change the contents for each region. Example usage: LD_FILES = $(BOARD_DIR)/my_board.ld boards/common.ld Signed-off-by: Dryw Wade --- ports/mimxrt/Makefile | 4 ++-- ports/mimxrt/boards/make-flexram-config.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index d2109e2a3be..9694464b5b8 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -56,7 +56,7 @@ MCUX_SDK_DIR = lib/nxp_driver/sdk MCU_DIR = $(MCUX_SDK_DIR)/devices/$(MCU_SERIES) # Select linker scripts based on MCU_SERIES -LD_FILES = boards/$(MCU_SERIES).ld boards/common.ld +LD_FILES ?= boards/$(MCU_SERIES).ld boards/common.ld # Parameter configurations for generation AF_FILE = boards/$(MCU_SERIES)_af.csv @@ -595,7 +595,7 @@ $(HEADER_BUILD)/qstrdefs.generated.h: $(BOARD_DIR)/mpconfigboard.h $(GEN_FLEXRAM_CONFIG_SRC): $(HEADER_BUILD) $(ECHO) "Create $@" $(Q)$(PYTHON) $(MAKE_FLEXRAM_LD) -d $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE).h \ - -f $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE)_features.h -l boards/$(MCU_SERIES).ld -c $(MCU_SERIES) > $@ + -f $(TOP)/$(MCU_DIR)/$(MCU_SERIES)$(MCU_CORE)_features.h $(addprefix -l,$(LD_FILES)) -c $(MCU_SERIES) > $@ # Use a pattern rule here so that make will only call make-pins.py once to make # both pins_gen.c and pins.h diff --git a/ports/mimxrt/boards/make-flexram-config.py b/ports/mimxrt/boards/make-flexram-config.py index 4a80fb1d247..f518746c979 100644 --- a/ports/mimxrt/boards/make-flexram-config.py +++ b/ports/mimxrt/boards/make-flexram-config.py @@ -55,9 +55,14 @@ # Value parser -def mimxrt_default_parser(defines_file, features_file, ld_script): - with open(ld_script, "r") as input_file: - input_str = input_file.read() +def mimxrt_default_parser(defines_file, features_file, ld_scripts): + input_str = "" + if ld_scripts is None: + # Default linker script + ld_scripts = ["boards/MIMXRT1021.ld"] + for ld_script in ld_scripts: + with open(ld_script, "r") as input_file: + input_str += input_file.read() # ocram_match = re.search(ocram_regex, input_str, re.MULTILINE) dtcm_match = re.search(dtcm_regex, input_str, re.MULTILINE) @@ -249,7 +254,9 @@ def main(defines_file, features_file, ld_script, controller): "--ld_file", dest="linker_file", help="Path to the aggregated linker-script", - default="MIMXRT1021.ld", + action="append", + # Can't specify default here when using 'append' action, see + # https://github.com/python/cpython/issues/60603 ) parser.add_argument( "-c", "--controller", dest="controller", help="Controller name", default="MIMXRT1021" From 023a49c55ed5f3ebabab8969013533dfbdfdff33 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 11 Nov 2025 13:54:20 +1100 Subject: [PATCH 1696/2098] mimxrt/boards/PHYBOARD_RT1170: Add PHYBOARD-RT1170 board support. Adds board support for PHYTEC phyBOARD-RT1170 Development Kit featuring MIMXRT1176 dual-core (Cortex-M7/M4), 64MB SDRAM, 16MB QSPI Flash, dual Gigabit Ethernet (DP83867 RGMII + KSZ8081 RMII), USB 2.0, MIPI-DSI/CSI, audio codec, CAN FD, RS-232, microSD, and M.2 Key E connector. Signed-off-by: Andrew Leech --- .../mimxrt/boards/PHYBOARD_RT1170/board.json | 28 ++ .../mimxrt/boards/PHYBOARD_RT1170/manifest.py | 3 + .../boards/PHYBOARD_RT1170/mpconfigboard.h | 308 ++++++++++++++++++ .../boards/PHYBOARD_RT1170/mpconfigboard.mk | 31 ++ ports/mimxrt/boards/PHYBOARD_RT1170/pins.csv | 127 ++++++++ pyproject.toml | 2 +- 6 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 ports/mimxrt/boards/PHYBOARD_RT1170/board.json create mode 100644 ports/mimxrt/boards/PHYBOARD_RT1170/manifest.py create mode 100644 ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h create mode 100644 ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.mk create mode 100644 ports/mimxrt/boards/PHYBOARD_RT1170/pins.csv diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/board.json b/ports/mimxrt/boards/PHYBOARD_RT1170/board.json new file mode 100644 index 00000000000..6eaafe1f488 --- /dev/null +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/board.json @@ -0,0 +1,28 @@ +{ + "deploy": [ + "../deploy_mimxrt.md" + ], + "docs": "", + "features": [ + "Audio Codec", + "CAN", + "Camera", + "Display", + "Dual-core", + "Ethernet", + "External Flash", + "External RAM", + "IMU", + "RGB LED", + "USB", + "microSD" + ], + "images": [ + "phyBOARD-RT1170_front.png" + ], + "mcu": "mimxrt", + "product": "phyBOARD-RT1170 Development Kit", + "thumbnail": "", + "url": "https://www.phytec.com/product/phyboard-rt1170-development-kit/", + "vendor": "PHYTEC" +} diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/manifest.py b/ports/mimxrt/boards/PHYBOARD_RT1170/manifest.py new file mode 100644 index 00000000000..59866772206 --- /dev/null +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/manifest.py @@ -0,0 +1,3 @@ +include("../manifest.py") +require("bundle-networking") +include("$(MPY_DIR)/extmod/asyncio/manifest.py") diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h new file mode 100644 index 00000000000..3cb49ab2cc1 --- /dev/null +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h @@ -0,0 +1,308 @@ +#define MICROPY_HW_BOARD_NAME "phyBOARD-RT1170 Development Kit" +#define MICROPY_HW_MCU_NAME "MIMXRT1176DVMAA" + +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-phyboard" + +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + } while (0); + +// phyBOARD-RT1170 SoM onboard LEDs (red and green from phyCORE SoM) +// Carrier board provides additional RGB LEDs via GPIO +#define MICROPY_HW_LED1_PIN (pin_GPIO_LPSR_07) // SoM Red LED +#define MICROPY_HW_LED2_PIN (pin_GPIO_LPSR_08) // SoM Green LED +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) // LEDs are active low +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) + +// phyBOARD carrier board RGB LEDs +#define MICROPY_HW_LED3_PIN (pin_GPIO_AD_14) // Carrier Red LED +#define MICROPY_HW_LED4_PIN (pin_GPIO_LPSR_13) // Carrier Green LED + +#define MICROPY_HW_NUM_PIN_IRQS (6 * 32) + +// Define mapping hardware UART # to logical UART # +// phyBOARD-RT1170 UART interfaces +// LPUART1 -> 0 (GPIO_AD_24/25) - Primary debug/console (SoM) +// LPUART2 -> 1 (GPIO_DISP_B2_10/11/12/13) - Carrier board +// LPUART3 -> 2 (GPIO_AD_30/31) - General purpose (SoM) +// LPUART5 -> 3 (GPIO_AD_28/29) - Expansion (SoM) +// LPUART6 -> 4 (GPIO_EMC_B1_40/41) - Console UART (FT2232H - Carrier) +// LPUART7 -> 5 (GPIO_DISP_B2_06/07) - General purpose (SoM) +// LPUART8 -> 6 (GPIO_AD_02/03/04/05) - RS-232 (Carrier) + +#define MICROPY_HW_UART_NUM (sizeof(uart_index_table) / sizeof(uart_index_table)[0]) +#define MICROPY_HW_UART_INDEX { 1, 2, 3, 5, 6, 7, 8 } + +#define IOMUX_TABLE_UART \ + { IOMUXC_GPIO_AD_24_LPUART1_TXD }, { IOMUXC_GPIO_AD_25_LPUART1_RXD }, \ + { IOMUXC_GPIO_DISP_B2_10_LPUART2_TXD }, { IOMUXC_GPIO_DISP_B2_11_LPUART2_RXD }, \ + { IOMUXC_GPIO_AD_30_LPUART3_TXD }, { IOMUXC_GPIO_AD_31_LPUART3_RXD }, \ + { 0 }, { 0 }, \ + { IOMUXC_GPIO_AD_28_LPUART5_TXD }, { IOMUXC_GPIO_AD_29_LPUART5_RXD }, \ + { IOMUXC_GPIO_EMC_B1_40_LPUART6_TXD }, { IOMUXC_GPIO_EMC_B1_41_LPUART6_RXD }, \ + { IOMUXC_GPIO_DISP_B2_06_LPUART7_TXD }, { IOMUXC_GPIO_DISP_B2_07_LPUART7_RXD }, \ + { IOMUXC_GPIO_AD_02_LPUART8_TXD }, { IOMUXC_GPIO_AD_03_LPUART8_RXD }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, + +#define IOMUX_TABLE_UART_CTS_RTS \ + { 0 }, { 0 }, \ + { IOMUXC_GPIO_DISP_B2_12_LPUART2_CTS_B }, { IOMUXC_GPIO_DISP_B2_13_LPUART2_RTS_B }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { IOMUXC_GPIO_AD_04_LPUART8_CTS_B }, { IOMUXC_GPIO_AD_05_LPUART8_RTS_B }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, + +// Define the mapping hardware SPI # to logical SPI # +// phyCORE SoM basic SPI interfaces available on connector +// LPSPI1 -> 0 (GPIO_AD_28/29/30/31) +// LPSPI2 -> 1 (GPIO_AD_24/25/26/27) + +#define MICROPY_HW_SPI_INDEX { 1, 2 } + +#define IOMUX_TABLE_SPI \ + { IOMUXC_GPIO_AD_28_LPSPI1_SCK }, { IOMUXC_GPIO_AD_29_LPSPI1_PCS0 }, \ + { IOMUXC_GPIO_AD_30_LPSPI1_SOUT }, { IOMUXC_GPIO_AD_31_LPSPI1_SIN }, \ + { IOMUXC_GPIO_AD_24_LPSPI2_SCK }, { IOMUXC_GPIO_AD_25_LPSPI2_PCS0 }, \ + { IOMUXC_GPIO_AD_26_LPSPI2_SOUT }, { IOMUXC_GPIO_AD_27_LPSPI2_SIN }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, \ + { 0 }, { 0 }, + + +#define DMA_REQ_SRC_RX { 0, kDmaRequestMuxLPSPI1Rx, kDmaRequestMuxLPSPI2Rx, \ + kDmaRequestMuxLPSPI3Rx, kDmaRequestMuxLPSPI4Rx } + +#define DMA_REQ_SRC_TX { 0, kDmaRequestMuxLPSPI1Tx, kDmaRequestMuxLPSPI2Tx, \ + kDmaRequestMuxLPSPI3Tx, kDmaRequestMuxLPSPI4Tx } + +// Define the mapping hardware I2C # to logical I2C # +// phyBOARD-RT1170 I2C interfaces +// LPI2C1 -> 0 (GPIO_AD_32/33) - EEPROM (SoM) +// LPI2C2 -> 1 (GPIO_AD_18/19) - EEPROM + Accelerometer (Carrier) +// LPI2C3 -> 2 (GPIO_DISP_B2_10/11) - Reserved (SoM) +// LPI2C5 -> 3 (GPIO_LPSR_08/09, GPIO_AD_26/27) - Audio Codec + Accelerometer (Carrier) + +#define MICROPY_HW_I2C_INDEX { 1, 2, 3, 5 } + +#define IOMUX_TABLE_I2C \ + { IOMUXC_GPIO_AD_32_LPI2C1_SCL }, { IOMUXC_GPIO_AD_33_LPI2C1_SDA }, \ + { IOMUXC_GPIO_AD_18_LPI2C2_SCL }, { IOMUXC_GPIO_AD_19_LPI2C2_SDA }, \ + { IOMUXC_GPIO_DISP_B2_10_LPI2C3_SCL }, { IOMUXC_GPIO_DISP_B2_11_LPI2C3_SDA }, \ + { 0 }, { 0 }, \ + { IOMUXC_GPIO_LPSR_08_LPI2C5_SDA }, { IOMUXC_GPIO_LPSR_09_LPI2C5_SCL }, \ + { 0 }, { 0 }, + +#define MICROPY_PY_MACHINE_I2S (1) +#define MICROPY_HW_I2S_NUM (1) +#define I2S_CLOCK_MUX { 0, kCLOCK_Root_Sai1, kCLOCK_Root_Sai2, kCLOCK_Root_Sai3, kCLOCK_Root_Sai4 } +#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx, kDmaRequestMuxSai3Rx, kDmaRequestMuxSai4Rx } +#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx, kDmaRequestMuxSai3Tx, kDmaRequestMuxSai4Tx } +#define I2S_WM8960_RX_MODE (1) +#define I2S_AUDIO_PLL_CLOCK (4U) +#define DMAMUX DMAMUX0 + +#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \ + { \ + .hw_id = _hwid, \ + .fn = _fn, \ + .mode = _mode, \ + .name = MP_QSTR_##_pin, \ + .iomux = {_iomux}, \ + } + +#define I2S_GPIO_MAP \ + { \ + I2S_GPIO(1, MCK, TX, GPIO_AD_17, IOMUXC_GPIO_AD_17_SAI1_MCLK), \ + I2S_GPIO(1, SCK, RX, GPIO_AD_19, IOMUXC_GPIO_AD_19_SAI1_RX_BCLK), \ + I2S_GPIO(1, WS, RX, GPIO_AD_18, IOMUXC_GPIO_AD_18_SAI1_RX_SYNC), \ + I2S_GPIO(1, SD, RX, GPIO_AD_20, IOMUXC_GPIO_AD_20_SAI1_RX_DATA00), \ + I2S_GPIO(1, SCK, TX, GPIO_AD_22, IOMUXC_GPIO_AD_22_SAI1_TX_BCLK), \ + I2S_GPIO(1, WS, TX, GPIO_AD_23, IOMUXC_GPIO_AD_23_SAI1_TX_SYNC), \ + I2S_GPIO(1, SD, TX, GPIO_AD_21, IOMUXC_GPIO_AD_21_SAI1_TX_DATA00), \ + } + +// USDHC1 (SDCARD) +#define MICROPY_PY_MACHINE_SDCARD 0 +#if MICROPY_PY_MACHINE_SDCARD +#define USDHC_DUMMY_PIN NULL, 0 +#define MICROPY_USDHC1 \ + { \ + .cmd = {GPIO_SD_B1_00_USDHC1_CMD}, \ + .clk = { GPIO_SD_B1_01_USDHC1_CLK }, \ + .cd_b = { USDHC_DUMMY_PIN }, \ + .data0 = { GPIO_SD_B1_02_USDHC1_DATA0 }, \ + .data1 = { GPIO_SD_B1_03_USDHC1_DATA1 }, \ + .data2 = { GPIO_SD_B1_04_USDHC1_DATA2 }, \ + .data3 = { GPIO_SD_B1_05_USDHC1_DATA3 }, \ + } +#define USDHC_DATA3_PULL_DOWN_ON_BOARD (1) +#endif + +// Network definitions + +// phyBOARD-RT1170 Dual Ethernet configuration +// Port 0: KSZ8081 100Mbps RMII PHY on carrier board (PBA-C-26) +// Port 1: DP83867 1Gbps RGMII PHY on phyCORE SoM + +// Primary Ethernet (100M RMII) - KSZ8081 on carrier board +#define ENET_PHY_ADDRESS (1) // KSZ8081 PHY address 001b +#define ENET_PHY KSZ8081 +#define ENET_PHY_OPS phyksz8081_ops + +// ENET RMII pin configuration - connected to carrier board KSZ8081 +#define IOMUX_TABLE_ENET \ + { IOMUXC_GPIO_DISP_B2_06_ENET_RX_DATA00, 0, 0x06u }, \ + { IOMUXC_GPIO_DISP_B2_07_ENET_RX_DATA01, 0, 0x06u }, \ + { IOMUXC_GPIO_DISP_B2_08_ENET_RX_EN, 0, 0x06u }, \ + { IOMUXC_GPIO_DISP_B2_02_ENET_TX_DATA00, 0, 0x02u }, \ + { IOMUXC_GPIO_DISP_B2_03_ENET_TX_DATA01, 0, 0x02u }, \ + { IOMUXC_GPIO_DISP_B2_04_ENET_TX_EN, 0, 0x06u }, \ + { IOMUXC_GPIO_DISP_B2_05_ENET_REF_CLK, 1, 0x03u }, \ + { IOMUXC_GPIO_DISP_B2_09_ENET_RX_ER, 0, 0x06u }, \ + { IOMUXC_GPIO_AD_33_ENET_MDIO, 0, 0x06u }, \ + { IOMUXC_GPIO_AD_32_ENET_MDC, 0, 0x06u }, + +// Secondary Ethernet (1G RGMII) - DP83867 on phyCORE SoM +#define ENET_1_PHY_ADDRESS (0) +#define ENET_1_PHY DP83867 +#define ENET_1_PHY_OPS phydp83867_ops + +// ENET_1G RGMII pin configuration - full RGMII pins for Gigabit operation +#define IOMUX_TABLE_ENET_1 \ + { IOMUXC_GPIO_DISP_B1_00_ENET_1G_RX_EN, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_01_ENET_1G_RX_CLK, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_02_ENET_1G_RX_DATA00, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_03_ENET_1G_RX_DATA01, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_04_ENET_1G_RX_DATA02, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_05_ENET_1G_RX_DATA03, 0, 0x08U }, \ + { IOMUXC_GPIO_DISP_B1_06_ENET_1G_TX_DATA03, 0, 0x0CU }, \ + { IOMUXC_GPIO_DISP_B1_07_ENET_1G_TX_DATA02, 0, 0x0CU }, \ + { IOMUXC_GPIO_DISP_B1_08_ENET_1G_TX_DATA01, 0, 0x0CU }, \ + { IOMUXC_GPIO_DISP_B1_09_ENET_1G_TX_DATA00, 0, 0x0CU }, \ + { IOMUXC_GPIO_DISP_B1_10_ENET_1G_TX_EN, 0, 0x0CU }, \ + { IOMUXC_GPIO_DISP_B1_11_ENET_1G_TX_CLK_IO, 0, 0x0CU }, \ + { IOMUXC_GPIO_EMC_B2_20_ENET_1G_MDIO, 0, 0x06u }, \ + { IOMUXC_GPIO_EMC_B2_19_ENET_1G_MDC, 0, 0x06u }, + + +// --- SEMC --- // +#define MIMXRT_IOMUXC_SEMC_DATA00 IOMUXC_GPIO_EMC_B1_00_SEMC_DATA00 +#define MIMXRT_IOMUXC_SEMC_DATA01 IOMUXC_GPIO_EMC_B1_01_SEMC_DATA01 +#define MIMXRT_IOMUXC_SEMC_DATA02 IOMUXC_GPIO_EMC_B1_02_SEMC_DATA02 +#define MIMXRT_IOMUXC_SEMC_DATA03 IOMUXC_GPIO_EMC_B1_03_SEMC_DATA03 +#define MIMXRT_IOMUXC_SEMC_DATA04 IOMUXC_GPIO_EMC_B1_04_SEMC_DATA04 +#define MIMXRT_IOMUXC_SEMC_DATA05 IOMUXC_GPIO_EMC_B1_05_SEMC_DATA05 +#define MIMXRT_IOMUXC_SEMC_DATA06 IOMUXC_GPIO_EMC_B1_06_SEMC_DATA06 +#define MIMXRT_IOMUXC_SEMC_DATA07 IOMUXC_GPIO_EMC_B1_07_SEMC_DATA07 +#define MIMXRT_IOMUXC_SEMC_DATA08 IOMUXC_GPIO_EMC_B1_30_SEMC_DATA08 +#define MIMXRT_IOMUXC_SEMC_DATA09 IOMUXC_GPIO_EMC_B1_31_SEMC_DATA09 +#define MIMXRT_IOMUXC_SEMC_DATA10 IOMUXC_GPIO_EMC_B1_32_SEMC_DATA10 +#define MIMXRT_IOMUXC_SEMC_DATA11 IOMUXC_GPIO_EMC_B1_33_SEMC_DATA11 +#define MIMXRT_IOMUXC_SEMC_DATA12 IOMUXC_GPIO_EMC_B1_34_SEMC_DATA12 +#define MIMXRT_IOMUXC_SEMC_DATA13 IOMUXC_GPIO_EMC_B1_35_SEMC_DATA13 +#define MIMXRT_IOMUXC_SEMC_DATA14 IOMUXC_GPIO_EMC_B1_36_SEMC_DATA14 +#define MIMXRT_IOMUXC_SEMC_DATA15 IOMUXC_GPIO_EMC_B1_37_SEMC_DATA15 + +#define MIMXRT_IOMUXC_SEMC_ADDR00 IOMUXC_GPIO_EMC_B1_09_SEMC_ADDR00 +#define MIMXRT_IOMUXC_SEMC_ADDR01 IOMUXC_GPIO_EMC_B1_10_SEMC_ADDR01 +#define MIMXRT_IOMUXC_SEMC_ADDR02 IOMUXC_GPIO_EMC_B1_11_SEMC_ADDR02 +#define MIMXRT_IOMUXC_SEMC_ADDR03 IOMUXC_GPIO_EMC_B1_12_SEMC_ADDR03 +#define MIMXRT_IOMUXC_SEMC_ADDR04 IOMUXC_GPIO_EMC_B1_13_SEMC_ADDR04 +#define MIMXRT_IOMUXC_SEMC_ADDR05 IOMUXC_GPIO_EMC_B1_14_SEMC_ADDR05 +#define MIMXRT_IOMUXC_SEMC_ADDR06 IOMUXC_GPIO_EMC_B1_15_SEMC_ADDR06 +#define MIMXRT_IOMUXC_SEMC_ADDR07 IOMUXC_GPIO_EMC_B1_16_SEMC_ADDR07 +#define MIMXRT_IOMUXC_SEMC_ADDR08 IOMUXC_GPIO_EMC_B1_17_SEMC_ADDR08 +#define MIMXRT_IOMUXC_SEMC_ADDR09 IOMUXC_GPIO_EMC_B1_18_SEMC_ADDR09 +#define MIMXRT_IOMUXC_SEMC_ADDR10 IOMUXC_GPIO_EMC_B1_23_SEMC_ADDR10 +#define MIMXRT_IOMUXC_SEMC_ADDR11 IOMUXC_GPIO_EMC_B1_19_SEMC_ADDR11 +#define MIMXRT_IOMUXC_SEMC_ADDR12 IOMUXC_GPIO_EMC_B1_20_SEMC_ADDR12 + +#define MIMXRT_IOMUXC_SEMC_BA0 IOMUXC_GPIO_EMC_B1_21_SEMC_BA0 +#define MIMXRT_IOMUXC_SEMC_BA1 IOMUXC_GPIO_EMC_B1_22_SEMC_BA1 +#define MIMXRT_IOMUXC_SEMC_CAS IOMUXC_GPIO_EMC_B1_24_SEMC_CAS +#define MIMXRT_IOMUXC_SEMC_RAS IOMUXC_GPIO_EMC_B1_25_SEMC_RAS +#define MIMXRT_IOMUXC_SEMC_CLK IOMUXC_GPIO_EMC_B1_26_SEMC_CLK +#define MIMXRT_IOMUXC_SEMC_CKE IOMUXC_GPIO_EMC_B1_27_SEMC_CKE +#define MIMXRT_IOMUXC_SEMC_WE IOMUXC_GPIO_EMC_B1_28_SEMC_WE +#define MIMXRT_IOMUXC_SEMC_DM00 IOMUXC_GPIO_EMC_B1_08_SEMC_DM00 +#define MIMXRT_IOMUXC_SEMC_DM01 IOMUXC_GPIO_EMC_B1_38_SEMC_DM01 +#define MIMXRT_IOMUXC_SEMC_DQS IOMUXC_GPIO_EMC_B1_39_SEMC_DQS + +#define MIMXRT_IOMUXC_SEMC_CS0 IOMUXC_GPIO_EMC_B1_29_SEMC_CS0 + +#define MIMXRT_IOMUXC_SEMC_DATA16 IOMUXC_GPIO_EMC_B2_00_SEMC_DATA16 +#define MIMXRT_IOMUXC_SEMC_DATA17 IOMUXC_GPIO_EMC_B2_01_SEMC_DATA17 +#define MIMXRT_IOMUXC_SEMC_DATA18 IOMUXC_GPIO_EMC_B2_02_SEMC_DATA18 +#define MIMXRT_IOMUXC_SEMC_DATA19 IOMUXC_GPIO_EMC_B2_03_SEMC_DATA19 +#define MIMXRT_IOMUXC_SEMC_DATA20 IOMUXC_GPIO_EMC_B2_04_SEMC_DATA20 +#define MIMXRT_IOMUXC_SEMC_DATA21 IOMUXC_GPIO_EMC_B2_05_SEMC_DATA21 +#define MIMXRT_IOMUXC_SEMC_DATA22 IOMUXC_GPIO_EMC_B2_06_SEMC_DATA22 +#define MIMXRT_IOMUXC_SEMC_DATA23 IOMUXC_GPIO_EMC_B2_07_SEMC_DATA23 +#define MIMXRT_IOMUXC_SEMC_DM02 IOMUXC_GPIO_EMC_B2_08_SEMC_DM02 + +#define MIMXRT_IOMUXC_SEMC_DATA24 IOMUXC_GPIO_EMC_B2_09_SEMC_DATA24 +#define MIMXRT_IOMUXC_SEMC_DATA25 IOMUXC_GPIO_EMC_B2_10_SEMC_DATA25 +#define MIMXRT_IOMUXC_SEMC_DATA26 IOMUXC_GPIO_EMC_B2_11_SEMC_DATA26 +#define MIMXRT_IOMUXC_SEMC_DATA27 IOMUXC_GPIO_EMC_B2_12_SEMC_DATA27 +#define MIMXRT_IOMUXC_SEMC_DATA28 IOMUXC_GPIO_EMC_B2_13_SEMC_DATA28 +#define MIMXRT_IOMUXC_SEMC_DATA29 IOMUXC_GPIO_EMC_B2_14_SEMC_DATA29 +#define MIMXRT_IOMUXC_SEMC_DATA30 IOMUXC_GPIO_EMC_B2_15_SEMC_DATA30 +#define MIMXRT_IOMUXC_SEMC_DATA31 IOMUXC_GPIO_EMC_B2_16_SEMC_DATA31 +#define MIMXRT_IOMUXC_SEMC_DM03 IOMUXC_GPIO_EMC_B2_17_SEMC_DM03 +#define MIMXRT_IOMUXC_SEMC_DQS4 IOMUXC_GPIO_EMC_B2_18_SEMC_DQS4 + +#if MICROPY_PY_MACHINE_I2S +#define MICROPY_BOARD_ROOT_POINTERS \ + struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM]; +#endif + +// AUTOGENERATED by update.py from copier +#define MICROPY_HW_USB_CDC_NUM (2) +#define MICROPY_HW_USB_MSC (0) +#define MICROPY_HW_USB_HID (0) + +#define MICROPY_HW_USB_MANUFACTURER_STRING "PHYTEC" +#define MICROPY_HW_USB_PRODUCT_HS_STRING "phyBOARD-RT1170 Development Kit" +#define MICROPY_HW_USB_PRODUCT_FS_STRING "phyBOARD-RT1170 Development Kit" +#define MICROPY_HW_USB_CONFIGURATION_HS_STRING "phyBOARD Config" +#define MICROPY_HW_USB_INTERFACE_HS_STRING "phyBOARD Interface" +#define MICROPY_HW_USB_CONFIGURATION_FS_STRING "phyBOARD Config" +#define MICROPY_HW_USB_INTERFACE_FS_STRING "phyBOARD Interface" + +#define MBOOT_USBD_MANUFACTURER_STRING "PHYTEC" +#define MBOOT_USBD_PRODUCT_STRING "phyBOARD Boot" + +// phyBOARD-RT1170 Development Kit hardware features + +// Onboard EEPROM (M24C32 - 32Kbit) +#define MICROPY_HW_EEPROM_I2C_BUS (0) // On LPI2C1 (SoM) +#define MICROPY_HW_EEPROM_ADDR (0x50) + +// Carrier board peripherals +// Audio Codec: TLV320AIC3110 on LPI2C5 (I2C address: 0x18) +// Accelerometer: ICM-40627 on LPI2C2 (I2C address: 0x6B) +// CAN Interface: CAN3 (GPIO_LPSR_00/01) +// RS-232 Serial: LPUART8 (GPIO_AD_02/03/04/05) +// User Button: GPIO_AD_35 +// RGB LEDs: GPIO_AD_14 (red), GPIO_LPSR_13 (green) +// microSD Card Slot: USDHC1 +// M.2 Connector (Key E): WiFi/Bluetooth modules + +// Basic ADC channels available on connector +#define MICROPY_HW_ADC_NUM_CHANNELS (16) // GPIO_AD domain pins diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.mk b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.mk new file mode 100644 index 00000000000..9cca7a54ab4 --- /dev/null +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.mk @@ -0,0 +1,31 @@ +MCU_SERIES = MIMXRT1176 +MCU_VARIANT = MIMXRT1176DVMAA +MCU_CORE = _cm7 + +MICROPY_FLOAT_IMPL = double +MICROPY_HW_FLASH_TYPE ?= qspi_nor_flash +MICROPY_HW_FLASH_SIZE ?= 0x1000000 # 16MB +MICROPY_HW_FLASH_RESERVED ?= 0x100000 # 1MB CM4 Code address space +MICROPY_HW_FLASH_CLK = kFlexSpiSerialClk_100MHz +MICROPY_HW_FLASH_QE_CMD = 0x31 +MICROPY_HW_FLASH_QE_ARG = 0x02 +# phyCORE SoM flash timing - use loopback from DQS pad for better signal integrity +MICROPY_HW_FLASH_DQS = kFlexSPIReadSampleClk_LoopbackFromDqsPad + +MICROPY_HW_SDRAM_AVAIL = 1 +MICROPY_HW_SDRAM_SIZE = 0x4000000 # 64MB + +MICROPY_PY_LWIP = 1 +MICROPY_PY_SSL = 1 +MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 + +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py + +CFLAGS += -DCPU_MIMXRT1176DVMAA_cm7 \ + -DMIMXRT117x_SERIES \ + -DENET_ENHANCEDBUFFERDESCRIPTOR_MODE=1 \ + -DCPU_HEADER_H='<$(MCU_SERIES)$(MCU_CORE).h>' \ + -DUSB1_BASE=USB_OTG1_BASE \ + -DUSB2_BASE=USB_OTG2_BASE diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/pins.csv b/ports/mimxrt/boards/PHYBOARD_RT1170/pins.csv new file mode 100644 index 00000000000..c0222d9b252 --- /dev/null +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/pins.csv @@ -0,0 +1,127 @@ +# phyBOARD-RT1170 Development Kit Pin Mapping +# phyCORE-i.MX RT1170 SoM + PBA-C-26 Carrier Board + +# LEDs - phyCORE SoM onboard (active low) +LED_SOM_RED,GPIO_LPSR_07 +LED_SOM_GREEN,GPIO_LPSR_08 + +# LEDs - Carrier board RGB LEDs (active low) +LED_CARRIER_RED,GPIO_AD_14 +LED_CARRIER_GREEN,GPIO_LPSR_13 + +# Buttons +USER_BUTTON,GPIO_AD_35 +POWER_BUTTON,GPIO_LPSR_04 +WAKE_BUTTON,GPIO_LPSR_03 +RESET_BUTTON,GPIO_LPSR_02 + +# Primary UART interfaces +UART1_TX,GPIO_AD_24 +UART1_RX,GPIO_AD_25 + +# Additional UART interfaces (Carrier board) +UART2_TX,GPIO_DISP_B2_10 +UART2_RX,GPIO_DISP_B2_11 +UART2_CTS,GPIO_DISP_B2_13 +UART2_RTS,GPIO_DISP_B2_12 + +UART5_TX,GPIO_AD_28 +UART5_RX,GPIO_AD_29 + +UART6_TX,GPIO_EMC_B1_40 +UART6_RX,GPIO_EMC_B1_41 + +UART7_TX,GPIO_DISP_B2_06 +UART7_RX,GPIO_DISP_B2_07 + +UART8_TX_RS232,GPIO_AD_02 +UART8_RX_RS232,GPIO_AD_03 +UART8_CTS,GPIO_AD_04 +UART8_RTS,GPIO_AD_05 + +# I2C interfaces +I2C1_SCL,GPIO_AD_32 +I2C1_SDA,GPIO_AD_33 + +I2C2_SCL,GPIO_AD_18 +I2C2_SDA,GPIO_AD_19 + +I2C3_SCL,GPIO_DISP_B2_10 +I2C3_SDA,GPIO_DISP_B2_11 + +I2C5_SCL,GPIO_LPSR_09 +I2C5_SDA,GPIO_LPSR_08 + +# SPI interface +SPI1_SCK,GPIO_AD_28 +SPI1_CS,GPIO_AD_29 + +# Display interface (MIPI-DSI) +MIPI_DSI_DP0,GPIO_DISP_B1_02 +MIPI_DSI_DN0,GPIO_DISP_B1_03 +MIPI_DSI_DP1,GPIO_DISP_B1_00 +MIPI_DSI_DN1,GPIO_DISP_B1_01 +MIPI_DSI_CKP,GPIO_DISP_B1_05 +MIPI_DSI_CKN,GPIO_DISP_B1_04 + +# Display control pins +LCD_RST_B,GPIO_DISP_B2_14 +LCD_PWR_EN,GPIO_AD_26 +LCD_BACKLIGHT_CTL,GPIO_AD_27 +CTP_INT,GPIO_AD_26 +CTP_RST_B,GPIO_DISP_B2_15 + +# Camera interface (MIPI-CSI) - on SoM connector (via pin definitions) +# Note: MIPI-CSI differential pairs are on SoM - reference schematic for pin assignments + +# Accelerometer (ICM-40627 on LPI2C2) +ACCEL_INT1,GPIO_AD_26 +ACCEL_INT2,GPIO_AD_27 + +# Audio codec (TLV320AIC3110 on LPI2C5 and SAI1) +SAI1_TX_BCLK,GPIO_AD_22 +SAI1_TX_SYNC,GPIO_AD_23 +SAI1_TX_DATA,GPIO_AD_21 +SAI1_RX_DATA,GPIO_AD_20 +SAI1_MCLK,GPIO_AD_17 + +# CAN interface (CAN3) +CAN3_TX,GPIO_LPSR_00 +CAN3_RX,GPIO_LPSR_01 + +# SD Card interface (USDHC1) +SD1_CLK,GPIO_SD_B1_01 +SD1_CMD,GPIO_SD_B1_00 +SD1_DATA0,GPIO_SD_B1_02 +SD1_DATA1,GPIO_SD_B1_03 +SD1_DATA2,GPIO_SD_B1_04 +SD1_DATA3,GPIO_SD_B1_05 + +# Basic ADC channels +ADC1_CH0,GPIO_AD_00 +ADC1_CH1,GPIO_AD_01 +ADC1_CH2,GPIO_AD_02 +ADC1_CH3,GPIO_AD_03 + +# GPIO expansion connector +GPIO_CONN_16,GPIO_AD_16 +GPIO_CONN_17,GPIO_AD_17 +GPIO_CONN_18,GPIO_AD_18 +GPIO_CONN_19,GPIO_AD_19 +GPIO_CONN_20,GPIO_AD_20 +GPIO_CONN_21,GPIO_AD_21 +GPIO_CONN_22,GPIO_AD_22 +GPIO_CONN_23,GPIO_AD_23 +GPIO_CONN_26,GPIO_AD_26 +GPIO_CONN_27,GPIO_AD_27 +GPIO_CONN_LPSR_09,GPIO_LPSR_09 +GPIO_CONN_LPSR_10,GPIO_LPSR_10 +GPIO_CONN_LPSR_11,GPIO_LPSR_11 +GPIO_CONN_LPSR_12,GPIO_LPSR_12 + +# JTAG interface +JTAG_TCK,GPIO_LPSR_14 +JTAG_TMS,GPIO_LPSR_15 +JTAG_TDI,GPIO_LPSR_12 +JTAG_TDO,GPIO_LPSR_11 +JTAG_nTRST,GPIO_LPSR_10 diff --git a/pyproject.toml b/pyproject.toml index 1cf57166e14..2f71058f607 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.codespell] count = "" ignore-regex = '\b[A-Z]{3}\b' -ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,notin,numer,ser,shft,synopsys,technic,ure,curren" +ignore-words-list = "ans,asend,deques,dout,emac,extint,hsi,iput,mis,notin,numer,ser,shft,som,synopsys,technic,ure,curren" quiet-level = 3 skip = """ */build*,\ From 1ecec98ac36a2a6cfa1086b084166519454e134e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 24 Nov 2025 09:30:00 +0000 Subject: [PATCH 1697/2098] lib/pico-sdk: Update pico-sdk to 2.2.0 release. Signed-off-by: Peter Harper --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index 9a4113fbbae..a1438dff1d3 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit 9a4113fbbae65ee82d8cd6537963bc3d3b14bcca +Subproject commit a1438dff1d38bd9c65dbd693f0e5db4b9ae91779 From 63dbaca34871190e2340a4dff1a76155d22c9575 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 24 Nov 2025 09:36:28 +0000 Subject: [PATCH 1698/2098] rp2: Add pico_platform_common to sdk components. Signed-off-by: Peter Harper --- ports/rp2/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index d88a96d4a70..d3413223889 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -235,6 +235,7 @@ set(PICO_SDK_COMPONENTS pico_flash pico_multicore pico_platform + pico_platform_common pico_platform_compiler pico_platform_panic pico_platform_sections From 6ce02145dd0595b6ddb047cf13ff5c637a51ee63 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 24 Nov 2025 09:40:35 +0000 Subject: [PATCH 1699/2098] rp2: Remove build patch for Findpioasm.cmake. This patch was added in 3fa77bdc7d413a410fb85a8943d417bca3b562e7 to fix the GCC 15 build issue https://github.com/raspberrypi/pico-sdk/issues/2448. The patch is no longer needed with SDK 2.2.0. Signed-off-by: Peter Harper --- ports/rp2/CMakeLists.txt | 3 -- ports/rp2/tools_patch/Findpioasm.cmake | 58 -------------------------- 2 files changed, 61 deletions(-) delete mode 100644 ports/rp2/tools_patch/Findpioasm.cmake diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index d3413223889..f10cf1a16d7 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -84,9 +84,6 @@ endif() list(APPEND GIT_SUBMODULES lib/mbedtls) list(APPEND GIT_SUBMODULES lib/tinyusb) -# Workaround for pico-sdk host toolchain issue, see directory for details -list(APPEND CMAKE_MODULE_PATH "${MICROPY_PORT_DIR}/tools_patch") - # Include component cmake fragments include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) diff --git a/ports/rp2/tools_patch/Findpioasm.cmake b/ports/rp2/tools_patch/Findpioasm.cmake deleted file mode 100644 index 72e6bf6fb61..00000000000 --- a/ports/rp2/tools_patch/Findpioasm.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# Finds (or builds) the pioasm executable -# -# This will define the following imported targets -# -# pioasm -# - -# This is a temporary patched copy of pico-sdk file Findpioasm.cmake to work around -# a host toolchain issue with GCC 15.1: -# https://github.com/raspberrypi/pico-sdk/issues/2448 - -if (NOT TARGET pioasm) - # todo we would like to use pckgconfig to look for it first - # see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ - - include(ExternalProject) - - set(PIOASM_SOURCE_DIR ${PICO_SDK_PATH}/tools/pioasm) - set(PIOASM_BINARY_DIR ${CMAKE_BINARY_DIR}/pioasm) - set(PIOASM_INSTALL_DIR ${CMAKE_BINARY_DIR}/pioasm-install CACHE PATH "Directory where pioasm has been installed" FORCE) - - set(pioasmBuild_TARGET pioasmBuild) - set(pioasm_TARGET pioasm) - - if (NOT TARGET ${pioasmBuild_TARGET}) - pico_message_debug("PIOASM will need to be built") -# message("Adding external project ${pioasmBuild_Target} in ${CMAKE_CURRENT_LIST_DIR}}") - ExternalProject_Add(${pioasmBuild_TARGET} - PREFIX pioasm - SOURCE_DIR ${PIOASM_SOURCE_DIR} - BINARY_DIR ${PIOASM_BINARY_DIR} - INSTALL_DIR ${PIOASM_INSTALL_DIR} - CMAKE_ARGS - "--no-warn-unused-cli" - "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}" - "-DPIOASM_FLAT_INSTALL=1" - "-DCMAKE_INSTALL_PREFIX=${PIOASM_INSTALL_DIR}" - "-DCMAKE_RULE_MESSAGES=OFF" # quieten the build - "-DCMAKE_INSTALL_MESSAGE=NEVER" # quieten the install - # Toolchain workaround follows - "-DCMAKE_CXX_FLAGS=-include cstdint" - CMAKE_CACHE_ARGS "-DPIOASM_EXTRA_SOURCE_FILES:STRING=${PIOASM_EXTRA_SOURCE_FILES}" - BUILD_ALWAYS 1 # force dependency checking - EXCLUDE_FROM_ALL TRUE - ) - endif() - - if (CMAKE_HOST_WIN32) - set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm.exe) - else() - set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm) - endif() - add_executable(${pioasm_TARGET} IMPORTED GLOBAL) - set_property(TARGET ${pioasm_TARGET} PROPERTY IMPORTED_LOCATION - ${pioasm_EXECUTABLE}) - - add_dependencies(${pioasm_TARGET} ${pioasmBuild_TARGET}) -endif() From 2b4b05a422f02caa40f21cb1a2aa0690915809e5 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 24 Nov 2025 09:49:18 +0000 Subject: [PATCH 1700/2098] rp2: Fix linker scripts to match SDK 2.2.0. Signed-off-by: Peter Harper --- ports/rp2/memmap_mp_rp2040.ld | 10 +++++----- ports/rp2/memmap_mp_rp2350.ld | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/rp2/memmap_mp_rp2040.ld b/ports/rp2/memmap_mp_rp2040.ld index 5c8d9f47188..2a63aefaeca 100644 --- a/ports/rp2/memmap_mp_rp2040.ld +++ b/ports/rp2/memmap_mp_rp2040.ld @@ -25,8 +25,8 @@ MEMORY { FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __micropy_flash_size__ RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k - SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k - SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 0k + SCRATCH_Y(rwx) : ORIGIN = 0x20040000, LENGTH = 8k } ENTRY(_entry_point) @@ -216,7 +216,7 @@ SECTIONS { __end__ = .; end = __end__; - *(.heap*) + KEEP(*(.heap*)) __HeapLimit = .; } > RAM @@ -231,11 +231,11 @@ SECTIONS */ .stack1_dummy (COPY): { - *(.stack1*) + KEEP(*(.stack1*)) } > SCRATCH_X .stack_dummy (COPY): { - *(.stack*) + KEEP(*(.stack*)) } > SCRATCH_Y .flash_end : { diff --git a/ports/rp2/memmap_mp_rp2350.ld b/ports/rp2/memmap_mp_rp2350.ld index 1c4770efe10..5eb85f53d96 100644 --- a/ports/rp2/memmap_mp_rp2350.ld +++ b/ports/rp2/memmap_mp_rp2350.ld @@ -25,8 +25,8 @@ MEMORY { FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __micropy_flash_size__ RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k - SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k - SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 0k + SCRATCH_Y(rwx) : ORIGIN = 0x20080000, LENGTH = 8k } ENTRY(_entry_point) From e40d609a0e38dee545a2b04ee066687c6a9a454f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Aug 2025 00:02:29 +1000 Subject: [PATCH 1701/2098] tests/multi_net: Skip all ssl/tls tests on axTLS. The axTLS implementation of the tls module only has a basic set of features. In particular it doesn't support the CERT_REQUIRED constant nor DTLS, nor can it load the `ec_key.der` key when acting as a server. So skip tests that require these features, which ends up being all the ssl/tls tests. Signed-off-by: Damien George --- tests/multi_net/asyncio_tls_server_client.py | 4 ++++ .../asyncio_tls_server_client_cert_required_error.py | 4 ++++ tests/multi_net/asyncio_tls_server_client_readline.py | 4 ++++ tests/multi_net/asyncio_tls_server_client_verify_error.py | 4 ++++ tests/multi_net/ssl_cert_ec.py | 4 ++++ tests/multi_net/ssl_cert_rsa.py | 4 ++++ tests/multi_net/sslcontext_check_hostname_error.py | 4 ++++ tests/multi_net/sslcontext_getpeercert.py | 4 ++++ tests/multi_net/sslcontext_server_client.py | 4 ++++ tests/multi_net/sslcontext_server_client_ciphers.py | 4 ++++ tests/multi_net/sslcontext_server_client_files.py | 4 ++++ tests/multi_net/sslcontext_verify_callback.py | 4 ++++ tests/multi_net/sslcontext_verify_error.py | 4 ++++ tests/multi_net/sslcontext_verify_time_error.py | 4 ++++ tests/multi_net/tls_dtls_server_client.py | 4 ++++ 15 files changed, 60 insertions(+) diff --git a/tests/multi_net/asyncio_tls_server_client.py b/tests/multi_net/asyncio_tls_server_client.py index 016f57970c0..959dcfe5994 100644 --- a/tests/multi_net/asyncio_tls_server_client.py +++ b/tests/multi_net/asyncio_tls_server_client.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py index eac9a98beae..5d66d9c1bd9 100644 --- a/tests/multi_net/asyncio_tls_server_client_cert_required_error.py +++ b/tests/multi_net/asyncio_tls_server_client_cert_required_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/asyncio_tls_server_client_readline.py b/tests/multi_net/asyncio_tls_server_client_readline.py index 6093628ce5b..539eae23d5c 100644 --- a/tests/multi_net/asyncio_tls_server_client_readline.py +++ b/tests/multi_net/asyncio_tls_server_client_readline.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/asyncio_tls_server_client_verify_error.py b/tests/multi_net/asyncio_tls_server_client_verify_error.py index 6dbff0482c8..50169dd679a 100644 --- a/tests/multi_net/asyncio_tls_server_client_verify_error.py +++ b/tests/multi_net/asyncio_tls_server_client_verify_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/ssl_cert_ec.py b/tests/multi_net/ssl_cert_ec.py index 8ecbd4d34f6..cb4e36f78cc 100644 --- a/tests/multi_net/ssl_cert_ec.py +++ b/tests/multi_net/ssl_cert_ec.py @@ -7,6 +7,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/ssl_cert_rsa.py b/tests/multi_net/ssl_cert_rsa.py index b99493b0ae5..473a5b16f8d 100644 --- a/tests/multi_net/ssl_cert_rsa.py +++ b/tests/multi_net/ssl_cert_rsa.py @@ -7,6 +7,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_check_hostname_error.py b/tests/multi_net/sslcontext_check_hostname_error.py index 6bd911ddfd7..65dff998e1f 100644 --- a/tests/multi_net/sslcontext_check_hostname_error.py +++ b/tests/multi_net/sslcontext_check_hostname_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_getpeercert.py b/tests/multi_net/sslcontext_getpeercert.py index e2d2a425116..89a12753bb4 100644 --- a/tests/multi_net/sslcontext_getpeercert.py +++ b/tests/multi_net/sslcontext_getpeercert.py @@ -9,6 +9,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_server_client.py b/tests/multi_net/sslcontext_server_client.py index fd330cee78a..1b6a0996688 100644 --- a/tests/multi_net/sslcontext_server_client.py +++ b/tests/multi_net/sslcontext_server_client.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_server_client_ciphers.py b/tests/multi_net/sslcontext_server_client_ciphers.py index 7017a6f5798..cd3fe919a34 100644 --- a/tests/multi_net/sslcontext_server_client_ciphers.py +++ b/tests/multi_net/sslcontext_server_client_ciphers.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(tls, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_server_client_files.py b/tests/multi_net/sslcontext_server_client_files.py index 48ff6376fad..e42b9ea033a 100644 --- a/tests/multi_net/sslcontext_server_client_files.py +++ b/tests/multi_net/sslcontext_server_client_files.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_verify_callback.py b/tests/multi_net/sslcontext_verify_callback.py index 80056a5868c..b762b95e6f0 100644 --- a/tests/multi_net/sslcontext_verify_callback.py +++ b/tests/multi_net/sslcontext_verify_callback.py @@ -9,6 +9,10 @@ print("SKIP") raise SystemExit +if not hasattr(tls, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_verify_error.py b/tests/multi_net/sslcontext_verify_error.py index 07dcc690b7c..f6e1477bfed 100644 --- a/tests/multi_net/sslcontext_verify_error.py +++ b/tests/multi_net/sslcontext_verify_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/sslcontext_verify_time_error.py b/tests/multi_net/sslcontext_verify_time_error.py index 7b986322d15..274d99d4bf6 100644 --- a/tests/multi_net/sslcontext_verify_time_error.py +++ b/tests/multi_net/sslcontext_verify_time_error.py @@ -8,6 +8,10 @@ print("SKIP") raise SystemExit +if not hasattr(ssl, "CERT_REQUIRED"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. diff --git a/tests/multi_net/tls_dtls_server_client.py b/tests/multi_net/tls_dtls_server_client.py index ec7d7de55fd..10fac3c6f42 100644 --- a/tests/multi_net/tls_dtls_server_client.py +++ b/tests/multi_net/tls_dtls_server_client.py @@ -7,6 +7,10 @@ print("SKIP") raise SystemExit +if not hasattr(tls, "PROTOCOL_DTLS_SERVER"): + print("SKIP") + raise SystemExit + PORT = 8000 # These are test certificates. See tests/README.md for details. From edab53c1daf0156092680fe72dba9f87d0cbbb31 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 Jan 2026 13:39:03 +1100 Subject: [PATCH 1702/2098] tests/multi_net: Use random.getrandbits instead of random.randrange. The `getrandbits()` function is guaranteed to exist on all ports that have the `random` module (but `randrange()` is not). Signed-off-by: Damien George --- tests/multi_net/tcp_recv_peek.py | 2 +- tests/multi_net/udp_recv_dontwait.py | 2 +- tests/multi_net/udp_recv_peek.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/multi_net/tcp_recv_peek.py b/tests/multi_net/tcp_recv_peek.py index ff540dd3c3c..4b8dd6768e4 100644 --- a/tests/multi_net/tcp_recv_peek.py +++ b/tests/multi_net/tcp_recv_peek.py @@ -9,7 +9,7 @@ # Server def instance0(): - PORT = random.randrange(10000, 50000) + PORT = 10000 + random.getrandbits(15) multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/tests/multi_net/udp_recv_dontwait.py b/tests/multi_net/udp_recv_dontwait.py index 640f3f060ea..2d2ee572d34 100644 --- a/tests/multi_net/udp_recv_dontwait.py +++ b/tests/multi_net/udp_recv_dontwait.py @@ -11,7 +11,7 @@ # Server def instance0(): - PORT = random.randrange(10000, 50000) + PORT = 10000 + random.getrandbits(15) multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/tests/multi_net/udp_recv_peek.py b/tests/multi_net/udp_recv_peek.py index 47897ce5539..e8a5f112919 100644 --- a/tests/multi_net/udp_recv_peek.py +++ b/tests/multi_net/udp_recv_peek.py @@ -6,7 +6,7 @@ # Server def instance0(): - PORT = random.randrange(10000, 50000) + PORT = 10000 + random.getrandbits(15) multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) From e5f772ae85fdfee2937674dfea9910b26a3b6200 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 21 Jan 2026 20:53:09 +1100 Subject: [PATCH 1703/2098] tests/extmod/machine_spi_rate.py: Use target wiring for SPI instances. This commit converts `tests/extmod/machine_spi_rate.py` to use the target wiring mechanism, moving the port-specefic definition of the SPI instances that are being tested out of the `tests/extmod/machine_spi_rate.py` file and into the target wiring code. As part of this, the test is changed to always use the default pins for the given SPI instance. This simplifies the test, makes it easier to add support for new ports/boards, and allows reusing the `spi_standalone_args_list` definition in other tests (if needed one day). Signed-off-by: Damien George --- tests/extmod/machine_spi_rate.py | 68 +++++------------------------- tests/run-tests.py | 1 + tests/target_wiring/NUCLEO_WB55.py | 2 + tests/target_wiring/PYBx.py | 2 + tests/target_wiring/alif.py | 2 + tests/target_wiring/esp32.py | 7 +++ tests/target_wiring/esp8266.py | 2 + tests/target_wiring/rp2.py | 2 + 8 files changed, 28 insertions(+), 58 deletions(-) diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index fe15b66fe64..c41bf3ac2f7 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -1,40 +1,21 @@ # Test machine.SPI data transfer rates -import sys - try: - import time from machine import Pin, SPI except ImportError: print("SKIP") raise SystemExit +import time, sys +from target_wiring import spi_standalone_args_list + MAX_DELTA_MS = 8 -# Configure pins based on the port/board details. -# Values are tuples of (spi_id, sck, mosi, miso) +# Tune test parameters based on the target. if "alif" in sys.platform: MAX_DELTA_MS = 20 - spi_instances = ((0, None, None, None),) -elif "pyboard" in sys.platform: - spi_instances = ( - (1, None, None, None), # "explicit choice of sck/mosi/miso is not implemented" - (2, None, None, None), - ) -elif "rp2" in sys.platform: - spi_instances = ((0, Pin(18), Pin(19), Pin(16)),) -elif "esp32" in sys.platform: - impl = str(sys.implementation) - if any(soc in impl for soc in ("ESP32C2", "ESP32C3", "ESP32C6")): - spi_instances = ((1, Pin(4), Pin(5), Pin(6)),) - else: - spi_instances = ((1, Pin(18), Pin(19), Pin(21)), (2, Pin(18), Pin(19), Pin(21))) elif "esp8266" in sys.platform: MAX_DELTA_MS = 50 # port requires much looser timing requirements - spi_instances = ((1, None, None, None),) # explicit pin choice not allowed -else: - print("Please add support for this test on this platform.") - raise SystemExit def get_real_baudrate(spi): @@ -54,7 +35,7 @@ def get_real_baudrate(spi): def test_instances(): print_results = True - for spi_id, sck, mosi, miso in spi_instances: + for spi_args in spi_standalone_args_list: for baudrate in ( 100_000, 250_000, @@ -64,30 +45,12 @@ def test_instances(): 4_000_000, 8_000_000, ): - test_spi( - spi_id, - sck, - mosi, - miso, - baudrate, - 0, - 0, - print_results, - ) + test_spi(spi_args, baudrate, 0, 0, print_results) for baudrate in (100_000, 4_000_000): # Test the other polarity and phase settings for polarity, phase in ((0, 1), (1, 0), (1, 1)): - test_spi( - spi_id, - sck, - mosi, - miso, - baudrate, - polarity, - phase, - print_results, - ) + test_spi(spi_args, baudrate, polarity, phase, print_results) # Ensure the same test output regardless of how many SPI instances are tested print_results = False @@ -100,19 +63,8 @@ def test_instances(): rd_long = bytearray(len(wr_long)) -def test_spi(spi_id, sck, mosi, miso, baudrate, polarity, phase, print_results): - if sck: - s = SPI( - spi_id, - sck=sck, - mosi=mosi, - miso=miso, - baudrate=baudrate, - polarity=polarity, - phase=phase, - ) - else: - s = SPI(spi_id, baudrate=baudrate, polarity=polarity, phase=phase) +def test_spi(spi_args, baudrate, polarity, phase, print_results): + s = SPI(*spi_args, baudrate=baudrate, polarity=polarity, phase=phase) # to keep the test runtime down, use shorter buffer for lower baud rate wr_buf = wr_long if baudrate > 500_000 else wr_short @@ -144,7 +96,7 @@ def test_readinto(): if print_results or not t_ok: print( - None if print_results else spi_id, + None if print_results else spi_args, baudrate, polarity, phase, diff --git a/tests/run-tests.py b/tests/run-tests.py index 0ff358c06e2..1ac3c374504 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -267,6 +267,7 @@ # Tests that require `import target_wiring` to work. tests_requiring_target_wiring = ( + "extmod/machine_spi_rate.py", "extmod/machine_uart_irq_txidle.py", "extmod/machine_uart_tx.py", "extmod_hardware/machine_encoder.py", diff --git a/tests/target_wiring/NUCLEO_WB55.py b/tests/target_wiring/NUCLEO_WB55.py index ad7c120d377..166f6f8d35f 100644 --- a/tests/target_wiring/NUCLEO_WB55.py +++ b/tests/target_wiring/NUCLEO_WB55.py @@ -6,3 +6,5 @@ # LPUART(1) is on PA2/PA3. uart_loopback_args = ("LP1",) uart_loopback_kwargs = {} + +spi_standalone_args_list = [(1,), (2,)] diff --git a/tests/target_wiring/PYBx.py b/tests/target_wiring/PYBx.py index 10ce520ef0a..a419320d902 100644 --- a/tests/target_wiring/PYBx.py +++ b/tests/target_wiring/PYBx.py @@ -6,3 +6,5 @@ # UART("XA") is on X1/X2 (usually UART(4) on PA0/PA1). uart_loopback_args = ("XA",) uart_loopback_kwargs = {} + +spi_standalone_args_list = [(1,), (2,)] diff --git a/tests/target_wiring/alif.py b/tests/target_wiring/alif.py index 18f3cbe7e5e..cb62ea40720 100644 --- a/tests/target_wiring/alif.py +++ b/tests/target_wiring/alif.py @@ -5,3 +5,5 @@ uart_loopback_args = (1,) uart_loopback_kwargs = {} + +spi_standalone_args_list = [(0,)] diff --git a/tests/target_wiring/esp32.py b/tests/target_wiring/esp32.py index 2767cd5acb2..a3d39b5111f 100644 --- a/tests/target_wiring/esp32.py +++ b/tests/target_wiring/esp32.py @@ -4,9 +4,16 @@ # - GPIO4 to GPIO5 # - GPIO12 to GPIO13 +import sys + uart_loopback_args = (1,) uart_loopback_kwargs = {"tx": 4, "rx": 5} +if "ESP32C" in sys.implementation._machine: + spi_standalone_args_list = [(1,)] +else: + spi_standalone_args_list = [(1,), (2,)] + encoder_loopback_id = 0 encoder_loopback_out_pins = (4, 12) encoder_loopback_in_pins = (5, 13) diff --git a/tests/target_wiring/esp8266.py b/tests/target_wiring/esp8266.py index 336deb3dda0..ba9bca01c86 100644 --- a/tests/target_wiring/esp8266.py +++ b/tests/target_wiring/esp8266.py @@ -5,3 +5,5 @@ uart_loopback_args = (1,) uart_loopback_kwargs = {} + +spi_standalone_args_list = [(1,)] diff --git a/tests/target_wiring/rp2.py b/tests/target_wiring/rp2.py index cb0fa0d6263..4024eb20884 100644 --- a/tests/target_wiring/rp2.py +++ b/tests/target_wiring/rp2.py @@ -5,3 +5,5 @@ uart_loopback_args = (0,) uart_loopback_kwargs = {"tx": "GPIO0", "rx": "GPIO1"} + +spi_standalone_args_list = [(0,), (1,)] From d291a280bd3c3e136e37dc9c65beff307722a697 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 21 Jan 2026 20:53:39 +1100 Subject: [PATCH 1704/2098] tests/target_wiring: Add SPI instance for mimxrt, nrf, renesas-ra, samd. Signed-off-by: Damien George --- tests/target_wiring/mimxrt.py | 2 ++ tests/target_wiring/nrf.py | 2 ++ tests/target_wiring/renesas-ra.py | 3 +++ tests/target_wiring/samd.py | 2 ++ 4 files changed, 9 insertions(+) create mode 100644 tests/target_wiring/renesas-ra.py diff --git a/tests/target_wiring/mimxrt.py b/tests/target_wiring/mimxrt.py index 669e9095990..8d2c0b15c28 100644 --- a/tests/target_wiring/mimxrt.py +++ b/tests/target_wiring/mimxrt.py @@ -5,3 +5,5 @@ uart_loopback_args = (1,) uart_loopback_kwargs = {} + +spi_standalone_args_list = [()] diff --git a/tests/target_wiring/nrf.py b/tests/target_wiring/nrf.py index 6979dd28ee5..b61a62fe3b6 100644 --- a/tests/target_wiring/nrf.py +++ b/tests/target_wiring/nrf.py @@ -5,3 +5,5 @@ uart_loopback_args = (0,) uart_loopback_kwargs = {} + +spi_standalone_args_list = [(1,)] diff --git a/tests/target_wiring/renesas-ra.py b/tests/target_wiring/renesas-ra.py new file mode 100644 index 00000000000..ecd04b4c2b6 --- /dev/null +++ b/tests/target_wiring/renesas-ra.py @@ -0,0 +1,3 @@ +# Target wiring for general renesas-ra board. + +spi_standalone_args_list = [(0,)] diff --git a/tests/target_wiring/samd.py b/tests/target_wiring/samd.py index 887c43a242f..1ee67e8e749 100644 --- a/tests/target_wiring/samd.py +++ b/tests/target_wiring/samd.py @@ -5,3 +5,5 @@ uart_loopback_args = () uart_loopback_kwargs = {"tx": "D1", "rx": "D0"} + +spi_standalone_args_list = [()] From cef2538b109c00cf1a833f253df737ad7b2f3996 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 26 Jan 2026 12:36:33 +1100 Subject: [PATCH 1705/2098] tests/target_wiring/README: Add README describing target_wiring specs. This is now the authoritative place for what should go in a target wiring script. Signed-off-by: Damien George --- tests/target_wiring/README.md | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/target_wiring/README.md diff --git a/tests/target_wiring/README.md b/tests/target_wiring/README.md new file mode 100644 index 00000000000..96c98da8dff --- /dev/null +++ b/tests/target_wiring/README.md @@ -0,0 +1,67 @@ +# Target wiring + +Some tests require hardware configuration and/or external connections, for example +bridging a pair of GPIO pins. Each board that such tests run on needs to be configured +individually. That is achieved by providing a target wiring configuration script that +defines the necessary hardware parameters for each test. + +## Selecting the target wiring + +There are three ways to provide the target wiring configuration: + +1. Specify it explicitly when running the test: `./run-tests.py --target-wiring " + t = t"User said: {user_input}" + print(safe_html(t)) + # Output: "User said: <script>alert('xss')</script>" + +See Also +-------- + +* `PEP 750 `_ - Template Strings specification +* :ref:`python:formatstrings` - Format string syntax +* `Formatted string literals `_ - f-strings in Python From 10af0a2510ae66734fd0294bb285993c0e4d1a76 Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Tue, 23 Dec 2025 20:05:04 -0500 Subject: [PATCH 1830/2098] rp2/boards/CYTRON_NANOXRP_CONTROLLER: Add support for NanoXRP board. The NanoXRP is a small version of the XRP Robot using rp2040. Signed-off-by: Jacob Williams --- .../CYTRON_NANOXRP_CONTROLLER/board.json | 23 +++++++++++ .../CYTRON_NANOXRP_CONTROLLER/manifest.py | 6 +++ .../mpconfigboard.cmake | 14 +++++++ .../CYTRON_NANOXRP_CONTROLLER/mpconfigboard.h | 31 ++++++++++++++ .../boards/CYTRON_NANOXRP_CONTROLLER/pins.csv | 41 +++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/board.json create mode 100644 ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/manifest.py create mode 100644 ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.cmake create mode 100644 ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.h create mode 100644 ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/pins.csv diff --git a/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/board.json b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/board.json new file mode 100644 index 00000000000..5cb529fad36 --- /dev/null +++ b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Dual-core", + "External Flash", + "IMU", + "JST-SH", + "USB", + "WiFi" + ], + "images": [ + "nanoxrp-board.jpg" + ], + "mcu": "rp2040", + "product": "NanoXRP Controller", + "thumbnail": "", + "url": "https://www.experiential.bot/", + "vendor": "Cytron" +} diff --git a/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/manifest.py b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/manifest.py new file mode 100644 index 00000000000..4e38f09cdee --- /dev/null +++ b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +# Bluetooth +require("aioble") diff --git a/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.cmake b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.cmake new file mode 100644 index 00000000000..f80c3351010 --- /dev/null +++ b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.cmake @@ -0,0 +1,14 @@ +# cmake file for Cytron NanoXRP Controller + +set(PICO_BOARD "pico_w") + +set(MICROPY_PY_LWIP ON) +set(MICROPY_PY_NETWORK_CYW43 ON) + +# Bluetooth +set(MICROPY_PY_BLUETOOTH ON) +set(MICROPY_BLUETOOTH_BTSTACK ON) +set(MICROPY_PY_BLUETOOTH_CYW43 ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.h b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.h new file mode 100644 index 00000000000..88a9a9840a7 --- /dev/null +++ b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/mpconfigboard.h @@ -0,0 +1,31 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Cytron NanoXRP Controller" + +// todo: We need something to check our binary size +#define MICROPY_HW_FLASH_STORAGE_BYTES (848 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "NanoXRP" + +// CYW43 driver configuration. +#define CYW43_USE_SPI (1) +#define CYW43_LWIP (1) +#define CYW43_GPIO (1) +#define CYW43_SPI_PIO (1) + +// For debugging mbedtls - also set +// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose +// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1 + +#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT + +// If this returns true for a pin then its irq will not be disabled on a soft reboot +int mp_hal_is_pin_reserved(int n); +#define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) + +#define MICROPY_HW_USB_VID (0x2E8A) +#define MICROPY_HW_USB_PID (0x110A) + +#define MICROPY_HW_I2C1_SDA (14) +#define MICROPY_HW_I2C1_SCL (15) diff --git a/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/pins.csv b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/pins.csv new file mode 100644 index 00000000000..ca8c460e693 --- /dev/null +++ b/ports/rp2/boards/CYTRON_NANOXRP_CONTROLLER/pins.csv @@ -0,0 +1,41 @@ +# XRP default pin names +MOTOR_L_IN_1,GPIO10 +MOTOR_L_IN_2,GPIO11 +MOTOR_R_IN_1,GPIO1 +MOTOR_R_IN_2,GPIO0 +MOTOR_3_IN_1,GPIO7 +MOTOR_3_IN_2,GPIO6 +MOTOR_L_ENCODER_A,GPIO9 +MOTOR_L_ENCODER_B,GPIO8 +MOTOR_R_ENCODER_A,GPIO3 +MOTOR_R_ENCODER_B,GPIO2 +MOTOR_3_ENCODER_A,GPIO5 +MOTOR_3_ENCODER_B,GPIO4 +SERVO_1,GPIO16 +SERVO_2,GPIO17 +I2C_SDA_0,GPIO4 +I2C_SCL_0,GPIO5 +I2C_SDA_1,GPIO14 +I2C_SCL_1,GPIO15 +DISTANCE_TRIGGER,GPIO22 +DISTANCE_ECHO,GPIO21 +LINE_L,GPIO26 +LINE_M,GPIO27 +LINE_R,GPIO28 +BOARD_VIN_MEASURE,GPIO29 +BOARD_USER_BUTTON,GPIO20 +BOARD_NEOPIXEL,GPIO12 +BOARD_BUZZER,GPIO13 +BOARD_LED,EXT_GPIO0 + +# XRP alternate pin names +RANGE_TRIGGER,GPIO22 +RANGE_ECHO,GPIO21 + +# LED default names +LED,EXT_GPIO0 + +# Radio GPIO pins +WL_GPIO0,EXT_GPIO0 +WL_GPIO1,EXT_GPIO1 +WL_GPIO2,EXT_GPIO2 From 3a24bdbe71e676317a6167fc68361181306d601c Mon Sep 17 00:00:00 2001 From: sync-on-luma Date: Fri, 20 Feb 2026 03:43:34 -0800 Subject: [PATCH 1831/2098] rp2/boards/CYTRON_MOTION_2350_PRO: Add Cytron Motion 2350 Pro board. Signed-off-by: sync-on-luma --- .../boards/CYTRON_MOTION_2350_PRO/board.json | 23 +++++++++ .../mpconfigboard.cmake | 5 ++ .../CYTRON_MOTION_2350_PRO/mpconfigboard.h | 3 ++ .../mpconfigvariant.cmake | 1 + .../mpconfigvariant_RISCV.cmake | 1 + .../boards/CYTRON_MOTION_2350_PRO/pins.csv | 50 +++++++++++++++++++ 6 files changed, 83 insertions(+) create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/board.json create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.cmake create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.h create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/CYTRON_MOTION_2350_PRO/pins.csv diff --git a/ports/rp2/boards/CYTRON_MOTION_2350_PRO/board.json b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/board.json new file mode 100644 index 00000000000..25f44568fac --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "USB-C", + "RGB LED" + ], + "images": [ + "motion-2350-pro.jpg" + ], + "mcu": "rp2350", + "product": "MOTION 2350 Pro", + "thumbnail": "", + "url": "https://www.cytron.io/p-motion-2350-pro", + "variants": { + "RISCV": "RISC-V CPU mode" + }, + "vendor": "Cytron" +} diff --git a/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.cmake b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.cmake new file mode 100644 index 00000000000..48b6545aa34 --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.cmake @@ -0,0 +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/CYTRON_MOTION_2350_PRO/mpconfigboard.h b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.h new file mode 100644 index 00000000000..b9399a58e7c --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Cytron MOTION 2350 Pro" +#define MICROPY_HW_FLASH_STORAGE_BYTES (1024 * 1024) diff --git a/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant.cmake b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant.cmake new file mode 100644 index 00000000000..6fe039ba51b --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..65a97fc3350 --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/mpconfigvariant_RISCV.cmake @@ -0,0 +1 @@ +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/CYTRON_MOTION_2350_PRO/pins.csv b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/pins.csv new file mode 100644 index 00000000000..53b51e7dfb4 --- /dev/null +++ b/ports/rp2/boards/CYTRON_MOTION_2350_PRO/pins.csv @@ -0,0 +1,50 @@ +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 +GP23,GPIO23 +GP24,GPIO24 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +GP29,GPIO29 +ADC0,GPIO26 +ADC1,GPIO27 +ADC2,GPIO28 +ADC3,GPIO29 +M1A,GPIO8 +M1B,GPIO9 +M2A,GPIO10 +M2B,GPIO11 +M3A,GPIO12 +M3B,GPIO13 +M4A,GPIO14 +M4B,GPIO15 +BTN1,GPIO20 +BTN2,GPIO21 +LED,GPIO23 +LED_RGB,GPIO23 +RGB_LED,GPIO23 +NEOPIXEL,GPIO23 +USBA_DN,GPIO24 +USBA_UP,GPIO25 From 20ffe6f139a587c98dcd45b4295846d28ee14890 Mon Sep 17 00:00:00 2001 From: Ned Konz Date: Fri, 26 Dec 2025 13:55:14 -0800 Subject: [PATCH 1832/2098] rp2/boards/WAVESHARE_RP2350B_CORE: Add Waveshare RP2350B Core board. Signed-off-by: Ned Konz --- .../boards/WAVESHARE_RP2350B_CORE/board.json | 23 +++++++ .../mpconfigboard.cmake | 10 ++++ .../WAVESHARE_RP2350B_CORE/mpconfigboard.h | 15 +++++ .../mpconfigvariant.cmake | 2 + .../mpconfigvariant_RISCV.cmake | 2 + .../boards/WAVESHARE_RP2350B_CORE/pins.csv | 1 + .../waveshare_rp2350b_core.h | 60 +++++++++++++++++++ 7 files changed, 113 insertions(+) create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/board.json create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.h create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant_RISCV.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/pins.csv create mode 100644 ports/rp2/boards/WAVESHARE_RP2350B_CORE/waveshare_rp2350b_core.h diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/board.json b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/board.json new file mode 100644 index 00000000000..68c095cc469 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "External RAM", + "USB-C" + ], + "images": [ + "waveshare_rp2350b_core.png" + ], + "mcu": "rp2350", + "product": "RP2350B Core", + "thumbnail": "", + "url": "https://www.waveshare.com/core2350b.htm", + "variants": { + "RISCV": "RISC V" + }, + "vendor": "Waveshare" +} diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.cmake b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.cmake new file mode 100644 index 00000000000..5d1d95aa092 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.cmake @@ -0,0 +1,10 @@ +# CMake file for Waveshare RP2350B Core + +# The Core is powered by an RP2350B with 48 GPIOs +set(PICO_NUM_GPIOS 48) + +# The Waveshare boards don't have official pico-sdk support. +# So, add this board directory to the header search path and define PICO_BOARD +# which will instruct pico-sdk to look for waveshare_rp2350b_core.h +list(APPEND PICO_BOARD_HEADER_DIRS ${MICROPY_BOARD_DIR}) +set(PICO_BOARD "waveshare_rp2350b_core") diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.h b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.h new file mode 100644 index 00000000000..ffdfb9df992 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigboard.h @@ -0,0 +1,15 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Waveshare RP2350B Core" +#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) +#define MICROPY_HW_FLASH_STORAGE_BYTES (14 * 1024 * 1024) + +#define MICROPY_HW_PSRAM_CS_PIN (47) +#define MICROPY_HW_ENABLE_PSRAM (1) + +// Override machine_uart.c definitions. +// See waveshare_rp2350b.h and note that the PICO_DEFAULT_UART configuration +// is not currently referenced in machine_uart.c. +#define MICROPY_HW_UART0_TX (0) +#define MICROPY_HW_UART0_RX (1) +#define MICROPY_HW_UART0_CTS (-1) +#define MICROPY_HW_UART0_RTS (-1) diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant.cmake b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant.cmake new file mode 100644 index 00000000000..531c9152440 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant.cmake @@ -0,0 +1,2 @@ +# Set the ARM-based RP2350 platform +set(PICO_PLATFORM "rp2350") diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant_RISCV.cmake b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant_RISCV.cmake new file mode 100644 index 00000000000..9f62e459aa0 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/mpconfigvariant_RISCV.cmake @@ -0,0 +1,2 @@ +# Set the RISC-V-based RP2350 platform +set(PICO_PLATFORM "rp2350-riscv") diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/pins.csv b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/pins.csv new file mode 100644 index 00000000000..bebda28db61 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/pins.csv @@ -0,0 +1 @@ +LED,GPIO39 diff --git a/ports/rp2/boards/WAVESHARE_RP2350B_CORE/waveshare_rp2350b_core.h b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/waveshare_rp2350b_core.h new file mode 100644 index 00000000000..b38e31fcd9c --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2350B_CORE/waveshare_rp2350b_core.h @@ -0,0 +1,60 @@ +#ifndef _BOARDS_WAVESHARE_RP2350B_CORE_COMMON_H +#define _BOARDS_WAVESHARE_RP2350B_CORE_COMMON_H + +// --- LED --- +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 39 +#endif + +// Note: Avoid using Pin 47 for any default peripheral since it can be used for +// extra PSRAM/flash +// NOTE that the PICO_DEFAULT_UART configuration +// is not currently referenced in machine_uart.c. + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 0 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 1 +#endif + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 8 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 9 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 18 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 19 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 16 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 17 +#endif + +// Flash configuration +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +#endif From 1968b964f3f2bdc20af7ddcdffbff4aae541d610 Mon Sep 17 00:00:00 2001 From: EngWill <646689853@qq.com> Date: Thu, 26 Oct 2023 13:49:39 +0800 Subject: [PATCH 1833/2098] rp2/boards/WAVESHARE_RP2040_LCD_0_96: Add Waveshare RP2040 LCD 0.96. Signed-off-by: EngWill <646689853@qq.com> Signed-off-by: Damien George --- .../WAVESHARE_RP2040_LCD_0_96/board.json | 21 +++++++++++++++++++ .../mpconfigboard.cmake | 2 ++ .../WAVESHARE_RP2040_LCD_0_96/mpconfigboard.h | 12 +++++++++++ 3 files changed, 35 insertions(+) create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/board.json create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.h diff --git a/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/board.json b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/board.json new file mode 100644 index 00000000000..d66f208a90c --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "Battery Charging", + "External Flash", + "Display", + "USB-C" + ], + "images": [ + "rp2040-lcd-0.96-1.jpg" + ], + "mcu": "rp2040", + "product": "RP2040-LCD-0.96", + "thumbnail": "", + "url": "https://www.waveshare.com/product/rp2040-lcd-0.96.htm", + "vendor": "Waveshare" +} diff --git a/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.cmake b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.cmake new file mode 100644 index 00000000000..adf96adab20 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.cmake @@ -0,0 +1,2 @@ +# cmake file for Waveshare RP2040-LCD-0.96 +set(PICO_BOARD "waveshare_rp2040_lcd_0.96") diff --git a/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.h b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.h new file mode 100644 index 00000000000..5f5f410ec52 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_LCD_0_96/mpconfigboard.h @@ -0,0 +1,12 @@ +// url: https://www.waveshare.com/product/rp2040-lcd-0.96.htm +// wiki: https://www.waveshare.com/wiki/RP2040-LCD-0.96 + +#define MICROPY_HW_BOARD_NAME "Waveshare RP2040-LCD-0.96" +#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) + +#define MICROPY_HW_USB_VID (0x2E8A) +#define MICROPY_HW_USB_PID (0x1021) + +#define MICROPY_HW_SPI1_SCK (10u) +#define MICROPY_HW_SPI1_MOSI (11u) +#define MICROPY_HW_SPI1_MISO (8u) From 8cbd32080979e9999fa771f5e298afdb74ecd887 Mon Sep 17 00:00:00 2001 From: EngWill <646689853@qq.com> Date: Thu, 26 Oct 2023 13:49:39 +0800 Subject: [PATCH 1834/2098] rp2/boards/WAVESHARE_RP2040_PLUS: Add Waveshare RP2040 Plus 4M and 16M. Includes 4MB and 16MB variants. Signed-off-by: EngWill <646689853@qq.com> Signed-off-by: Damien George --- .../boards/WAVESHARE_RP2040_PLUS/board.json | 23 +++++++++++++++++++ .../WAVESHARE_RP2040_PLUS/mpconfigboard.cmake | 1 + .../WAVESHARE_RP2040_PLUS/mpconfigboard.h | 7 ++++++ .../mpconfigvariant.cmake | 5 ++++ .../mpconfigvariant_FLASH_16M.cmake | 5 ++++ 5 files changed, 41 insertions(+) create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_PLUS/board.json create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.h create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant_FLASH_16M.cmake diff --git a/ports/rp2/boards/WAVESHARE_RP2040_PLUS/board.json b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/board.json new file mode 100644 index 00000000000..a338eb619e6 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "Battery Charging", + "External Flash", + "USB-C" + ], + "images": [ + "rp2040-plus-1.jpg" + ], + "mcu": "rp2040", + "product": "RP2040-Plus", + "thumbnail": "", + "url": "https://www.waveshare.com/rp2040-plus.htm", + "variants": { + "FLASH_16M": "16 MiB Flash" + }, + "vendor": "Waveshare" +} diff --git a/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.cmake b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.cmake new file mode 100644 index 00000000000..09b8fba590f --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.cmake @@ -0,0 +1 @@ +# cmake file for Waveshare RP2040-Plus diff --git a/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.h b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.h new file mode 100644 index 00000000000..ac1a394a2fa --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigboard.h @@ -0,0 +1,7 @@ +// url : https://www.waveshare.com/product/rp2040-plus.htm +// wiki : https://www.waveshare.com/wiki/RP2040-Plus + +#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - (1 * 1024 * 1024)) + +#define MICROPY_HW_USB_VID (0x2E8A) +#define MICROPY_HW_USB_PID (0x1020) diff --git a/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant.cmake b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant.cmake new file mode 100644 index 00000000000..d8469db2515 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant.cmake @@ -0,0 +1,5 @@ +set(PICO_BOARD "waveshare_rp2040_plus_4mb") + +list(APPEND MICROPY_DEF_BOARD + MICROPY_HW_BOARD_NAME="Waveshare RP2040-Plus 4MB" +) diff --git a/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant_FLASH_16M.cmake b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant_FLASH_16M.cmake new file mode 100644 index 00000000000..3823d0413b6 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_PLUS/mpconfigvariant_FLASH_16M.cmake @@ -0,0 +1,5 @@ +set(PICO_BOARD "waveshare_rp2040_plus_16mb") + +list(APPEND MICROPY_DEF_BOARD + MICROPY_HW_BOARD_NAME="Waveshare RP2040-Plus 16MB" +) From ad054fc520f8eaeec8df211e993c6dd0c7409aea Mon Sep 17 00:00:00 2001 From: EngWill <646689853@qq.com> Date: Thu, 26 Oct 2023 13:49:39 +0800 Subject: [PATCH 1835/2098] rp2/boards/WAVESHARE_RP2040_ZERO: Add Waveshare RP2040 Zero board. Signed-off-by: EngWill <646689853@qq.com> Signed-off-by: Damien George --- .../boards/WAVESHARE_RP2040_ZERO/board.json | 20 +++++++++++++++++++ .../WAVESHARE_RP2040_ZERO/mpconfigboard.cmake | 2 ++ .../WAVESHARE_RP2040_ZERO/mpconfigboard.h | 8 ++++++++ .../rp2/boards/WAVESHARE_RP2040_ZERO/pins.csv | 1 + 4 files changed, 31 insertions(+) create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_ZERO/board.json create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.cmake create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.h create mode 100644 ports/rp2/boards/WAVESHARE_RP2040_ZERO/pins.csv diff --git a/ports/rp2/boards/WAVESHARE_RP2040_ZERO/board.json b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/board.json new file mode 100644 index 00000000000..01cb67fd8dc --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/board.json @@ -0,0 +1,20 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "RGB LED", + "USB-C" + ], + "images": [ + "rp2040-zero-1.jpg" + ], + "mcu": "rp2040", + "product": "RP2040-Zero", + "thumbnail": "", + "url": "https://www.waveshare.com/product/rp2040-zero.htm", + "vendor": "Waveshare" +} diff --git a/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.cmake b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.cmake new file mode 100644 index 00000000000..aa664498678 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.cmake @@ -0,0 +1,2 @@ +# cmake file for Waveshare RP2040-Zero +set(PICO_BOARD "waveshare_rp2040_zero") diff --git a/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.h b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.h new file mode 100644 index 00000000000..1f32dda43c3 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/mpconfigboard.h @@ -0,0 +1,8 @@ +// url: https://www.waveshare.com/product/rp2040-zero.htm +// wiki: http://www.waveshare.com/wiki/RP2040-Zero + +#define MICROPY_HW_BOARD_NAME "Waveshare RP2040-Zero" +#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) + +#define MICROPY_HW_USB_VID (0x2E8A) +#define MICROPY_HW_USB_PID (0x101F) diff --git a/ports/rp2/boards/WAVESHARE_RP2040_ZERO/pins.csv b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/pins.csv new file mode 100644 index 00000000000..51805201252 --- /dev/null +++ b/ports/rp2/boards/WAVESHARE_RP2040_ZERO/pins.csv @@ -0,0 +1 @@ +NEOPIXEL,GPIO16 From 43199278eb52eaeefcec516e70dbfd64aa0f5cf9 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 25 Feb 2026 22:40:10 +1100 Subject: [PATCH 1836/2098] tests/run-tests.py: Ignore known-flaky test failures. Reclassify failures of tests listed in flaky_tests_to_ignore as "ignored" instead of retrying them. Ignored tests still run and their output is reported, but they don't affect the exit code. The ci.sh --exclude lists for these tests are removed so they run normally. Signed-off-by: Andrew Leech --- tests/run-tests.py | 28 ++++++++++++++++++++++++++++ tests/test_utils.py | 16 +++++++++++++++- tools/ci.sh | 18 +++++------------- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 2add13df21f..84daf4cbbf8 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -31,6 +31,7 @@ get_test_instance, prepare_script_for_target, create_test_report, + FLAKY_REASON_PREFIX, ) RV32_ARCH_FLAGS = { @@ -193,6 +194,23 @@ ), } +# Tests with known intermittent failures. These tests still run, but failures +# are reclassified as "ignored" instead of "fail" so they don't affect the CI +# exit code. Paths are relative to the tests/ directory (must match test_file +# format used by run_one_test, which normalises backslashes to forward slashes). +# +# Values are (reason, platforms) tuples where platforms is None (all platforms) +# or a tuple of sys.platform strings to restrict ignoring to those platforms. +flaky_tests_to_ignore = { + "thread/thread_gc1.py": ("GC race condition", None), + "thread/stress_schedule.py": ("intermittent crash under QEMU", None), + "thread/stress_recurse.py": ("stack overflow under emulation", None), + "thread/stress_heap.py": ("flaky on macOS", ("darwin",)), + "cmdline/repl_lock.py": ("REPL timing under QEMU", None), + "cmdline/repl_cont.py": ("REPL escaping on macOS", ("darwin",)), + "extmod/time_time_ns.py": ("CI runner clock precision", None), +} + # These tests don't test float explicitly but rather use it to perform the test. tests_requiring_float = ( "extmod/asyncio_basic.py", @@ -1062,6 +1080,16 @@ def run_one_test(test_file): print(line) sys.exit(2) + # Reclassify known-flaky test failures as ignored. + # Safe to mutate: thread pool has joined. + results = test_results.value + for i, r in enumerate(results): + if r[1] == "fail": + reason, platforms = flaky_tests_to_ignore.get(r[0], (None, None)) + if reason is not None: + if platforms is None or sys.platform in platforms: + results[i] = (r[0], "ignored", "{}: {}".format(FLAKY_REASON_PREFIX, reason)) + # Return test results. return test_results.value, testcase_count.value diff --git a/tests/test_utils.py b/tests/test_utils.py index 99b92ea7b30..7e43c4cae9f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -22,6 +22,9 @@ def base_path(*p): sys.path.append(base_path("../tools")) import pyboard +# Prefix used by run-tests.py to tag known-flaky test results. +FLAKY_REASON_PREFIX = "flaky" + # File with the test results. _RESULTS_FILE = "_results.json" @@ -313,11 +316,12 @@ def create_test_report(args, test_results, testcase_count=None): r for r in test_results if r[1] == "skip" and r[2] == "too large" ) failed_tests = list(r for r in test_results if r[1] == "fail") + ignored_tests = list(r for r in test_results if r[1] == "ignored") dry_run = getattr(args, "dry_run", False) if dry_run: found_tests = list(r for r in test_results if r[1] == "found") - num_tests_performed = len(passed_tests) + len(failed_tests) + num_tests_performed = len(passed_tests) + len(failed_tests) + len(ignored_tests) if dry_run: print("{} tests found".format(len(found_tests))) @@ -329,6 +333,14 @@ def create_test_report(args, test_results, testcase_count=None): print("{} tests passed".format(len(passed_tests))) + if len(ignored_tests) > 0: + print( + "{} tests had known-flaky failures (ignored): {}".format( + len(ignored_tests), + " ".join("{} [{}]".format(t[0], t[2]) for t in ignored_tests), + ) + ) + if len(skipped_tests) > 0: print( "{} tests skipped: {}".format( @@ -365,6 +377,8 @@ def to_json(obj): "results": list(test for test in test_results), # A list of failed tests. This is deprecated, use the "results" above instead. "failed_tests": [test[0] for test in failed_tests], + # A list of known-flaky tests whose failures were ignored. + "ignored_tests": [test[0] for test in ignored_tests], }, f, default=to_json, diff --git a/tools/ci.sh b/tools/ci.sh index 588bb31638c..6bc74bd54ce 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -905,9 +905,7 @@ function ci_unix_macos_run_tests { # Issues with macOS tests: # - float_parse and float_parse_doubleprec parse/print floats out by a few mantissa bits # - ffi_callback crashes for an unknown reason - # - thread/stress_heap.py is flaky - # - thread/thread_gc1.py is flaky - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback|thread/stress_heap|thread/thread_gc1).py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython ./run-tests.py --exclude '(float_parse|float_parse_doubleprec|ffi_callback).py') } function ci_unix_qemu_mips_setup { @@ -927,10 +925,8 @@ function ci_unix_qemu_mips_build { function ci_unix_qemu_mips_run_tests { # Issues with MIPS tests: # - thread/stress_aes.py takes around 90 seconds - # - thread/stress_recurse.py is flaky - # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=180 ./run-tests.py) } function ci_unix_qemu_arm_setup { @@ -950,10 +946,8 @@ function ci_unix_qemu_arm_build { function ci_unix_qemu_arm_run_tests { # Issues with ARM tests: # - thread/stress_aes.py takes around 70 seconds - # - thread/stress_recurse.py is flaky - # - thread/thread_gc1.py is flaky file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py') + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py) } function ci_unix_qemu_riscv64_setup { @@ -976,12 +970,10 @@ function ci_unix_qemu_riscv64_build { function ci_unix_qemu_riscv64_run_tests { # Issues with RISCV-64 tests: - # - thread/stress_aes.py takes around 180 seconds - # - thread/stress_recurse.py is flaky - # - thread/thread_gc1.py is flaky + # - thread/stress_aes.py takes around 180 seconds, so exclude it to keep execution time down file ./ports/unix/build-coverage/micropython pushd tests - MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=200 ./run-tests.py --exclude 'thread/stress_recurse.py|thread/thread_gc1.py' + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py --exclude 'thread/stress_aes.py' MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-natmodtests.py extmod/btree*.py extmod/deflate*.py extmod/framebuf*.py extmod/heapq*.py extmod/random_basic*.py extmod/re*.py popd } From fc5195bfad49a9853f7c15cf8c0d1dd901a62ce0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Oct 2025 11:42:40 +1000 Subject: [PATCH 1837/2098] README: Remove warning about the project being in beta stage. It's been over 12 years and the project is relatively stable now. Signed-off-by: Damien George --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 375ff17d924..49b1d7ec3eb 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,10 @@ The MicroPython project MicroPython Logo

-This is the MicroPython project, which aims to put an implementation -of Python 3.x on microcontrollers and small embedded systems. +This is the MicroPython project, an implementation of Python 3.x for +microcontrollers, embedded systems and other constrained platforms. You can find the official website at [micropython.org](http://www.micropython.org). -WARNING: this project is in beta stage and is subject to changes of the -code-base, including project-wide name changes and API changes. - MicroPython implements the entire Python 3.4 syntax (including exceptions, `with`, `yield from`, etc., and additionally `async`/`await` keywords from Python 3.5 and some select features from later versions). The following core From 4625f97d09b1e3d2b6ef8a38ce2023370ad4aeed Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 2 Oct 2025 12:18:20 +1000 Subject: [PATCH 1838/2098] README: Add a section describing MicroPython's values. This commit adds a section to the top-level README describing MicroPython's general design philosophy and core values. Thanks to @projectgus who suggested I add this. Signed-off-by: Damien George --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 49b1d7ec3eb..5f6d4288bf2 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,44 @@ the officially supported board from the see the [schematics and pinouts](http://github.com/micropython/pyboard) and [documentation](https://docs.micropython.org/en/latest/pyboard/quickref.html). +MicroPython design values +------------------------- + +"Perfection is achieved, not when there is nothing more to add, but when there +is nothing left to take away." ―- Antoine de Saint-Exupéry. + +For its design and implementation, MicroPython aims to follow a set of values. +Although not a strict set of rules, these values and principles serve as a +useful guide for new and seasoned contributors, as well as maintainers. + +MicroPython is at heart a combination of "Micro" and "Python": it's about +resource constrained systems running the Python programming language. Both of +these concepts balance off against each other in all parts of MicroPython's +design and implementation. + +The key concepts that focus the development of MicroPython are: +- Minimalism: do lots with little. +- Efficiency: engineering, build, execution, storage, power consumption. +- Consistency: the whole system feels like it was designed at once. + +When using MicroPython, the Python language is used as the human interface to a +system, giving fine control over the entities attached to that system. +In a hardware setting, MicroPython aims to give the user a bare-metal feeling: +one should feel like they have complete control over the system, with very +little between the programmer and the physical world. + +MicroPython recognises that systems can be very complex. The existing Python +libraries in combination with the MicroPython-specific libraries provide a +user-friendly way to harness the complexity of a system. + +Python language compatibility is very important to MicroPython, and at first +glance MicroPython should look just like regular Python. In the first instance, +most Python scripts should run unchanged on MicroPython, even on devices with very +tight resources. Beyond that, there are ways to extend MicroPython if needed to +better match Python. The provided built-in modules are an efficient subset of +the corresponding Python ones, without duplication of functionality, and allow +extension in Python if needed. + Contributing ------------ From e4920d63f7f4fddaae47e343434f93d44c6f2e40 Mon Sep 17 00:00:00 2001 From: Didier C <7452700+europrimus@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:02:08 +0100 Subject: [PATCH 1839/2098] docs/esp32: Replace 'esptool.py' by 'esptool' in command line example. 'esptool.py' is deprecated, use 'esptool' instead. Signed-off-by: europrimus --- docs/esp32/tutorial/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/esp32/tutorial/intro.rst b/docs/esp32/tutorial/intro.rst index 599731ad755..2fca5e270a3 100644 --- a/docs/esp32/tutorial/intro.rst +++ b/docs/esp32/tutorial/intro.rst @@ -85,7 +85,7 @@ after flashing, here are some troubleshooting recommendations: * Esptool will try to detect the serial port where your ESP32 is connected. If this doesn't work, or you have multiple serial ports, then you may need to manually specify the port by adding the ``--port`` option to the start of the - ``esptool.py`` command line. For example, ``esptool.py --port /dev/ttyUSB0 + ``esptool`` command line. For example, ``esptool --port /dev/ttyUSB0 `` for Linux or ``esptool --port COM4 `` for Windows. * If the board isn't responding to esptool at all, it may need to be manually From df9b714af4f98960f03e951e0a71e994be79c006 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 12 Mar 2026 13:21:46 +0100 Subject: [PATCH 1840/2098] docs/library/re: Document non-capturing grouping. This commit updates the documentation for the `re` library, officially documenting non-capturing grouping rules (ie. "(?:...)"). The documentation mistakenly marked that feature as not supported, but is is indeed supported in the current iteration of the regex library. This closes #18900. Signed-off-by: Alessandro Gatti --- docs/library/re.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/library/re.rst b/docs/library/re.rst index b8aeefd90cf..47f623c5600 100644 --- a/docs/library/re.rst +++ b/docs/library/re.rst @@ -54,6 +54,10 @@ Supported operators and special sequences are: Grouping. Each group is capturing (a substring it captures can be accessed with `match.group()` method). +``(?:...)`` + Non-capturing grouping. Each group is matched using the same rules as + regular grouping, but will not be part of the match object. + ``\d`` Matches digit. Equivalent to ``[0-9]``. @@ -87,7 +91,6 @@ Supported operators and special sequences are: * counted repetitions (``{m,n}``) * named groups (``(?P...)``) -* non-capturing groups (``(?:...)``) * more advanced assertions (``\b``, ``\B``) * special character escapes like ``\r``, ``\n`` - use Python's own escaping instead From 06dbc1f48653badb5488fece192f233fc13dc2a8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 15:05:36 +1100 Subject: [PATCH 1841/2098] github: Revert "Run esp32&zephyr daily to keep mstr branch caches hot". This reverts commit 046013a1ffbeccb971b6067ff389ebd0350b9e9c. Looks like since the latest round of GitHub Actions updates, the Cache LRU algorithm is working as designed again. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_esp32.yml | 4 ---- .github/workflows/ports_zephyr.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 87ab6dbe355..446db794cb4 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -12,10 +12,6 @@ on: - 'lib/**' - 'drivers/**' - 'ports/esp32/**' - schedule: - # Scheduled run exists to keep master branch ESP-IDF cache entry hot - # and prevent creating many redundant per-branch cache entries instead. - - cron: "20 0 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml index 571a443e903..330121d1de6 100644 --- a/.github/workflows/ports_zephyr.yml +++ b/.github/workflows/ports_zephyr.yml @@ -12,10 +12,6 @@ on: - 'lib/**' - 'ports/zephyr/**' - 'tests/**' - schedule: - # Scheduled run exists to keep master branch Zephyr cache entry hot - # and prevent creating many redundant per-branch cache entries instead. - - cron: "40 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} From d2cda57e9d6a6dedecf7f581c4e4e960d9fdd877 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 10:50:19 +1100 Subject: [PATCH 1842/2098] rp2/rp2_dma: Disable DMA IRQ before clearing handler function. Both the overall IRQ line and the per-channel IRQ, for good measure. Otherwise, soft reset will remove the handler before the finaliser for the DMA object(s) run and trigger IRQs if the channel is still active. Closes #18765 This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/rp2_dma.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 471cf24ea2c..bb935f3b484 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -427,6 +427,9 @@ static mp_obj_t rp2_dma_close(mp_obj_t self_in) { uint8_t channel = self->channel; if (channel != CHANNEL_CLOSED) { + // Disable channel IRQ + dma_channel_set_irq0_enabled(channel, false); + // Reset this channel's registers to their default values (zeros). dma_channel_config config = { .ctrl = 0 }; dma_channel_configure(channel, &config, NULL, NULL, 0, false); @@ -441,7 +444,6 @@ static mp_obj_t rp2_dma_close(mp_obj_t self_in) { if (irq) { irq->parent = MP_OBJ_NULL; irq->handler = MP_OBJ_NULL; - dma_channel_set_irq0_enabled(channel, false); } dma_channel_unclaim(channel); self->channel = CHANNEL_CLOSED; @@ -479,7 +481,8 @@ void rp2_dma_init(void) { } void rp2_dma_deinit(void) { - // Remove our interrupt handler. + // Disable and remove our interrupt handler. + irq_set_enabled(DMA_IRQ_0, false); irq_remove_handler(DMA_IRQ_0, rp2_dma_irq_handler); } From 82c6b0e59486496d98d8c401924d1ce6a7b6d523 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 14:20:29 +1100 Subject: [PATCH 1843/2098] esp32: Only check the lockfile currently used by the build. Small tweak to avoid changes in other targets' lockfiles from printing warnings when building esp32 port. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index ee062be203d..c739368c550 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -63,19 +63,20 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # Generate individual dependencies.lock files based on chip target -idf_build_set_property(DEPENDENCIES_LOCK lockfiles/dependencies.lock.${IDF_TARGET}) +set(LOCKFILE_PATH lockfiles/dependencies.lock.${IDF_TARGET}) +idf_build_set_property(DEPENDENCIES_LOCK ${LOCKFILE_PATH}) # Define the project. project(micropython) # Check for lockfile changes and either warn or error depending on build type message("Checking lockfile contents...") -execute_process(COMMAND git diff --exit-code lockfiles/ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} +execute_process(COMMAND git diff --exit-code ${LOCKFILE_PATH} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE RES) if (RES) # Maintainer builds (CI or autobuild runs) should fail if this has happened if($ENV{MICROPY_MAINTAINER_BUILD}) - message(FATAL_ERROR "Failing build as lockfiles are dirty (see above). Check that ESP-IDF versions match.") + message(FATAL_ERROR "Failing build as lockfile is dirty (see above). Check that ESP-IDF versions match.") else() message(WARNING "Component lockfile contents have changed (see above). This may be due to building with a different ESP-IDF version. Please mention this output if reporting an issue with MicroPython.") endif() From fe32e1d3a17d4210bbdc342bb2e291910d7bfec9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 12:00:20 +1100 Subject: [PATCH 1844/2098] esp32: Drop support for ESP-IDF --- ports/esp32/README.md | 2 +- ports/esp32/esp32_common.cmake | 8 -------- ports/esp32/lockfiles/dependencies.lock.esp32 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c2 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c3 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c5 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32c6 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32p4 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32s2 | 2 +- ports/esp32/lockfiles/dependencies.lock.esp32s3 | 2 +- ports/esp32/machine_bitstream.c | 7 ------- ports/esp32/machine_touchpad.c | 8 -------- ports/esp32/main/idf_component.yml | 3 +-- ports/esp32/modnetwork.h | 4 ++-- ports/esp32/network_wlan.c | 4 ---- ports/esp32/usb_serial_jtag.c | 2 -- 16 files changed, 12 insertions(+), 42 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index b5cd1c2a8c6..2cfc09afadf 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -53,7 +53,7 @@ build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. The current recommended version of ESP-IDF for MicroPython is v5.5.1. MicroPython -also supports v5.2, v5.2.2, v5.3, v5.4, v5.4.1 and v5.4.2. +also supports v5.3, v5.4, v5.4.1 and v5.4.2. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index d6a4aedb85c..5ad7f7e003e 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -277,14 +277,6 @@ target_compile_options(${MICROPY_TARGET} PUBLIC target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble ) -if (IDF_VERSION VERSION_LESS "5.3") -# Additional include directories needed for private RMT header. -# IDF 5.x versions before 5.3.1 - message(STATUS "Using private rmt headers for ${IDF_VERSION}") - target_include_directories(${MICROPY_TARGET} PRIVATE - ${IDF_PATH}/components/driver/rmt - ) -endif() # Add additional extmod and usermod components. if (MICROPY_PY_BTREE) diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32 b/ports/esp32/lockfiles/dependencies.lock.esp32 index 8ba25c77011..e242478083e 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32 @@ -30,6 +30,6 @@ direct_dependencies: - espressif/lan867x - espressif/mdns - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c2 b/ports/esp32/lockfiles/dependencies.lock.esp32c2 index 8a366af3423..a6bbf8d61c8 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c2 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32c2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c3 b/ports/esp32/lockfiles/dependencies.lock.esp32c3 index 3aa99692d9f..4f99727f4e8 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c3 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32c3 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c5 b/ports/esp32/lockfiles/dependencies.lock.esp32c5 index 2fb130b8e52..ac6b1d99d91 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c5 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c5 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32c5 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32c6 b/ports/esp32/lockfiles/dependencies.lock.esp32c6 index c81806909a6..8dfb4d77bc2 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32c6 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32c6 @@ -16,6 +16,6 @@ dependencies: direct_dependencies: - espressif/mdns - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32c6 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32p4 b/ports/esp32/lockfiles/dependencies.lock.esp32p4 index aea6ec2cc36..4582830b5b9 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32p4 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32p4 @@ -88,6 +88,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32p4 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s2 b/ports/esp32/lockfiles/dependencies.lock.esp32s2 index 8717181c10a..417b999f85a 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s2 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s2 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32s2 version: 2.0.0 diff --git a/ports/esp32/lockfiles/dependencies.lock.esp32s3 b/ports/esp32/lockfiles/dependencies.lock.esp32s3 index 0b8b8e92bd5..f0376c9a8d6 100644 --- a/ports/esp32/lockfiles/dependencies.lock.esp32s3 +++ b/ports/esp32/lockfiles/dependencies.lock.esp32s3 @@ -32,6 +32,6 @@ direct_dependencies: - espressif/mdns - espressif/tinyusb - idf -manifest_hash: 482087bc40f0e187795a9ef9ad08ef15a585b4cdabc296c715a9d19284622150 +manifest_hash: 40b684ab14058130e675aab422296e4ad9d87ee39c5aa46d7b3df55c245e14f5 target: esp32s3 version: 2.0.0 diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index 60addcc15b6..d44f9e71a40 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -95,9 +95,6 @@ static void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, u /******************************************************************************/ // RMT implementation -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) -#include "rmt_private.h" -#endif #include "driver/rmt_tx.h" #include "driver/rmt_encoder.h" @@ -159,11 +156,7 @@ static bool machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timin // Disable and release channel. check_esp_err(rmt_del_encoder(encoder)); rmt_disable(channel); - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) - channel->del(channel); - #else rmt_del_channel(channel); - #endif // Cancel RMT output to GPIO pin. esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index 88b34d64ff0..61a3bcf83db 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -31,14 +31,6 @@ #if SOC_TOUCH_SENSOR_SUPPORTED -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) -#if SOC_TOUCH_VERSION_1 -#define SOC_TOUCH_SENSOR_VERSION (1) -#elif SOC_TOUCH_VERSION_2 -#define SOC_TOUCH_SENSOR_VERSION (2) -#endif -#endif - #if SOC_TOUCH_SENSOR_VERSION == 1 // ESP32 only #include "driver/touch_pad.h" #elif SOC_TOUCH_SENSOR_VERSION == 2 // most ESP32 diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index 176e29c3c48..47ae737b39c 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -19,6 +19,5 @@ dependencies: version: "~1.0.0" rules: - if: "target == esp32" - - if: "idf_version >=5.3" idf: - version: ">=5.2.0" + version: ">=5.3.0" diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index a68db41a3e3..68260dd19a3 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -29,8 +29,8 @@ #include "esp_wifi_types.h" #include "esp_netif.h" -// lan867x component requires newer IDF version -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && CONFIG_IDF_TARGET_ESP32 +// lan867x component requires Original ESP32 +#if CONFIG_IDF_TARGET_ESP32 #define PHY_LAN867X_ENABLED (1) #else #define PHY_LAN867X_ENABLED (0) diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index 07b16e91cb9..5433bf862f1 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -769,9 +769,7 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT_192), MP_ROM_INT(WIFI_AUTH_WPA3_ENT_192) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) { MP_ROM_QSTR(MP_QSTR_SEC_DPP), MP_ROM_INT(WIFI_AUTH_DPP) }, - #endif #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA3_ENTERPRISE) }, { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, @@ -797,8 +795,6 @@ _Static_assert(WIFI_AUTH_MAX == 17, "Synchronize WIFI_AUTH_XXX constants with th _Static_assert(WIFI_AUTH_MAX == 16, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) _Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); -#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) -_Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); #else #error "Error in macro logic, all supported versions should be covered." #endif diff --git a/ports/esp32/usb_serial_jtag.c b/ports/esp32/usb_serial_jtag.c index 2df7e200862..86c89385fae 100644 --- a/ports/esp32/usb_serial_jtag.c +++ b/ports/esp32/usb_serial_jtag.c @@ -36,9 +36,7 @@ #include "freertos/portmacro.h" // Number of bytes in the input buffer, and number of bytes for output chunking. -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #define USB_SERIAL_JTAG_PACKET_SZ_BYTES (64) -#endif static DRAM_ATTR portMUX_TYPE rx_mux = portMUX_INITIALIZER_UNLOCKED; static uint8_t rx_buf[USB_SERIAL_JTAG_PACKET_SZ_BYTES]; From f625d2ed6c6ae6be44cc2763fe632a43ce216bcf Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 14:15:37 +1100 Subject: [PATCH 1845/2098] esp32: Fix build for ESP-IDF version 5.3. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/esp32_common.cmake | 6 +++++- ports/esp32/machine_timer.c | 8 ++++++++ ports/esp32/mpconfigport.h | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 5ad7f7e003e..86ea1aaf4ab 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -169,7 +169,6 @@ list(APPEND IDF_COMPONENTS esp_app_format esp_mm esp_common - esp_driver_touch_sens esp_eth esp_event esp_hw_support @@ -198,6 +197,11 @@ list(APPEND IDF_COMPONENTS vfs ) +if($ENV{IDF_VERSION} VERSION_GREATER_EQUAL "5.4") + list(APPEND IDF_COMPONENTS + esp_driver_touch_sens) +endif() + # Provide the default LD fragment if not set if (MICROPY_USER_LDFRAGMENTS) set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index b9cd80f48ef..d953f324b9c 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -183,7 +183,15 @@ void machine_timer_enable(machine_timer_obj_t *self) { } timer_ll_enable_counter(self->hal_context.dev, self->index, false); + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) esp_clk_tree_enable_src(TIMER_CLK_SRC, true); + #elif TIMER_CLK_SRC != SOC_MOD_CLK_APB + // esp_clk_tree_enable_src() is only required on some newer chips where timer + // source clock may not be enabled by default + #error "This chip requires ESP-IDF v5.4 or newer for working Timer." + #endif + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) timer_ll_set_clock_source(self->hal_context.dev, self->index, TIMER_CLK_SRC); timer_ll_enable_clock(self->hal_context.dev, self->index, true); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index dd8f89c4bc4..a1b4594da56 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -140,8 +140,8 @@ #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #ifndef MICROPY_PY_MACHINE_I2C_TARGET -// I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs. -#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32) +// I2C target hardware is limited on ESP32 (eg read event comes after the read) so we only support newer SoCs & ESP-IDF v5.4 or newer +#define MICROPY_PY_MACHINE_I2C_TARGET (SOC_I2C_SUPPORT_SLAVE && !CONFIG_IDF_TARGET_ESP32 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/esp32/machine_i2c_target.c" #define MICROPY_PY_MACHINE_I2C_TARGET_MAX (2) #endif From bac45e5aafed2eca81b8cbfb85bbda68a64ff513 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Mar 2026 10:50:22 +1100 Subject: [PATCH 1846/2098] tests/ports/stm32/can: Update pyb.CAN tests for FDCAN. Also rename the prefix from can to pyb_can, in anticipation of machine.CAN tests. Signed-off-by: Angus Gratton --- tests/ports/stm32/can2.py | 25 --- tests/ports/stm32/{can.py => pyb_can.py} | 209 +++++++++--------- .../stm32/{can.py.exp => pyb_can.py.exp} | 55 ++--- tests/ports/stm32/pyb_can2.py | 50 +++++ tests/ports/stm32/pyb_can2.py.exp | 5 + .../ports/stm32/pyb_can_classic_rtr_filter.py | 30 +++ ....exp => pyb_can_classic_rtr_filter.py.exp} | 0 tests/ports/stm32/pyb_can_classic_rx.py | 89 ++++++++ tests/ports/stm32/pyb_can_classic_rx.py.exp | 25 +++ 9 files changed, 315 insertions(+), 173 deletions(-) delete mode 100644 tests/ports/stm32/can2.py rename tests/ports/stm32/{can.py => pyb_can.py} (52%) rename tests/ports/stm32/{can.py.exp => pyb_can.py.exp} (62%) create mode 100644 tests/ports/stm32/pyb_can2.py create mode 100644 tests/ports/stm32/pyb_can2.py.exp create mode 100644 tests/ports/stm32/pyb_can_classic_rtr_filter.py rename tests/ports/stm32/{can2.py.exp => pyb_can_classic_rtr_filter.py.exp} (100%) create mode 100644 tests/ports/stm32/pyb_can_classic_rx.py create mode 100644 tests/ports/stm32/pyb_can_classic_rx.py.exp diff --git a/tests/ports/stm32/can2.py b/tests/ports/stm32/can2.py deleted file mode 100644 index 2ce438f1af9..00000000000 --- a/tests/ports/stm32/can2.py +++ /dev/null @@ -1,25 +0,0 @@ -try: - from pyb import CAN - - CAN(2) -except (ImportError, ValueError): - print("SKIP") - raise SystemExit - -# Testing rtr messages -bus2 = CAN(2, CAN.LOOPBACK) -while bus2.any(0): - bus2.recv(0) -bus2.setfilter(0, CAN.LIST32, 0, (1, 2), rtr=(True, True), extframe=True) -bus2.setfilter(1, CAN.LIST32, 0, (3, 4), rtr=(True, False), extframe=True) -bus2.setfilter(2, CAN.MASK32, 0, (16, 16), rtr=(False,), extframe=True) -bus2.setfilter(2, CAN.MASK32, 0, (32, 32), rtr=(True,), extframe=True) - -bus2.send("", 1, rtr=True, extframe=True) -print(bus2.recv(0)) -bus2.send("", 2, rtr=True, extframe=True) -print(bus2.recv(0)) -bus2.send("", 3, rtr=True, extframe=True) -print(bus2.recv(0)) -bus2.send("", 4, rtr=True, extframe=True) -print(bus2.any(0)) diff --git a/tests/ports/stm32/can.py b/tests/ports/stm32/pyb_can.py similarity index 52% rename from tests/ports/stm32/can.py rename to tests/ports/stm32/pyb_can.py index 020efae0531..e8a86375668 100644 --- a/tests/ports/stm32/can.py +++ b/tests/ports/stm32/pyb_can.py @@ -7,6 +7,15 @@ from array import array import micropython import pyb +import sys + +# Classic CAN (aka bxCAN) hardware has a different filter API +# and some different behaviours to newer FDCAN hardware +IS_CLASSIC = hasattr(CAN, "MASK16") + +# STM32H7 series has a gold-plated FDCAN peripheral with much deeper TX Queue +# than all other parts +IS_H7 = (not IS_CLASSIC) and "STM32H7" in str(sys.implementation) # test we can correctly create by id (2 handled in can2.py test) for bus in (-1, 0, 1, 3): @@ -25,22 +34,26 @@ can.init(CAN.LOOPBACK, num_filter_banks=14) print(can) -print(can.any(0)) +print("any", can.any(0)) # Test state when freshly created -print(can.state() == can.ERROR_ACTIVE) +print("error_active", can.state() == can.ERROR_ACTIVE) # Test that restart can be called can.restart() # Test info returns a sensible value -print(can.info()) +print("info", can.info()) -# Catch all filter -can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0)) +# Catch all filter (standard IDs) +if IS_CLASSIC: + can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0)) +else: + can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=False) can.send("abcd", 123, timeout=5000) -print(can.any(0), can.info()) +pyb.delay(10) # For FDCAN, needs some time to send +print("any+info", can.any(0), can.info()) print(can.recv(0)) can.send("abcd", -1, timeout=5000) @@ -51,11 +64,16 @@ # Test too long message try: - can.send("abcdefghi", 0x7FF, timeout=5000) + if IS_CLASSIC: + payload = "abcdefghi" # 9 bytes long + else: + # the pyb.CAN API for FDCAN always accepts messages up to 64 bytes and sends as FDCAN + payload = b"x" * 65 + can.send(payload, 0x7FF, timeout=5000) except ValueError: - print("passed") + print("overlong passed") else: - print("failed") + print("overlong failed") # Test that recv can work without allocating memory on the heap @@ -135,7 +153,13 @@ can = CAN(1, CAN.LOOPBACK) # Catch all filter, but only for extframe's -can.setfilter(0, CAN.MASK32, 0, (0, 0), extframe=True) +if IS_CLASSIC: + can.setfilter(0, CAN.MASK32, 0, (0, 0), extframe=True) +else: + # FDCAN manages standard and extframe IDs independently, so need to + # clear the standard ID filter and then set the extended ID filter + can.clearfilter(0, extframe=False) + can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=True) print(can) @@ -144,11 +168,12 @@ except ValueError: print("failed") else: + pyb.delay(10) r = can.recv(0) if r[0] == 0x7FF + 1 and r[4] == b"abcde": - print("passed") + print("extframe passed") else: - print("failed, wrong data received") + print("failed, wrong data received", r) # Test filters for n in [0, 8, 16, 24]: @@ -158,109 +183,40 @@ id_fail = 0b00011010 << n can.clearfilter(0, extframe=True) - can.setfilter(0, pyb.CAN.MASK32, 0, (filter_id, filter_mask), extframe=True) + if IS_CLASSIC: + can.setfilter(0, CAN.MASK32, 0, (filter_id, filter_mask), extframe=True) + else: + can.setfilter(0, CAN.MASK, 0, (filter_id, filter_mask), extframe=True) can.send("ok", id_ok, timeout=3, extframe=True) + pyb.delay(10) if can.any(0): msg = can.recv(0) print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4])) can.send("fail", id_fail, timeout=3, extframe=True) + pyb.delay(10) if can.any(0): msg = can.recv(0) print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4])) del can -# Test RxCallbacks -print("==== TEST rx callbacks ====") - -can = CAN(1, CAN.LOOPBACK) -can.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) -can.setfilter(1, CAN.LIST16, 1, (5, 6, 7, 8)) - - -def cb0(bus, reason): - print("cb0") - if reason == 0: - print("pending") - if reason == 1: - print("full") - if reason == 2: - print("overflow") - - -def cb1(bus, reason): - print("cb1") - if reason == 0: - print("pending") - if reason == 1: - print("full") - if reason == 2: - print("overflow") - - -def cb0a(bus, reason): - print("cb0a") - if reason == 0: - print("pending") - if reason == 1: - print("full") - if reason == 2: - print("overflow") - - -def cb1a(bus, reason): - print("cb1a") - if reason == 0: - print("pending") - if reason == 1: - print("full") - if reason == 2: - print("overflow") - - -can.rxcallback(0, cb0) -can.rxcallback(1, cb1) - -can.send("11111111", 1, timeout=5000) -can.send("22222222", 2, timeout=5000) -can.send("33333333", 3, timeout=5000) -can.rxcallback(0, cb0a) -can.send("44444444", 4, timeout=5000) - -can.send("55555555", 5, timeout=5000) -can.send("66666666", 6, timeout=5000) -can.send("77777777", 7, timeout=5000) -can.rxcallback(1, cb1a) -can.send("88888888", 8, timeout=5000) - -print(can.recv(0)) -print(can.recv(0)) -print(can.recv(0)) -print(can.recv(1)) -print(can.recv(1)) -print(can.recv(1)) - -can.send("11111111", 1, timeout=5000) -can.send("55555555", 5, timeout=5000) - -print(can.recv(0)) -print(can.recv(1)) - -del can - # Testing asynchronous send print("==== TEST async send ====") can = CAN(1, CAN.LOOPBACK) -can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0)) +# Catch all filter (standard IDs) +if IS_CLASSIC: + can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0)) +else: + can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=False) while can.any(0): can.recv(0) can.send("abcde", 1, timeout=0) -print(can.any(0)) +print("any", can.any(0)) while not can.any(0): pass @@ -270,45 +226,82 @@ def cb1a(bus, reason): can.send("abcde", 2, timeout=0) can.send("abcde", 3, timeout=0) can.send("abcde", 4, timeout=0) - can.send("abcde", 5, timeout=0) + if not IS_H7: + can.send("abcde", 5, timeout=0) + else: + # Hack around the STM32H7's deeper transmit queue by pretending this call failed + # (STM32G4 will fail here, using otherwise the same code, so there is still some test coverage.) + print("send fail ok") except OSError as e: - if str(e) == "16": - print("passed") + # When send() fails Classic CAN raises OSError(MP_EBUSY) (16), FDCAN raises OSError(MP_ETIMEDOUT) (110) + if e.errno == (16 if IS_CLASSIC else 110): + print("send fail ok") else: - print("failed") + print("send fail not ok", e) pyb.delay(500) while can.any(0): print(can.recv(0)) +del can + # Testing rtr messages print("==== TEST rtr messages ====") bus1 = CAN(1, CAN.LOOPBACK) while bus1.any(0): bus1.recv(0) -bus1.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) -bus1.setfilter(1, CAN.LIST16, 0, (5, 6, 7, 8), rtr=(True, True, True, True)) -bus1.setfilter(2, CAN.MASK16, 0, (64, 64, 32, 32), rtr=(False, True)) + +if IS_CLASSIC: + # pyb.CAN Classic API allows distinguishing between RTR in the filter + bus1.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) + bus1.setfilter(1, CAN.LIST16, 0, (5, 6, 7, 8), rtr=(True, True, True, True)) + bus1.setfilter(2, CAN.MASK16, 0, (64, 64, 32, 32), rtr=(False, True)) +else: + # pyb.CAN FDCAN API does not allow distinguishing RTR in filter args, so + # instead we'll only filter the message IDs where Classic CAN equivalent is + # setting the RTR flag (meaning we're verifying RTR is received correctly, but + # not verifying the missing filter behaviour.) + bus1.setfilter(0, CAN.RANGE, 0, (5, 8)) + bus1.setfilter(1, CAN.MASK, 0, (32, 32)) + + +def print_rtr(msg): + if msg: + # Skip printing msg[3] as this is the filter match index, and the value + # is different between Classic and FDCAN implementations + print(msg[0], msg[1], msg[2], msg[4]) + else: + print(msg) + bus1.send("", 1, rtr=True) -print(bus1.any(0)) +print("any", bus1.any(0)) bus1.send("", 5, rtr=True) -print(bus1.recv(0)) +print_rtr(bus1.recv(0)) bus1.send("", 6, rtr=True) -print(bus1.recv(0)) +print_rtr(bus1.recv(0)) bus1.send("", 7, rtr=True) -print(bus1.recv(0)) +print_rtr(bus1.recv(0)) bus1.send("", 16, rtr=True) -print(bus1.any(0)) +print("any", bus1.any(0)) bus1.send("", 32, rtr=True) -print(bus1.recv(0)) +print_rtr(bus1.recv(0)) + +del bus1 # test HAL error, timeout print("==== TEST errors ====") +# Note: this test requires no other CAN node is attached to the CAN peripheral can = pyb.CAN(1, pyb.CAN.NORMAL) try: - can.send("1", 1, timeout=50) + if IS_CLASSIC: + can.send("1", 1, timeout=50) + else: + # Difference between pyb.CAN on Classic vs FDCAN - Classic waits until the message is sent to the bus, + # FDCAN only times out if the TX queue is full + while True: + can.send("1", 1, timeout=50) except OSError as e: - print(repr(e)) + print("timeout", repr(e)) diff --git a/tests/ports/stm32/can.py.exp b/tests/ports/stm32/pyb_can.py.exp similarity index 62% rename from tests/ports/stm32/can.py.exp rename to tests/ports/stm32/pyb_can.py.exp index bd8f6d60b60..800b3514c00 100644 --- a/tests/ports/stm32/can.py.exp +++ b/tests/ports/stm32/pyb_can.py.exp @@ -5,14 +5,14 @@ ValueError 3 CAN(1) True CAN(1, CAN.LOOPBACK, auto_restart=False) -False -True -[0, 0, 0, 0, 0, 0, 0, 0] -True [0, 0, 0, 0, 0, 0, 1, 0] +any False +error_active True +info [0, 0, 0, 0, 0, 0, 0, 0] +any+info True [0, 0, 0, 0, 0, 0, 1, 0] (123, False, False, 0, b'abcd') (2047, False, False, 0, b'abcd') (0, False, False, 0, b'abcd') -passed +overlong passed [42, False, False, 0, ] 0 bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') [42, False, False, 0, ] 4 bytearray(b'1234\x00\x00\x00\x00\x00\x00') [42, False, False, 0, ] 8 bytearray(b'01234567\x00\x00') @@ -26,49 +26,24 @@ ValueError ValueError ==== TEST extframe=True ==== CAN(1, CAN.LOOPBACK, auto_restart=False) -passed +extframe passed ('0x8', '0x1c', '0xa', True, b'ok') ('0x800', '0x1c00', '0xa00', True, b'ok') ('0x80000', '0x1c0000', '0xa0000', True, b'ok') ('0x8000000', '0x1c000000', '0xa000000', True, b'ok') -==== TEST rx callbacks ==== -cb0 -pending -cb0 -full -cb0a -overflow -cb1 -pending -cb1 -full -cb1a -overflow -(1, False, False, 0, b'11111111') -(2, False, False, 1, b'22222222') -(4, False, False, 3, b'44444444') -(5, False, False, 0, b'55555555') -(6, False, False, 1, b'66666666') -(8, False, False, 3, b'88888888') -cb0a -pending -cb1a -pending -(1, False, False, 0, b'11111111') -(5, False, False, 0, b'55555555') ==== TEST async send ==== -False +any False (1, False, False, 0, b'abcde') -passed +send fail ok (2, False, False, 0, b'abcde') (3, False, False, 0, b'abcde') (4, False, False, 0, b'abcde') ==== TEST rtr messages ==== -False -(5, False, True, 4, b'') -(6, False, True, 5, b'') -(7, False, True, 6, b'') -False -(32, False, True, 9, b'') +any False +5 False True b'' +6 False True b'' +7 False True b'' +any False +32 False True b'' ==== TEST errors ==== -OSError(110,) +timeout OSError(110,) diff --git a/tests/ports/stm32/pyb_can2.py b/tests/ports/stm32/pyb_can2.py new file mode 100644 index 00000000000..62ae935357c --- /dev/null +++ b/tests/ports/stm32/pyb_can2.py @@ -0,0 +1,50 @@ +try: + from pyb import CAN + + CAN(2) +except (ImportError, ValueError): + print("SKIP") + raise SystemExit + +# Classic CAN (aka bxCAN) hardware has a different filter API +# and some different behaviours to newer FDCAN hardware +IS_CLASSIC = hasattr(CAN, "MASK16") + +# Setting up each CAN peripheral independently is deliberate here, to catch +# catch cases where initialising CAN2 breaks CAN1 + +can1 = CAN(1, CAN.LOOPBACK) +if IS_CLASSIC: + can1.setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) +else: + can1.setfilter(0, CAN.RANGE, 0, (123, 126)) + +can2 = CAN(2, CAN.LOOPBACK) +if IS_CLASSIC: + can2.setfilter(0, CAN.LIST16, 0, (3, 4, 5, 6)) +else: + can2.setfilter(0, CAN.RANGE, 0, (3, 6)) + +# Drain any old messages in RX FIFOs +for can in (can1, can2): + while can.any(0): + can.recv(0) + +for id, can in ((1, can1), (2, can2)): + print("testing", id) + # message1 should only receive on can1, message2 on can2 + can.send("message1", 123) + can.send("message2", 3) + did_recv = False + try: + while True: + res = can.recv(0, timeout=50) + # not printing all of 'res' as the filter index result is different + # on Classic vs FD-CAN + print("rx", res[0], res[4]) + did_recv = True + except OSError: + if not did_recv: + print("no rx!") + +print("done") diff --git a/tests/ports/stm32/pyb_can2.py.exp b/tests/ports/stm32/pyb_can2.py.exp new file mode 100644 index 00000000000..9696f2fa010 --- /dev/null +++ b/tests/ports/stm32/pyb_can2.py.exp @@ -0,0 +1,5 @@ +testing 1 +rx 123 b'message1' +testing 2 +rx 3 b'message2' +done diff --git a/tests/ports/stm32/pyb_can_classic_rtr_filter.py b/tests/ports/stm32/pyb_can_classic_rtr_filter.py new file mode 100644 index 00000000000..90ae28ca9be --- /dev/null +++ b/tests/ports/stm32/pyb_can_classic_rtr_filter.py @@ -0,0 +1,30 @@ +try: + from pyb import CAN +except ImportError: + print("SKIP") + raise SystemExit + +if not hasattr(CAN, "LIST16"): + # This test relies on the RTR-aware filters of Classic CAN, + # so needs to be skipped on FDCAN hardware + print("SKIP") + raise SystemExit + +can = CAN(1, CAN.LOOPBACK) +while can.any(0): + can.recv(0) +can.setfilter(0, CAN.LIST32, 0, (1, 2), rtr=(True, True), extframe=True) +can.setfilter(1, CAN.LIST32, 0, (3, 4), rtr=(True, False), extframe=True) +can.setfilter(2, CAN.MASK32, 0, (16, 16), rtr=(False,), extframe=True) +can.setfilter(2, CAN.MASK32, 0, (32, 32), rtr=(True,), extframe=True) + +can.send("", 1, rtr=True, extframe=True) +print(can.recv(0)) +can.send("", 2, rtr=True, extframe=True) +print(can.recv(0)) +can.send("", 3, rtr=True, extframe=True) +print(can.recv(0)) +can.send("", 4, rtr=True, extframe=True) +print(can.any(0)) + +del can diff --git a/tests/ports/stm32/can2.py.exp b/tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp similarity index 100% rename from tests/ports/stm32/can2.py.exp rename to tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp diff --git a/tests/ports/stm32/pyb_can_classic_rx.py b/tests/ports/stm32/pyb_can_classic_rx.py new file mode 100644 index 00000000000..39a5eadca54 --- /dev/null +++ b/tests/ports/stm32/pyb_can_classic_rx.py @@ -0,0 +1,89 @@ +try: + from pyb import CAN +except ImportError: + print("SKIP") + raise SystemExit + +if not hasattr(CAN, "LIST16"): + # This test relies on some specific behaviours of the Classic CAN RX FIFO + # interrupt, so needs to be skipped on FDCAN hardware + print("SKIP") + raise SystemExit + +# Test RxCallbacks +print("==== TEST rx callbacks ====") + +can = CAN(1, CAN.LOOPBACK) +can.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) +can.setfilter(1, CAN.LIST16, 1, (5, 6, 7, 8)) + + +def cb0(bus, reason): + print("cb0") + if reason == 0: + print("pending") + if reason == 1: + print("full") + if reason == 2: + print("overflow") + + +def cb1(bus, reason): + print("cb1") + if reason == 0: + print("pending") + if reason == 1: + print("full") + if reason == 2: + print("overflow") + + +def cb0a(bus, reason): + print("cb0a") + if reason == 0: + print("pending") + if reason == 1: + print("full") + if reason == 2: + print("overflow") + + +def cb1a(bus, reason): + print("cb1a") + if reason == 0: + print("pending") + if reason == 1: + print("full") + if reason == 2: + print("overflow") + + +can.rxcallback(0, cb0) +can.rxcallback(1, cb1) + +can.send("11111111", 1, timeout=5000) +can.send("22222222", 2, timeout=5000) +can.send("33333333", 3, timeout=5000) +can.rxcallback(0, cb0a) +can.send("44444444", 4, timeout=5000) + +can.send("55555555", 5, timeout=5000) +can.send("66666666", 6, timeout=5000) +can.send("77777777", 7, timeout=5000) +can.rxcallback(1, cb1a) +can.send("88888888", 8, timeout=5000) + +print(can.recv(0)) +print(can.recv(0)) +print(can.recv(0)) +print(can.recv(1)) +print(can.recv(1)) +print(can.recv(1)) + +can.send("11111111", 1, timeout=5000) +can.send("55555555", 5, timeout=5000) + +print(can.recv(0)) +print(can.recv(1)) + +del can diff --git a/tests/ports/stm32/pyb_can_classic_rx.py.exp b/tests/ports/stm32/pyb_can_classic_rx.py.exp new file mode 100644 index 00000000000..6b848eaeb0b --- /dev/null +++ b/tests/ports/stm32/pyb_can_classic_rx.py.exp @@ -0,0 +1,25 @@ +==== TEST rx callbacks ==== +cb0 +pending +cb0 +full +cb0a +overflow +cb1 +pending +cb1 +full +cb1a +overflow +(1, False, False, 0, b'11111111') +(2, False, False, 1, b'22222222') +(4, False, False, 3, b'44444444') +(5, False, False, 0, b'55555555') +(6, False, False, 1, b'66666666') +(8, False, False, 3, b'88888888') +cb0a +pending +cb1a +pending +(1, False, False, 0, b'11111111') +(5, False, False, 0, b'55555555') From 4b339ee7dcf9c6130951671f24033e2769045804 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Mar 2026 15:23:12 +1100 Subject: [PATCH 1847/2098] stm32/pyb_can: Fix initialising CAN2 clearing CAN1 filters. Closes #18922 Signed-off-by: Angus Gratton --- ports/stm32/pyb_can.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 0d004ecfcb5..2de33f511c8 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -273,8 +273,9 @@ static mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp #else // Init filter banks for classic CAN. can2_start_bank = args[ARG_num_filter_banks].u_int; + int bank_offs = (self->can_id == 2) ? can2_start_bank : 0; for (int f = 0; f < CAN_MAX_FILTER; f++) { - can_clearfilter(&self->can, f, can2_start_bank); + can_clearfilter(&self->can, f + bank_offs, can2_start_bank); } #endif From a906cfbb4b3c183b4e7605e2487eb2efac55be4d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Mar 2026 15:24:04 +1100 Subject: [PATCH 1848/2098] stm32/can: Clarify can_clearfilter() arguments. The function arguments mean totally different things for Classic vs FDCAN hardware, but the argument name wasn't particularly clear for either. This commit shouldn't really change the binary firmware at all. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/can.c | 4 ++-- ports/stm32/can.h | 5 ++++- ports/stm32/fdcan.c | 15 ++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ports/stm32/can.c b/ports/stm32/can.c index d43d73ad42d..04514a14f0e 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -152,7 +152,7 @@ void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool e ((enable_msg_received ? CAN_IT_FMP1 : 0) | CAN_IT_FF1 | CAN_IT_FOV1))); } -void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t bank) { +void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t can2_start_bank) { CAN_FilterConfTypeDef filter; filter.FilterIdHigh = 0; @@ -164,7 +164,7 @@ void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t bank) filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_16BIT; filter.FilterActivation = DISABLE; - filter.BankNumber = bank; + filter.BankNumber = can2_start_bank; HAL_CAN_ConfigFilter(can, &filter); } diff --git a/ports/stm32/can.h b/ports/stm32/can.h index 3422f4180d6..82cd89bc2f3 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -90,7 +90,6 @@ typedef enum { bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); void can_deinit(CAN_HandleTypeDef *can); -void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t bank); int can_receive(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t Timeout); @@ -111,12 +110,16 @@ static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo return HAL_FDCAN_GetRxFifoFillLevel(can, fifo == CAN_RX_FIFO0 ? FDCAN_RX_FIFO0 : FDCAN_RX_FIFO1); } +void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, bool is_extid); + #else static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { return __HAL_CAN_MSG_PENDING(can, fifo == CAN_RX_FIFO0 ? CAN_FIFO0 : CAN_FIFO1); } +void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, uint8_t can2_start_bank); + #endif // MICROPY_HW_ENABLE_FDCAN #endif // MICROPY_HW_ENABLE_CAN diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index 3034dd38ef2..ad1f04a4670 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -248,15 +248,12 @@ void can_deinit(FDCAN_HandleTypeDef *can) { } } -void can_clearfilter(FDCAN_HandleTypeDef *can, uint32_t f, uint8_t extid) { - FDCAN_FilterTypeDef filter = {0}; - if (extid == 1) { - filter.IdType = FDCAN_EXTENDED_ID; - } else { - filter.IdType = FDCAN_STANDARD_ID; - } - filter.FilterIndex = f; - filter.FilterConfig = FDCAN_FILTER_DISABLE; +void can_clearfilter(FDCAN_HandleTypeDef *can, uint32_t f, bool is_extid) { + FDCAN_FilterTypeDef filter = { + .IdType = is_extid ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID, + .FilterIndex = f, + .FilterConfig = FDCAN_FILTER_DISABLE, + }; HAL_FDCAN_ConfigFilter(can, &filter); } From d1c936db73be96fca5170608280e3f2f4e33f48a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Mar 2026 15:25:01 +1100 Subject: [PATCH 1849/2098] stm32: Expose FDCAN2 on board NUCLEO_G474RE. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h index 21619d6d0d1..6f4fd6df48e 100644 --- a/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_G474RE/mpconfigboard.h @@ -86,3 +86,9 @@ #define MICROPY_HW_CAN1_NAME "FDCAN1" #define MICROPY_HW_CAN1_TX (pin_A12) // A12, B9, D1 #define MICROPY_HW_CAN1_RX (pin_A11) // A11, B8, D0 + +#define MICROPY_HW_CAN2_NAME "FDCAN2" +#define MICROPY_HW_CAN2_TX (pin_B13) // B13, B6 +#define MICROPY_HW_CAN2_RX (pin_B12) // B12, B5 + +// Note: This MCU has an FDCAN3 peripheral, but not currently supported in MicroPython From cda49bed278a5a85bccc6c60fe097c3039769d89 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 16 Dec 2025 17:15:20 +1100 Subject: [PATCH 1850/2098] py/objlist,stm32,esp32: Add helpers for creating/ensuring list args. Simplifies the pattern of an optional arg which can be a list of at least a certain length, otherwise one is lazily initialised. Modify pyb.CAN and ESP-NOW APIs to use the helper. Note this changes the return type of pyb.CAN.recv() from tuple to list. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/pyb.CAN.rst | 6 +-- ports/esp32/modespnow.c | 5 +-- ports/esp8266/modespnow.c | 5 +-- ports/stm32/pyb_can.c | 35 ++++------------- ports/unix/coverage.c | 39 +++++++++++++++++++ py/objlist.c | 19 +++++++++ py/objlist.h | 7 ++++ tests/multi_pyb_can/rx_callback.py.exp | 4 +- tests/multi_pyb_can/rx_filters.py.exp | 12 +++--- tests/ports/stm32/pyb_can.py.exp | 14 +++---- .../stm32/pyb_can_classic_rtr_filter.py.exp | 6 +-- tests/ports/stm32/pyb_can_classic_rx.py.exp | 16 ++++---- tests/ports/unix/extra_coverage.py.exp | 7 ++++ 13 files changed, 110 insertions(+), 65 deletions(-) diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index eb21d8223f2..139b54f5b11 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -237,7 +237,7 @@ Methods - *list* is an optional list object to be used as the return value - *timeout* is the timeout in milliseconds to wait for the receive. - Return value: A tuple containing five values. + Return value: A list containing five values. - The id of the message. - A boolean that indicates if the message ID is standard or extended. @@ -245,8 +245,8 @@ Methods - The FMI (Filter Match Index) value. - An array containing the data. - If *list* is ``None`` then a new tuple will be allocated, as well as a new - bytes object to contain the data (as the fifth element in the tuple). + If *list* is ``None`` then a new list will be allocated, as well as a new + bytes object to contain the data (as the fifth element in the list). If *list* is not ``None`` then it should be a list object with a least five elements. The fifth element should be a memoryview object which is created diff --git a/ports/esp32/modespnow.c b/ports/esp32/modespnow.c index 725374ea77c..c7a9c6eb1b9 100644 --- a/ports/esp32/modespnow.c +++ b/ports/esp32/modespnow.c @@ -423,10 +423,7 @@ static mp_obj_t espnow_recvinto(size_t n_args, const mp_obj_t *args) { mp_int_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none) ? mp_obj_get_int(args[2]) : self->recv_timeout_ms); - mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]); - if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recvinto(): Invalid argument")); - } + mp_obj_list_t *list = mp_obj_list_ensure(args[1], 2); mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]); if (mp_obj_is_type(msg, &mp_type_bytearray)) { msg->len += msg->free; // Make all the space in msg array available diff --git a/ports/esp8266/modespnow.c b/ports/esp8266/modespnow.c index 91510a3f9df..cbe9cdc4d44 100644 --- a/ports/esp8266/modespnow.c +++ b/ports/esp8266/modespnow.c @@ -304,10 +304,7 @@ static mp_obj_t espnow_recvinto(size_t n_args, const mp_obj_t *args) { size_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none) ? mp_obj_get_int(args[2]) : self->recv_timeout_ms); - mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]); - if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recvinto(): Invalid argument")); - } + mp_obj_list_t *list = mp_obj_list_ensure(args[1], 2); mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]); size_t msg_size = msg->len + msg->free; if (mp_obj_is_type(msg, &mp_type_bytearray)) { diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 2de33f511c8..e546163f7fc 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -441,18 +441,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_state_obj, pyb_can_state); // Get info about error states and TX/RX buffers static mp_obj_t pyb_can_info(size_t n_args, const mp_obj_t *args) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); - mp_obj_list_t *list; - if (n_args == 1) { - list = MP_OBJ_TO_PTR(mp_obj_new_list(8, NULL)); - } else { - if (!mp_obj_is_type(args[1], &mp_type_list)) { - mp_raise_TypeError(NULL); - } - list = MP_OBJ_TO_PTR(args[1]); - if (list->len < 8) { - mp_raise_ValueError(NULL); - } - } + mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : NULL, 8); #if MICROPY_HW_ENABLE_FDCAN FDCAN_GlobalTypeDef *can = self->can.Instance; @@ -657,24 +646,14 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * can_enable_rx_interrupts(&self->can, fifo, fifo_empty); } - // Create the tuple, or get the list, that will hold the return values + // Create or get the list, that will hold the return values // Also populate the fifth element, either a new bytes or reuse existing memoryview - mp_obj_t ret_obj = args[ARG_list].u_obj; - mp_obj_t *items; - if (ret_obj == mp_const_none) { - ret_obj = mp_obj_new_tuple(5, NULL); - items = ((mp_obj_tuple_t *)MP_OBJ_TO_PTR(ret_obj))->items; + mp_obj_list_t *list = mp_obj_list_optional_arg(args[ARG_list].u_obj, 5); + mp_obj_t *items = list->items; + if (MP_OBJ_FROM_PTR(list) != args[ARG_list].u_obj) { + // If newly allocated, create item 4 items[4] = mp_obj_new_bytes(rx_data, rx_dlc); } else { - // User should provide a list of length at least 5 to hold the values - if (!mp_obj_is_type(ret_obj, &mp_type_list)) { - mp_raise_TypeError(NULL); - } - mp_obj_list_t *list = MP_OBJ_TO_PTR(ret_obj); - if (list->len < 5) { - mp_raise_ValueError(NULL); - } - items = list->items; // Fifth element must be a memoryview which we assume points to a // byte-like array which is large enough, and then we resize it inplace if (!mp_obj_is_type(items[4], &mp_type_memoryview)) { @@ -703,7 +682,7 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * #endif // Return the result - return ret_obj; + return MP_OBJ_FROM_PTR(list); } static MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_recv_obj, 1, pyb_can_recv); diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index f5f3657aca8..79ec07f45e1 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -548,6 +548,45 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); } + // list argument helpers + { + mp_printf(&mp_plat_print, "# list argument helpers\n"); + + // Create a list to test with + mp_obj_t list_items[] = { mp_const_none, MP_OBJ_NEW_SMALL_INT(77), mp_obj_new_str_from_cstr("hello") }; + size_t list_len = MP_ARRAY_SIZE(list_items); + mp_obj_t list = mp_obj_new_list(list_len, list_items); + + // mp_obj_list_ensure + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_list_ensure(MP_OBJ_NEW_SMALL_INT(-1), 5); // Not a list + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + if (nlr_push(&nlr) == 0) { + mp_obj_list_ensure(list, list_len + 2); // List shorter than minimum length + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + mp_obj_list_t *as_ptr = mp_obj_list_ensure(list, list_len); // Acceptable! + mp_printf(&mp_plat_print, "mp_obj_list_ensure same list? %d\n", MP_OBJ_TO_PTR(list) == as_ptr); + + // mp_obj_list_optional_arg() + as_ptr = mp_obj_list_optional_arg(list, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg same list? %d\n", MP_OBJ_TO_PTR(list) == as_ptr); + + as_ptr = mp_obj_list_optional_arg(mp_const_none, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg new list len %d\n", as_ptr->len); + + as_ptr = mp_obj_list_optional_arg(MP_OBJ_NULL, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg new list from NULL len %d\n", as_ptr->len); + } + // runtime utils { mp_printf(&mp_plat_print, "# runtime utils\n"); diff --git a/py/objlist.c b/py/objlist.c index d5aca033960..41c920511d2 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -520,3 +520,22 @@ mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t * o->cur = cur; return MP_OBJ_FROM_PTR(o); } + +mp_obj_list_t *mp_obj_list_optional_arg(mp_obj_t arg_in, size_t min_len) { + if (arg_in == MP_OBJ_NULL || arg_in == mp_const_none) { + return MP_OBJ_TO_PTR(mp_obj_new_list(min_len, NULL)); + } else { + return mp_obj_list_ensure(arg_in, min_len); + } +} + +mp_obj_list_t *mp_obj_list_ensure(mp_obj_t in, size_t min_len) { + if (!mp_obj_is_type(in, &mp_type_list)) { + mp_raise_TypeError(NULL); + } + mp_obj_list_t *list = MP_OBJ_TO_PTR(in); + if (list->len < min_len) { + mp_raise_ValueError(NULL); + } + return list; +} diff --git a/py/objlist.h b/py/objlist.h index 1f4c70504c8..e72778266a3 100644 --- a/py/objlist.h +++ b/py/objlist.h @@ -60,4 +60,11 @@ static inline void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t self->items[i] = value; } +// Helper function for pattern of an optional argument which can be a list of a specified size, and is +// allocated on-demand otherwise +mp_obj_list_t *mp_obj_list_optional_arg(mp_obj_t arg_in, size_t min_len); + +// Ensure provided object is a list of minimum length min_len. Raises TypeError & ValueError otherwise. +mp_obj_list_t *mp_obj_list_ensure(mp_obj_t in, size_t min_len); + #endif // MICROPY_INCLUDED_PY_OBJLIST_H diff --git a/tests/multi_pyb_can/rx_callback.py.exp b/tests/multi_pyb_can/rx_callback.py.exp index 3ab197cc103..2126a3fccb0 100644 --- a/tests/multi_pyb_can/rx_callback.py.exp +++ b/tests/multi_pyb_can/rx_callback.py.exp @@ -2,8 +2,8 @@ rx0 reason full rx0 reason overflow rxed_spam True -(256, False, False, 0, b'aaaaa') +[256, False, False, 0, b'aaaaa'] any False --- instance1 --- -(85, False, False, 0, b'overflow') +[85, False, False, 0, b'overflow'] any False diff --git a/tests/multi_pyb_can/rx_filters.py.exp b/tests/multi_pyb_can/rx_filters.py.exp index 65fbdf4b1b2..8016f641524 100644 --- a/tests/multi_pyb_can/rx_filters.py.exp +++ b/tests/multi_pyb_can/rx_filters.py.exp @@ -1,13 +1,13 @@ --- instance0 --- 0 -fifo0 (837, False, False, 0, b'') -fifo1 (14080, True, False, 0, b'') +fifo0 [837, False, False, 0, b''] +fifo1 [14080, True, False, 0, b''] 1 -fifo0 (837, False, False, 0, b'\x01\x03') -fifo1 (14081, True, False, 0, b'\xee') +fifo0 [837, False, False, 0, b'\x01\x03'] +fifo1 [14081, True, False, 0, b'\xee'] 2 -fifo0 (837, False, False, 0, b'\x02\x03\x02\x03') -fifo1 (14082, True, False, 0, b'\xee\xee') +fifo0 [837, False, False, 0, b'\x02\x03\x02\x03'] +fifo1 [14082, True, False, 0, b'\xee\xee'] Timed out as expected --- instance1 --- 0 diff --git a/tests/ports/stm32/pyb_can.py.exp b/tests/ports/stm32/pyb_can.py.exp index 800b3514c00..80f44981231 100644 --- a/tests/ports/stm32/pyb_can.py.exp +++ b/tests/ports/stm32/pyb_can.py.exp @@ -9,9 +9,9 @@ any False error_active True info [0, 0, 0, 0, 0, 0, 0, 0] any+info True [0, 0, 0, 0, 0, 0, 1, 0] -(123, False, False, 0, b'abcd') -(2047, False, False, 0, b'abcd') -(0, False, False, 0, b'abcd') +[123, False, False, 0, b'abcd'] +[2047, False, False, 0, b'abcd'] +[0, False, False, 0, b'abcd'] overlong passed [42, False, False, 0, ] 0 bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') [42, False, False, 0, ] 4 bytearray(b'1234\x00\x00\x00\x00\x00\x00') @@ -33,11 +33,11 @@ extframe passed ('0x8000000', '0x1c000000', '0xa000000', True, b'ok') ==== TEST async send ==== any False -(1, False, False, 0, b'abcde') +[1, False, False, 0, b'abcde'] send fail ok -(2, False, False, 0, b'abcde') -(3, False, False, 0, b'abcde') -(4, False, False, 0, b'abcde') +[2, False, False, 0, b'abcde'] +[3, False, False, 0, b'abcde'] +[4, False, False, 0, b'abcde'] ==== TEST rtr messages ==== any False 5 False True b'' diff --git a/tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp b/tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp index 3339e5cbeca..b970a0509fe 100644 --- a/tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp +++ b/tests/ports/stm32/pyb_can_classic_rtr_filter.py.exp @@ -1,4 +1,4 @@ -(1, True, True, 0, b'') -(2, True, True, 1, b'') -(3, True, True, 2, b'') +[1, True, True, 0, b''] +[2, True, True, 1, b''] +[3, True, True, 2, b''] False diff --git a/tests/ports/stm32/pyb_can_classic_rx.py.exp b/tests/ports/stm32/pyb_can_classic_rx.py.exp index 6b848eaeb0b..c1c743c42b5 100644 --- a/tests/ports/stm32/pyb_can_classic_rx.py.exp +++ b/tests/ports/stm32/pyb_can_classic_rx.py.exp @@ -11,15 +11,15 @@ cb1 full cb1a overflow -(1, False, False, 0, b'11111111') -(2, False, False, 1, b'22222222') -(4, False, False, 3, b'44444444') -(5, False, False, 0, b'55555555') -(6, False, False, 1, b'66666666') -(8, False, False, 3, b'88888888') +[1, False, False, 0, b'11111111'] +[2, False, False, 1, b'22222222'] +[4, False, False, 3, b'44444444'] +[5, False, False, 0, b'55555555'] +[6, False, False, 1, b'66666666'] +[8, False, False, 3, b'88888888'] cb0a pending cb1a pending -(1, False, False, 0, b'11111111') -(5, False, False, 0, b'55555555') +[1, False, False, 0, b'11111111'] +[5, False, False, 0, b'55555555'] diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 3a46994c23d..73393e68b63 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -115,6 +115,13 @@ deadbeef 0deadbeef c0ffee 000c0ffee +# list argument helpers +TypeError: +ValueError: +mp_obj_list_ensure same list? 1 +mp_obj_list_optional_arg same list? 1 +mp_obj_list_optional_arg new list len 3 +mp_obj_list_optional_arg new list from NULL len 3 # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' From 6768325ea3f310311a94ce593d0459ec2ab01b4e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 29 Jan 2026 15:27:58 +1100 Subject: [PATCH 1851/2098] stm32: Add can_get_state() function, use from pyb.CAN. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/can.c | 19 +++++++++++++++++++ ports/stm32/can.h | 2 ++ ports/stm32/fdcan.c | 24 ++++++++++++++++++++++++ ports/stm32/pyb_can.c | 28 +++------------------------- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/ports/stm32/can.c b/ports/stm32/can.c index 04514a14f0e..5804f60cd2e 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -307,6 +307,25 @@ HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, } } +can_state_t can_get_state(CAN_HandleTypeDef *can) { + uint32_t esr; + + if (can->State == HAL_CAN_STATE_RESET) { + return CAN_STATE_STOPPED; + } + + esr = can->Instance->ESR; + if (esr & CAN_ESR_BOFF) { + return CAN_STATE_BUS_OFF; + } else if (esr & CAN_ESR_EPVF) { + return CAN_STATE_ERROR_PASSIVE; + } else if (esr & CAN_ESR_EWGF) { + return CAN_STATE_ERROR_WARNING; + } else { + return CAN_STATE_ERROR_ACTIVE; + } +} + // Workaround for the __HAL_CAN macros expecting a CAN_HandleTypeDef which we // don't have in the ISR. Using this "fake" struct instead of CAN_HandleTypeDef // so it's not possible to accidentally call an API that uses one of the other diff --git a/ports/stm32/can.h b/ports/stm32/can.h index 82cd89bc2f3..f2601313cf2 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -101,6 +101,8 @@ void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo); // Interrupt for CAN_INT_MESSAGE_RECEIVED is only enabled if enable_msg_received is set. void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received); +can_state_t can_get_state(CAN_HandleTypeDef *can); + // Implemented in pyb_can.c, called from lower layer extern void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo); diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index ad1f04a4670..072cfe3dc4a 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -359,6 +359,30 @@ int can_receive(FDCAN_HandleTypeDef *can, can_rx_fifo_t fifo, FDCAN_RxHeaderType return 0; // success } +can_state_t can_get_state(CAN_HandleTypeDef *can) { + uint32_t psr; + + if (can->State != HAL_FDCAN_STATE_BUSY) { + // The HAL states which map to "stopped" are: + // HAL_FDCAN_STATE_RESET - peripheral never initialised + // HAL_FDCAN_STATE_READY - CAN is stopped (i.e. HAL_FDCAN_CAN_Stop() called) + // HAL_FDCAN_STATE_ERROR - Internal transition timeout, mostly relates to + // low-power states we currently don't use + return CAN_STATE_STOPPED; + } + + psr = can->Instance->PSR; + if (psr & FDCAN_PSR_BO) { + return CAN_STATE_BUS_OFF; + } else if (psr & FDCAN_PSR_EP) { + return CAN_STATE_ERROR_PASSIVE; + } else if (psr & FDCAN_PSR_EW) { + return CAN_STATE_ERROR_WARNING; + } else { + return CAN_STATE_ERROR_ACTIVE; + } +} + static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { uint32_t ints, rx_fifo_ints, error_ints; diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index e546163f7fc..c499a28197a 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -408,33 +408,11 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_restart_obj, pyb_can_restart); // Get the state of the controller static mp_obj_t pyb_can_state(mp_obj_t self_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t state = CAN_STATE_STOPPED; if (self->is_enabled) { - CAN_TypeDef *can = self->can.Instance; - #if MICROPY_HW_ENABLE_FDCAN - uint32_t psr = can->PSR; - if (psr & FDCAN_PSR_BO) { - state = CAN_STATE_BUS_OFF; - } else if (psr & FDCAN_PSR_EP) { - state = CAN_STATE_ERROR_PASSIVE; - } else if (psr & FDCAN_PSR_EW) { - state = CAN_STATE_ERROR_WARNING; - } else { - state = CAN_STATE_ERROR_ACTIVE; - } - #else - if (can->ESR & CAN_ESR_BOFF) { - state = CAN_STATE_BUS_OFF; - } else if (can->ESR & CAN_ESR_EPVF) { - state = CAN_STATE_ERROR_PASSIVE; - } else if (can->ESR & CAN_ESR_EWGF) { - state = CAN_STATE_ERROR_WARNING; - } else { - state = CAN_STATE_ERROR_ACTIVE; - } - #endif + return MP_OBJ_NEW_SMALL_INT(can_get_state(&self->can)); + } else { + return MP_OBJ_NEW_SMALL_INT(CAN_STATE_STOPPED); } - return MP_OBJ_NEW_SMALL_INT(state); } static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_state_obj, pyb_can_state); From 022570445b689bbdd9ee9d9dec861b4b088f998b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 15 Dec 2025 17:30:15 +1100 Subject: [PATCH 1852/2098] extmod,docs: Add generic machine.CAN helpers & docs. API is different to the original machine.CAN proposal, as numerous shortcomings were found during initial implementation. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.CAN.rst | 614 ++++++++++++++++++++++++++++ docs/library/machine.rst | 1 + extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/machine_can.c | 749 +++++++++++++++++++++++++++++++++++ extmod/machine_can.h | 41 ++ extmod/machine_can_port.h | 188 +++++++++ extmod/modmachine.c | 3 + extmod/modmachine.h | 1 + 9 files changed, 1599 insertions(+) create mode 100644 docs/library/machine.CAN.rst create mode 100644 extmod/machine_can.c create mode 100644 extmod/machine_can.h create mode 100644 extmod/machine_can_port.h diff --git a/docs/library/machine.CAN.rst b/docs/library/machine.CAN.rst new file mode 100644 index 00000000000..0da26b90167 --- /dev/null +++ b/docs/library/machine.CAN.rst @@ -0,0 +1,614 @@ +.. currentmodule:: machine +.. _machine.CAN: + +class CAN -- Controller Area Network protocol +============================================= + +CAN is a two-wire serial protocol used for reliable real-time message delivery +between one or more nodes connected to a common bus. CAN 2.0 was standardised in +ISO-11898, and is now also known as CAN Classic. + +There is also a newer, backwards compatible, protocol named CAN FD (CAN with +Flexible Data-Rate). *The machine.CAN driver does not currently support CAN FD +features, use `pyb.CAN` on stm32 if you need CAN FD*. + +CAN support requires a controller (often an internal microcontroller +peripheral), and an external transceiver to level-shift the signals onto the CAN +bus. + +The ``machine.CAN`` interface is a *low level basic* CAN messaging interface +that abstracts a CAN controller as an outgoing priority queue for sending +messages, an incoming queue for receiving messages, and mechanisms for reporting +errors. + +.. note:: The planned ``can`` and ``aiocan`` micropython-lib modules will be the + recommended way to use CAN with MicroPython. + +Constructor +----------- + +.. class:: CAN(id, *args, **kwargs) + + Construct a CAN controller object of the given id: + + - ``id`` identifies a particular CAN controller object; it is board and port specific. + - All other arguments are passed to :func:`CAN.init`. At least one argument (``bitrate``) + must be provided. + + Future versions of this class may also accept port-specific keyword arguments + here which configure the hardware. Currently no such keyword arguments are + implemented. + + Example + ^^^^^^^ + + Construct and initialise CAN controller 1 with bitrate 500kbps:: + + from machine import CAN + can = CAN(1, 500_000) + +.. Add a table of port-specific keyword arguments here, once they exist + +Methods +------- + +.. method:: CAN.init(bitrate, mode=CAN.MODE_NORMAL, sample_point=75, sjw=1, tseg1=None, tseg2=None) + + Initialise the CAN bus with the given parameters: + + - *bitrate* is the desired bus bit rate in bits per second. + - *mode* is one of the values shown under `can-modes`, indicating the + desired mode of operation. The default is "normal" operation on the bus. + + The next parameters are optional and relate to CAN bit timings. In most cases + you can leave these parameters set to the default values: + + - *sample_point* is an integer percentage of the data bit time. It + specifies the position of the bit sample with respect to the whole nominal + bit time. The CAN driver will calculate parameters accordingly. This + parameter is ignored if *tseg1* and *tseg2* are set. + - *sjw* is the resynchronisation jump width in units of time quanta for + nominal bits; it can be a value between 1 and 4 inclusive for classic CAN. + - *tseg1* defines the location of the sample point in units of time quanta + for nominal bits; it can be a value between 1 and 16 inclusive for classic + CAN. This is the sum of the ``Prop_Seg`` and ``Phase_Seg1`` phases as + defined in the ISO-11898 standard. If this value is set then *tseg2* + must also be set and *sample_point* is ignored. + - *tseg2* defines the location of the transmit point in units of the time + quanta for nominal bits; it can be a value between 1 and 8 inclusive for + classic CAN. This corresponds to ``Phase_Seg2`` in the ISO-11898 standard. + If this value is set then *tseg1* must also be set. + + If these arguments are specified then the CAN controller is configured + correctly for the desired *bitrate* and the specified total number of time + quanta per bit. The *tseg1* and *tseg2* values override the + *sample_point* argument if all of these are supplied. + + .. note:: Individual controller hardware may have additional restrictions on + valid values for these parameters, and will raise a ``ValueError`` + if a given value is not supported. + + .. note:: Specific controller hardware may accept additional optional + keyword parameters for hardware-specific features such as oversampling. + +.. method:: CAN.set_filters(filters) + + Set receive filters in the CAN controller. *filters* can be: + + - ``None`` to accept all incoming messages, or + - ``[]`` or ``()`` to disable all message receiving, or + - An iterable of one or more items defining the filter criteria. Each item + should be a tuple or list with three elements: + + - ``identifier`` is a CAN identifier (int). + - ``bit_mask`` is a bit mask for bits in the CAN identifier field (int). + - ``flags`` is an integer with zero or more of the bits defined in + `can-flags` set. This specifies properties that the incoming message needs + to match. Not all controllers support filtering on all flags, a + ``ValueError`` is raised if an unsupported flag is requested. + + Incoming messages are accepted if the bits masked in ``bit_mask`` match between + the message identifier and the filter ``identifier`` value, and flags set in the + filter match the incoming message. + + If the `CAN.FLAG_EXT_ID` bit is set in flags, the filter matches Extended + CAN IDs only. If the `CAN.FLAG_EXT_ID` bit is not set, the filter matches + Standard CAN IDs only. + + All filters are ORed together in the controller. Passing an empty list or + tuple for the filters argument means that no messages will be received. + + Some CAN controllers require each filter to be associated with only one + receive FIFO. In these cases, the filter items in the argument are allocated + round-robin to the available FIFOs. This driver does not distinguish between + FIFOs in the receive IRQ. + + .. note:: If the caller passes an iterable with more items than + :data:`CAN.FILTERS_MAX`, ``ValueError`` will be raised. + + .. note:: If either the ``identifier`` or the ``bit_mask`` is out of range + for the specified ID type, a ``ValueError`` with reason "invalid id" + will be raised. + + Examples + ^^^^^^^^ + + Receive all incoming messages:: + + can.set_filters(None) + + Receive messages with Standard ID values 0x301 and 0x700 only:: + + can.set_filters(((0x301, 0x7FF, 0), + (0x700, 0x7FF, 0))) + + Receive messages with Standard ID values in range 0x300-0x3FF, and Extended + ID value 0x50700 only:: + + can.set_filters(((0x300, 0x700, 0), + (0x50700, 0x1FFF_FFFF, CAN.FLAG_EXT_ID))) + +.. data:: CAN.FILTERS_MAX + + Constant value that reads the maximum number of supported receive filters + for this hardware controller. + + Note that some controllers may have more complex hardware restrictions on the + number of filters in use (for example, counting Standard and Extended ID + filters independently.) In these cases `CAN.set_filters` may raise a + ``ValueError`` even when the ``FILTERS_MAX`` limit is not exceeded. + +.. method:: CAN.send(id, data, flags=0) + + Copy a new CAN message into the controller's hardware transmit queue to be + sent onto the bus. The transmit queue is a priority queue sorted on CAN + identifier priority (lower numeric identifiers have higher priority). + + - *id* is an integer CAN identifier value. + - *data* is a bytes object (or similar) containing the CAN message data, + or describing a Remote Transmission Request (see below). + - *flags* is an integer with zero or more of the bits defined in + `can-flags` set, specifying properties of the outgoing CAN message + (Extended ID, Remote Transmission Request, etc.) + + If the message is successfully queued for transmit onto the bus, the function + returns an integer in the range ``0`` to `CAN.TX_QUEUE_LEN` (exclusive). This + value is the transmit buffer index where the message is queued to send, and + can be used by the `CAN.cancel_send` function and in `CAN.IRQ_TX` events. + + If the queue is full then the send will fail and ``None`` is returned. + + The send can also fail and return ``None`` if the provided *id* value has + equal priority to an existing message in the transmit queue and the CAN + controller hardware cannot guarantee that messages with the same ID will be + sent onto the bus in the same order they were added to the queue. To queue + the message anyway, pass the value :data:`CAN.FLAG_UNORDERED` flag in + the *flags* argument. This flag indicates that it's OK to send messages with + the same CAN ID onto the bus in any order. + + If the controller is in the "Bus Off" error state or disabled then calling + this function will raise an ``OSError``. + + .. note:: This intentionally low-level implementation is designed so the + caller can establish a software queue of outgoing messages. + + .. important:: The CAN "transmit queue" is not a FIFO queue, it is priority + ordered, and although it can hold up to `CAN.TX_QUEUE_LEN` + items there may be other hardware restrictions on messages + which can be queued at the same time. + + Remote Transmission Requests + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If the bit `CAN.FLAG_RTR` is set in the *flags* argument then the controller + will send a Remote Transmission Request instead of a message. In this case + the contents of the *data* argument is ignored. The controller will send + a request where the ``DLC`` length field is equal to the length of the + *data* argument. + + Examples + ^^^^^^^^ + + Attempt to send a message with three byte payload ``0a0b0c`` and Standard ID 0x200:: + + can.send(0x200, b"\x0a\x0b\x0c", 0) + + Attempt to send a message with an empty payload and Extended ID 0x180008. + Indicate that the controller can send messages with this ID in any order, in + case other messages are already queued to send with the same ID:: + + can.send(0x180008, b"", can.FLAG_EXT_ID | can.FLAG_UNORDERED) + + Attempt to send a Remote Transmission Request with length 8 bytes and Standard ID 0x555:: + + can.send(0x555, b" " * 8, can.FLAG_RTR) + +.. method:: CAN.recv(arg=None) + + Return a CAN message that has been received by the controller, according to + filters set by :func:`CAN.set_filters`. + + This function takes a single optional argument, if provided then it must be a + list of at least 4 elements where the second element is a `memoryview` object + that refers to a `bytearray` or similar object that has enough capacity to hold + any received CAN message (8 bytes for CAN Classic, 64 bytes for CAN FD). The + provided list will be returned as a successful result, and avoids memory + allocation inside the function. + + If no messages have been received by the CAN controller, this function + returns ``None``. + + .. note:: `CAN.set_filters` must be called before any messages can be received by + the controller. To receive all messages, call ``set_filters(None)``. + + If a message has been received by the CAN controller, this function returns a + list with 4 elements: + + - Index 0 is the CAN ID of the received message, as an integer. + - Index 1 is a memoryview that provides access to the received message data. + + - If *arg* is not provided then this is a `memoryview` holding the bytes + which were received. This `memoryview` is backed by a newly allocated + `bytearray` large enough to hold any received CAN message. This allows + the result to be safely reused as a future *arg*, to save memory allocations. + - If *arg* is provided then the provided `memoryview` will be resized to + hold exactly the bytes which were received. The caller is responsible for + making sure the backing object for the `memoryview` can hold a CAN + message of any length. + + - Index 2 is an integer with zero or more of the bits defined in + `can-flags` set. It indicates metadata about the received message. + - Index 3 is an integer with zero or more of the bits defined in + `can-recv-errors` set. Any non-zero value indicates potential issues when + receiving CAN messages. These flags are reset inside the controller each + time this function returns. + + Remote Transmission Requests + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If a Remote Transmission Request is received then the bit `CAN.FLAG_RTR` + will be set in Index 2 and the memoryview at Index 1 will contain all + zeroes, with a length equal to the ``DLC`` field of the received request. + + Example + ^^^^^^^ + :: + + can.set_filters(None) # receive all + while True: + res = can.recv() + if res: + can_id, data, flags, errs = res + print("Received", hex(can_id), data.hex(), hex(flags), hex(errs)) + else: + time.sleep_ms(1) # not a good pattern, use the irq instead! + + +.. method:: CAN.irq(handler=None, trigger=0, hard=False) + + Sets an interrupt *handler* function to be called when one or more of the + events flagged in *trigger* has occurred. + + - *handler* is a function to be called when the interrupt event + triggers. The handler must take exactly one argument which is the + :class:`CAN` instance. + + - *trigger* configures the event(s) which can generate an interrupt. + Possible values are a mask of one or more of the following: + + - `CAN.IRQ_RX` event occurs after the CAN controller has received + at least one message into its RX FIFO (meaning that :func:`CAN.recv()` + will return successfully). + - `CAN.IRQ_TX` event occurs after the CAN controller has either + successfully sent a message onto the CAN bus or failed to send a + message. This trigger has additional requirements for the handler, see + `machine_can_irq_flags` for details. + - `CAN.IRQ_STATE` event occurs when the CAN controller has transitioned + into a more severe error state. Call :func:`CAN.state()` to get the + updated state. + + - *hard* if True, a hard interrupt is used. This reduces the delay between + the CAN controller event and the handler being called. Hard interrupt + handlers may not allocate memory; see :ref:`isr_rules`. + + Returns an irq object. If called with no arguments then a previously-configured + irq object is returned. + + See `machine_can_irq_flags` for an example. + +.. method:: CAN.cancel_send(index) + + Request the CAN controller to cancel sending a message onto the bus. + + Argument *index* identifies a single transmit buffer. It should be an + integer in the range ``0`` to ``CAN.TX_QUEUE_LEN`` (exclusive). Generally + this will be a value previously returned by :func:`CAN.send()`. + + The result is ``True`` if a message was pending transmission in this buffer + and transmission was cancelled. + + The result is ``False`` otherwise (either no message was pending transmission + in this buffer, or transmission succeeded already). + + The IRQ event `CAN.IRQ_TX` should be used to determine if a message + was definitely sent or not, but note there are potential race conditions if a + transmission is cancelled and then the same buffer is used to send another + message (especially if the CAN controller IRQ is not "hard"). + +.. method:: CAN.state() + + Returns an integer value indicating the current state of the controller. + The value will be one of the values defined in `can-states`. + + Lower severity error states may automatically clear if the bus recovers, but + the `CAN.STATE_BUS_OFF` state can only be recovered by calling + :func:`CAN.restart()`. + +.. method:: CAN.get_counters(list=None /) + + Returns controller's error counter values. The result is a list of eight + values. If the optional *list* parameter is specified then the provided + list object is updated and returned as the result, to avoid an allocation. + + The list items are: + + - TEC (Transmit Error Counter) value + - REC (Receive Error Counter) value + - Number of times the controller entered the Warning state from the Active state. + - Number of times the controller entered the Error Passive state from the Warning state. + - Number of times the controller entered the Bus Off state from the Error Passive state. + - Total number of pending TX messages in the hardware queue. + - Total number of pending RX messages in the hardware queue. + - Number of times an RX overrun occurred. + + .. note:: Depending on the controller, these values may overflow back to 0 after + a certain value. + + .. note:: If a controller doesn't support a particular counter, it will return + ``None`` for that list element. + +.. method:: CAN.get_timings(list=None /) + + Returns a list of elements indicating the current timings configured in the + CAN controller. This can be used to verify timings for debugging purposes. + The result is a list of six values. If the optional *list* parameter is + specified then the provided list object is updated and returned as the + result, to avoid an allocation. + + The list items are: + + - Exact bitrate used by the controller. May vary from *bitrate* argument + passed to :func:`CAN.init()` due to quantisation to meet hardware + constraints. + - Resynchronisation jump width (SJW) in units of time quanta for nominal + bits. Has the same meaning as the *sjw* parameter of :func:`CAN.init()`. + - Location of the sample point in units of time quanta for nominal bits. Has + the same meaning as the *tseg1* parameter of :func:`CAN.init()`. + - Location of the transmit point in units of time quanta for nominal bits. + Has the same meaning as the *tseg2* parameter of :func:`CAN.init()`. + - CAN FD timing information. ``None`` for controllers which don't support CAN + FD, or if CAN FD is not initialised. Otherwise, a nested list of four + elements corresponding to the items above but applicable to the CAN FD BRS + feature. + - Optional controller-specific timing information. Depending on the + controller this will either be ``None`` if controller doesn't report any, + or it will be a constant length list whose elements are specific to a + particular hardware controller. + + .. note:: If :func:`CAN.init()` has not been called then this function + still returns a result, but the result depends on the controller + internals and may not be accurate. + +.. method:: CAN.restart() + + Causes the controller to exit `STATE_BUS_OFF` without clearing any other + internal state. Also clears some of the error counters (always the number + of times each error state has been entered, possibly TEC and REC depending + on the controller.) + + Calling this function also cancels any messages waiting to be sent. No + `IRQ_TX` interrupts are delivered for these messages. + + Note that this function may or may not cause the controller to exit the + "Error Passive" state, depending whether the controller hardware zeroes + TEC and REC or not. + +.. method:: CAN.deinit() + + De-initialises a previously active CAN instance. All pending messages + (transmit and receive) are dropped and the controller stops interacting on + the bus. To use this instance again, call :func:`CAN.init()`. + + No `IRQ_TX` or `IRQ_RX` interrupts are called in response to calling this + function. + + See also :func:`CAN.restart()`. + +Constants +--------- + +.. data:: CAN.TX_QUEUE_LEN + + Maximum number of CAN messages which can be queued in the outgoing hardware + message queue of the controller. The "transmit buffer indexes" used by + :func:`CAN.send()`, :func:`CAN.cancel_send()` and `machine_can_irq_flags` + will be in this range. + +.. _can-modes: + +Modes +^^^^^ + +These values represent controller modes of operation, as passed to `CAN.init()`. Not all controllers may support all modes. + +Changing the mode of a running controller requires calling `CAN.deinit()` and then calling `CAN.init()` again with the new mode. + +.. data:: CAN.MODE_NORMAL + + The controller is active as a standard CAN network node (will acknowledge + valid messages and may transmit errors depending on its current `State + `). + +.. data:: CAN.MODE_SLEEP + + CAN controller is asleep in a low power mode. Depending on the controller, + this may support waking the controller and transitioning to `CAN.MODE_NORMAL` + if CAN traffic is received. + +.. data:: CAN.MODE_LOOPBACK + + A testing mode. The CAN controller is still connected to the external bus, + but will also receive its own transmitted messages and ignore any ACK errors. + +.. data:: CAN.MODE_SILENT + + CAN controller receives messages but does not interact with the CAN bus + (including sending ACKs, errors, etc.) + +.. data:: CAN.MODE_SILENT_LOOPBACK + + A testing mode that does not require a CAN transceiver to be connected at + all. The CAN controller receives its own transmitted messages without + interacting with the CAN bus at all. The CAN TX and RX pins remain idle. + +.. _can-states: + +States +^^^^^^ + +These values are returned by :func:`CAN.state()` and reflect the error state of the CAN controller: + +.. data:: CAN.STATE_STOPPED + + The controller has not been initialised. + +.. data:: CAN.STATE_ACTIVE + + The controller is active and ``TEC`` and ``REC`` error counters are both below the + warning threshold of 96. See :func:`CAN.get_counters()`. + +.. data:: CAN.STATE_WARNING + + The controller is active but at last one of the ``TEC`` and ``REC`` error counters + are between 96 and 127. See :func:`CAN.get_counters()`. + +.. data:: CAN.STATE_PASSIVE + + The controller is in the "Error Passive" state meaning it no longer transmits active + errors to the bus, but it is otherwise functional. This state is entered when at + least one of the ``TEC`` and ``REC`` error counters is 128 or greater, but + ``TEC`` is less than 255. See :func:`CAN.get_counters()`. + +.. data:: CAN.STATE_BUS_OFF + + The controller is in the Bus-Off state, meaning ``TEC`` error counter is + greater than 255. The CAN controller will not interact with the bus in this + state, and needs to be restarted via :func:`CAN.restart()` to continue. + +.. _can-flags: + +Message Flags +^^^^^^^^^^^^^ + + These values represent metadata about a CAN message. Functions + :func:`CAN.send()`, :func:`CAN.recv()`, and :func:`CAN.set_filters()` either + accept or return an integer value made up of zero or more of these flags bitwise + ORed together. + +.. data:: CAN.FLAG_RTR + + Indicates a message is a remote transmission request. + +.. data:: CAN.FLAG_EXT_ID + + If set, indicates a Message identifier is Extended (29-bit). If not set, + indicates a message identifier is Standard (11-bit). + +.. data:: CAN.FLAG_UNORDERED + + If set in the ``flags`` argument of :func:`CAN.send`, indicates that it's + OK if messages with the same CAN ID are sent in any order onto the bus. + + Otherwise trying to queue multiple messages with the same ID may result in + :func:`CAN.send` failing if the controller hardware can't enforce ordering. + + This flag is never set on received messages, and is ignored by + `CAN.set_filters()`. + +.. _can-recv-errors: + +Receive Error Flags +^^^^^^^^^^^^^^^^^^^ + +The result of :func:`CAN.recv()` includes an integer value made up of zero or +more of these flags bitwise ORed together. If set, these flags indicate +potential general issues with receiving CAN messages. + +.. data:: CAN.RECV_ERR_FULL + +The hardware FIFO where this message was received is full, and additional incoming messages may be lost. + +.. data:: CAN.RECV_ERR_OVERRUN + +The hardware FIFO where this message was received is full, and one or more incoming messages has been lost. + +IRQ values +^^^^^^^^^^ + +.. data:: CAN.IRQ_RX + CAN.IRQ_TX + CAN.IRQ_STATE + + IRQ event triggers. Used with :func:`CAN.irq()` and `machine_can_irq_flags`. + +.. data:: CAN.IRQ_TX_FAILED + CAN.IRQ_TX_IDX_SHIFT + CAN.IRQ_TX_IDX_MASK + + Additional IRQ event flags for `CAN.IRQ_TX`. See `machine_can_irq_flags`. + +.. _machine_can_irq_flags: + +IRQ flags +--------- + +Calling :func:`CAN.irq()` registers an interrupt handler with one or more of the +triggers `CAN.IRQ_RX`, `CAN.IRQ_TX` and `CAN.IRQ_STATE`. + +The function returns an IRQ object, and calling the ``flags()`` function on this +object returns an integer indicating which trigger event(s) triggered the +interrupt. A CAN IRQ handler should call the ``flags()`` function repeatedly +until it returns ``0``. + +When the ``flags()`` function returns with `CAN.IRQ_TX` bit set, the +handler can also check the following flag bits in the result for additional +information about the TX event: + +* ``CAN.IRQ_TX_FAILED`` bit is set if the transmit failed. Usually this will + only happen if :func:`CAN.cancel_send()` was called, although it may also + happen if the controller enters an error state. +* ``CAN.IRQ_TX_MASK << CAN.IRQ_TX_SHIFT`` is a bitmasked region of the flags + value that holds the index of the transmit buffer which generated the event. + This will be an integer in the range ``0`` to `CAN.TX_QUEUE_LEN` (exclusive), + and will match the result of a previous call to `CAN.send()`. + +IRQ_TX Example +^^^^^^^^^^^^^^ +:: + + from machine import CAN + + def irq_send(can): + while flags := can.irq().flags(): + if flags & can.IRQ_TX: + idx = (flags >> can.IRQ_TX_IDX_SHIFT) & can.IRQ_TX_IDX_MASK + success = not (flags & can.IRQ_TX_FAILED) + print("irq_send", idx, success) + + can = CAN(1, 500_000) + can.irq(irq_send, trigger=can.IRQ_TX, hard=True) + +.. important:: If the `CAN.IRQ_TX` trigger is set then the handler **must** + call ``flags()`` repeatedly until it returns ``0``, as shown in + this example. Otherwise, CAN interrupts may not be correctly + re-enabled. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 31acb74920c..481820defc1 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -259,6 +259,7 @@ Classes machine.I2C.rst machine.I2CTarget.rst machine.I2S.rst + machine.CAN.rst machine.RTC.rst machine.Timer.rst machine.Counter.rst diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 9ca869582e0..40ae717a54f 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -10,6 +10,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/machine_adc.c ${MICROPY_EXTMOD_DIR}/machine_adc_block.c ${MICROPY_EXTMOD_DIR}/machine_bitstream.c + ${MICROPY_EXTMOD_DIR}/machine_can.c ${MICROPY_EXTMOD_DIR}/machine_i2c.c ${MICROPY_EXTMOD_DIR}/machine_i2c_target.c ${MICROPY_EXTMOD_DIR}/machine_i2s.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 37151ad120e..961699f089e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -5,6 +5,7 @@ SRC_EXTMOD_C += \ extmod/machine_adc.c \ extmod/machine_adc_block.c \ extmod/machine_bitstream.c \ + extmod/machine_can.c \ extmod/machine_i2c.c \ extmod/machine_i2c_target.c \ extmod/machine_i2s.c \ diff --git a/extmod/machine_can.c b/extmod/machine_can.c new file mode 100644 index 00000000000..11577d9da94 --- /dev/null +++ b/extmod/machine_can.c @@ -0,0 +1,749 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2026 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 "py/objstr.h" +#include "py/runtime.h" + +#if MICROPY_PY_MACHINE_CAN + +#include +#include +#include "extmod/modmachine.h" +#include "extmod/machine_can.h" +#include "extmod/machine_can_port.h" + +#ifndef MICROPY_HW_NUM_CAN +#error "Port must provide a definition of MICROPY_HW_NUM_CAN" +#endif + +#define CAN_MSG_RTR (1 << 0) +#define CAN_MSG_EXTENDED_ID (1 << 1) +#define CAN_MSG_FD_F (1 << 2) +#define CAN_MSG_BRS (1 << 3) + +#define CAN_CLASSIC_MAX_LEN 8 + +// The port provides implementations of the static machine_can_port.h functions in this file. +#include MICROPY_PY_MACHINE_CAN_INCLUDEFILE + +// Note: not all possible return values of flags() are allowed as triggers +#define CAN_IRQ_ALLOWED_TRIGGERS (MP_CAN_IRQ_TX | MP_CAN_IRQ_RX | MP_CAN_IRQ_STATE) + +// Port can override any of these limits if necessary +#ifndef CAN_TSEG1_MIN +#define CAN_TSEG1_MIN 1 +#endif +#ifndef CAN_TSEG1_MAX +#define CAN_TSEG1_MAX 16 +#endif +#ifndef CAN_TSEG2_MIN +#define CAN_TSEG2_MIN 1 +#endif +#ifndef CAN_TSEG2_MAX +#define CAN_TSEG2_MAX 8 +#endif +#ifndef CAN_SJW_MIN +#define CAN_SJW_MIN 1 +#endif +#ifndef CAN_SJW_MAX +#define CAN_SJW_MAX 4 +#endif + +#if MICROPY_HW_ENABLE_FDCAN +// CAN-FD BRS (Baud Rate Switch) default limits +#ifndef CAN_FD_BRS_TSEG1_MIN +#define CAN_FD_BRS_TSEG1_MIN 1 +#endif +#ifndef CAN_FD_BRS_TSEG1_MAX +#define CAN_FD_BRS_TSEG1_MAX 32 +#endif +#ifndef CAN_FD_BRS_TSEG2_MIN +#define CAN_FD_BRS_TSEG2_MIN 1 +#endif +#ifndef CAN_FD_BRS_TSEG2_MAX +#define CAN_FD_BRS_TSEG2_MAX 16 +#endif +#ifndef CAN_FD_BRS_SJW_MIN +#define CAN_FD_BRS_SJW_MIN 1 +#endif +#ifndef CAN_FD_BRS_SJW_MAX +#define CAN_FD_BRS_SJW_MAX 16 +#endif +#endif // MICROPY_HW_ENABLE_FDCAN + +#ifndef CAN_FILTERS_STD_EXT_SEPARATE +// Set if the hardware maintains separate filter indexes for Standard vs Extended IDs +#define CAN_FILTERS_STD_EXT_SEPARATE 0 +#endif +#ifndef CAN_PORT_PRINT_FUNCTION +// If this is set then the port should define its own function machine_can_print() +#define CAN_PORT_PRINT_FUNCTION 0 +#endif + +// Non port-specific functions follow + +// Calculate baud rate prescaler, and if necessary also find tseg1, tseg2 +// to satisfy sample_point +// +static int calculate_brp(int bitrate_nom, int f_clock, int *tseg1, int *tseg2, int sample_point, bool is_fd_brs) { + bool find_tseg = (*tseg1 == -1); // We need to calculate tseg1, tseg2 + + // Set min/max limits for Classic CAN + int brp_min = CAN_BRP_MIN; + int brp_max = CAN_BRP_MAX; + int tseg1_min = CAN_TSEG1_MIN; + int tseg1_max = CAN_TSEG1_MAX; + int tseg2_min = CAN_TSEG2_MIN; + int tseg2_max = CAN_TSEG2_MAX; + #if MICROPY_HW_ENABLE_FDCAN + // If CAN-FD controller, set min/max limits for CAN-FD BRS + if (is_fd_brs) { + brp_min = CAN_FD_BRS_BRP_MIN; + brp_max = CAN_FD_BRS_BRP_MAX; + tseg1_min = CAN_FD_BRS_TSEG1_MIN; + tseg1_max = CAN_FD_BRS_TSEG1_MAX; + tseg2_min = CAN_FD_BRS_TSEG2_MIN; + tseg2_max = CAN_FD_BRS_TSEG2_MAX; + } + #else + (void)is_fd_brs; + #endif + + int brp_best = brp_min; + int best_bitrate_err = bitrate_nom; // best case deviation from bitrate_nom, start at max + int best_sample_err = 10000; // Units of .01%, start at max + + for (int brp = brp_max; brp >= brp_min; brp--) { + unsigned scaled_clock = f_clock / brp; + + if (find_tseg) { + // Find the total number of time quanta that gets closest to the nominal bitrate + int ts_total = (scaled_clock + bitrate_nom / 2) / bitrate_nom; + + if (ts_total < tseg1_min + tseg2_min + 1 + || ts_total > tseg1_max + tseg2_max + 1) { + // The total time quanta doesn't fit in the allowed range + continue; + } + + int bitrate_err = abs((int)bitrate_nom - (int)(scaled_clock / ts_total)); + + if (bitrate_err > best_bitrate_err) { + // This result is worse than our current best bitrate + continue; + } + + // Look for tseg1 & tseg2 that come closest to the sample point + for (int ts1 = tseg1_min; ts1 <= tseg1_max; ts1++) { + int ts2 = ts_total - 1 - ts1; + if (ts2 >= tseg2_min && ts2 <= tseg2_max) { + int try_sample = 10000 * (1 + ts1) / (1 + ts1 + ts2); // sample point, units of .01% + int try_err = abs(try_sample - sample_point * 100); + // Priorities for selecting the best: + // 1. Smallest bitrate error. + // 2. Smallest sample point error. + // 3. Shorter (i.e. more total) time quanta (meaning if all else is equal, choose lower brp). + if (bitrate_err < best_bitrate_err || try_err <= best_sample_err) { + *tseg1 = ts1; + *tseg2 = ts2; + best_sample_err = try_err; + brp_best = brp; + best_bitrate_err = bitrate_err; + } + } + } + } else { + // tseg1 and tseg2 already set, find brp with the lowest bitrate error + int ts_total = *tseg1 + *tseg2 + 1; + int bitrate_err = abs((int)bitrate_nom - (int)(scaled_clock / ts_total)); + if (bitrate_err <= best_bitrate_err) { + brp_best = brp; + best_bitrate_err = bitrate_err; + } + } + } + + if (best_bitrate_err == bitrate_nom) { + // Didn't find any eligible bitrates + mp_raise_ValueError(MP_ERROR_TEXT("unable to calculate BRP for baudrate")); + } + + return brp_best; +} + +// Check a CAN Message ID value is within the valid range, based on supplied flags +static void id_range_check(mp_uint_t can_id, mp_uint_t flags) { + mp_uint_t max = (flags & CAN_MSG_FLAG_EXT_ID) ? (1 << 29) : (1 << 11); + if (can_id >= max) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); + } +} + +static int machine_can_get_actual_bitrate(machine_can_obj_t *self) { + int f_clock = machine_can_port_f_clock(self); + return f_clock / self->brp / (1 + self->tseg1 + self->tseg2); +} + +static mp_obj_t machine_can_deinit(mp_obj_t self_in) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Each CAN instance should have at most one peripheral object + assert(self == MP_STATE_PORT(machine_can_objs)[self->can_idx]); + self->mp_irq_trigger = 0; + self->mp_irq_obj = NULL; + if (self->port) { + machine_can_update_irqs(self); + machine_can_port_deinit(self); + self->port = NULL; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_deinit_obj, machine_can_deinit); + +static void machine_can_init_helper(machine_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_bitrate, ARG_mode, ARG_sample_point, ARG_sjw, ARG_tseg1, ARG_tseg2}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bitrate, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_mode, MP_ARG_INT, {.u_int = MP_CAN_MODE_NORMAL} }, + { MP_QSTR_sample_point, MP_ARG_INT, {.u_int = 75} }, + { MP_QSTR_sjw, MP_ARG_INT, {.u_int = CAN_SJW_MIN } }, + { MP_QSTR_tseg1, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_tseg2, MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Verify arguments + + int bitrate_nom = args[ARG_bitrate].u_int; + + machine_can_mode_t mode = args[ARG_mode].u_int; + if (mode >= MP_CAN_MODE_MAX || !machine_can_port_supports_mode(self, mode)) { + mp_raise_ValueError(MP_ERROR_TEXT("mode")); + } + + int sjw = args[ARG_sjw].u_int; + if (sjw < CAN_SJW_MIN || sjw > CAN_SJW_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("sjw")); + } + + int tseg1 = args[ARG_tseg1].u_int; + int tseg2 = args[ARG_tseg2].u_int; + if ((tseg1 != -1 && (tseg1 < CAN_TSEG1_MIN || tseg1 > CAN_TSEG1_MAX)) + || (tseg1 == -1 && tseg2 != -1)) { + mp_raise_ValueError(MP_ERROR_TEXT("tseg1")); + } + if ((tseg2 != -1 && (tseg2 < CAN_TSEG2_MIN || tseg2 > CAN_TSEG2_MAX)) + || (tseg2 == -1 && tseg1 != -1)) { + mp_raise_ValueError(MP_ERROR_TEXT("tseg2")); + } + + int sample_point = args[ARG_sample_point].u_int; + if (sample_point <= 0 || sample_point >= 100) { + // Probably can make these values more restrictive + mp_raise_ValueError(MP_ERROR_TEXT("sample_point")); + } + + int f_clock = machine_can_port_f_clock(self); + + // This function will also set tseg1 and tseg2 if they are -1 + int brp = calculate_brp(bitrate_nom, f_clock, &tseg1, &tseg2, sample_point, false); + + // Set up the hardware + self->tseg1 = tseg1; + self->tseg2 = tseg2; + self->brp = brp; + self->sjw = sjw; + self->mode = mode; + memset(&self->counters, 0, sizeof(self->counters)); + + if (self->port != NULL) { + machine_can_port_deinit(self); + } + machine_can_port_init(self); +} + +// Raise an exception if the CAN controller is not initialised +static void machine_can_check_initialised(machine_can_obj_t *self) { + // Use self->port being allocated as indicator that controller is initialised + if (self->port == NULL) { + mp_raise_OSError(MP_EINVAL); + } +} + +mp_uint_t machine_can_get_index(mp_obj_t identifier) { + mp_uint_t can_num; + if (mp_obj_is_str(identifier)) { + const char *port = mp_obj_str_get_str(identifier); + if (0) { + #ifdef MICROPY_HW_CAN1_NAME + } else if (strcmp(port, MICROPY_HW_CAN1_NAME) == 0) { + can_num = 1; + #endif + #ifdef MICROPY_HW_CAN2_NAME + } else if (strcmp(port, MICROPY_HW_CAN2_NAME) == 0) { + can_num = 2; + #endif + #ifdef MICROPY_HW_CAN3_NAME + } else if (strcmp(port, MICROPY_HW_CAN3_NAME) == 0) { + can_num = 3; + #endif + } else { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%s) doesn't exist"), port); + } + } else { + can_num = mp_obj_get_int(identifier); + } + if (can_num < 1 || can_num > MICROPY_HW_NUM_CAN) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) doesn't exist"), can_num); + } + + // check if the CAN is reserved for system use or not + if (MICROPY_HW_CAN_IS_RESERVED(can_num)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) is reserved"), can_num); + } + + return can_num - 1; +} + +static mp_obj_t machine_can_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, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // work out port + mp_uint_t can_idx = machine_can_get_index(args[0]); + + machine_can_obj_t *self = MP_STATE_PORT(machine_can_objs)[can_idx]; + if (self == NULL) { + self = mp_obj_malloc(machine_can_obj_t, &machine_can_type); + self->can_idx = can_idx; + MP_STATE_PORT(machine_can_objs)[can_idx] = self; + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_can_init_helper(self, n_args, args, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t machine_can_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + machine_can_init_helper(self, n_args, pos_args, kw_args); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_init_obj, 1, machine_can_init); + +void machine_can_deinit_all(void) { + for (int i = 0; i < MICROPY_HW_NUM_CAN; i++) { + mp_obj_t can = MP_OBJ_FROM_PTR(MP_STATE_PORT(machine_can_objs)[i]); + if (can) { + machine_can_deinit(can); + MP_STATE_PORT(machine_can_objs)[i] = NULL; + } + } +} + +static mp_uint_t can_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_can_check_initialised(self); + self->mp_irq_trigger = new_trigger; + // Call into port layer to update hardware IRQ config + machine_can_update_irqs(self); + return 0; +} + +static mp_uint_t can_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_can_check_initialised(self); + if (info_type == MP_IRQ_INFO_FLAGS) { + // Call into port layer to get current irq flags + return machine_can_port_irq_flags(self); + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t can_irq_methods = { + .trigger = can_irq_trigger, + .info = can_irq_info, +}; + +static mp_obj_t mp_machine_can_irq(machine_can_obj_t *self, bool any_args, mp_arg_val_t *args) { + machine_can_check_initialised(self); + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&can_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + // TODO: refactor this into a helper to save some code size + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~CAN_IRQ_ALLOWED_TRIGGERS; + if (not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%08x unsupported"), not_supported); + } + + 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")); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + + can_irq_trigger(MP_OBJ_FROM_PTR(self), trigger); + } + + return MP_OBJ_FROM_PTR(self->mp_irq_obj); +} + +static mp_obj_t machine_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + machine_can_check_initialised(self); + + enum { ARG_id, ARG_data, ARG_flags, /*ARG_context*/ }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {} }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {} }, + { MP_QSTR_flags, MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_uint_t can_id = args[ARG_id].u_int; + mp_uint_t flags = args[ARG_flags].u_int; + + mp_buffer_info_t data; + mp_get_buffer_raise(args[ARG_data].u_obj, &data, MP_BUFFER_READ); + + if (data.len > machine_can_port_max_data_len(flags)) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("data too long")); + } + id_range_check(can_id, flags); + + mp_int_t res = machine_can_port_send(self, can_id, data.buf, data.len, flags); + + return res >= 0 ? MP_OBJ_NEW_SMALL_INT(res) : mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_send_obj, 3, machine_can_send); + +static mp_obj_t machine_can_cancel_send(mp_obj_t self_in, mp_obj_t arg) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_can_check_initialised(self); + mp_uint_t idx = mp_obj_get_uint(arg); + if (idx >= CAN_TX_QUEUE_LEN) { + mp_raise_type(&mp_type_IndexError); + } + bool res = machine_can_port_cancel_send(self, mp_obj_get_uint(arg)); + return mp_obj_new_bool(res); +} +static MP_DEFINE_CONST_FUN_OBJ_2(machine_can_cancel_send_obj, machine_can_cancel_send); + +static mp_obj_t machine_can_recv(size_t n_args, const mp_obj_t *args) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + machine_can_check_initialised(self); + + uint8_t data[MP_CAN_MAX_LEN]; + size_t dlen = sizeof(data); + mp_uint_t id = 0; + mp_uint_t flags = 0; + mp_uint_t errors = 0; + mp_obj_array_t *data_memoryview; + bool use_arg_as_result = n_args > 1 && args[1] != mp_const_none; + mp_obj_list_t *result = mp_obj_list_optional_arg(use_arg_as_result ? args[1] : mp_const_none, RECV_ARG_LEN); + mp_obj_t *items = result->items; + + // Validate the memoryview item if list passed in arg + if (use_arg_as_result) { + if (!mp_obj_is_type(items[RECV_ARG_DATA], &mp_type_memoryview)) { + mp_raise_TypeError(NULL); + } + } + + // Call the port-specific receive function + if (!machine_can_port_recv(self, data, &dlen, &id, &flags, &errors)) { + return mp_const_none; // Nothing to return + } + + // Create memoryview for the result list, if not passed in + if (!use_arg_as_result) { + // Make the result memoryview large enough for the entire result buffer, not the recv result + // ... it will be resized further down + // (Potentially wasteful, but means the result can be safely reused as arg to a subsequent call) + void *backing_buf = m_malloc(MP_CAN_MAX_LEN); + items[RECV_ARG_DATA] = mp_obj_new_memoryview('B', MP_CAN_MAX_LEN, backing_buf); + } + // TODO: length check any memoryview 'result' that was passed in as arg? + // (may not be possible as I don't think memoryview records the size of its backing buffer) + + data_memoryview = MP_OBJ_TO_PTR(items[RECV_ARG_DATA]); // type of obj was checked already + + if ((flags & CAN_MSG_FLAG_RTR) == 0) { + memcpy(data_memoryview->items, data, dlen); + } else { + // Remote request, return a memoryview of 0s with correct length + memset(data_memoryview->items, 0, dlen); + } + data_memoryview->len = dlen; + + items[RECV_ARG_ID] = MP_OBJ_NEW_SMALL_INT(id); + items[RECV_ARG_FLAGS] = MP_OBJ_NEW_SMALL_INT(flags); + items[RECV_ARG_ERRORS] = MP_OBJ_NEW_SMALL_INT(errors); + + return MP_OBJ_FROM_PTR(result); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_recv_obj, 1, 2, machine_can_recv); + +static mp_obj_t machine_can_set_filters(mp_obj_t self_in, mp_obj_t filters) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_can_check_initialised(self); + mp_obj_iter_buf_t iter_buf; + int idx = 0; + #if CAN_FILTERS_STD_EXT_SEPARATE + int idx_ext = 0; + #endif + mp_obj_t filter; + + machine_can_port_clear_filters(self); + + if (filters == mp_const_none) { + // Passing 'None' is shortcut for "allow all" - pass standard IDs + // via filter 0 and extended IDs via filter 1. + machine_can_port_set_filter(self, 0, 0, 0, 0); + machine_can_port_set_filter(self, 1, 0, 0, CAN_MSG_FLAG_EXT_ID); + } else { + // Walk the iterable argument and call machine_can_port_set_filter() + // for each item + filters = mp_getiter(filters, &iter_buf); + while ((filter = mp_iternext(filters)) != MP_OBJ_STOP_ITERATION) { + mp_obj_t *args; + size_t arg_len; + mp_obj_get_array(filter, &arg_len, &args); + if (arg_len != 3) { + mp_raise_ValueError(MP_ERROR_TEXT("filter items must have length 3")); + } + mp_uint_t id = mp_obj_get_int(args[0]); + mp_uint_t mask = mp_obj_get_int(args[1]); + mp_uint_t flags = mp_obj_get_int(args[2]); + + id_range_check(id, flags); + id_range_check(mask, flags); + + // This function is expected to raise an exception if filter cannot be set + machine_can_port_set_filter(self, + #if CAN_FILTERS_STD_EXT_SEPARATE + (flags & CAN_MSG_FLAG_EXT_ID) ? idx_ext++ : idx++, + #else + idx++, + #endif + id, + mask, + flags + ); + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(machine_can_set_filters_obj, machine_can_set_filters); + +static mp_obj_t machine_can_state(mp_obj_t self_in) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->port == NULL) { + return MP_OBJ_NEW_SMALL_INT(MP_CAN_STATE_STOPPED); + } + return MP_OBJ_NEW_SMALL_INT(machine_can_port_get_state(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_state_obj, machine_can_state); + +static mp_obj_t machine_can_get_counters(size_t n_args, const mp_obj_t *args) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + machine_can_counters_t *counters = &self->counters; + mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : mp_const_none, 8); + machine_can_check_initialised(self); + machine_can_port_update_counters(self); + + // Note: the members of 'counters' are laid out in the same order, + // so compiler should be able to infer some kind of loop here... + list->items[0] = MP_OBJ_NEW_SMALL_INT(counters->tec); + list->items[1] = MP_OBJ_NEW_SMALL_INT(counters->rec); + list->items[2] = MP_OBJ_NEW_SMALL_INT(counters->num_warning); + list->items[3] = MP_OBJ_NEW_SMALL_INT(counters->num_passive); + list->items[4] = MP_OBJ_NEW_SMALL_INT(counters->num_bus_off); + list->items[5] = MP_OBJ_NEW_SMALL_INT(counters->tx_pending); + list->items[6] = MP_OBJ_NEW_SMALL_INT(counters->rx_pending); + list->items[7] = MP_OBJ_NEW_SMALL_INT(counters->rx_overruns); + + return MP_OBJ_FROM_PTR(list); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_get_counters_obj, 1, 2, machine_can_get_counters); + +static mp_obj_t machine_can_get_timings(size_t n_args, const mp_obj_t *args) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : mp_const_none, 6); + + list->items[0] = MP_OBJ_NEW_SMALL_INT(machine_can_get_actual_bitrate(self)); + list->items[1] = MP_OBJ_NEW_SMALL_INT(self->sjw); + list->items[2] = MP_OBJ_NEW_SMALL_INT(self->tseg1); + list->items[3] = MP_OBJ_NEW_SMALL_INT(self->tseg2); + #if MICROPY_HW_ENABLE_FDCAN + mp_obj_list_t *fd_list = mp_obj_list_optional_arg(list->items[4], 4); + fd_list->items[0] = mp_const_none; // TODO: CAN-FD timings support + fd_list->items[1] = mp_const_none; + fd_list->items[2] = mp_const_none; + fd_list->items[3] = mp_const_none; + list->items[4] = MP_OBJ_FROM_PTR(fd_list); + #else + list->items[4] = mp_const_none; + #endif + list->items[5] = machine_can_port_get_additional_timings(self, n_args > 1 ? list->items[5] : mp_const_none); + + return MP_OBJ_FROM_PTR(list); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_get_timings_obj, 1, 2, machine_can_get_timings); + + +static mp_obj_t machine_can_restart(mp_obj_t self_in) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_can_check_initialised(self); + machine_can_port_restart(self); + memset(&self->counters, 0, sizeof(self->counters)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_restart_obj, machine_can_restart); + +#if !CAN_PORT_PRINT_FUNCTION +static void machine_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + // We don't store the bitrate argument, instead print the real achieved bitrate + int f_clock = machine_can_port_f_clock(self); + int actual_bitrate = machine_can_get_actual_bitrate(self); + + qstr mode; + switch (self->mode) { + case MP_CAN_MODE_NORMAL: + mode = MP_QSTR_MODE_NORMAL; + break; + case MP_CAN_MODE_SLEEP: + mode = MP_QSTR_MODE_SLEEP; + break; + case MP_CAN_MODE_LOOPBACK: + mode = MP_QSTR_MODE_LOOPBACK; + break; + case MP_CAN_MODE_SILENT: + mode = MP_QSTR_MODE_SILENT; + break; + case MP_CAN_MODE_SILENT_LOOPBACK: + default: + mode = MP_QSTR_MODE_SILENT_LOOPBACK; + break; + } + + mp_printf(print, "CAN(%d, bitrate=%u, mode=CAN.%q, sjw=%u, tseg1=%u, tseg2=%u, f_clock=%u)", + self->can_idx + 1, + actual_bitrate, + mode, + self->sjw, + self->tseg1, + self->tseg2, + f_clock); +} +#endif + +// CAN.irq(handler, trigger, hard) +static mp_obj_t machine_can_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_arg_val_t args[MP_IRQ_ARG_INIT_NUM_ARGS]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_IRQ_ARG_INIT_NUM_ARGS, mp_irq_init_args, args); + machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + bool any_args = n_args > 1 || kw_args->used != 0; + return MP_OBJ_FROM_PTR(mp_machine_can_irq(self, any_args, args)); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_irq_obj, 1, machine_can_irq); + +static const mp_rom_map_elem_t machine_can_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_can_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_can_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_can_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&machine_can_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_cancel_send), MP_ROM_PTR(&machine_can_cancel_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&machine_can_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_filters), MP_ROM_PTR(&machine_can_set_filters_obj) }, + { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&machine_can_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_counters), MP_ROM_PTR(&machine_can_get_counters_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_timings), MP_ROM_PTR(&machine_can_get_timings_obj) }, + { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&machine_can_restart_obj) }, + + // Mode enum constants + { MP_ROM_QSTR(MP_QSTR_MODE_NORMAL), MP_ROM_INT(MP_CAN_MODE_NORMAL) }, + { MP_ROM_QSTR(MP_QSTR_MODE_SLEEP), MP_ROM_INT(MP_CAN_MODE_SLEEP) }, + { MP_ROM_QSTR(MP_QSTR_MODE_LOOPBACK), MP_ROM_INT(MP_CAN_MODE_LOOPBACK) }, + { MP_ROM_QSTR(MP_QSTR_MODE_SILENT), MP_ROM_INT(MP_CAN_MODE_SILENT) }, + { MP_ROM_QSTR(MP_QSTR_MODE_SILENT_LOOPBACK), MP_ROM_INT(MP_CAN_MODE_SILENT_LOOPBACK) }, + + // State enum constants + { MP_ROM_QSTR(MP_QSTR_STATE_STOPPED), MP_ROM_INT(MP_CAN_STATE_STOPPED) }, + { MP_ROM_QSTR(MP_QSTR_STATE_ACTIVE), MP_ROM_INT(MP_CAN_STATE_ACTIVE) }, + { MP_ROM_QSTR(MP_QSTR_STATE_WARNING), MP_ROM_INT(MP_CAN_STATE_WARNING) }, + { MP_ROM_QSTR(MP_QSTR_STATE_PASSIVE), MP_ROM_INT(MP_CAN_STATE_PASSIVE) }, + { MP_ROM_QSTR(MP_QSTR_STATE_BUS_OFF), MP_ROM_INT(MP_CAN_STATE_BUS_OFF) }, + + // Message Flag enum constants + { MP_ROM_QSTR(MP_QSTR_FLAG_RTR), MP_ROM_INT(CAN_MSG_FLAG_RTR) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_EXT_ID), MP_ROM_INT(CAN_MSG_FLAG_EXT_ID) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_UNORDERED), MP_ROM_INT(CAN_MSG_FLAG_UNORDERED) }, + + // Receive Error Flag enum constants + { MP_ROM_QSTR(MP_QSTR_RECV_ERR_FULL), MP_ROM_INT(CAN_RECV_ERR_FULL) }, + { MP_ROM_QSTR(MP_QSTR_RECV_ERR_OVERRUN), MP_ROM_INT(CAN_RECV_ERR_OVERRUN) }, + + // IRQ enum constants + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(MP_CAN_IRQ_RX) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_TX), MP_ROM_INT(MP_CAN_IRQ_TX) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_STATE), MP_ROM_INT(MP_CAN_IRQ_STATE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_TX_FAILED), MP_ROM_INT(MP_CAN_IRQ_TX_FAILED) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_TX_IDX_SHIFT), MP_ROM_INT(MP_CAN_IRQ_IDX_SHIFT) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_TX_IDX_MASK), MP_ROM_INT(MP_CAN_IRQ_IDX_MASK) }, + + // Other constants + { MP_ROM_QSTR(MP_QSTR_TX_QUEUE_LEN), MP_ROM_INT(CAN_TX_QUEUE_LEN) }, + { MP_ROM_QSTR(MP_QSTR_FILTERS_MAX), MP_ROM_INT(CAN_HW_MAX_FILTER) }, +}; +static MP_DEFINE_CONST_DICT(machine_can_locals_dict, machine_can_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_can_type, + MP_QSTR_CAN, + 0, + make_new, machine_can_make_new, + print, machine_can_print, + locals_dict, &machine_can_locals_dict + ); + +MP_REGISTER_ROOT_POINTER(struct _machine_can_obj_t *machine_can_objs[MICROPY_HW_NUM_CAN]); + +#endif // MICROPY_PY_MACHINE_CAN diff --git a/extmod/machine_can.h b/extmod/machine_can.h new file mode 100644 index 00000000000..6f2d3103670 --- /dev/null +++ b/extmod/machine_can.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2026 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. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_H +#define MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_H + +#include "py/obj.h" + +// machine.CAN support APIs that are called from port-level C code + +// Return the 0-based index of the CAN peripheral based on the name or the +// (1-based) number. +// +// Raises an exception if the identifier is invalid, doesn't exist, or is reserved. +mp_uint_t machine_can_get_index(mp_obj_t identifier); + +void machine_can_deinit_all(void); + +#endif // MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_H diff --git a/extmod/machine_can_port.h b/extmod/machine_can_port.h new file mode 100644 index 00000000000..58eaca0f5c8 --- /dev/null +++ b/extmod/machine_can_port.h @@ -0,0 +1,188 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2026 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. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_PORT_H +#define MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_PORT_H + +#include "py/obj.h" +#include "py/objarray.h" +#include "py/runtime.h" +#include "shared/runtime/mpirq.h" + +// This header is included into both extmod/machine_can.c and port-specific +// machine_can.c implementations and provides shared (static) function +// declarations to both. +// +// In a MicroPython build including this header from port-specific machine_can.c +// include is a no-op (as the file is included directly into +// extmod/machine_can.c). However, including it anyway means that Language +// Servers and IDEs can correctly analyse the machine_can.c file while the +// developer is writing it. + +typedef enum { + MP_CAN_STATE_STOPPED, + MP_CAN_STATE_ACTIVE, + MP_CAN_STATE_WARNING, + MP_CAN_STATE_PASSIVE, + MP_CAN_STATE_BUS_OFF, +} machine_can_state_t; + +typedef enum { + MP_CAN_MODE_NORMAL, + MP_CAN_MODE_SLEEP, + MP_CAN_MODE_LOOPBACK, + MP_CAN_MODE_SILENT, + MP_CAN_MODE_SILENT_LOOPBACK, + MP_CAN_MODE_MAX, +} machine_can_mode_t; + +// CAN IRQ Flags +// (currently the same for all ports) +#define MP_CAN_IRQ_TX (1 << 0) +#define MP_CAN_IRQ_RX (1 << 1) +#define MP_CAN_IRQ_TX_FAILED (1 << 2) +#define MP_CAN_IRQ_STATE (1 << 3) + +// Transmit buffer index is encoded into the irq().flags() response for MP_CAN_IRQ_TX +#define MP_CAN_IRQ_IDX_SHIFT 16 +#define MP_CAN_IRQ_IDX_MASK 0xFF + +#if MICROPY_HW_ENABLE_FDCAN +#define MP_CAN_MAX_LEN 64 +#else +#define MP_CAN_MAX_LEN 8 +#endif + +struct machine_can_port; + +// These values appear in the same order as the result of CAN.get_counters() +typedef struct { + mp_uint_t tec; + mp_uint_t rec; + mp_uint_t num_warning; // Number of "Error Warning" transitions + mp_uint_t num_passive; // Number of "Error Passive" transitions + mp_uint_t num_bus_off; // Number of "Bus-Off" transitions + mp_uint_t tx_pending; + mp_uint_t rx_pending; + mp_uint_t rx_overruns; +} machine_can_counters_t; + +typedef struct _machine_can_obj_t { + mp_obj_base_t base; + mp_uint_t can_idx; + + // Timing register settings + byte tseg1; + byte tseg2; + byte brp; + byte sjw; + + machine_can_mode_t mode; + + mp_irq_obj_t *mp_irq_obj; + uint16_t mp_irq_trigger; + mp_uint_t rx_error_flags; + + // Assumed some of these counters are updated from different port ISRs, etc. and some + // are updated by calling machine_can_port_update_counters() + machine_can_counters_t counters; + + struct machine_can_port *port; +} machine_can_obj_t; + +// Indexes for recv result list +typedef enum { + RECV_ARG_ID, + RECV_ARG_DATA, + RECV_ARG_FLAGS, + RECV_ARG_ERRORS, + RECV_ARG_LEN, // Overall length, not an index +} recv_arg_idx_t; + +#define CAN_STD_ID_MASK 0x7FF +#define CAN_EXT_ID_MASK 0x1fffffff + +// CAN Message Flags +#define CAN_MSG_FLAG_RTR (1 << 0) +#define CAN_MSG_FLAG_EXT_ID (1 << 1) +#define CAN_MSG_FLAG_FD_F (1 << 2) +#define CAN_MSG_FLAG_BRS (1 << 3) +#define CAN_MSG_FLAG_UNORDERED (1 << 4) + +// CAN recv() Error Flags +#define CAN_RECV_ERR_FULL (1 << 0) +#define CAN_RECV_ERR_OVERRUN (1 << 1) +#define CAN_RECV_ERR_ESI (1 << 2) + +// The port must provide implementations of these low-level CAN functions +static int machine_can_port_f_clock(const machine_can_obj_t *self); + +static bool machine_can_port_supports_mode(const machine_can_obj_t *self, machine_can_mode_t mode); + +static void machine_can_port_clear_filters(machine_can_obj_t *self); + +static mp_uint_t machine_can_port_max_data_len(mp_uint_t flags); + +// The extmod layer calls this function in a loop with incrementing filter_idx +// values. It's up to the port how to apply the filters from here, and to raise +// an exception if there are too many. +// +// If the CAN_FILTERS_STD_EXT_SEPARATE flag is set to 1, filter_idx will +// enumerate standard id filters separately to extended id filters (the +// CAN_MSG_FLAG_EXT_ID bit in 'flags' differentiates the type). +static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags); + +// Update interrupt configuration based on the new contents of 'self' +static void machine_can_update_irqs(machine_can_obj_t *self); + +// Return the irq().flags() result. Calling this function may also update the hardware state machine. +static mp_uint_t machine_can_port_irq_flags(machine_can_obj_t *self); + +static void machine_can_port_init(machine_can_obj_t *self); + +static void machine_can_port_deinit(machine_can_obj_t *self); + +static mp_int_t machine_can_port_send(machine_can_obj_t *self, mp_uint_t id, const byte *data, size_t data_len, mp_uint_t flags); + +static bool machine_can_port_cancel_send(machine_can_obj_t *self, mp_uint_t idx); + +static bool machine_can_port_recv(machine_can_obj_t *self, void *data, size_t *dlen, mp_uint_t *id, mp_uint_t *flags, mp_uint_t *errors); + +static machine_can_state_t machine_can_port_get_state(machine_can_obj_t *self); + +static void machine_can_port_restart(machine_can_obj_t *self); + +// Updates values in self->counters (which counters are updated by this function versus from ISRs and the like +// is port specific +static void machine_can_port_update_counters(machine_can_obj_t *self); + +// Hook for port to fill in the final item of the get_timings() result list with controller-specific values +static mp_obj_t machine_can_port_get_additional_timings(machine_can_obj_t *self, mp_obj_t optional_arg); + +// This function is only optionally defined by the port. If macro CAN_PORT_PRINT_FUNCTION is not set +// then a default machine_can_print function will be used. +static void machine_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); + +#endif // MICROPY_INCLUDED_EXTMOD_MACHINE_CAN_PORT_H diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 28b60683b1e..60107a9020c 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -213,6 +213,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_ADC_BLOCK { MP_ROM_QSTR(MP_QSTR_ADCBlock), MP_ROM_PTR(&machine_adc_block_type) }, #endif + #if MICROPY_PY_MACHINE_CAN + { MP_ROM_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&machine_can_type) }, + #endif #if MICROPY_PY_MACHINE_DAC { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 53660a7b7ab..18ee229dc79 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -203,6 +203,7 @@ extern const machine_mem_obj_t machine_mem32_obj; // is provided by a port. extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_adc_block_type; +extern const mp_obj_type_t machine_can_type; extern const mp_obj_type_t machine_i2c_type; extern const mp_obj_type_t machine_i2c_target_type; extern const mp_obj_type_t machine_i2s_type; From 6f835b365a9040cd541f6dadbafe04c9a37eb462 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Feb 2026 15:56:13 +1100 Subject: [PATCH 1853/2098] stm32: Implement index-aware STM32G4 FDCAN HAL TX functions. These are oddly missing from the STM32G4 HAL, but the reference manual describes being able to use them and the implementations seem to work as expected. Note that unlike STM32H7 it doesn't seem like we must use this approach, because HAL_FDCAN_AddMessageToTxFifoQ() does seem to not have the issues with priority inversion seen on the H7. However it's simpler to use the same API for both... Signed-off-by: Angus Gratton Signed-off-by: Angus Gratton --- ports/stm32/fdcan.c | 150 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index 072cfe3dc4a..ae54273e151 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -65,6 +65,12 @@ // as (SRAMCAN_BASE + FDCAN_MESSAGE_RAM_SIZE - 0x4U) limits the usable number of words to 2559 words. #define FDCAN_MESSAGE_RAM_SIZE (2560 - 1) +#if defined(STM32G4) +// These HAL APIs are not implemented for STM32G4, so we implement them here... +static HAL_StatusTypeDef HAL_FDCAN_AddMessageToTxBuffer(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader, uint8_t *pTxData, uint32_t BufferIndex); +static HAL_StatusTypeDef HAL_FDCAN_EnableTxBufferRequest(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndex); +#endif // STM32G4 + // also defined in _hal_fdcan.c, but not able to declare extern and reach the variable const uint8_t DLCtoBytes[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; @@ -461,4 +467,148 @@ void FDCAN2_IT1_IRQHandler(void) { } #endif +#if defined(STM32G4) +// These implementations are copied from stm32h7xx_hal_fdcan.c with modifications for different G4 registers & code formatting + +// *FORMAT-OFF* +// ^^^ Keep original STM HAL code style for easier comparison + +static void FDCAN_CopyMessageToRAM(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader, uint8_t *pTxData, uint32_t BufferIndex); + +static HAL_StatusTypeDef HAL_FDCAN_AddMessageToTxBuffer(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader, uint8_t *pTxData, uint32_t BufferIndex) +{ + HAL_FDCAN_StateTypeDef state = hfdcan->State; + + /* Check function parameters */ + assert_param(IS_FDCAN_ID_TYPE(pTxHeader->IdType)); + if (pTxHeader->IdType == FDCAN_STANDARD_ID) + { + assert_param(IS_FDCAN_MAX_VALUE(pTxHeader->Identifier, 0x7FFU)); + } + else /* pTxHeader->IdType == FDCAN_EXTENDED_ID */ + { + assert_param(IS_FDCAN_MAX_VALUE(pTxHeader->Identifier, 0x1FFFFFFFU)); + } + assert_param(IS_FDCAN_FRAME_TYPE(pTxHeader->TxFrameType)); + assert_param(IS_FDCAN_DLC(pTxHeader->DataLength)); + assert_param(IS_FDCAN_ESI(pTxHeader->ErrorStateIndicator)); + assert_param(IS_FDCAN_BRS(pTxHeader->BitRateSwitch)); + assert_param(IS_FDCAN_FDF(pTxHeader->FDFormat)); + assert_param(IS_FDCAN_EFC(pTxHeader->TxEventFifoControl)); + assert_param(IS_FDCAN_MAX_VALUE(pTxHeader->MessageMarker, 0xFFU)); + assert_param(IS_FDCAN_TX_LOCATION(BufferIndex)); + + if ((state == HAL_FDCAN_STATE_READY) || (state == HAL_FDCAN_STATE_BUSY)) + { + /* Check that the selected buffer has an allocated area into the RAM */ + if (POSITION_VAL(BufferIndex) >= CAN_TX_QUEUE_LEN) // Note: Modified for G4 here + { + /* Update error code */ + hfdcan->ErrorCode |= HAL_FDCAN_ERROR_PARAM; + + return HAL_ERROR; + } + + /* Check that there is no transmission request pending for the selected buffer */ + if ((hfdcan->Instance->TXBRP & BufferIndex) != 0U) + { + /* Update error code */ + hfdcan->ErrorCode |= HAL_FDCAN_ERROR_PENDING; + + return HAL_ERROR; + } + else + { + /* Add the message to the Tx buffer */ + FDCAN_CopyMessageToRAM(hfdcan, pTxHeader, pTxData, POSITION_VAL(BufferIndex)); + } + + /* Return function status */ + return HAL_OK; + } + else + { + /* Update error code */ + hfdcan->ErrorCode |= HAL_FDCAN_ERROR_NOT_INITIALIZED; + + return HAL_ERROR; + } +} + +static HAL_StatusTypeDef HAL_FDCAN_EnableTxBufferRequest(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndex) +{ + if (hfdcan->State == HAL_FDCAN_STATE_BUSY) + { + /* Add transmission request */ + hfdcan->Instance->TXBAR = BufferIndex; + + /* Return function status */ + return HAL_OK; + } + else + { + /* Update error code */ + hfdcan->ErrorCode |= HAL_FDCAN_ERROR_NOT_STARTED; + + return HAL_ERROR; + } +} + +#define SRAMCAN_TFQ_SIZE (18U * 4U) /* TX FIFO/Queue Elements Size in bytes */ + +// This function is copied 100% as-is from stm32g4xx_hal_fdcan.c, unfortunately +static void FDCAN_CopyMessageToRAM(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader, uint8_t *pTxData, + uint32_t BufferIndex) +{ + uint32_t TxElementW1; + uint32_t TxElementW2; + uint32_t *TxAddress; + uint32_t ByteCounter; + + /* Build first word of Tx header element */ + if (pTxHeader->IdType == FDCAN_STANDARD_ID) + { + TxElementW1 = (pTxHeader->ErrorStateIndicator | + FDCAN_STANDARD_ID | + pTxHeader->TxFrameType | + (pTxHeader->Identifier << 18U)); + } + else /* pTxHeader->IdType == FDCAN_EXTENDED_ID */ + { + TxElementW1 = (pTxHeader->ErrorStateIndicator | + FDCAN_EXTENDED_ID | + pTxHeader->TxFrameType | + pTxHeader->Identifier); + } + + /* Build second word of Tx header element */ + TxElementW2 = ((pTxHeader->MessageMarker << 24U) | + pTxHeader->TxEventFifoControl | + pTxHeader->FDFormat | + pTxHeader->BitRateSwitch | + pTxHeader->DataLength); + + /* Calculate Tx element address */ + TxAddress = (uint32_t *)(hfdcan->msgRam.TxFIFOQSA + (BufferIndex * SRAMCAN_TFQ_SIZE)); + + /* Write Tx element header to the message RAM */ + *TxAddress = TxElementW1; + TxAddress++; + *TxAddress = TxElementW2; + TxAddress++; + + /* Write Tx payload to the message RAM */ + for (ByteCounter = 0; ByteCounter < DLCtoBytes[pTxHeader->DataLength >> 16U]; ByteCounter += 4U) + { + *TxAddress = (((uint32_t)pTxData[ByteCounter + 3U] << 24U) | + ((uint32_t)pTxData[ByteCounter + 2U] << 16U) | + ((uint32_t)pTxData[ByteCounter + 1U] << 8U) | + (uint32_t)pTxData[ByteCounter]); + TxAddress++; + } +} + +#endif // STM32G4 + +// *FORMAT-ON* #endif // MICROPY_HW_ENABLE_CAN && MICROPY_HW_ENABLE_FDCAN From 6cac2d275d7f8cad0da233014e0f042d01a940f1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 15 Dec 2025 17:38:47 +1100 Subject: [PATCH 1854/2098] stm32: Add machine.CAN implementation. Implemented according to API docs in a parent comment. Adds new multi_extmod/machine_can_* tests which pass when testing between NUCLEO_G474RE, NUCLEO_H723ZG and PYBDV11. This work was mostly funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/can.c | 372 ++++++++++--- ports/stm32/can.h | 92 +++- ports/stm32/fdcan.c | 304 ++++++++--- ports/stm32/machine_can.c | 489 ++++++++++++++++++ ports/stm32/main.c | 4 + ports/stm32/mpconfigport.h | 19 + ports/stm32/pyb_can.c | 182 ++----- ports/stm32/pyb_can.h | 2 - tests/extmod_hardware/machine_can2.py | 44 ++ tests/extmod_hardware/machine_can2.py.exp | 5 + tests/extmod_hardware/machine_can_timings.py | 60 +++ .../machine_can_01_rxtx_simple.py | 35 ++ .../machine_can_01_rxtx_simple.py.exp | 4 + .../machine_can_02_rx_callback.py | 122 +++++ .../machine_can_02_rx_callback.py.exp | 7 + .../multi_extmod/machine_can_03_rx_filters.py | 103 ++++ .../machine_can_03_rx_filters.py.exp | 49 ++ tests/multi_extmod/machine_can_04_tx_order.py | 170 ++++++ .../machine_can_04_tx_order.py.exp | 8 + .../machine_can_05_tx_prio_cancel.py | 118 +++++ .../machine_can_05_tx_prio_cancel.py.exp | 21 + .../multi_extmod/machine_can_06_remote_req.py | 70 +++ .../machine_can_06_remote_req.py.exp | 8 + .../machine_can_07_error_states.py | 205 ++++++++ .../machine_can_07_error_states.py.exp | 37 ++ .../multi_extmod/machine_can_08_init_mode.py | 102 ++++ .../machine_can_08_init_mode.py.exp | 14 + tests/ports/stm32/pyb_can.py | 4 +- tests/run-tests.py | 1 + tests/target_wiring/PYBx.py | 4 + tests/target_wiring/stm32.py | 7 + 31 files changed, 2367 insertions(+), 295 deletions(-) create mode 100644 ports/stm32/machine_can.c create mode 100644 tests/extmod_hardware/machine_can2.py create mode 100644 tests/extmod_hardware/machine_can2.py.exp create mode 100644 tests/extmod_hardware/machine_can_timings.py create mode 100644 tests/multi_extmod/machine_can_01_rxtx_simple.py create mode 100644 tests/multi_extmod/machine_can_01_rxtx_simple.py.exp create mode 100644 tests/multi_extmod/machine_can_02_rx_callback.py create mode 100644 tests/multi_extmod/machine_can_02_rx_callback.py.exp create mode 100644 tests/multi_extmod/machine_can_03_rx_filters.py create mode 100644 tests/multi_extmod/machine_can_03_rx_filters.py.exp create mode 100644 tests/multi_extmod/machine_can_04_tx_order.py create mode 100644 tests/multi_extmod/machine_can_04_tx_order.py.exp create mode 100644 tests/multi_extmod/machine_can_05_tx_prio_cancel.py create mode 100644 tests/multi_extmod/machine_can_05_tx_prio_cancel.py.exp create mode 100644 tests/multi_extmod/machine_can_06_remote_req.py create mode 100644 tests/multi_extmod/machine_can_06_remote_req.py.exp create mode 100644 tests/multi_extmod/machine_can_07_error_states.py create mode 100644 tests/multi_extmod/machine_can_07_error_states.py.exp create mode 100644 tests/multi_extmod/machine_can_08_init_mode.py create mode 100644 tests/multi_extmod/machine_can_08_init_mode.py.exp create mode 100644 tests/target_wiring/stm32.py diff --git a/ports/stm32/can.c b/ports/stm32/can.c index 5804f60cd2e..fd03e895f46 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -35,9 +35,81 @@ #if !MICROPY_HW_ENABLE_FDCAN -bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { +#if defined(MICROPY_HW_CAN3_TX) +#define NUM_CAN 3 +#elif defined(MICROPY_HW_CAN2_TX) +#define NUM_CAN 2 +#else +#define NUM_CAN 1 +#endif + +static int get_inst_index(CAN_HandleTypeDef *hcan) { + #if defined(MICROPY_HW_CAN1_TX) + if (hcan->Instance == CAN1) { + return 0; + } + #endif + #if defined(MICROPY_HW_CAN2_TX) + if (hcan->Instance == CAN2) { + return 1; + } + #endif + #if defined(MICROPY_HW_CAN3_TX) + if (hcan->Instance == CAN3) { + return 2; + } + #endif + assert(0); // Invalid hcan argument + return 0; +} + +static uint32_t get_tx_irqn(int can_id) { + switch (can_id) { + #if defined(MICROPY_HW_CAN1_TX) + case PYB_CAN_1: + return CAN1_TX_IRQn; + #endif + #if defined(MICROPY_HW_CAN2_TX) + case PYB_CAN_2: + return CAN2_TX_IRQn; + #endif + #if defined(MICROPY_HW_CAN3_TX) + case PYB_CAN_3: + return CAN3_TX_IRQn; + #endif + default: + return -1; + } +} + +int can_get_transmit_finished(CAN_HandleTypeDef *hcan, bool *is_success) { + CAN_TypeDef *instance = hcan->Instance; + uint32_t tsr = instance->TSR; + int result = -1; + + if (tsr & CAN_TSR_RQCP0) { + *is_success = tsr & CAN_TSR_TXOK0; + instance->TSR = CAN_TSR_RQCP0; // This also resets TXOK0, ALST0, TERR0 + result = 0; + } else if (tsr & CAN_TSR_RQCP1) { + *is_success = tsr & CAN_TSR_TXOK1; + instance->TSR = CAN_TSR_RQCP1; // This also resets TXOK1, ALST1, TERR1 + result = 1; + } else if (tsr & CAN_TSR_RQCP2) { + *is_success = tsr & CAN_TSR_TXOK2; + instance->TSR = CAN_TSR_RQCP2; // This also resets TXOK2, ALST2, TERR2 + result = 2; + } + + // Re-enable interrupts, to fire again if any transmit events outstanding + HAL_NVIC_EnableIRQ(get_tx_irqn(get_inst_index(hcan) + 1)); + + return result; +} + +bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { CAN_InitTypeDef *init = &can->Init; - init->Mode = mode << 4; // shift-left so modes fit in a small-int + init->Mode = mode; init->Prescaler = prescaler; init->SJW = ((sjw - 1) & 3) << 24; init->BS1 = ((bs1 - 1) & 0xf) << 16; @@ -49,8 +121,11 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca init->RFLM = DISABLE; init->TXFP = DISABLE; + (void)tx_mode; // This parameter is important for initialising FDCAN variant, but not bxCAN + CAN_TypeDef *CANx = NULL; - uint32_t sce_irq = 0; + uint32_t sce_irq; + uint32_t tx_irq = get_tx_irqn(can_id); const machine_pin_obj_t *pins[2]; switch (can_id) { @@ -100,13 +175,18 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca // init CANx can->Instance = CANx; - HAL_CAN_Init(can); + if (HAL_CAN_Init(can) != HAL_OK) { + return false; + } __HAL_CAN_ENABLE_IT(can, CAN_IT_ERR | CAN_IT_BOF | CAN_IT_EPV | CAN_IT_EWG); NVIC_SetPriority(sce_irq, IRQ_PRI_CAN); HAL_NVIC_EnableIRQ(sce_irq); + NVIC_SetPriority(tx_irq, IRQ_PRI_CAN); + HAL_NVIC_EnableIRQ(tx_irq); + return true; } @@ -140,6 +220,10 @@ void can_deinit(CAN_HandleTypeDef *can) { } } +uint32_t can_get_source_freq(void) { + return HAL_RCC_GetPCLK1Freq(); +} + void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { __HAL_CAN_DISABLE_IT(can, ((fifo == CAN_RX_FIFO0) ? (CAN_IT_FMP0 | CAN_IT_FF0 | CAN_IT_FOV0) : @@ -147,6 +231,23 @@ void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { } void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received) { + uint32_t irq = 0; + if (can->Instance == CAN1) { + irq = (fifo == CAN_RX_FIFO0) ? CAN1_RX0_IRQn : CAN1_RX1_IRQn; + } + #if defined(CAN2) + else if (can->Instance == CAN2) { + irq = (fifo == CAN_RX_FIFO0) ? CAN2_RX0_IRQn : CAN2_RX1_IRQn; + } + #endif + #if defined(CAN3) + else { + irq = (fifo == CAN_RX_FIFO0) ? CAN3_RX0_IRQn : CAN3_RX1_IRQn; + } + #endif + NVIC_SetPriority(irq, IRQ_PRI_CAN); + HAL_NVIC_EnableIRQ(irq); + __HAL_CAN_ENABLE_IT(can, ((fifo == CAN_RX_FIFO0) ? ((enable_msg_received ? CAN_IT_FMP0 : 0) | CAN_IT_FF0 | CAN_IT_FOV0) : ((enable_msg_received ? CAN_IT_FMP1 : 0) | CAN_IT_FF1 | CAN_IT_FOV1))); @@ -214,12 +315,46 @@ int can_receive(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, CanRxMsgTypeDef *msg return 0; // success } -// Lightly modified version of HAL CAN_Transmit to handle Timeout=0 correctly +static HAL_StatusTypeDef can_transmit_common(CAN_HandleTypeDef *hcan, int index, CanTxMsgTypeDef *txmsg, const uint8_t *data) { + hcan->pTxMsg = txmsg; + (void)data; // Not needed here, caller has set it up as &tx_msg->Data + + // Set up the Id + hcan->Instance->sTxMailBox[index].TIR &= CAN_TI0R_TXRQ; + if (hcan->pTxMsg->IDE == CAN_ID_STD) { + assert_param(IS_CAN_STDID(hcan->pTxMsg->StdId)); + hcan->Instance->sTxMailBox[index].TIR |= ((hcan->pTxMsg->StdId << 21) | \ + hcan->pTxMsg->RTR); + } else { + assert_param(IS_CAN_EXTID(hcan->pTxMsg->ExtId)); + hcan->Instance->sTxMailBox[index].TIR |= ((hcan->pTxMsg->ExtId << 3) | \ + hcan->pTxMsg->IDE | \ + hcan->pTxMsg->RTR); + } + + // Set up the DLC + hcan->pTxMsg->DLC &= (uint8_t)0x0000000F; + hcan->Instance->sTxMailBox[index].TDTR &= (uint32_t)0xFFFFFFF0; + hcan->Instance->sTxMailBox[index].TDTR |= hcan->pTxMsg->DLC; + + // Set up the data field + hcan->Instance->sTxMailBox[index].TDLR = (((uint32_t)hcan->pTxMsg->Data[3] << 24) | + ((uint32_t)hcan->pTxMsg->Data[2] << 16) | + ((uint32_t)hcan->pTxMsg->Data[1] << 8) | + ((uint32_t)hcan->pTxMsg->Data[0])); + hcan->Instance->sTxMailBox[index].TDHR = (((uint32_t)hcan->pTxMsg->Data[7] << 24) | + ((uint32_t)hcan->pTxMsg->Data[6] << 16) | + ((uint32_t)hcan->pTxMsg->Data[5] << 8) | + ((uint32_t)hcan->pTxMsg->Data[4])); + + // Request transmit + hcan->Instance->sTxMailBox[index].TIR |= CAN_TI0R_TXRQ; + return HAL_OK; +} + HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t Timeout) { uint32_t transmitmailbox; uint32_t tickstart; - uint32_t rqcpflag = 0; - uint32_t txokflag = 0; hcan->pTxMsg = txmsg; (void)data; // Not needed here, caller has set it up as &tx_msg->Data @@ -232,79 +367,86 @@ HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, // Select one empty transmit mailbox if ((hcan->Instance->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) { transmitmailbox = CAN_TXMAILBOX_0; - rqcpflag = CAN_FLAG_RQCP0; - txokflag = CAN_FLAG_TXOK0; } else if ((hcan->Instance->TSR & CAN_TSR_TME1) == CAN_TSR_TME1) { transmitmailbox = CAN_TXMAILBOX_1; - rqcpflag = CAN_FLAG_RQCP1; - txokflag = CAN_FLAG_TXOK1; } else if ((hcan->Instance->TSR & CAN_TSR_TME2) == CAN_TSR_TME2) { transmitmailbox = CAN_TXMAILBOX_2; - rqcpflag = CAN_FLAG_RQCP2; - txokflag = CAN_FLAG_TXOK2; } else { - transmitmailbox = CAN_TXSTATUS_NOMAILBOX; - } - - if (transmitmailbox != CAN_TXSTATUS_NOMAILBOX) { - // Set up the Id - hcan->Instance->sTxMailBox[transmitmailbox].TIR &= CAN_TI0R_TXRQ; - if (hcan->pTxMsg->IDE == CAN_ID_STD) { - assert_param(IS_CAN_STDID(hcan->pTxMsg->StdId)); - hcan->Instance->sTxMailBox[transmitmailbox].TIR |= ((hcan->pTxMsg->StdId << 21) | \ - hcan->pTxMsg->RTR); - } else { - assert_param(IS_CAN_EXTID(hcan->pTxMsg->ExtId)); - hcan->Instance->sTxMailBox[transmitmailbox].TIR |= ((hcan->pTxMsg->ExtId << 3) | \ - hcan->pTxMsg->IDE | \ - hcan->pTxMsg->RTR); - } + return HAL_BUSY; + } - // Set up the DLC - hcan->pTxMsg->DLC &= (uint8_t)0x0000000F; - hcan->Instance->sTxMailBox[transmitmailbox].TDTR &= (uint32_t)0xFFFFFFF0; - hcan->Instance->sTxMailBox[transmitmailbox].TDTR |= hcan->pTxMsg->DLC; - - // Set up the data field - hcan->Instance->sTxMailBox[transmitmailbox].TDLR = (((uint32_t)hcan->pTxMsg->Data[3] << 24) | - ((uint32_t)hcan->pTxMsg->Data[2] << 16) | - ((uint32_t)hcan->pTxMsg->Data[1] << 8) | - ((uint32_t)hcan->pTxMsg->Data[0])); - hcan->Instance->sTxMailBox[transmitmailbox].TDHR = (((uint32_t)hcan->pTxMsg->Data[7] << 24) | - ((uint32_t)hcan->pTxMsg->Data[6] << 16) | - ((uint32_t)hcan->pTxMsg->Data[5] << 8) | - ((uint32_t)hcan->pTxMsg->Data[4])); - // Request transmission - hcan->Instance->sTxMailBox[transmitmailbox].TIR |= CAN_TI0R_TXRQ; - - if (Timeout == 0) { - return HAL_OK; - } + HAL_StatusTypeDef err = can_transmit_common(hcan, transmitmailbox, txmsg, data); + if (err != HAL_OK) { + return err; + } - // Get tick - tickstart = HAL_GetTick(); - // Check End of transmission flag - while (!(__HAL_CAN_TRANSMIT_STATUS(hcan, transmitmailbox))) { - // Check for the Timeout - if (Timeout != HAL_MAX_DELAY) { - if ((HAL_GetTick() - tickstart) > Timeout) { - // When the timeout expires, we try to abort the transmission of the packet - __HAL_CAN_CANCEL_TRANSMIT(hcan, transmitmailbox); - while (!__HAL_CAN_GET_FLAG(hcan, rqcpflag)) { - } - if (__HAL_CAN_GET_FLAG(hcan, txokflag)) { - // The abort attempt failed and the message was sent properly - return HAL_OK; - } else { - return HAL_TIMEOUT; - } + if (Timeout == 0) { + return HAL_OK; + } + + // Get tick + tickstart = HAL_GetTick(); + // Check End of transmission flag + while (!(__HAL_CAN_TRANSMIT_STATUS(hcan, transmitmailbox))) { + // Check for the Timeout + if (Timeout != HAL_MAX_DELAY) { + if ((HAL_GetTick() - tickstart) > Timeout) { + // When the timeout expires, we try to abort the transmission of the packet + bool was_transmitting = can_cancel_transmit(hcan, transmitmailbox); + // Note: there is a small race here where a message that transmits exactly as + // we call can_cancel_transmit() will still look like it failed + if (!was_transmitting) { + // The abort attempt failed and the message was sent properly + return HAL_OK; + } else { + return HAL_TIMEOUT; } } } - return HAL_OK; - } else { - return HAL_BUSY; } + return HAL_OK; +} + +HAL_StatusTypeDef can_transmit_buf_index(CAN_HandleTypeDef *hcan, int index, CanTxMsgTypeDef *txmsg, const uint8_t *data) { + __HAL_CAN_ENABLE_IT(hcan, CAN_IT_TME); + return can_transmit_common(hcan, index, txmsg, data); +} + + +bool can_cancel_transmit(CAN_HandleTypeDef *hcan, int index) { + uint32_t empty_flag, mailbox; + bool result = false; + switch (index) { + case 0: + empty_flag = CAN_FLAG_TME0; + mailbox = CAN_TXMAILBOX_0; + break; + case 1: + empty_flag = CAN_FLAG_TME1; + mailbox = CAN_TXMAILBOX_1; + break; + default: + empty_flag = CAN_FLAG_TME2; + mailbox = CAN_TXMAILBOX_2; + break; + } + if (__HAL_CAN_GET_FLAG(hcan, empty_flag) == 0) { + result = true; + __HAL_CAN_CANCEL_TRANSMIT(hcan, mailbox); + mp_uint_t start = mp_hal_ticks_us(); + while (__HAL_CAN_GET_FLAG(hcan, empty_flag) == 0) { + // we don't expect this to take longer than a few clock cycles, if + // it does then it probably indicates a bug in the driver. However, + // either way we don't want to end up stuck here + mp_uint_t elapsed = mp_hal_ticks_us() - start; + assert(elapsed < 2000); + if (elapsed >= 2000) { + break; + } + } + } + + return result; } can_state_t can_get_state(CAN_HandleTypeDef *can) { @@ -326,6 +468,29 @@ can_state_t can_get_state(CAN_HandleTypeDef *can) { } } +void can_get_counters(CAN_HandleTypeDef *can, can_counters_t *counters) { + CAN_TypeDef *inst = can->Instance; + uint32_t esr = inst->ESR; + counters->tec = esr >> CAN_ESR_TEC_Pos & 0xff; + counters->rec = esr >> CAN_ESR_REC_Pos & 0xff; + counters->tx_pending = 0x01121223 >> ((inst->TSR >> CAN_TSR_TME_Pos & 7) << 2) & 0xf; + counters->rx_fifo0_pending = inst->RF0R >> CAN_RF0R_FMP0_Pos & 3; + counters->rx_fifo1_pending = inst->RF1R >> CAN_RF1R_FMP1_Pos & 3; +} + +// Compatibility shim: call both the pyb.CAN and machine.CAN handlers if necessary, +// allow them to decide which is initialised. +static inline void call_can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { + #if MICROPY_PY_MACHINE_CAN + machine_can_irq_handler(can_id, interrupt); + #endif + if (interrupt != CAN_INT_TX_COMPLETE) { + pyb_can_irq_handler(can_id, interrupt, fifo); + } + // TODO: Need to do something to clear the transmit state if pyb.CAN is in use, I think + // (check usage of can_get_transmit_finished() from pyb CAN code) +} + // Workaround for the __HAL_CAN macros expecting a CAN_HandleTypeDef which we // don't have in the ISR. Using this "fake" struct instead of CAN_HandleTypeDef // so it's not possible to accidentally call an API that uses one of the other @@ -336,6 +501,7 @@ typedef struct { static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { uint32_t full_flag, full_int, overrun_flag, overrun_int, pending_int; + bool msg_received; const fake_handle_t handle = { .Instance = instance, @@ -347,12 +513,14 @@ static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t overrun_flag = CAN_FLAG_FOV0; overrun_int = CAN_IT_FOV0; pending_int = CAN_IT_FMP0; + msg_received = __HAL_CAN_MSG_PENDING(&handle, CAN_FIFO0); } else { full_flag = CAN_FLAG_FF1; full_int = CAN_IT_FF1; overrun_flag = CAN_FLAG_FOV1; overrun_int = CAN_IT_FOV1; pending_int = CAN_IT_FMP1; + msg_received = __HAL_CAN_MSG_PENDING(&handle, CAN_FIFO1); } bool full = __HAL_CAN_GET_FLAG(&handle, full_flag); @@ -361,39 +529,67 @@ static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t // Note: receive interrupt bits are disabled below, and re-enabled by the // higher layer after calling can_receive() + // Only leave msg_received set if the interrupt is enabled, + // otherwise an CAN_INT_MESSAGE_RECEIVED interrupt is already pending + // and this ISR is being called for another reason + msg_received = msg_received && (handle.Instance->IER & pending_int); + if (full) { __HAL_CAN_DISABLE_IT(&handle, full_int); __HAL_CAN_CLEAR_FLAG(&handle, full_flag); if (!overrun) { - can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); + call_can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); } } if (overrun) { __HAL_CAN_DISABLE_IT(&handle, overrun_int); __HAL_CAN_CLEAR_FLAG(&handle, overrun_flag); - can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); + call_can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); } - if (!(full || overrun)) { - // Process of elimination, if neither of the above - // FIFO status flags are set then message pending interrupt is what fired. + if (msg_received) { __HAL_CAN_DISABLE_IT(&handle, pending_int); - can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); + call_can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); } } -static void can_sce_irq_handler(uint can_id, CAN_TypeDef *instance) { +static void can_sce_irq_handler(int can_id, CAN_TypeDef *instance) { instance->MSR = CAN_MSR_ERRI; // Write to clear ERRIE interrupt uint32_t esr = instance->ESR; if (esr & CAN_ESR_BOFF) { - can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); + call_can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); } else if (esr & CAN_ESR_EPVF) { - can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); + call_can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); } else if (esr & CAN_ESR_EWGF) { - can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); + call_can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); + } +} + +void can_disable_tx_interrupts(CAN_HandleTypeDef *can) { + __HAL_CAN_DISABLE_IT(can, CAN_IT_TME); +} + +void can_restart(CAN_HandleTypeDef *can) { + CAN_TypeDef *instance = can->Instance; + // This sequence puts the hardware in and out of initialisation mode, + // which is the manual way to leave Bus-Off mode (see RM0090 CAN_MCR bit ABOM) + instance->MCR |= CAN_MCR_INRQ; + while ((instance->MSR & CAN_MSR_INAK) == 0) { + } + instance->MCR &= ~CAN_MCR_INRQ; + while ((instance->MSR & CAN_MSR_INAK)) { } } +static void can_tx_irq_handler(int can_id, CAN_TypeDef *instance) { + // Update mailbox tx state based on any RQCPx flags which are set, + // and then clear the RQCPx flags. + + // TX IRQ is re-enabled by higher layer + HAL_NVIC_DisableIRQ(get_tx_irqn(can_id)); + call_can_irq_handler(can_id, CAN_INT_TX_COMPLETE, 0); +} + #if defined(MICROPY_HW_CAN1_TX) void CAN1_RX0_IRQHandler(void) { IRQ_ENTER(CAN1_RX0_IRQn); @@ -412,6 +608,12 @@ void CAN1_SCE_IRQHandler(void) { can_sce_irq_handler(PYB_CAN_1, CAN1); IRQ_EXIT(CAN1_SCE_IRQn); } + +void CAN1_TX_IRQHandler(void) { + IRQ_ENTER(CAN1_TX_IRQn); + can_tx_irq_handler(PYB_CAN_1, CAN1); + IRQ_EXIT(CAN1_TX_IRQn); +} #endif #if defined(MICROPY_HW_CAN2_TX) @@ -432,6 +634,12 @@ void CAN2_SCE_IRQHandler(void) { can_sce_irq_handler(PYB_CAN_2, CAN2); IRQ_EXIT(CAN2_SCE_IRQn); } + +void CAN2_TX_IRQHandler(void) { + IRQ_ENTER(CAN2_TX_IRQn); + can_tx_irq_handler(PYB_CAN_2, CAN2); + IRQ_EXIT(CAN2_TX_IRQn); +} #endif #if defined(MICROPY_HW_CAN3_TX) @@ -452,6 +660,12 @@ void CAN3_SCE_IRQHandler(void) { can_sce_irq_handler(PYB_CAN_3, CAN3); IRQ_EXIT(CAN3_SCE_IRQn); } + +void CAN3_TX_IRQHandler(void) { + IRQ_ENTER(CAN3_TX_IRQn); + can_tx_irq_handler(PYB_CAN_3, CAN3); + IRQ_EXIT(CAN3_TX_IRQn); +} #endif #endif // !MICROPY_HW_ENABLE_FDCAN diff --git a/ports/stm32/can.h b/ports/stm32/can.h index f2601313cf2..ff73e1a74d2 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -47,19 +47,48 @@ #define LIST32 (3) #if MICROPY_HW_ENABLE_FDCAN +// Interface compatibility for the classic CAN controller HAL #define CAN_TypeDef FDCAN_GlobalTypeDef #define CAN_HandleTypeDef FDCAN_HandleTypeDef #define CanTxMsgTypeDef FDCAN_TxHeaderTypeDef #define CanRxMsgTypeDef FDCAN_RxHeaderTypeDef + +#define CAN_MODE_NORMAL FDCAN_MODE_NORMAL +#define CAN_MODE_LOOPBACK FDCAN_MODE_EXTERNAL_LOOPBACK +#define CAN_MODE_SILENT FDCAN_MODE_BUS_MONITORING +#define CAN_MODE_SILENT_LOOPBACK FDCAN_MODE_INTERNAL_LOOPBACK + +// FDCAN peripheral has independent indexes for standard id vs extended id filters +#if defined(STM32G4) +#define CAN_HW_MAX_STD_FILTER 28 +#define CAN_HW_MAX_EXT_FILTER 8 +#elif defined(STM32H7) +// The RAM filtering section is configured for 64 x 1 word elements for 11-bit standard +// identifiers, and 31 x 2 words elements for 29-bit extended identifiers. +// The total number of words reserved for the filtering per FDCAN instance is 126 words. +#define CAN_HW_MAX_STD_FILTER 64 +#define CAN_HW_MAX_EXT_FILTER 31 #endif -enum { +// Value reported via machine.CAN.FILTER_MAX, somewhat optimistic as requires using +// the exact numbers of each type of filter. +#define CAN_HW_MAX_FILTER (CAN_HW_MAX_STD_FILTER + CAN_HW_MAX_EXT_FILTER) + +#else + +// CAN1 & CAN2 have 28 filters which can be arbitrarily split, but machine.CAN +// implementation hard-codes to 14/14. CAN3 has 14 independent filters. +#define CAN_HW_MAX_FILTER 14 + +#endif + +typedef enum { CAN_STATE_STOPPED, CAN_STATE_ERROR_ACTIVE, CAN_STATE_ERROR_WARNING, CAN_STATE_ERROR_PASSIVE, CAN_STATE_BUS_OFF, -}; +} can_state_t; typedef enum _rx_state_t { RX_STATE_FIFO_EMPTY = 0, @@ -76,8 +105,31 @@ typedef enum { CAN_INT_ERR_BUS_OFF, CAN_INT_ERR_PASSIVE, CAN_INT_ERR_WARNING, + + CAN_INT_TX_COMPLETE, } can_int_t; +typedef enum { + CAN_TX_FIFO, + CAN_TX_QUEUE, +} can_tx_mode_t; + +// Counter data as used by pyb.CAN.info() and machine.CAN.get_counters() +typedef struct { + unsigned tec; + unsigned rec; + unsigned tx_pending; + unsigned rx_fifo0_pending; + unsigned rx_fifo1_pending; +} can_counters_t; + +#if defined(STM32H7) +#define CAN_TX_QUEUE_LEN 16 +#else +// FDCAN STM32G4, bxCAN +#define CAN_TX_QUEUE_LEN 3 +#endif + // RX FIFO numbering // // Note: For traditional CAN peripheral, the values of CAN_FIFO0 and CAN_FIFO1 are the same @@ -87,12 +139,29 @@ typedef enum { CAN_RX_FIFO1, } can_rx_fifo_t; -bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); +bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); void can_deinit(CAN_HandleTypeDef *can); +uint32_t can_get_source_freq(void); + int can_receive(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); + +// Transmit a CAN frame (callee to choose the transmit slot). Used by pyb.CAN only, does not enable TX interrupt +// On FDCAN this function invalidates the 'txmsg' structure if successful. HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *hcan, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t Timeout); +// Tell the controller to copy a CAN frame copied to 'index' and start transmitting +// On FDCAN this function invalidates the 'txmsg' structure if successful. +HAL_StatusTypeDef can_transmit_buf_index(CAN_HandleTypeDef *hcan, int index, CanTxMsgTypeDef *txmsg, const uint8_t *data); + +// Cancel the pending transmission in the specified buffer index. Returns after buffer stops transmitting. +// Result is true if buffer was transmitting, false if not transmitting (or finished transmitting before cancellation) +bool can_cancel_transmit(CAN_HandleTypeDef *hcan, int index); + +// Get the lowest index of a buffer in FAILED or SUCCEEDED state, or -1 if none exists +// Calling this function also re-enables the TX done IRQ for this peripheral +int can_get_transmit_finished(CAN_HandleTypeDef *hcan, bool *is_success); + // Disable all CAN receive interrupts for a FIFO void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo); @@ -101,14 +170,25 @@ void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo); // Interrupt for CAN_INT_MESSAGE_RECEIVED is only enabled if enable_msg_received is set. void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool enable_msg_received); +// Disable all pending TX interrupts (ahead of restart or deinit). Will re-enable n next transmit +void can_disable_tx_interrupts(CAN_HandleTypeDef *can); + can_state_t can_get_state(CAN_HandleTypeDef *can); +void can_get_counters(CAN_HandleTypeDef *can, can_counters_t *counters); + +// Restart controller (clears error states). Caller expected to check controller initialised already. +void can_restart(CAN_HandleTypeDef *can); + // Implemented in pyb_can.c, called from lower layer -extern void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo); +extern void pyb_can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo); + +// Implemented in machine_can.c, called from lower layer +extern void machine_can_irq_handler(uint can_id, can_int_t interrupt); #if MICROPY_HW_ENABLE_FDCAN -static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { +static inline unsigned can_is_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { return HAL_FDCAN_GetRxFifoFillLevel(can, fifo == CAN_RX_FIFO0 ? FDCAN_RX_FIFO0 : FDCAN_RX_FIFO1); } @@ -116,7 +196,7 @@ void can_clearfilter(CAN_HandleTypeDef *can, uint32_t filter_num, bool is_extid) #else -static inline unsigned can_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { +static inline unsigned can_is_rx_pending(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { return __HAL_CAN_MSG_PENDING(can, fifo == CAN_RX_FIFO0 ? CAN_FIFO0 : CAN_FIFO1); } diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index ae54273e151..a7fcf7330b1 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -59,11 +59,11 @@ #define FDCAN_IT_GROUP_BIT_LINE_ERROR (FDCAN_ILS_EPE | FDCAN_ILS_ELOE) #define FDCAN_IT_GROUP_PROTOCOL_ERROR (FDCAN_ILS_ARAE | FDCAN_ILS_PEDE | FDCAN_ILS_PEAE | FDCAN_ILS_WDIE | FDCAN_ILS_BOE | FDCAN_ILS_EWE) #define FDCAN_IT_GROUP_RX_FIFO1 (FDCAN_ILS_RF1NL | FDCAN_ILS_RF1FL | FDCAN_ILS_RF1LL) -#endif // The dedicated Message RAM should be 2560 words, but the way it's defined in stm32h7xx_hal_fdcan.c // as (SRAMCAN_BASE + FDCAN_MESSAGE_RAM_SIZE - 0x4U) limits the usable number of words to 2559 words. #define FDCAN_MESSAGE_RAM_SIZE (2560 - 1) +#endif // STM32H7 #if defined(STM32G4) // These HAL APIs are not implemented for STM32G4, so we implement them here... @@ -72,10 +72,55 @@ static HAL_StatusTypeDef HAL_FDCAN_EnableTxBufferRequest(FDCAN_HandleTypeDef *hf #endif // STM32G4 // also defined in _hal_fdcan.c, but not able to declare extern and reach the variable -const uint8_t DLCtoBytes[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; +static const uint8_t DLCtoBytes[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; + +#if defined(MICROPY_HW_CAN3_TX) +#error "Support is not yet added for FDCAN CAN3" +#elif defined(MICROPY_HW_CAN2_TX) +#define NUM_CAN 2 +#else +#define NUM_CAN 1 +#endif + +int can_get_transmit_finished(CAN_HandleTypeDef *hcan, bool *is_success) { + // Note: No HAL API available for the below regs, unless we use the HAL's IRQ handler + FDCAN_GlobalTypeDef *instance = hcan->Instance; + + uint32_t enabled_tx = instance->TXBTIE; // Which buffers have TX ints enabled? + uint32_t tx_success = instance->TXBTO & enabled_tx; // Which TX buffers succeeded? + uint32_t tx_cancel = instance->TXBCF & enabled_tx; // Which TX buffers cancelled? + int result = -1; + + for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) { + if (tx_success & (1U << i)) { + *is_success = true; + result = i; + break; + } + if (tx_cancel & (1U << i)) { + *is_success = false; + result = i; + break; + } + } + + if (result != -1) { + // Clear the TX interrupts for this buffer, will re-enable + // when next sending + instance->TXBTIE &= ~(1U << result); + instance->TXBCIE &= ~(1U << result); + } -bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { - (void)auto_restart; + // Re-enable transmit interrupts + instance->IE |= (FDCAN_IT_TX_COMPLETE | FDCAN_IT_TX_ABORT_COMPLETE); + + return result; +} + +bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { + (void)auto_restart; // FDCAN peripheral doesn't support automatic exit of Bus-Off + + uint32_t fifo_queue_mode = (tx_mode == CAN_TX_FIFO) ? FDCAN_TX_FIFO_OPERATION : FDCAN_TX_QUEUE_OPERATION; FDCAN_InitTypeDef *init = &can->Init; // Configure FDCAN with FD frame and BRS support. @@ -91,15 +136,16 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca init->TransmitPause = DISABLE; init->ProtocolException = ENABLE; + init->StdFiltersNbr = CAN_HW_MAX_STD_FILTER; + init->ExtFiltersNbr = CAN_HW_MAX_EXT_FILTER; + #if defined(STM32G4) init->ClockDivider = FDCAN_CLOCK_DIV1; init->DataPrescaler = 1; init->DataSyncJumpWidth = 1; init->DataTimeSeg1 = 1; init->DataTimeSeg2 = 1; - init->StdFiltersNbr = 28; - init->ExtFiltersNbr = 8; - init->TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; + init->TxFifoQueueMode = fifo_queue_mode; #elif defined(STM32H7) // The dedicated FDCAN RAM is 2560 32-bit words and shared between the FDCAN instances. // To support 2 FDCAN instances simultaneously, the Message RAM is divided in half by @@ -114,26 +160,24 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca // data field and the specific transmission or reception bits field for control. // The following code configures the different Message RAM sections per FDCAN instance. - // The RAM filtering section is configured for 64 x 1 word elements for 11-bit standard - // identifiers, and 31 x 2 words elements for 29-bit extended identifiers. - // The total number of words reserved for the filtering per FDCAN instance is 126 words. - init->StdFiltersNbr = 64; - init->ExtFiltersNbr = 31; - // The Tx event FIFO is used to store the message ID and the timestamp of successfully // transmitted elements. The Tx event FIFO can store a maximum of 32 (2 words) elements. // NOTE: Events are stored in Tx event FIFO only if tx_msg.TxEventFifoControl is enabled. init->TxEventsNbr = 0; - // Transmission section is configured in FIFO mode operation, with no dedicated Tx buffers. - // The Tx FIFO can store a maximum of 32 elements (or 576 words), each element is 18 words + // The Tx FIFO or Queue can store a maximum of 32 elements (or 576 words), each element is 18 words // long (to support a maximum of 64 bytes data field): - // 2 words header + 16 words data field (to support up to 64 bytes of data). + // 2 words header + 16 words data field (to support up to 64 bytes of data). // The total number of words reserved for the Tx FIFO per FDCAN instance is 288 words. - init->TxBuffersNbr = 0; - init->TxFifoQueueElmtsNbr = 16; + if (tx_mode == CAN_TX_FIFO) { + init->TxBuffersNbr = 0; + init->TxFifoQueueElmtsNbr = CAN_TX_QUEUE_LEN; + } else { + init->TxBuffersNbr = CAN_TX_QUEUE_LEN; + init->TxFifoQueueElmtsNbr = 0; + } + init->TxFifoQueueMode = fifo_queue_mode; init->TxElmtSize = FDCAN_DATA_BYTES_64; - init->TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Reception section is configured to use Rx FIFO 0 and Rx FIFO1, with no dedicated Rx buffers. // Each Rx FIFO can store a maximum of 64 elements (1152 words), each element is 18 words @@ -145,15 +189,14 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca init->RxFifo0ElmtSize = FDCAN_DATA_BYTES_64; init->RxFifo1ElmtsNbr = 24; init->RxFifo1ElmtSize = FDCAN_DATA_BYTES_64; - #endif + #endif // STM32H7 - FDCAN_GlobalTypeDef *CANx = NULL; const machine_pin_obj_t *pins[2]; switch (can_id) { #if defined(MICROPY_HW_CAN1_TX) case PYB_CAN_1: - CANx = FDCAN1; + can->Instance = FDCAN1; pins[0] = MICROPY_HW_CAN1_TX; pins[1] = MICROPY_HW_CAN1_RX; break; @@ -161,7 +204,7 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca #if defined(MICROPY_HW_CAN2_TX) case PYB_CAN_2: - CANx = FDCAN2; + can->Instance = FDCAN2; pins[0] = MICROPY_HW_CAN2_TX; pins[1] = MICROPY_HW_CAN2_RX; break; @@ -183,9 +226,7 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca } } - // init CANx - can->Instance = CANx; - // catch bad configuration errors. + // initialise hardware, catching bad configuration errors. if (HAL_FDCAN_Init(can) != HAL_OK) { return false; } @@ -194,7 +235,9 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca HAL_FDCAN_ConfigGlobalFilter(can, FDCAN_REJECT, FDCAN_REJECT, DISABLE, DISABLE); // The configuration registers are locked after CAN is started. - HAL_FDCAN_Start(can); + if (HAL_FDCAN_Start(can) != HAL_OK) { + return false; + } // Reset all filters for (int f = 0; f < init->StdFiltersNbr; ++f) { @@ -228,29 +271,37 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, uint32_t mode, uint32_t presca // FDCAN IT 1 HAL_FDCAN_ConfigInterruptLines(can, FDCAN_IT_GROUP_RX_FIFO1, FDCAN_INTERRUPT_LINE1); - // Enable error interrupts. RX-related interrupts are enabled via can_enable_rx_interrupts() - HAL_FDCAN_ActivateNotification(can, FDCAN_IT_BUS_OFF | FDCAN_IT_ERROR_WARNING | FDCAN_IT_ERROR_PASSIVE, 0); + // Enable error interrupts for all queue positions. + // (RX-related interrupts are enabled via can_enable_rx_interrupts(), + // and TX-related interrupts are enabled during TX + HAL_FDCAN_ActivateNotification(can, + FDCAN_IT_BUS_OFF | FDCAN_IT_ERROR_WARNING | FDCAN_IT_ERROR_PASSIVE, 0); return true; } void can_deinit(FDCAN_HandleTypeDef *can) { + bool any_enabled = false; HAL_FDCAN_DeInit(can); + if (can->Instance == FDCAN1) { HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn); - // TODO check if FDCAN2 is used. - __HAL_RCC_FDCAN_FORCE_RESET(); - __HAL_RCC_FDCAN_RELEASE_RESET(); - __HAL_RCC_FDCAN_CLK_DISABLE(); + #if defined(MICROPY_HW_CAN2_TX) + any_enabled = NVIC_GetEnableIRQ(FDCAN2_IT0_IRQn); + #endif #if defined(MICROPY_HW_CAN2_TX) } else if (can->Instance == FDCAN2) { HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN2_IT1_IRQn); - // TODO check if FDCAN2 is used. + any_enabled = NVIC_GetEnableIRQ(FDCAN1_IT0_IRQn); + #endif + } + + if (!any_enabled) { + // Only reset FDCAN block and disable clock if all FDCAN units are disabled __HAL_RCC_FDCAN_FORCE_RESET(); __HAL_RCC_FDCAN_RELEASE_RESET(); __HAL_RCC_FDCAN_CLK_DISABLE(); - #endif } } @@ -263,6 +314,36 @@ void can_clearfilter(FDCAN_HandleTypeDef *can, uint32_t f, bool is_extid) { HAL_FDCAN_ConfigFilter(can, &filter); } +uint32_t can_get_source_freq(void) { + // Find CAN kernel clock + #if defined(STM32H7) + switch (__HAL_RCC_GET_FDCAN_SOURCE()) { + case RCC_FDCANCLKSOURCE_HSE: + return HSE_VALUE; + case RCC_FDCANCLKSOURCE_PLL: { + PLL1_ClocksTypeDef pll1_clocks; + HAL_RCCEx_GetPLL1ClockFreq(&pll1_clocks); + return pll1_clocks.PLL1_Q_Frequency; + } + case RCC_FDCANCLKSOURCE_PLL2: { + PLL2_ClocksTypeDef pll2_clocks; + HAL_RCCEx_GetPLL2ClockFreq(&pll2_clocks); + return pll2_clocks.PLL2_Q_Frequency; + } + default: + abort(); // Should be unreachable, macro should return one of the above + } + #elif defined(STM32G4) + // STM32G4 CAN clock from reset is HSE, unchanged by MicroPython + return HSE_VALUE; + #else // G0, and assume other MCUs too. + // CAN1/CAN2/CAN3 on APB1 use GetPCLK1Freq, alternatively use the following: + // can_kern_clk = ((HSE_VALUE / osc_config.PLL.PLLM ) * osc_config.PLL.PLLN) / + // (osc_config.PLL.PLLQ * clk_init.AHBCLKDivider * clk_init.APB1CLKDivider); + return HAL_RCC_GetPCLK1Freq(); + #endif +} + void can_disable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo) { HAL_FDCAN_DeactivateNotification(can, (fifo == CAN_RX_FIFO0) ? FDCAN_IT_RX_FIFO0_MASK : FDCAN_IT_RX_FIFO1_MASK); } @@ -275,6 +356,19 @@ void can_enable_rx_interrupts(CAN_HandleTypeDef *can, can_rx_fifo_t fifo, bool e HAL_FDCAN_ActivateNotification(can, ints, 0); } +// Fixup the DataLength field from a byte count to a valid DLC value index (rounding up) +static void encode_datalength(CanTxMsgTypeDef *txmsg) { + // Roundup DataLength to next DLC size and encode to DLC. + size_t len_bytes = txmsg->DataLength; + for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(DLCtoBytes); i++) { + if (len_bytes <= DLCtoBytes[i]) { + txmsg->DataLength = (i << 16); + return; + } + } + assert(0); // DataLength value is invalid +} + HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *can, CanTxMsgTypeDef *txmsg, uint8_t *data, uint32_t timeout_ms) { uint32_t start = HAL_GetTick(); while (HAL_FDCAN_GetTxFifoFreeLevel(can) == 0) { @@ -289,9 +383,58 @@ HAL_StatusTypeDef can_transmit(CAN_HandleTypeDef *can, CanTxMsgTypeDef *txmsg, u } mp_event_wait_ms(1); } + // Note: this function doesn't set up TX interrupts, because it's only used by pyb.CAN which + // doesn't care about this - machine.CAN calls can_transmit_buf_index() + encode_datalength(txmsg); + return HAL_FDCAN_AddMessageToTxFifoQ(can, txmsg, data); } +HAL_StatusTypeDef can_transmit_buf_index(CAN_HandleTypeDef *hcan, int index, CanTxMsgTypeDef *txmsg, const uint8_t *data) { + uint32_t tx_loc = 1U << index; + + encode_datalength(txmsg); + + HAL_StatusTypeDef err = HAL_FDCAN_ActivateNotification(hcan, FDCAN_IT_TX_COMPLETE | FDCAN_IT_TX_ABORT_COMPLETE, tx_loc); + if (err == HAL_OK) { + // Note: casting away const from data, the HAL implementation still treats 'data' as const + err = HAL_FDCAN_AddMessageToTxBuffer(hcan, txmsg, (void *)data, tx_loc); + } + if (err == HAL_OK) { + err = HAL_FDCAN_EnableTxBufferRequest(hcan, tx_loc); + } + return err; +} + +bool can_cancel_transmit(CAN_HandleTypeDef *hcan, int index) { + FDCAN_GlobalTypeDef *instance = hcan->Instance; + bool result = false; + + if (instance->TXBRP & (1U << index)) { + result = true; + HAL_StatusTypeDef err = HAL_FDCAN_AbortTxRequest(hcan, 1U << index); + assert(err == HAL_OK); // Should only fail if controller not started + if (err != HAL_OK) { + return false; + } + mp_uint_t start = mp_hal_ticks_us(); + + // Wait for the TX buffer to be marked as no longer pending + while ((instance->TXBRP & (1U << index)) != 0) { + // we don't expect this to take longer than a few clock cycles, if + // it does then it probably indicates a bug in the driver. However, + // either way we don't want to end up stuck here + mp_uint_t elapsed = mp_hal_ticks_us() - start; + assert(elapsed < 1000); + if (elapsed >= 1000) { + break; + } + } + } + + return result; +} + int can_receive(FDCAN_HandleTypeDef *can, can_rx_fifo_t fifo, FDCAN_RxHeaderTypeDef *hdr, uint8_t *data, uint32_t timeout_ms) { volatile uint32_t *rxf, *rxa; uint32_t fl; @@ -389,8 +532,48 @@ can_state_t can_get_state(CAN_HandleTypeDef *can) { } } -static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { - uint32_t ints, rx_fifo_ints, error_ints; +void can_get_counters(CAN_HandleTypeDef *can, can_counters_t *counters) { + FDCAN_GlobalTypeDef *inst = can->Instance; + uint32_t esr = inst->ECR; + counters->tec = (esr & FDCAN_ECR_TEC_Msk) >> FDCAN_ECR_TEC_Pos; + if (esr & FDCAN_ECR_RP) { + counters->rec = 128; // "at least 128" + } else { + counters->rec = (esr & FDCAN_ECR_REC_Msk) >> FDCAN_ECR_REC_Pos; + } + if (can->Init.TxFifoQueueMode == FDCAN_TX_FIFO_OPERATION) { + counters->tx_pending = inst->TXEFS & 0x7; + } else { + counters->tx_pending = mp_popcount(inst->TXBRP); + } + counters->rx_fifo0_pending = (inst->RXF0S & FDCAN_RXF0S_F0FL_Msk) >> FDCAN_RXF0S_F0FL_Pos; + counters->rx_fifo1_pending = (inst->RXF1S & FDCAN_RXF1S_F1FL_Msk) >> FDCAN_RXF1S_F1FL_Pos; +} + +void can_disable_tx_interrupts(CAN_HandleTypeDef *can) { + HAL_FDCAN_DeactivateNotification(can, FDCAN_IT_TX_COMPLETE | FDCAN_IT_TX_ABORT_COMPLETE); +} + +void can_restart(CAN_HandleTypeDef *can) { + HAL_FDCAN_Stop(can); + HAL_FDCAN_Start(can); +} + +// Compatibility shim: call both the pyb.CAN and machine.CAN handlers if necessary, +// allow them to decide which is initialised. +static inline void call_can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { + #if MICROPY_PY_MACHINE_CAN + machine_can_irq_handler(can_id, interrupt); + #endif + if (interrupt != CAN_INT_TX_COMPLETE) { + pyb_can_irq_handler(can_id, interrupt, fifo); + } + // TODO: Need to do something to clear the transmit state if pyb.CAN is in use, I think + // (check usage of can_get_transmit_finished() from pyb CAN code) +} + +static void can_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t fifo) { + uint32_t ints, rx_fifo_ints, error_ints, tx_complete_int; ints = instance->IR & instance->IE; @@ -401,54 +584,57 @@ static void can_rx_irq_handler(uint can_id, CAN_TypeDef *instance, can_rx_fifo_t } error_ints = ints & FDCAN_IT_ERROR_STATUS_MASK; + tx_complete_int = ints & (FDCAN_IT_TX_COMPLETE | FDCAN_IT_TX_ABORT_COMPLETE); + // Disable receive interrupts, re-enabled by higher layer after calling can_receive() + // (Note: can't use __HAL_CAN API as only have a CAN_TypeDef, not CAN_HandleTypeDef) instance->IE &= ~rx_fifo_ints; - instance->IR = rx_fifo_ints | error_ints; + instance->IR = rx_fifo_ints | error_ints | tx_complete_int; if (rx_fifo_ints) { - if (rx_fifo_ints & FDCAN_IT_RX_NEW_MESSAGE_MASK) { - can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); - } if (rx_fifo_ints & FDCAN_IT_RX_FULL_MASK) { - can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); + call_can_irq_handler(can_id, CAN_INT_FIFO_FULL, fifo); } if (rx_fifo_ints & FDCAN_IT_RX_MESSAGE_LOST_MASK) { - can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); + call_can_irq_handler(can_id, CAN_INT_FIFO_OVERFLOW, fifo); + } + if (rx_fifo_ints & FDCAN_IT_RX_NEW_MESSAGE_MASK) { + call_can_irq_handler(can_id, CAN_INT_MESSAGE_RECEIVED, fifo); } } if (error_ints) { uint32_t Psr = instance->PSR; - if (error_ints & FDCAN_IT_ERROR_WARNING) { - if (Psr & FDCAN_PSR_EW) { - can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); - } - } - if (error_ints & FDCAN_IT_ERROR_PASSIVE) { - if (Psr & FDCAN_PSR_EP) { - can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); - } - } - if (error_ints & FDCAN_IT_BUS_OFF) { + if (error_ints & (FDCAN_IT_ERROR_WARNING | FDCAN_IT_ERROR_PASSIVE | FDCAN_IT_BUS_OFF)) { if (Psr & FDCAN_PSR_BO) { - can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); + call_can_irq_handler(can_id, CAN_INT_ERR_BUS_OFF, 0); + } else if (Psr & FDCAN_PSR_EP) { + call_can_irq_handler(can_id, CAN_INT_ERR_PASSIVE, 0); + } else if (Psr & FDCAN_PSR_EW) { + call_can_irq_handler(can_id, CAN_INT_ERR_WARNING, 0); } } } + + if (tx_complete_int) { + // Disable TX interrupts until we process these ones + instance->IE &= ~tx_complete_int; + call_can_irq_handler(can_id, CAN_INT_TX_COMPLETE, 0); + } } #if defined(MICROPY_HW_CAN1_TX) void FDCAN1_IT0_IRQHandler(void) { IRQ_ENTER(FDCAN1_IT0_IRQn); - can_rx_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO0); + can_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO0); IRQ_EXIT(FDCAN1_IT0_IRQn); } void FDCAN1_IT1_IRQHandler(void) { IRQ_ENTER(FDCAN1_IT1_IRQn); - can_rx_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO1); + can_irq_handler(PYB_CAN_1, FDCAN1, CAN_RX_FIFO1); IRQ_EXIT(FDCAN1_IT1_IRQn); } #endif @@ -456,13 +642,13 @@ void FDCAN1_IT1_IRQHandler(void) { #if defined(MICROPY_HW_CAN2_TX) void FDCAN2_IT0_IRQHandler(void) { IRQ_ENTER(FDCAN2_IT0_IRQn); - can_rx_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO0); + can_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO0); IRQ_EXIT(FDCAN2_IT0_IRQn); } void FDCAN2_IT1_IRQHandler(void) { IRQ_ENTER(FDCAN2_IT1_IRQn); - can_rx_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO1); + can_irq_handler(PYB_CAN_2, FDCAN2, CAN_RX_FIFO1); IRQ_EXIT(FDCAN2_IT1_IRQn); } #endif diff --git a/ports/stm32/machine_can.c b/ports/stm32/machine_can.c new file mode 100644 index 00000000000..74821b88d5b --- /dev/null +++ b/ports/stm32/machine_can.c @@ -0,0 +1,489 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024-2026 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_can.c via MICROPY_PY_MACHINE_CAN_INCLUDEFILE. +#include +#include "extmod/machine_can_port.h" +#include "can.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/gc.h" + +#if MICROPY_HW_ENABLE_FDCAN +#define CAN_BRP_MIN 1 +#define CAN_BRP_MAX 512 +#define CAN_FD_BRS_BRP_MIN 1 +#define CAN_FD_BRS_BRP_MAX 32 +#define CAN_FILTERS_STD_EXT_SEPARATE 1 + +#else // Classic bxCAN +#define CAN_BRP_MIN 1 +#define CAN_BRP_MAX 1024 +#define CAN_FILTERS_STD_EXT_SEPARATE 0 +#endif + +#define TX_EMPTY UINT32_MAX + +struct machine_can_port { + CAN_HandleTypeDef h; + uint32_t tx[CAN_TX_QUEUE_LEN]; // ID stored in each hardware tx buffer, or TX_EMPTY if empty + bool irq_state_pending; + bool error_passive; +}; + +// Convert the port agnostic CAN mode to the ST mode +static uint32_t can_port_mode(machine_can_mode_t mode) { + switch (mode) { + case MP_CAN_MODE_NORMAL: + return CAN_MODE_NORMAL; + case MP_CAN_MODE_SLEEP: + return CAN_MODE_SILENT; // Sleep is not an operating mode for ST's peripheral + case MP_CAN_MODE_LOOPBACK: + return CAN_MODE_LOOPBACK; + case MP_CAN_MODE_SILENT: + return CAN_MODE_SILENT; + case MP_CAN_MODE_SILENT_LOOPBACK: + return CAN_MODE_SILENT_LOOPBACK; + default: + assert(0); // Mode should have been checked already + return CAN_MODE_NORMAL; + } +} + +static int machine_can_port_f_clock(const machine_can_obj_t *self) { + return (int)can_get_source_freq(); +} + +static bool machine_can_port_supports_mode(const machine_can_obj_t *self, machine_can_mode_t mode) { + return mode < MP_CAN_MODE_MAX; +} + +static mp_uint_t machine_can_port_max_data_len(mp_uint_t flags) { + #if MICROPY_HW_ENABLE_FDCAN + if (flags & CAN_MSG_FLAG_FD_F) { + return 64; + } + #endif + return 8; +} + +static void machine_can_port_init(machine_can_obj_t *self) { + if (!self->port) { + self->port = m_new(struct machine_can_port, 1); + } + memset(self->port, 0, sizeof(struct machine_can_port)); + for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) { + self->port->tx[i] = TX_EMPTY; + } + + bool res = can_init(&self->port->h, + self->can_idx + 1, // Convert 0-based index to 1-based 'can_id' for lower layer + CAN_TX_QUEUE, + can_port_mode(self->mode), + self->brp, + self->sjw, + self->tseg1, + self->tseg2, + false); // auto_restart not currently exposed + + if (!res) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("CAN init failed")); + } +} + +static void machine_can_port_cancel_all_tx(machine_can_obj_t *self) { + struct machine_can_port *port = self->port; + can_disable_tx_interrupts(&port->h); + for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) { + can_cancel_transmit(&port->h, i); + port->tx[i] = TX_EMPTY; + } +} + +static void machine_can_port_deinit(machine_can_obj_t *self) { + machine_can_port_cancel_all_tx(self); + can_deinit(&self->port->h); +} + +static mp_int_t machine_can_port_send(machine_can_obj_t *self, mp_uint_t id, const byte *data, size_t data_len, mp_uint_t flags) { + int idx_empty = -1; // Empty transmit buffer, where no later index has the same ID message in it + + // Scan through the current transmit queue to find an eligible buffer for transmit + for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) { + uint32_t tx_id = self->port->tx[i]; + if (tx_id == TX_EMPTY) { + // This slot is empty + if (idx_empty == -1) { + // Still have to keep scanning as we might see a later message with the same ID, + idx_empty = i; + } + } else if (tx_id == id && !(flags & CAN_MSG_FLAG_UNORDERED)) { + // Can't queue a second message with the same ID and guarantee order + + // (Undocumented hardware limitation - CANFD reference suggests + // messages with the same ID are sent in buffer index order but + // testing shows not always the case at least on STM32H7! Unsure if + // also a limitation of bxCAN or STM32G4, but these only have 3 TX + // buffers so inserting in buffer index order is likely to run out + // of buffers relatively quickly anyway...) + + // Note: currently the driver considers a Standard and an Extended + // ID with the same numeric value to be the same ID... could fix + // this, although it's a relatively uncommon case. + return -1; + } + } + + if (idx_empty == -1) { + // No space in transmit queue + return -1; + } + + CanTxMsgTypeDef tx = { + #if MICROPY_HW_ENABLE_FDCAN + .MessageMarker = 0, + .ErrorStateIndicator = FDCAN_ESI_ACTIVE, + .TxEventFifoControl = FDCAN_NO_TX_EVENTS, + .Identifier = id, // Range checked by caller + .IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID, + .TxFrameType = (flags & CAN_MSG_FLAG_RTR) ? FDCAN_REMOTE_FRAME : FDCAN_DATA_FRAME, + .FDFormat = (flags & CAN_MSG_FLAG_FD_F) ? FDCAN_FD_CAN : FDCAN_CLASSIC_CAN, + .BitRateSwitch = (flags & CAN_MSG_FLAG_BRS) ? FDCAN_BRS_ON : FDCAN_BRS_OFF, + .DataLength = data_len, // Converted inside can_transmit_buf_index + #else // Classic + .StdId = (flags & CAN_MSG_FLAG_EXT_ID) ? 0 : id, + .ExtId = (flags & CAN_MSG_FLAG_EXT_ID) ? id : 0, + .IDE = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_ID_EXT : CAN_ID_STD, + .RTR = (flags & CAN_MSG_FLAG_RTR) ? CAN_RTR_REMOTE : CAN_RTR_DATA, + .DLC = data_len, + #endif + }; + #if !MICROPY_HW_ENABLE_FDCAN + assert(data_len <= sizeof(tx.Data)); // Also checked by caller + memcpy(tx.Data, data, data_len); + #endif + + HAL_StatusTypeDef err = can_transmit_buf_index(&self->port->h, idx_empty, &tx, data); + if (err != HAL_OK) { + return -1; + } + self->port->tx[idx_empty] = id; + + return idx_empty; +} + +static bool machine_can_port_cancel_send(machine_can_obj_t *self, mp_uint_t idx) { + return can_cancel_transmit(&self->port->h, idx); +} + +static bool machine_can_port_recv(machine_can_obj_t *self, void *data, size_t *dlen, mp_uint_t *id, mp_uint_t *flags, mp_uint_t *errors) { + CAN_HandleTypeDef *can = &self->port->h; + CanRxMsgTypeDef msg; + + for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) { + if (can_receive(can, fifo, &msg, data, 0) == 0) { + // CanRxMsgTypeDef is different for Classic vs FD + #if MICROPY_HW_ENABLE_FDCAN + *flags = ((msg.IdType == FDCAN_EXTENDED_ID) ? CAN_MSG_FLAG_EXT_ID : 0) | + ((msg.RxFrameType == FDCAN_REMOTE_FRAME) ? CAN_MSG_FLAG_RTR : 0); + *id = msg.Identifier; + *dlen = msg.DataLength; // Lower layer has converted to bytes already + #else + *flags = (msg.IDE ? CAN_MSG_FLAG_EXT_ID : 0) | + (msg.RTR ? CAN_MSG_FLAG_RTR : 0); + *id = msg.IDE ? msg.ExtId : msg.StdId; + *dlen = msg.DLC; + #endif + + *errors = self->rx_error_flags; + self->rx_error_flags = 0; + + // Re-enable any interrupts that were disabled in RX IRQ handlers + can_enable_rx_interrupts(can, fifo, self->mp_irq_trigger & MP_CAN_IRQ_RX); + + return true; + } + } + return false; +} + +static void machine_can_update_irqs(machine_can_obj_t *self) { + uint16_t triggers = self->mp_irq_trigger; + + for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) { + if (triggers & MP_CAN_IRQ_RX) { + can_enable_rx_interrupts(&self->port->h, fifo, true); + } else { + can_disable_rx_interrupts(&self->port->h, fifo); + } + } + + // Note: TX complete interrupt is always enabled to manage the internal queue state +} + +static void machine_can_port_clear_filters(machine_can_obj_t *self) { + #if MICROPY_HW_ENABLE_FDCAN + for (int f = 0; f < CAN_HW_MAX_STD_FILTER; f++) { + can_clearfilter(&self->port->h, f, false); + } + for (int f = 0; f < CAN_HW_MAX_EXT_FILTER; f++) { + can_clearfilter(&self->port->h, f, true); + } + #else + int bank_offs = (self->can_idx == 1) ? CAN_HW_MAX_FILTER : 0; // CAN2 filters index after CAN1 + for (int f = 0; f < CAN_HW_MAX_FILTER; f++) { + can_clearfilter(&self->port->h, f + bank_offs, CAN_HW_MAX_FILTER); + } + #endif +} + +#if MICROPY_HW_ENABLE_FDCAN +static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) { + int max_idx = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_HW_MAX_EXT_FILTER : CAN_HW_MAX_STD_FILTER; + if (filter_idx >= max_idx) { + mp_raise_ValueError(MP_ERROR_TEXT("too many filters for this ID type")); + } + if (flags & ~CAN_MSG_FLAG_EXT_ID) { + mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID + } + + FDCAN_FilterTypeDef filter = { + .IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID, + // FDCAN counts standard and extended id filters separately, but this is + // already accounted for in filter_idx due to CAN_FILTERS_STD_EXT_SEPARATE. + .FilterIndex = filter_idx, + .FilterType = FDCAN_FILTER_MASK, + // Round-robin between FIFO1 and FIFO0 + .FilterConfig = (filter_idx & 1) ? FDCAN_FILTER_TO_RXFIFO1 : FDCAN_FILTER_TO_RXFIFO0, + .FilterID1 = can_id, + .FilterID2 = mask, + }; + + int r = HAL_FDCAN_ConfigFilter(&self->port->h, &filter); + assert(r == HAL_OK); + (void)r; +} +#else +static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) { + if (filter_idx >= CAN_HW_MAX_FILTER) { + mp_raise_ValueError(MP_ERROR_TEXT("too many filters")); + } + if (flags & ~CAN_MSG_FLAG_EXT_ID) { + mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID + } + + if (self->can_idx == 1) { + filter_idx += CAN_HW_MAX_FILTER; // CAN2 filters index after CAN1 + } + + CAN_FilterConfTypeDef filter = { + .FilterActivation = ENABLE, + .FilterScale = CAN_FILTERSCALE_32BIT, + .FilterMode = CAN_FILTERMODE_IDMASK, + .FilterNumber = filter_idx, + // Apply the filters round-robin to each FIFO, as each filter in bxCAN is + // associated with only one FIFO. + .FilterFIFOAssignment = filter_idx % 2, + .BankNumber = CAN_HW_MAX_FILTER, // Assign same number of filters to CAN2 as CAN1 + }; + + // This somewhat corresponds to STM32 RM Figure 342 "Filter bank scale + // configuration", although the Reference Manual makes 32-bit mask filters look + // a lot more complex than they are, then the ST HAL makes it even more + // complex by only supporting filter configuration via 16-bit halfwords + // which are re-assembled to full words inside the HAL... + if (flags & CAN_MSG_FLAG_EXT_ID) { + filter.FilterIdLow = (can_id << 3) | CAN_ID_EXT; + filter.FilterIdHigh = can_id >> 13; + filter.FilterMaskIdLow = (mask << 3) | CAN_ID_EXT; + filter.FilterMaskIdHigh = mask >> 13; + } else { + filter.FilterIdLow = 0; + filter.FilterIdHigh = can_id << 5; + filter.FilterMaskIdLow = CAN_ID_EXT; // Set to require CAN_ID_EXT unset in message + filter.FilterMaskIdHigh = mask << 5; + } + + int r = HAL_CAN_ConfigFilter(&self->port->h, &filter); + assert(r == HAL_OK); // Params should be verified before passing to HAL + (void)r; +} +#endif // MICROPY_HW_ENABLE_FDCAN + +static machine_can_state_t machine_can_port_get_state(machine_can_obj_t *self) { + // machine_can_port.h defines MP_CAN_STATE_xxx enums, verify they all match + // numerically with stm32 can.h CAN_STATE_xxx enums + MP_STATIC_ASSERT((int)MP_CAN_STATE_STOPPED == (int)CAN_STATE_STOPPED); + MP_STATIC_ASSERT((int)MP_CAN_STATE_ACTIVE == (int)CAN_STATE_ERROR_ACTIVE); + MP_STATIC_ASSERT((int)MP_CAN_STATE_WARNING == (int)CAN_STATE_ERROR_WARNING); + MP_STATIC_ASSERT((int)MP_CAN_STATE_PASSIVE == (int)CAN_STATE_ERROR_PASSIVE); + MP_STATIC_ASSERT((int)MP_CAN_STATE_BUS_OFF == (int)CAN_STATE_BUS_OFF); + return (machine_can_state_t)can_get_state(&self->port->h); +} + +static void machine_can_port_update_counters(machine_can_obj_t *self) { + can_counters_t hw_counters; + struct machine_can_port *port = self->port; + machine_can_counters_t *counters = &self->counters; + + can_get_counters(&port->h, &hw_counters); + + counters->tec = hw_counters.tec; + counters->rec = hw_counters.rec; + counters->tx_pending = hw_counters.tx_pending; + counters->rx_pending = hw_counters.rx_fifo0_pending + hw_counters.rx_fifo1_pending; + + // Other fields in 'counters' are updated from ISR directly +} + +static mp_obj_t machine_can_port_get_additional_timings(machine_can_obj_t *self, mp_obj_t optional_arg) { + return mp_const_none; +} + +static void machine_can_port_restart(machine_can_obj_t *self) { + // extmod layer has already checked CAN is initialised + struct machine_can_port *port = self->port; + machine_can_port_cancel_all_tx(self); + can_restart(&port->h); + port->irq_state_pending = false; +} + +static bool clear_complete_transfer(machine_can_obj_t *self, int *index, bool *is_success) { + *index = can_get_transmit_finished(&self->port->h, is_success); + if (*index == -1) { + return false; + } + self->port->tx[*index] = TX_EMPTY; + + return true; +} + +static mp_uint_t machine_can_port_irq_flags(machine_can_obj_t *self) { + mp_uint_t flags = 0; + CAN_HandleTypeDef *can = &self->port->h; + + if (self->mp_irq_trigger & MP_CAN_IRQ_STATE && self->port->irq_state_pending) { + flags |= MP_CAN_IRQ_STATE; + self->port->irq_state_pending = false; + } + + // Check for RX + if (self->mp_irq_trigger & MP_CAN_IRQ_RX) { + for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) { + if (can_is_rx_pending(can, fifo)) { + flags |= MP_CAN_IRQ_RX; + } + } + } + + // Check for TX done + if (self->mp_irq_trigger & MP_CAN_IRQ_TX) { + bool is_success = false; + int index; + if (clear_complete_transfer(self, &index, &is_success)) { + flags |= (mp_uint_t)(index << MP_CAN_IRQ_IDX_SHIFT) | MP_CAN_IRQ_TX; + if (!is_success) { + flags |= MP_CAN_IRQ_TX_FAILED; + } + } + } + + return flags; +} + +void machine_can_irq_handler(uint can_id, can_int_t interrupt) { + assert(can_id > 0); + machine_can_obj_t *self = MP_STATE_PORT(machine_can_objs)[can_id - 1]; + if (self == NULL) { + return; // Should only hit this code path if pyb.CAN has enabled interrupt + } + struct machine_can_port *port = self->port; + machine_can_counters_t *counters = &self->counters; + bool call_irq = false; + bool irq_state = false; + + switch (interrupt) { + // RX + case CAN_INT_FIFO_FULL: + self->rx_error_flags |= CAN_RECV_ERR_FULL; + break; + case CAN_INT_FIFO_OVERFLOW: + self->rx_error_flags |= CAN_RECV_ERR_OVERRUN; + counters->rx_overruns++; + break; + case CAN_INT_MESSAGE_RECEIVED: + call_irq = call_irq || (self->mp_irq_trigger & MP_CAN_IRQ_RX); + break; + + // Error states + case CAN_INT_ERR_WARNING: + if (!port->error_passive) { + // Only count entering warning state, not leaving it + counters->num_warning++; + irq_state = true; + } + port->error_passive = false; + break; + case CAN_INT_ERR_PASSIVE: + counters->num_passive++; + port->error_passive = true; + irq_state = true; + break; + case CAN_INT_ERR_BUS_OFF: + counters->num_bus_off++; + irq_state = true; + port->error_passive = false; + break; + + // TX + case CAN_INT_TX_COMPLETE: + if (!(self->mp_irq_trigger & MP_CAN_IRQ_TX)) { + // No TX IRQ, so mark this buffer as free and move on + int index; + bool is_success = false; + clear_complete_transfer(self, &index, &is_success); + } else { + // Otherwise, the slot is marked empty after the irq calls flags() + call_irq = true; + } + break; + + default: + assert(0); // Should be unreachable + } + + if (irq_state && (self->mp_irq_trigger & MP_CAN_IRQ_STATE)) { + self->port->irq_state_pending = true; + call_irq = true; + } + + if (call_irq) { + assert(self->mp_irq_obj != NULL); // Can't set mp_irq_trigger otherwise + mp_irq_handler(self->mp_irq_obj); + } +} diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 6ae8061c413..ccf3b6d4060 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -41,6 +41,7 @@ #include "lib/littlefs/lfs2_util.h" #include "extmod/modmachine.h" #include "extmod/modnetwork.h" +#include "extmod/machine_can.h" #include "extmod/vfs.h" #include "extmod/vfs_fat.h" #include "extmod/vfs_lfs.h" @@ -763,7 +764,10 @@ void stm32_main(uint32_t reset_mode) { #endif #if MICROPY_HW_ENABLE_CAN pyb_can_deinit_all(); + #if MICROPY_PY_MACHINE_CAN + machine_can_deinit_all(); #endif + #endif // MICROPY_HW_ENABLE_CAN #if MICROPY_HW_ENABLE_DAC dac_deinit_all(); #endif diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 4ffe5752be9..e7cf28accee 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -122,6 +122,14 @@ #ifndef MICROPY_PY_MACHINE_BITSTREAM #define MICROPY_PY_MACHINE_BITSTREAM (1) #endif +#ifndef MICROPY_PY_MACHINE_CAN +#ifdef MICROPY_HW_CAN1_TX +#define MICROPY_PY_MACHINE_CAN (1) +#else +#define MICROPY_PY_MACHINE_CAN (0) +#endif +#endif +#define MICROPY_PY_MACHINE_CAN_INCLUDEFILE "ports/stm32/machine_can.c" #define MICROPY_PY_MACHINE_DHT_READINTO (1) #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new @@ -207,6 +215,17 @@ extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_NIC_ETH #endif +// Provide a port-level default of MICROPY_HW_NUM_CAN based on pin definitions +#ifndef MICROPY_HW_NUM_CAN +#if defined(MICROPY_HW_CAN3_TX) +#define MICROPY_HW_NUM_CAN 3 +#elif defined(MICROPY_HW_CAN2_TX) +#define MICROPY_HW_NUM_CAN 2 +#elif defined(MICROPY_HW_CAN1_TX) +#define MICROPY_HW_NUM_CAN 1 +#endif +#endif // MICROPY_HW_NUM_CAN + // extra constants #define MICROPY_PORT_CONSTANTS \ MACHINE_BUILTIN_MODULE_CONSTANTS \ diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index c499a28197a..a0b4de73bf9 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -39,6 +39,8 @@ #include "pyb_can.h" #include "can.h" #include "irq.h" +// For some non-port-specific utility functions +#include "extmod/machine_can.h" #if MICROPY_HW_ENABLE_CAN @@ -70,19 +72,12 @@ #define CAN_MAXIMUM_DBS1 (32) #define CAN_MAXIMUM_DBS2 (16) -#define CAN_MODE_NORMAL FDCAN_MODE_NORMAL -#define CAN_MODE_LOOPBACK FDCAN_MODE_EXTERNAL_LOOPBACK -#define CAN_MODE_SILENT FDCAN_MODE_BUS_MONITORING -#define CAN_MODE_SILENT_LOOPBACK FDCAN_MODE_INTERNAL_LOOPBACK - #define CAN1_RX0_IRQn FDCAN1_IT0_IRQn #define CAN1_RX1_IRQn FDCAN1_IT1_IRQn #if defined(CAN2) #define CAN2_RX0_IRQn FDCAN2_IT0_IRQn #define CAN2_RX1_IRQn FDCAN2_IT1_IRQn #endif - -extern const uint8_t DLCtoBytes[16]; #else #define CAN_MAX_FILTER (28) @@ -115,6 +110,7 @@ void pyb_can_deinit_all(void) { pyb_can_obj_t *can_obj = MP_STATE_PORT(pyb_can_obj_all)[i]; if (can_obj != NULL) { pyb_can_deinit(MP_OBJ_FROM_PTR(can_obj)); + MP_STATE_PORT(pyb_can_obj_all)[i] = NULL; } } } @@ -152,45 +148,10 @@ static void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki } } -static uint32_t pyb_can_get_source_freq() { - uint32_t can_kern_clk = 0; - - // Find CAN kernel clock - #if defined(STM32H7) - switch (__HAL_RCC_GET_FDCAN_SOURCE()) { - case RCC_FDCANCLKSOURCE_HSE: - can_kern_clk = HSE_VALUE; - break; - case RCC_FDCANCLKSOURCE_PLL: { - PLL1_ClocksTypeDef pll1_clocks; - HAL_RCCEx_GetPLL1ClockFreq(&pll1_clocks); - can_kern_clk = pll1_clocks.PLL1_Q_Frequency; - break; - } - case RCC_FDCANCLKSOURCE_PLL2: { - PLL2_ClocksTypeDef pll2_clocks; - HAL_RCCEx_GetPLL2ClockFreq(&pll2_clocks); - can_kern_clk = pll2_clocks.PLL2_Q_Frequency; - break; - } - } - #elif defined(STM32G4) - // STM32G4 CAN clock from reset is HSE, unchanged by MicroPython - can_kern_clk = HSE_VALUE; - #else // G0, F4, F7 and assume other MCUs too. - // CAN1/CAN2/CAN3 on APB1 use GetPCLK1Freq, alternatively use the following: - // can_kern_clk = ((HSE_VALUE / osc_config.PLL.PLLM ) * osc_config.PLL.PLLN) / - // (osc_config.PLL.PLLQ * clk_init.AHBCLKDivider * clk_init.APB1CLKDivider); - can_kern_clk = HAL_RCC_GetPCLK1Freq(); - #endif - - return can_kern_clk; -} - static void pyb_can_get_bit_timing(mp_uint_t baudrate, mp_uint_t sample_point, uint32_t max_brp, uint32_t max_bs1, uint32_t max_bs2, uint32_t min_tseg, mp_int_t *bs1_out, mp_int_t *bs2_out, mp_int_t *prescaler_out) { - uint32_t can_kern_clk = pyb_can_get_source_freq(); + uint32_t can_kern_clk = can_get_source_freq(); mp_uint_t max_baud_error = baudrate / 1000; // Allow .1% deviation const mp_uint_t MAX_SAMPLE_ERROR = 5; // round to nearest 1%, which is the param resolution sample_point *= 10; @@ -279,7 +240,12 @@ static mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp } #endif - if (!can_init(&self->can, self->can_id, args[ARG_mode].u_int, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, + mp_uint_t mode = args[ARG_mode].u_int; + #if !MICROPY_HW_ENABLE_FDCAN + mode = mode << 4; // Undo the '>> 4' set when defining the bxCAN Python constants further down in this file + #endif + + if (!can_init(&self->can, self->can_id, CAN_TX_FIFO, mode, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, args[ARG_bs1].u_int, args[ARG_bs2].u_int, args[ARG_auto_restart].u_bool)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) init failure"), self->can_id); } @@ -298,45 +264,16 @@ static mp_obj_t pyb_can_make_new(const mp_obj_type_t *type, size_t n_args, size_ mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); // work out port - mp_uint_t can_idx; - if (mp_obj_is_str(args[0])) { - const char *port = mp_obj_str_get_str(args[0]); - if (0) { - #ifdef MICROPY_HW_CAN1_NAME - } else if (strcmp(port, MICROPY_HW_CAN1_NAME) == 0) { - can_idx = PYB_CAN_1; - #endif - #ifdef MICROPY_HW_CAN2_NAME - } else if (strcmp(port, MICROPY_HW_CAN2_NAME) == 0) { - can_idx = PYB_CAN_2; - #endif - #ifdef MICROPY_HW_CAN3_NAME - } else if (strcmp(port, MICROPY_HW_CAN3_NAME) == 0) { - can_idx = PYB_CAN_3; - #endif - } else { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%s) doesn't exist"), port); - } - } else { - can_idx = mp_obj_get_int(args[0]); - } - if (can_idx < 1 || can_idx > MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) doesn't exist"), can_idx); - } - - // check if the CAN is reserved for system use or not - if (MICROPY_HW_CAN_IS_RESERVED(can_idx)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) is reserved"), can_idx); - } + mp_uint_t can_idx = machine_can_get_index(args[0]); // 0-based index pyb_can_obj_t *self; - if (MP_STATE_PORT(pyb_can_obj_all)[can_idx - 1] == NULL) { + if (MP_STATE_PORT(pyb_can_obj_all)[can_idx] == NULL) { self = mp_obj_malloc(pyb_can_obj_t, &pyb_can_type); - self->can_id = can_idx; + self->can_id = can_idx + 1; // ID is 1-based self->is_enabled = false; - MP_STATE_PORT(pyb_can_obj_all)[can_idx - 1] = self; + MP_STATE_PORT(pyb_can_obj_all)[can_idx] = self; } else { - self = MP_STATE_PORT(pyb_can_obj_all)[can_idx - 1]; + self = MP_STATE_PORT(pyb_can_obj_all)[can_idx]; } if (!self->is_enabled || n_args > 1) { @@ -382,25 +319,7 @@ static mp_obj_t pyb_can_restart(mp_obj_t self_in) { if (!self->is_enabled) { mp_raise_ValueError(NULL); } - CAN_TypeDef *can = self->can.Instance; - #if MICROPY_HW_ENABLE_FDCAN - can->CCCR |= FDCAN_CCCR_INIT; - while ((can->CCCR & FDCAN_CCCR_INIT) == 0) { - } - can->CCCR |= FDCAN_CCCR_CCE; - while ((can->CCCR & FDCAN_CCCR_CCE) == 0) { - } - can->CCCR &= ~FDCAN_CCCR_INIT; - while ((can->CCCR & FDCAN_CCCR_INIT)) { - } - #else - can->MCR |= CAN_MCR_INRQ; - while ((can->MSR & CAN_MSR_INAK) == 0) { - } - can->MCR &= ~CAN_MCR_INRQ; - while ((can->MSR & CAN_MSR_INAK)) { - } - #endif + can_restart(&self->can); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_restart_obj, pyb_can_restart); @@ -419,33 +338,19 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_state_obj, pyb_can_state); // Get info about error states and TX/RX buffers static mp_obj_t pyb_can_info(size_t n_args, const mp_obj_t *args) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); - mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : NULL, 8); + mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : mp_const_none, 8); + can_counters_t hw_counters; - #if MICROPY_HW_ENABLE_FDCAN - FDCAN_GlobalTypeDef *can = self->can.Instance; - uint32_t esr = can->ECR; - list->items[0] = MP_OBJ_NEW_SMALL_INT((esr & FDCAN_ECR_TEC_Msk) >> FDCAN_ECR_TEC_Pos); - list->items[1] = MP_OBJ_NEW_SMALL_INT((esr & FDCAN_ECR_REC_Msk) >> FDCAN_ECR_REC_Pos); - list->items[2] = MP_OBJ_NEW_SMALL_INT(self->num_error_warning); - list->items[3] = MP_OBJ_NEW_SMALL_INT(self->num_error_passive); - list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); - uint32_t TXEFS = can->TXEFS; - list->items[5] = MP_OBJ_NEW_SMALL_INT(TXEFS & 0x7); - list->items[6] = MP_OBJ_NEW_SMALL_INT((can->RXF0S & FDCAN_RXF0S_F0FL_Msk) >> FDCAN_RXF0S_F0FL_Pos); - list->items[7] = MP_OBJ_NEW_SMALL_INT((can->RXF1S & FDCAN_RXF1S_F1FL_Msk) >> FDCAN_RXF1S_F1FL_Pos); - #else - CAN_TypeDef *can = self->can.Instance; - uint32_t esr = can->ESR; - list->items[0] = MP_OBJ_NEW_SMALL_INT(esr >> CAN_ESR_TEC_Pos & 0xff); - list->items[1] = MP_OBJ_NEW_SMALL_INT(esr >> CAN_ESR_REC_Pos & 0xff); + can_get_counters(&self->can, &hw_counters); + + list->items[0] = MP_OBJ_NEW_SMALL_INT(hw_counters.tec); + list->items[1] = MP_OBJ_NEW_SMALL_INT(hw_counters.rec); list->items[2] = MP_OBJ_NEW_SMALL_INT(self->num_error_warning); list->items[3] = MP_OBJ_NEW_SMALL_INT(self->num_error_passive); list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); - int n_tx_pending = 0x01121223 >> ((can->TSR >> CAN_TSR_TME_Pos & 7) << 2) & 0xf; - list->items[5] = MP_OBJ_NEW_SMALL_INT(n_tx_pending); - list->items[6] = MP_OBJ_NEW_SMALL_INT(can->RF0R >> CAN_RF0R_FMP0_Pos & 3); - list->items[7] = MP_OBJ_NEW_SMALL_INT(can->RF1R >> CAN_RF1R_FMP1_Pos & 3); - #endif + list->items[5] = MP_OBJ_NEW_SMALL_INT(hw_counters.tx_pending); + list->items[6] = MP_OBJ_NEW_SMALL_INT(hw_counters.rx_fifo0_pending); + list->items[7] = MP_OBJ_NEW_SMALL_INT(hw_counters.rx_fifo1_pending); return MP_OBJ_FROM_PTR(list); } @@ -455,7 +360,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_can_info_obj, 1, 2, pyb_can_info) static mp_obj_t pyb_can_any(mp_obj_t self_in, mp_obj_t fifo_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); can_rx_fifo_t fifo = mp_obj_get_int(fifo_in); - return mp_obj_new_bool(can_rx_pending(&self->can, fifo) != 0); + return mp_obj_new_bool(can_is_rx_pending(&self->can, fifo) != 0); } static MP_DEFINE_CONST_FUN_OBJ_2(pyb_can_any_obj, pyb_can_any); @@ -522,13 +427,7 @@ static mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * } else { tx_msg.BitRateSwitch = FDCAN_BRS_ON; } - // Roundup DataLength to next DLC size and encode to DLC. - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(DLCtoBytes); i++) { - if (bufinfo.len <= DLCtoBytes[i]) { - tx_msg.DataLength = (i << 16); - break; - } - } + tx_msg.DataLength = bufinfo.len; // Converted to DLC encoding inside can_transmit #else tx_msg.DLC = bufinfo.len; uint8_t *tx_data = tx_msg.Data; // Data is uint32_t but holds only 1 byte @@ -602,7 +501,7 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * // Manage the rx state machine if ((fifo == CAN_RX_FIFO0 && self->rxcallback0 != mp_const_none) || (fifo == CAN_RX_FIFO1 && self->rxcallback1 != mp_const_none)) { - bool fifo_empty = can_rx_pending(&self->can, fifo) == 0; + bool fifo_empty = can_is_rx_pending(&self->can, fifo) == 0; byte *state = (fifo == CAN_RX_FIFO0) ? &self->rx_state0 : &self->rx_state1; switch (*state) { case RX_STATE_FIFO_EMPTY: @@ -879,20 +778,6 @@ static mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t *callback = callback_in; } else if (mp_obj_is_callable(callback_in)) { *callback = callback_in; - uint32_t irq = 0; - if (self->can_id == PYB_CAN_1) { - irq = (fifo == CAN_RX_FIFO0) ? CAN1_RX0_IRQn : CAN1_RX1_IRQn; - #if defined(CAN2) - } else if (self->can_id == PYB_CAN_2) { - irq = (fifo == CAN_RX_FIFO0) ? CAN2_RX0_IRQn : CAN2_RX1_IRQn; - #endif - #if defined(CAN3) - } else { - irq = (fifo == CAN_RX_FIFO0) ? CAN3_RX0_IRQn : CAN3_RX1_IRQn; - #endif - } - NVIC_SetPriority(irq, IRQ_PRI_CAN); - HAL_NVIC_EnableIRQ(irq); can_enable_rx_interrupts(&self->can, fifo, true); } return mp_const_none; @@ -953,8 +838,8 @@ static mp_uint_t can_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i uintptr_t flags = arg; ret = 0; if ((flags & MP_STREAM_POLL_RD) - && ((can_rx_pending(&self->can, 0) != 0) - || (can_rx_pending(&self->can, 1) != 0))) { + && ((can_is_rx_pending(&self->can, 0) != 0) + || (can_is_rx_pending(&self->can, 1) != 0))) { ret |= MP_STREAM_POLL_RD; } #if MICROPY_HW_ENABLE_FDCAN @@ -974,12 +859,15 @@ static mp_uint_t can_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i // IRQ handler, called from lower layer can.c or fdcan.c in ISR context -void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { +void pyb_can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { mp_obj_t callback; pyb_can_obj_t *self; byte *state; self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; + if (self == NULL) { + return; // Should only hit this code path if machine.CAN has enabled interrupt + } if (fifo == CAN_RX_FIFO0) { callback = self->rxcallback0; @@ -1014,7 +902,7 @@ void can_irq_handler(uint can_id, can_int_t interrupt, can_rx_fifo_t fifo) { return; default: - return; // Should be unreachable + return; // CAN_INT_TX_COMPLETE is ignored by pyb.CAN } // Run the callback diff --git a/ports/stm32/pyb_can.h b/ports/stm32/pyb_can.h index a82043d7863..c548bc9367d 100644 --- a/ports/stm32/pyb_can.h +++ b/ports/stm32/pyb_can.h @@ -52,7 +52,5 @@ extern const mp_obj_type_t pyb_can_type; void pyb_can_deinit_all(void); void pyb_can_init0(void); -void pyb_can_irq_handler(uint can_id, can_rx_fifo_t fifo, can_int_t interrupt); - #endif #endif diff --git a/tests/extmod_hardware/machine_can2.py b/tests/extmod_hardware/machine_can2.py new file mode 100644 index 00000000000..0ecced82865 --- /dev/null +++ b/tests/extmod_hardware/machine_can2.py @@ -0,0 +1,44 @@ +# Test machine.CAN(1) and machine.CAN(2) using loopback +# +# Single device test, assumes support for loopback and no connections to the CAN pins +# +# This test is ported from tests/ports/stm32/pyb_can2.py + +try: + from machine import CAN + + CAN(2, 125_000) +except (ImportError, ValueError): + print("SKIP") + raise SystemExit + +import time + +# Setting up each CAN peripheral independently is deliberate here, to catch +# catch cases where initialising CAN2 breaks CAN1 + +can1 = CAN(1, 125_000, mode=CAN.MODE_LOOPBACK) +can1.set_filters([(0x100, 0x700, 0)]) + +can2 = CAN(2, 125_000, mode=CAN.MODE_LOOPBACK) +can2.set_filters([(0x000, 0x7F0, 0)]) + +# Drain any old messages in RX FIFOs +for can in (can1, can2): + while can.recv(): + pass + +for id, can in ((1, can1), (2, can2)): + print("testing", id) + # message1 should only receive on can1, message2 on can2 + can.send(0x123, b"message1", 0) + can.send(0x003, "message2", 0) + time.sleep_ms(10) + did_recv = False + while res := can.recv(): + did_recv = True + print(hex(res[0]), bytes(res[1]), res[2], res[3]) + if not did_recv: + print("no rx!") + +print("done") diff --git a/tests/extmod_hardware/machine_can2.py.exp b/tests/extmod_hardware/machine_can2.py.exp new file mode 100644 index 00000000000..bfb6a5088ba --- /dev/null +++ b/tests/extmod_hardware/machine_can2.py.exp @@ -0,0 +1,5 @@ +testing 1 +0x123 b'message1' 0 0 +testing 2 +0x3 b'message2' 0 0 +done diff --git a/tests/extmod_hardware/machine_can_timings.py b/tests/extmod_hardware/machine_can_timings.py new file mode 100644 index 00000000000..441059f5da5 --- /dev/null +++ b/tests/extmod_hardware/machine_can_timings.py @@ -0,0 +1,60 @@ +# Test machine.CAN timings results +# +# Single device test, assumes no connections to the CAN pins + +try: + from machine import CAN +except ImportError: + print("SKIP") + raise SystemExit + +import unittest + +from target_wiring import can_args, can_kwargs + + +class TestTimings(unittest.TestCase): + def test_bitrate(self): + for bitrate in (125_000, 250_000, 500_000, 1_000_000): + can = CAN(*can_args, bitrate=bitrate, **can_kwargs) + print(can) + timings = can.get_timings() + print(timings) + # Actual bitrate may not be exactly equal to requested rate + self.assertAlmostEqual(timings[0], bitrate, delta=1_000) + can.deinit() + + def test_sample_point(self): + # Verify that tseg1 and tseg2 are set correctly from the sample_point argument + for sample_point in (66, 75, 95): + can = CAN(*can_args, bitrate=500_000, sample_point=sample_point, **can_kwargs) + _bitrate, _sjw, tseg1, tseg2, _fd, _port = can.get_timings() + print(f"sample_point={sample_point}, tseg1={tseg1}, tseg2={tseg2}") + self.assertAlmostEqual(sample_point / 100, tseg1 / (tseg1 + tseg2), delta=0.05) + can.deinit() + + def test_tseg_args(self): + # Verify that tseg1 and tseg2 are set correctly and sample_point is ignored if these are provided + for tseg1, tseg2 in ((5, 2), (16, 8), (16, 5), (15, 5)): + print(f"tseg1={tseg1} tseg2={tseg2}") + can = CAN( + *can_args, bitrate=250_000, tseg1=tseg1, tseg2=tseg2, sample_point=99, **can_kwargs + ) + bitrate, _sjw, ret_tseg1, ret_tseg2, _fd, _port = can.get_timings() + self.assertEqual(ret_tseg1, tseg1) + self.assertEqual(ret_tseg2, tseg2) + + def test_invalid_timing_args(self): + # Test various kwargs out of their allowed value ranges + with self.assertRaises(ValueError): + CAN(*can_args, bitrate=250_000, tseg1=55, **can_kwargs) + with self.assertRaises(ValueError): + CAN(*can_args, bitrate=500_000, tseg2=9, **can_kwargs) + with self.assertRaises(ValueError): + CAN(*can_args, bitrate=-1, **can_kwargs) + with self.assertRaises(ValueError): + CAN(*can_args, bitrate=500_000, sample_point=101, **can_kwargs) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/multi_extmod/machine_can_01_rxtx_simple.py b/tests/multi_extmod/machine_can_01_rxtx_simple.py new file mode 100644 index 00000000000..fbc7692926d --- /dev/null +++ b/tests/multi_extmod/machine_can_01_rxtx_simple.py @@ -0,0 +1,35 @@ +from machine import CAN +import time + +ID_0 = 0x50 +ID_1 = 0x50333 +FLAGS_1 = CAN.FLAG_EXT_ID # ID_1 is an extended CAN ID + +can = CAN(1, 500_000) +can.set_filters(None) # receive all + + +def print_rx(can_id, data, flags, errors): + print(hex(can_id), bytes(data), hex(flags), hex(errors)) + + +def instance0(): + multitest.next() + + # receive from instance1 first + while not (rx := can.recv()): + time.sleep(0) + print_rx(*rx) + + # now send one + can.send(ID_0, b"1234") + + +def instance1(): + multitest.next() + + can.send(ID_1, b"ABCD", FLAGS_1) + + while not (rx := can.recv()): + time.sleep(0) + print_rx(*rx) diff --git a/tests/multi_extmod/machine_can_01_rxtx_simple.py.exp b/tests/multi_extmod/machine_can_01_rxtx_simple.py.exp new file mode 100644 index 00000000000..479b6809490 --- /dev/null +++ b/tests/multi_extmod/machine_can_01_rxtx_simple.py.exp @@ -0,0 +1,4 @@ +--- instance0 --- +0x50333 b'ABCD' 0x2 0x0 +--- instance1 --- +0x50 b'1234' 0x0 0x0 diff --git a/tests/multi_extmod/machine_can_02_rx_callback.py b/tests/multi_extmod/machine_can_02_rx_callback.py new file mode 100644 index 00000000000..80b13dcd622 --- /dev/null +++ b/tests/multi_extmod/machine_can_02_rx_callback.py @@ -0,0 +1,122 @@ +from machine import CAN +import time + +# Test the CAN.IRQ_RX irq handler, including overflow + +rx_overflow = False +rx_full = False +received = [] + +# CAN IDs +ID_SPAM = 0x345 # messages spammed into the receive FIFO +ID_ACK_OFLOW = 0x055 # message the receiver sends after it's seen an overflow +ID_AFTER = 0x100 # message the sender sends after the ACK + +can = CAN(1, 500_000) + + +# A very basic "soft" receiver handler that stores received messages into a global list +def receiver_irq_recv(can): + global rx_overflow, rx_full + + assert can.irq().flags() & can.IRQ_RX # the only enabled IRQ + + can_id, data, _flags, errors = can.recv() + + received.append((can_id, None)) + + # The FIFO is expected not to overflow by itself, wait until 40 messages + # have been received and then block the receive handler to induce an overflow + if len(received) == 40: + assert not rx_overflow # shouldn't have already happened, either + time.sleep_ms(500) + + if not rx_overflow and (errors & CAN.RECV_ERR_OVERRUN): + # expected this should happen on the very next message after + # the one where we slept for 500ms + print("irq_recv overrun", len(received)) + received.clear() # check we still get some messages, see rx_spam print line below + rx_overflow = True + + # also expect the FIFO to be FULL again immediately after overrunning and rx_overflow event + if rx_overflow and (errors & CAN.RECV_ERR_OVERRUN | CAN.RECV_ERR_FULL) == CAN.RECV_ERR_FULL: + rx_full = True + + +# Receiver +def instance0(): + can.irq(receiver_irq_recv, trigger=can.IRQ_RX, hard=False) + + can.set_filters(None) # receive all + + multitest.next() + + while not rx_overflow: + pass # Resume ASAP after FIFO0 overflows + + can.send(ID_ACK_OFLOW, b"overflow") + + # at least one ID_SPAM message should have been received + # *after* we overflowed and 'received' was clear in the irq handler + print("rx_spam", any(r[0] == ID_SPAM for r in received)) + + # wait until the "after" message is received + for n in range(100): + if any(r[0] == ID_AFTER for r in received): + break + time.sleep_ms(10) + + can.irq(None) # disable the IRQ + received.clear() + + # at some point while waiting for ID_AFTER the FIFO should have gotten + # full again + print("rx_full", rx_full) + + # now IRQ is disabled, no new messages should be received + time.sleep_ms(250) + print("len", len(received)) + + +received_ack = False + +# reusing the result buffer so sender_irq_recv can be 'hard' +sender_irq_result = [None, memoryview(bytearray(64)), None, None] + + +def sender_irq_recv(can): + global received_ack + + assert can.irq().flags() & can.IRQ_RX # the only enabled IRQ + + can_id, data, _flags, _errors = can.recv(sender_irq_result) + print("sender_irq_recv", can_id, len(data)) # should be ID_ACK_OFLOW and "overflow" payload + received_ack = True + + +# Sender +def instance1(): + can.irq(sender_irq_recv, CAN.IRQ_RX, hard=True) + + can.set_filters(None) + + multitest.next() + + # Spam out messages until the receiver tells us its RX FIFO is full. + # + # The RX FIFO on the receiver can vary from 3 deep (BXCAN) to 25 deep (STM32H7), + # so we keep sending to it until we see a CAN message on ID_ACK_OFLOW indicating + # the receiver's FIFO has overflowed + while not received_ack: + for i in range(255): + while can.send(ID_SPAM, bytes([i] * 8)) is None and not received_ack: + # Don't overflow the TX FIFO + time.sleep_ms(1) + if received_ack: + break + + # give the receiver some time to make space in the FIFO + time.sleep_ms(200) + + # send the final message, the receiver should get this one + can.send(ID_AFTER, b"aaaaa") diff --git a/tests/multi_extmod/machine_can_02_rx_callback.py.exp b/tests/multi_extmod/machine_can_02_rx_callback.py.exp new file mode 100644 index 00000000000..8e4e5dc6475 --- /dev/null +++ b/tests/multi_extmod/machine_can_02_rx_callback.py.exp @@ -0,0 +1,7 @@ +--- instance0 --- +irq_recv overrun 41 +rx_spam True +rx_full True +len 0 +--- instance1 --- +sender_irq_recv 85 8 diff --git a/tests/multi_extmod/machine_can_03_rx_filters.py b/tests/multi_extmod/machine_can_03_rx_filters.py new file mode 100644 index 00000000000..a56acbbe91e --- /dev/null +++ b/tests/multi_extmod/machine_can_03_rx_filters.py @@ -0,0 +1,103 @@ +from machine import CAN +import time + +# Test for filtering capabilities + +can = CAN(1, 500_000) + +# IDs and filter phases used for the 'single id' part of the test +SINGLE_EXT_ID = (0x1234_5678, 0x1FFF_FFFF, CAN.FLAG_EXT_ID) +SINGLE_STD_ID = (0x505, 0x7FF, 0) +SINGLE_ID_PHASES = [ + ("single ext id", [SINGLE_EXT_ID]), + ("single std id", [SINGLE_STD_ID]), + ("ext+std ids", [SINGLE_EXT_ID, SINGLE_STD_ID]), + ("std+ext ids", [SINGLE_STD_ID, SINGLE_EXT_ID]), # these two should be equivalent + ("accept none", []), + ("accept all", None), + ("accept none again", ()), +] + + +# Receiver +def receiver_irq_recv(can): + assert can.irq().flags() & can.IRQ_RX # the only enabled IRQ + can_id, data, _flags, _errors = can.recv() + print("recv", hex(can_id), data.hex()) + + +def instance0(): + can.irq(receiver_irq_recv, trigger=can.IRQ_RX, hard=False) + + multitest.next() + + # Configure to receive standard frames (in a range), and + # extended frames (in a range). + can.set_filters([(0x300, 0x300, 0), (0x3000, 0x3000, CAN.FLAG_EXT_ID)]) + multitest.broadcast("ready id ranges") + + # Run through the phases of filtering for individual IDs + for phase, filters in SINGLE_ID_PHASES: + multitest.wait("configure " + phase) + if filters and len(filters) > CAN.FILTERS_MAX: + # this check really exists to add test coverage for the FILTERS_MAX constant + print("Warning: Too many filters for hardware!") + can.set_filters(filters) + print("receiver configured " + phase) + multitest.broadcast("ready " + phase) + + multitest.wait("Sender done") + + +def send_messages(messages): + for can_id, payload, flags in messages: + r = can.send(can_id, payload, flags) + if r is None: + print("Failed to send:", hex(can_id), payload.hex()) + time.sleep_ms(5) # avoid flooding either our or the receiver's FIFO + + +# Sender +def instance1(): + multitest.next() + multitest.wait("ready id ranges") + + print("Sending ID ranges...") + for i in range(3): + send_messages( + [ + (0x345, bytes([i, 0xFF] * (i + 1)), 0), + (0x3700 + i, bytes([0xEE] * (i + 1)), CAN.FLAG_EXT_ID), + (0x123, b"abcdef", 0), # matches no filter, expect ACKed but not received + ] + ) + + # Now move on to single ID filtering + + single_id_messages = [ + (0x1234_5678, b"\x01\x02\x03\x04\x05", CAN.FLAG_EXT_ID), # matches ext id + (0x0234_5678, b"\x00\x00", CAN.FLAG_EXT_ID), # no match + (0x678, b"\x00\x01", 0), # no match + (0x505, b"\x06\x07\x08\x09\x0a\x0b", 0), # matches standard id + (0x345, b"\x00\x02", 0), # no match (in prev filter) + (0x1234_5679, b"\x00\x03", CAN.FLAG_EXT_ID), # no match + (0x3705, b"\x00\x04", CAN.FLAG_EXT_ID), # no match (in prev filter) + (0x1234_5678, b"\x01\x02\x03", CAN.FLAG_EXT_ID), # matches ext id + (0x505, b"\x04\x05\x06", 0), # matches standard id + (0x505, b"\x00\x05", CAN.FLAG_EXT_ID), # no match (is ext id) + (0x507, b"\x00\x06", 0), # no match + (0x1334_5678, b"\x00\x07", CAN.FLAG_EXT_ID), # no match + (0x1234_5670, b"\x00\x08", CAN.FLAG_EXT_ID), # no match + ] + + # Send the same list of messages for each phase of the test. + # The receiver will have configured different filters, and the .exp + # file is what selects which messages should be received or not. + for phase, _ in SINGLE_ID_PHASES: + multitest.broadcast("configure " + phase) + multitest.wait("ready " + phase) + print("Sending for " + phase + "...") + send_messages(single_id_messages) + + print("Sender done") + multitest.broadcast("Sender done") diff --git a/tests/multi_extmod/machine_can_03_rx_filters.py.exp b/tests/multi_extmod/machine_can_03_rx_filters.py.exp new file mode 100644 index 00000000000..d55c0c97d15 --- /dev/null +++ b/tests/multi_extmod/machine_can_03_rx_filters.py.exp @@ -0,0 +1,49 @@ +--- instance0 --- +recv 0x345 00ff +recv 0x3700 ee +recv 0x345 01ff01ff +recv 0x3701 eeee +recv 0x345 02ff02ff02ff +recv 0x3702 eeeeee +receiver configured single ext id +recv 0x12345678 0102030405 +recv 0x12345678 010203 +receiver configured single std id +recv 0x505 060708090a0b +recv 0x505 040506 +receiver configured ext+std ids +recv 0x12345678 0102030405 +recv 0x505 060708090a0b +recv 0x12345678 010203 +recv 0x505 040506 +receiver configured std+ext ids +recv 0x12345678 0102030405 +recv 0x505 060708090a0b +recv 0x12345678 010203 +recv 0x505 040506 +receiver configured accept none +receiver configured accept all +recv 0x12345678 0102030405 +recv 0x2345678 0000 +recv 0x678 0001 +recv 0x505 060708090a0b +recv 0x345 0002 +recv 0x12345679 0003 +recv 0x3705 0004 +recv 0x12345678 010203 +recv 0x505 040506 +recv 0x505 0005 +recv 0x507 0006 +recv 0x13345678 0007 +recv 0x12345670 0008 +receiver configured accept none again +--- instance1 --- +Sending ID ranges... +Sending for single ext id... +Sending for single std id... +Sending for ext+std ids... +Sending for std+ext ids... +Sending for accept none... +Sending for accept all... +Sending for accept none again... +Sender done diff --git a/tests/multi_extmod/machine_can_04_tx_order.py b/tests/multi_extmod/machine_can_04_tx_order.py new file mode 100644 index 00000000000..204bdafd59d --- /dev/null +++ b/tests/multi_extmod/machine_can_04_tx_order.py @@ -0,0 +1,170 @@ +from machine import CAN +import time +from random import seed, randrange + +import micropython + +micropython.alloc_emergency_exception_buf(256) +seed(0) + +# Testing that transmit order obeys the priority ordering + +ID_LOW = 0x500 +ID_HIGH = 0x200 + +NUM_MSGS = 255 + +MSG_LEN = 4 + +can = CAN(1, 500_000) + + +def check_sequence(items, label): + # The full range of NUM_MSGS values should have been received or sent, in + # order, without duplicates + if len(items) != NUM_MSGS: + print(label, "wrong count", len(items), "vs", NUM_MSGS) + if items == list(range(NUM_MSGS)): + print(label, "OK") + else: + print(label, "error:") + print(items) + + +# Receiver + +# lists of received messages, one list per ID +received_low = [] +received_high = [] + + +def irq_recv(can): + while can.irq().flags() & can.IRQ_RX: + can_id, data, flags, _errors = can.recv() + + if can_id == ID_LOW and len(data) == MSG_LEN: + received_low.append(data[0]) + elif can_id == ID_HIGH and len(data) == MSG_LEN: + received_high.append(data[0]) + else: + print("unexpected recv", can_id, data, flags) + + +def instance0(): + can.irq(irq_recv, trigger=can.IRQ_RX, hard=False) + can.set_filters(None) # receive all + + multitest.next() + + multitest.wait("sender done") + check_sequence(received_low, "Low prio received") + check_sequence(received_high, "High prio received") + + +# Sender + +## Messages pending to send +pending_low = list(range(NUM_MSGS)) +pending_high = list(range(NUM_MSGS)) + +# List of the messages currently queued to send +tx_queue = [None] * CAN.TX_QUEUE_LEN + +# Messages sent, recorded in order as [high_prio, val] +sent = [] +for _ in range(NUM_MSGS * 2): + sent.append([None, None]) +num_sent = 0 + + +def irq_send(can): + global num_sent + + while flags := can.irq().flags(): + assert flags & can.IRQ_TX # the only enabled IRQ + + idx = (flags >> can.IRQ_TX_IDX_SHIFT) & can.IRQ_TX_IDX_MASK + success = not (flags & can.IRQ_TX_FAILED) + + if not success: + return # We don't worry about failures here + + if not tx_queue[idx]: + print("bad done", idx, success) + return + + was_high, val = tx_queue[idx] + tx_queue[idx] = None + sent[num_sent][0] = was_high + sent[num_sent][1] = val + num_sent += 1 + + +def instance1(): + # note: this test can pass with hard=True, but in a debug build + # the completion IRQ may race ahead of setting tx_queue[idx], below + can.irq(irq_send, trigger=can.IRQ_TX, hard=False) + data = bytearray(MSG_LEN) + + multitest.next() + + while pending_low or pending_high: + if pending_high: + val = pending_high.pop(0) + data[0] = val + data[1] = 1 + while True: + idx = can.send(ID_HIGH, data) + if idx is None: + continue # keep trying until a queue spot opens up + old = tx_queue[idx] + tx_queue[idx] = (True, val) + if old: + print("error high priority queue race", idx, val, old) + break + + for _ in range(randrange(4)): + # Try and queue many low priority messages (expecting most will fail) + if pending_low: + val = pending_low[0] + data[0] = val + data[1] = 0 + idx = can.send(ID_LOW, data) + if idx is None: + # don't retry indefinitely for low priority messages + continue + + old = tx_queue[idx] + tx_queue[idx] = (False, val) + pending_low.pop(0) + if old is not None: + print("error low priority queue race", idx, val, old) + + print("waiting for tx queue to empty...") + while any(x is not None for x in tx_queue): + pass + + multitest.broadcast("sender done") + + # Check we sent the right number of messages + if num_sent != 2 * NUM_MSGS: + print("Sent %d expected %d" % (num_sent, 2 * NUM_MSGS)) + else: + print("Sent right number of messages") + + # Check the low and high priority messages all arrived in order + sent_low = [val for (prio, val) in sent[:num_sent] if prio == False] + sent_high = [val for (prio, val) in sent[:num_sent] if prio == True] + check_sequence(sent_low, "Low prio sent") + check_sequence(sent_high, "High prio sent") + + # check that high priority message queue items always stayed ahead of the low priority + high_val = -1 + for idx, (prio, val) in enumerate(sent): + if prio: + high_val = val + elif high_val <= val and val < NUM_MSGS - 1: + print( + "Low priority message %d overtook high priority %d at index %d" + % (val, high_val, idx) + ) diff --git a/tests/multi_extmod/machine_can_04_tx_order.py.exp b/tests/multi_extmod/machine_can_04_tx_order.py.exp new file mode 100644 index 00000000000..a2de6ee2706 --- /dev/null +++ b/tests/multi_extmod/machine_can_04_tx_order.py.exp @@ -0,0 +1,8 @@ +--- instance0 --- +Low prio received OK +High prio received OK +--- instance1 --- +waiting for tx queue to empty... +Sent right number of messages +Low prio sent OK +High prio sent OK diff --git a/tests/multi_extmod/machine_can_05_tx_prio_cancel.py b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py new file mode 100644 index 00000000000..64756a1a1af --- /dev/null +++ b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py @@ -0,0 +1,118 @@ +from machine import CAN +import time + +# Check that cancelling a low priority outgoing message and replacing it with a +# high priority message causes it to be transmitted successfully onto a busy bus + +recv = [] + +ITERS = 5 + +can = CAN(1, 500_000) + + +def irq_recv(can): + global recv_std_id + while can.irq().flags() & can.IRQ_RX: + can_id, data, flags, _errors = can.recv() + assert flags & CAN.FLAG_EXT_ID # test uses all extended IDs + if len(recv) < ITERS: + recv.append(can_id) + + +def instance0(): + can.irq(irq_recv, trigger=can.IRQ_RX, hard=False) + can.set_filters(None) # receive all + + multitest.next() + + # "Babble" medium priority messages onto the bus to prevent + # instance1() from sending anything lower priority than this + while len(recv) < ITERS: + for id in range(0x5000, 0x6000): + can.send(id, b"BABBLE", CAN.FLAG_EXT_ID) + if len(recv) >= ITERS: + break + + print("received", ITERS, "messages") + for can_id in recv: + print(hex(can_id)) # should be the high priority messages from instance1, only + + multitest.wait("sender done") + print("done") + + +last_idx = 0 +total_cancels = 0 +total_sent = 0 + + +def irq_send(can): + global total_cancels, total_sent + + while flags := can.irq().flags(): + assert flags & can.IRQ_TX # the only enabled IRQ + + idx = (flags >> can.IRQ_TX_IDX_SHIFT) & can.IRQ_TX_IDX_MASK + + if flags & can.IRQ_TX_FAILED: + # we should only see failed transmits due to cancels in buffer 'last_idx' + assert idx == last_idx + total_cancels += 1 + else: + # this includes the messages we explicitly send, plus queued low + # priority messages once the receiver stops 'babbling' on the bus + total_sent += 1 + + +def instance1(): + global last_idx + can.irq(irq_send, trigger=can.IRQ_TX, hard=True) + + multitest.next() + + for i in range(ITERS): + # Fill the transmit queue with low priority messages (all extended IDs) + last_idx = 0 + if i < 3: + # For the first 3 iterations, send unique message IDs + id_range = range(0x7000, 0x7FFF) + flags = CAN.FLAG_EXT_ID + else: + # For the last iterations, repeat the same ID but tell controller to ignore + # ordering (allows it to queue more than one despite hardware limitations) + id_range = [0x50000 + i] * CAN.TX_QUEUE_LEN + flags = CAN.FLAG_EXT_ID | CAN.FLAG_UNORDERED + + for id in id_range: + idx = can.send(id, b"LOWPRIO", flags) + if idx is None: + break # send queue is full, stop trying to send + last_idx = idx + + time.sleep_ms(50) # the send queue shouldn't empty as instance0 is "babbling" + + # try and cancel the last message we queued + res = can.cancel_send(last_idx) + print(i, "cancel result", res) + + # send a high priority message, that we expect to go out + idx = can.send(0x500 + i, b"HIPRIO", CAN.FLAG_EXT_ID) + print(i, "send result", idx is not None) + + # make sure this message is sent onto the bus + time.sleep_ms(1) + + multitest.broadcast("sender done") + + # let the entire transmit queue drain, now instance0 should have gone quiet + time.sleep_ms(50) + + print("total cancels", total_cancels) # should equal ITERS + + if total_sent == CAN.TX_QUEUE_LEN - 1 + ITERS: + # expect we send one message for each of ITERS, plus all low priority + # queued messages once instance0 stops babbling on the bus + print("total sent OK") + else: + print("total sent", total_sent, CAN.TX_QUEUE_LEN - 1 + ITERS) diff --git a/tests/multi_extmod/machine_can_05_tx_prio_cancel.py.exp b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py.exp new file mode 100644 index 00000000000..26bab097c44 --- /dev/null +++ b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py.exp @@ -0,0 +1,21 @@ +--- instance0 --- +received 5 messages +0x500 +0x501 +0x502 +0x503 +0x504 +done +--- instance1 --- +0 cancel result True +0 send result True +1 cancel result True +1 send result True +2 cancel result True +2 send result True +3 cancel result True +3 send result True +4 cancel result True +4 send result True +total cancels 5 +total sent OK diff --git a/tests/multi_extmod/machine_can_06_remote_req.py b/tests/multi_extmod/machine_can_06_remote_req.py new file mode 100644 index 00000000000..4a1414c0946 --- /dev/null +++ b/tests/multi_extmod/machine_can_06_remote_req.py @@ -0,0 +1,70 @@ +from machine import CAN +import time + +# Test CAN remote transmission requests + +ID = 0x750 + +FILTER_ID = 0x101 +FILTER_MASK = 0x7FF + +can = CAN(1, 500_000) + + +def receiver_irq_recv(can): + assert can.irq().flags() & can.IRQ_RX # the only enabled IRQ + can_id, data, flags, _errors = can.recv() + is_rtr = flags == CAN.FLAG_RTR + print( + "recv", + hex(can_id), + is_rtr, + len(data) if is_rtr else bytes(data), + ) + if is_rtr: + # The 'data' response of a remote request should be all zeroes + assert bytes(data) == b"\x00" * len(data) + + +# Receiver +def instance0(): + can.irq(receiver_irq_recv, trigger=can.IRQ_RX, hard=False) + can.set_filters(None) # receive all + + multitest.next() + + multitest.wait("enable filter") + can.set_filters([(FILTER_ID, FILTER_MASK, 0)]) + multitest.broadcast("filter set") + + multitest.wait("done") + + +# Sender +def instance1(): + multitest.next() + + can.send(ID, b"abc", CAN.FLAG_RTR) # length==3 remote request + time.sleep_ms(5) + can.send(ID, b"abc", 0) # regular message using the same ID + time.sleep_ms(5) + can.send(ID, b"abcde", CAN.FLAG_RTR) # length==5 remote request + time.sleep_ms(5) + + multitest.broadcast("enable filter") + multitest.wait("filter set") + + # these two messages should be filtered out + can.send(ID, b"abc", CAN.FLAG_RTR) # length==3 remote request + time.sleep_ms(5) + can.send(ID, b"abc", 0) # regular message using the same ID + time.sleep_ms(5) + + # these messages should be filtered in + can.send(FILTER_ID, b"def", CAN.FLAG_RTR) # length==3 remote request + time.sleep_ms(5) + can.send(FILTER_ID, b"hij", 0) # regular message using the same ID + time.sleep_ms(5) + + multitest.broadcast("done") + print("done") diff --git a/tests/multi_extmod/machine_can_06_remote_req.py.exp b/tests/multi_extmod/machine_can_06_remote_req.py.exp new file mode 100644 index 00000000000..fe94508ba3d --- /dev/null +++ b/tests/multi_extmod/machine_can_06_remote_req.py.exp @@ -0,0 +1,8 @@ +--- instance0 --- +recv 0x750 True 3 +recv 0x750 False b'abc' +recv 0x750 True 5 +recv 0x101 True 3 +recv 0x101 False b'hij' +--- instance1 --- +done diff --git a/tests/multi_extmod/machine_can_07_error_states.py b/tests/multi_extmod/machine_can_07_error_states.py new file mode 100644 index 00000000000..e7eec513ee1 --- /dev/null +++ b/tests/multi_extmod/machine_can_07_error_states.py @@ -0,0 +1,205 @@ +from machine import CAN +from micropython import const +import time + +# Test that without a second CAN node on the network the controller will +# correctly go into the correct error states, and can then recover. +# +# Note this test depends on no other CAN node being active on the network apart +# from the two test instances. (Although it's OK for extra nodes to be in a +# "listen only" mode where they won't ACK messages.) + +rx_overflow = False +rx_full = False +received = [] + +# CAN IDs +_ID = const(0x100) + +# can.state() result list indexes, as constants +_IDX_TEC = const(0) +_IDX_REC = const(1) +_IDX_NUM_WARNING = const(2) +_IDX_NUM_PASSIVE = const(3) +_IDX_NUM_BUS_OFF = const(4) +_IDX_PEND_TX = const(5) + +can = CAN(1, 500_000) + + +def state_name(state): + for name in dir(CAN): + if name.startswith("STATE_") and state == getattr(CAN, name): + return name + return f"UNKNOWN-{state}" + + +def irq_recv(can): + while can.irq().flags() & can.IRQ_RX: + can_id, data, _flags, errors = can.recv() + print("recv", hex(can_id), data.hex()) + + +# Receiver +def instance0(): + can.irq(irq_recv, trigger=can.IRQ_RX, hard=False) + + can.set_filters(None) # receive all + + multitest.next() + + # Receive at least one CAN message before sender asks us to disable the controller + + multitest.wait("disable receiver") + can.deinit() + print("can deinit()") + multitest.broadcast("receiver disabled") + + # Wait for the sender to tell us to re-enable + + multitest.wait("enable receiver") + # note the irq is no longer active after deinit() + can.init(500_000) + print("can init()") + multitest.broadcast("receiver enabled") + + # Receive CAN messages until the sender asks us to switch to an invalid baud rate + + multitest.wait("switch baud") + can.init(125_000) + print("can switch baud") + multitest.broadcast("switched baud") + + time.sleep_ms(1) + + print("sending bad msg") + # trying to send this frame should introduce more bus errors + idx_bad = can.send(_ID, b"BADBAUD") + + multitest.wait("fix baud") + print("sending cancelling") + print("cancelled", can.cancel_send(idx_bad)) + print("re-init") + can.init(500_000) + multitest.broadcast("fixed baud") + + # Should be receiving CAN messages OK again + + print("done") + + +def irq_sender(can): + while flags := can.irq().flags(): + if flags & can.IRQ_STATE: + print("irq state", can.state()) + if flags & can.IRQ_TX: + print("irq sent", not (flags & can.IRQ_TX_FAILED)) + + +# Sender +def instance1(): + can.irq(irq_sender, CAN.IRQ_STATE | CAN.IRQ_TX, hard=False) + + can.set_filters(None) + + multitest.next() + + print("started", state_name(can.state())) # should be ERROR_ACTIVE + + # Send a single message to the receiver, to verify it's working + can.send(_ID, b"PAYLOAD") + active_counters = can.get_counters() + # print(active_counters) # DEBUG + + multitest.broadcast("disable receiver") + multitest.wait("receiver disabled") + + # Now the receiver shouldn't be ACKing our frames, queue will stay full + # ... we will get the ISR for ERROR_WARNING but it'll go from ERROR_WARNING to ERROR_PASSIVE + # very quickly as all messages are failing + can.send(_ID, b"MORE") + while can.state() in (CAN.STATE_ACTIVE, CAN.STATE_WARNING): + pass + + print(state_name(can.state())) # should be ERROR_PASSIVE now + passive_counters = can.get_counters() + # print(passive_counters) # DEBUG + print("tec increased", passive_counters[_IDX_TEC] > active_counters[_IDX_TEC]) + print("tec over thresh", passive_counters[_IDX_TEC] >= 128) + # we should have counted exactly one ERROR_WARNING and ERROR_PASSIVE transition + print( + "counted warning", + passive_counters[_IDX_NUM_WARNING] == active_counters[_IDX_NUM_WARNING] + 1, + ) + print( + "counted passive", + passive_counters[_IDX_NUM_PASSIVE] == active_counters[_IDX_NUM_PASSIVE] + 1, + ) + print("some pending tx", passive_counters[_IDX_PEND_TX] > 0) + + # Re-enable the receiver which should allow us to go from ERROR_PASSIVE to ERROR_WARNING + multitest.broadcast("enable receiver") + multitest.wait("receiver enabled") + + can.send(_ID, b"MORE") + while can.state() == CAN.STATE_PASSIVE: + pass + + print(state_name(can.state())) # should be ERROR_WARNING now + warning_counters = can.get_counters() + # print(warning_counters) # DEBUG + print("tec decreased", warning_counters[_IDX_TEC] < passive_counters[_IDX_TEC]) + print( + "tec below thresh", warning_counters[_IDX_TEC] < 128 + ) # and should be more than error passive threshold + # error warning count should stay the same, as we went "down" in severity not up + print( + "no new warning", warning_counters[_IDX_NUM_WARNING] == passive_counters[_IDX_NUM_WARNING] + ) + print( + "no new passive", warning_counters[_IDX_NUM_PASSIVE] == passive_counters[_IDX_NUM_PASSIVE] + ) + + # Tell the receiver to change to the wrong baud rate, which should create both RX and TX errorxs + multitest.broadcast("switch baud") + multitest.wait("switched baud") + + # queue another message. This will keep trying to send until we revert back to ERROR_PASSIVE + idx = can.send(_ID, b"YETMORE") + print("queued yetmore", idx is not None) + while can.state() != CAN.STATE_PASSIVE: + pass + + print(state_name(can.state())) # should be ERROR_PASSIVE again + passive_counters = can.get_counters() + # print(passive_counters) # DEBUG + # we can't say for sure which error counter will hit the ERROR_PASSIVE threshold first + print( + "one over thresh", passive_counters[_IDX_TEC] >= 128 or passive_counters[_IDX_REC] >= 128 + ) + print( + "no new warning", passive_counters[_IDX_NUM_WARNING] == warning_counters[_IDX_NUM_WARNING] + ) + print( + "counted passive", + passive_counters[_IDX_NUM_PASSIVE] == warning_counters[_IDX_NUM_PASSIVE] + 1, + ) + + # Note that we can't get all the way to the most severe BUS_OFF error state + # with this test setup, as Bus Off requires more than just "normal" frame + # transmit errors. + + # restarting the controller may cause it to leave its error state, or not, depending + # on the implementation - but it shouldn't cause any recovery issues. Also cancels all pending TX + # (note: have to do this before 'fix baud' or we create a race condition for pending tx) + can.restart() + + # tell the receiver to go back to a valid baud rate + multitest.broadcast("fix baud") + multitest.wait("fixed baud") + + idx_more = can.send(_ID, b"MOREMORE") + time.sleep_ms(50) # irq_sender should fire during this window + print("queued moremore", idx_more is not None) + + print("done") diff --git a/tests/multi_extmod/machine_can_07_error_states.py.exp b/tests/multi_extmod/machine_can_07_error_states.py.exp new file mode 100644 index 00000000000..158ae63bfbc --- /dev/null +++ b/tests/multi_extmod/machine_can_07_error_states.py.exp @@ -0,0 +1,37 @@ +--- instance0 --- +recv 0x100 5041594c4f4144 +can deinit() +can init() +can switch baud +sending bad msg +sending cancelling +cancelled True +re-init +done +--- instance1 --- +started STATE_ACTIVE +irq sent True +irq state 2 +irq state 3 +STATE_PASSIVE +tec increased True +tec over thresh True +counted warning True +counted passive True +some pending tx True +irq sent True +irq sent True +STATE_WARNING +tec decreased True +tec below thresh True +no new warning True +no new passive True +irq state 3 +queued yetmore True +STATE_PASSIVE +one over thresh True +no new warning True +counted passive True +irq sent True +queued moremore True +done diff --git a/tests/multi_extmod/machine_can_08_init_mode.py b/tests/multi_extmod/machine_can_08_init_mode.py new file mode 100644 index 00000000000..459e6180ab6 --- /dev/null +++ b/tests/multi_extmod/machine_can_08_init_mode.py @@ -0,0 +1,102 @@ +from machine import CAN +from micropython import const +import time + +# instance0 transitions through various modes, instance1 +# listens for various messages (or not) +# +# Note this test assumes no other CAN nodes are connected apart from the test +# instances (or if they are connected they must be in silent mode.) +# +# TODO: This test needs to eventually support the case where modes aren't supported +# on a controller, maybe by printing fake output if the mode switch fails? + +# MODE_NORMAL, MODE_SLEEP, MODE_LOOPBACK, MODE_SILENT, MODE_SILENT_LOOPBACK +can = CAN(1, 500_000, mode=CAN.MODE_NORMAL) + +# While instance0 is in Silent mode, instance1 sends a message with this ID +# that will be retried for 100ms (as instance0 won't ACK). So don't print every one. +_SILENT_RX_ID = const(0x53) +silent_rx_count = 0 + + +def irq_print(can): + global silent_rx_count + while flags := can.irq().flags(): + if flags & can.IRQ_RX: + can_id, data, _flags, errors = can.recv() + if can_id != _SILENT_RX_ID: + print("recv", hex(can_id), bytes(data)) + else: + silent_rx_count += 1 + if flags & can.IRQ_TX: # note: only enabled on instance1 to avoid race conditions + print("send", "failed" if flags & can.IRQ_TX_FAILED else "ok") + + +def reinit_with_mode(mode): + can.deinit() + can.init(bitrate=500_000, mode=mode) + can.irq(irq_print, trigger=can.IRQ_RX, hard=False) + can.set_filters(None) # receive all + + +def instance0(): + multitest.next() + multitest.wait("instance1 ready") + + reinit_with_mode(can.MODE_NORMAL) + print("Normal", "MODE_NORMAL" in str(can)) + can.send(0x50, b"Normal") + time.sleep_ms(100) + + # Skipping MODE_SLEEP as means different things on different hardware + + reinit_with_mode(can.MODE_LOOPBACK) + print("Loopback", "MODE_LOOPBACK" in str(can)) + + # This message should go out to the bus, but will also be received by instance0 itself + can.send(0x51, b"Loopback") + time.sleep_ms(100) + + reinit_with_mode(can.MODE_SILENT) + print("Silent", "MODE_SLIENT" in str(can)) + + # This message shouldn't go out onto the bus + idx = can.send(0x52, b"Silent") + multitest.broadcast("silent") + multitest.wait("silent done") + # we should have received the message from instance1 many times, as instance0 won't have ACKed it + print("silent_rx_count", silent_rx_count > 5) + can.cancel_send(idx) + + reinit_with_mode(can.MODE_SILENT_LOOPBACK) + print("Silent Loopback", "MODE_SILENT_LOOPBACK" in str(can)) + + # This message should be received by instance0 only + idx = can.send(0x54, b"SiLoop") + time.sleep_ms(50) + + reinit_with_mode(can.MODE_NORMAL) + print("Normal again", "MODE_NORMAL" in str(can)) + can.send(0x55, b"Normal2") # should be received by instance1 only, again + multitest.broadcast("normal done") + + +# Receiver +def instance1(): + can.irq(irq_print, trigger=can.IRQ_RX | can.IRQ_TX, hard=False) + can.set_filters(None) # receive all + multitest.next() + + multitest.broadcast("instance1 ready") + + # The IRQ does most of the work on this instance + + multitest.wait("silent") + # Sending this message back, it should fail to send as Silent mode won't ACK it + idx = can.send(0x53, b"Silent2") + time.sleep_ms(20) + can.cancel_send(idx) + multitest.broadcast("silent done") + + multitest.wait("normal done") diff --git a/tests/multi_extmod/machine_can_08_init_mode.py.exp b/tests/multi_extmod/machine_can_08_init_mode.py.exp new file mode 100644 index 00000000000..b9f0f11ae10 --- /dev/null +++ b/tests/multi_extmod/machine_can_08_init_mode.py.exp @@ -0,0 +1,14 @@ +--- instance0 --- +Normal True +Loopback True +recv 0x51 b'Loopback' +Silent False +silent_rx_count True +Silent Loopback True +recv 0x54 b'SiLoop' +Normal again True +--- instance1 --- +recv 0x50 b'Normal' +recv 0x51 b'Loopback' +send failed +recv 0x55 b'Normal2' diff --git a/tests/ports/stm32/pyb_can.py b/tests/ports/stm32/pyb_can.py index e8a86375668..8178d91fe74 100644 --- a/tests/ports/stm32/pyb_can.py +++ b/tests/ports/stm32/pyb_can.py @@ -188,13 +188,13 @@ else: can.setfilter(0, CAN.MASK, 0, (filter_id, filter_mask), extframe=True) - can.send("ok", id_ok, timeout=3, extframe=True) + can.send("ok", id_ok, timeout=5, extframe=True) pyb.delay(10) if can.any(0): msg = can.recv(0) print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4])) - can.send("fail", id_fail, timeout=3, extframe=True) + can.send("fail", id_fail, timeout=5, extframe=True) pyb.delay(10) if can.any(0): msg = can.recv(0) diff --git a/tests/run-tests.py b/tests/run-tests.py index 84daf4cbbf8..a5659fff8b3 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -289,6 +289,7 @@ "extmod/machine_spi_rate.py", "extmod/machine_uart_irq_txidle.py", "extmod/machine_uart_tx.py", + "extmod_hardware/machine_can_timings.py", "extmod_hardware/machine_encoder.py", "extmod_hardware/machine_uart_irq_break.py", "extmod_hardware/machine_uart_irq_rx.py", diff --git a/tests/target_wiring/PYBx.py b/tests/target_wiring/PYBx.py index a419320d902..a825dbb5141 100644 --- a/tests/target_wiring/PYBx.py +++ b/tests/target_wiring/PYBx.py @@ -8,3 +8,7 @@ uart_loopback_kwargs = {} spi_standalone_args_list = [(1,), (2,)] + +# CAN args assume no connection for single device tests +can_args = (1,) +can_kwargs = {} diff --git a/tests/target_wiring/stm32.py b/tests/target_wiring/stm32.py new file mode 100644 index 00000000000..da73d7fa9a2 --- /dev/null +++ b/tests/target_wiring/stm32.py @@ -0,0 +1,7 @@ +# Target wiring for non-PyBoard stm32 boards +# +# See PYBx.py for PyBoards + +# CAN args assume no connection for single device tests +can_args = (1,) +can_kwargs = {} From c802a13fb69f55febc199135cdb52cf099d450aa Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 19 Mar 2026 17:45:00 +1100 Subject: [PATCH 1855/2098] stm32: Fix printing value of pyb.CAN auto_restart on FDCAN hardware. The DAR register field is for auto-retransmit, FDCAN doesn't support automatic restart to clear Bus Off. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/pyb_can.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index a0b4de73bf9..2d676b23d20 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -140,7 +140,7 @@ static void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki self->can_id, mode, #if MICROPY_HW_ENABLE_FDCAN - (self->can.Instance->CCCR & FDCAN_CCCR_DAR) ? MP_QSTR_True : MP_QSTR_False + MP_QSTR_False // auto_restart not supported on FDCAN hardware #else (self->can.Instance->MCR & CAN_MCR_ABOM) ? MP_QSTR_True : MP_QSTR_False #endif From e74f3d5eaac0249e473b00e49852b5cf308fe1e4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Mar 2026 23:53:10 +1100 Subject: [PATCH 1856/2098] stm32/timer: Use HAL macro to determine if TIM is 32-bit. Some MCUs (eg N6) have more timers which are 32-bit, and it's best to use this macro to work that out. Signed-off-by: Damien George --- ports/stm32/timer.c | 6 +----- ports/stm32/timer.h | 5 +++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index a0db5963079..1a2188451ec 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -1026,15 +1026,11 @@ static mp_obj_t pyb_timer_make_new(const mp_obj_type_t *type, size_t n_args, siz memset(tim, 0, sizeof(*tim)); tim->base.type = &pyb_timer_type; tim->tim_id = tim_id; - #if defined(STM32L1) - tim->is_32bit = tim_id == 5; - #else - tim->is_32bit = tim_id == 2 || tim_id == 5; - #endif tim->callback = mp_const_none; uint32_t ti = tim_instance_table[tim_id - 1]; tim->tim.Instance = (TIM_TypeDef *)(ti & 0xffffff00); tim->irqn = ti & 0xff; + tim->is_32bit = IS_TIM_32B_COUNTER_INSTANCE(tim->tim.Instance); MP_STATE_PORT(pyb_timer_obj_all)[tim_id - 1] = tim; } else { // reference existing Timer object diff --git a/ports/stm32/timer.h b/ports/stm32/timer.h index 2ba91cf158d..1abcd1b9b42 100644 --- a/ports/stm32/timer.h +++ b/ports/stm32/timer.h @@ -26,6 +26,11 @@ #ifndef MICROPY_INCLUDED_STM32_TIMER_H #define MICROPY_INCLUDED_STM32_TIMER_H +// Define this helper macro for MCUs that the HAL misses. +#if defined(STM32L0) +#define IS_TIM_32B_COUNTER_INSTANCE(tim) (false) +#endif + extern TIM_HandleTypeDef TIM5_Handle; extern const mp_obj_type_t pyb_timer_type; From 2d3241f34c8d026dadeedc8f7a85f6361153f196 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Mar 2026 23:54:25 +1100 Subject: [PATCH 1857/2098] stm32/timer: Expose functions to convert id to reg and enable TIM clock. This functionality already exists in the TIM code, and can be reused by the upcoming PWM implementation. Signed-off-by: Damien George --- ports/stm32/timer.c | 232 +++++++++++++++++++++++--------------------- ports/stm32/timer.h | 2 + 2 files changed, 123 insertions(+), 111 deletions(-) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 1a2188451ec..c05c4716d47 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -149,6 +149,8 @@ TIM_HandleTypeDef TIM6_Handle; #define PYB_TIMER_OBJ_ALL_NUM MP_ARRAY_SIZE(MP_STATE_PORT(pyb_timer_obj_all)) +static const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER]; + static mp_obj_t pyb_timer_deinit(mp_obj_t self_in); static mp_obj_t pyb_timer_callback(mp_obj_t self_in, mp_obj_t callback); static mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback); @@ -229,6 +231,124 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { #endif } +TIM_TypeDef *timer_id_to_reg(uint32_t tim_id) { + return (TIM_TypeDef *)(tim_instance_table[tim_id - 1] & 0xffffff00); +} + +void timer_clock_enable(size_t tim_id) { + // enable TIM clock + switch (tim_id) { + #if defined(TIM1) + case 1: + __HAL_RCC_TIM1_CLK_ENABLE(); + break; + #endif + case 2: + __HAL_RCC_TIM2_CLK_ENABLE(); + break; + #if defined(TIM3) + case 3: + __HAL_RCC_TIM3_CLK_ENABLE(); + break; + #endif + #if defined(TIM4) + case 4: + __HAL_RCC_TIM4_CLK_ENABLE(); + break; + #endif + #if defined(TIM5) + case 5: + __HAL_RCC_TIM5_CLK_ENABLE(); + break; + #endif + #if defined(TIM6) + case 6: + __HAL_RCC_TIM6_CLK_ENABLE(); + break; + #endif + #if defined(TIM7) + case 7: + __HAL_RCC_TIM7_CLK_ENABLE(); + break; + #endif + #if defined(TIM8) + case 8: + __HAL_RCC_TIM8_CLK_ENABLE(); + break; + #endif + #if defined(TIM9) + case 9: + __HAL_RCC_TIM9_CLK_ENABLE(); + break; + #endif + #if defined(TIM10) + case 10: + __HAL_RCC_TIM10_CLK_ENABLE(); + break; + #endif + #if defined(TIM11) + case 11: + __HAL_RCC_TIM11_CLK_ENABLE(); + break; + #endif + #if defined(TIM12) + case 12: + __HAL_RCC_TIM12_CLK_ENABLE(); + break; + #endif + #if defined(TIM13) + case 13: + __HAL_RCC_TIM13_CLK_ENABLE(); + break; + #endif + #if defined(TIM14) + case 14: + __HAL_RCC_TIM14_CLK_ENABLE(); + break; + #endif + #if defined(TIM15) + case 15: + __HAL_RCC_TIM15_CLK_ENABLE(); + break; + #endif + #if defined(TIM16) + case 16: + __HAL_RCC_TIM16_CLK_ENABLE(); + break; + #endif + #if defined(TIM17) + case 17: + __HAL_RCC_TIM17_CLK_ENABLE(); + break; + #endif + #if defined(TIM18) + case 18: + __HAL_RCC_TIM18_CLK_ENABLE(); + break; + #endif + #if defined(TIM19) + case 19: + __HAL_RCC_TIM19_CLK_ENABLE(); + break; + #endif + #if defined(TIM20) + case 20: + __HAL_RCC_TIM20_CLK_ENABLE(); + break; + #endif + #if defined(TIM21) + case 21: + __HAL_RCC_TIM21_CLK_ENABLE(); + break; + #endif + #if defined(TIM22) + case 22: + __HAL_RCC_TIM22_CLK_ENABLE(); + break; + #endif + } +} + // Get the frequency (in Hz) of the source clock for the given timer. // On STM32F405/407/415/417 there are 2 cases for how the clock freq is set. // If the APB prescaler is 1, then the timer clock is equal to its respective @@ -694,117 +814,7 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons init->RepetitionCounter = 0; #endif - // enable TIM clock - switch (self->tim_id) { - #if defined(TIM1) - case 1: - __HAL_RCC_TIM1_CLK_ENABLE(); - break; - #endif - case 2: - __HAL_RCC_TIM2_CLK_ENABLE(); - break; - #if defined(TIM3) - case 3: - __HAL_RCC_TIM3_CLK_ENABLE(); - break; - #endif - #if defined(TIM4) - case 4: - __HAL_RCC_TIM4_CLK_ENABLE(); - break; - #endif - #if defined(TIM5) - case 5: - __HAL_RCC_TIM5_CLK_ENABLE(); - break; - #endif - #if defined(TIM6) - case 6: - __HAL_RCC_TIM6_CLK_ENABLE(); - break; - #endif - #if defined(TIM7) - case 7: - __HAL_RCC_TIM7_CLK_ENABLE(); - break; - #endif - #if defined(TIM8) - case 8: - __HAL_RCC_TIM8_CLK_ENABLE(); - break; - #endif - #if defined(TIM9) - case 9: - __HAL_RCC_TIM9_CLK_ENABLE(); - break; - #endif - #if defined(TIM10) - case 10: - __HAL_RCC_TIM10_CLK_ENABLE(); - break; - #endif - #if defined(TIM11) - case 11: - __HAL_RCC_TIM11_CLK_ENABLE(); - break; - #endif - #if defined(TIM12) - case 12: - __HAL_RCC_TIM12_CLK_ENABLE(); - break; - #endif - #if defined(TIM13) - case 13: - __HAL_RCC_TIM13_CLK_ENABLE(); - break; - #endif - #if defined(TIM14) - case 14: - __HAL_RCC_TIM14_CLK_ENABLE(); - break; - #endif - #if defined(TIM15) - case 15: - __HAL_RCC_TIM15_CLK_ENABLE(); - break; - #endif - #if defined(TIM16) - case 16: - __HAL_RCC_TIM16_CLK_ENABLE(); - break; - #endif - #if defined(TIM17) - case 17: - __HAL_RCC_TIM17_CLK_ENABLE(); - break; - #endif - #if defined(TIM18) - case 18: - __HAL_RCC_TIM18_CLK_ENABLE(); - break; - #endif - #if defined(TIM19) - case 19: - __HAL_RCC_TIM19_CLK_ENABLE(); - break; - #endif - #if defined(TIM20) - case 20: - __HAL_RCC_TIM20_CLK_ENABLE(); - break; - #endif - #if defined(TIM21) - case 21: - __HAL_RCC_TIM21_CLK_ENABLE(); - break; - #endif - #if defined(TIM22) - case 22: - __HAL_RCC_TIM22_CLK_ENABLE(); - break; - #endif - } + timer_clock_enable(self->tim_id); // set IRQ priority (if not a special timer) if (self->tim_id != 5) { diff --git a/ports/stm32/timer.h b/ports/stm32/timer.h index 1abcd1b9b42..10aa81bd52c 100644 --- a/ports/stm32/timer.h +++ b/ports/stm32/timer.h @@ -39,6 +39,8 @@ void timer_init0(void); void timer_tim5_init(void); TIM_HandleTypeDef *timer_tim6_init(uint freq); void timer_deinit(void); +TIM_TypeDef *timer_id_to_reg(uint32_t tim_id); +void timer_clock_enable(size_t tim_id); uint32_t timer_get_source_freq(uint32_t tim_id); void timer_irq_handler(uint tim_id); From dbe6a11670c02bc0f08e6f7c53bed59596b12345 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Feb 2026 11:32:41 +1100 Subject: [PATCH 1858/2098] stm32/machine_pwm: Implement machine.PWM class. This commit implements the standard `machine.PWM` class on stm32, using the common bindings in `extmod/machine_pwm.c`. Features implemented are: - construct a PWM object from a pin, with automatic selection of TIM instance and channel; - get and set freq, duty_u16 and duty_ns; - optionally invert the output. The PWM objects are static objects (partly in ROM, partly in RAM) so creating a PWM instance on the same pin will return exactly the same object. That's consistent with other peripherals in the stm32 port, and consistent with other PWM implementations (eg rp2). When creating a PWM object on a pin, if that pin has multiple TIM instances then only the first will be selected. A future extension could allow selecting the TIM/channel (eg similar to how ADCBlock allows selecting an ADC). Signed-off-by: Damien George --- ports/stm32/machine_pwm.c | 658 +++++++++++++++++++++++++++++++++++++ ports/stm32/main.c | 3 + ports/stm32/modmachine.h | 1 + ports/stm32/mpconfigport.h | 2 + 4 files changed, 664 insertions(+) create mode 100644 ports/stm32/machine_pwm.c diff --git a/ports/stm32/machine_pwm.c b/ports/stm32/machine_pwm.c new file mode 100644 index 00000000000..611bb80f46f --- /dev/null +++ b/ports/stm32/machine_pwm.c @@ -0,0 +1,658 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 OpenMV LLC. + * Copyright (c) 2026 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/machine_pwm.c via MICROPY_PY_MACHINE_PWM_INCLUDEFILE. + +#include "py/mphal.h" +#include "timer.h" + +// tim is a TIMx instance, ch is one of PWM_CHx +#define CCRx(tim, ch) ((&(tim)->CCR1)[(ch)]) + +enum { + #if defined(TIM1) + TIM1_ENABLED, + #endif + #if defined(TIM2) + TIM2_ENABLED, + #endif + #if defined(TIM3) + TIM3_ENABLED, + #endif + #if defined(TIM4) + TIM4_ENABLED, + #endif + #if defined(TIM5) + TIM5_ENABLED, + #endif + // TIM6, TIM7 have no output channels so aren't used for PWM. + #if defined(TIM8) + TIM8_ENABLED, + #endif + #if defined(TIM9) + TIM9_ENABLED, + #endif + #if defined(TIM10) + TIM10_ENABLED, + #endif + #if defined(TIM11) + TIM11_ENABLED, + #endif + #if defined(TIM12) + TIM12_ENABLED, + #endif + #if defined(TIM13) + TIM13_ENABLED, + #endif + #if defined(TIM14) + TIM14_ENABLED, + #endif + #if defined(TIM15) + TIM15_ENABLED, + #endif + #if defined(TIM16) + TIM16_ENABLED, + #endif + #if defined(TIM17) + TIM17_ENABLED, + #endif + #if defined(TIM18) + TIM18_ENABLED, + #endif + #if defined(TIM19) + TIM19_ENABLED, + #endif + #if defined(TIM20) + TIM20_ENABLED, + #endif + #if defined(TIM21) + TIM21_ENABLED, + #endif + #if defined(TIM22) + TIM22_ENABLED, + #endif + NUM_TIMERS, +}; + +enum { + PWM_CH1 = 0, + PWM_CH2 = 1, + PWM_CH3 = 2, + PWM_CH4 = 3, + NUM_CHANNELS_PER_TIMER, +}; + +enum { + PWM_POLARITY_NORMAL, + PWM_POLARITY_INVERTED, +}; + +typedef struct _pwm_t { + uint8_t tim_id; // TIMx id + uint8_t channel; // one of PWM_CH1-PWM_CH4 +} pwm_t; + +static void pwm_init(pwm_t *pwm) { + // The following code assumes each channel has 8 bits in CCMR1/2. + MP_STATIC_ASSERT(TIM_CCMR1_CC1S_Pos + 8 == TIM_CCMR1_CC2S_Pos); + MP_STATIC_ASSERT(TIM_CCMR2_CC3S_Pos + 8 == TIM_CCMR2_CC4S_Pos); + + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + + // Initialise TIM if it's not already running. + if (!(tim->CR1 & TIM_CR1_CEN)) { + timer_clock_enable(pwm->tim_id); + + // Initialise with: clock division 1, up counter mode, auto-reload buffered. + // ARPE allows to smoothly change the frequency of the timer, and prevents + // long silent periods when a 32-bit timer is used and CNT goes beyond ARR. + tim->CR1 = TIM_CR1_ARPE; + + // Invalidate the frequency so `pwm_freq_is_valid()` works. + tim->ARR = 0; + + #if defined(IS_TIM_REPETITION_COUNTER_INSTANCE) + if (IS_TIM_REPETITION_COUNTER_INSTANCE(tim)) { + tim->RCR = 0; + } + #endif + } + + // Configure PWM mode. + uint32_t reg = 6 << TIM_CCMR1_OC1M_Pos // PWM1 mode + | 1 << TIM_CCMR1_OC1PE_Pos // preload enabled + | 0 << TIM_CCMR1_CC1S_Pos // output mode + ; + uint32_t shift = 8 * (pwm->channel & 1); + if (pwm->channel == PWM_CH1 || pwm->channel == PWM_CH2) { + tim->CCMR1 = (tim->CCMR1 & ~(0xff << shift)) | reg << shift; + } else { + tim->CCMR2 = (tim->CCMR2 & ~(0xff << shift)) | reg << shift; + } + + #if defined(IS_TIM_BREAK_INSTANCE) + // Enable master output if needed. + if (IS_TIM_BREAK_INSTANCE(tim)) { + tim->BDTR |= TIM_BDTR_MOE; + } + #endif +} + +static void pwm_set_polarity(pwm_t *pwm, unsigned int polarity) { + // The following code assumes each channel has 4 bits in CCER. + MP_STATIC_ASSERT(TIM_CCER_CC1P_Pos + 4 == TIM_CCER_CC2P_Pos); + + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + unsigned int shift = 4 * pwm->channel; + if (polarity == PWM_POLARITY_NORMAL) { + tim->CCER &= ~(TIM_CCER_CC1P << shift); + } else { + tim->CCER |= TIM_CCER_CC1P << shift; + } +} + +static void pwm_deinit(pwm_t *pwm) { + // The following code assumes each channel has 4 bits in CCER. + MP_STATIC_ASSERT(TIM_CCER_CC1E_Pos + 4 == TIM_CCER_CC2E_Pos); + + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + + // Disable the normal output. + tim->CCER &= ~(TIM_CCER_CC1E << (4 * pwm->channel)); + + // Disable the TIM peripheral if all channels are disabled. + uint32_t ccer_out_mask = TIM_CCER_CCxE_MASK; + #if defined(TIM_CCER_CCxNE_MASK) + ccer_out_mask |= TIM_CCER_CCxNE_MASK; + #endif + if ((tim->CCER & ccer_out_mask) == 0) { + #if defined(IS_TIM_BREAK_INSTANCE) + if (IS_TIM_BREAK_INSTANCE(tim)) { + tim->BDTR &= ~TIM_BDTR_MOE; + } + #endif + tim->CR1 &= ~TIM_CR1_CEN; + } +} + +static bool pwm_freq_is_valid(pwm_t *pwm) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + return tim->ARR != 0; +} + +// The ARR needs to be configured (eg `pwm_set_freq()`) before this function is called. +static void pwm_start(pwm_t *pwm) { + // The following code assumes each channel has 4 bits in CCER. + MP_STATIC_ASSERT(TIM_CCER_CC1E_Pos + 4 == TIM_CCER_CC2E_Pos); + + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + if (!(tim->CR1 & TIM_CR1_CEN)) { + // Reinitialise the counter and update the registers. + tim->EGR = TIM_EGR_UG; + + // Enable the timer if it's not already running. + tim->CR1 |= TIM_CR1_CEN; + } + + // Enable output on pin. + unsigned int shift = 4 * pwm->channel; + tim->CCER |= TIM_CCER_CC1E << shift; +} + +static uint32_t pwm_get_freq(pwm_t *pwm) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + uint32_t prescaler = tim->PSC; + uint32_t period = tim->ARR; + uint32_t freq = timer_get_source_freq(pwm->tim_id) / ((prescaler + 1) * (period + 1)); + return freq; +} + +// Input freq_hz must be > 0. +// Returns false if freq_hz is too large. +static bool pwm_set_freq(pwm_t *pwm, uint32_t freq_hz) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + + uint32_t source_freq = timer_get_source_freq(pwm->tim_id); + + // Find optimal prescaler and period values for the given frequency: + // - For 32-bit TIM, the prescaler is always 1 to get maximum precision. + // - For 16-bit TIM, find the smallest prescaler with a period that fits 16-bits. + uint32_t prescaler = 1; + uint32_t period = (source_freq + freq_hz / 2) / freq_hz; + if (!IS_TIM_32B_COUNTER_INSTANCE(tim)) { + while (period > 0xffff) { + // If we can divide exactly, do that first. + if (period % 5 == 0) { + prescaler *= 5; + period /= 5; + } else if (period % 3 == 0) { + prescaler *= 3; + period /= 3; + } else { + // May not divide exactly, but loses minimal precision. + prescaler <<= 1; + period >>= 1; + } + } + } + + if (period <= 1) { + // Requested frequency too high. + return false; + } + + // Set the prescaler and period registers. + tim->PSC = prescaler - 1; + tim->ARR = period - 1; + + return true; +} + +static uint16_t pwm_get_duty_u16(pwm_t *pwm) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + uint32_t top = tim->ARR + 1; + uint32_t cc = CCRx(tim, pwm->channel); + return (cc * 65535ULL + top / 2U) / top; +} + +static void pwm_set_duty_u16(pwm_t *pwm, uint32_t duty_u16) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + uint32_t top = tim->ARR + 1; + uint32_t cc = ((uint64_t)duty_u16 * top + 65535ULL / 2) / 65535U; + if (cc > top) { + cc = top; + } + CCRx(tim, pwm->channel) = cc; +} + +static uint32_t pwm_get_duty_ns(pwm_t *pwm) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + uint32_t source_freq = timer_get_source_freq(pwm->tim_id) / (tim->PSC + 1); + uint32_t cc = CCRx(tim, pwm->channel); + return ((uint64_t)cc * 1000000000ULL + source_freq / 2U) / source_freq; +} + +static void pwm_set_duty_ns(pwm_t *pwm, uint32_t duty_ns) { + TIM_TypeDef *tim = timer_id_to_reg(pwm->tim_id); + uint32_t source_freq = timer_get_source_freq(pwm->tim_id) / (tim->PSC + 1); + uint32_t top = tim->ARR + 1; + uint32_t cc = ((uint64_t)duty_ns * source_freq + 1000000000ULL / 2U) / 1000000000ULL; + if (cc > top) { + cc = top; + } + CCRx(tim, pwm->channel) = cc; +} + +/******************************************************************************/ +// Pin helper + +typedef struct _pwm_pin_config_t { + uint8_t timer_id; + uint8_t timer_channel; + uint8_t alt; +} pwm_pin_config_t; + +static bool pin_find_af_for_pwm(mp_hal_pin_obj_t pin, pwm_pin_config_t *cfg) { + for (size_t i = 0; i < pin->num_af; ++i) { + if (pin->af[i].fn == AF_FN_TIM && pin->af[i].type >= AF_PIN_TYPE_TIM_CH1 && pin->af[i].type <= AF_PIN_TYPE_TIM_CH4) { + cfg->timer_id = pin->af[i].unit; + cfg->timer_channel = pin->af[i].type - AF_PIN_TYPE_TIM_CH1; + cfg->alt = pin->af[i].idx; + return true; + } + } + return false; +} + +/******************************************************************************/ +// MicroPython bindings for machine.PWM + +enum { + DUTY_NOT_SET = 0, + DUTY_U16, + DUTY_NS +}; + +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + pwm_t pwm; +} machine_pwm_obj_t; + +typedef struct _machine_pwm_state_t { + uint8_t duty_type; + mp_int_t duty; +} machine_pwm_state_t; + +static uint32_t timer_pwm_active = 0; + +static const machine_pwm_obj_t machine_pwm_obj[NUM_TIMERS * NUM_CHANNELS_PER_TIMER] = { + #if defined(TIM1) + {{&machine_pwm_type}, {1, PWM_CH1}}, + {{&machine_pwm_type}, {1, PWM_CH2}}, + {{&machine_pwm_type}, {1, PWM_CH3}}, + {{&machine_pwm_type}, {1, PWM_CH4}}, + #endif + #if defined(TIM2) + {{&machine_pwm_type}, {2, PWM_CH1}}, + {{&machine_pwm_type}, {2, PWM_CH2}}, + {{&machine_pwm_type}, {2, PWM_CH3}}, + {{&machine_pwm_type}, {2, PWM_CH4}}, + #endif + #if defined(TIM3) + {{&machine_pwm_type}, {3, PWM_CH1}}, + {{&machine_pwm_type}, {3, PWM_CH2}}, + {{&machine_pwm_type}, {3, PWM_CH3}}, + {{&machine_pwm_type}, {3, PWM_CH4}}, + #endif + #if defined(TIM4) + {{&machine_pwm_type}, {4, PWM_CH1}}, + {{&machine_pwm_type}, {4, PWM_CH2}}, + {{&machine_pwm_type}, {4, PWM_CH3}}, + {{&machine_pwm_type}, {4, PWM_CH4}}, + #endif + #if defined(TIM5) + {{&machine_pwm_type}, {5, PWM_CH1}}, + {{&machine_pwm_type}, {5, PWM_CH2}}, + {{&machine_pwm_type}, {5, PWM_CH3}}, + {{&machine_pwm_type}, {5, PWM_CH4}}, + #endif + // TIM6, TIM7 have no output channels so aren't used for PWM. + #if defined(TIM8) + {{&machine_pwm_type}, {8, PWM_CH1}}, + {{&machine_pwm_type}, {8, PWM_CH2}}, + {{&machine_pwm_type}, {8, PWM_CH3}}, + {{&machine_pwm_type}, {8, PWM_CH4}}, + #endif + #if defined(TIM9) + {{&machine_pwm_type}, {9, PWM_CH1}}, + {{&machine_pwm_type}, {9, PWM_CH2}}, + {{&machine_pwm_type}, {9, PWM_CH3}}, + {{&machine_pwm_type}, {9, PWM_CH4}}, + #endif + #if defined(TIM10) + {{&machine_pwm_type}, {10, PWM_CH1}}, + {{&machine_pwm_type}, {10, PWM_CH2}}, + {{&machine_pwm_type}, {10, PWM_CH3}}, + {{&machine_pwm_type}, {10, PWM_CH4}}, + #endif + #if defined(TIM11) + {{&machine_pwm_type}, {11, PWM_CH1}}, + {{&machine_pwm_type}, {11, PWM_CH2}}, + {{&machine_pwm_type}, {11, PWM_CH3}}, + {{&machine_pwm_type}, {11, PWM_CH4}}, + #endif + #if defined(TIM12) + {{&machine_pwm_type}, {12, PWM_CH1}}, + {{&machine_pwm_type}, {12, PWM_CH2}}, + {{&machine_pwm_type}, {12, PWM_CH3}}, + {{&machine_pwm_type}, {12, PWM_CH4}}, + #endif + #if defined(TIM13) + {{&machine_pwm_type}, {13, PWM_CH1}}, + {{&machine_pwm_type}, {13, PWM_CH2}}, + {{&machine_pwm_type}, {13, PWM_CH3}}, + {{&machine_pwm_type}, {13, PWM_CH4}}, + #endif + #if defined(TIM14) + {{&machine_pwm_type}, {14, PWM_CH1}}, + {{&machine_pwm_type}, {14, PWM_CH2}}, + {{&machine_pwm_type}, {14, PWM_CH3}}, + {{&machine_pwm_type}, {14, PWM_CH4}}, + #endif + #if defined(TIM15) + {{&machine_pwm_type}, {15, PWM_CH1}}, + {{&machine_pwm_type}, {15, PWM_CH2}}, + {{&machine_pwm_type}, {15, PWM_CH3}}, + {{&machine_pwm_type}, {15, PWM_CH4}}, + #endif + #if defined(TIM16) + {{&machine_pwm_type}, {16, PWM_CH1}}, + {{&machine_pwm_type}, {16, PWM_CH2}}, + {{&machine_pwm_type}, {16, PWM_CH3}}, + {{&machine_pwm_type}, {16, PWM_CH4}}, + #endif + #if defined(TIM17) + {{&machine_pwm_type}, {17, PWM_CH1}}, + {{&machine_pwm_type}, {17, PWM_CH2}}, + {{&machine_pwm_type}, {17, PWM_CH3}}, + {{&machine_pwm_type}, {17, PWM_CH4}}, + #endif + #if defined(TIM18) + {{&machine_pwm_type}, {18, PWM_CH1}}, + {{&machine_pwm_type}, {18, PWM_CH2}}, + {{&machine_pwm_type}, {18, PWM_CH3}}, + {{&machine_pwm_type}, {18, PWM_CH4}}, + #endif + #if defined(TIM19) + {{&machine_pwm_type}, {19, PWM_CH1}}, + {{&machine_pwm_type}, {19, PWM_CH2}}, + {{&machine_pwm_type}, {19, PWM_CH3}}, + {{&machine_pwm_type}, {19, PWM_CH4}}, + #endif + #if defined(TIM20) + {{&machine_pwm_type}, {20, PWM_CH1}}, + {{&machine_pwm_type}, {20, PWM_CH2}}, + {{&machine_pwm_type}, {20, PWM_CH3}}, + {{&machine_pwm_type}, {20, PWM_CH4}}, + #endif + #if defined(TIM21) + {{&machine_pwm_type}, {21, PWM_CH1}}, + {{&machine_pwm_type}, {21, PWM_CH2}}, + {{&machine_pwm_type}, {21, PWM_CH3}}, + {{&machine_pwm_type}, {21, PWM_CH4}}, + #endif + #if defined(TIM22) + {{&machine_pwm_type}, {22, PWM_CH1}}, + {{&machine_pwm_type}, {22, PWM_CH2}}, + {{&machine_pwm_type}, {22, PWM_CH3}}, + {{&machine_pwm_type}, {22, PWM_CH4}}, + #endif +}; + +static machine_pwm_state_t machine_pwm_state[NUM_TIMERS * NUM_CHANNELS_PER_TIMER]; + +static inline machine_pwm_state_t *get_state(machine_pwm_obj_t *pwm) { + size_t pwm_index = pwm - &machine_pwm_obj[0]; + return &machine_pwm_state[pwm_index]; +} + +static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->pwm.tim_id, self->pwm.channel + 1); +} + +static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, + size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_freq, ARG_duty_u16, ARG_duty_ns, ARG_invert }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + // Parse the arguments. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_invert].u_int != -1) { + pwm_set_polarity(&self->pwm, args[ARG_invert].u_int ? PWM_POLARITY_INVERTED : PWM_POLARITY_NORMAL); + } + if (args[ARG_freq].u_int != -1) { + mp_machine_pwm_freq_set(self, args[ARG_freq].u_int); + } + if (args[ARG_duty_u16].u_int != -1) { + mp_machine_pwm_duty_set_u16(self, args[ARG_duty_u16].u_int); + } + if (args[ARG_duty_ns].u_int != -1) { + mp_machine_pwm_duty_set_ns(self, args[ARG_duty_ns].u_int); + } + if (pwm_freq_is_valid(&self->pwm)) { + pwm_start(&self->pwm); + } +} + +// PWM(pin [, args]) +static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Get pin to connect to PWM. + mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(all_args[0]); + + // Search the given pin's alternate functions for a TIMx_CHy function. + machine_pwm_obj_t *self = NULL; + pwm_pin_config_t cfg; + if (pin_find_af_for_pwm(pin, &cfg)) { + for (size_t i = 0; i < MP_ARRAY_SIZE(machine_pwm_obj); ++i) { + if (machine_pwm_obj[i].pwm.tim_id == cfg.timer_id && machine_pwm_obj[i].pwm.channel == cfg.timer_channel) { + self = (machine_pwm_obj_t *)&machine_pwm_obj[i]; + break; + } + } + } + + if (self == NULL) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Pin(%q) doesn't have PWM capabilities"), pin->name); + } + + // If inactive, clear out the state (may be set from a previous soft reset cycle). + if (!(timer_pwm_active & (1U << self->pwm.tim_id))) { + timer_pwm_active |= 1U << self->pwm.tim_id; + size_t pwm_base_index = (self - &machine_pwm_obj[0]) & ~(NUM_CHANNELS_PER_TIMER - 1); + for (size_t i = 0; i < 4; ++i) { + machine_pwm_state_t *state = &machine_pwm_state[pwm_base_index + i]; + state->duty_type = DUTY_NOT_SET; + } + } + + // Initialise the TIM and the output driver. + pwm_init(&self->pwm); + if (n_args > 1 || n_kw > 0) { + // Arguments given, so reset the state. + machine_pwm_state_t *state = get_state(self); + state->duty_type = DUTY_NOT_SET; + pwm_set_polarity(&self->pwm, PWM_POLARITY_NORMAL); + } + + // Process the remaining parameters. + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, all_args + n_args); + mp_machine_pwm_init_helper(self, n_args - 1, all_args + 1, &kw_args); + + // Select PWM function for given pin. + mp_hal_pin_config(pin, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, cfg.alt); + + return MP_OBJ_FROM_PTR(self); +} + +void machine_pwm_deinit_all(void) { + for (size_t i = 0; i < MP_ARRAY_SIZE(machine_pwm_obj); ++i) { + machine_pwm_state_t *state = &machine_pwm_state[i]; + if (state->duty_type != DUTY_NOT_SET) { + state->duty_type = DUTY_NOT_SET; + mp_machine_pwm_deinit((machine_pwm_obj_t *)&machine_pwm_obj[i]); + } + } + timer_pwm_active = 0; +} + +static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + pwm_deinit(&self->pwm); +} + +static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + if (pwm_freq_is_valid(&self->pwm)) { + return MP_OBJ_NEW_SMALL_INT(pwm_get_freq(&self->pwm)); + } else { + return MP_OBJ_NEW_SMALL_INT(0); + } +} + +static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + // Check validity and change the frequency of the TIM peripheral. + if (freq <= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("freq too small")); + } + if (!pwm_set_freq(&self->pwm, freq)) { + mp_raise_ValueError(MP_ERROR_TEXT("freq too large")); + } + + // Update the duty cycle of all active channels that use this TIM. + size_t pwm_base_index = (self - &machine_pwm_obj[0]) & ~(NUM_CHANNELS_PER_TIMER - 1); + for (size_t i = 0; i < NUM_CHANNELS_PER_TIMER; ++i) { + machine_pwm_obj_t *obj = (machine_pwm_obj_t *)&machine_pwm_obj[pwm_base_index + i]; + machine_pwm_state_t *state = &machine_pwm_state[pwm_base_index + i]; + if (state->duty_type == DUTY_U16) { + mp_machine_pwm_duty_set_u16(obj, state->duty); + } else if (state->duty_type == DUTY_NS) { + mp_machine_pwm_duty_set_ns(obj, state->duty); + } + } +} + +static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { + machine_pwm_state_t *state = get_state(self); + if (state->duty_type != DUTY_NOT_SET && pwm_freq_is_valid(&self->pwm)) { + uint32_t duty_u16 = pwm_get_duty_u16(&self->pwm); + return MP_OBJ_NEW_SMALL_INT(duty_u16); + } else { + return MP_OBJ_NEW_SMALL_INT(0); + } +} + +static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { + machine_pwm_state_t *state = get_state(self); + state->duty = duty_u16; + state->duty_type = DUTY_U16; + + if (pwm_freq_is_valid(&self->pwm)) { + pwm_set_duty_u16(&self->pwm, duty_u16); + pwm_start(&self->pwm); + } +} + +static mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { + machine_pwm_state_t *state = get_state(self); + if (state->duty_type != DUTY_NOT_SET && pwm_freq_is_valid(&self->pwm)) { + return mp_obj_new_int_from_uint(pwm_get_duty_ns(&self->pwm)); + } else { + return MP_OBJ_NEW_SMALL_INT(0); + } +} + +static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { + machine_pwm_state_t *state = get_state(self); + state->duty = duty_ns; + state->duty_type = DUTY_NS; + + if (pwm_freq_is_valid(&self->pwm)) { + pwm_set_duty_ns(&self->pwm, duty_ns); + pwm_start(&self->pwm); + } +} diff --git a/ports/stm32/main.c b/ports/stm32/main.c index ccf3b6d4060..17111c6df98 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -771,6 +771,9 @@ void stm32_main(uint32_t reset_mode) { #if MICROPY_HW_ENABLE_DAC dac_deinit_all(); #endif + #if MICROPY_PY_MACHINE_PWM + machine_pwm_deinit_all(); + #endif #if MICROPY_PY_MACHINE machine_deinit(); #endif diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index 899a29be8eb..7e5fa42861e 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -31,6 +31,7 @@ void machine_init(void); void machine_deinit(void); void machine_i2s_init0(); +void machine_pwm_deinit_all(void); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_info_obj); diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index e7cf28accee..bc9e94902ff 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -143,6 +143,8 @@ #define MICROPY_PY_MACHINE_I2S_CONSTANT_RX (I2S_MODE_MASTER_RX) #define MICROPY_PY_MACHINE_I2S_CONSTANT_TX (I2S_MODE_MASTER_TX) #define MICROPY_PY_MACHINE_I2S_RING_BUF (1) +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/stm32/machine_pwm.c" #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_FIRSTBIT_MSB) #define MICROPY_PY_MACHINE_SPI_LSB (SPI_FIRSTBIT_LSB) From 094c268ff1fd7057a83f408d890ee5d7252d9e23 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Mar 2026 15:06:12 +1100 Subject: [PATCH 1859/2098] stm32/machine_pwm: Use heuristic to assign TIMx_CHy to a pin. When assigning a TIMx_CHy to a pin, the second available alternate function is chosen (or the first if there is only one). This gives better overall static allocation of TIM's to pins. On most MCUs (eg F4, F7, H5, H7) picking the second gives TIM5_CH[1-4] for PA0-PA3, and TIM5 is a 32-bit timer. That leaves TIM2 (also usually on PA0-PA3) for other pins that only have TIM2. For STM32G0, STM32L432 and STM32L452 the heuristic is to simply use the first available alternate function because that gives TIM2 (a 32-bit timer) on PA0-PA3. The above heuristic guarantees that PA0-PA3 always get a 32-bit timer on all supported MCUs. Signed-off-by: Damien George --- ports/stm32/machine_pwm.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/ports/stm32/machine_pwm.c b/ports/stm32/machine_pwm.c index 611bb80f46f..2560e0caf1d 100644 --- a/ports/stm32/machine_pwm.c +++ b/ports/stm32/machine_pwm.c @@ -311,6 +311,18 @@ static void pwm_set_duty_ns(pwm_t *pwm, uint32_t duty_ns) { /******************************************************************************/ // Pin helper +// The heuristic to search for a TIMx_CHy alt configuration depends on the MCU. +// Some use "first available" and others use "second available". The scheme is +// chosen to give the best overall assignment of TIMx_CHy to pins which is: +// - deterministic (doesn't depend on order of allocation); +// - maximises the number of unique assignments; +// - guarantees that PA0-PA3 are on a 32-bit timer. +#if defined(STM32G0) || defined(STM32L432xx) || defined(STM32L452xx) +#define TIM_SEARCH_FOR_SECOND (0) +#else +#define TIM_SEARCH_FOR_SECOND (1) +#endif + typedef struct _pwm_pin_config_t { uint8_t timer_id; uint8_t timer_channel; @@ -318,14 +330,29 @@ typedef struct _pwm_pin_config_t { } pwm_pin_config_t; static bool pin_find_af_for_pwm(mp_hal_pin_obj_t pin, pwm_pin_config_t *cfg) { + const pin_af_obj_t *pin_af = NULL; for (size_t i = 0; i < pin->num_af; ++i) { if (pin->af[i].fn == AF_FN_TIM && pin->af[i].type >= AF_PIN_TYPE_TIM_CH1 && pin->af[i].type <= AF_PIN_TYPE_TIM_CH4) { - cfg->timer_id = pin->af[i].unit; - cfg->timer_channel = pin->af[i].type - AF_PIN_TYPE_TIM_CH1; - cfg->alt = pin->af[i].idx; - return true; + #if TIM_SEARCH_FOR_SECOND + // Get the second TIMx_CHy configuration (or first if there's only one). + if (pin_af != NULL) { + pin_af = &pin->af[i]; + break; + } + pin_af = &pin->af[i]; + #else + // Get the first TIMx_CHy configuration. + pin_af = &pin->af[i]; + break; + #endif } } + if (pin_af != NULL) { + cfg->timer_id = pin_af->unit; + cfg->timer_channel = pin_af->type - AF_PIN_TYPE_TIM_CH1; + cfg->alt = pin_af->idx; + return true; + } return false; } From 6e9d35b6889c62b54a192a2c952c862d7c579e2c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Mar 2026 23:56:10 +1100 Subject: [PATCH 1860/2098] tests/extmod_hardware/machine_pwm.py: Round expected timing calculation. To be slightly more accurate computing the expected low/high times for the PWM output. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index a756493f0e5..89a4984a947 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -68,8 +68,8 @@ def _test_freq_duty(self, pulse_in, pwm, freq, duty_u16): self.assertLessEqual(duty_error, duty_margin_per_thousand) # Calculate expected timing. - expected_total_us = 1_000_000 // freq - expected_high_us = expected_total_us * duty_u16 // 65535 + expected_total_us = (1_000_000 + freq // 2) // freq + expected_high_us = (expected_total_us * duty_u16 + 65535 // 2) // 65535 expected_low_us = expected_total_us - expected_high_us expected_us = (expected_low_us, expected_high_us) timeout = 2 * expected_total_us From 142f8b96bf53a99b707e13745db8b266d6b9a645 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 2 Mar 2026 23:57:51 +1100 Subject: [PATCH 1861/2098] tests/extmod_hardware/machine_pwm.py: Add pin settings for stm32 port. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index 89a4984a947..f78825795e2 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -42,6 +42,8 @@ ) else: pwm_pulse_pins = (("D0", "D1"),) +elif "pyboard" in sys.platform: + pwm_pulse_pins = ((Pin.cpu.A0, Pin.cpu.A1),) elif "rp2" in sys.platform: pwm_pulse_pins = (("GPIO0", "GPIO1"),) elif "samd" in sys.platform: From 1d5073f609b7640a3764b383a167c4098b13f894 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 3 Mar 2026 01:18:02 +1100 Subject: [PATCH 1862/2098] extmod/machine_pwm: Fix use of object when pointer is needed. Signed-off-by: Damien George --- extmod/machine_pwm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/machine_pwm.c b/extmod/machine_pwm.c index 0c1834886c4..eb68bd9671c 100644 --- a/extmod/machine_pwm.c +++ b/extmod/machine_pwm.c @@ -50,7 +50,7 @@ static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns #include MICROPY_PY_MACHINE_PWM_INCLUDEFILE static mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - mp_machine_pwm_init_helper(args[0], n_args - 1, args + 1, kw_args); + mp_machine_pwm_init_helper(MP_OBJ_TO_PTR(args[0]), n_args - 1, args + 1, kw_args); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_init_obj, 1, machine_pwm_init); From af31472e3d86e124f8d2af86ffbfaa65736c3786 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 4 Mar 2026 17:03:29 +1100 Subject: [PATCH 1863/2098] stm32/boards: Disable some features on boards with small flash. This allows the newly-added `machine.PWM` class to fit on these boards, which is arguably more useful than the features disabled in this commit. Signed-off-by: Damien George --- ports/stm32/boards/ESPRUINO_PICO/mpconfigboard.h | 1 + ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/stm32/boards/ESPRUINO_PICO/mpconfigboard.h b/ports/stm32/boards/ESPRUINO_PICO/mpconfigboard.h index cfc46491e07..e38f8e6f61d 100644 --- a/ports/stm32/boards/ESPRUINO_PICO/mpconfigboard.h +++ b/ports/stm32/boards/ESPRUINO_PICO/mpconfigboard.h @@ -3,6 +3,7 @@ #define MICROPY_EMIT_THUMB (0) #define MICROPY_EMIT_INLINE_THUMB (0) +#define MICROPY_OPT_COMPUTED_GOTO (0) #define MICROPY_PY_BUILTINS_COMPLEX (0) #define MICROPY_PY_SOCKET (0) #define MICROPY_PY_NETWORK (0) diff --git a/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h b/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h index 0daf4877ff6..f38601c5a7d 100644 --- a/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h @@ -12,6 +12,7 @@ #define MICROPY_PY_STM (0) #define MICROPY_PY_PYB_LEGACY (0) #define MICROPY_PY_HEAPQ (0) +#define MICROPY_PY_FRAMEBUF (0) #define MICROPY_HW_ENABLE_RTC (1) #define MICROPY_HW_ENABLE_ADC (1) From 2b64d6d023de0bc44c97ea7b837ed93cc3f35c48 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 6 Mar 2026 11:40:49 +1100 Subject: [PATCH 1864/2098] stm32/boards/stm32f091_af.csv: Split TIM2_CH1 from TIM2_ETR. So that TIM2_CH1 can be used. Signed-off-by: Damien George --- ports/stm32/boards/stm32f091_af.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/stm32f091_af.csv b/ports/stm32/boards/stm32f091_af.csv index 5e44697776b..b8fba765e18 100644 --- a/ports/stm32/boards/stm32f091_af.csv +++ b/ports/stm32/boards/stm32f091_af.csv @@ -1,6 +1,6 @@ Port ,Pin ,AF0 ,AF1 ,AF2 ,AF3 ,AF4 ,AF5 ,AF6 ,AF7 ,,,,,,,,,ADC , ,AF0 ,AF1 ,AF2 ,AF3 ,AF4 ,AF5 ,AF6 ,AF7 ,,,,,,,,,ADC -PortA,PA0 , ,USART2_CTS ,TIM2_CH1_ETR ,TSC_G1_IO1,USART4_TX , , ,COMP1_OUT,,,,,,,,,ADC1_IN0 +PortA,PA0 , ,USART2_CTS ,TIM2_CH1/TIM2_ETR ,TSC_G1_IO1,USART4_TX , , ,COMP1_OUT,,,,,,,,,ADC1_IN0 PortA,PA1 ,EVENTOUT ,USART2_RTS ,TIM2_CH2 ,TSC_G1_IO2,USART4_RX ,TIM15_CH1N , , ,,,,,,,,,ADC1_IN1 PortA,PA2 ,TIM15_CH1 ,USART2_TX ,TIM2_CH3 ,TSC_G1_IO3, , , ,COMP2_OUT,,,,,,,,,ADC1_IN2 PortA,PA3 ,TIM15_CH2 ,USART2_RX ,TIM2_CH4 ,TSC_G1_IO4, , , , ,,,,,,,,,ADC1_IN3 From 98ab12a4912c9b6e223b33bf3007ad3e2b97bdb2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 6 Mar 2026 11:41:15 +1100 Subject: [PATCH 1865/2098] stm32/boards/stm32n657_af.csv: Add TIM alt funcs to PA0-PA3. Signed-off-by: Damien George --- ports/stm32/boards/stm32n657_af.csv | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv index c02c658bd73..b269d0497c6 100644 --- a/ports/stm32/boards/stm32n657_af.csv +++ b/ports/stm32/boards/stm32n657_af.csv @@ -1,7 +1,9 @@ Port ,Pin ,AF0 ,AF1 ,AF2 ,AF3 ,AF4 ,AF5 ,AF6 ,AF7 ,AF8 ,AF9 ,AF10 ,AF11 ,AF12 ,AF13 ,AF14 ,AF15 ,ADC , ,SYS ,LPTIM1/TIM1/2/16/17,LPTIM3/PDM_SAI1/TIM3/4/5/12/15,I3C1/LPTIM2/3/LPUART1/OCTOSPI/TIM1/8,CEC/DCMI/I2C1/2/3/4/LPTIM1/2/SPI1/I2S1/TIM15/USART1,CEC/I3C1/LPTIM1/SPI1/I2S1/SPI2/I2S2/SPI3/I2S3/SPI4/5/6,I2C4/OCTOSPI/SAI1/SPI3/I2S3/SPI4/UART4/12/USART10/USB_PD,SDMMC1/SPI2/I2S2/SPI3/I2S3/SPI6/UART7/8/12/USART1/2/3/6/10/11,LPUART1/SAI2/SDMMC1/SPI6/UART4/5/8,FDCAN1/2/FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/OCTOSPI/SDMMC2/TIM13/14,CRS/FMC[NAND16]/OCTOSPI/SAI2/SDMMC2/TIM8/USB_,ETH[MII/RMII]/FMC[NAND16]/OCTOSPI/SDMMC2/UART7/9/USB_PD,FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/FMC[SDRAM_16bit]/SDMMC1,DCMI/FMC[NAND16]/FMC[NORmux]/FMC[NOR_RAM]/LPTIM5,LPTIM3/4/5/6/TIM2/UART5,SYS ,ADC -PortA,PA0 , , , , , , , , , , , ,SDMMC2_CMD , , , , ,ADC12_INP0/ADC12_INN1 -PortA,PA3 , , , , , ,SPI5_NSS , , , , , , , , , , , +PortA,PA0 , ,TIM2_CH1 ,TIM5_CH1 ,TIM9_CH1 ,TIM15_BKIN , , , , , , ,SDMMC2_CMD , , , , ,ADC12_INP0/ADC12_INN1 +PortA,PA1 , ,TIM2_CH2 ,TIM5_CH2 ,LPTIM3_IN1 ,TIM15_CH1N , , , , , , , , , , , ,ADC12_INP1 +PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3 ,LPTIM3_IN2 ,TIM15_CH1 , , , , , , , , , , , ,ADC12_INP14 +PortA,PA3 , ,TIM16_CH1 , , , ,SPI5_NSS , , , , , , , , , , , PortA,PA5 , , , , , , , , , , , , , , , , ,ADC2_INP18 PortA,PA8 , , , , , , , , , , , , , , , , ,ADC12_INP5 PortA,PA9 , , , , , , , , , , , , , , , , ,ADC12_INP10 From ef2b30b560f5a231745164766c0320228d70290f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Mar 2026 16:07:04 +1100 Subject: [PATCH 1866/2098] docs/library/machine.PWM: Add alif,stm32 to list of ports with invert. Signed-off-by: Damien George --- docs/library/machine.PWM.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst index c2b606affd6..0fdc4b28a1e 100644 --- a/docs/library/machine.PWM.rst +++ b/docs/library/machine.PWM.rst @@ -40,7 +40,7 @@ Constructors Setting *freq* may affect other PWM objects if the objects share the same underlying PWM generator (this is hardware specific). Only one of *duty_u16* and *duty_ns* should be specified at a time. - *invert* is available only on the esp32, mimxrt, nrf, rp2, samd and zephyr ports. + *invert* is available only on the alif, esp32, mimxrt, nrf, rp2, samd, stm32 and zephyr ports. Methods ------- From 47871a4276bfd8e8207324223dbdf2c61867fc38 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 6 Mar 2026 11:41:32 +1100 Subject: [PATCH 1867/2098] docs/library/machine.PWM: Document hardware PWM layout. Signed-off-by: Damien George --- docs/library/machine.PWM.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst index 0fdc4b28a1e..fdf7d7f4e0d 100644 --- a/docs/library/machine.PWM.rst +++ b/docs/library/machine.PWM.rst @@ -84,6 +84,22 @@ Methods Specific PWM class implementations ---------------------------------- +On the alif port there are 11 independent PWM blocks with independent +frequencies, and they have 2 outputs each. The underlying counter is +32-bits wide for all 11 PWM blocks. + +On the rp2 port there are 8 independent PWM blocks on RP2040 and 12 on +RP2350, each with independent frequencies, and each with 2 outputs. +The underlying counter is 16-bits wide for all PWM blocks. + +On the stm32 port the number of independent PWM blocks depends on the MCU +and can range between 4 and 19. TIM2 and TIM5 blocks (also TIM3 and TIM4 +blocks on STM32U5 and STM32N6) are 32-bits wide, and the others are +16-bits wide. All MCUs supported by MicroPython have at least one 32-bit +block available, and most have two. MCUs will have pins PA0 through PA3 +assigned to a 32-bit PWM block (except STM32N6 which has a 16-bit PWM +block on PA3). PWM blocks have up to 4 outputs each. + The following concrete class(es) implement enhancements to the PWM class. | :ref:`pyb.Timer for PyBoard ` From 8f24c86263d713dc3a132a301e96d4857978f4e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 18 Mar 2026 13:26:08 +1100 Subject: [PATCH 1868/2098] tests/extmod_hardware/machine_pwm.py: Convert test to use target_wiring. Signed-off-by: Damien George --- tests/extmod_hardware/machine_pwm.py | 51 ++++++++++------------------ tests/run-tests.py | 1 + tests/target_wiring/NUCLEO_WB55.py | 2 ++ tests/target_wiring/PYBx.py | 2 ++ tests/target_wiring/README.md | 14 ++++++++ tests/target_wiring/alif.py | 2 ++ tests/target_wiring/esp32.py | 2 ++ tests/target_wiring/esp8266.py | 2 ++ tests/target_wiring/mimxrt.py | 12 +++++++ tests/target_wiring/rp2.py | 2 ++ tests/target_wiring/samd.py | 2 ++ tests/target_wiring/stm32.py | 5 +++ 12 files changed, 63 insertions(+), 34 deletions(-) diff --git a/tests/extmod_hardware/machine_pwm.py b/tests/extmod_hardware/machine_pwm.py index f78825795e2..7d4f82fd2fe 100644 --- a/tests/extmod_hardware/machine_pwm.py +++ b/tests/extmod_hardware/machine_pwm.py @@ -1,10 +1,7 @@ # Test machine.PWM, frequency and duty cycle (using machine.time_pulse_us). # # IMPORTANT: This test requires hardware connections: the PWM-output and pulse-input -# pins must be wired together (see the variable `pwm_pulse_pins`). - -import sys -import time +# pins must be wired together (see the variable `pwm_loopback_pins`). try: from machine import time_pulse_us, Pin, PWM @@ -12,48 +9,34 @@ print("SKIP") raise SystemExit -import unittest +import machine, sys, time, unittest +from target_wiring import pwm_loopback_pins pwm_freq_limit = 1000000 freq_margin_per_thousand = 0 duty_margin_per_thousand = 0 timing_margin_us = 5 -# Configure pins based on the target. -if "alif" in sys.platform: - pwm_pulse_pins = (("P0_4", "P0_5"),) -elif "esp32" in sys.platform: - pwm_pulse_pins = ((4, 5),) +# Slow MCUs cannot capture short pulses using `time_pulse_us` so limit the maximum PWM +# frequency tested on such targets. +if hasattr(machine, "freq"): + f = machine.freq() + if isinstance(f, tuple): + f = f[0] + if f <= 48_000_000: + pwm_freq_limit = 2_000 + elif f <= 64_000_000: + pwm_freq_limit = 5_000 + +# Tune test parameters based on the target. +if "esp32" in sys.platform: freq_margin_per_thousand = 2 duty_margin_per_thousand = 1 timing_margin_us = 20 elif "esp8266" in sys.platform: - pwm_pulse_pins = ((4, 5),) pwm_freq_limit = 1_000 duty_margin_per_thousand = 3 timing_margin_us = 50 -elif "mimxrt" in sys.platform: - if "Teensy" in sys.implementation._machine: - # Teensy 4.x - pwm_pulse_pins = ( - ("D0", "D1"), # FLEXPWM X and UART 1 - ("D2", "D3"), # FLEXPWM A/B - ("D11", "D12"), # QTMR and MOSI/MISO of SPI 0 - ) - else: - pwm_pulse_pins = (("D0", "D1"),) -elif "pyboard" in sys.platform: - pwm_pulse_pins = ((Pin.cpu.A0, Pin.cpu.A1),) -elif "rp2" in sys.platform: - pwm_pulse_pins = (("GPIO0", "GPIO1"),) -elif "samd" in sys.platform: - pwm_pulse_pins = (("D0", "D1"),) - if "SAMD21" in sys.implementation._machine: - # MCU is too slow to capture short pulses. - pwm_freq_limit = 2_000 -else: - print("Please add support for this test on this platform.") - raise SystemExit # Test a specific frequency and duty cycle. @@ -160,7 +143,7 @@ def test_freq_10000(self): # Generate test classes, one for each set of pins to test. -for pwm, pulse in pwm_pulse_pins: +for pwm, pulse in pwm_loopback_pins: cls_name = "Test_{}_{}".format(pwm, pulse) globals()[cls_name] = type( cls_name, (TestBase, unittest.TestCase), {"pwm_pin": pwm, "pulse_pin": pulse} diff --git a/tests/run-tests.py b/tests/run-tests.py index a5659fff8b3..59a292ed4b8 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -291,6 +291,7 @@ "extmod/machine_uart_tx.py", "extmod_hardware/machine_can_timings.py", "extmod_hardware/machine_encoder.py", + "extmod_hardware/machine_pwm.py", "extmod_hardware/machine_uart_irq_break.py", "extmod_hardware/machine_uart_irq_rx.py", "extmod_hardware/machine_uart_irq_rxidle.py", diff --git a/tests/target_wiring/NUCLEO_WB55.py b/tests/target_wiring/NUCLEO_WB55.py index 166f6f8d35f..e40d35e225b 100644 --- a/tests/target_wiring/NUCLEO_WB55.py +++ b/tests/target_wiring/NUCLEO_WB55.py @@ -8,3 +8,5 @@ uart_loopback_kwargs = {} spi_standalone_args_list = [(1,), (2,)] + +pwm_loopback_pins = [("D1", "D0")] diff --git a/tests/target_wiring/PYBx.py b/tests/target_wiring/PYBx.py index a825dbb5141..b36e342e944 100644 --- a/tests/target_wiring/PYBx.py +++ b/tests/target_wiring/PYBx.py @@ -12,3 +12,5 @@ # CAN args assume no connection for single device tests can_args = (1,) can_kwargs = {} + +pwm_loopback_pins = [("X1", "X2")] diff --git a/tests/target_wiring/README.md b/tests/target_wiring/README.md index 96c98da8dff..c3a7038bb91 100644 --- a/tests/target_wiring/README.md +++ b/tests/target_wiring/README.md @@ -48,6 +48,20 @@ SPI instances will be created using: for spi_args in spi_standalone_args_list: machine.SPI(*spi_args) +### PWM tests + +PWM tests require a PWM output to be connected in loopback mode to another GPIO pin +that will use `machine.time_pulse_us()` to time the PWM output signal. The variables +are: + + pwm_loopback_pins: list[tuple[Any, Any]] + +The PWM and input Pin instances will be created using: + + for pwm_pin, pulse_pin in pwm_loopback_pins: + machine.PWM(pwm_pin) + machine.Pin(pulse_pin, Pin.IN) + ### Encoder tests Encoder tests require one encoder to be connected in loopback mode to two other GPIO diff --git a/tests/target_wiring/alif.py b/tests/target_wiring/alif.py index cb62ea40720..708b4591846 100644 --- a/tests/target_wiring/alif.py +++ b/tests/target_wiring/alif.py @@ -7,3 +7,5 @@ uart_loopback_kwargs = {} spi_standalone_args_list = [(0,)] + +pwm_loopback_pins = [("P0_4", "P0_5")] diff --git a/tests/target_wiring/esp32.py b/tests/target_wiring/esp32.py index a3d39b5111f..d94a6f60759 100644 --- a/tests/target_wiring/esp32.py +++ b/tests/target_wiring/esp32.py @@ -14,6 +14,8 @@ else: spi_standalone_args_list = [(1,), (2,)] +pwm_loopback_pins = [(4, 5)] + encoder_loopback_id = 0 encoder_loopback_out_pins = (4, 12) encoder_loopback_in_pins = (5, 13) diff --git a/tests/target_wiring/esp8266.py b/tests/target_wiring/esp8266.py index ba9bca01c86..ee0c3020bfb 100644 --- a/tests/target_wiring/esp8266.py +++ b/tests/target_wiring/esp8266.py @@ -7,3 +7,5 @@ uart_loopback_kwargs = {} spi_standalone_args_list = [(1,)] + +pwm_loopback_pins = [(4, 5)] diff --git a/tests/target_wiring/mimxrt.py b/tests/target_wiring/mimxrt.py index e1e51ea143d..2836d88ab9d 100644 --- a/tests/target_wiring/mimxrt.py +++ b/tests/target_wiring/mimxrt.py @@ -3,11 +3,23 @@ # Connect: # - UART1 TX and RX, usually D0 and D1 +import sys + uart_loopback_args = (1,) uart_loopback_kwargs = {} spi_standalone_args_list = [()] +if "Teensy" in sys.implementation._machine: + # Teensy 4.x + pwm_loopback_pins = [ + ("D0", "D1"), # FLEXPWM X and UART 1 + ("D2", "D3"), # FLEXPWM A/B + ("D11", "D12"), # QTMR and MOSI/MISO of SPI 0 + ] +else: + pwm_loopback_pins = [("D0", "D1")] + encoder_loopback_id = 0 encoder_loopback_out_pins = ("D0", "D2") encoder_loopback_in_pins = ("D1", "D3") diff --git a/tests/target_wiring/rp2.py b/tests/target_wiring/rp2.py index 4024eb20884..c11cbd46904 100644 --- a/tests/target_wiring/rp2.py +++ b/tests/target_wiring/rp2.py @@ -7,3 +7,5 @@ uart_loopback_kwargs = {"tx": "GPIO0", "rx": "GPIO1"} spi_standalone_args_list = [(0,), (1,)] + +pwm_loopback_pins = [("GPIO0", "GPIO1")] diff --git a/tests/target_wiring/samd.py b/tests/target_wiring/samd.py index 1ee67e8e749..50c7b087e51 100644 --- a/tests/target_wiring/samd.py +++ b/tests/target_wiring/samd.py @@ -7,3 +7,5 @@ uart_loopback_kwargs = {"tx": "D1", "rx": "D0"} spi_standalone_args_list = [()] + +pwm_loopback_pins = [("D0", "D1")] diff --git a/tests/target_wiring/stm32.py b/tests/target_wiring/stm32.py index da73d7fa9a2..533431e6947 100644 --- a/tests/target_wiring/stm32.py +++ b/tests/target_wiring/stm32.py @@ -1,7 +1,12 @@ # Target wiring for non-PyBoard stm32 boards # # See PYBx.py for PyBoards +# +# Connect: +# - D0 to D1 (Arduino labels) # CAN args assume no connection for single device tests can_args = (1,) can_kwargs = {} + +pwm_loopback_pins = [("D0", "D1")] From 2ccf78ae194ac86dbfffca1a06ae454b02a09029 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 12 Mar 2026 12:21:45 +1100 Subject: [PATCH 1869/2098] ci,esp32: Build oldest & newest ESP-IDF versions in CI. Intended to catch problems where new features don't build in old ESP-IDF. Includes major refactor to the GitHub Actions Workflow for esp32 port, including making a reusable workflow for both Code Size and ESP32 build jobs. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/actions/setup_esp32/action.yml | 47 ++++++++++++++++++++++++ .github/workflows/code_size.yml | 29 +++++---------- .github/workflows/ports_esp32.yml | 50 +++++++++++++------------- ports/esp32/README.md | 6 ++++ tools/ci.sh | 21 ++++------- 5 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 .github/actions/setup_esp32/action.yml diff --git a/.github/actions/setup_esp32/action.yml b/.github/actions/setup_esp32/action.yml new file mode 100644 index 00000000000..42c44cf76ce --- /dev/null +++ b/.github/actions/setup_esp32/action.yml @@ -0,0 +1,47 @@ +name: Setup ESP-IDF for CI +description: Install ESP-IDF +inputs: + idf_ver: + required: true + type: string + ccache_key: + required: true + type: string + +runs: + using: "composite" + + steps: + - id: python_ver + name: Read the Python version + run: echo PYTHON_VER=py$(python --version | cut -d' ' -f2) | tee "${GITHUB_OUTPUT}" + shell: bash + + - name: Cached ESP-IDF install + id: cache_esp_idf + uses: actions/cache@v5 + with: + path: | + ./esp-idf/ + ~/.espressif/ + !~/.espressif/dist/ + ~/.cache/pip/ + # Cache is keyed on both IDF version (from the job) and Python version (from the runner) + key: esp-idf-${{ inputs.idf_ver }}-${{ steps.python_ver.outputs.PYTHON_VER }} + + - name: Install ESP-IDF packages + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + env: + IDF_VER: ${{ inputs.idf_ver }} + run: tools/ci.sh esp32_idf_setup + shell: bash + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: esp32-${{ inputs.idf_ver }}-${{ inputs.ccache_key }} + + - name: Enable CCache for ESP-IDF + run: echo "IDF_CCACHE_ENABLE=1" >> ${GITHUB_ENV} + shell: bash + diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index 42e7183c4f4..f3238a85d9a 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -32,28 +32,17 @@ jobs: - name: Install packages run: tools/ci.sh code_size_setup - # Needs to be kept in synch with ports_esp32.yml - - id: idf_ver - name: Read the ESP-IDF version (including Python version) and set outputs.IDF_VER - run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" - - name: Cached ESP-IDF install - id: cache_esp_idf - uses: actions/cache@v5 - with: - path: | - ./esp-idf/ - ~/.espressif/ - !~/.espressif/dist/ - ~/.cache/pip/ - key: esp-idf-${{ steps.idf_ver.outputs.IDF_VER }} - - name: Install ESP-IDF packages - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: tools/ci.sh esp32_idf_setup + - name: Find IDF_NEWEST_VER + id: idf_ver + run: | + echo "IDF_VER="$(yq .env.IDF_NEWEST_VER < .github/workflows/ports_esp32.yml) \ + | tee "${GITHUB_OUTPUT}" - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + - name: Setup ESP-IDF + uses: ./.github/actions/setup_esp32 with: - key: code_size + idf_ver: ${{ steps.idf_ver.outputs.IDF_VER }} + ccache_key: code_size - name: Build run: tools/ci.sh code_size_build diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index 446db794cb4..56dce3b69d9 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -17,45 +17,47 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + # Oldest and newest supported ESP-IDF versions, should match ports/esp32/README.md + IDF_OLDEST_VER: &oldest "v5.3" + IDF_NEWEST_VER: &newest "v5.5.1" + jobs: build_idf: strategy: fail-fast: false matrix: + idf_ver: + - *oldest + - *newest ci_func: # names are functions in ci.sh - esp32_build_cmod_spiram_s2 - esp32_build_s3_c3 - esp32_build_c2_c5_c6 - esp32_build_p4 + exclude: + # Exclude some jobs on the oldest IDF version, to save resources + - idf_ver: *oldest + ci_func: esp32_build_c2_c5_c6 + - idf_ver: *oldest + ci_func: esp32_build_p4 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - # Needs to be kept in synch with code_size.yml - - id: idf_ver - name: Read the ESP-IDF version (including Python version) and set outputs.IDF_VER - run: tools/ci.sh esp32_idf_ver | tee "${GITHUB_OUTPUT}" + # Only the newest IDF version will build the ESP-IDF lockfiles correctly, + # so we need to disable MICROPY_MAINTAINER_BUILD on older versions. + - name: Disable extra checks for older ESP-IDF + id: check_newest_ver + if: ${{ matrix.idf_ver != env.IDF_NEWEST_VER }} + run: echo "MICROPY_MAINTAINER_BUILD=0" >> ${GITHUB_ENV} - - name: Cached ESP-IDF install - id: cache_esp_idf - uses: actions/cache@v5 - with: - path: | - ./esp-idf/ - ~/.espressif/ - !~/.espressif/dist/ - ~/.cache/pip/ - key: esp-idf-${{ steps.idf_ver.outputs.IDF_VER }} - - - name: Install ESP-IDF packages - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: tools/ci.sh esp32_idf_setup - - # Needs to be kept in synch with code_size.yml - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + - name: Setup ESP-IDF + uses: ./.github/actions/setup_esp32 with: - key: esp32-${{ matrix.ci_func }} + idf_ver: ${{ matrix.idf_ver }} + ccache_key: ${{ matrix.ci_func }} + - - name: Build ci_${{matrix.ci_func }} + - name: Build ci_${{matrix.ci_func }} on ESP-IDF ${{ matrix.idf_ver }} run: tools/ci.sh ${{ matrix.ci_func }} diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 2cfc09afadf..85e13904054 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -55,6 +55,12 @@ The ESP-IDF changes quickly and MicroPython only supports certain versions. The current recommended version of ESP-IDF for MicroPython is v5.5.1. MicroPython also supports v5.3, v5.4, v5.4.1 and v5.4.2. + + To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). diff --git a/tools/ci.sh b/tools/ci.sh index 6bc74bd54ce..0532c1c7570 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -10,7 +10,9 @@ fi ulimit -n 1024 # Fail on some things which are warnings otherwise -export MICROPY_MAINTAINER_BUILD=1 +if [ -z "$MICROPY_MAINTAINER_BUILD" ]; then + export MICROPY_MAINTAINER_BUILD=1 +fi ######################################################################################## # general helper functions @@ -206,20 +208,11 @@ function ci_embedding_build { ######################################################################################## # ports/esp32 -# GitHub tag of ESP-IDF to use for CI, extracted from the esp32 dependency lockfile -# This should end up as a tag name like vX.Y.Z -# (note: This hacky parsing can be replaced with 'yq' once Ubuntu >=24.04 is in use) -IDF_VER=v$(grep -A10 "idf:" ports/esp32/lockfiles/dependencies.lock.esp32 | grep "version:" | head -n1 | sed -E 's/ +version: //') -PYTHON=$(command -v python3 2> /dev/null) -PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) - -export IDF_CCACHE_ENABLE=1 - -function ci_esp32_idf_ver { - echo "IDF_VER=${IDF_VER}-py${PYTHON_VER}" -} - function ci_esp32_idf_setup { + if [ -z "$IDF_VER" ]; then + echo "IDF_VER environment variable must be set before running" + return 1 + fi echo "Using ESP-IDF version $IDF_VER" git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git # doing a treeless clone isn't quite as good as --shallow-submodules, but it From e0beace19ff4b6e11d43668153f5e6f03f2d3a68 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Mar 2026 16:42:58 +1100 Subject: [PATCH 1870/2098] LICENSE,docs: Update copyright year range to include 2026. Signed-off-by: Damien George --- LICENSE | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 929a2e97de7..28b5239e5fe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2025 Damien P. George +Copyright (c) 2013-2026 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 diff --git a/docs/conf.py b/docs/conf.py index 603543aa18c..f80ca97edca 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,7 +72,7 @@ # General information about the project. project = "MicroPython" -copyright = "- The MicroPython Documentation is Copyright © 2014-2025, " + micropy_authors +copyright = "- The MicroPython Documentation is Copyright © 2014-2026, " + micropy_authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From b1d635fa823e89ffc4c5659af4260bb29e0faf32 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 17 Mar 2026 18:42:07 +0100 Subject: [PATCH 1871/2098] docs/reference/speed_python: Update native emitter limitations. This commit updates the listed limitations of the native emitter in the documentation related to how to increase speed of python code. Context managers are supported, as in functions marked as native can use the `with` statement in regular code. Generators can be used in native functions both on the emitting (ie. `yield `) and on the receiving end. Signed-off-by: Alessandro Gatti --- docs/reference/speed_python.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/reference/speed_python.rst b/docs/reference/speed_python.rst index 9360fd61080..6382394bd50 100644 --- a/docs/reference/speed_python.rst +++ b/docs/reference/speed_python.rst @@ -243,12 +243,10 @@ no adaptation (but see below). It is invoked by means of a function decorator: There are certain limitations in the current implementation of the native code emitter. -* Context managers are not supported (the ``with`` statement). -* Generators are not supported. * If ``raise`` is used an argument must be supplied. * The background scheduler (see `micropython.schedule`) is not run during execution of native code. -* On targets with thrteading and the GIL, the GIL is not released during +* On targets with threading and the GIL, the GIL is not released during execution of native code. To mitigate the last two points, long running native functions should call From b3d88cf210d0408b76f7375773ae665896334759 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 18 Mar 2026 20:04:15 +0100 Subject: [PATCH 1872/2098] extmod/nimble/modbluetooth_nimble: Handle port init failures. This commit fixes an issue related to the NimBLE initialisation procedure in low memory environments on ESP32 boards. MicroPython uses at least two different NimBLE stacks across the supported ports, mynewt (imported as an external library), and the one provided by Espressif in their own SDKs. The problem is that these two ports differ in the signature for `nimble_port_init(void)`, with mynewt returning `void`, and Espressif's returning a status code on failure. On ESP32 boards, allocating almost all the available heap and then turning on the Bluetooth stack would trigger a failure in the NimBLE initialisation function that is not handled by the NimBLE integration code, as there's no expectation of a recoverable condition. Since the stack initialisation would progress, then uninitialised memory accesses crash the board. Since we cannot really modify neither mynewt nor Espressif SDKs, the next best thing is to provide two conditional initialisation paths depending on a configuration setting. This would make Espressif ports recover from a failed initialisation whilst retaining the existing behaviour on other ports. This fixes #14293. Signed-off-by: Alessandro Gatti --- extmod/nimble/modbluetooth_nimble.c | 10 ++++++++++ ports/esp32/mpconfigport.h | 1 + 2 files changed, 11 insertions(+) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 5e7030e36fa..6b000f77f8a 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -618,7 +618,17 @@ int mp_bluetooth_init(void) { // Initialise NimBLE memory and data structures. DEBUG_printf("mp_bluetooth_init: nimble_port_init\n"); + + // On some ports `nimble_port_init` may return an error code indicating a + // failed initialisation. + #if MICROPY_BLUETOOTH_NIMBLE_PORT_INIT_RETURNS_ERROR + if (nimble_port_init() < 0) { + mp_bluetooth_deinit(); + return MP_EIO; + } + #else nimble_port_init(); + #endif ble_hs_cfg.reset_cb = reset_cb; ble_hs_cfg.sync_cb = sync_cb; diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index a1b4594da56..97c44c8076f 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -108,6 +108,7 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) #define MICROPY_BLUETOOTH_NIMBLE (1) #define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) +#define MICROPY_BLUETOOTH_NIMBLE_PORT_INIT_RETURNS_ERROR (1) #endif // MICROPY_PY_BLUETOOTH #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (esp_random()) From 406356ec8b289ae47bbad1bf9869171a7b13fd1d Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 7 Feb 2026 23:11:16 +1100 Subject: [PATCH 1873/2098] extmod/modlwip: Ensure socket is finalisable if error during creation. Because socket objects have a finaliser they must be created carefully, in case an exception is raised during the population of their members, eg invalid input argument or out-of-memory when allocating additional arrays. Prior to the fix in this commit, the finaliser would crash due to `incoming.udp_raw.array` being an invalid pointer in the following cases: - if a SOCK_RAW was created with a proto argument that was not an integer - if a SOCK_DGRAM or SOCK_RAW was created where the allocation of `lwip_incoming_packet_t` failed - if an integer was passed in for the socket type but it was not one of SOCK_STREAM, SOCK_DGRAM or SOCK_RAW Furthermore, if the allocation of `lwip_incoming_packet_t` failed then it may have led to corruption within lwIP when freeing `socket->pcb.raw` because that PCB was not fully set up with its callbacks. This commit fixes all of these issues by ensuring: - `pcb.tcp` and `incoming.udp_raw.array` are initialised to NULL early on - the proto argument is parsed before allocating the PCB - the allocation of `lwip_incoming_packet_t` occurs befor allocating the PCB - `incoming.udp_raw.array` is checked for NULL in the finaliser code The corresponding test (which already checked most of these causes of failure) has been updated to include a previously-uncovered scenario. Signed-off-by: Damien George --- extmod/modlwip.c | 18 ++++++++++++++---- tests/extmod/socket_badconstructor.py | 5 +++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 4a72a05d84b..9365bd492b0 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -384,7 +384,7 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket, bool free_queue pbuf_free(socket->incoming.tcp.pbuf); socket->incoming.tcp.pbuf = NULL; } - } else { + } else if (socket->incoming.udp_raw.array != NULL) { for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) { lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[i]; if (slot->pbuf != NULL) { @@ -938,7 +938,12 @@ static void lwip_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_prin static mp_obj_t lwip_socket_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, 0, 4, false); + // Once the socket is allocated it must be in a valid state to be finalised: + // - `incoming.udp_raw.array` is NULL or a valid heap pointer + // - `pcb` is NULL or a valid lwIP PCB that has been fully initialised lwip_socket_obj_t *socket = mp_obj_malloc_with_finaliser(lwip_socket_obj_t, &lwip_socket_type); + socket->pcb.tcp = NULL; + socket->incoming.udp_raw.array = NULL; socket->timeout = -1; socket->recv_offset = 0; socket->domain = MOD_NETWORK_AF_INET; @@ -946,10 +951,16 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s socket->callback = MP_OBJ_NULL; socket->state = STATE_NEW; + // Parse given arguments. + uint8_t socket_proto = 0; + (void)socket_proto; if (n_args >= 1) { socket->domain = mp_obj_get_int(args[0]); if (n_args >= 2) { socket->type = mp_obj_get_int(args[1]); + if (n_args >= 3) { + socket_proto = mp_obj_get_int(args[2]); + } } } @@ -963,18 +974,17 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s #if MICROPY_PY_LWIP_SOCK_RAW case MOD_NETWORK_SOCK_RAW: #endif + socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN); if (socket->type == MOD_NETWORK_SOCK_DGRAM) { socket->pcb.udp = udp_new(); } #if MICROPY_PY_LWIP_SOCK_RAW else { - mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]); - socket->pcb.raw = raw_new(proto); + socket->pcb.raw = raw_new(socket_proto); } #endif socket->incoming.udp_raw.iget = 0; socket->incoming.udp_raw.iput = 0; - socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN); break; default: mp_raise_OSError(MP_EINVAL); diff --git a/tests/extmod/socket_badconstructor.py b/tests/extmod/socket_badconstructor.py index 4a9d2668c7f..1ea5d750b3e 100644 --- a/tests/extmod/socket_badconstructor.py +++ b/tests/extmod/socket_badconstructor.py @@ -16,6 +16,11 @@ except TypeError: print("TypeError") +try: + s = socket.socket(socket.AF_INET, 123456) +except OSError: + print("OSError") + try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, None) except TypeError: From 8a3c9f0bf2daf3d270ad4b15ed290ef74e44ced5 Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Thu, 8 Jan 2026 19:36:49 +0000 Subject: [PATCH 1874/2098] extmod/modlwip: Call user callback on newly-received UDP or RAW packet. User callbacks allow code to respond to incoming messages without blocking or polling. User callbacks are optional, and if no callback is registered the code has no effect. The mechanism is the same as for TCP: when a connection is accepted or a TCP packet is received, a user callback is executed. Fixes issue #3594. Signed-off-by: Jack Whitham --- extmod/modlwip.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 9365bd492b0..0e2df8f3290 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -463,6 +463,8 @@ static void udp_raw_incoming(lwip_socket_obj_t *socket, struct pbuf *p, const ip slot->peer_addr = *addr; slot->peer_port = port; socket->incoming.udp_raw.iput = (socket->incoming.udp_raw.iput + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; + // Notify user callback of the new packet + exec_user_callback(socket); } } From 134bf4d847579b6a38955b344017a48e5db3f918 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 18 Mar 2026 21:52:31 +0100 Subject: [PATCH 1875/2098] alif/irq: Add missing IRQ priorities. Add DMA, NPU and PDM IRQ priorities to irq.h. Signed-off-by: iabdalkader --- ports/alif/irq.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/alif/irq.h b/ports/alif/irq.h index 86b739795c1..0aa97747485 100644 --- a/ports/alif/irq.h +++ b/ports/alif/irq.h @@ -43,11 +43,14 @@ #define IRQ_PRI_MHU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 0, 0) #define IRQ_PRI_QUIET_TIMING NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) #define IRQ_PRI_UART_REPL NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 1, 0) +#define IRQ_PRI_DMA NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 2, 0) #define IRQ_PRI_ADC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 3, 0) #define IRQ_PRI_CSI NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 5, 0) #define IRQ_PRI_USB NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 7, 0) #define IRQ_PRI_HWSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 8, 0) +#define IRQ_PRI_NPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 9, 0) #define IRQ_PRI_GPU NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) +#define IRQ_PRI_PDM NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 10, 0) #define IRQ_PRI_GPIO NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 50, 0) #define IRQ_PRI_I2C NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 60, 0) #define IRQ_PRI_RTC NVIC_EncodePriority(NVIC_PRIORITYGROUP_7, 100, 0) From 82e44e07e711befd9eb49505c180ed6303e4b836 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Feb 2026 01:57:58 +1100 Subject: [PATCH 1876/2098] stm32/mpthreadport: Increase minimum thread stack size to 2.5k. 2.25k Seems necessary so it doesn't crash `thread/thread_stacksize1.py`. But 2.5k gives a little extra headroom to make that test actually pass. Signed-off-by: Damien George --- ports/stm32/mpthreadport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/mpthreadport.c b/ports/stm32/mpthreadport.c index 621b4311bfc..369171067e5 100644 --- a/ports/stm32/mpthreadport.c +++ b/ports/stm32/mpthreadport.c @@ -61,8 +61,8 @@ mp_uint_t mp_thread_get_id(void) { mp_uint_t mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { if (*stack_size == 0) { *stack_size = 4096; // default stack size - } else if (*stack_size < 2048) { - *stack_size = 2048; // minimum stack size + } else if (*stack_size < 2560) { + *stack_size = 2560; // minimum stack size } // round stack size to a multiple of the word size From 702f15ab9800769c76539d760fe7b365d8654595 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 21 Mar 2026 16:04:46 +1100 Subject: [PATCH 1877/2098] stm32/boards/PYBD_SF2: Free up some space in internal flash. With the recent addition of `machine.PWM` and `machine.CAN`, the internal flash of PYBD_SF3 overflows by about 300 bytes. This commit moves the inline assembler compiler functions from internal to external QSPI flash. That frees up about 3k internal flash, and shouldn't affect performance. Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index 354c1919b41..8ada037d6e0 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -50,6 +50,7 @@ SECTIONS .text_ext : { . = ALIGN(4); + *py/emit*(.text.emit_inline_thumb_* .rodata.emit_inline_thumb_*) *lib/btstack/*(.text* .rodata*) *lib/mbedtls/*(.text* .rodata*) *lib/mynewt-nimble/*(.text* .rodata*) From 803a4d77171ff8a994fe0ee7cc88a7e6129cb64a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 13 Mar 2026 10:32:57 -0500 Subject: [PATCH 1878/2098] py/objtemplate: Correctly cast qstr literals when printing. qstr literals are of type qstr_short_t (aka uint16_t) for efficiency, but when the type is passed to `mp_printf` it must be cast explicitly to type `qstr`. These locations were found using an experimental gcc plugin for `mp_printf` error checking. Signed-off-by: Jeff Epler --- py/objtemplate.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/objtemplate.c b/py/objtemplate.c index 0fc51a78d9a..86451350a21 100644 --- a/py/objtemplate.c +++ b/py/objtemplate.c @@ -152,9 +152,9 @@ static mp_obj_t mp_obj_template_make_new(const mp_obj_type_t *type, size_t n_arg static void mp_obj_template_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_template_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "%q(%q=", MP_QSTR_Template, MP_QSTR_strings); + mp_printf(print, "%q(%q=", (qstr)MP_QSTR_Template, (qstr)MP_QSTR_strings); mp_obj_print_helper(print, self->strings, PRINT_REPR); - mp_printf(print, ", %q=", MP_QSTR_interpolations); + mp_printf(print, ", %q=", (qstr)MP_QSTR_interpolations); mp_obj_print_helper(print, self->interpolations, PRINT_REPR); mp_print_str(print, ")"); } @@ -346,7 +346,7 @@ static mp_obj_t mp_obj_interpolation_make_new(const mp_obj_type_t *type, size_t static void mp_obj_interpolation_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_interpolation_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "%q(", MP_QSTR_Interpolation); + mp_printf(print, "%q(", (qstr)MP_QSTR_Interpolation); mp_obj_print_helper(print, self->value, PRINT_REPR); mp_print_str(print, ", "); mp_obj_print_helper(print, self->expression, PRINT_REPR); From 74e945752b91798c4c5d9211df59f69fe37b69a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Feb 2026 00:49:06 +1100 Subject: [PATCH 1879/2098] py/modweakref: Implement weakref module with ref and finalize classes. This adds support for the standard `weakref` module, to make weak references to Python objects and have callbacks for when an object is reclaimed by the GC. This feature was requested by PyScript, to allow control over the lifetime of external proxy objects (distinct from JS<->Python proxies). Addresses issue #646 (that's nearly a 12 year old issue!). Functionality added here: - `weakref.ref(object [, callback])` create a simple weak reference with optional callback to be called when the object is reclaimed by the GC - `weakref.finalize(object, callback, /, *args, **kwargs)` create a finalize object that holds a weak reference to an object and allows more convenient callback usage and state change The new module is enabled at the "everything" level. The implementation aims to be as efficient as possible, by adding another bit-per-block to the garbage collector, the WTB (weak table). Similar to the finalizer bit (FTB), if a GC block has its corresponding WTB bit set then a weak reference to that block is held. The details of that weak reference are stored in a global map, `mp_weakref_map`, which maps weak reference to ref/finalize objects, allowing the callbacks to be efficiently found when the object is reclaimed. With this feature enabled the overhead is: - 1/128th of the available memory is used for the new WTB table (eg a 128k heap now needs an extra 1k for the WTB). - Code size is increased. - At garbage collection time, there is a small overhead to check if the collected objects had weak references. This check is the same as the existing FTB finaliser scan, so shouldn't add much overhead. If there are weak reference objects alive (ref/finalize objects) then additional time is taken to call the callbacks and do some accounting to clean up the used weak reference. Signed-off-by: Damien George --- py/gc.c | 118 ++++++++-- py/gc.h | 5 + py/modweakref.c | 314 +++++++++++++++++++++++++ py/mpconfig.h | 5 + py/mpstate.h | 3 + py/py.cmake | 1 + py/py.mk | 1 + py/runtime.c | 4 + tests/ports/unix/extra_coverage.py.exp | 2 +- 9 files changed, 433 insertions(+), 20 deletions(-) create mode 100644 py/modweakref.c diff --git a/py/gc.c b/py/gc.c index 0d4d19ce9e7..5fe26ef8905 100644 --- a/py/gc.c +++ b/py/gc.c @@ -104,14 +104,21 @@ #if MICROPY_ENABLE_FINALISER // FTB = finaliser table byte // if set, then the corresponding block may have a finaliser - #define BLOCKS_PER_FTB (8) - #define FTB_GET(area, block) ((area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1) #define FTB_SET(area, block) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0) #define FTB_CLEAR(area, block) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0) #endif +#if MICROPY_PY_WEAKREF +// WTB = weakref table byte +// if set, then the corresponding block may have a weakref in MP_STATE_VM(mp_weakref_map). +#define BLOCKS_PER_WTB (8) +#define WTB_GET(area, block) ((area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] >> ((block) & 7)) & 1) +#define WTB_SET(area, block) do { area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] |= (1 << ((block) & 7)); } while (0) +#define WTB_CLEAR(area, block) do { area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] &= (~(1 << ((block) & 7))); } while (0) +#endif + #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL #define GC_MUTEX_INIT() mp_thread_recursive_mutex_init(&MP_STATE_MEM(gc_mutex)) #define GC_ENTER() mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1) @@ -138,17 +145,23 @@ static void gc_sweep_free_blocks(void); // TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) { // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes): - // T = A + F + P + // T = A + F + W + P // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB + // W = A * BLOCKS_PER_ATB / BLOCKS_PER_WTB // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK - // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) + // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB / BLOCKS_PER_WTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) size_t total_byte_len = (byte *)end - (byte *)start; - #if MICROPY_ENABLE_FINALISER + #if MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF area->gc_alloc_table_byte_len = (total_byte_len - ALLOC_TABLE_GAP_BYTE) * MP_BITS_PER_BYTE / ( MP_BITS_PER_BYTE + #if MICROPY_ENABLE_FINALISER + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + #endif + #if MICROPY_PY_WEAKREF + + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_WTB + #endif + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK ); #else @@ -157,26 +170,36 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) { area->gc_alloc_table_start = (byte *)start; + // Allocate FTB and WTB blocks if they are enabled. + byte *next_table = area->gc_alloc_table_start + area->gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE; + (void)next_table; #if MICROPY_ENABLE_FINALISER size_t gc_finaliser_table_byte_len = (area->gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB; - area->gc_finaliser_table_start = area->gc_alloc_table_start + area->gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE; + area->gc_finaliser_table_start = next_table; + next_table += gc_finaliser_table_byte_len; + #endif + #if MICROPY_PY_WEAKREF + size_t gc_weakref_table_byte_len = (area->gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_WTB - 1) / BLOCKS_PER_WTB; + area->gc_weakref_table_start = next_table; + next_table += gc_weakref_table_byte_len; #endif + // Allocate the GC pool of heap blocks. size_t gc_pool_block_len = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; area->gc_pool_start = (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK; area->gc_pool_end = end; + assert(area->gc_pool_start >= next_table); - #if MICROPY_ENABLE_FINALISER - assert(area->gc_pool_start >= area->gc_finaliser_table_start + gc_finaliser_table_byte_len); - #endif - - #if MICROPY_ENABLE_FINALISER - // clear ATB's and FTB's - memset(area->gc_alloc_table_start, 0, gc_finaliser_table_byte_len + area->gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE); - #else - // clear ATB's - memset(area->gc_alloc_table_start, 0, area->gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE); - #endif + // Clear ATB's, and FTB's and WTB's if they are enabled. + memset(area->gc_alloc_table_start, 0, + area->gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE + #if MICROPY_ENABLE_FINALISER + + gc_finaliser_table_byte_len + #endif + #if MICROPY_PY_WEAKREF + + gc_weakref_table_byte_len + #endif + ); area->gc_last_free_atb_index = 0; area->gc_last_used_block = 0; @@ -196,6 +219,12 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) { gc_finaliser_table_byte_len, gc_finaliser_table_byte_len * BLOCKS_PER_FTB); #endif + #if MICROPY_PY_WEAKREF + DEBUG_printf(" weakref table at %p, length " UINT_FMT " bytes, " + UINT_FMT " blocks\n", area->gc_weakref_table_start, + gc_weakref_table_byte_len, + gc_weakref_table_byte_len * BLOCKS_PER_WTB); + #endif DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", area->gc_pool_start, gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len); @@ -310,6 +339,9 @@ static bool gc_try_add_heap(size_t failed_alloc) { #if MICROPY_ENABLE_FINALISER + total_blocks / BLOCKS_PER_FTB #endif + #if MICROPY_PY_WEAKREF + + total_blocks / BLOCKS_PER_WTB + #endif + total_blocks * BYTES_PER_BLOCK + ALLOC_TABLE_GAP_BYTE + sizeof(mp_state_mem_area_t); @@ -556,6 +588,9 @@ void gc_collect_end(void) { } MP_STATE_THREAD(gc_lock_depth) &= ~GC_COLLECT_FLAG; GC_EXIT(); + #if MICROPY_PY_WEAKREF + gc_weakref_sweep(); + #endif } static void gc_deal_with_stack_overflow(void) { @@ -581,12 +616,16 @@ static void gc_deal_with_stack_overflow(void) { // Run finalisers for all to-be-freed blocks static void gc_sweep_run_finalisers(void) { - #if MICROPY_ENABLE_FINALISER + #if MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF + #if MICROPY_ENABLE_FINALISER && MICROPY_PY_WEAKREF + MP_STATIC_ASSERT(BLOCKS_PER_FTB == BLOCKS_PER_WTB); + #endif for (const mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB); // Small speed optimisation: skip over empty FTB blocks size_t ftb_end = area->gc_last_used_block / BLOCKS_PER_FTB; // index is inclusive for (size_t ftb_idx = 0; ftb_idx <= ftb_end; ftb_idx++) { + #if MICROPY_ENABLE_FINALISER byte ftb = area->gc_finaliser_table_start[ftb_idx]; size_t block = ftb_idx * BLOCKS_PER_FTB; while (ftb) { @@ -616,9 +655,26 @@ static void gc_sweep_run_finalisers(void) { ftb >>= 1; block++; } + #endif + #if MICROPY_PY_WEAKREF + byte wtb = area->gc_weakref_table_start[ftb_idx]; + block = ftb_idx * BLOCKS_PER_WTB; + while (wtb) { + MICROPY_GC_HOOK_LOOP(block); + if (wtb & 1) { // WTB_GET(area, block) shortcut + if (ATB_GET_KIND(area, block) == AT_HEAD) { + mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(area, block); + gc_weakref_about_to_be_freed(obj); + WTB_CLEAR(area, block); + } + } + wtb >>= 1; + block++; + } + #endif } } - #endif // MICROPY_ENABLE_FINALISER + #endif // MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF } // Free unmarked heads and their tails @@ -769,6 +825,25 @@ void gc_info(gc_info_t *info) { GC_EXIT(); } +#if MICROPY_PY_WEAKREF +// Mark the GC heap pointer as having a weakref. +void gc_weakref_mark(void *ptr) { + mp_state_mem_area_t *area; + #if MICROPY_GC_SPLIT_HEAP + area = gc_get_ptr_area(ptr); + assert(area); + #else + assert(VERIFY_PTR(ptr)); + area = &MP_STATE_MEM(area); + #endif + + size_t block = BLOCK_FROM_PTR(area, ptr); + assert(ATB_GET_KIND(area, block) == AT_HEAD); + + WTB_SET(area, block); +} +#endif + void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { bool has_finaliser = alloc_flags & GC_ALLOC_FLAG_HAS_FINALISER; size_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK; @@ -967,6 +1042,11 @@ void gc_free(void *ptr) { FTB_CLEAR(area, block); #endif + #if MICROPY_PY_WEAKREF + // Objects that have a weak reference should not be explicitly freed. + assert(!WTB_GET(area, block)); + #endif + #if MICROPY_GC_SPLIT_HEAP if (MP_STATE_MEM(gc_last_free_area) != area) { // We freed something but it isn't the current area. Reset the diff --git a/py/gc.h b/py/gc.h index 36177633062..4679d6dc863 100644 --- a/py/gc.h +++ b/py/gc.h @@ -58,6 +58,11 @@ void gc_collect_end(void); // Use this function to sweep the whole heap and run all finalisers void gc_sweep_all(void); +// These functions are used to manage weakrefs. +void gc_weakref_mark(void *ptr); +void gc_weakref_about_to_be_freed(void *ptr); +void gc_weakref_sweep(void); + enum { GC_ALLOC_FLAG_HAS_FINALISER = 1, }; diff --git a/py/modweakref.c b/py/modweakref.c new file mode 100644 index 00000000000..3360a4e2095 --- /dev/null +++ b/py/modweakref.c @@ -0,0 +1,314 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 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/gc.h" +#include "py/runtime.h" + +#if MICROPY_PY_WEAKREF + +// Macros to obfuscate a heap pointer as a small integer object. +#define PTR_TO_INT_OBJ(ptr) (MP_OBJ_NEW_SMALL_INT(((uintptr_t)ptr) >> 1)) +#define PTR_FROM_INT_OBJ(obj) ((void *)(MP_OBJ_SMALL_INT_VALUE((obj)) << 1)) + +// Macros to convert between a weak reference and a heap pointer. +#define WEAK_REFERENCE_FROM_HEAP_PTR(ptr) PTR_TO_INT_OBJ(ptr) +#define WEAK_REFERENCE_TO_HEAP_PTR(weak_ref) PTR_FROM_INT_OBJ(weak_ref) + +// Macros to manage ref-finalizer linked-list pointers. +// - mp_obj_ref_t is obfuscated as a small integer object so it's not traced by the GC. +// - mp_obj_finalize_t is stored as-is so it is traced by the GC. +#define REF_FIN_LIST_OBJ_IS_FIN(r) (!mp_obj_is_small_int((r))) +#define REF_FIN_LIST_OBJ_TO_PTR(r) ((mp_obj_ref_t *)(mp_obj_is_small_int((r)) ? PTR_FROM_INT_OBJ((r)) : MP_OBJ_TO_PTR((r)))) +#define REF_FIN_LIST_OBJ_FROM_REF(r) (PTR_TO_INT_OBJ((r))) +#define REF_FIN_LIST_OBJ_FROM_FIN(r) (MP_OBJ_FROM_PTR((r))) +#define REF_FIN_LIST_OBJ_TAIL (PTR_TO_INT_OBJ(NULL)) + +// weakref.ref() instance. +typedef struct _mp_obj_ref_t { + mp_obj_base_t base; + mp_obj_t ref_fin_next; + mp_obj_t obj_weak_ref; + mp_obj_t callback; +} mp_obj_ref_t; + +// weakref.finalize() instance. +// This is an extension of weakref.ref() and shares a lot of code with it. +typedef struct _mp_obj_finalize_t { + mp_obj_ref_t base; + size_t n_args; + size_t n_kw; + mp_obj_t *args; +} mp_obj_finalize_t; + +static const mp_obj_type_t mp_type_ref; +static const mp_obj_type_t mp_type_finalize; + +static mp_obj_t ref___del__(mp_obj_t self_in); + +void gc_weakref_about_to_be_freed(void *ptr) { + mp_obj_t idx = WEAK_REFERENCE_FROM_HEAP_PTR(ptr); + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), idx, MP_MAP_LOOKUP); + if (elem != NULL) { + // Mark element as being freed. + elem->key = mp_const_none; + } +} + +void gc_weakref_sweep(void) { + mp_map_t *map = &MP_STATE_VM(mp_weakref_map); + for (size_t i = 0; i < map->alloc; i++) { + if (map->table[i].key == mp_const_none) { + // Element was just freed, so call all the registered callbacks. + --map->used; + map->table[i].key = MP_OBJ_SENTINEL; + mp_obj_ref_t *ref = REF_FIN_LIST_OBJ_TO_PTR(map->table[i].value); + map->table[i].value = MP_OBJ_NULL; + while (ref != NULL) { + // Invalidate the weak reference. + assert(ref->obj_weak_ref != mp_const_none); + ref->obj_weak_ref = mp_const_none; + + // Call any registered callbacks. + if (ref->callback != mp_const_none) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + if (ref->base.type == &mp_type_ref) { + // weakref.ref() type. + mp_call_function_1(ref->callback, MP_OBJ_FROM_PTR(ref)); + } else { + // weakref.finalize() type. + mp_obj_finalize_t *fin = (mp_obj_finalize_t *)ref; + mp_call_function_n_kw(fin->base.callback, fin->n_args, fin->n_kw, fin->args); + } + nlr_pop(); + } else { + mp_printf(MICROPY_ERROR_PRINTER, "Unhandled exception in weakref callback:\n"); + mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + } + + // Unlink the node. + mp_obj_ref_t *ref_fin_next = REF_FIN_LIST_OBJ_TO_PTR(ref->ref_fin_next); + ref->ref_fin_next = REF_FIN_LIST_OBJ_TAIL; + ref = ref_fin_next; + } + } + } +} + +static mp_obj_t mp_obj_ref_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (type == &mp_type_ref) { + // weakref.ref() type. + mp_arg_check_num(n_args, n_kw, 1, 2, false); + } else { + // weakref.finalize() type. + mp_arg_check_num(n_args, n_kw, 2, MP_OBJ_FUN_ARGS_MAX, true); + } + + // Validate the input object can have a weakref. + void *ptr = NULL; + if (mp_obj_is_obj(args[0])) { + ptr = MP_OBJ_TO_PTR(args[0]); + if (gc_nbytes(ptr) == 0) { + ptr = NULL; + } + } + if (ptr == NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("not a heap object")); + } + + // Create or get the entry in mp_weakref_map corresponding to this object. + mp_obj_t obj_weak_reference = WEAK_REFERENCE_FROM_HEAP_PTR(ptr); + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), obj_weak_reference, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (elem->value == MP_OBJ_NULL) { + // This heap object does not have any existing weakref's, so initialise it. + elem->value = REF_FIN_LIST_OBJ_TAIL; + gc_weakref_mark(ptr); + } + + mp_obj_ref_t *self; + if (type == &mp_type_ref) { + // Create a new weakref.ref() object. + self = mp_obj_malloc_with_finaliser(mp_obj_ref_t, type); + // Link this new ref into the list of all refs/finalizers pointing to this object. + // To ensure it will *NOT* be traced by the GC (the user must manually hold onto it), + // store an integer version of the object after any weakref.finalize() objects (so + // the weakref.finalize() objects continue to be traced by the GC). + mp_obj_t *link = &elem->value; + while (REF_FIN_LIST_OBJ_IS_FIN(*link)) { + link = &REF_FIN_LIST_OBJ_TO_PTR(*link)->ref_fin_next; + } + self->ref_fin_next = *link; + *link = REF_FIN_LIST_OBJ_FROM_REF(self); + } else { + // Create a new weakref.finalize() object. + mp_obj_finalize_t *self_fin = mp_obj_malloc(mp_obj_finalize_t, type); + self_fin->n_args = n_args - 2; + self_fin->n_kw = n_kw; + size_t n_args_kw = self_fin->n_args + self_fin->n_kw * 2; + if (n_args_kw == 0) { + self_fin->args = NULL; + } else { + self_fin->args = m_new(mp_obj_t, n_args_kw); + memcpy(self_fin->args, args + 2, n_args_kw * sizeof(mp_obj_t)); + } + self = &self_fin->base; + // Link this new finalizer into the list of all refs/finalizers pointing to this object. + // To ensure it will be traced by the GC, store its pointer at the start of the list. + self->ref_fin_next = elem->value; + elem->value = REF_FIN_LIST_OBJ_FROM_FIN(self_fin); + } + + // Populate the object weak reference, and the callback. + self->obj_weak_ref = obj_weak_reference; + if (n_args > 1) { + self->callback = args[1]; + } else { + self->callback = mp_const_none; + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t mp_obj_ref_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_ref_t *self = MP_OBJ_TO_PTR(self_in); + if (self->obj_weak_ref == mp_const_none) { + return mp_const_none; + } + if (self->base.type == &mp_type_ref) { + // weakref.ref() type. + return MP_OBJ_FROM_PTR(WEAK_REFERENCE_TO_HEAP_PTR(self->obj_weak_ref)); + } else { + // weakref.finalize() type. + mp_obj_finalize_t *self_fin = MP_OBJ_TO_PTR(self_in); + ref___del__(self_in); + return mp_call_function_n_kw(self_fin->base.callback, self_fin->n_args, self_fin->n_kw, self_fin->args); + } +} + +static mp_obj_t ref___del__(mp_obj_t self_in) { + mp_obj_ref_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), self->obj_weak_ref, MP_MAP_LOOKUP); + if (elem != NULL) { + for (mp_obj_t *link = &elem->value; REF_FIN_LIST_OBJ_TO_PTR(*link) != NULL; link = &REF_FIN_LIST_OBJ_TO_PTR(*link)->ref_fin_next) { + if (self == REF_FIN_LIST_OBJ_TO_PTR(*link)) { + // Unlink and clear this node. + *link = self->ref_fin_next; + self->ref_fin_next = REF_FIN_LIST_OBJ_TAIL; + self->obj_weak_ref = mp_const_none; + break; + } + } + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(ref___del___obj, ref___del__); + +static mp_obj_t finalize_peek_detach_helper(mp_obj_t self_in, bool detach) { + mp_obj_finalize_t *self = MP_OBJ_TO_PTR(self_in); + if (self->base.obj_weak_ref == mp_const_none) { + return mp_const_none; + } + mp_obj_t tuple[4] = { + MP_OBJ_FROM_PTR(WEAK_REFERENCE_TO_HEAP_PTR(self->base.obj_weak_ref)), + self->base.callback, + mp_obj_new_tuple(self->n_args, self->args), + mp_obj_dict_make_new(&mp_type_dict, 0, self->n_kw, self->args + self->n_args), + }; + if (detach) { + ref___del__(self_in); + } + return mp_obj_new_tuple(MP_ARRAY_SIZE(tuple), tuple); +} + +static mp_obj_t finalize_peek(mp_obj_t self_in) { + return finalize_peek_detach_helper(self_in, false); +} +static MP_DEFINE_CONST_FUN_OBJ_1(finalize_peek_obj, finalize_peek); + +static mp_obj_t finalize_detach(mp_obj_t self_in) { + return finalize_peek_detach_helper(self_in, true); +} +static MP_DEFINE_CONST_FUN_OBJ_1(finalize_detach_obj, finalize_detach); + +static void mp_obj_finalize_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // Store/delete attribute, unsupported. + return; + } + + if (attr == MP_QSTR_alive) { + mp_obj_finalize_t *self = MP_OBJ_TO_PTR(self_in); + dest[0] = mp_obj_new_bool(self->base.obj_weak_ref != mp_const_none); + return; + } else if (attr == MP_QSTR_peek) { + dest[0] = MP_OBJ_FROM_PTR(&finalize_peek_obj); + dest[1] = self_in; + } else if (attr == MP_QSTR_detach) { + dest[0] = MP_OBJ_FROM_PTR(&finalize_detach_obj); + dest[1] = self_in; + } +} + +static const mp_rom_map_elem_t ref_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ref___del___obj) }, +}; +static MP_DEFINE_CONST_DICT(ref_locals_dict, ref_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + mp_type_ref, + MP_QSTR_ref, + MP_TYPE_FLAG_NONE, + make_new, mp_obj_ref_make_new, + call, mp_obj_ref_call, + locals_dict, &ref_locals_dict + ); + +static MP_DEFINE_CONST_OBJ_TYPE( + mp_type_finalize, + MP_QSTR_finalize, + MP_TYPE_FLAG_NONE, + make_new, mp_obj_ref_make_new, + call, mp_obj_ref_call, + attr, mp_obj_finalize_attr + ); + +static const mp_rom_map_elem_t mp_module_weakref_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_weakref) }, + { MP_ROM_QSTR(MP_QSTR_ref), MP_ROM_PTR(&mp_type_ref) }, + { MP_ROM_QSTR(MP_QSTR_finalize), MP_ROM_PTR(&mp_type_finalize) }, +}; +static MP_DEFINE_CONST_DICT(mp_module_weakref_globals, mp_module_weakref_globals_table); + +const mp_obj_module_t mp_module_weakref = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_weakref_globals, +}; + +MP_REGISTER_ROOT_POINTER(mp_map_t mp_weakref_map); +MP_REGISTER_MODULE(MP_QSTR_weakref, mp_module_weakref); + +#endif // MICROPY_PY_WEAKREF diff --git a/py/mpconfig.h b/py/mpconfig.h index d0150ec7b99..0951651e7d6 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1914,6 +1914,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_THREAD_RECURSIVE_MUTEX (MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL) #endif +// Whether to provide the "weakref" module. +#ifndef MICROPY_PY_WEAKREF +#define MICROPY_PY_WEAKREF (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + // Extended modules #ifndef MICROPY_PY_ASYNCIO diff --git a/py/mpstate.h b/py/mpstate.h index 325c1221752..32d1adb13ed 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -107,6 +107,9 @@ typedef struct _mp_state_mem_area_t { #if MICROPY_ENABLE_FINALISER byte *gc_finaliser_table_start; #endif + #if MICROPY_PY_WEAKREF + byte *gc_weakref_table_start; + #endif byte *gc_pool_start; byte *gc_pool_end; diff --git a/py/py.cmake b/py/py.cmake index 8b3c857ef48..c2efab556c4 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -54,6 +54,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/modsys.c ${MICROPY_PY_DIR}/modthread.c ${MICROPY_PY_DIR}/moderrno.c + ${MICROPY_PY_DIR}/modweakref.c ${MICROPY_PY_DIR}/mpprint.c ${MICROPY_PY_DIR}/mpstate.c ${MICROPY_PY_DIR}/mpz.c diff --git a/py/py.mk b/py/py.mk index a8b50b8d24b..932c47ef177 100644 --- a/py/py.mk +++ b/py/py.mk @@ -204,6 +204,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ modsys.o \ moderrno.o \ modthread.o \ + modweakref.o \ vm.o \ bc.o \ showbc.o \ diff --git a/py/runtime.c b/py/runtime.c index d35cf4025f7..618e9b5ae41 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -179,6 +179,10 @@ void mp_init(void) { MP_STATE_VM(usbd) = MP_OBJ_NULL; #endif + #if MICROPY_PY_WEAKREF + mp_map_init(&MP_STATE_VM(mp_weakref_map), 0); + #endif + #if MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); #endif diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 73393e68b63..1c3fe1558f2 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -75,7 +75,7 @@ json machine marshal math os platform random re select socket string struct sys termios time tls -uctypes vfs websocket +uctypes vfs weakref websocket me micropython machine marshal math From c91d09a00dc047bba4a5215bab50dea54ea82138 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 13 Feb 2026 01:11:00 +1100 Subject: [PATCH 1880/2098] tests/basics: Add tests for weakref.ref and weakref.finalize. Signed-off-by: Damien George --- tests/basics/weakref_finalize_basic.py | 58 ++++++++++++++++++ tests/basics/weakref_finalize_collect.py | 75 +++++++++++++++++++++++ tests/basics/weakref_multiple_refs.py | 34 ++++++++++ tests/basics/weakref_multiple_refs.py.exp | 6 ++ tests/basics/weakref_ref_basic.py | 14 +++++ tests/basics/weakref_ref_collect.py | 68 ++++++++++++++++++++ tests/run-tests.py | 2 + 7 files changed, 257 insertions(+) create mode 100644 tests/basics/weakref_finalize_basic.py create mode 100644 tests/basics/weakref_finalize_collect.py create mode 100644 tests/basics/weakref_multiple_refs.py create mode 100644 tests/basics/weakref_multiple_refs.py.exp create mode 100644 tests/basics/weakref_ref_basic.py create mode 100644 tests/basics/weakref_ref_collect.py diff --git a/tests/basics/weakref_finalize_basic.py b/tests/basics/weakref_finalize_basic.py new file mode 100644 index 00000000000..792cffacb13 --- /dev/null +++ b/tests/basics/weakref_finalize_basic.py @@ -0,0 +1,58 @@ +# Test weakref.finalize() functionality that doesn't require gc.collect(). + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +# Cannot reference non-heap objects. +for value in (None, False, True, Ellipsis, 0, "", ()): + try: + weakref.finalize(value, lambda: None) + except TypeError: + print(value, "TypeError") + + +# Convert (obj, func, args, kwargs) so CPython and MicroPython have a chance to match. +def convert_4_tuple(values): + if values is None: + return None + return (type(values[0]).__name__, type(values[1]), values[2], values[3]) + + +class A: + def __str__(self): + return "
" + + +print("test alive, peek, detach") +a = A() +f = weakref.finalize(a, lambda: None, 1, 2, kwarg=3) +print("alive", f.alive) +print("peek", convert_4_tuple(f.peek())) +print("detach", convert_4_tuple(f.detach())) +print("alive", f.alive) +print("peek", convert_4_tuple(f.peek())) +print("detach", convert_4_tuple(f.detach())) +print("call", f()) +a = None + +print("test alive, peek, call") +a = A() +f = weakref.finalize(a, lambda *args, **kwargs: (args, kwargs), 1, 2, kwarg=3) +print("alive", f.alive) +print("peek", convert_4_tuple(f.peek())) +print("call", f()) +print("alive", f.alive) +print("peek", convert_4_tuple(f.peek())) +print("call", f()) +print("detach", convert_4_tuple(f.detach())) + +print("test call which raises exception") +a = A() +f = weakref.finalize(a, lambda: 1 / 0) +try: + f() +except ZeroDivisionError as er: + print("call ZeroDivisionError") diff --git a/tests/basics/weakref_finalize_collect.py b/tests/basics/weakref_finalize_collect.py new file mode 100644 index 00000000000..d364be9f62d --- /dev/null +++ b/tests/basics/weakref_finalize_collect.py @@ -0,0 +1,75 @@ +# Test weakref.finalize() functionality requiring gc.collect(). + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +# gc module must be available if weakref is. +import gc + + +class A: + def __str__(self): + return "" + + +def callback(*args, **kwargs): + print("callback({}, {})".format(args, kwargs)) + return 42 + + +def test(): + print("test basic use of finalize() with a simple callback") + a = A() + f = weakref.finalize(a, callback) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print("alive", f.alive) + print("peek", f.peek()) + print("detach", f.detach()) + print("call", f()) + + print("test that a callback is passed the correct values") + a = A() + f = weakref.finalize(a, callback, 1, 2, kwarg=3) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print("alive", f.alive) + print("peek", f.peek()) + print("detach", f.detach()) + print("call", f()) + + print("test that calling the finalizer cancels the finalizer") + a = A() + f = weakref.finalize(a, callback) + print(f()) + print(a) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + + print("test that calling detach cancels the finalizer") + a = A() + f = weakref.finalize(a, callback) + print(len(f.detach())) + print(a) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + + print("test that finalize does not get collected before its ref does") + a = A() + weakref.finalize(a, callback) + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print("free a") + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + + +test() diff --git a/tests/basics/weakref_multiple_refs.py b/tests/basics/weakref_multiple_refs.py new file mode 100644 index 00000000000..373cd6bb691 --- /dev/null +++ b/tests/basics/weakref_multiple_refs.py @@ -0,0 +1,34 @@ +# Test weakref when multiple weak references are active. +# +# This test has different output to CPython due to the order that MicroPython +# executes weak reference callbacks. + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +# gc module must be available if weakref is. +import gc + + +class A: + def __str__(self): + return "" + + +def test(): + print("test having multiple ref and finalize objects referencing the same thing") + a = A() + r1 = weakref.ref(a, lambda r: print("ref1", r())) + f1 = weakref.finalize(a, lambda: print("finalize1")) + r2 = weakref.ref(a, lambda r: print("ref2", r())) + f2 = weakref.finalize(a, lambda: print("finalize2")) + print(r1(), f1.alive, r2(), f2.alive) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + + +test() diff --git a/tests/basics/weakref_multiple_refs.py.exp b/tests/basics/weakref_multiple_refs.py.exp new file mode 100644 index 00000000000..1f2d366f776 --- /dev/null +++ b/tests/basics/weakref_multiple_refs.py.exp @@ -0,0 +1,6 @@ +test having multiple ref and finalize objects referencing the same thing + True True +finalize2 +finalize1 +ref2 None +ref1 None diff --git a/tests/basics/weakref_ref_basic.py b/tests/basics/weakref_ref_basic.py new file mode 100644 index 00000000000..058045f6c33 --- /dev/null +++ b/tests/basics/weakref_ref_basic.py @@ -0,0 +1,14 @@ +# Test weakref.ref() functionality that doesn't require gc.collect(). + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +# Cannot reference non-heap objects. +for value in (None, False, True, Ellipsis, 0, "", ()): + try: + weakref.ref(value) + except TypeError: + print(value, "TypeError") diff --git a/tests/basics/weakref_ref_collect.py b/tests/basics/weakref_ref_collect.py new file mode 100644 index 00000000000..8b2d86fb3c7 --- /dev/null +++ b/tests/basics/weakref_ref_collect.py @@ -0,0 +1,68 @@ +# Test weakref.ref() functionality requiring gc.collect(). + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +# gc module must be available if weakref is. +import gc + +# Cannot reference non-heap objects. +for value in (None, False, True, Ellipsis, 0, "", ()): + try: + weakref.ref(value) + except TypeError: + print(value, "TypeError") + + +class A: + def __str__(self): + return "" + + +def callback(r): + print("callback", r()) + + +def test(): + print("test basic use of ref() with only one argument") + a = A() + r = weakref.ref(a) + print(r()) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print(r()) + + print("test use of ref() with a callback") + a = A() + r = weakref.ref(a, callback) + print(r()) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print(r()) + + print("test when weakref gets collected before the object it refs") + a = A() + r = weakref.ref(a, callback) + print(r()) + r = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + a = None + + print("test a double reference") + a = A() + r1 = weakref.ref(a, callback) + r2 = weakref.ref(a, callback) + print(r1(), r2()) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print(r1(), r2()) + + +test() diff --git a/tests/run-tests.py b/tests/run-tests.py index 59a292ed4b8..cb4c9ab0d7c 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -158,6 +158,8 @@ "webassembly": ( "basics/string_format_modulo.py", # can't print nulls to stdout "basics/string_strip.py", # can't print nulls to stdout + "basics/weakref_ref_collect.py", # requires custom test due to GC behaviour + "basics/weakref_finalize_collect.py", # requires custom test due to GC behaviour "extmod/asyncio_basic2.py", "extmod/asyncio_cancel_self.py", "extmod/asyncio_current_task.py", From 2cca3481f2d3d9c1bae29417d274f6326dfe5746 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 16 Feb 2026 15:16:57 +1100 Subject: [PATCH 1881/2098] tests/basics: Add test for weakref having exception in callback. Needs a native exp file because native code doesn't print line numbers in the traceback. Signed-off-by: Damien George --- tests/basics/weakref_callback_exception.py | 42 +++++++++++++++++++ .../basics/weakref_callback_exception.py.exp | 12 ++++++ .../weakref_callback_exception.py.native.exp | 8 ++++ tests/run-tests.py | 2 + 4 files changed, 64 insertions(+) create mode 100644 tests/basics/weakref_callback_exception.py create mode 100644 tests/basics/weakref_callback_exception.py.exp create mode 100644 tests/basics/weakref_callback_exception.py.native.exp diff --git a/tests/basics/weakref_callback_exception.py b/tests/basics/weakref_callback_exception.py new file mode 100644 index 00000000000..df8e5129803 --- /dev/null +++ b/tests/basics/weakref_callback_exception.py @@ -0,0 +1,42 @@ +# Test weakref ref/finalize raising an exception within the callback. +# +# This test has different output to CPython due to the way that MicroPython +# prints the exception. + +try: + import weakref +except ImportError: + print("SKIP") + raise SystemExit + +import gc + + +class A: + def __str__(self): + return "" + + +def callback(*args): + raise ValueError("weakref callback", args) + + +def test(): + print("test ref with exception in the callback") + a = A() + r = weakref.ref(a, callback) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print("collect done") + + print("test finalize with exception in the callback") + a = A() + weakref.finalize(a, callback) + a = None + clean_the_stack = [0, 0, 0, 0] + gc.collect() + print("collect done") + + +test() diff --git a/tests/basics/weakref_callback_exception.py.exp b/tests/basics/weakref_callback_exception.py.exp new file mode 100644 index 00000000000..c2f7796310c --- /dev/null +++ b/tests/basics/weakref_callback_exception.py.exp @@ -0,0 +1,12 @@ +test ref with exception in the callback +Unhandled exception in weakref callback: +Traceback (most recent call last): + File "\.\+weakref_callback_exception.py", line 21, in callback +ValueError: ('weakref callback', (,)) +collect done +test finalize with exception in the callback +Unhandled exception in weakref callback: +Traceback (most recent call last): + File "\.\+weakref_callback_exception.py", line 21, in callback +ValueError: ('weakref callback', ()) +collect done diff --git a/tests/basics/weakref_callback_exception.py.native.exp b/tests/basics/weakref_callback_exception.py.native.exp new file mode 100644 index 00000000000..a06a35c3b7e --- /dev/null +++ b/tests/basics/weakref_callback_exception.py.native.exp @@ -0,0 +1,8 @@ +test ref with exception in the callback +Unhandled exception in weakref callback: +ValueError: ('weakref callback', (,)) +collect done +test finalize with exception in the callback +Unhandled exception in weakref callback: +ValueError: ('weakref callback', ()) +collect done diff --git a/tests/run-tests.py b/tests/run-tests.py index cb4c9ab0d7c..153424f65f7 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -158,6 +158,7 @@ "webassembly": ( "basics/string_format_modulo.py", # can't print nulls to stdout "basics/string_strip.py", # can't print nulls to stdout + "basics/weakref_callback_exception.py", # has different exception printing output "basics/weakref_ref_collect.py", # requires custom test due to GC behaviour "basics/weakref_finalize_collect.py", # requires custom test due to GC behaviour "extmod/asyncio_basic2.py", @@ -435,6 +436,7 @@ def detect_target_wiring_script(pyb, args): "micropython/meminfo.py", "basics/bytes_compare3.py", "basics/builtin_help.py", + "basics/weakref_callback_exception.py", "misc/sys_settrace_cov.py", "net_inet/tls_text_errors.py", "thread/thread_exc2.py", From f83f363bb8f59a758334e37648bb09b8cae707d3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 15 Feb 2026 23:06:47 +1100 Subject: [PATCH 1882/2098] webassembly/Makefile: Add test//% target. Following a69425b533932bbcac0ef463f9e27f79ff2150e3, this is a convenient way to run a subset of tests. Signed-off-by: Damien George --- ports/webassembly/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/webassembly/Makefile b/ports/webassembly/Makefile index 9a673b757b2..3dbe2418878 100644 --- a/ports/webassembly/Makefile +++ b/ports/webassembly/Makefile @@ -130,7 +130,7 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) ################################################################################ # Main targets. -.PHONY: all repl min test test_min +.PHONY: all repl min test test//% test_min all: $(BUILD)/micropython.mjs @@ -150,6 +150,9 @@ min: $(BUILD)/micropython.min.mjs test: $(BUILD)/micropython.mjs $(TOP)/tests/run-tests.py cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py -t webassembly +test//%: $(BUILD)/micropython.mjs $(TOP)/tests/run-tests.py + cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py -t webassembly -i "$*" + test_min: $(BUILD)/micropython.min.mjs $(TOP)/tests/run-tests.py cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py -t webassembly From 6f96d260e655ea87107a916ec8b48d67b5d55195 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 15 Feb 2026 23:07:39 +1100 Subject: [PATCH 1883/2098] webassembly/variants/pyscript: Enable weakref module and add tests. The webassembly port needs some additional weakref tests due to the fact that garbage collection only happens when Python execution finishes and JavaScript resumes. The `tests/ports/webassembly/heap_expand.py` expected output also needs to be updated because the amount of GC heap got smaller (weakref WTB takes some of the available RAM). Signed-off-by: Damien George --- .../variants/pyscript/mpconfigvariant.h | 1 + tests/basics/weakref_finalize_collect.py | 1 + tests/basics/weakref_multiple_refs.py | 2 + tests/basics/weakref_ref_collect.py | 1 + tests/ports/webassembly/heap_expand.mjs.exp | 46 +++++----- .../webassembly/weakref_finalize_collect.mjs | 86 +++++++++++++++++++ .../weakref_finalize_collect.mjs.exp | 28 ++++++ .../ports/webassembly/weakref_ref_collect.mjs | 69 +++++++++++++++ .../webassembly/weakref_ref_collect.mjs.exp | 16 ++++ 9 files changed, 227 insertions(+), 23 deletions(-) create mode 100644 tests/ports/webassembly/weakref_finalize_collect.mjs create mode 100644 tests/ports/webassembly/weakref_finalize_collect.mjs.exp create mode 100644 tests/ports/webassembly/weakref_ref_collect.mjs create mode 100644 tests/ports/webassembly/weakref_ref_collect.mjs.exp diff --git a/ports/webassembly/variants/pyscript/mpconfigvariant.h b/ports/webassembly/variants/pyscript/mpconfigvariant.h index ed8e8128035..0b77efc4b32 100644 --- a/ports/webassembly/variants/pyscript/mpconfigvariant.h +++ b/ports/webassembly/variants/pyscript/mpconfigvariant.h @@ -1,3 +1,4 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) #define MICROPY_GC_SPLIT_HEAP (1) #define MICROPY_GC_SPLIT_HEAP_AUTO (1) +#define MICROPY_PY_WEAKREF (1) diff --git a/tests/basics/weakref_finalize_collect.py b/tests/basics/weakref_finalize_collect.py index d364be9f62d..f6e7c14843e 100644 --- a/tests/basics/weakref_finalize_collect.py +++ b/tests/basics/weakref_finalize_collect.py @@ -1,4 +1,5 @@ # Test weakref.finalize() functionality requiring gc.collect(). +# Should be kept in sync with tests/ports/webassembly/weakref_finalize_collect.py. try: import weakref diff --git a/tests/basics/weakref_multiple_refs.py b/tests/basics/weakref_multiple_refs.py index 373cd6bb691..400e03a17c7 100644 --- a/tests/basics/weakref_multiple_refs.py +++ b/tests/basics/weakref_multiple_refs.py @@ -19,6 +19,8 @@ def __str__(self): def test(): + global r1, r2 # needed for webassembly port to retain references to them + print("test having multiple ref and finalize objects referencing the same thing") a = A() r1 = weakref.ref(a, lambda r: print("ref1", r())) diff --git a/tests/basics/weakref_ref_collect.py b/tests/basics/weakref_ref_collect.py index 8b2d86fb3c7..0e8db977d77 100644 --- a/tests/basics/weakref_ref_collect.py +++ b/tests/basics/weakref_ref_collect.py @@ -1,4 +1,5 @@ # Test weakref.ref() functionality requiring gc.collect(). +# Should be kept in sync with tests/ports/webassembly/weakref_ref_collect.py. try: import weakref diff --git a/tests/ports/webassembly/heap_expand.mjs.exp b/tests/ports/webassembly/heap_expand.mjs.exp index 67ebe98e7fe..4161fc7eaed 100644 --- a/tests/ports/webassembly/heap_expand.mjs.exp +++ b/tests/ports/webassembly/heap_expand.mjs.exp @@ -1,27 +1,27 @@ -135241312 -135241280 -135241248 -135241216 -135241168 -135241120 -135241040 -135240896 -135240592 -135240064 -135239024 -135236960 +135233568 +135233536 +135233504 +135233472 +135233424 +135233376 +135233296 +135233152 135232848 -135224640 -135208240 -135175456 -135109840 -134978752 -134716592 -135216800 -136217168 -138217984 -142219568 -150222816 +135232320 +135231280 +135229216 +135225104 +135216896 +135200496 +135167712 +135102096 +134971008 +134708848 +135201312 +136186256 +138156160 +142095984 +149975648 1 2 4 diff --git a/tests/ports/webassembly/weakref_finalize_collect.mjs b/tests/ports/webassembly/weakref_finalize_collect.mjs new file mode 100644 index 00000000000..1e0bc951350 --- /dev/null +++ b/tests/ports/webassembly/weakref_finalize_collect.mjs @@ -0,0 +1,86 @@ +// Test weakref.finalize() functionality requiring gc.collect(). +// Should be kept in sync with tests/basics/weakref_finalize_collect.py. +// +// This needs custom testing on the webassembly port since the GC can only +// run when Python code returns to JavaScript. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +// Set up. +mp.runPython(` +import gc, weakref + +class A: + def __str__(self): + return "" + +def callback(*args, **kwargs): + print("callback({}, {})".format(args, kwargs)) + return 42 +`); + +console.log("test basic use of finalize() with a simple callback"); +mp.runPython(` + a = A() + f = weakref.finalize(a, callback) + a = None + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print("alive", f.alive) + print("peek", f.peek()) + print("detach", f.detach()) + print("call", f()) +`); + +console.log("test that a callback is passed the correct values"); +mp.runPython(` + a = A() + f = weakref.finalize(a, callback, 1, 2, kwarg=3) + a = None + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print("alive", f.alive) + print("peek", f.peek()) + print("detach", f.detach()) + print("call", f()) +`); + +console.log("test that calling the finalizer cancels the finalizer"); +mp.runPython(` + a = A() + f = weakref.finalize(a, callback) + print(f()) + print(a) + a = None + gc.collect() +`); +console.log("(outside Python)"); + +console.log("test that calling detach cancels the finalizer"); +mp.runPython(` + a = A() + f = weakref.finalize(a, callback) + print(len(f.detach())) + print(a) + a = None + gc.collect() +`); +console.log("(outside Python)"); + +console.log("test that finalize does not get collected before its ref does"); +mp.runPython(` + a = A() + weakref.finalize(a, callback) + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print("free a") + a = None + gc.collect() +`); +console.log("(outside Python)"); diff --git a/tests/ports/webassembly/weakref_finalize_collect.mjs.exp b/tests/ports/webassembly/weakref_finalize_collect.mjs.exp new file mode 100644 index 00000000000..e8087a4ae9b --- /dev/null +++ b/tests/ports/webassembly/weakref_finalize_collect.mjs.exp @@ -0,0 +1,28 @@ +test basic use of finalize() with a simple callback +callback((), {}) +(outside Python) +alive False +peek None +detach None +call None +test that a callback is passed the correct values +callback((1, 2), {'kwarg': 3}) +(outside Python) +alive False +peek None +detach None +call None +test that calling the finalizer cancels the finalizer +callback((), {}) +42 + +(outside Python) +test that calling detach cancels the finalizer +4 + +(outside Python) +test that finalize does not get collected before its ref does +(outside Python) +free a +callback((), {}) +(outside Python) diff --git a/tests/ports/webassembly/weakref_ref_collect.mjs b/tests/ports/webassembly/weakref_ref_collect.mjs new file mode 100644 index 00000000000..546a851f0ac --- /dev/null +++ b/tests/ports/webassembly/weakref_ref_collect.mjs @@ -0,0 +1,69 @@ +// Test weakref.ref() functionality requiring gc.collect(). +// Should be kept in sync with tests/basics/weakref_ref_collect.py. +// +// This needs custom testing on the webassembly port since the GC can only +// run when Python code returns to JavaScript. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +// Set up. +mp.runPython(` +import gc, weakref + +class A: + def __str__(self): + return "" + +def callback(r): + print("callback", r()) +`); + +console.log("test basic use of ref() with only one argument"); +mp.runPython(` + a = A() + r = weakref.ref(a) + print(r()) + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print(r()) + a = None + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print(r()) +`); + +console.log("test use of ref() with a callback"); +mp.runPython(` + a = A() + r = weakref.ref(a, callback) + print(r()) + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print(r()) + a = None + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + print(r()) +`); + +console.log("test when weakref gets collected before the object it refs"); +mp.runPython(` + a = A() + r = weakref.ref(a, callback) + print(r()) + r = None + gc.collect() +`); +console.log("(outside Python)"); +mp.runPython(` + a = None + gc.collect() +`); diff --git a/tests/ports/webassembly/weakref_ref_collect.mjs.exp b/tests/ports/webassembly/weakref_ref_collect.mjs.exp new file mode 100644 index 00000000000..f903d417028 --- /dev/null +++ b/tests/ports/webassembly/weakref_ref_collect.mjs.exp @@ -0,0 +1,16 @@ +test basic use of ref() with only one argument + +(outside Python) + +(outside Python) +None +test use of ref() with a callback + +(outside Python) + +callback None +(outside Python) +None +test when weakref gets collected before the object it refs + +(outside Python) From 44d8f70a8ed729f677c264fe16a824cce61d9acd Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 16 Feb 2026 13:22:09 +1100 Subject: [PATCH 1884/2098] docs/library/weakref: Add documentation for weakref module. Signed-off-by: Damien George --- docs/library/index.rst | 1 + docs/library/weakref.rst | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/library/weakref.rst diff --git a/docs/library/index.rst b/docs/library/index.rst index 04598145276..fca9cbb5371 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -82,6 +82,7 @@ library. struct.rst sys.rst time.rst + weakref.rst zlib.rst _thread.rst diff --git a/docs/library/weakref.rst b/docs/library/weakref.rst new file mode 100644 index 00000000000..ffebc91277b --- /dev/null +++ b/docs/library/weakref.rst @@ -0,0 +1,78 @@ +:mod:`weakref` -- Python object lifetime management +=================================================== + +.. module:: weakref + :synopsis: Create weak references to Python objects + +|see_cpython_module| :mod:`python:weakref`. + +This module allows creation of weak references to Python objects. A weak reference +is a non-traceable reference to a heap-allocated Python object, so the garbage +collector can still reclaim the object even though the weak reference refers to it. + +Python callbacks can be registered to be called when an object is reclaimed by the +garbage collector. This provides a safe way to clean up when objects are no longer +needed. + +**Availability:** the weakref module requires ``MICROPY_PY_WEAKREF`` to be enabled +at compile time. It is enabled on the unix coverage variant and the webassembly +pyscript variant. + +ref objects +----------- + +A ref object is the simplest way to make a weak reference. + +.. class:: ref(object [, callback], /) + + Return a weak reference to the given *object*. + + If *callback* is given and is not ``None`` then, when *object* is reclaimed + by the garbage collector and if the weak reference object is still alive, the + *callback* will be called. The *callback* will be passed the weak reference + object as its single argument. + +.. method:: ref.__call__() + + Calling the weak reference object will return its referenced object if that + object is still alive. Otherwise ``None`` will be returned. + +finalize objects +---------------- + +A finalize object is an extended version of a ref object that is more convenient to +use, and allows more control over the callback. + +.. class:: finalize(object, callback, /, *args, **kwargs) + + Return a weak reference to the given *object*. In contrast to *weakref.ref* + objects, finalize objects are held onto internally and will not be collected until + *object* is collected. + + A finalize object starts off alive. It transitions to the dead state when the + finalize object is called, either explicitly or when *object* is collected. It also + transitions to dead if the `finalize.detach()` method is called. + + When *object* is reclaimed by the garbage collector (or the finalize object is + explicitly called by user code) and the finalize object is still in the alive state, + the *callback* will be called. The *callback* will be passed arguments as: + ``callback(*args, **kwargs)``. + +.. method:: finalize.__call__() + + If the finalize object is alive then it transitions to the dead state and returns + the value of ``callback(*args, **kwargs)``. Otherwise ``None`` will be returned. + +.. method:: finalize.alive + + Read-only boolean attribute that indicates if the finalizer is in the alive state. + +.. method:: finalize.peek() + + If the finalize object is alive then return ``(object, callback, args, kwargs)``. + Otherwise return ``None``. + +.. method:: finalize.detach() + + If the finalize object is alive then it transitions to the dead state and returns + ``(object, callback, args, kwargs)``. Otherwise ``None`` will be returned. From 5c00edcee28491b6961a5db6aa0e5aa856664de4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 16 Feb 2026 18:46:31 +1100 Subject: [PATCH 1885/2098] tools/ci.sh: Increase qemu_arm test run timeout. It takes longer now that weakref is enabled in the coverage build. Signed-off-by: Damien George --- tools/ci.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 0532c1c7570..056f6a6102d 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -938,9 +938,9 @@ function ci_unix_qemu_arm_build { function ci_unix_qemu_arm_run_tests { # Issues with ARM tests: - # - thread/stress_aes.py takes around 70 seconds + # - thread/stress_aes.py takes around 90 seconds file ./ports/unix/build-coverage/micropython - (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=90 ./run-tests.py) + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython MICROPY_TEST_TIMEOUT=120 ./run-tests.py) } function ci_unix_qemu_riscv64_setup { From 85e8f6189e846d76aded0317089f1736ca1c9d5f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Mar 2026 23:38:44 +1100 Subject: [PATCH 1886/2098] lib/micropython-lib: Update submodule to latest. This brings in: - sdcard: Send stop bit after multi-block read/write - sdcard: Compute CRC7 for all SPI commands - sdcard: Add read/write speed test to sdtest - lsm6dsox: Add pedometer support - lsm6dsox: Add pedometer example code - unix-ffi/re: Handle PCRE2_UNSET in group and groups methods - unix-ffi/re: Add tests for empty string match in ffi regex - unix-ffi/machine: Retrieve a unique identifier if one is known - senml/docs: Correct capitalization of 'MicroPython' - unix-ffi/_libc: Extend FreeBSD libc versions range - string: Convert string module to package and import templatelib Signed-off-by: Damien George --- lib/micropython-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/micropython-lib b/lib/micropython-lib index 6ae440a8a14..8380c7bb8f9 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit 6ae440a8a144233e6e703f6759b7e7a0afaa37a4 +Subproject commit 8380c7bb8f9e5e5260e9539156742925e00366b2 From bce854928d49cee88a722630e2df1a5d2517cd2c Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Mar 2026 23:40:05 +1100 Subject: [PATCH 1887/2098] tests/feature_check/tstring.py: Remove check for string.templatelib. If a port enables t-strings then it is required to have the `string.templatelib` package (at least to run the tests). That's automatically the case if `MICROPY_PY_TSTRINGS` is enabled. If a port freezes in the micropython-lib `string` extension package then the latest version of this package will include the built-in `string.templatelib` classes. So the feature check for t-strings no longer needs to check if they are available. Signed-off-by: Damien George --- tests/feature_check/tstring.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/feature_check/tstring.py b/tests/feature_check/tstring.py index e1d428b4e7b..05322b2ae61 100644 --- a/tests/feature_check/tstring.py +++ b/tests/feature_check/tstring.py @@ -1,8 +1,5 @@ # check whether t-strings (PEP-750) are supported -# TODO remove this check when micropython-lib's string extends ustring -from string.templatelib import Template, Interpolation - a = 1 t = t"a={a}" print("tstring") From 93201ff2d12211ca016a4510d737a5c544a3599a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 24 Mar 2026 23:59:17 +1100 Subject: [PATCH 1888/2098] lib/cyw43-driver: Update driver to latest version v1.1.1. Includes a fix to STA teardown to deinit tcpip and clear itf_state. Signed-off-by: Damien George --- lib/cyw43-driver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cyw43-driver b/lib/cyw43-driver index dd7568229f3..055d64274b0 160000 --- a/lib/cyw43-driver +++ b/lib/cyw43-driver @@ -1 +1 @@ -Subproject commit dd7568229f3bf7a37737b9e1ef250c26efe75b23 +Subproject commit 055d64274b014dd7b1c2fc94d26e8a18face7124 From ac48088749853d52124f7175eb9f91e979a17242 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Wed, 1 May 2024 14:58:37 +1000 Subject: [PATCH 1889/2098] rp2/boards/SEEED_XIAO_RP2040: Add XIAO RP2040 board definition. Signed-off-by: Matt Trentini --- ports/rp2/boards/SEEED_XIAO_RP2040/board.json | 20 ++++++++++++++++++ .../SEEED_XIAO_RP2040/mpconfigboard.cmake | 3 +++ .../boards/SEEED_XIAO_RP2040/mpconfigboard.h | 19 +++++++++++++++++ ports/rp2/boards/SEEED_XIAO_RP2040/pins.csv | 21 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2040/board.json create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.cmake create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.h create mode 100644 ports/rp2/boards/SEEED_XIAO_RP2040/pins.csv diff --git a/ports/rp2/boards/SEEED_XIAO_RP2040/board.json b/ports/rp2/boards/SEEED_XIAO_RP2040/board.json new file mode 100644 index 00000000000..ae7e31be1d5 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2040/board.json @@ -0,0 +1,20 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Dual-core", + "External Flash", + "RGB LED", + "USB", + "USB-C" + ], + "images": [ + "seeedstudio_xiao_rp2040.jpg" + ], + "mcu": "rp2040", + "product": "XIAO RP2040", + "url": "https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html", + "vendor": "Seeed Studio" +} diff --git a/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.cmake b/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.cmake new file mode 100644 index 00000000000..a069458867d --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.cmake @@ -0,0 +1,3 @@ +# cmake file for Seeed Studio XIAO RP204 + +set(PICO_BOARD "seeed_xiao_rp2040") diff --git a/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.h b/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.h new file mode 100644 index 00000000000..4291f8c00c3 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2040/mpconfigboard.h @@ -0,0 +1,19 @@ +// https://wiki.seeedstudio.com/XIAO-RP2040/ + +#define MICROPY_HW_BOARD_NAME "Seeed Studio XIAO RP2040" +#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) + +// No VID/PID defined for the Seeed XIAO RP2040 +// #define MICROPY_HW_USB_VID (0x) +// #define MICROPY_HW_USB_PID (0x) + +// I2C0 +#define MICROPY_HW_I2C0_SCL (7) +#define MICROPY_HW_I2C0_SDA (6) + +// SPI0 +#define MICROPY_HW_SPI0_SCK (2) +#define MICROPY_HW_SPI0_MOSI (3) +#define MICROPY_HW_SPI0_MISO (4) + +// UART0 is, by default, assigned the correct pins (TX=0, RX=1) diff --git a/ports/rp2/boards/SEEED_XIAO_RP2040/pins.csv b/ports/rp2/boards/SEEED_XIAO_RP2040/pins.csv new file mode 100644 index 00000000000..7ae0ca50bb0 --- /dev/null +++ b/ports/rp2/boards/SEEED_XIAO_RP2040/pins.csv @@ -0,0 +1,21 @@ +D0,GPIO26 +D1,GPIO27 +D2,GPIO28 +D3,GPIO29 +D4,GPIO6 +D5,GPIO7 +D6,GPIO0 +D7,GPIO1 +D8,GPIO2 +D9,GPIO4 +D10,GPIO3 +A0,GPIO26 +A1,GPIO27 +A2,GPIO28 +A3,GPIO29 +NEOPIXEL_POWER,GPIO11 +NEOPIXEL,GPIO12 +LED_R,GPIO17 +LED_G,GPIO16 +LED_B,GPIO25 +LED,GPIO25 From f4d2447174e2bd75551db6bdf58e08046d88a2b5 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 16 Mar 2026 15:16:15 -0600 Subject: [PATCH 1890/2098] esp32/boards/SPARKFUN_THINGPLUS_ESP32C5: Add SF Thing Plus ESP32-C5. Signed-off-by: Dryw Wade --- .../SPARKFUN_THINGPLUS_ESP32C5/board.json | 29 +++++++++++++++++++ .../SPARKFUN_THINGPLUS_ESP32C5/manifest.py | 2 ++ .../mpconfigboard.cmake | 13 +++++++++ .../mpconfigboard.h | 11 +++++++ .../SPARKFUN_THINGPLUS_ESP32C5/pins.csv | 18 ++++++++++++ .../sdkconfig.board | 2 ++ 6 files changed, 75 insertions(+) create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/board.json create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/manifest.py create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.h create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/pins.csv create mode 100644 ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/sdkconfig.board diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/board.json b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/board.json new file mode 100644 index 00000000000..2be28659b9e --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/board.json @@ -0,0 +1,29 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x2000" + }, + "docs": "", + "features": [ + "BLE", + "Battery Charging", + "External Flash", + "External RAM", + "Feather", + "JST-SH", + "RGB LED", + "USB-C", + "WiFi", + "microSD" + ], + "images": [ + "30678-Thing-Plus-ESP32-C5-Feature.jpg" + ], + "mcu": "esp32c5", + "product": "Thing Plus ESP32-C5", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-thing-plus-esp32-c5.html", + "vendor": "SparkFun" +} diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/manifest.py b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/manifest.py new file mode 100644 index 00000000000..73446ecac98 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +require("sdcard") diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.cmake b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.cmake new file mode 100644 index 00000000000..216cd664dbe --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.cmake @@ -0,0 +1,13 @@ +set(IDF_TARGET esp32c5) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.riscv + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/sdkconfig.free_ram + boards/sdkconfig.spiram + boards/SPARKFUN_THINGPLUS_ESP32C5/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.h b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.h new file mode 100644 index 00000000000..7c892399da9 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/mpconfigboard.h @@ -0,0 +1,11 @@ +// Board specific definitions for the SparkFun Thing Plus ESP32-C5. + +#define MICROPY_HW_BOARD_NAME "SparkFun Thing Plus ESP32-C5" +#define MICROPY_HW_MCU_NAME "ESP32C5" + +#define MICROPY_HW_I2C0_SCL (24) +#define MICROPY_HW_I2C0_SDA (23) + +#define MICROPY_HW_SPI1_SCK (10) +#define MICROPY_HW_SPI1_MOSI (8) +#define MICROPY_HW_SPI1_MISO (9) diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/pins.csv b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/pins.csv new file mode 100644 index 00000000000..aec57725e78 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/pins.csv @@ -0,0 +1,18 @@ +ALERT,GPIO0 +BAT_STAT,GPIO6 +SD_DET,GPIO7 +MOSI,GPIO8 +PICO,GPIO8 +MISO,GPIO9 +POCI,GPIO9 +SCK,GPIO10 +TX,GPIO11 +RX,GPIO12 +SDA,GPIO23 +SCL,GPIO24 +CS,GPIO25 +LP,GPIO26 +LED,GPIO27 +RGB_LED,GPIO27 +NEOPIXEL,GPIO27 +BUTTON,GPIO28 diff --git a/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/sdkconfig.board b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/sdkconfig.board new file mode 100644 index 00000000000..369330682f9 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_THINGPLUS_ESP32C5/sdkconfig.board @@ -0,0 +1,2 @@ +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y From 2dc2e30d98ee225070e990586526f8e43b3c95a2 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sat, 21 Mar 2026 10:17:08 +1100 Subject: [PATCH 1891/2098] esp32/boards/SEEED_XIAO_ESP32C6: Add new XIAO board definition. Signed-off-by: Matt Trentini Signed-off-by: Matt Trentini --- .../boards/SEEED_XIAO_ESP32C6/board.json | 24 +++++++++++++++++++ .../SEEED_XIAO_ESP32C6/mpconfigboard.cmake | 8 +++++++ .../boards/SEEED_XIAO_ESP32C6/mpconfigboard.h | 9 +++++++ .../esp32/boards/SEEED_XIAO_ESP32C6/pins.csv | 22 +++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 ports/esp32/boards/SEEED_XIAO_ESP32C6/board.json create mode 100644 ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.h create mode 100644 ports/esp32/boards/SEEED_XIAO_ESP32C6/pins.csv diff --git a/ports/esp32/boards/SEEED_XIAO_ESP32C6/board.json b/ports/esp32/boards/SEEED_XIAO_ESP32C6/board.json new file mode 100644 index 00000000000..81e15d4a3e6 --- /dev/null +++ b/ports/esp32/boards/SEEED_XIAO_ESP32C6/board.json @@ -0,0 +1,24 @@ +{ + "deploy": [ + "../deploy_nativeusb.md" + ], + "deploy_options": { + "flash_offset": "0" + }, + "docs": "", + "features": [ + "Battery Charging", + "BLE", + "External Flash", + "WiFi", + "USB", + "USB-C" + ], + "images": [ + "seeed_xiao_esp32c6.jpg" + ], + "mcu": "esp32c6", + "product": "XIAO ESP32C6", + "url": "https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html", + "vendor": "Seeed Studio" +} diff --git a/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.cmake b/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.cmake new file mode 100644 index 00000000000..48946f70945 --- /dev/null +++ b/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.cmake @@ -0,0 +1,8 @@ +set(IDF_TARGET esp32c6) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.riscv + boards/sdkconfig.c6 + boards/sdkconfig.ble +) diff --git a/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.h b/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.h new file mode 100644 index 00000000000..a85f1389986 --- /dev/null +++ b/ports/esp32/boards/SEEED_XIAO_ESP32C6/mpconfigboard.h @@ -0,0 +1,9 @@ +#define MICROPY_HW_BOARD_NAME "Seeed XIAO ESP32C6" +#define MICROPY_HW_MCU_NAME "ESP32C6" + +#define MICROPY_HW_I2C0_SCL (23) +#define MICROPY_HW_I2C0_SDA (22) + +#define MICROPY_HW_SPI1_MOSI (18) +#define MICROPY_HW_SPI1_MISO (20) +#define MICROPY_HW_SPI1_SCK (19) diff --git a/ports/esp32/boards/SEEED_XIAO_ESP32C6/pins.csv b/ports/esp32/boards/SEEED_XIAO_ESP32C6/pins.csv new file mode 100644 index 00000000000..aca4bd99489 --- /dev/null +++ b/ports/esp32/boards/SEEED_XIAO_ESP32C6/pins.csv @@ -0,0 +1,22 @@ +D0,GPIO0 +D1,GPIO1 +D2,GPIO2 +D3,GPIO21 +D4,GPIO22 +D5,GPIO23 +D6,GPIO16 +D7,GPIO17 +D8,GPIO19 +D9,GPIO20 +D10,GPIO18 +A0,GPIO0 +A1,GPIO1 +A2,GPIO2 +LED,GPIO15 +MTDO,GPIO7 +MTDI,GPIO5 +MTCK,GPIO6 +MTMS,GPIO4 +BOOT,GPIO9 +RF_SEL,GPIO14 +RF_POWER,GPIO3 From d41b8dc52e70e9652cd44125444b4a3101e9accf Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 31 Mar 2026 00:03:10 +1100 Subject: [PATCH 1892/2098] py/emitglue: Fix macro logic that selects cache flushing code. This fixes a regression made by 9e9da6cb0d794e85dbafcabd39972528afc15474. There are two issues here: 1. `MPY_FEATURE_ARCH` and `MP_NATIVE_ARCH_xxx` are not visible in this file because `py/persistentcode.h` is not included. 2. Even if they were visible the macro logic will not work because `MP_NATIVE_ARCH_xxx` are enum values and cannot be used in #if logic. What this means is that the first #if is always true, so there is no cache flushing on ARM (non-Thumb) or RISC-V targets. This breaks native code on, eg, ESP32-P4. The fix here aims to simplify the logic by using built-in compiler defines to select the target: - On an ARM Thumb target with `__ICACHE_PRESENT` enabled, it will flush the cache. - On an ARM (non-Thumb) target, it will prefer `__builtin___clear_cache()` if possible, otherwise it will use inline assembler. - On a RISC-V target, it will use `MP_HAL_CLEAN_DCACHE()` if that macro is defined (that's only on ESP32-P4 at the moment). The logic should be the same as before, except for the cases where an emitter is enabled on a mismatching architecture. For example, if `MICROPY_EMIT_THUMB` and/or `MICROPY_EMIT_INLINE_THUMB` were enabled on a non-Thumb target, previously that will try to generate code to flush caches (which doesn't really make sense), but now it will not. This actually happened for `mpy-cross` which does enable all the emitters, and prior to this fix would follow the Thumb path, but not generate any code because `__ICACHE_PRESENT` is disabled (at least when building `mpy-cross` on x86). Signed-off-by: Damien George --- py/emitglue.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/emitglue.c b/py/emitglue.c index 6a1816c2c79..9526acac805 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -107,17 +107,17 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons // Some architectures require flushing/invalidation of the I/D caches, // so that the generated native code which was created in data RAM will // be available for execution from instruction RAM. - #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB || (MP_NATIVE_ARCH_ARMV6M <= MPY_FEATURE_ARCH && MPY_FEATURE_ARCH <= MP_NATIVE_ARCH_ARMV7EMDP) + #if defined(__thumb__) || defined(__thumb2__) #if __ICACHE_PRESENT == 1 // Flush D-cache, so the code emitted is stored in RAM. MP_HAL_CLEAN_DCACHE(fun_data, fun_len); // Invalidate I-cache, so the newly-created code is reloaded from RAM. SCB_InvalidateICache(); #endif - #elif MICROPY_EMIT_ARM || (MPY_FEATURE_ARCH == MP_NATIVE_ARCH_ARMV6) + #elif defined(__arm__) #if (defined(__linux__) && defined(__GNUC__)) || __ARM_ARCH == 7 __builtin___clear_cache((void *)fun_data, (char *)fun_data + fun_len); - #elif defined(__arm__) + #else // Flush I-cache and D-cache. asm volatile ( "0:" @@ -127,7 +127,7 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, cons "mcr p15, 0, r0, c7, c7, 0\n" // invalidate I-cache and D-cache : : : "r0", "cc"); #endif - #elif (MICROPY_EMIT_RV32 || MICROPY_EMIT_INLINE_RV32 || (MPY_FEATURE_ARCH == MP_NATIVE_ARCH_RV32IMC)) && defined(MP_HAL_CLEAN_DCACHE) + #elif defined(__riscv) && defined(MP_HAL_CLEAN_DCACHE) // Flush the D-cache. MP_HAL_CLEAN_DCACHE(fun_data, fun_len); #endif From e8a3ee034293cbb0baf422a97272383e9ab26337 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 3 Mar 2026 15:19:49 +1100 Subject: [PATCH 1893/2098] esp32/esp32_common.cmake: Add missing C flags to user C module sources. On the esp32 port when adding a user C module, the source code for the user C module was being compiled without certain C flags. Notably the optimisation flag was missing. This commit fixes that by explicitly adding the missing C flags to the compilation of user C modules. The missing flags were the following, which are now included when building user C modules (found by inspecting the generated CMake files when building with ulab): -ffunction-sections -fdata-sections -Wall -Werror=all -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=unused-but-set-variable -Wno-error=deprecated-declarations -Wextra -Wno-error=extra -Wno-unused-parameter -Wno-sign-compare -Wno-enum-conversion -gdwarf-4 -ggdb -mdisable-hardware-atomics -O2 -fmacro-prefix-map=micropython/ports/esp32=. -fmacro-prefix-map=espressif/esp-idf=/IDF -fstrict-volatile-bitfields -fno-jump-tables -fno-tree-switch-conversion See related issue #18880. Work done in collaboration with @andrewleech. Signed-off-by: Damien George --- ports/esp32/esp32_common.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 86ea1aaf4ab..b71fc2437c5 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -277,6 +277,11 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-missing-field-initializers ) +# User C modules don't pick up certain compile options set by the IDF, most +# importantly the optimisation level. So set them here. +idf_build_get_property(idf_compile_options COMPILE_OPTIONS) +target_compile_options(usermod INTERFACE ${idf_compile_options}) + # Additional include directories needed for private NimBLE headers. target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble From e0e9fbb17ed6fd06bb76e266ae554784c9c80804 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 6 Apr 2026 23:12:49 +1000 Subject: [PATCH 1894/2098] all: Bump version to 1.28.0. Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 0951651e7d6..8d28775c8c4 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -41,7 +41,7 @@ #define MICROPY_VERSION_MAJOR 1 #define MICROPY_VERSION_MINOR 28 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 1 +#define MICROPY_VERSION_PRERELEASE 0 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 367178fce7a7513862b15f940afa75ba005ba67b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 7 Apr 2026 13:07:26 +1000 Subject: [PATCH 1895/2098] all: Bump version to 1.29.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 8d28775c8c4..8a26ce4ddcd 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -39,9 +39,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 28 +#define MICROPY_VERSION_MINOR 29 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From c20302dbb2c95741b04fffeef04d0115c4740263 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 21 Jun 2025 22:43:58 +1000 Subject: [PATCH 1896/2098] shared/netutils/dhcpserver: Set default DNS to the server address. This sets the DHCP server's own IP address as the default DNS server when no explicit DNS is configured, providing a more complete network configuration for DHCP clients. Signed-off-by: Andrew Leech --- shared/netutils/dhcpserver.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/netutils/dhcpserver.c b/shared/netutils/dhcpserver.c index 9a4d461a93f..1c788ac1156 100644 --- a/shared/netutils/dhcpserver.c +++ b/shared/netutils/dhcpserver.c @@ -65,11 +65,9 @@ #define PORT_DHCP_SERVER (67) #define PORT_DHCP_CLIENT (68) -#define DEFAULT_DNS MAKE_IP4(192, 168, 4, 1) #define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds #define MAC_LEN (6) -#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) typedef struct { uint8_t op; // message opcode @@ -283,7 +281,7 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &ip_2_ip4(&d->ip)->addr); opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &ip_2_ip4(&d->nm)->addr); opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip_2_ip4(&d->ip)->addr); // aka gateway; can have multiple addresses - opt_write_u32(&opt, DHCP_OPT_DNS, DEFAULT_DNS); // can have multiple addresses + opt_write_n(&opt, DHCP_OPT_DNS, 4, &ip_2_ip4(&d->ip)->addr); opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); *opt++ = DHCP_OPT_END; struct netif *netif = ip_current_input_netif(); From 4fe08ff16b036479f7c7ed4e8cfce48fa9a3610f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 13 Mar 2026 10:45:00 -0500 Subject: [PATCH 1897/2098] all: Avoid `qstr_str` calls when printing a qstr. The `%q` formatter of mp_printf can be used instead, and may save a small amount of code. Signed-off-by: Jeff Epler --- ports/mimxrt/machine_pin.c | 2 +- ports/stm32/timer.c | 4 ++-- ports/unix/modjni.c | 2 +- py/emitndebug.c | 2 +- py/parse.c | 4 ++-- py/showbc.c | 36 ++++++++++++++++++------------------ 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ports/mimxrt/machine_pin.c b/ports/mimxrt/machine_pin.c index 61b540076c1..e0c940879a9 100644 --- a/ports/mimxrt/machine_pin.c +++ b/ports/mimxrt/machine_pin.c @@ -329,7 +329,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ static void machine_pin_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind) { (void)kind; const machine_pin_obj_t *self = MP_OBJ_TO_PTR(o); - mp_printf(print, "Pin(%s)", qstr_str(self->name)); + mp_printf(print, "Pin(%q)", (qstr)self->name); } // pin(id, mode, pull, ...) diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index c05c4716d47..a2f48139ee4 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -1589,10 +1589,10 @@ MP_DEFINE_CONST_OBJ_TYPE( static void pyb_timer_channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_timer_channel_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "TimerChannel(timer=%u, channel=%u, mode=%s)", + mp_printf(print, "TimerChannel(timer=%u, channel=%u, mode=%q)", self->timer->tim_id, self->channel, - qstr_str(channel_mode_info[self->mode].name)); + (qstr)channel_mode_info[self->mode].name); } /// \method capture([value]) diff --git a/ports/unix/modjni.c b/ports/unix/modjni.c index dbce61aec11..0a29a94fbd4 100644 --- a/ports/unix/modjni.c +++ b/ports/unix/modjni.c @@ -357,7 +357,7 @@ static void jmethod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki (void)kind; mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); // Variable value printed as cast to int - mp_printf(print, "", qstr_str(self->name)); + mp_printf(print, "", (qstr)self->name); } #define IMATCH(s, static) ((!strncmp(s, static, sizeof(static) - 1)) && (s += sizeof(static) - 1)) diff --git a/py/emitndebug.c b/py/emitndebug.c index 2144d14e6b5..27c3bbdcef7 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -134,7 +134,7 @@ static void asm_debug_reg_imm(asm_debug_t *as, const char *op, int reg, int imm) #if !MICROPY_PERSISTENT_CODE_SAVE static void asm_debug_reg_qstr(asm_debug_t *as, const char *op, int reg, int qst) { - asm_debug_printf(as, "%s(%s, %s)\n", op, reg_name_table[reg], qstr_str(qst)); + asm_debug_printf(as, "%s(%s, %q)\n", op, reg_name_table[reg], (qstr)qst); } #endif diff --git a/py/parse.c b/py/parse.c index 1a50b13b5c7..fef8ae92d57 100644 --- a/py/parse.c +++ b/py/parse.c @@ -491,10 +491,10 @@ void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t ind uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: - mp_printf(print, "id(%s)\n", qstr_str(arg)); + mp_printf(print, "id(%q)\n", (qstr)arg); break; case MP_PARSE_NODE_STRING: - mp_printf(print, "str(%s)\n", qstr_str(arg)); + mp_printf(print, "str(%q)\n", (qstr)arg); break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); diff --git a/py/showbc.c b/py/showbc.c index 792fccd0133..68e2f33138c 100644 --- a/py/showbc.c +++ b/py/showbc.c @@ -99,8 +99,8 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t #else qstr source_file = cm->source_file; #endif - mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p %u bytes)\n", - qstr_str(source_file), qstr_str(block_name), rc, ip_start, (unsigned)fun_data_len); + mp_printf(print, "File %q, code block '%q' (descriptor: %p, bytecode @%p %u bytes)\n", + source_file, block_name, rc, ip_start, (unsigned)fun_data_len); // raw bytecode dump size_t prelude_size = ip - ip_start + n_info + n_cell; @@ -121,7 +121,7 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE qst = cm->qstr_table[qst]; #endif - mp_printf(print, " %s", qstr_str(qst)); + mp_printf(print, " %q", qst); } mp_printf(print, "\n"); @@ -189,7 +189,7 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; - mp_printf(print, "LOAD_CONST_STRING '%s'", qstr_str(qst)); + mp_printf(print, "LOAD_CONST_STRING '%q'", qst); break; case MP_BC_LOAD_CONST_OBJ: @@ -214,27 +214,27 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_LOAD_NAME: DECODE_QSTR; - mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); + mp_printf(print, "LOAD_NAME %q", qst); break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; - mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "LOAD_GLOBAL %q", qst); break; case MP_BC_LOAD_ATTR: DECODE_QSTR; - mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); + mp_printf(print, "LOAD_ATTR %q", qst); break; case MP_BC_LOAD_METHOD: DECODE_QSTR; - mp_printf(print, "LOAD_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_METHOD %q", qst); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; - mp_printf(print, "LOAD_SUPER_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_SUPER_METHOD %q", qst); break; case MP_BC_LOAD_BUILD_CLASS: @@ -257,17 +257,17 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_STORE_NAME: DECODE_QSTR; - mp_printf(print, "STORE_NAME %s", qstr_str(qst)); + mp_printf(print, "STORE_NAME %q", qst); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; - mp_printf(print, "STORE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "STORE_GLOBAL %q", qst); break; case MP_BC_STORE_ATTR: DECODE_QSTR; - mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); + mp_printf(print, "STORE_ATTR %q", qst); break; case MP_BC_STORE_SUBSCR: @@ -286,12 +286,12 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_DELETE_NAME: DECODE_QSTR; - mp_printf(print, "DELETE_NAME %s", qstr_str(qst)); + mp_printf(print, "DELETE_NAME %q", qst); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; - mp_printf(print, "DELETE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "DELETE_GLOBAL %q", qst); break; case MP_BC_DUP_TOP: @@ -506,12 +506,12 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_IMPORT_NAME: DECODE_QSTR; - mp_printf(print, "IMPORT_NAME '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_NAME '%q'", qst); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; - mp_printf(print, "IMPORT_FROM '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_FROM '%q'", qst); break; case MP_BC_IMPORT_STAR: @@ -527,10 +527,10 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_UNARY_OP_MULTI; - mp_printf(print, "UNARY_OP " UINT_FMT " %s", op, qstr_str(mp_unary_op_method_name[op])); + mp_printf(print, "UNARY_OP " UINT_FMT " %q", op, (qstr)mp_unary_op_method_name[op]); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; - mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); + mp_printf(print, "BINARY_OP " UINT_FMT " %q", op, (qstr)mp_binary_op_method_name[op]); } else { mp_printf(print, "code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); From 8abca6bacb4031307e4335e9b2e514b5d5978752 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 14 Mar 2026 19:51:51 -0500 Subject: [PATCH 1898/2098] py/objexcept: Use mp_obj_get_type_str when printing exception name. Saves a few bytes of code. Signed-off-by: Jeff Epler --- py/objexcept.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/objexcept.c b/py/objexcept.c index d43cf979e69..a3c302c3819 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -165,7 +165,7 @@ void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kin mp_print_kind_t k = kind & ~PRINT_EXC_SUBCLASS; bool is_subclass = kind & PRINT_EXC_SUBCLASS; if (!is_subclass && (k == PRINT_REPR || k == PRINT_EXC)) { - mp_print_str(print, qstr_str(o->base.type->name)); + mp_print_str(print, mp_obj_get_type_str(o_in)); } if (k == PRINT_EXC) { From c16a484796bed76510b28045af7c34fbebfe855b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fin=20Maa=C3=9F?= Date: Tue, 17 Mar 2026 11:52:44 +0100 Subject: [PATCH 1899/2098] zephyr/machine_uart: Ignore return value of uart_irq_update. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For uart_irq_update() the allowed return values are a negative errno or 1. 0 is not allowd. Therefore ignore the return value. Also the return type might soon be changed to void. Signed-off-by: Fin Maaß --- ports/zephyr/machine_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c index 60613befb12..81957c69330 100644 --- a/ports/zephyr/machine_uart.c +++ b/ports/zephyr/machine_uart.c @@ -265,7 +265,7 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint static void uart_interrupt_handler(const struct device *dev, void *user_data) { machine_uart_obj_t *self = (machine_uart_obj_t *)user_data; - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + while (uart_irq_update(dev), uart_irq_is_pending(dev)) { if (uart_irq_rx_ready(dev)) { uint8_t _rx_buffer[32]; size_t _free_space = MIN(ringbuf_free(&self->rx_ringbuffer), sizeof(_rx_buffer)); From 47fc0d5b8142d47324429403951ae13f1a470871 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 17 Mar 2026 14:32:20 +1100 Subject: [PATCH 1900/2098] docs/library: Document Pin.board and Pin.cpu attributes. Add an Attributes section to the machine.Pin reference describing the Pin.board and Pin.cpu class attributes, their purpose, availability across ports, and their role in string-based pin name resolution. Signed-off-by: Andrew Leech --- docs/library/machine.Pin.rst | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/library/machine.Pin.rst b/docs/library/machine.Pin.rst index 4aac0f4b94b..d419c7ac27d 100644 --- a/docs/library/machine.Pin.rst +++ b/docs/library/machine.Pin.rst @@ -244,6 +244,46 @@ The following methods are not part of the core Pin API and only implemented on c Availability: cc3200, esp32, esp8266, mimxrt, rp2, samd ports. +Attributes +---------- + +.. data:: Pin.board + + Contains pins named after the board's silkscreen or schematic labels. + For example ``Pin.board.X1`` or ``Pin.board.LED``. + + Availability: alif, esp32, mimxrt, nrf, renesas-ra, rp2, samd, stm32 ports. + +.. data:: Pin.cpu + + Contains the MCU pin names as given in the datasheet. + For example ``Pin.cpu.A0`` or ``Pin.cpu.GPIO0``. + + Availability: alif, mimxrt, nrf, renesas-ra, rp2, samd, stm32 ports. + +Multiple board pins can refer to the same CPU pin. Not all ports provide +both attributes, and the available names depend on the board definition. On +the esp32 port only ``Pin.board`` is provided, and only for boards whose +definition includes named pins (e.g. ``UM_TINYS3``, ``M5STACK_NANOC6``). +Generic ESP32 boards do not define any board pin names. + +When constructing a ``Pin`` from a string name, the board pins are searched +first, then the cpu pins:: + + from machine import Pin + + # On a Pyboard v1.0, these all refer to the same physical pin: + p = Pin(Pin.board.X1, Pin.OUT) + p = Pin(Pin.cpu.A0, Pin.OUT) + p = Pin("X1", Pin.OUT) # searches Pin.board, then Pin.cpu + + # On a Raspberry Pi Pico W: + p = Pin(Pin.board.LED, Pin.OUT) + p = Pin("LED", Pin.OUT) + +Use ``help(Pin.board)`` or ``help(Pin.cpu)`` to list the pin names available +on a particular board. + Constants --------- From c3b3f201c4e6335921c872540ed16dc0ccb913f5 Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Sat, 28 Mar 2026 02:43:22 +0800 Subject: [PATCH 1901/2098] py/objint: Fix comment for true operand in binary op extra cases. The comment incorrectly said true acts as 0; the implementation passes small int 1, matching Python bool semantics. Signed-off-by: Weixie Cui --- py/objint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/objint.c b/py/objint.c index 901264aca65..cd966352999 100644 --- a/py/objint.c +++ b/py/objint.c @@ -383,7 +383,7 @@ mp_obj_t mp_obj_int_binary_op_extra_cases(mp_binary_op_t op, mp_obj_t lhs_in, mp // false acts as 0 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(0)); } else if (rhs_in == mp_const_true) { - // true acts as 0 + // true acts as 1 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(1)); } else if (op == MP_BINARY_OP_MULTIPLY) { if (mp_obj_is_str_or_bytes(rhs_in) || mp_obj_is_type(rhs_in, &mp_type_tuple) || mp_obj_is_type(rhs_in, &mp_type_list)) { From e29bcdcb7be6da5614390167961fa811b68ba4d3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 4 Apr 2026 09:19:20 -0500 Subject: [PATCH 1902/2098] tests/cpydiff: Add test file for annotation expressions. Document the behavior on expression annotations rejected by CPython but accepted by MicroPython. Closes issue #19031. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_annotation_expression.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/cpydiff/syntax_annotation_expression.py diff --git a/tests/cpydiff/syntax_annotation_expression.py b/tests/cpydiff/syntax_annotation_expression.py new file mode 100644 index 00000000000..91814ec9522 --- /dev/null +++ b/tests/cpydiff/syntax_annotation_expression.py @@ -0,0 +1,24 @@ +""" +categories: Syntax,Annotations +description: MicroPython accepts type annotations on expressions where CPython forbids them. +cause: To reduce code size, MicroPython does not check the form of expressions with type annotations applied. +workaround: Always check for valid Python code using a linting tool. + +The expressions themselves are not evaluated. +""" + + +def test(expr): + code = f"def f():\n {expr}: int" + print(code) + try: + exec(code) + print("OK") + except SyntaxError as e: + print("SyntaxError") + print() + + +test("print('test')") +test("[x,y]") +test("x,y") From b6bc2d473c17dcdbc2bac213a395af084fea1efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Fri, 20 Mar 2026 16:11:52 +0100 Subject: [PATCH 1903/2098] esp32/adc: Raise error if ADC read result is invalid. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- ports/esp32/adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/adc.c b/ports/esp32/adc.c index d0e5e81330d..e717673f68f 100644 --- a/ports/esp32/adc.c +++ b/ports/esp32/adc.c @@ -54,7 +54,7 @@ mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t chan adc_is_init_guard(self); int reading = 0; - adc_oneshot_read(self->handle, channel_id, &reading); + check_esp_err(adc_oneshot_read(self->handle, channel_id, &reading)); return reading; } From e8992ebc78fcae10d51c83b5ad15c5afe91b2002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 4 Mar 2026 15:12:31 +0100 Subject: [PATCH 1904/2098] esp32/machine_uart: Allow passing -1 to specify pin will be unused. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- ports/esp32/machine_uart.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 4a0782f7443..5b9cd02c4a4 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -372,21 +372,12 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); - if (args[ARG_tx].u_obj != MP_OBJ_NULL) { - self->tx = machine_pin_get_id(args[ARG_tx].u_obj); - } - - if (args[ARG_rx].u_obj != MP_OBJ_NULL) { - self->rx = machine_pin_get_id(args[ARG_rx].u_obj); - } - - if (args[ARG_rts].u_obj != MP_OBJ_NULL) { - self->rts = machine_pin_get_id(args[ARG_rts].u_obj); - } - - if (args[ARG_cts].u_obj != MP_OBJ_NULL) { - self->cts = machine_pin_get_id(args[ARG_cts].u_obj); - } + // set pins + #define SET_PIN(prop, arg) if (arg != MP_OBJ_NULL) prop = mp_obj_is_int(arg) && mp_obj_get_int(arg) == -1 ? UART_PIN_NO_CHANGE : machine_pin_get_id(arg); + SET_PIN(self->tx, args[ARG_tx].u_obj); + SET_PIN(self->rx, args[ARG_rx].u_obj); + SET_PIN(self->rts, args[ARG_rts].u_obj); + SET_PIN(self->cts, args[ARG_cts].u_obj); check_esp_err(uart_set_pin(self->uart_num, self->tx, self->rx, self->rts, self->cts)); // set data bits From 6b2488f1d6f4668c78ce67330c036140c232e84f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 15 Mar 2026 21:56:46 +0100 Subject: [PATCH 1905/2098] qemu: Add basic ROMFS support. This commit adds basic ROMFS support. This helps with running tests since mpremote mount is too slow with big files. To be configured per-board, and then run with: $ make QEMU_ROMFS_IMG0=image.romfs ... Signed-off-by: iabdalkader --- ports/qemu/Makefile | 9 +++++ ports/qemu/README.md | 1 + ports/qemu/mpconfigport.h | 2 +- ports/qemu/vfs_rom_ioctl.c | 83 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 ports/qemu/vfs_rom_ioctl.c diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 3bc46ffefdc..380355dbe94 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -158,6 +158,14 @@ QEMU_SYSTEM = $(QEMU_BASE)$(QEMU_ARCH) QEMU_ARGS += -machine $(QEMU_MACHINE) -nographic -monitor null -semihosting QEMU_ARGS += $(QEMU_EXTRA) +# Load ROMFS images into emulated memory if configured. +ifneq ($(QEMU_ROMFS_IMG0),) +QEMU_ARGS += -device loader,file=$(QEMU_ROMFS_IMG0),addr=$(MICROPY_HW_ROMFS_PART0_START),force-raw=on +endif +ifneq ($(QEMU_ROMFS_IMG1),) +QEMU_ARGS += -device loader,file=$(QEMU_ROMFS_IMG1),addr=$(MICROPY_HW_ROMFS_PART1_START),force-raw=on +endif + # Specifying QEMU_DEBUG=1 will block qemu until a debugger is connected. ifeq ($(QEMU_DEBUG),1) QEMU_DEBUG_ARGS ?= -s @@ -209,6 +217,7 @@ SRC_C += \ main.c \ uart.c \ mphalport.c \ + vfs_rom_ioctl.c \ shared/libc/string0.c \ shared/readline/readline.c \ shared/runtime/interrupt_char.c \ diff --git a/ports/qemu/README.md b/ports/qemu/README.md index e6c16e3662d..5950562d623 100644 --- a/ports/qemu/README.md +++ b/ports/qemu/README.md @@ -166,6 +166,7 @@ The following options can be specified on the `make` command line: - `QEMU_DEBUG_ARGS`: defaults to `-s` (gdb on TCP port 1234), but can be overridden with different qemu gdb arguments. - `QEMU_DEBUG_EXTRA`: extra options to pass to qemu when `QEMU_DEBUG=1` is used. +- `QEMU_ROMFS_IMG`: pass in romfs image to be loaded by qemu (if enabled by the board). - `TEST_NATMODS`: pass an optional list of space-separated names of natmods to test, so only the given subset of example natmods will be used by `test_natmod` (for example, `make test_natmod TEST_NATMODS="btree heapq re"`). diff --git a/ports/qemu/mpconfigport.h b/ports/qemu/mpconfigport.h index 522c5926328..c9867a1fb70 100644 --- a/ports/qemu/mpconfigport.h +++ b/ports/qemu/mpconfigport.h @@ -67,7 +67,7 @@ #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS (1) #define MICROPY_VFS_ROM (1) -#define MICROPY_VFS_ROM_IOCTL (0) +#define MICROPY_VFS_ROM_IOCTL (MICROPY_HW_ROMFS_ENABLE_PART0 || MICROPY_HW_ROMFS_ENABLE_PART1) // type definitions for the specific machine diff --git a/ports/qemu/vfs_rom_ioctl.c b/ports/qemu/vfs_rom_ioctl.c new file mode 100644 index 00000000000..0db258cfdc8 --- /dev/null +++ b/ports/qemu/vfs_rom_ioctl.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 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/obj.h" +#include "py/objarray.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" + +#if MICROPY_VFS_ROM_IOCTL + +#if MICROPY_HW_ROMFS_ENABLE_PART0 && !defined(MICROPY_HW_ROMFS_PART0_START) +#define MICROPY_HW_ROMFS_PART0_START (uintptr_t)(&_micropy_hw_romfs_part0_start) +#define MICROPY_HW_ROMFS_PART0_SIZE (uintptr_t)(&_micropy_hw_romfs_part0_size) +extern uint8_t _micropy_hw_romfs_part0_start; +extern uint8_t _micropy_hw_romfs_part0_size; +#endif + +#if MICROPY_HW_ROMFS_ENABLE_PART1 && !defined(MICROPY_HW_ROMFS_PART1_START) +#define MICROPY_HW_ROMFS_PART1_START (uintptr_t)(&_micropy_hw_romfs_part1_start) +#define MICROPY_HW_ROMFS_PART1_SIZE (uintptr_t)(&_micropy_hw_romfs_part1_size) +extern uint8_t _micropy_hw_romfs_part1_start; +extern uint8_t _micropy_hw_romfs_part1_size; +#endif + +#define ROMFS_MEMORYVIEW(base, size) {{&mp_type_memoryview}, 'B', 0, (size), (void *)(base)} + +static const mp_obj_array_t romfs_obj_table[] = { + #if MICROPY_HW_ROMFS_ENABLE_PART0 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART0_START, MICROPY_HW_ROMFS_PART0_SIZE), + #endif + #if MICROPY_HW_ROMFS_ENABLE_PART1 + ROMFS_MEMORYVIEW(MICROPY_HW_ROMFS_PART1_START, MICROPY_HW_ROMFS_PART1_SIZE), + #endif +}; + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + mp_int_t cmd = mp_obj_get_int(args[0]); + if (cmd == MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS) { + return MP_OBJ_NEW_SMALL_INT(MP_ARRAY_SIZE(romfs_obj_table)); + } + + if (n_args < 2) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + mp_int_t romfs_id = mp_obj_get_int(args[1]); + if (!(0 <= romfs_id && romfs_id < MP_ARRAY_SIZE(romfs_obj_table))) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + + if (cmd == MP_VFS_ROM_IOCTL_GET_SEGMENT) { + // Return the ROMFS memoryview object. + const mp_obj_array_t *romfs_obj = &romfs_obj_table[romfs_id]; + return MP_OBJ_FROM_PTR(romfs_obj); + } + + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); +} + +#endif // MICROPY_VFS_ROM_IOCTL From a22d9da0a3871d0d7b58671bc776a3f9fd79a5f0 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 15 Mar 2026 21:56:46 +0100 Subject: [PATCH 1906/2098] qemu/boards/MPS3_AN547: Configure and enable ROMFS. Uses DDR address space. Signed-off-by: iabdalkader --- ports/qemu/boards/MPS3_AN547/mpconfigboard.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk b/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk index 772853935b7..fea9c3d5deb 100644 --- a/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk +++ b/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk @@ -6,6 +6,12 @@ CFLAGS += -DQEMU_SOC_MPS3 CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M55"' CFLAGS += -DCPU_FREQ_HZ=32000000 +MICROPY_HW_ROMFS_PART0_START ?= 0x62000000 +MICROPY_HW_ROMFS_PART0_SIZE ?= 0x02000000 +CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART0=1 +CFLAGS += -DMICROPY_HW_ROMFS_PART0_START=$(MICROPY_HW_ROMFS_PART0_START) +CFLAGS += -DMICROPY_HW_ROMFS_PART0_SIZE=$(MICROPY_HW_ROMFS_PART0_SIZE) + LDSCRIPT = mcu/arm/mps3.ld SRC_BOARD_O = shared/runtime/gchelper_generic.o From ed463edb36397147d3a6a90bf26930e2dc69bc15 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 31 Mar 2026 20:18:21 +0200 Subject: [PATCH 1907/2098] qemu/boards/MPS2_AN500: Configure and enable ROMFS. Using the last 2MBs of PSRAM. Signed-off-by: iabdalkader --- ports/qemu/boards/MPS2_AN500/mpconfigboard.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk index b7b54a80563..725ffbc895f 100644 --- a/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk +++ b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk @@ -5,6 +5,12 @@ CFLAGS += -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 CFLAGS += -DQEMU_SOC_MPS2 CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M7"' +MICROPY_HW_ROMFS_PART0_START = 0x60C00000 +MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 +CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART0=1 +CFLAGS += -DMICROPY_HW_ROMFS_PART0_START=$(MICROPY_HW_ROMFS_PART0_START) +CFLAGS += -DMICROPY_HW_ROMFS_PART0_SIZE=$(MICROPY_HW_ROMFS_PART0_SIZE) + LDSCRIPT = mcu/arm/mps2.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o From 501b0e6babca177ef043c64f00af2dc26a0aeb97 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Mon, 30 Mar 2026 13:27:29 -0700 Subject: [PATCH 1908/2098] alif/tinyusb_port: Fix _xfer_trb cache alignment. TRB buffers, which are the target of cache clean/invalidate actions, were not cache-aligned between buffers. Reported the problem to Alif, and adding padding to them is the proper fix. Appears to work without any other changes in device behavior. Signed-off-by: Kwabena W. Agyeman --- ports/alif/tinyusb_port/tusb_alif_dcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/alif/tinyusb_port/tusb_alif_dcd.c b/ports/alif/tinyusb_port/tusb_alif_dcd.c index c8f86082ddc..015d27ac49a 100644 --- a/ports/alif/tinyusb_port/tusb_alif_dcd.c +++ b/ports/alif/tinyusb_port/tusb_alif_dcd.c @@ -52,7 +52,7 @@ static uint32_t _evnt_buf[1024] CFG_TUSB_MEM_SECTION __attribute__((aligned(409 static volatile uint32_t* _evnt_tail; static uint8_t _ctrl_buf[64] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc -static uint32_t _xfer_trb[8][4] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc +static uint32_t _xfer_trb[8][8] CFG_TUSB_MEM_SECTION __attribute__((aligned(32))); // [TODO] runtime alloc static uint16_t _xfer_bytes[8]; static bool _ctrl_long_data = false; static bool _xfer_cfgd = false; From dc0b2c7f57501f9dec71ba537c48113269281c6d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 26 Mar 2026 21:06:13 +1100 Subject: [PATCH 1909/2098] py/malloc: Add m_tracked_realloc. Add realloc support to the tracked allocation API, using an alloc-copy-free approach for simplicity and thread safety. This function compliments the existing `m_tracked_calloc()` and `m_tracked_free()` functions. Signed-off-by: Andrew Leech --- py/malloc.c | 50 +++++++++++++++++++++++++++++++++++++++++++++----- py/misc.h | 1 + 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/py/malloc.c b/py/malloc.c index 5135dc26bd3..959034e7cb1 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -245,6 +245,15 @@ typedef struct _m_tracked_node_t { uint8_t data[]; } m_tracked_node_t; +// Helper to get data size of a tracked node, abstracting MICROPY_TRACKED_ALLOC_STORE_SIZE. +static inline size_t get_tracked_node_size(m_tracked_node_t *node) { + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + return node->size; + #else + return gc_nbytes(node) - sizeof(m_tracked_node_t); + #endif +} + #if MICROPY_DEBUG_VERBOSE static size_t m_tracked_count_links(size_t *nb) { m_tracked_node_lock(); @@ -292,6 +301,41 @@ void *m_tracked_calloc(size_t nmemb, size_t size) { return &node->data[0]; } +void *m_tracked_realloc(void *ptr_in, size_t n_bytes) { + // Handle pure allocation + if (ptr_in == NULL) { + return m_tracked_calloc(1, n_bytes); + } + + // Handle pure free + if (n_bytes == 0) { + m_tracked_free(ptr_in); + return NULL; + } + // To keep the implementation simple, we always allocate a new buffer and copy the old data into it. + // This could be optimised if faster performance or lower worst-case memory usage is required. + + // Get old size + m_tracked_node_t *old_node = (m_tracked_node_t *)((uint8_t *)ptr_in - sizeof(m_tracked_node_t)); + size_t old_size = get_tracked_node_size(old_node); + + // Allocate new buffer + void *new_ptr = m_tracked_calloc(1, n_bytes); + if (new_ptr == NULL) { + // Allocation failed, return NULL but leave original pointer intact + return NULL; + } + + // Copy data (minimum of old and new size) + size_t copy_size = MIN(old_size, n_bytes); + memcpy(new_ptr, ptr_in, copy_size); + + // Free old buffer + m_tracked_free(ptr_in); + + return new_ptr; +} + void m_tracked_free(void *ptr_in) { if (ptr_in == NULL) { return; @@ -299,11 +343,7 @@ void m_tracked_free(void *ptr_in) { m_tracked_node_t *node = (m_tracked_node_t *)((uint8_t *)ptr_in - sizeof(m_tracked_node_t)); #if MICROPY_DEBUG_VERBOSE size_t data_bytes; - #if MICROPY_TRACKED_ALLOC_STORE_SIZE - data_bytes = node->size; - #else - data_bytes = gc_nbytes(node); - #endif + data_bytes = get_tracked_node_size(node); size_t nb; size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb); diff --git a/py/misc.h b/py/misc.h index a9c02a4dd96..2fe0f11796b 100644 --- a/py/misc.h +++ b/py/misc.h @@ -138,6 +138,7 @@ MP_NORETURN void m_malloc_fail(size_t num_bytes); // These alloc/free functions track the pointers in a linked list so the GC does not reclaim // them. They can be used by code that requires traditional C malloc/free semantics. void *m_tracked_calloc(size_t nmemb, size_t size); +void *m_tracked_realloc(void *ptr_in, size_t n_bytes); void m_tracked_free(void *ptr_in); #endif From 18735dde3becfee023f3ecf33ff36cfe1c7f5cc3 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 26 Mar 2026 21:06:19 +1100 Subject: [PATCH 1910/2098] tests/ports/unix: Add tests for m_tracked_realloc. Signed-off-by: Andrew Leech --- ports/unix/coverage.c | 66 ++++++++++++++++++++++++++ tests/ports/unix/extra_coverage.py.exp | 8 ++++ 2 files changed, 74 insertions(+) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 79ec07f45e1..ff944b2488c 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -343,6 +343,13 @@ static mp_obj_t extra_coverage(void) { gc_collect(); } + // resize one of the blocks + void *before = ptrs[1]; + ptrs[1] = FLIP_POINTER(m_tracked_realloc(FLIP_POINTER(ptrs[1]), 2 * NUM_BYTES)); + void *after = ptrs[1]; + bool location_changed = before != after; + mp_printf(&mp_plat_print, "%d\n", location_changed); + // check the memory blocks have the correct content for (size_t i = 0; i < NUM_PTRS; ++i) { bool correct_contents = true; @@ -361,6 +368,65 @@ static mp_obj_t extra_coverage(void) { } mp_printf(&mp_plat_print, "m_tracked_head = %p\n", MP_STATE_VM(m_tracked_head)); + + // Test realloc with black-box behavioral testing + mp_printf(&mp_plat_print, "# tracked realloc\n"); + + // Test 1: Basic realloc with data preservation + uint8_t *test_ptr = m_tracked_calloc(1, 32); + for (int i = 0; i < 32; i++) { + test_ptr[i] = i; + } + + test_ptr = m_tracked_realloc(test_ptr, 64); // Grow + bool data_preserved = (test_ptr[0] == 0 && test_ptr[31] == 31); + mp_printf(&mp_plat_print, "grow preserves data: %d\n", data_preserved); + + test_ptr = m_tracked_realloc(test_ptr, 16); // Shrink + bool shrink_ok = (test_ptr[0] == 0 && test_ptr[15] == 15); + mp_printf(&mp_plat_print, "shrink preserves data: %d\n", shrink_ok); + + m_tracked_free(test_ptr); + + // Test 2: Multiple allocations + reallocs + GC stability + uint8_t *realloc_ptrs[5]; + for (int i = 0; i < 5; i++) { + realloc_ptrs[i] = m_tracked_calloc(1, 32); + realloc_ptrs[i][0] = 'A' + i; // Mark each + } + + // Realloc some in different positions + realloc_ptrs[0] = m_tracked_realloc(realloc_ptrs[0], 64); // First allocated (tail of list) + realloc_ptrs[2] = m_tracked_realloc(realloc_ptrs[2], 64); // Middle + realloc_ptrs[4] = m_tracked_realloc(realloc_ptrs[4], 64); // Last allocated (head of list) + + // Run GC - if list corrupted, this might crash/fail + gc_collect(); + + // Verify markers intact + bool markers_ok = true; + for (int i = 0; i < 5; i++) { + if (realloc_ptrs[i][0] != 'A' + i) { + markers_ok = false; + break; + } + } + mp_printf(&mp_plat_print, "realloc gc stable: %d\n", markers_ok); + + // Cleanup + for (int i = 0; i < 5; i++) { + m_tracked_free(realloc_ptrs[i]); + } + + // Test 3: Edge cases + uint8_t *null_alloc = m_tracked_realloc(NULL, 32); + null_alloc[0] = 'X'; + mp_printf(&mp_plat_print, "realloc(NULL) ok: %d\n", null_alloc[0] == 'X'); + + void *free_result = m_tracked_realloc(null_alloc, 0); + mp_printf(&mp_plat_print, "realloc(ptr, 0) returns NULL: %d\n", free_result == NULL); + + mp_printf(&mp_plat_print, "m_tracked_head after cleanup: %p\n", MP_STATE_VM(m_tracked_head)); } // vstr diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 1c3fe1558f2..4746d20ab29 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -45,6 +45,7 @@ m_tracked_head = 0 5 1 6 1 7 1 +1 0 1 1 1 2 1 @@ -54,6 +55,13 @@ m_tracked_head = 0 6 1 7 1 m_tracked_head = 0 +# tracked realloc +grow preserves data: 1 +shrink preserves data: 1 +realloc gc stable: 1 +realloc(NULL) ok: 1 +realloc(ptr, 0) returns NULL: 1 +m_tracked_head after cleanup: 0 # vstr tests sts From 41137f05261a13bd2b2916e8dc40cd68f2b14113 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 9 Jul 2025 01:41:39 +0200 Subject: [PATCH 1911/2098] py/parse: Recognise const assignments with type hints. This commit extends the parser's behaviour to also recognise constant integer assignments even when the assignment itself has a type annotation. Now declarations like "var: int = const(val)" can be treated as constants by the compiler and thus be folded in the rest of the program as it sees fit. Before this change, that line would generate a variable creation and its value assignment, without any folding being performed. This fixes #15608. Signed-off-by: Alessandro Gatti --- py/parse.c | 84 +++++++++++++----------- tests/micropython/const_annotated.py | 18 +++++ tests/micropython/const_annotated.py.exp | 6 ++ 3 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 tests/micropython/const_annotated.py create mode 100644 tests/micropython/const_annotated.py.exp diff --git a/py/parse.c b/py/parse.c index fef8ae92d57..5937427f19e 100644 --- a/py/parse.c +++ b/py/parse.c @@ -820,50 +820,54 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { if (!MP_PARSE_NODE_IS_NULL(pn1) && !(MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_augassign) || MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_assign_list))) { - // this node is of the form = + // this node is of the form = or : = mp_parse_node_t pn0 = peek_result(parser, 1); - if (MP_PARSE_NODE_IS_ID(pn0) - && MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_atom_expr_normal) - && MP_PARSE_NODE_IS_ID(((mp_parse_node_struct_t *)pn1)->nodes[0]) - && MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn1)->nodes[0]) == MP_QSTR_const - && MP_PARSE_NODE_IS_STRUCT_KIND(((mp_parse_node_struct_t *)pn1)->nodes[1], RULE_trailer_paren) - ) { - // code to assign dynamic constants: id = const(value) - - // get the id - qstr id = MP_PARSE_NODE_LEAF_ARG(pn0); - - // get the value - mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; - if (!mp_parse_node_is_const(pn_value)) { - mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - MP_ERROR_TEXT("not a constant")); - mp_obj_exception_add_traceback(exc, parser->lexer->source_name, - ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); - nlr_raise(exc); - } - mp_obj_t value = mp_parse_node_convert_to_obj(pn_value); - - // store the value in the table of dynamic constants - mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); - assert(elem->value == MP_OBJ_NULL); - elem->value = value; - - // If the constant starts with an underscore then treat it as a private - // variable and don't emit any code to store the value to the id. - if (qstr_str(id)[0] == '_') { - pop_result(parser); // pop const(value) - pop_result(parser); // pop id - push_result_rule(parser, 0, RULE_pass_stmt, 0); // replace with "pass" - return true; + if (MP_PARSE_NODE_IS_ID(pn0)) { + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_annassign)) { + // extract annassign rhs + pn1 = ((mp_parse_node_struct_t *)pn1)->nodes[1]; } + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_atom_expr_normal) + && MP_PARSE_NODE_IS_ID(((mp_parse_node_struct_t *)pn1)->nodes[0]) + && MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn1)->nodes[0]) == MP_QSTR_const + && MP_PARSE_NODE_IS_STRUCT_KIND(((mp_parse_node_struct_t *)pn1)->nodes[1], RULE_trailer_paren)) { + // code to assign dynamic constants: id = const(value) + + // get the id + qstr id = MP_PARSE_NODE_LEAF_ARG(pn0); + + // get the value + mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; + if (!mp_parse_node_is_const(pn_value)) { + mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, + MP_ERROR_TEXT("not a constant")); + mp_obj_exception_add_traceback(exc, parser->lexer->source_name, + ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); + nlr_raise(exc); + } + mp_obj_t value = mp_parse_node_convert_to_obj(pn_value); + + // store the value in the table of dynamic constants + mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + assert(elem->value == MP_OBJ_NULL); + elem->value = value; + + // If the constant starts with an underscore then treat it as a private + // variable and don't emit any code to store the value to the id. + if (qstr_str(id)[0] == '_') { + pop_result(parser); // pop const(value) + pop_result(parser); // pop id + push_result_rule(parser, 0, RULE_pass_stmt, 0); // replace with "pass" + return true; + } - // replace const(value) with value - pop_result(parser); - push_result_node(parser, pn_value); + // replace const(value) with value + pop_result(parser); + push_result_node(parser, pn_value); - // finished folding this assignment, but we still want it to be part of the tree - return false; + // finished folding this assignment, but we still want it to be part of the tree + return false; + } } } return false; diff --git a/tests/micropython/const_annotated.py b/tests/micropython/const_annotated.py new file mode 100644 index 00000000000..4d01389e43a --- /dev/null +++ b/tests/micropython/const_annotated.py @@ -0,0 +1,18 @@ +# Test type annotations in combination with const. +# This test will only work when MICROPY_COMP_CONST and MICROPY_COMP_CONST_TUPLE are enabled. + +from micropython import const + +_X0: bool = const(True) +_X1: int = const(123) +_X2: str = const("test") +_X3: tuple = const((1, 2)) +_X4: tuple[bool, int] = const((True, 4)) +_X5: bytes = b"\x01\x02\x03" + +print(_X0) +print(_X1) +print(_X2) +print(_X3) +print(_X4) +print(_X5) diff --git a/tests/micropython/const_annotated.py.exp b/tests/micropython/const_annotated.py.exp new file mode 100644 index 00000000000..65edb58e72e --- /dev/null +++ b/tests/micropython/const_annotated.py.exp @@ -0,0 +1,6 @@ +True +123 +test +(1, 2) +(True, 4) +b'\x01\x02\x03' From f89112dba7c8a4f0e0f7d25db385c6cc29062ebc Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sat, 8 Nov 2025 10:29:24 -0500 Subject: [PATCH 1912/2098] py/obj: Refactor MP_DEFINE_CONST_OBJ_TYPE_NARGS script for readability. This is a comment-only change. The edited script produces exactly the same output as the original, just in a way that will allow more minimal diffs to modify it. Signed-off-by: Anson Mansfield --- py/obj.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/py/obj.h b/py/obj.h index 3c9122a69c0..64d4f2658a4 100644 --- a/py/obj.h +++ b/py/obj.h @@ -780,7 +780,16 @@ typedef struct _mp_obj_full_type_t { // Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE. // Generated with: // for i in range(13): -// print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{''.join(f', f{j+1}, v{j+1}' for j in range(i))}) const _struct_type _typename = {{ .base = {{ &mp_type_type }}, .flags = _flags, .name = _name{''.join(f', .slot_index_##f{j+1} = {j+1}' for j in range(i))}{', .slots = { ' + ''.join(f'v{j+1}, ' for j in range(i)) + '}' if i else '' } }}") +// args = ['_struct_type', '_typename', '_name', '_flags'] +// struct = ['.base = { &mp_type_type }', '.flags = _flags', '.name = _name'] +// slots = [] +// for j in range(i): +// args += [f"f{j+1}", f"v{j+1}"] +// struct += [f".slot_index_##f{j+1} = {j+1}"] +// slots += [f"v{j+1},"] +// if slots: +// struct += [f".slots = {{ {' '.join(slots)} }}"] +// print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}({', '.join(args)}) const _struct_type _typename = {{ {', '.join(struct)} }}") #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1, } } #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } } From 8223287f243ee153760e98fa2a70de1a5a4d2a86 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sat, 8 Nov 2025 10:29:24 -0500 Subject: [PATCH 1913/2098] py/obj: Eliminate trailing comma from MP_DEFINE_CONST_OBJ_TYPE_NARGS. This is mostly to simplify and make the associated code-gen logic easier to modify, by inserting the comma in the "obvious" place as part of the `.join` rather than as part of the element template. Signed-off-by: Anson Mansfield --- py/obj.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/py/obj.h b/py/obj.h index 64d4f2658a4..64621c293a3 100644 --- a/py/obj.h +++ b/py/obj.h @@ -786,23 +786,23 @@ typedef struct _mp_obj_full_type_t { // for j in range(i): // args += [f"f{j+1}", f"v{j+1}"] // struct += [f".slot_index_##f{j+1} = {j+1}"] -// slots += [f"v{j+1},"] +// slots += [f"v{j+1}"] // if slots: -// struct += [f".slots = {{ {' '.join(slots)} }}"] +// struct += [f".slots = {{ {', '.join(slots)} }}"] // print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}({', '.join(args)}) const _struct_type _typename = {{ {', '.join(struct)} }}") #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 } } // Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take // slot_index_foo=0 to mean that the slot is unset. This also simplifies checking From 483715dc72b5f29eba4b02198d7c8983fbf1741e Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 3 Oct 2025 11:50:18 -0400 Subject: [PATCH 1914/2098] py/obj: Reorganize slot access macros to put with slot definitions. i.e. rather than having them just arbitrarily between the generated macros for the define macro and the other macros in that suite. Signed-off-by: Anson Mansfield --- py/obj.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/py/obj.h b/py/obj.h index 64621c293a3..313a766c135 100644 --- a/py/obj.h +++ b/py/obj.h @@ -776,6 +776,19 @@ typedef struct _mp_obj_full_type_t { #define _MP_OBJ_TYPE_SLOT_TYPE_parent (const void *) #define _MP_OBJ_TYPE_SLOT_TYPE_locals_dict (struct _mp_obj_dict_t *) +// Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take +// slot_index_foo=0 to mean that the slot is unset. This also simplifies checking +// if the slot is set. That means that we need to store index+1 in slot_index_foo +// though and then access it as slots[slot_index_foo - 1]. This is an implementation +// detail, the user of these macros doesn't need to be aware of it, and when using +// MP_OBJ_TYPE_OFFSETOF_SLOT you should use zero-based indexing. +#define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->slot_index_##f) +#define MP_OBJ_TYPE_GET_SLOT(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(t)->slots[(t)->slot_index_##f - 1]) +#define MP_OBJ_TYPE_GET_SLOT_OR_NULL(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(MP_OBJ_TYPE_HAS_SLOT(t, f) ? MP_OBJ_TYPE_GET_SLOT(t, f) : NULL)) +#define MP_OBJ_TYPE_SET_SLOT(t, f, v, n) ((t)->slot_index_##f = (n) + 1, (t)->slots[(n)] = (void *)v) +#define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (offsetof(mp_obj_type_t, slot_index_##f)) +#define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (*(uint8_t *)((char *)(t) + (offset)) != 0) + // Implementation of MP_DEFINE_CONST_OBJ_TYPE for each number of arguments. // Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE. // Generated with: @@ -804,19 +817,6 @@ typedef struct _mp_obj_full_type_t { #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 } } #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 } } -// Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take -// slot_index_foo=0 to mean that the slot is unset. This also simplifies checking -// if the slot is set. That means that we need to store index+1 in slot_index_foo -// though and then access it as slots[slot_index_foo - 1]. This is an implementation -// detail, the user of these macros doesn't need to be aware of it, and when using -// MP_OBJ_TYPE_OFFSETOF_SLOT you should use zero-based indexing. -#define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->slot_index_##f) -#define MP_OBJ_TYPE_GET_SLOT(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(t)->slots[(t)->slot_index_##f - 1]) -#define MP_OBJ_TYPE_GET_SLOT_OR_NULL(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(MP_OBJ_TYPE_HAS_SLOT(t, f) ? MP_OBJ_TYPE_GET_SLOT(t, f) : NULL)) -#define MP_OBJ_TYPE_SET_SLOT(t, f, v, n) ((t)->slot_index_##f = (n) + 1, (t)->slots[(n)] = (void *)v) -#define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (offsetof(mp_obj_type_t, slot_index_##f)) -#define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (*(uint8_t *)((char *)(t) + (offset)) != 0) - // Workaround for https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-160#macro-arguments-are-unpacked #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x From e274a38e0f1103e5ecf229b67d847b4f215c68cd Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 3 Oct 2025 12:52:05 -0400 Subject: [PATCH 1915/2098] py/obj: Enforce slot function signatures at compile time. By explicitly casting each slot to its slot type, this will ensure that a compiler diagnostic is generated when MP_DEFINE_CONST_OBJ_TYPE is invoked with a slot function with the incorrect signature. Signed-off-by: Anson Mansfield --- py/obj.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/py/obj.h b/py/obj.h index 313a766c135..843d3ff7c5b 100644 --- a/py/obj.h +++ b/py/obj.h @@ -799,23 +799,23 @@ typedef struct _mp_obj_full_type_t { // for j in range(i): // args += [f"f{j+1}", f"v{j+1}"] // struct += [f".slot_index_##f{j+1} = {j+1}"] -// slots += [f"v{j+1}"] +// slots += [f"_MP_OBJ_TYPE_SLOT_TYPE_##f{j+1} v{j+1}"] // if slots: // struct += [f".slots = {{ {', '.join(slots)} }}"] // print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}({', '.join(args)}) const _struct_type _typename = {{ {', '.join(struct)} }}") #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, _MP_OBJ_TYPE_SLOT_TYPE_##f11 v11 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, _MP_OBJ_TYPE_SLOT_TYPE_##f11 v11, _MP_OBJ_TYPE_SLOT_TYPE_##f12 v12 } } // Workaround for https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-160#macro-arguments-are-unpacked #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x From 8af714e5b916f25ab8ded24c28fc16aa514caf2c Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 3 Oct 2025 11:57:10 -0400 Subject: [PATCH 1916/2098] py/obj: Explicitly cast slot initializer values to `(const void *)`. This resolves an issue compiling with IAR, where an implicit cast from a function pointer to a void pointer is disallowed. Based on comments from @hammondeggs in the MicroPython discord: https://discord.com/channels/574275045187125269/1012726673709469818/1423683501374308392 Signed-off-by: Anson Mansfield --- py/obj.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/py/obj.h b/py/obj.h index 843d3ff7c5b..4683f8a719f 100644 --- a/py/obj.h +++ b/py/obj.h @@ -799,23 +799,23 @@ typedef struct _mp_obj_full_type_t { // for j in range(i): // args += [f"f{j+1}", f"v{j+1}"] // struct += [f".slot_index_##f{j+1} = {j+1}"] -// slots += [f"_MP_OBJ_TYPE_SLOT_TYPE_##f{j+1} v{j+1}"] +// slots += [f"(const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f{j+1} v{j+1}"] // if slots: // struct += [f".slots = {{ {', '.join(slots)} }}"] // print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}({', '.join(args)}) const _struct_type _typename = {{ {', '.join(struct)} }}") #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, _MP_OBJ_TYPE_SLOT_TYPE_##f11 v11 } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { _MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, _MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, _MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, _MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, _MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, _MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, _MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, _MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, _MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, _MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, _MP_OBJ_TYPE_SLOT_TYPE_##f11 v11, _MP_OBJ_TYPE_SLOT_TYPE_##f12 v12 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f11 v11 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f11 v11, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f12 v12 } } // Workaround for https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-160#macro-arguments-are-unpacked #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x From a91910cd7375d3e8524c5f3a523bfcc9fb4d09b2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Mar 2026 15:17:45 +1100 Subject: [PATCH 1917/2098] py/persistentcode: Use MP_ENCODE_UINT_MAX_BYTES instead of custom macro. MP_ENCODE_UINT_MAX_BYTES has an equivalent value to BYTES_FOR_INT and the former is used in other locations to encode variable (u)int values. Signed-off-by: Damien George --- py/persistentcode.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index 2c7a4256566..5a22287c6d9 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -558,9 +558,8 @@ static void mp_print_bytes(mp_print_t *print, const byte *data, size_t len) { print->print_strn(print->data, (const char *)data, len); } -#define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) static void mp_print_uint(mp_print_t *print, size_t n) { - byte buf[BYTES_FOR_INT]; + byte buf[MP_ENCODE_UINT_MAX_BYTES]; byte *p = buf + sizeof(buf); *--p = n & 0x7f; n >>= 7; From 024178455d97e730f551b7246e7a1017caa3c7ff Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 17 Mar 2026 13:24:49 +0100 Subject: [PATCH 1918/2098] unix/modsocket: Add "sendall" method to socket class. This commit adds an implementation for "socket.sendall" to the Unix port. Right now the implementation is a wrapper over a single "socket.send" call, since it wasn't possible to let "socket.send" perform a partial transfer. With no partial transfers it is not possible to have a testable implementation following the expected behaviour, so a single send operation is done and OSErr(EINTR) is raised if a partial transfer occurs. The discussion for the issue linked to this PR contains more information about the efforts made to let partial transfers happen. This fixes #16803. Signed-off-by: Alessandro Gatti --- ports/unix/modsocket.c | 16 ++++++++ tests/multi_net/tcp_sendall.py | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/multi_net/tcp_sendall.py diff --git a/ports/unix/modsocket.c b/ports/unix/modsocket.c index 16eedc6d691..618b9b7c75f 100644 --- a/ports/unix/modsocket.c +++ b/ports/unix/modsocket.c @@ -333,6 +333,12 @@ static mp_obj_t socket_send(size_t n_args, const mp_obj_t *args) { mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); int flags = 0; + bool is_sendall = false; + if (n_args < 2) { + is_sendall = true; + n_args += 2; + } + if (n_args > 2) { flags = mp_obj_get_int(args[2]); } @@ -342,10 +348,19 @@ static mp_obj_t socket_send(size_t n_args, const mp_obj_t *args) { ssize_t out_sz; MP_HAL_RETRY_SYSCALL(out_sz, send(self->fd, bufinfo.buf, bufinfo.len, flags), mp_raise_OSError(err)); + if (is_sendall && ((size_t)out_sz != bufinfo.len)) { + mp_raise_OSError(MP_EINTR); + } return MP_OBJ_NEW_SMALL_INT(out_sz); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_send_obj, 2, 3, socket_send); +static mp_obj_t socket_sendall(size_t n_args, const mp_obj_t *args) { + socket_send(n_args - 2, args); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_sendall_obj, 2, 3, socket_sendall); + static mp_obj_t socket_sendto(size_t n_args, const mp_obj_t *args) { mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); int flags = 0; @@ -511,6 +526,7 @@ static const mp_rom_map_elem_t socket_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&socket_recv_obj) }, { MP_ROM_QSTR(MP_QSTR_recvfrom), MP_ROM_PTR(&socket_recvfrom_obj) }, { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&socket_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendall), MP_ROM_PTR(&socket_sendall_obj) }, { MP_ROM_QSTR(MP_QSTR_sendto), MP_ROM_PTR(&socket_sendto_obj) }, { MP_ROM_QSTR(MP_QSTR_setsockopt), MP_ROM_PTR(&socket_setsockopt_obj) }, { MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socket_setblocking_obj) }, diff --git a/tests/multi_net/tcp_sendall.py b/tests/multi_net/tcp_sendall.py new file mode 100644 index 00000000000..ee9b780edff --- /dev/null +++ b/tests/multi_net/tcp_sendall.py @@ -0,0 +1,71 @@ +# Simple test of a TCP server and client transferring data using sendall. + +import socket + +if not hasattr(socket.socket, "sendall"): + print("SKIP") + raise SystemExit + +PORT = 8000 + +# This is the smallest send buffer size supported by Linux, see socket(7). +SEND_SIZE = 1024 +BUFFERS = 8 + + +# G. D. Nguyen, "Fast CRCs", vol. 18, no. 10, pp. 1321-1331, Oct. 2009 +# https://dl.acm.org/doi/abs/10.1109/TC.2009.83 +def calculate_checksum(buffer): + checksum = 0 + for byte in buffer: + checksum = (checksum ^ byte) & 0xFFFF + for step in range(16): + if (checksum & 0x8000) != 0: + checksum = ((checksum << 1) ^ 0x8005) & 0xFFFF + else: + checksum = (checksum << 1) & 0xFFFF + return checksum + + +# Server +def instance0(): + multitest.globals(IP=multitest.get_network_ip()) + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + s.listen() + multitest.next() + s2, _ = s.accept() + buffer = b"" + while len(buffer) < SEND_SIZE * BUFFERS: + buffer += s2.recv(SEND_SIZE * BUFFERS) + s2.send(b"%08x" % len(buffer)) + s2.send(b"%04x" % calculate_checksum(buffer)) + s2.close() + s.close() + print("received {} bytes".format(len(buffer))) + + +# Client +def instance1(): + multitest.next() + s = socket.socket() + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + # Try our best to trigger a partial send in socket.sendall + if hasattr(socket, "SO_SNDBUF"): + s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, SEND_SIZE) + buffer = bytearray(SEND_SIZE * BUFFERS) + for index in range(len(buffer)): + buffer[index] = index & 0xFF + expected_checksum = calculate_checksum(buffer) + s.sendall(buffer) + print("sent {} bytes".format(len(buffer))) + length = int(s.recv(8), 16) + if length != len(buffer): + raise Exception("sendall len fail (expected %d, got %d)" % (len(buffer), length)) + checksum = int(s.recv(4), 16) + if checksum != expected_checksum: + raise Exception( + "sendall checksum fail (expected %04x, got %04x)" % (expected_checksum, checksum) + ) + s.close() From 71d2314164efce3ce831e2761190564585845a63 Mon Sep 17 00:00:00 2001 From: Pierre Gaufillet Date: Mon, 16 Feb 2026 16:26:26 +0100 Subject: [PATCH 1919/2098] esp32/boards/LILYGO_T3_S3: Add LILYGO T3-S3 board definition. ESP32-S3FH4R2 with SX1262 LoRa (HPD17A), SSD1306 OLED, SD card. Freezes lora-sx126x and ssd1306 from micropython-lib. Signed-off-by: Pierre Gaufillet --- ports/esp32/boards/LILYGO_T3_S3/board.json | 24 ++++++ ports/esp32/boards/LILYGO_T3_S3/board.md | 4 + ports/esp32/boards/LILYGO_T3_S3/manifest.py | 6 ++ .../esp32/boards/LILYGO_T3_S3/modules/t3s3.py | 74 +++++++++++++++++++ .../boards/LILYGO_T3_S3/mpconfigboard.cmake | 11 +++ .../esp32/boards/LILYGO_T3_S3/mpconfigboard.h | 14 ++++ ports/esp32/boards/LILYGO_T3_S3/pins.csv | 16 ++++ .../esp32/boards/LILYGO_T3_S3/sdkconfig.board | 3 + 8 files changed, 152 insertions(+) create mode 100644 ports/esp32/boards/LILYGO_T3_S3/board.json create mode 100644 ports/esp32/boards/LILYGO_T3_S3/board.md create mode 100644 ports/esp32/boards/LILYGO_T3_S3/manifest.py create mode 100644 ports/esp32/boards/LILYGO_T3_S3/modules/t3s3.py create mode 100644 ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.cmake create mode 100644 ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.h create mode 100644 ports/esp32/boards/LILYGO_T3_S3/pins.csv create mode 100644 ports/esp32/boards/LILYGO_T3_S3/sdkconfig.board diff --git a/ports/esp32/boards/LILYGO_T3_S3/board.json b/ports/esp32/boards/LILYGO_T3_S3/board.json new file mode 100644 index 00000000000..0f4c998c119 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/board.json @@ -0,0 +1,24 @@ +{ + "deploy": [ + "../deploy_nativeusb.md" + ], + "deploy_options": { + "flash_offset": "0" + }, + "docs": "", + "features": [ + "BLE", + "Display", + "External Flash", + "External RAM", + "LoRa", + "SDCard", + "USB-C", + "WiFi" + ], + "mcu": "esp32s3", + "product": "T3-S3", + "thumbnail": "", + "url": "https://www.lilygo.cc/products/t3s3-v1-0", + "vendor": "LILYGO" +} diff --git a/ports/esp32/boards/LILYGO_T3_S3/board.md b/ports/esp32/boards/LILYGO_T3_S3/board.md new file mode 100644 index 00000000000..311fd8abfb6 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/board.md @@ -0,0 +1,4 @@ +The following files are firmware for the LILYGO T3-S3 v1.2/v1.3 +(ESP32-S3FH4R2 with SX1262 LoRa radio, SSD1306 OLED display, and SD card slot). + +This board uses native USB for the serial REPL. diff --git a/ports/esp32/boards/LILYGO_T3_S3/manifest.py b/ports/esp32/boards/LILYGO_T3_S3/manifest.py new file mode 100644 index 00000000000..91c3a6cb7e5 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/manifest.py @@ -0,0 +1,6 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") +require("ssd1306") +require("lora") +require("lora-sx126x") +require("lora-sync") diff --git a/ports/esp32/boards/LILYGO_T3_S3/modules/t3s3.py b/ports/esp32/boards/LILYGO_T3_S3/modules/t3s3.py new file mode 100644 index 00000000000..583418e3276 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/modules/t3s3.py @@ -0,0 +1,74 @@ +# LILYGO T3-S3 MicroPython Board Helper +# Pin assignments from pins.csv, verified against schematic T3S3_V1.2 + +from machine import Pin +from micropython import const + +_pins = Pin.board + +_OLED_ADDR = const(0x3C) + +# Battery voltage divider: R42=100K / R43=100K (VBAT/2 at ADC input) +_VBAT_DIVIDER_RATIO = 2.0 + + +def get_battery_voltage(): + """Read battery voltage via ADC with voltage divider compensation. + + Returns voltage in volts (e.g. 3.85). Accuracy is approximate. + """ + from machine import ADC + + adc = ADC(_pins.VBAT_SENSE) + adc.atten(ADC.ATTN_11DB) + raw_uv = adc.read_uv() + return round(raw_uv / 1_000_000 * _VBAT_DIVIDER_RATIO, 2) + + +def set_led(state): + """Turn on-board LED on (True) or off (False). LED is active HIGH.""" + Pin(_pins.LED, Pin.OUT).value(1 if state else 0) + + +def get_button(): + """Read boot button state. Returns True when pressed (active LOW).""" + return Pin(_pins.BUTTON, Pin.IN).value() == 0 + + +def get_oled(): + """Create and return an SSD1306 128x64 OLED display instance.""" + from machine import I2C + import ssd1306 + + i2c = I2C(0, scl=_pins.OLED_SCL, sda=_pins.OLED_SDA, freq=400000) + return ssd1306.SSD1306_I2C(128, 64, i2c, addr=_OLED_ADDR) + + +def get_lora(lora_cfg=None): + """Create and return a configured SX1262 LoRa modem for the T3-S3. + + Optional lora_cfg dict, e.g.: + {"freq_khz": 869500, "sf": 7, "bw": "125", "coding_rate": 5, "output_power": 14} + """ + from machine import SPI + from lora import SX1262 + + spi = SPI( + 1, + baudrate=2000000, + polarity=0, + phase=0, + sck=_pins.LORA_SCK, + mosi=_pins.LORA_MOSI, + miso=_pins.LORA_MISO, + ) + return SX1262( + spi=spi, + cs=_pins.LORA_CS, + busy=_pins.LORA_BUSY, + dio1=_pins.LORA_DIO1, + dio2_rf_sw=True, + dio3_tcxo_millivolts=1800, + reset=_pins.LORA_RST, + lora_cfg=lora_cfg, + ) diff --git a/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.cmake b/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.cmake new file mode 100644 index 00000000000..2ce8cc8a452 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.cmake @@ -0,0 +1,11 @@ +set(IDF_TARGET esp32s3) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/sdkconfig.spiram_sx + ${MICROPY_BOARD_DIR}/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.h b/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.h new file mode 100644 index 00000000000..73bbe8f5677 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/mpconfigboard.h @@ -0,0 +1,14 @@ +#define MICROPY_HW_BOARD_NAME "LILYGO T3-S3" +#define MICROPY_HW_MCU_NAME "ESP32-S3" +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-t3s3" + +// I2C0 default pins — OLED SSD1306 +// Verified: schematic T3S3_V1.2 U24 D0=SCL(IO17), D1=SDA(IO18) in I2C mode +#define MICROPY_HW_I2C0_SCL (17) +#define MICROPY_HW_I2C0_SDA (18) + +// SPI1 default pins — SX1262 LoRa radio (HPD17 module) +// Verified: schematic T3S3_V1.2 HPD17 pin4=SCK(IO5), pin5=MOSI(IO6), pin6=MISO(IO3) +#define MICROPY_HW_SPI1_MOSI (6) +#define MICROPY_HW_SPI1_MISO (3) +#define MICROPY_HW_SPI1_SCK (5) diff --git a/ports/esp32/boards/LILYGO_T3_S3/pins.csv b/ports/esp32/boards/LILYGO_T3_S3/pins.csv new file mode 100644 index 00000000000..4a4c1bbfe19 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/pins.csv @@ -0,0 +1,16 @@ +LORA_SCK,GPIO5 +LORA_MOSI,GPIO6 +LORA_MISO,GPIO3 +LORA_CS,GPIO7 +LORA_RST,GPIO8 +LORA_DIO1,GPIO33 +LORA_BUSY,GPIO34 +OLED_SDA,GPIO18 +OLED_SCL,GPIO17 +SD_MOSI,GPIO11 +SD_MISO,GPIO2 +SD_SCK,GPIO14 +SD_CS,GPIO13 +LED,GPIO37 +BUTTON,GPIO0 +VBAT_SENSE,GPIO1 diff --git a/ports/esp32/boards/LILYGO_T3_S3/sdkconfig.board b/ports/esp32/boards/LILYGO_T3_S3/sdkconfig.board new file mode 100644 index 00000000000..5f04a13dae7 --- /dev/null +++ b/ports/esp32/boards/LILYGO_T3_S3/sdkconfig.board @@ -0,0 +1,3 @@ +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_LWIP_LOCAL_HOSTNAME="LILYGO-T3S3" From e57e5221827c3c71935df8cd6381ce54a1420b7a Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Fri, 27 Mar 2026 13:40:00 -0600 Subject: [PATCH 1920/2098] esp32/machine_sdcard: Make default SDMMC slot configurable. Resolves part of #18984. On ESP32-P4, SDMMC slot 0 supports SDIO 3.0, and is liekly the default for most boards. MICROPY_HW_SDMMC_DEFAULT_SLOT can be set in `mpconfigboard.h` to change default slot. Signed-off-by: Dryw Wade --- ports/esp32/machine_sdcard.c | 2 +- ports/esp32/mpconfigport.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 4785a7254a8..edecabaac76 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -214,7 +214,7 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args ARG_freq, }; #if SOC_SDMMC_HOST_SUPPORTED - static const int DEFAULT_SLOT = 1; + static const int DEFAULT_SLOT = MICROPY_HW_SDMMC_DEFAULT_SLOT; #else static const int DEFAULT_SLOT = SD_SLOT_MAX; #endif diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 97c44c8076f..938f26e27c6 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -195,6 +195,13 @@ #ifndef MICROPY_HW_ENABLE_SDCARD #define MICROPY_HW_ENABLE_SDCARD (1) #endif +#ifndef MICROPY_HW_SDMMC_DEFAULT_SLOT +#if CONFIG_IDF_TARGET_ESP32P4 +#define MICROPY_HW_SDMMC_DEFAULT_SLOT (0) +#else +#define MICROPY_HW_SDMMC_DEFAULT_SLOT (1) +#endif +#endif #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (esp_rom_get_cpu_ticks_per_us() * 1000000 / 200) // roughly #ifndef MICROPY_HW_ESP_NEW_I2C_DRIVER From 2f16bf430a7284e8f11990958c10b1b327ba6eac Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 20 Mar 2026 08:21:09 +0100 Subject: [PATCH 1921/2098] esp32/network_wlan: Look up IP addresses for the stations list. This commit extends the output of `WLAN.status('stations')` to also include the IP address of WiFi stations connected to the device if in AP mode. IP address lookup is currently not available on targets that use hosted WiFi (eg. ESP32P4), as the hosted API does not yet support performing the MAC->IP lookup on the WiFi MCU end. The ESP32 port is brought up to parity with most ports in this regard (primarily with the ESP8266). This functionality depends on the DHCP server provided by ESP-IDF, which is automatically run on the WiFi interface if the device is put in AP mode. Currently the ESP32 port cannot run with said DHCP server disabled, so if a device does not request a static IP it may either have the wrong IP address returned (since the DHCP server assigned an address which is then discarded by the client), or it won't have any (ie. "0.0.0.0"). In the latter case `None` will be returned as the station's address instead. This implements #9203. Signed-off-by: Alessandro Gatti --- ports/esp32/network_wlan.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index 5433bf862f1..9824ea84fa9 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -37,11 +37,15 @@ #include "py/runtime.h" #include "py/mphal.h" #include "extmod/modnetwork.h" +#include "shared/netutils/netutils.h" #include "modnetwork.h" #include "esp_wifi.h" #include "esp_log.h" #include "esp_psram.h" +#if !CONFIG_ESP_HOSTED_ENABLED +#include "esp_wifi_ap_get_sta_list.h" +#endif #ifndef NO_QSTR #include "mdns.h" @@ -409,11 +413,28 @@ static mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { require_if(args[0], ESP_IF_WIFI_AP); wifi_sta_list_t station_list; esp_exceptions(esp_wifi_ap_get_sta_list(&station_list)); - wifi_sta_info_t *stations = (wifi_sta_info_t *)station_list.sta; + #if !CONFIG_ESP_HOSTED_ENABLED + wifi_sta_mac_ip_list_t mac_ip_list; + esp_exceptions(esp_wifi_ap_get_sta_list_with_ip(&station_list, &mac_ip_list)); + #endif mp_obj_t list = mp_obj_new_list(0, NULL); - for (int i = 0; i < station_list.num; ++i) { + #if CONFIG_ESP_HOSTED_ENABLED + int count = station_list.num; + wifi_sta_info_t *source = (wifi_sta_info_t *)station_list.sta; + #else + int count = mac_ip_list.num; + esp_netif_pair_mac_ip_t *source = (esp_netif_pair_mac_ip_t *)mac_ip_list.sta; + #endif + for (int i = 0; i < count; ++i) { + #if CONFIG_ESP_HOSTED_ENABLED mp_obj_tuple_t *t = mp_obj_new_tuple(1, NULL); - t->items[0] = mp_obj_new_bytes(stations[i].mac, sizeof(stations[i].mac)); + #else + mp_obj_tuple_t *t = mp_obj_new_tuple(2, NULL); + #endif + t->items[0] = mp_obj_new_bytes(source[i].mac, sizeof(source[i].mac)); + #if !CONFIG_ESP_HOSTED_ENABLED + t->items[1] = source[i].ip.addr != 0 ? netutils_format_ipv4_addr((uint8_t *)(&source[i].ip), NETUTILS_BIG) : mp_const_none; + #endif mp_obj_list_append(list, t); } return list; From f456a23b6296882c9e685f06f6876add18c74b1a Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 23 Mar 2026 14:12:01 -0600 Subject: [PATCH 1922/2098] esp32/machine_pin: Make all pins on ESP32-P4 output capable. All GPIO pins on the ESP32-P4 are output capable, so there is no need to define GPIO_FIRST_NON_OUTPUT. Fixes #18982. Signed-off-by: Dryw Wade --- ports/esp32/machine_pin.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 74ee15a24ac..efe6733194c 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -55,8 +55,6 @@ #define GPIO_FIRST_NON_OUTPUT (34) #elif CONFIG_IDF_TARGET_ESP32S2 #define GPIO_FIRST_NON_OUTPUT (46) -#elif CONFIG_IDF_TARGET_ESP32P4 -#define GPIO_FIRST_NON_OUTPUT (54) #endif // Return the gpio_num_t index for a given machine_pin_obj_t pointer. From 295df7c0b40b2c35b21dd1d912bbbceb5f085a81 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 25 Mar 2026 21:58:03 +1100 Subject: [PATCH 1923/2098] github/workflows: Drop VS2017, use preinstalled v142 toolset for VS2019. The VS 2017 and VS 2019 MSVC CI jobs have been failing intermittently for months due to Chocolatey/Microsoft installer infrastructure issues when downloading old Visual Studio build tools onto windows-latest runners. These are not code failures - the build tools installation itself is unreliable (CDN timeouts, corrupt downloads, installer self-update loops). This removes VS 2017 from the matrix entirely and switches VS 2019 to use the v142 toolset that's already pre-installed on windows-2022 runners as a component of VS 2022. The PlatformToolset is now passed explicitly to MSBuild for both VS 2019 (v142) and VS 2022 (v143), and the Chocolatey installation step is removed. VS 2017 reached end-of-life in 2022, and the v141 toolset is no longer reliably installable on GitHub-hosted runners (actions/runner-images#9701, actions/runner-images#12764). The windows-2019 runner image was removed in June 2025. Docker containers are not supported on Windows runners, and the repo's 10GB Actions cache is already full, so caching the installation isn't feasible either. MSVC v143 is binary-compatible back to v141, so the testing value of v141 was marginal. The net effect is 16 MSVC jobs (down from 24) with zero installation steps and hopefully no more flaky failures. Signed-off-by: Andrew Leech --- .github/workflows/ports_windows.yml | 32 ++++++++++------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 5318a851981..3a7a38df322 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -25,23 +25,24 @@ jobs: platform: [x86, x64] configuration: [Debug, Release] variant: [dev, standard] - visualstudio: ['2017', '2019', '2022'] + visualstudio: ['2019', '2022'] include: - - visualstudio: '2017' - vs_version: '[15, 16)' - custom_vs_install: true - visualstudio: '2019' - vs_version: '[16, 17)' - custom_vs_install: true + # The v142 toolset (VS 2019 compiler) is pre-installed on + # windows-2022 as a component of VS 2022. Use VS 2022's + # MSBuild and select the v142 toolset via PlatformToolset. + vs_version: '[17, 18)' + platform_toolset: v142 - visualstudio: '2022' vs_version: '[17, 18)' + platform_toolset: v143 # trim down the number of jobs in the matrix exclude: - variant: standard configuration: Debug - visualstudio: '2019' configuration: Debug - runs-on: windows-latest + runs-on: windows-2022 env: CI_BUILD_CONFIGURATION: ${{ matrix.configuration }} steps: @@ -51,31 +52,20 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.11' - - name: Install Visual Studio ${{ matrix.visualstudio }} - if: matrix.custom_vs_install - shell: bash - # Shell functions in this block are to retry intermittent corrupt - # downloads (with a clean download dir) before failing the job outright - run: | - try () { ($@) || ($@) || ($@) || ($@) } - clean_install () ( rm -rf $TEMP/chocolatey; choco install $1 ) - try clean_install visualstudio${{ matrix.visualstudio }}buildtools - try clean_install visualstudio${{ matrix.visualstudio }}-workload-vctools - try clean_install windows-sdk-8.1 - uses: microsoft/setup-msbuild@v2 with: vs-version: ${{ matrix.vs_version }} - uses: actions/checkout@v6 - name: Build mpy-cross.exe - run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} + run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PlatformToolset=${{ matrix.platform_toolset }} - name: Update submodules run: git submodule update --init lib/micropython-lib - name: Build micropython.exe - run: msbuild ports\windows\micropython.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }} + run: msbuild ports\windows\micropython.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }} -property:PlatformToolset=${{ matrix.platform_toolset }} - name: Get micropython.exe path id: get_path run: | - $exePath="$(msbuild ports\windows\micropython.vcxproj -nologo -v:m -t:ShowTargetPath -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }})" + $exePath="$(msbuild ports\windows\micropython.vcxproj -nologo -v:m -t:ShowTargetPath -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }} -property:PlatformToolset=${{ matrix.platform_toolset }})" echo ("micropython=" + $exePath.Trim()) >> $env:GITHUB_OUTPUT - name: Run tests id: test From da0149f629648643cc68a2bae3825355e2579799 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 25 Mar 2026 15:25:31 +1100 Subject: [PATCH 1924/2098] github: Improve error on "Build mpremote wheel" if fork is missing tags. Avoids a vague error from the 'vcs' versioning scheme if recent tags haven't been pushed to the fork. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/mpremote.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mpremote.yml b/.github/workflows/mpremote.yml index 9f837c9902e..ad5dd454905 100644 --- a/.github/workflows/mpremote.yml +++ b/.github/workflows/mpremote.yml @@ -20,7 +20,17 @@ jobs: - name: Install build tools run: pip install build - name: Build mpremote wheel - run: cd tools/mpremote && python -m build --wheel + run: | + if ! git rev-parse --verify -q v1.23.0 >/dev/null; then + echo "::error::mpremote wheel build requires recent MicroPython version tags in the forked repo." + echo "" + echo "To fix, push tags from upstream:" + echo " git remote add upstream https://github.com/micropython/micropython.git" + echo " git fetch upstream --tags" + echo " git push origin --tags" + exit 1 + fi + cd tools/mpremote && python -m build --wheel - name: Archive mpremote wheel uses: actions/upload-artifact@v7 with: From b0d207211428187b7b8ce764273dca192fa77b30 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 25 Mar 2026 15:38:11 +1100 Subject: [PATCH 1925/2098] github: Ignore codecov upload failure on forks with no token set. If you enable Actions on your fork of MicroPython then it consistently fails the Coverage Upload step (unless you set a codecov token in your fork). This means you get a failure email each time you push to your own repo. This commit fixes that. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_unix.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index bd04163ded8..aa9f5bce0ab 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -95,8 +95,12 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: - fail_ci_if_error: true + # Only fail the job on error if a token is set, or we're running against upstream repo. + # This avoids the annoying situation of the job failing on every push to a fork (if no token is set). + fail_ci_if_error: ${{ secrets.CODECOV_TOKEN != '' || github.repository_owner == 'micropython' }} verbose: true + # note: when a fork opens a PR into MicroPython repo, the pull_request trigger can't access + # secrets so this token value will be empty (codecov will do a 'tokenless' upload). token: ${{ secrets.CODECOV_TOKEN }} - name: Print failures if: failure() From ba28b8f2315ab1d8e1e054b450e07398b841226f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:35:29 +0000 Subject: [PATCH 1926/2098] github/workflows: Bump microsoft/setup-msbuild from 2 to 3. Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 2 to 3. - [Release notes](https://github.com/microsoft/setup-msbuild/releases) - [Commits](https://github.com/microsoft/setup-msbuild/compare/v2...v3) --- updated-dependencies: - dependency-name: microsoft/setup-msbuild dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ports_windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 3a7a38df322..1243366d413 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -52,7 +52,7 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.11' - - uses: microsoft/setup-msbuild@v2 + - uses: microsoft/setup-msbuild@v3 with: vs-version: ${{ matrix.vs_version }} - uses: actions/checkout@v6 From 8c6dfa5bd437875b39f05cbb86e2891fa08ce8c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:00:37 +0000 Subject: [PATCH 1927/2098] github/workflows: Bump codecov/codecov-action from 5 to 6. Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ports_unix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index aa9f5bce0ab..57f4a964b2f 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -93,7 +93,7 @@ jobs: (cd ports/unix && gcov -o build-coverage/py ../../py/*.c || true) (cd ports/unix && gcov -o build-coverage/extmod ../../extmod/*.c || true) - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: # Only fail the job on error if a token is set, or we're running against upstream repo. # This avoids the annoying situation of the job failing on every push to a fork (if no token is set). From a179cb6913dd73ce95e4d30c690bd12cae075309 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 26 Mar 2026 14:07:30 +1100 Subject: [PATCH 1928/2098] tests: Use a unique domain name for tests/multi_wlan/getaddrinfo. Otherwise test can fail if the "should fail" lookup is resolved from a cached result. Signed-off-by: Angus Gratton --- tests/multi_wlan/getaddrinfo.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/multi_wlan/getaddrinfo.py b/tests/multi_wlan/getaddrinfo.py index 6b95c7e6f08..8ad6155ed0e 100644 --- a/tests/multi_wlan/getaddrinfo.py +++ b/tests/multi_wlan/getaddrinfo.py @@ -20,8 +20,11 @@ def instance0(): multitest.next() + # Note: Lookup domain for this test doesn't need to exist, as the board + # isn't internet connected. It also shouldn't exist, so a cached result is + # never returned! try: - socket.getaddrinfo("micropython.org", 80) + socket.getaddrinfo("doesnotexist.example.com", 80) except OSError as er: print( "active(0) failed" @@ -30,7 +33,7 @@ def instance0(): wlan.active(1) try: - socket.getaddrinfo("micropython.org", 80) + socket.getaddrinfo("doesnotexist.example.com", 80) except OSError as er: print( "active(1) failed", er.errno in (-2, -202) From 6d327a1a845cd89efc1f51e737d88cc3391002e3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Mar 2026 16:56:27 +1100 Subject: [PATCH 1929/2098] mimxrt: Provide abort so that C++ libunwind can link. Also add missing SRC_CXX support to the mimxrt build, same as on other ports. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/mimxrt/Makefile | 3 ++- ports/mimxrt/main.c | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 03a07627b63..666964ae617 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -396,7 +396,7 @@ hal/resethandler_MIMXRT10xx.S: $(GEN_FLEXRAM_CONFIG_SRC) # ============================================================================= # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(GEN_PINS_SRC) +SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(SRC_CXX) $(GEN_PINS_SRC) # ============================================================================= # Compiler Flags @@ -547,6 +547,7 @@ LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 664d1c4c16f..d8ccd1e9798 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -217,6 +217,11 @@ void nlr_jump_fail(void *val) { } } +void abort(void) { + for (;;) { + } +} + #ifndef NDEBUG void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) { mp_printf(MP_PYTHON_PRINTER, "Assertion '%s' failed, at file %s:%d\n", expr, file, line); From 523ad2c6f0d1399c2a32e397d997b1e246bb585a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Mar 2026 17:19:03 +1100 Subject: [PATCH 1930/2098] tools/ci.sh: Build the usercmodule example in CI for more ports. Adds build coverage of C++ compilation on the following ports: mimxrt, renesas-ra, esp8266, windows (mingw32). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tools/ci.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index 056f6a6102d..872b73ac2aa 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -287,7 +287,7 @@ function ci_esp8266_build { make ${MAKEOPTS} -C ports/esp8266 submodules make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_512K - make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_1M + make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_1M USER_C_MODULES=../../examples/usercmodule # Test building native .mpy with xtensa architecture. ci_native_mpy_modules_build xtensa @@ -324,7 +324,7 @@ function ci_mimxrt_build { make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK submodules make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 submodules - make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 + make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK submodules make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK CFLAGS_EXTRA=-DMICROPY_HW_USB_MSC=1 } @@ -462,7 +462,7 @@ function ci_renesas_ra_board_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/renesas-ra submodules make ${MAKEOPTS} -C ports/renesas-ra BOARD=RA4M1_CLICKER - make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M2 + make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M2 USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M1 make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA4M1 make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA4W1 @@ -729,6 +729,7 @@ function ci_unix_coverage_setup { } function ci_unix_coverage_build { + # note: the coverage variant incorporates ../../examples/usercmodule, set in mpconfigvariant.mk ci_unix_build_helper VARIANT=coverage ci_unix_build_ffi_lib_helper gcc } @@ -987,13 +988,13 @@ function ci_unix_repr_b_run_tests { function ci_windows_setup { sudo apt-get update - sudo apt-get install gcc-mingw-w64 + sudo apt-get install gcc-mingw-w64 g++-mingw-w64 } function ci_windows_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/windows submodules - make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- + make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=x86_64-w64-mingw32- BUILD=build-standard-w64 } From c080565170876edf28066e4a0797e9a29a20b1bb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Mar 2026 16:59:21 +1100 Subject: [PATCH 1931/2098] ports: Refactor C++ compilation support. Move the common CXXFLAGS and LDFLAGS into py/mkrules.mk Changes ports minimal, esp8266, mimxrt, renesas-ra, samd, stm32, unix, windows (mingw32). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp8266/Makefile | 3 --- ports/mimxrt/Makefile | 9 --------- ports/minimal/Makefile | 3 --- ports/renesas-ra/Makefile | 9 --------- ports/samd/Makefile | 9 --------- ports/stm32/Makefile | 9 --------- ports/unix/Makefile | 2 -- ports/windows/Makefile | 2 -- py/mkrules.mk | 12 ++++++++++++ 9 files changed, 12 insertions(+), 46 deletions(-) diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index a0d4d769d43..b961c76fdaf 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -107,9 +107,6 @@ COPT += -Os -DNDEBUG LDFLAGS += --gc-sections endif -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS)) - # Options for mpy-cross MPY_CROSS_FLAGS += -march=xtensa diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 666964ae617..a57ff68a497 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -448,15 +448,6 @@ CFLAGS += \ -Wfloat-conversion \ -Wno-error=unused-parameter -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) - -# TODO make this common -- shouldn't be using these "private" vars from py.mk -ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) -LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" -LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" -endif - # Configure respective board flash type # Add hal/flexspi_nor_flash.h or hal/flexspi_hyper_flash.h respectively CFLAGS += -DBOARD_FLASH_OPS_HEADER_H=\"hal/flexspi_$(subst qspi_,,$(FLEXSPI_FLASH_TYPE)).h\" diff --git a/ports/minimal/Makefile b/ports/minimal/Makefile index bb05798eaf5..aac7ce73b8b 100644 --- a/ports/minimal/Makefile +++ b/ports/minimal/Makefile @@ -47,9 +47,6 @@ CFLAGS += -Os -DNDEBUG CFLAGS += -fdata-sections -ffunction-sections endif -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) - LIBS = SRC_C = \ diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index 77a09502d1a..fed0f996598 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -148,15 +148,6 @@ else COPT += -Os -DNDEBUG endif -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS)) - -# TODO make this common -- shouldn't be using these "private" vars from py.mk -ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) -LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" -LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" -endif - # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m diff --git a/ports/samd/Makefile b/ports/samd/Makefile index ada550bf129..9204317aa41 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -108,15 +108,6 @@ ifeq ($(MICROPY_HW_ROMFS_BYTES),0) CFLAGS += -DMICROPY_VFS_ROM=0 endif -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) - -# TODO make this common -- shouldn't be using these "private" vars from py.mk -ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) -LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" -LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" -endif - MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH) SRC_C += \ diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 8c81b058d0a..4c9366b8bdc 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -189,15 +189,6 @@ endif COPT ?= -Os -DNDEBUG endif -# Flags for optional C++ source code -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS)) - -# TODO make this common -- shouldn't be using these "private" vars from py.mk -ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) -LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" -LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" -endif - # Options for mpy-cross MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH_$(MCU_SERIES)) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 70e4f339172..7d71fc3f955 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -239,8 +239,6 @@ ifneq ($(FROZEN_MANIFEST),) CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs endif -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS) $(CXXFLAGS_MOD)) - ifeq ($(MICROPY_FORCE_32BIT),1) RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' endif diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 4129b7fe2cc..8d371ed8ac3 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -104,8 +104,6 @@ ifeq ($(shell $(CC) -dumpmachine),i686-w64-mingw32) CFLAGS += -msse -mfpmath=sse -march=pentium4 endif -CXXFLAGS += $(filter-out -std=gnu99,$(CFLAGS)) - include $(TOP)/py/mkrules.mk .PHONY: test test_full diff --git a/py/mkrules.mk b/py/mkrules.mk index 4968ed58b03..6735526186e 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -38,6 +38,18 @@ CFLAGS += -DMICROPY_BOARD_BUILD_NAME=\"$(BOARD)-$(BOARD_VARIANT)\" endif endif +# Add default C++ compiler flags based on CFLAGS. For use with C++ user modules. +CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99 -std=c99,$(CFLAGS) $(CXXFLAGS_MOD)) + +# Add LDFLAGS to link libstdc++ on bare metal ports. Added only if a port has +# -nostdlib in LDFLAGS and C++ source files are provided. +ifneq ($(findstring nostdlib,"$(LDFLAGS)"),) +ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) +LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" +LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" +endif +endif + # QSTR generation uses the same CFLAGS, with these modifications. QSTR_GEN_FLAGS = -DNO_QSTR # Note: := to force evaluation immediately. From 5ce74c3f7f0b3445ad680631497b44d2d2ac9191 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 5 Mar 2026 17:07:12 +1100 Subject: [PATCH 1932/2098] tests/misc: Skip cexample_subclass.py on minimal unix variant. Signed-off-by: Angus Gratton --- tests/misc/cexample_subclass.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/misc/cexample_subclass.py b/tests/misc/cexample_subclass.py index 9f52a2c737a..6857788e5dc 100644 --- a/tests/misc/cexample_subclass.py +++ b/tests/misc/cexample_subclass.py @@ -2,6 +2,7 @@ try: from cexample import AdvancedTimer + import time # used to skip this test on minimal unix variant except ImportError: print("SKIP") raise SystemExit From bc92f5a249f753089c8d79fb4733a16a33ec563d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 25 Mar 2026 14:38:27 +1100 Subject: [PATCH 1933/2098] nrf: Add support for building C++ user modules. The compiler flags -ansi -std=c11 were both being passed to g++, causing weirdness. Removed the -ansi flag entirely, it's superseded by explicitly setting the standard. Also adds build coverage for user C modules (including C++) in CI. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/nrf/Makefile | 2 +- py/mkrules.mk | 2 +- tools/ci.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index a5304763df8..d35eeed47ab 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -139,7 +139,7 @@ LDFLAGS += -Wl,--gc-sections endif CFLAGS += $(CFLAGS_MCU_$(MCU_SERIES)) -CFLAGS += $(INC) -Wall -Werror -ansi -std=c11 $(COPT) $(NRF_DEFINES) $(CFLAGS_EXTRA) +CFLAGS += $(INC) -Wall -Werror -std=c11 $(COPT) $(NRF_DEFINES) $(CFLAGS_EXTRA) CFLAGS += -fno-strict-aliasing CFLAGS += -I$(BOARD_DIR) CFLAGS += -DNRF5_HAL_H='<$(MCU_VARIANT)_hal.h>' diff --git a/py/mkrules.mk b/py/mkrules.mk index 6735526186e..6ac731b368b 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -39,7 +39,7 @@ endif endif # Add default C++ compiler flags based on CFLAGS. For use with C++ user modules. -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99 -std=c99,$(CFLAGS) $(CXXFLAGS_MOD)) +CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99 -std=c99 -std=c11,$(CFLAGS) $(CXXFLAGS_MOD)) # Add LDFLAGS to link libstdc++ on bare metal ports. Added only if a port has # -nostdlib in LDFLAGS and C++ source files are provided. diff --git a/tools/ci.sh b/tools/ci.sh index 872b73ac2aa..063188fe9f2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -341,7 +341,7 @@ function ci_nrf_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/nrf submodules make ${MAKEOPTS} -C ports/nrf BOARD=PCA10040 - make ${MAKEOPTS} -C ports/nrf BOARD=MICROBIT + make ${MAKEOPTS} -C ports/nrf BOARD=MICROBIT USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/nrf BOARD=PCA10056 SD=s140 make ${MAKEOPTS} -C ports/nrf BOARD=PCA10090 } From e379cb447926114d0a200f65e4be41f5149ab1e2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 25 Mar 2026 14:39:34 +1100 Subject: [PATCH 1934/2098] rp2: Allow setting MICROPY_C_HEAP_SIZE from make command line. This isn't a necessary change, but allows make MICROPY_C_HEAP_SIZE=nnn as an alternative to setting it in a board's .cmake file. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 2895faaca61..0ba6c81a006 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -56,6 +56,10 @@ ifdef MICROPY_PREVIEW_VERSION_2 CMAKE_ARGS += -DMICROPY_PREVIEW_VERSION_2=1 endif +ifdef MICROPY_C_HEAP_SIZE +CMAKE_ARGS += -DMICROPY_C_HEAP_SIZE=$(MICROPY_C_HEAP_SIZE) +endif + HELP_PICO_SDK_SUBMODULE ?= "\033[1;31mError: pico-sdk submodule is not initialized.\033[0m Run 'make submodules'" HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" From 3eb617da024f11b3ebd20adf4fc8fd8857a4ae1a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 25 Mar 2026 14:46:58 +1100 Subject: [PATCH 1935/2098] docs/develop/cmodules: Add some notes about C dynamic memory and C++. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/develop/cmodules.rst | 76 +++++++++++++++++++++++++++++++++- docs/reference/constrained.rst | 5 +++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index c5aa919b764..2a94cf84389 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -8,8 +8,8 @@ limitations with the Python environment, often due to an inability to access certain hardware resources or Python speed limitations. If your limitations can't be resolved with suggestions in :ref:`speed_python`, -writing some or all of your module in C (and/or C++ if implemented for your port) -is a viable option. +writing some or all of your module in C (and/or +:ref:`C++ if implemented for your port`) is a viable option. If your module is designed to access or work with commonly available hardware or libraries please consider implementing it inside the MicroPython @@ -285,3 +285,75 @@ can now be accessed in Python just like any other builtin module, e.g. sleep_ms(1000) print(watch.time()) # should display approximately 1000 + + +.. _c_heap: + +C Dynamic Memory Allocation +--------------------------- + +MicroPython uses its own "Python heap" for `memorymanagement`, +which is not the same as the "C heap" used by C library functions ``malloc()``, +``free()``, etc. Not every MicroPython port comes with a "C heap" at all. + +Tier 1 & 2 ports have varying support for C dynamic memory allocation via a "C +heap": + +- unix, windows, esp32 and webassembly ports support C dynamic memory + allocation. +- rp2 port will fail to allocate any memory at runtime unless the firmware is + built with ``MICROPY_C_HEAP_SIZE=n`` to reserve ``n`` bytes of memory for a C + heap. This memory will not be available for Python code to use. +- alif, mimxrt, nrf, renesas-ra, samd, and stm32 port builds that include + dynamic C allocation will fail at link-time with errors such as ``undefined + reference to `malloc'``. MicroPython has no built-in support for dynamic C + allocation on these ports. Any solution requires manually adding a C heap + implementation to the custom build. +- zephyr port currently does not support building with user modules. + +Python heap as C heap +~~~~~~~~~~~~~~~~~~~~~ + +It may be practical for C code to call "Python heap" dynamic allocation +functions such ``m_malloc()``, ``m_malloc0()`` and ``m_free()`` instead. + +Buffers allocated from the "Python heap" in this way are freed during a +:ref:`soft_reset`. It's important to ensure any C static variables +don't become "dangling pointers" after a soft reset. + +.. _cxx_support: + +C++ Modules +----------- + +Most Tier 1 & 2 MicroPython ports (and some Tier 3) support building C++ user +modules, using the C++-specific environment variables described above. + +Integrating C++ and MicroPython successfully involves some additional +considerations: + +C++ Dynamic Memory Allocation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +C++ programs (as well as C++ Standard Library features) typically use dynamic +memory allocation. The C++ default memory allocator (i.e. operators ``new`` and +``delete``) is typically implemented as a layer on top of `c_heap`. + +For MicroPython ports which don't include C dynamic memory allocation support, +C++ dynamic memory allocation can be supported in one of two ways: + +- Implement C dynamic memory allocation in your custom build. +- Implement a custom C++ allocator in your custom build. + +Linkage Considerations +~~~~~~~~~~~~~~~~~~~~~~ + +Because MicroPython is a C-based project, any symbols which link to or from +MicroPython need to be qualified ``extern "C"`` in C++ code. + +It's strongly recommended to follow the pattern demonstrated in +`examples/usercmodule/cppexample +`_, +where the Python module is implemented in a minimal C file wrapper around the +C++ code. + diff --git a/docs/reference/constrained.rst b/docs/reference/constrained.rst index d9a349aa6bb..a53f0858c19 100644 --- a/docs/reference/constrained.rst +++ b/docs/reference/constrained.rst @@ -316,6 +316,11 @@ following periodically: gc.collect() gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) +For more technical details, see :doc:`/develop/memorymgt`. + +Note: The heap used by MicroPython is a specialised "Python heap", separate from +any heap used for :ref:`c_heap`. + Fragmentation ~~~~~~~~~~~~~ From 4f04f08903b7f3336ea95adb7a037e7a0471fdc0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 1 Apr 2026 11:08:35 +1100 Subject: [PATCH 1936/2098] docs/develop/memorymgt: Expand on MicroPython memory management from C. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/develop/cmodules.rst | 4 +- docs/develop/memorymgt.rst | 88 ++++++++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index 2a94cf84389..ec20e65f477 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -317,9 +317,7 @@ Python heap as C heap It may be practical for C code to call "Python heap" dynamic allocation functions such ``m_malloc()``, ``m_malloc0()`` and ``m_free()`` instead. -Buffers allocated from the "Python heap" in this way are freed during a -:ref:`soft_reset`. It's important to ensure any C static variables -don't become "dangling pointers" after a soft reset. +See `python_memory_from_c` for more information about this approach. .. _cxx_support: diff --git a/docs/develop/memorymgt.rst b/docs/develop/memorymgt.rst index 5b1690cc827..df9f893977f 100644 --- a/docs/develop/memorymgt.rst +++ b/docs/develop/memorymgt.rst @@ -4,25 +4,19 @@ Memory Management ================= Unlike programming languages such as C/C++, MicroPython hides memory management -details from the developer by supporting automatic memory management. -Automatic memory management is a technique used by operating systems or applications to automatically manage -the allocation and deallocation of memory. This eliminates challenges such as forgetting to -free the memory allocated to an object. Automatic memory management also avoids the critical issue of using memory -that is already released. Automatic memory management takes many forms, one of them being -garbage collection (GC). +details from the developer by supporting automatic memory management of a +":ref:`Python heap`" that holds all Python objects. MicroPython uses +garbage collection (GC) for automatic memory management. The garbage collector +is responsible for freeing memory which is no longer in use. -The garbage collector usually has two responsibilities; +Specifically, MicroPython uses a `Mark and Sweep +`_ +garbage collection algorithm. This algorithm has a mark phase that scans the +heap marking all live objects, and then a sweep phase goes through the heap +reclaiming all unmarked objects. -#. Allocate new objects in available memory. -#. Free unused memory. - -There are many GC algorithms but MicroPython uses the -`Mark and Sweep `_ -policy for managing memory. This algorithm has a mark phase that traverses the heap marking all -live objects while the sweep phase goes through the heap reclaiming all unmarked objects. - -Garbage collection functionality in MicroPython is available through the ``gc`` built-in -module: +The MicroPython garbage collector is normally automatic, but manual control is +available through the :mod:`gc` built-in module: .. code-block:: bash @@ -40,7 +34,65 @@ module: >>> gc.disable() >>> -Even when ``gc.disable()`` is invoked, collection can be triggered with ``gc.collect()``. +Even when ``gc.disable()`` is invoked, collection can be manually triggered with +``gc.collect()``. + +.. _python_memory_from_c: + +MicroPython Memory from C code +------------------------------ + +Awareness of the garbage collector is needed when writing C code that allocates +memory from the "Python heap" (i.e. functions ``m_malloc()``, ``m_malloc0()``, +``m_free()``, etc). + +The mark phase of the garbage collector scans for live pointers to heap memory +starting from the following roots: + +- The stack of the main Python runtime (or REPL). +- The stacks of each "Python thread", for ports which implement Python threads + on top of native operating system threads or tasks. +- The "root pointers" defined in C code using the macro + ``MP_REGISTER_ROOT_POINTER``. These are the recommended way to have statically + scoped pointers to the Python heap. +- Tracked allocations made with the ``m_tracked_calloc()``, ``m_tracked_realloc`` + and ``m_tracked_free()`` functions. These special functions allow allocating a + block of memory which is always considered live by the garbage collector. + Similar to memory allocation in C, this memory is only freed by calling + ``m_tracked_free()`` or by soft reset. There is a small memory usage and + runtime overhead to each tracked allocation. This feature is not enabled by + default on all ports. + +The garbage collector then recursively scans and marks all the memory pointed to +by the root pointers, until all addresses are exhausted. This is sufficient to +find all Python objects that are still in use by the MicroPython runtime. + +However, the following memory will **not** be scanned by the garbage collector +and could be freed prematurely: + +- Static or global C variables which contain pointers to heap memory. +- Pointers which don't point to the "head" of an allocated buffer (i.e. to the + exact address returned by ``m_malloc()``), but instead to an address inside + the allocated buffer (for example, a pointer to a nested struct). For + performance reasons, the garbage collector doesn't mark the enclosing buffer + in these cases. +- The stack of any thread or RTOS task which isn't running Python code or + manually registered as a "Python thread" (for ports which support native + threads or tasks). + +Ways to avoid use-after-free in these scenarios: + +- Use the tracked allocation API ``m_tracked_calloc()``, ``m_tracked_realloc()`` + and ``m_tracked_free()``. +- Register a root pointer (see above), instead of storing a pointer in a static + variable. +- Restructure the code, for example by having an API where Python code + initialises a singleton Python object (implemented in C) which holds all of the + relevant pointers instead of having them in static variables. + +.. note:: :ref:`soft_reset` always clears the Python heap and frees all memory. + It's important not to hold any pointers to the heap after a soft + reset, as they will become dangling pointers to freed memory. The object model ---------------- From 50218dd2a879f4dcd0861a2486aab665a058e0f1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 1 Apr 2026 11:32:29 +1100 Subject: [PATCH 1937/2098] docs: Link memory management & gc docs more, reduce duplication. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/develop/memorymgt.rst | 27 ++++++--------------------- docs/library/gc.rst | 29 ++++++++++++++++++++++++++++- docs/library/micropython.rst | 29 +++++++++++++++++++++++++++-- docs/reference/constrained.rst | 31 ++++++------------------------- 4 files changed, 67 insertions(+), 49 deletions(-) diff --git a/docs/develop/memorymgt.rst b/docs/develop/memorymgt.rst index df9f893977f..4ae80120044 100644 --- a/docs/develop/memorymgt.rst +++ b/docs/develop/memorymgt.rst @@ -15,27 +15,8 @@ garbage collection algorithm. This algorithm has a mark phase that scans the heap marking all live objects, and then a sweep phase goes through the heap reclaiming all unmarked objects. -The MicroPython garbage collector is normally automatic, but manual control is -available through the :mod:`gc` built-in module: - -.. code-block:: bash - - >>> x = 5 - >>> x - 5 - >>> import gc - >>> gc.enable() - >>> gc.mem_alloc() - 1312 - >>> gc.mem_free() - 2071392 - >>> gc.collect() - 19 - >>> gc.disable() - >>> - -Even when ``gc.disable()`` is invoked, collection can be manually triggered with -``gc.collect()``. +The MicroPython garbage collector is by default automatic, but manual control is +available through the :mod:`gc` built-in module. .. _python_memory_from_c: @@ -94,6 +75,10 @@ Ways to avoid use-after-free in these scenarios: It's important not to hold any pointers to the heap after a soft reset, as they will become dangling pointers to freed memory. + Some ports support a "C heap" as well (see `c_heap`), in which case + you can allocate memory that will stay valid over soft reset by + calling standard C functions ``malloc``, etc. + The object model ---------------- diff --git a/docs/library/gc.rst b/docs/library/gc.rst index 2ba204e2293..cf669b96f3d 100644 --- a/docs/library/gc.rst +++ b/docs/library/gc.rst @@ -2,7 +2,8 @@ ========================================== .. module:: gc - :synopsis: control the garbage collector + :synopsis: control the garbage collector which automatically frees + :ref:`heap memory ` |see_cpython_module| :mod:`python:gc`. @@ -64,3 +65,29 @@ Functions This function is a MicroPython extension. CPython has a similar function - ``set_threshold()``, but due to different GC implementations, its signature and semantics are different. + +Example +------- + +.. code-block:: bash + + >>> import gc + >>> gc.mem_free() # Gets number of bytes free in memory + 8192 + >>> gc.mem_alloc() # Gets number of bytes allocated in memory + 1024 + >>> foo = bytearray(1000) # Create a big array of data + >>> gc.mem_free() # Show that there's less memory available + 7168 + >>> gc.mem_alloc() # Show that there's more memory used + 2048 + >>> del foo # Delete the object + >>> gc.mem_free() # Show that collection hasn't run yet + 7168 + >>> gc.mem_alloc() # That memory is still allocated + 2048 + >>> gc.collect() # Manually run the collection + >>> gc.mem_free() # Now we have reclaimed that memory + 8192 + >>> gc.mem_alloc() # That memory is no longer allocated + 1024 diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index 2da9a6e3fd7..65fa204315a 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -62,8 +62,33 @@ Functions is given then extra information is printed. The information that is printed is implementation dependent, but currently - includes the amount of stack and heap used. In verbose mode it prints out - the entire heap indicating which blocks are used and which are free. + includes the amount of stack and heap used. In verbose mode it prints out a + summary of the entire heap indicating which blocks are used and which are + free. + + The exact output of verbose mode varies between ports, but in general each + letter represents a single 16 byte block of memory. Each line of + output represents 0x400 bytes or 1KiB of RAM. + + The meaning of each letter: + + ====== ================= + Symbol Meaning + ====== ================= + . free block + h head block + = tail block + m marked head block + T tuple + L list + D dict + F float + B byte code + M module + S string or bytes + A bytearray + ====== ================= + .. function:: qstr_info([verbose]) diff --git a/docs/reference/constrained.rst b/docs/reference/constrained.rst index a53f0858c19..616dc8833fd 100644 --- a/docs/reference/constrained.rst +++ b/docs/reference/constrained.rst @@ -316,10 +316,11 @@ following periodically: gc.collect() gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) -For more technical details, see :doc:`/develop/memorymgt`. +For more information, see below and the documentation for built-in module +:mod:`gc`. -Note: The heap used by MicroPython is a specialised "Python heap", separate from -any heap used for :ref:`c_heap`. +For details from MicroPython internals/developer perspective, see also +:doc:`/develop/memorymgt`. Fragmentation ~~~~~~~~~~~~~ @@ -381,28 +382,8 @@ Running the function uses over 10KiB, but on return ``a`` is garbage because it is out of scope and cannot be referenced. The final `gc.collect()` recovers that memory. -The final output produced by ``micropython.mem_info(1)`` will vary in detail but -may be interpreted as follows: - -====== ================= -Symbol Meaning -====== ================= - . free block - h head block - = tail block - m marked head block - T tuple - L list - D dict - F float - B byte code - M module - S string or bytes - A bytearray -====== ================= - -Each letter represents a single block of memory, a block being 16 bytes. So each -line of the heap dump represents 0x400 bytes or 1KiB of RAM. +The verbose output from ``micropython.mem_info(1)`` is documented at +`micropython.mem_info()`. Control of garbage collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From c9bb9250db9579cce1772cff9cf69c8d17031a9d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 1 Apr 2026 11:42:50 +1100 Subject: [PATCH 1938/2098] docs/develop: Link to module docs, remove extraneous example code. Small cleanup. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/develop/library.rst | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/develop/library.rst b/docs/develop/library.rst index 830211d81c8..6ff9bfe023d 100644 --- a/docs/develop/library.rst +++ b/docs/develop/library.rst @@ -26,15 +26,9 @@ Implementing a core module -------------------------- Like CPython, MicroPython has core builtin modules that can be accessed through import statements. -An example is the ``gc`` module discussed in :ref:`memorymanagement`. +An example is the :mod:`gc` module discussed in :ref:`memorymanagement`. -.. code-block:: bash - - >>> import gc - >>> gc.enable() - >>> - -MicroPython has several other builtin standard/core modules like ``io``, ``array`` etc. +MicroPython has several other builtin standard/core modules like :mod:`io`, :mod:`array`, etc. Adding a new core module involves several modifications. First, create the ``C`` file in the ``py/`` directory. In this example we are adding a From 5e7d4bf9a654c538d32382e9750e558e5772082b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Apr 2026 11:26:27 +1000 Subject: [PATCH 1939/2098] docs/library/gc: Add documentation for gc.isenabled(). Signed-off-by: Angus Gratton --- docs/library/gc.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/library/gc.rst b/docs/library/gc.rst index cf669b96f3d..cc2288943c7 100644 --- a/docs/library/gc.rst +++ b/docs/library/gc.rst @@ -19,6 +19,10 @@ Functions Disable automatic garbage collection. Heap memory can still be allocated, and garbage collection can still be initiated manually using :meth:`gc.collect`. +.. function:: isenabled() + + Returns True if automatic garbage collection is enabled, and False otherwise. + .. function:: collect() Run a garbage collection. From 35652ff03c1479d51b66b1bd6b86419c52434761 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Apr 2026 11:30:20 +1000 Subject: [PATCH 1940/2098] docs/library/gc: Add some simple examples for gc.threshold(). Signed-off-by: Angus Gratton --- docs/library/gc.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/library/gc.rst b/docs/library/gc.rst index cc2288943c7..919f82ed520 100644 --- a/docs/library/gc.rst +++ b/docs/library/gc.rst @@ -70,6 +70,17 @@ Functions function - ``set_threshold()``, but due to different GC implementations, its signature and semantics are different. + Examples + ^^^^^^^^ + + To trigger a garbage collection each time 32768 bytes of RAM have been allocated in total:: + + gc.threshold(32768) + + To restore the default behaviour, only triggering garbage collection when out of memory:: + + gc.threshold(-1) + Example ------- From 1a8c835308037f25c63950fc85abbedf41b6de9f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 17:37:27 +0200 Subject: [PATCH 1941/2098] docs/library/machine.Pin: Update pull-up/down constants. This commit updates the documentation bits for pull-up/pull-down constants, part of the `machine.Pin` class. The documentation now mentions port-specific entries into a separate section, making the only three standard pull-up/pull-down values being `Pin.PULL_UP`, `Pin.PULL_DOWN`, and `None`. Signed-off-by: Alessandro Gatti --- docs/library/machine.Pin.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/library/machine.Pin.rst b/docs/library/machine.Pin.rst index d419c7ac27d..37dcac877fa 100644 --- a/docs/library/machine.Pin.rst +++ b/docs/library/machine.Pin.rst @@ -301,11 +301,22 @@ not all constants are available on all ports. .. data:: Pin.PULL_UP Pin.PULL_DOWN - Pin.PULL_HOLD Selects whether there is a pull up/down resistor. Use the value ``None`` for no pull. + Some ports have a different constants set that can be used to select + hardware-specific behaviour: + + - The esp8266 port does not have pull-down resistors on GPIO pins, hence + ``Pin.PULL_DOWN`` is not supported. + - The mimxrt port has several extra constants to enable different pull + modes: ``Pin.PULL_UP_22K`` enables a 22KΩ pull-up on the pin, + ``Pin.PULL_UP_47K`` enables a 47KΩ pull-up on the pin, and + ``Pin.PULL_HOLD`` that puts the pin into high-impedance mode. The + ``Pin.PULL_UP`` and ``Pin.PULL_DOWN`` constants will use a 100KΩ internal + resistor. + .. data:: Pin.DRIVE_0 Pin.DRIVE_1 Pin.DRIVE_2 From b67c55cb4f88e8b85f43b111c793eb46d089fe62 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 25 Mar 2026 21:47:37 +0100 Subject: [PATCH 1942/2098] py/emitinlinethumb: Shrink condition code lookups. This commit optimises the lookup of opcodes' condition codes, in order to take up less code than before. The original data was held in a table containing the condition code value (an incrementing 0-based integer) and the two condition ASCII characters. Given that the condition code value also matches the entry's index in the table, that can be safely omitted. This saves 52 bytes when compiled for Thumb. Signed-off-by: Alessandro Gatti --- py/emitinlinethumb.c | 47 +++++++++----------- tests/inlineasm/thumb/asmbcc.py | 56 +++++++++++++++++++----- tests/inlineasm/thumb/asmbcc.py.exp | 66 +++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 36 deletions(-) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index d6596337ae5..9c82921edfc 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -364,24 +364,15 @@ static int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_ return 0; } -typedef struct _cc_name_t { byte cc; - byte name[2]; -} cc_name_t; -static const cc_name_t cc_name_table[] = { - { ASM_THUMB_CC_EQ, { 'e', 'q' }}, - { ASM_THUMB_CC_NE, { 'n', 'e' }}, - { ASM_THUMB_CC_CS, { 'c', 's' }}, - { ASM_THUMB_CC_CC, { 'c', 'c' }}, - { ASM_THUMB_CC_MI, { 'm', 'i' }}, - { ASM_THUMB_CC_PL, { 'p', 'l' }}, - { ASM_THUMB_CC_VS, { 'v', 's' }}, - { ASM_THUMB_CC_VC, { 'v', 'c' }}, - { ASM_THUMB_CC_HI, { 'h', 'i' }}, - { ASM_THUMB_CC_LS, { 'l', 's' }}, - { ASM_THUMB_CC_GE, { 'g', 'e' }}, - { ASM_THUMB_CC_LT, { 'l', 't' }}, - { ASM_THUMB_CC_GT, { 'g', 't' }}, - { ASM_THUMB_CC_LE, { 'l', 'e' }}, +#define ENCODE_CC(c1, c2) (((uint16_t)(c1) << 8) | (uint16_t)(c2)) + +// Positions in the table match the condition code value. +static const uint16_t CONDITION_CODES[] = { + ENCODE_CC('e', 'q'), ENCODE_CC('n', 'e'), ENCODE_CC('c', 's'), + ENCODE_CC('c', 'c'), ENCODE_CC('m', 'i'), ENCODE_CC('p', 'l'), + ENCODE_CC('v', 's'), ENCODE_CC('v', 'c'), ENCODE_CC('h', 'i'), + ENCODE_CC('l', 's'), ENCODE_CC('g', 'e'), ENCODE_CC('l', 't'), + ENCODE_CC('g', 't'), ENCODE_CC('l', 'e'), }; typedef struct _format_4_op_t { byte op; @@ -574,9 +565,11 @@ static void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a || (op_len == 5 && op_str[3] == '_' && (op_str[4] == 'n' || (ARMV7M && op_str[4] == 'w'))))) { mp_uint_t cc = -1; - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { - if (op_str[1] == cc_name_table[i].name[0] && op_str[2] == cc_name_table[i].name[1]) { - cc = cc_name_table[i].cc; + uint16_t condition_code = ENCODE_CC(op_str[1], op_str[2]); + for (size_t i = 0; i < MP_ARRAY_SIZE(CONDITION_CODES); ++i) { + if (condition_code == CONDITION_CODES[i]) { + cc = i; + break; } } if (cc == (mp_uint_t)-1) { @@ -592,12 +585,14 @@ static void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } } else if (ARMV7M && op_str[0] == 'i' && op_str[1] == 't') { const char *arg_str = get_arg_str(pn_args[0]); + if (strlen(arg_str) != 2) { + goto unknown_op; + } mp_uint_t cc = -1; - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { - if (arg_str[0] == cc_name_table[i].name[0] - && arg_str[1] == cc_name_table[i].name[1] - && arg_str[2] == '\0') { - cc = cc_name_table[i].cc; + uint16_t condition_code = ENCODE_CC(arg_str[0], arg_str[1]); + for (size_t i = 0; i < MP_ARRAY_SIZE(CONDITION_CODES); ++i) { + if (condition_code == CONDITION_CODES[i]) { + cc = i; break; } } diff --git a/tests/inlineasm/thumb/asmbcc.py b/tests/inlineasm/thumb/asmbcc.py index 08967d48c74..bf0ee0a48fe 100644 --- a/tests/inlineasm/thumb/asmbcc.py +++ b/tests/inlineasm/thumb/asmbcc.py @@ -1,29 +1,65 @@ -# test bcc instructions -# at the moment only tests beq, narrow and wide versions +# test bcc instructions, narrow and wide versions +RESULT = [] + + +TEMPLATE = """ @micropython.asm_thumb -def f(r0): +def t(r0): mov(r1, r0) mov(r0, 10) cmp(r1, 1) - beq(end) + b{}(next1) + + b(end) + + label(next1) mov(r0, 20) cmp(r1, 2) - beq_n(end) + b{}_n(next2) + + b(end) + label(next2) mov(r0, 30) cmp(r1, 3) - beq_w(end) + b{}_w(next3) + + b(end) + label(next3) mov(r0, 0) label(end) +""" +try: + for code in ( + "eq", + "ne", + "cs", + "cc", + "mi", + "pl", + "vs", + "vc", + "hi", + "ls", + "ge", + "lt", + "gt", + "le", + ): + exec(TEMPLATE.format(code, code, code)) + RESULT.append("B" + code.upper()) + for i in range(4): + RESULT.append(t(i)) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit -print(f(0)) -print(f(1)) -print(f(2)) -print(f(3)) +for line in RESULT: + print(line) diff --git a/tests/inlineasm/thumb/asmbcc.py.exp b/tests/inlineasm/thumb/asmbcc.py.exp index 39da7d1a99e..06fd057ae52 100644 --- a/tests/inlineasm/thumb/asmbcc.py.exp +++ b/tests/inlineasm/thumb/asmbcc.py.exp @@ -1,4 +1,70 @@ +BEQ +10 +20 +10 +10 +BNE +0 +10 +20 +30 +BCS +10 +20 +30 +0 +BCC +0 +10 +10 +10 +BMI +0 +10 +10 +10 +BPL +10 +20 +30 +0 +BVS +10 +10 +10 +10 +BVC +0 +0 +0 +0 +BHI +10 +10 +20 +30 +BLS +0 +0 +10 +10 +BGE +10 +20 +30 0 +BLT +0 +10 +10 +10 +BGT +10 10 20 30 +BLE +0 +0 +10 +10 From 5ddc551bc48b30342bdb02a855c6a05267d4c322 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 2 Apr 2026 21:40:11 -0500 Subject: [PATCH 1943/2098] py/compile: Reject *arg after keyword argument. Document this in cpydiff and add a test with expected output for coverage testing. As discussed in #11441, the code growth from handling this case seems to outweigh the benefit of implementing it properly. Closes: #11439 Signed-off-by: Jeff Epler --- py/compile.c | 8 ++++++++ tests/basics/fun_callstar_kwarg.py | 8 ++++++++ tests/basics/fun_callstar_kwarg.py.exp | 1 + tests/cpydiff/core_function_star.py | 16 ++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 tests/basics/fun_callstar_kwarg.py create mode 100644 tests/basics/fun_callstar_kwarg.py.exp create mode 100644 tests/cpydiff/core_function_star.py diff --git a/py/compile.c b/py/compile.c index 72dad8dc315..37a3e6d32c4 100644 --- a/py/compile.c +++ b/py/compile.c @@ -2421,6 +2421,14 @@ static void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("* arg after **")); return; } + if (n_keyword) { + // Support for *arg after kwarg is a CPython feature omitted + // from MicroPython in order to reduce code size. See + // https://github.com/micropython/micropython/issues/11439 for + // more info. + compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("* arg after kwarg")); + return; + } #if MICROPY_DYNAMIC_COMPILER if (i >= (size_t)mp_dynamic_compiler.small_int_bits - 1) #else diff --git a/tests/basics/fun_callstar_kwarg.py b/tests/basics/fun_callstar_kwarg.py new file mode 100644 index 00000000000..15bc19e216e --- /dev/null +++ b/tests/basics/fun_callstar_kwarg.py @@ -0,0 +1,8 @@ +# A CPython syntax feature not supported by MicroPython. For more information +# see `this issue `_. +# and tests/cpydiff/core_function_star.py + +try: + exec("f(y=1, *(3,))") +except SyntaxError as e: + print("SyntaxError") diff --git a/tests/basics/fun_callstar_kwarg.py.exp b/tests/basics/fun_callstar_kwarg.py.exp new file mode 100644 index 00000000000..8729fc43437 --- /dev/null +++ b/tests/basics/fun_callstar_kwarg.py.exp @@ -0,0 +1 @@ +SyntaxError diff --git a/tests/cpydiff/core_function_star.py b/tests/cpydiff/core_function_star.py new file mode 100644 index 00000000000..26f5effd4e3 --- /dev/null +++ b/tests/cpydiff/core_function_star.py @@ -0,0 +1,16 @@ +""" +categories: Core,Functions +description: ``*args`` cannot follow a keyword argument +cause: MicroPython is optimised for code space. For more information see `this issue `_. +workaround: Re-order the arguments +""" + + +def f(x, y): + return x + y + + +try: + print(f(y=1, *(3,))) +except Exception as e: + print(e) From 5b127f022e5e22717cd013820c9fd76ab90e1e72 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 4 Apr 2026 20:25:26 -0500 Subject: [PATCH 1944/2098] py/modio: Error out on over-long read()/write() in user IO code. It is a mistake in Python code if the result of readinto()/write() is more bytes than requested. This can lead to misbehavior e.g., in mp_stream_rw if the invariant is not respected. CPython appears to largely ignore the values returned from readinto()/write(), at least from `print()` & `json.load()`. Consequently, an expected output file is needed for the new test. Closes: #18845 Signed-off-by: Jeff Epler --- py/modio.c | 4 +++ tests/micropython/io_badlength.py | 35 +++++++++++++++++++++++++++ tests/micropython/io_badlength.py.exp | 2 ++ 3 files changed, 41 insertions(+) create mode 100644 tests/micropython/io_badlength.py create mode 100644 tests/micropython/io_badlength.py.exp diff --git a/py/modio.c b/py/modio.c index 9aeb42d30aa..fdee484376f 100644 --- a/py/modio.c +++ b/py/modio.c @@ -63,6 +63,10 @@ static mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int } mp_int_t ret = mp_obj_get_int(ret_obj); if (ret >= 0) { + if ((mp_uint_t)ret > size) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } return ret; } else { *errcode = -ret; diff --git a/tests/micropython/io_badlength.py b/tests/micropython/io_badlength.py new file mode 100644 index 00000000000..646a2cd9241 --- /dev/null +++ b/tests/micropython/io_badlength.py @@ -0,0 +1,35 @@ +# Test when a use IOBase class has write/readinto which returns more data than +# requested (https://github.com/micropython/micropython/issues/18845) + +try: + import io, json +except: + print("SKIP") + raise SystemExit + + +class S(io.IOBase): + def write(self, buf): + assert len(buf) >= 0 + return 2 + + def ioctl(self, cmd, arg): + return 0 + + def readinto(self, buf): + assert len(buf) >= 0 + return 3 + + +try: + print("abc", file=S()) + print("write OK") +except OSError as e: + print("write failed, errno", e.errno) + +buf = bytearray(1) +try: + json.load(S()) + print("readinto OK") +except OSError as e: + print("read failed, errno", e.errno) diff --git a/tests/micropython/io_badlength.py.exp b/tests/micropython/io_badlength.py.exp new file mode 100644 index 00000000000..bba660dd3c8 --- /dev/null +++ b/tests/micropython/io_badlength.py.exp @@ -0,0 +1,2 @@ +write failed, errno 5 +read failed, errno 5 From 1834dcf8b5387e82c49547b17312c38cf1cbe60f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 4 Apr 2026 21:03:29 -0500 Subject: [PATCH 1945/2098] py/objtuple: Support __add__ and __iadd__ with tuple subclasses. Properly cast tuple subclasses to the native base class before performing `__add__` or `__iadd_`. Closes: #7304 Signed-off-by: Jeff Epler --- py/objtuple.c | 29 ++++++++++------ tests/basics/tuple_subclass.py | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 tests/basics/tuple_subclass.py diff --git a/py/objtuple.c b/py/objtuple.c index c9d8e9b31fe..c0ba1fd0d11 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -103,20 +103,27 @@ static mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg } } +static mp_obj_tuple_t *tuple_subclass_helper(mp_obj_t obj) { + assert(obj != MP_OBJ_NULL); + const mp_obj_type_t *tuple_type = mp_obj_get_type(obj); + if (MP_OBJ_TYPE_GET_SLOT_OR_NULL(tuple_type, iter) != mp_obj_tuple_getiter) { + // Slow path for user subclasses + obj = mp_obj_cast_to_native_base(obj, MP_OBJ_FROM_PTR(&mp_type_tuple)); + if (obj == MP_OBJ_NULL) { + return NULL; + } + } + return MP_OBJ_TO_PTR(obj); +} + // Don't pass MP_BINARY_OP_NOT_EQUAL here static mp_obj_t tuple_cmp_helper(mp_uint_t op, mp_obj_t self_in, mp_obj_t another_in) { mp_check_self(mp_obj_is_tuple_compatible(self_in)); - const mp_obj_type_t *another_type = mp_obj_get_type(another_in); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); - if (MP_OBJ_TYPE_GET_SLOT_OR_NULL(another_type, iter) != mp_obj_tuple_getiter) { - // Slow path for user subclasses - another_in = mp_obj_cast_to_native_base(another_in, MP_OBJ_FROM_PTR(&mp_type_tuple)); - if (another_in == MP_OBJ_NULL) { - return MP_OBJ_NULL; - } + mp_obj_tuple_t *another = tuple_subclass_helper(another_in); + if (!another) { + return MP_OBJ_NULL; } - mp_obj_tuple_t *another = MP_OBJ_TO_PTR(another_in); - return mp_obj_new_bool(mp_seq_cmp_objs(op, self->items, self->len, another->items, another->len)); } @@ -145,10 +152,10 @@ mp_obj_t mp_obj_tuple_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: { - if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(rhs)), MP_OBJ_FROM_PTR(&mp_type_tuple))) { + mp_obj_tuple_t *p = tuple_subclass_helper(rhs); + if (!p) { return MP_OBJ_NULL; // op not supported } - mp_obj_tuple_t *p = MP_OBJ_TO_PTR(rhs); mp_obj_tuple_t *s = MP_OBJ_TO_PTR(mp_obj_new_tuple(o->len + p->len, NULL)); mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); return MP_OBJ_FROM_PTR(s); diff --git a/tests/basics/tuple_subclass.py b/tests/basics/tuple_subclass.py new file mode 100644 index 00000000000..a8ee3ff1a4f --- /dev/null +++ b/tests/basics/tuple_subclass.py @@ -0,0 +1,62 @@ +try: + from collections import namedtuple +except ImportError: + print("SKIP") + raise SystemExit + + +class MyTuple(tuple): + pass + + +N = namedtuple("N", ("a", "b")) + + +class MyNamedTuple(N): + pass + + +t = (1, 2) +m = MyTuple((3, 4)) +n = N(5, 6) +q = MyNamedTuple(7, 8) + +print(t + t) +print(t + m) +print(t + n) +print(t + q) + +print(m + t) +print(m + m) +print(m + n) +print(m + q) + +print(n + t) +print(n + m) +print(n + n) +print(n + q) + +print(q + t) +print(q + m) +print(q + n) +print(q + q) + +print(t < t) +print(t < m) +print(t < n) +print(t < q) + +print(m < t) +print(m < m) +print(m < n) +print(m < q) + +print(n < t) +print(n < m) +print(n < n) +print(n < q) + +print(q < t) +print(q < m) +print(q < n) +print(q < q) From a43a8ac89e3c613770193974e17a6fe55ed05bc9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 7 Apr 2026 12:15:20 +1000 Subject: [PATCH 1946/2098] py/mpconfig: Automatically configure thumb2 and float emitter features. Prior to this commit a port would need to manually configure the `MICROPY_EMIT_THUMB_ARMV7M` and `MICROPY_EMIT_INLINE_THUMB_FLOAT` options, based on whether the CPU is Thumb2 and whether it has hardware floating point support (eg Cortex-M0+ vs Cortex-M3 vs Cortex-M4). This is error prone, for example on stm32: - `NUCLEO_G0B1RE` (a Cortex-M0+ MCU) had both enabled even though neither options work on that target. - `NUCLEO_L152RE` (a Cortex-M3 MCU) had both enabled but this target does not support hardware floating point. The change here automatically enables the two options based on built-in compiler macros. This change is tested on the following boards: - alif ALIF_ENSEMBLE: both enabled - rp2 RPI_PICO: both disabled - rp2 RPI_PICO2: both enabled - samd SAMD21_XPLAINED_PRO: both disabled - samd SAMD_GENERIC_D51X20: both enabled - stm32 NUCLEO_G0B1RE: both disabled - stm32 NUCLEO_L152RE: only MICROPY_EMIT_THUMB_ARMV7M enabled - stm32 PYBD_SF6: both enabled - stm32 PYBV10: both enabled Signed-off-by: Damien George --- ports/alif/mpconfigport.h | 2 -- ports/rp2/mpconfigport.h | 4 ---- ports/samd/mcu/samd21/mpconfigmcu.h | 1 - py/mpconfig.h | 8 ++++++++ 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ports/alif/mpconfigport.h b/ports/alif/mpconfigport.h index 57e5fed7b6c..d0767dfbae1 100644 --- a/ports/alif/mpconfigport.h +++ b/ports/alif/mpconfigport.h @@ -87,9 +87,7 @@ // MicroPython emitters #define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_EMIT_THUMB (1) -#define MICROPY_EMIT_THUMB_ARMV7M (1) #define MICROPY_EMIT_INLINE_THUMB (1) -#define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) // Optimisations #define MICROPY_OPT_COMPUTED_GOTO (1) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index c8df4cdb0e2..29d084582bb 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -103,10 +103,6 @@ #if PICO_ARM #define MICROPY_EMIT_THUMB (1) #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) #define MICROPY_EMIT_RV32_ZBA (1) diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index a757893943b..a6be13b2420 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -6,7 +6,6 @@ // MicroPython emitters #define MICROPY_EMIT_THUMB (SAMD21_EXTRA_FEATURES) #define MICROPY_EMIT_INLINE_THUMB (SAMD21_EXTRA_FEATURES) -#define MICROPY_EMIT_THUMB_ARMV7M (0) #define MICROPY_MODULE_BUILTIN_INIT (1) // Selected extensions beyond the basic features set. diff --git a/py/mpconfig.h b/py/mpconfig.h index 8a26ce4ddcd..0d81898537a 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -464,7 +464,11 @@ typedef uint64_t mp_uint_t; // Whether to emit ARMv7-M instruction support in thumb native code #ifndef MICROPY_EMIT_THUMB_ARMV7M +#if defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 #define MICROPY_EMIT_THUMB_ARMV7M (1) +#else +#define MICROPY_EMIT_THUMB_ARMV7M (0) +#endif #endif // Whether to enable the thumb inline assembler @@ -474,7 +478,11 @@ typedef uint64_t mp_uint_t; // Whether to enable float support in the Thumb2 inline assembler #ifndef MICROPY_EMIT_INLINE_THUMB_FLOAT +#if defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 && defined(__ARM_FP) #define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) +#else +#define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#endif #endif // Whether to emit ARM native code From 02f2683bfc51519fb65c19210b08b5a0e83dae84 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 2 Aug 2025 11:51:05 -0500 Subject: [PATCH 1947/2098] py/objint_longlong: Fix signed comparison error. Under `afl-cc` (acting as a wrapper for clang), the following diagnostic occurs (wrapped for clarity): ../../py/objint_longlong.c:232:32: error: comparison of integers of different signs: 'long long' and 'unsigned long' [-Werror,-Wsign-compare] Add a cast to silence it. The value is known statically to fit inside `long long`. Signed-off-by: Jeff Epler --- py/objint_longlong.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 1a6242b9792..e16fd526581 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -229,7 +229,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } - overflow = rhs_val >= (sizeof(long long) * MP_BITS_PER_BYTE) + overflow = rhs_val >= (long long)(sizeof(long long) * MP_BITS_PER_BYTE) || lhs_val > (LLONG_MAX >> rhs_val) || lhs_val < (LLONG_MIN >> rhs_val); result = (unsigned long long)lhs_val << rhs_val; From eeed037f70abc08f6f3c7d67e85396fb3cf6fb26 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 8 Apr 2026 10:55:15 +1000 Subject: [PATCH 1948/2098] extmod/machine_usb_device: Document xfer_cb result value, add enums. This callback argument was previously mis-labelled as a boolean, but it's actually the tusb_xfer_result_t values from TinyUSB. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.USBDevice.rst | 25 +++++++++++++++++++++---- extmod/machine_usb_device.c | 10 ++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst index 5291679ac12..45f9e9cef38 100644 --- a/docs/library/machine.USBDevice.rst +++ b/docs/library/machine.USBDevice.rst @@ -155,10 +155,11 @@ Methods The callback has three arguments: 1. The Endpoint number for the completed transfer. - 2. Result value: ``True`` if the transfer succeeded, ``False`` - otherwise. - 3. Number of bytes successfully transferred. In the case of a - "short" transfer, The result is ``True`` and ``xferred_bytes`` + 2. Result value. This is an integer which is ``0`` (`XFER_SUCCESS`) on + success, or one of the non-zero values `XFER_FAILED` or `XFER_STALLED` + if the transfer failed. + 3. Number of bytes successfully transferred. In the case of a "short" + transfer, the result is ``0`` (`XFER_SUCCESS`) and ``xferred_bytes`` will be smaller than the length of the buffer submitted for the transfer. @@ -301,4 +302,20 @@ Constants - ``desc_cfg`` - ``bytes`` object containing the complete built-in USB configuration descriptor. +.. data:: USBDevice.XFER_SUCCESS +.. data:: USBDevice.XFER_FAILED +.. data:: USBDevice.XFER_STALLED + + These are integer constants that represent the possible transfer + result values passed to the ``xfer_cb`` callback (see + `USBDevice.config`). + + - ``XFER_SUCCESS`` has value ``0`` and indicates the transfer was + successful. + - ``XFER_FAILED`` indicates the transfer failed due to low-level + integrity errors. + - ``XFER_STALLED`` indicates that the host has stalled this endpoint. + + All failure values are non-zero integers. + .. _usb driver modules in micropython-lib: https://github.com/micropython/micropython-lib/tree/master/micropython/usb#readme diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index 3d4cde942cb..f6e97f42054 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -302,6 +302,16 @@ static const mp_rom_map_elem_t usb_device_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, #endif #endif // !HAS_BUILTIN_DRIVERS + + // xfer_cb result values + // These are a subset of tusb_xfer_result_t + { MP_ROM_QSTR(MP_QSTR_XFER_SUCCESS), MP_OBJ_NEW_SMALL_INT(XFER_RESULT_SUCCESS) }, + { MP_ROM_QSTR(MP_QSTR_XFER_FAILED), MP_OBJ_NEW_SMALL_INT(XFER_RESULT_FAILED) }, + { MP_ROM_QSTR(MP_QSTR_XFER_STALLED), MP_OBJ_NEW_SMALL_INT(XFER_RESULT_STALLED) }, + // Some values of tusb_xfer_result_t are not exposed here: + // - XFER_RESULT_TIMEOUT only appears if you call the "sync" API subset, or in one + // case from the samd host controller. + // - XFER_RESULT_INVALID only appears in the host controller APIs }; static MP_DEFINE_CONST_DICT(usb_device_locals_dict, usb_device_locals_dict_table); From 98e4264ea66a681089ef9bc64f50e7f073be366d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 8 Apr 2026 16:33:27 -0500 Subject: [PATCH 1949/2098] tests/run-tests.py: Correct spelling errors. Signed-off-by: Jeff Epler --- tests/run-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 153424f65f7..6a9f10fc325 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -873,7 +873,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): def run_one_test(test_file): test_file_abspath = os.path.abspath(test_file).replace("\\", "/") # If test_file is one of our own tests always make it relative to our tests/ dir and - # otherwise use the abosulte path, irregardless of actual path passed, + # otherwise use the absolute path, regardless of actual path passed, # such that display and result output is always the same. try: test_file_relpath = os.path.relpath(test_file, start=base_path()) From bd69a0ddef30af30f82dc99946bf0c66beaa8e51 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 8 Apr 2026 16:33:27 -0500 Subject: [PATCH 1950/2098] lib/re1.5: Check stack during compilecode. Otherwise, a very deeply nested regular expression like re.compile("(" * 65536) can exhaust the host stack during the compile phase. This turns that into a `RuntimeError: maximum recursion depth exceeded` instead. This crash was found via fuzzing. Signed-off-by: Jeff Epler --- lib/re1.5/compilecode.c | 2 ++ tests/extmod/re_stack_overflow2.py | 25 +++++++++++++++++++++++++ tests/run-tests.py | 1 + 3 files changed, 28 insertions(+) create mode 100644 tests/extmod/re_stack_overflow2.py diff --git a/lib/re1.5/compilecode.c b/lib/re1.5/compilecode.c index 513a155970a..fade45324d4 100644 --- a/lib/re1.5/compilecode.c +++ b/lib/re1.5/compilecode.c @@ -29,6 +29,8 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode) int term = PC; int alt_label = 0; + re1_5_stack_chk(); + for (; *re && *re != ')'; re++) { switch (*re) { case '\\': diff --git a/tests/extmod/re_stack_overflow2.py b/tests/extmod/re_stack_overflow2.py new file mode 100644 index 00000000000..c5ddd012b32 --- /dev/null +++ b/tests/extmod/re_stack_overflow2.py @@ -0,0 +1,25 @@ +# Test overflow in re.compile output code. + +try: + import re +except ImportError: + print("SKIP") + raise SystemExit + + +def test_re(r): + try: + re.compile(r) + except: + print("Error") + + +try: + r = "(" * 65536 + ")" * 65536 +except MemoryError: + print("SKIP") + raise SystemExit + +# This happens to trigger RecursionError on current versions of CPython +# (tested with 3.13.5) as well, so no .exp file is needed. +test_re(r) diff --git a/tests/run-tests.py b/tests/run-tests.py index 6a9f10fc325..c5c5dc740bd 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -178,6 +178,7 @@ "extmod/binascii_a2b_base64.py", "extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory "extmod/re_stack_overflow.py", + "extmod/re_stack_overflow2.py", "extmod/time_res.py", "extmod/vfs_posix.py", "extmod/vfs_posix_enoent.py", From cf0ef5a9d76208406707de64e83c16569c167513 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 30 Mar 2026 21:07:54 +0200 Subject: [PATCH 1951/2098] rp2/rp2_dma: Reduce footprint of the DMA control fields table. This commit shortens the amount of space taken by the DMA control fields table, and explicitly marks it as `const`. The DMA fields info table used a full-size QSTR index value, and 9 bits of numeric information. Given that the QSTR index could be converted into a `qstr_short_t`, there is no fields spill outside a machine word boundary - albeit with having 7 unused bits but there isn't much that can be done for that. The effective structure size for each entry is halved, from 8 bytes down to 4. Also, the structure is only read from, yet it was not marked as `const`. Marking the structure as constant did not help reduce the final size but at least correctly signals the compiler that no write accesses are possible. This shrinks the RPI_PICO/RPI_PICO_W build by 56 bytes, with a similar size reduction for RPI_PICO2/RPI_PICO2_W. Signed-off-by: Alessandro Gatti --- ports/rp2/rp2_dma.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index bb935f3b484..7e4d3cd92bf 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -50,13 +50,14 @@ typedef struct _rp2_dma_obj_t { } rp2_dma_obj_t; typedef struct _rp2_dma_ctrl_field_t { - qstr name; - uint8_t shift : 5; - uint8_t length : 3; - uint8_t read_only : 1; + qstr_short_t name; + uint16_t shift : 5; + uint16_t length : 3; + uint16_t read_only : 1; + // 7 bits available here. } rp2_dma_ctrl_field_t; -static rp2_dma_ctrl_field_t rp2_dma_ctrl_fields_table[] = { +static const rp2_dma_ctrl_field_t rp2_dma_ctrl_fields_table[] = { { 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 }, From 083b0cc60fd4a00b6b3e08ea4bb9da52d880bfef Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 19:07:43 +0200 Subject: [PATCH 1952/2098] qemu/Makefile: Refactor shared ROMFS definitions. This commit refactors shared ROMFS definitions appearing in some Arm boards configuration, moving them into the QEMU port's Makefile. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 12 ++++++++++++ ports/qemu/boards/MPS2_AN500/mpconfigboard.mk | 3 --- ports/qemu/boards/MPS3_AN547/mpconfigboard.mk | 3 --- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 380355dbe94..7b803a03957 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -64,6 +64,18 @@ else ifeq ($(SUPPORTS_HARDWARE_FP_DOUBLE),1) CFLAGS += -DMICROPY_HW_FPU=1 endif +ifneq ($(MICROPY_HW_ROMFS_PART0_START),) +CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART0=1 +CFLAGS += -DMICROPY_HW_ROMFS_PART0_START=$(MICROPY_HW_ROMFS_PART0_START) +CFLAGS += -DMICROPY_HW_ROMFS_PART0_SIZE=$(MICROPY_HW_ROMFS_PART0_SIZE) +endif + +ifneq ($(MICROPY_HW_ROMFS_PART1_START),) +CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART1=1 +CFLAGS += -DMICROPY_HW_ROMFS_PART1_START=$(MICROPY_HW_ROMFS_PART1_START) +CFLAGS += -DMICROPY_HW_ROMFS_PART1_SIZE=$(MICROPY_HW_ROMFS_PART1_SIZE) +endif + ################################################################################ # ARM specific settings diff --git a/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk index 725ffbc895f..2b78149dee1 100644 --- a/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk +++ b/ports/qemu/boards/MPS2_AN500/mpconfigboard.mk @@ -7,9 +7,6 @@ CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M7"' MICROPY_HW_ROMFS_PART0_START = 0x60C00000 MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 -CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART0=1 -CFLAGS += -DMICROPY_HW_ROMFS_PART0_START=$(MICROPY_HW_ROMFS_PART0_START) -CFLAGS += -DMICROPY_HW_ROMFS_PART0_SIZE=$(MICROPY_HW_ROMFS_PART0_SIZE) LDSCRIPT = mcu/arm/mps2.ld diff --git a/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk b/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk index fea9c3d5deb..7469f0408a5 100644 --- a/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk +++ b/ports/qemu/boards/MPS3_AN547/mpconfigboard.mk @@ -8,9 +8,6 @@ CFLAGS += -DCPU_FREQ_HZ=32000000 MICROPY_HW_ROMFS_PART0_START ?= 0x62000000 MICROPY_HW_ROMFS_PART0_SIZE ?= 0x02000000 -CFLAGS += -DMICROPY_HW_ROMFS_ENABLE_PART0=1 -CFLAGS += -DMICROPY_HW_ROMFS_PART0_START=$(MICROPY_HW_ROMFS_PART0_START) -CFLAGS += -DMICROPY_HW_ROMFS_PART0_SIZE=$(MICROPY_HW_ROMFS_PART0_SIZE) LDSCRIPT = mcu/arm/mps3.ld From acaba3f469e4adbab5406a91d3ccf5e5cb168dff Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 19:15:36 +0200 Subject: [PATCH 1953/2098] qemu/boards/VIRT_RV32: Add ROMFS support. This commit reserves a memory area to mount ROMFS partitions into, and lets the port Makefile know the memory area details. A 4 MiB segment is allocated at 0x8061_0000, which is inside the emulated machine's DRAM segment. The virt board requires the image loaded in its DRAM segment to be contiguous, so the ROMFS segment is placed right after the stack area. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/VIRT_RV32/mpconfigboard.mk | 6 ++++++ ports/qemu/mcu/rv32/virt.ld | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk index 6a3b94a98bc..e38991aa341 100644 --- a/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk +++ b/ports/qemu/boards/VIRT_RV32/mpconfigboard.mk @@ -6,6 +6,12 @@ CFLAGS += -DMICROPY_HW_MCU_NAME='"$(RV32_ARCH)"' LDSCRIPT = mcu/rv32/virt.ld +# If the ROMFS partition size is not enough, remember to change +# ports/qemu/mcu/rv32/virt.ld to resize the ROMFS section as well. + +MICROPY_HW_ROMFS_PART0_START = 0x80610000 +MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 + SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv32i.o MPY_CROSS_FLAGS += -march=rv32imc -march-flags=zba diff --git a/ports/qemu/mcu/rv32/virt.ld b/ports/qemu/mcu/rv32/virt.ld index 64675ad4ee8..5d625ac343f 100644 --- a/ports/qemu/mcu/rv32/virt.ld +++ b/ports/qemu/mcu/rv32/virt.ld @@ -38,12 +38,14 @@ OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv"); * 0x8060_0000: .stack * 0x8060_0000: _sstack * 0x8060_FFFF: _estack + * 0x8061_0000: .romfs */ MEMORY { - ROM (xr) : ORIGIN = 0x80000000, LENGTH = 4M - RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M - STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 64K + ROM (xr) : ORIGIN = 0x80000000, LENGTH = 4M + RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M + STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 64K + ROMFS (xr) : ORIGIN = ORIGIN(STACK) + LENGTH(STACK), LENGTH = 4M } SECTIONS From f3e79c8b565aa4e74dfeedd1b562784e01cac81f Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 19:56:42 +0200 Subject: [PATCH 1954/2098] qemu/boards/VIRT_RV64: Add ROMFS support. This commit reserves a memory area to mount ROMFS partitions into, and lets the port Makefile know the memory area details. A 4 MiB segment is allocated at 0x8062_0000, which is inside the emulated machine's DRAM segment. The virt board requires the image loaded in its DRAM segment to be contiguous, so the ROMFS segment is placed right after the stack area. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/VIRT_RV64/mpconfigboard.mk | 6 ++++++ ports/qemu/mcu/rv64/virt.ld | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk b/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk index dae4ca3e036..38939b032c4 100644 --- a/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk +++ b/ports/qemu/boards/VIRT_RV64/mpconfigboard.mk @@ -6,6 +6,12 @@ CFLAGS += -DMICROPY_HW_MCU_NAME='"$(RV64_ARCH)"' LDSCRIPT = mcu/rv64/virt.ld +# If the ROMFS partition size is not enough, remember to change +# ports/qemu/mcu/rv64/virt.ld to resize the ROMFS section as well. + +MICROPY_HW_ROMFS_PART0_START = 0x80620000 +MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 + SRC_BOARD_O += shared/runtime/gchelper_native.o shared/runtime/gchelper_rv64i.o MPY_CROSS_FLAGS += -march=rv64imc diff --git a/ports/qemu/mcu/rv64/virt.ld b/ports/qemu/mcu/rv64/virt.ld index 7727839e0d5..4373be30c15 100644 --- a/ports/qemu/mcu/rv64/virt.ld +++ b/ports/qemu/mcu/rv64/virt.ld @@ -38,12 +38,14 @@ OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv"); * 0x0000_0000_8060_0000: .stack * 0x0000_0000_8060_0000: _sstack * 0x0000_0000_8061_FFFF: _estack + * 0x0000_0000_8062_0000: .romfs */ MEMORY { - ROM (xr) : ORIGIN = 0x0000000080000000, LENGTH = 4M - RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M - STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 128K + ROM (xr) : ORIGIN = 0x0000000080000000, LENGTH = 4M + RAM (xrw) : ORIGIN = ORIGIN(ROM) + LENGTH(ROM), LENGTH = 2M + STACK (rw) : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 128K + ROMFS (xr) : ORIGIN = ORIGIN(STACK) + LENGTH(STACK), LENGTH = 4M } SECTIONS From 809cf6f5610b7ba4caa6069b2f104b27e72a9582 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 20:09:17 +0200 Subject: [PATCH 1955/2098] qemu/boards/MPS2_AN385: Add ROMFS support. This commit lets the MPS2_AN385 machine definition load a ROMFS partition right at the beginning of the 16MiB PSRAM area. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/MPS2_AN385/mpconfigboard.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/qemu/boards/MPS2_AN385/mpconfigboard.mk b/ports/qemu/boards/MPS2_AN385/mpconfigboard.mk index 182d076eb35..53b6aec3d68 100644 --- a/ports/qemu/boards/MPS2_AN385/mpconfigboard.mk +++ b/ports/qemu/boards/MPS2_AN385/mpconfigboard.mk @@ -5,6 +5,9 @@ CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft CFLAGS += -DQEMU_SOC_MPS2 CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M3"' +MICROPY_HW_ROMFS_PART0_START = 0x21000000 +MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 + LDSCRIPT = mcu/arm/mps2.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o From 75441f49ad6b35769392b8b7dc07c58f1b6ab939 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 21:34:53 +0200 Subject: [PATCH 1956/2098] qemu/boards/SABRELITE: Add ROMFS support. This commit lets the SABRELITE machine definition load a ROMFS partition at the very end of the RAM space. Signed-off-by: Alessandro Gatti --- ports/qemu/boards/SABRELITE/mpconfigboard.mk | 6 ++++++ ports/qemu/mcu/arm/imx6.ld | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ports/qemu/boards/SABRELITE/mpconfigboard.mk b/ports/qemu/boards/SABRELITE/mpconfigboard.mk index e56dba712ff..f46559deccf 100644 --- a/ports/qemu/boards/SABRELITE/mpconfigboard.mk +++ b/ports/qemu/boards/SABRELITE/mpconfigboard.mk @@ -8,6 +8,12 @@ CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-A9"' # Cortex-A9 should support unaligned-access, but qemu doesn't seem to. CFLAGS += -mno-unaligned-access +# If the ROMFS partition size is not enough, remember to change +# ports/qemu/mcu/arm/imx6.ld to resize the ROMFS section as well. + +MICROPY_HW_ROMFS_PART0_START = 0x17C00000 +MICROPY_HW_ROMFS_PART0_SIZE = 0x00400000 + LDSCRIPT = mcu/arm/imx6.ld SRC_BOARD_O = shared/runtime/gchelper_generic.o diff --git a/ports/qemu/mcu/arm/imx6.ld b/ports/qemu/mcu/arm/imx6.ld index c6d8e081811..76648616098 100644 --- a/ports/qemu/mcu/arm/imx6.ld +++ b/ports/qemu/mcu/arm/imx6.ld @@ -7,8 +7,9 @@ MEMORY { - ROM : ORIGIN = 0x00000000, LENGTH = 96K - RAM : ORIGIN = 0x10000000, LENGTH = 128M + ROM : ORIGIN = 0x00000000, LENGTH = 96K + RAM : ORIGIN = 0x10000000, LENGTH = 124M + ROMFS : ORIGIN = ORIGIN(RAM) + LENGTH(RAM), LENGTH = 4M } _estack = ORIGIN(RAM) + LENGTH(RAM); From 67de20ae0aeccee999968f7b86adea888aee9069 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 9 Apr 2026 13:50:36 +0200 Subject: [PATCH 1957/2098] tests: Rename "tests/frozen" into "tests/assets". This commit renames the "tests/frozen" directory into "tests/assets" to make it more explicit that it does contain files that are needed for other tests to function. Right now there's only a single pre-compiled module being used for miscellaneous tests, but it will soon hold ROMFS test data as well. Signed-off-by: Alessandro Gatti --- ports/minimal/Makefile | 2 +- ports/powerpc/Makefile | 2 +- ports/unix/variants/coverage/manifest.py | 2 +- tests/{frozen => assets}/README.md | 0 tests/{frozen => assets}/frozentest.mpy | Bin tests/{frozen => assets}/frozentest.py | 0 tools/ci.sh | 4 ++-- 7 files changed, 5 insertions(+), 5 deletions(-) rename tests/{frozen => assets}/README.md (100%) rename tests/{frozen => assets}/frozentest.mpy (100%) rename tests/{frozen => assets}/frozentest.py (100%) diff --git a/ports/minimal/Makefile b/ports/minimal/Makefile index aac7ce73b8b..65ef8564726 100644 --- a/ports/minimal/Makefile +++ b/ports/minimal/Makefile @@ -74,7 +74,7 @@ else all: $(BUILD)/firmware.elf endif -$(BUILD)/_frozen_mpy.c: $(TOP)/tests/frozen/frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h +$(BUILD)/_frozen_mpy.c: $(TOP)/tests/assets/frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h $(ECHO) "MISC freezing bytecode" $(Q)$(TOP)/tools/mpy-tool.py -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h -mlongint-impl=none $< > $@ diff --git a/ports/powerpc/Makefile b/ports/powerpc/Makefile index 949980f5403..cc39040e4b4 100644 --- a/ports/powerpc/Makefile +++ b/ports/powerpc/Makefile @@ -50,7 +50,7 @@ OBJ += $(BUILD)/head.o all: $(BUILD)/firmware.elf $(BUILD)/firmware.map $(BUILD)/firmware.bin -$(BUILD)/_frozen_mpy.c: $(TOP)/tests/frozen/frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h +$(BUILD)/_frozen_mpy.c: $(TOP)/tests/assets/frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h $(ECHO) "MISC freezing bytecode" $(Q)$(MPY_TOOL) -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h -mlongint-impl=mpz $< > $@ diff --git a/ports/unix/variants/coverage/manifest.py b/ports/unix/variants/coverage/manifest.py index 37f2531a842..2d459103ef2 100644 --- a/ports/unix/variants/coverage/manifest.py +++ b/ports/unix/variants/coverage/manifest.py @@ -1,5 +1,5 @@ add_library("unix-ffi", "$(MPY_LIB_DIR)/unix-ffi") freeze_as_str("frzstr") freeze_as_mpy("frzmpy") -freeze_mpy("$(MPY_DIR)/tests/frozen") +freeze_mpy("$(MPY_DIR)/tests/assets") require("ssl") diff --git a/tests/frozen/README.md b/tests/assets/README.md similarity index 100% rename from tests/frozen/README.md rename to tests/assets/README.md diff --git a/tests/frozen/frozentest.mpy b/tests/assets/frozentest.mpy similarity index 100% rename from tests/frozen/frozentest.mpy rename to tests/assets/frozentest.mpy diff --git a/tests/frozen/frozentest.py b/tests/assets/frozentest.py similarity index 100% rename from tests/frozen/frozentest.py rename to tests/assets/frozentest.py diff --git a/tools/ci.sh b/tools/ci.sh index 063188fe9f2..81006ea4e7c 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -158,7 +158,7 @@ function ci_mpy_format_setup { function ci_mpy_format_test { # Test mpy-tool.py dump feature on bytecode - python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy + python3 ./tools/mpy-tool.py -xd tests/assets/frozentest.mpy # Build MicroPython ci_unix_standard_build @@ -167,7 +167,7 @@ function ci_mpy_format_test { export MICROPYPATH=. # Test mpy-tool.py running under MicroPython - $micropython ./tools/mpy-tool.py -x -d tests/frozen/frozentest.mpy + $micropython ./tools/mpy-tool.py -x -d tests/assets/frozentest.mpy # Test mpy-tool.py dump feature on native code make -C examples/natmod/features1 From c895770d4a66d606291adbfbc08513edf49b0d1e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 8 Apr 2026 21:11:49 +0200 Subject: [PATCH 1958/2098] tests/ports/qemu: Add ROMFS tests to CI. This commit adds a new test to make sure ROMFS files are mounted and read correctly, to be run as part of the CI process. The changes also include the source binary files that have been used to create the pre-baked ROMFS partition image used in the test, along with a Makefile to allow recreating said file. The CI test ROMFS image is mounted only if no other ROMFS partition is mounted in slot 0. The specific test is executed only if there actually is a ROMFS partition mounted and if the partition is identified as the one used to run tests on. This allows for user images to be mounted and for a successful test run if that is the case. Signed-off-by: Alessandro Gatti --- .gitattributes | 1 + ports/qemu/Makefile | 10 ++++ tests/assets/Makefile | 29 ++++++++++ tests/assets/README.md | 10 +++- tests/assets/random_romfs.bin | Bin 0 -> 98574 bytes tests/assets/romfs_source/0x30d83fe5.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/0x37bef0eb.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/0x442f3b5f.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/0x648793fb.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/0x913837b6.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/0xdb14aac7.bin | Bin 0 -> 16384 bytes tests/assets/romfs_source/romfs_sentinel.txt | 3 + tests/ports/qemu/romfs_test.py | 55 +++++++++++++++++++ tests/ports/qemu/romfs_test.py.exp | 7 +++ 14 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 tests/assets/Makefile create mode 100644 tests/assets/random_romfs.bin create mode 100644 tests/assets/romfs_source/0x30d83fe5.bin create mode 100644 tests/assets/romfs_source/0x37bef0eb.bin create mode 100644 tests/assets/romfs_source/0x442f3b5f.bin create mode 100644 tests/assets/romfs_source/0x648793fb.bin create mode 100644 tests/assets/romfs_source/0x913837b6.bin create mode 100644 tests/assets/romfs_source/0xdb14aac7.bin create mode 100644 tests/assets/romfs_source/romfs_sentinel.txt create mode 100644 tests/ports/qemu/romfs_test.py create mode 100644 tests/ports/qemu/romfs_test.py.exp diff --git a/.gitattributes b/.gitattributes index c14a61b0d97..fbab4e69abe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,6 +14,7 @@ *.dxf binary *.mpy binary *.der binary +*.bin binary # These should also not be modified by git. tests/basics/string_cr_conversion.py -text diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 7b803a03957..16a53f52329 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -220,6 +220,16 @@ CFLAGS += $(SPECS_FRAGMENT) LDFLAGS += $(SPECS_FRAGMENT) endif +# Mount the ROMFS test image only if no other ROMFS partition has been mounted on slot 0. + +ROMFS_TEST_IMAGE = assets/random_romfs.bin + +ifneq ($(MICROPY_HW_ROMFS_PART0_START),) +ifeq ($(QEMU_ROMFS_IMG0),) +QEMU_ARGS += -device loader,file=$(ROMFS_TEST_IMAGE),addr=$(MICROPY_HW_ROMFS_PART0_START),force-raw=on +endif +endif + RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../ports/qemu/$<" $(RUN_TESTS_ARGS) ################################################################################ diff --git a/tests/assets/Makefile b/tests/assets/Makefile new file mode 100644 index 00000000000..6048ee2a405 --- /dev/null +++ b/tests/assets/Makefile @@ -0,0 +1,29 @@ +MPY_DIR = ../.. + +PYTHON := $(command -v python3 2> /dev/null) +ifndef PYTHON +PYTHON = python +endif + +# Files created with dd if=/dev/urandom ... - the file name is the CRC32 +# of the data contained in the file itself. + +ROMFS_FILES = romfs_source/0x30d83fe5.bin \ + romfs_source/0x37bef0eb.bin \ + romfs_source/0x442f3b5f.bin \ + romfs_source/0x648793fb.bin \ + romfs_source/0x913837b6.bin \ + romfs_source/0xdb14aac7.bin \ + romfs_source/romfs_sentinel.txt + +.PHONY: romfs +romfs: all + +.PHONY: clean +clean: + rm -f random_romfs.bin + +all: random_romfs.bin + +random_romfs.bin: $(ROMFS_FILES) + $(PYTHON) $(MPY_DIR)/tools/mpremote/mpremote.py romfs --partition 0 --no-mpy --output $@ build romfs_source diff --git a/tests/assets/README.md b/tests/assets/README.md index bd786d5a3c4..a5f935ee1b9 100644 --- a/tests/assets/README.md +++ b/tests/assets/README.md @@ -1,2 +1,8 @@ -This is a .mpy built against the current .mpy version that can be used to test -freezing without a dependency on mpy-cross. +This directory contains assets for other tests to use: + +- A .mpy built against the current .mpy version that can be used to test + freezing without a dependency on mpy-cross (`frozentest.py` and + `frozentest.mpy`) +- A ROMFS image containing random binary files for testing mounting and reading + from the partition without having to generate a custom image + (`random_romfs.bin`, the `romfs_source` directory, and `Makefile`) diff --git a/tests/assets/random_romfs.bin b/tests/assets/random_romfs.bin new file mode 100644 index 0000000000000000000000000000000000000000..387bd7ec476dbea78f5388db9fb545479b15b36e GIT binary patch literal 98574 zcmV(%K;pmB%`t|82nB(F6AmzVGcaU0GiGHqE@EkJ0)c=4=r2VkRZKIptkD3;b9m_g zza*id#wLJkz9#Y<#wEoeBE924PEAREW_;P2S{|fddxqB{3l){ocJ{kU7D6oC`?2j< z(9$*=rE2;MQUP>03t?~FH~sI>{|tLQY`bagg9HX0&2E=H#j2OHKq#{rFv3%-bq~cVu(T&X_{?9{t7}PV z7wJV;Q>g&AyHE!q<=B*D=__khRM%NnI1yuUNkC!SESVM14t+C2-Hhd3i(eok=9mnkV2XPW4P#W z_+cZQxXs{quR@yDYWYP@re*1Um-=OARd^X6nG0pSk$aTvt=s)oSmDw!2g^sam>Ogn@MEX6L-|>{NWwq1r%*q zKOFKibn%a6KFCZK;QPRgPW>0!5cYNw)sK7u~bfgpEZ{`8SI~>4Bq? zCaonB3D;&eXYfX7xRz->Vim1C7~$I_x80zl=Q7JHA0*0~^d_VGdcJ2w?`;KobvA1~ zKbZc*h~bFHwF1~z_c^B39m6c7U#lwQ{k7Ee3~AMEbwAz!d7!b-x8|dF4hE1`dnHcd zI$RQ&X(U3IgwAvDn0nN0#Kvv#xPu8b;lBp|-M)*8J`T~)MSJ1`;|fj~Q=>n@ z2jK&VKAeMKaLk3_^~+y5+`NL*98#XGrL1&;GX(K^&`9d`n~%v*xT>j=LN41Bta0al z*EUBxedp*A=wQ7%%25d9h}2|x@uzz8{I82425ES-oAfF5(;nSdKITlw0N1C~UWxK6 zF#vH_@Y9IXsiy9{Sp4p=yL5+l4T?eW9qAYLeSPH@(8OA#h&f*Ge1fTxpBVRcC^Lyb z`cut=;vUaC!QuM^1I80W&7PENBRzrZDn>;lB+}B4xnm3pf8pTd{M*$lQ{w>g z0mi?3$~vIOME(XgzQF-{d-(chgL&`BgSo8t52gw9-Z92C{4cY@s>~)#$S|F@F`tQP z1h(PV6=%*s$V5gz0T?|-appJSMl2SW7k8fQ=Cur&?HU8|zQ-4{|FmhlkL|sUd2%iP zmwT!?JyD&}A08jf2LDs@>xNgh7EG3{$aXOjwPNsWj-hEhWLD2c~FErYx zB2}h2&h}mbs~LDLu0VaRn!C1$mYDgDSrO~B7X#yK5?jJ0dV`M@*eCeGG0M_HO}k0# zg)ip$Zb^Q31tV|fcYX{K+9Tr6VPSqgyRWeYHUR!DIfR&H6)^>$8@D;L3NDoB2T~o4 zc^xrgAv)%81WmKb4G-r_o9v4x)|cA3TAUM z)uN5VwDWa)9=6JI%lOrWi<4j(&Hw1a77TRm)t@(Bd$qr}C^E3^slOmH;{edN)I3_} znUdiQhB*`qti)V&jc)G9@S#x`cKbM*JM%~z4z6V6vm?mL%StOm$31hGDR`K?W6DW< z6i(ZohN}6W(tzvWdKHF^MvP#}12?Xb@)kP+F?Ct)q)~OcwGkIe1dcad$#g(DneqXp z&Z}CMql3wElEV`xQe9SY4A7VGPq2HNmM)!4*!%pQRfy{gL_a{AprO*2I3M9$UM^~6 zNLVTB&P5$Nw1iF_@8XRa3=JsW*JLc5x@QTOKUyKLQ;JUW&ti8a>p{ME9zsWPJPK7P zv~r3Se5&o)P;@H6#~7#TB_3SOu==?ZyfBc(Mt8kXnsBuCNfrBn!jA*I|BE1FCfcY; z)NG$_<5bl;7zixoIFO24Uslh}1i=qKV(W+Ku}8%eCXb2Duzkr3XQs9)Zr~GkaWy`R z91nH!bVYOB#V!%oo5++oudg?PY0DpE(oO)J#n1eR_9v3ioAq+8o=Qi6zy4=cR>w;P!4H6M8eBM(4TZwOHgJyJM)!1>+MS zwl+a-C2u$S7`7KfZ=pcMTfip4ijC@~R-EVR6?EG2)h@?<^}P71SUT9U&UO&<3)LwQ zhJ000&Zv2<3_o>fxN_Zh5Nw)WK`e!+%WLf1PtpP&Hy#b;wwsiD;p%lMkp@ANjRs`-|g3KqsCgyv}NG5 zWwcb1xM@4z?W=ZE=poSVw4-bnfssZaF}NJzdn8-Y6GTx_zOmp@QkGYUI$HUL^oa*3 z{hEJ_NlvQ0HZ;9g<$G2b{RcB^eP{Ybl?W_FN=59;vg9^xYsA{|d6Evm2+HOj=9rr^ON?I0e6sHGHA}{VL+(=#>di8Y-xaTvCeO2Q2_C#K+ zOhVg9gs>#FWIV^fQ(equGdk8@4SjZZNJa|nhIhI~K_vIOS~LP9qv5TuVuG`ru8e0O zLSagT(9GNrF_x_ddpB%rO^}eR0O{6Q4TFle9;LB7r7z#==U(w+rRG_YX^2ghzQu_emI}tEpoMz7~qjiK`^CtwfI@!A54Q#-Dc~PJW(0fk_1r ztl1~#b;3wlz#jkYZ@qne z78trkatTtM@yA67%_?FEoP+URa}=B?Jo`ai=J;DM{Ob_-ecT6*eAYh}QKE9izA@pd zHu(<1iGo_tBI1%MX!qd-aN`Xspan@c-;{%C@tE$JlYq8OJnu#7lrF7|rGEmNKxaH& zf-;s#YvYMO+SI7*{X2Z+DVr(+mQODsG`ouQ(+2T?2a)%wq?Y+G2Un6{)QvyI4{qcP zy@z-4LQ=7tD`I50&@A{9KExRc#DpA!DTFC(Y4PM6KqvDE;8q{g(J#pDkYyYNV~kmb zTH=b2P@P=%(Q885>fQcU}bw0?_fdt@#h?7%K$u4qu{0ySG0(SvItw3d5_bh>R7fg%Irxcd379Q47LXy5=BJz1*x3jfJomIO6Ey5IbZV$*7= zf;ukk`=CTmrij+kVCB!s3~%zfS&RF7b*Lo&cn&CB>v|<# zY`hs?{3)s7=vsI;!c|Yg2_1aXd%Li_h9ErC42Qgz6L!`STe* z>!bYAj=a-(GM%F@CH|6^*AAmbvB`{Q;jY@bs0554j7rcJrQnuQEi7|(FdFWr!S15c zI}j6N$6L1+lb`6Z<{+dv%BAZEzYIWFWL99q(!oTSya1Q6m$o66>^?R>)rX2nzeXR1 zi>wgO$cus2j{6Psp;XO5oT~iN95BF@^N;~i!N48RnH>+J7!M}3@&5UkJ9%O{{of!9 z#jR`_shIrwX`EujE2l90ZYuWdVCm+U+EElo>RT8r(1fq``X=6mmm*gRWEycd+xj`R z<9&S(r^WF*-w+uxnEgjTH?dX@#LP>cg*P{emS~=`2*fx57o59oqFPsjK1GXTvtFpifE<`o22#Wt! zlDK-0rQt-Xg^;%;pF%4gPvBekK^-w^8f(^89RZ2HwrI7AA3`>}eK5Ii7hn=%wLu03 zH+!+tD8O4SPQ|X=bX_F)oqp-NBGvjLY2+~w<>FvZJo5|KN?TyMml+-&GhumWOX-r3 z62u>)Tuv)*_>t~7$Cse5Ki%s6v*?w{Z!L{W4ti4ND#0%5{ZV+m?Dr@104E+SgYKBI z)Ur>S;N8(r@qi$Caq$#$zWIOh^5!(N609;G6uZ&u>K*LlI&FDSSEV5tDE1ZGg;2vC z5E(B^+2d~`;LIT$ZQ+D^%cf+CskFq4a^onXAPI17`^u<;C1;po#i@7C)jm!2XD6@v zcIp_+aCj?1RfEszq=T{fND7M| ztrIu46Z1mYg_36%h^x7`n)Hf*LZV0c$J()UtO)kM{pv0oUdQ`G+>M0oU#FB-?9SXz zRBaTW^AyXBy}hH*ftAL)(#+=eIf6=L6g<`8OLT&G;WadERf#|>=}52xhH`=+NXv14 zg4Y*l-<-p8&|*Wn#U6qw0ABc1 zI_#)4Lsd|^>wSLR{Lrb;=0j+3IxHBAkUa%&e^Vjwg5 z@@sXx%L#uLNQloZY9R>pTly8p6pYr|XFEkm0lKtK$i##`Fv~*K+6HU-`?$d~Q(MGP zvr#SpD=&1X-J(VEg0@bZ_$8axEM#e^8$HD&zQK0C^G$r;@tqN^vw??P#@vfs51i8+ zaPLYExmxkpf@}~~=xdlbWoi0cX>d|hpw*P3#5xn1Elq_`UXTD;Ae;JTYul!6d~ zHYP#){K8)=_q@!=m|1V!0VK-v83Z6mJzUfQeR;Kz4*>JANYlqVM0o?6VIrQL7B zd|fOSXZkszd6+H3&@@X1c_03NkWZk@QIJK7-y^5%nIw?I$ZDpQOG!Y5*xk2Bh?U81 z0Bhf4IW|iU`-z?++3afCSvw7cLR4{Gi_+0(7x<^kS~S5yv`GmA?2ZKNeCgQnHN<81 z`PVsOLolIayKa zk-4D?j?BeF5$7m<6ecE5=DKZ^GQMD1W2y=ukj&`iml!>ncfW)7{i85$00>9lrMDj% zcEEV7NhyZ@)h=`I@Zrp$(R@1f#yIQx2ErbWjJ_CIY=LP9*uPR4GoOXuh^Kr`+NZUy z7$o*J&TQ%WDLc=r^W!zi!O=d4ToB3XZ8rU`A&Nj9SsB-$vb!%le`p4|qdn;^m5t}X z%t|6PgLXn~x+w(%<9bhO_j%;ng)IHNg@2NQWAV^@tP?16siHxPy`K~gsXhsO58Sks zHa!kYB_kNJI=b}LcG*&y53AbCz1a{kIA-A;W32U$R7rdANk@Gn1-#)M`zOht#_5Y5 zA92j(a)MelP@+pK+6hq8eb^Q$5q*B3MmYwy4-{rsucHb76>Gx*`Ji{h6X1*@_H2u1sIYalz5$q_ zTojl|P+9hC;2a%z(4#YgoXrDf)vMUgh8HAhidK*kSMrbZA+A%(aOGgB`5||v z=<9zE+m3-+?hoEYffVp8{RU2==<9*ILdO$U*1>95&2N1)S z&vK4G39=4D1!k!1K+a3iG0woL2Hq|p|04t*#=Ti?)ZfLVIzqeNVBJ*X$lL2IG*>>c z{P80r=TDqul|**wuB;`$))xjeWM*qZq)trD@qzSg(5>x=#%;XN6PrHxm77dPyq*88 zUu9N0qs=fx?Fna$Zj0R3lbwY+Pxu75%bBVr??Awe_7TZ^TJdtR`T5Vidy1PvQv+T? zB0t*Iv3|>QchnsS0X;p)Y}TX37H1L>uc|*iKr0dLmDqy8n`?BS?~lY4igYs2tW{10 zl9y;Z_A*(?3$OyGb=SUPP>Uye=qNfy@$26orKeYo+(o%LY|LUsyOx zAR@3R2lw=fJ`DBar>rW~;&OniY@Cttg^lEc(61V~T6r^cTXff!XJG7IxfsUVtM@5r zhM2B~;s_quDz!G^ynf4c0l4YiwtipqM^2WOVQ5b{4C>!kA@lp$HGrpASU){$Izo*H zPc41iWvHKJWWPg8*`pJq$dhUJ)pgUAl4r0@QEG=`h{ISF`r~<+?Zts~C#TzQ0pGCr znQ|fg)88yreFU6LzTj4V86k~o;QcNbitZc@4vBv!!O6n2`5j0&c4&wFi0~>QYv0Oe z(jR_b2tT7va{}5z0BL#Y*Z#xe-I}~-d z-0fUEuWlLVUs9WP~jW7$-kOO4R^4_{<_VdnWAewvvE*#&(6 z4pMS|-l^&PW_E5Ma$M1p7>LvsEu+KQ5FecYAhqiOB}gfYI}4~p1KK>jT3_$bJ0{T2 z$KU6v!&J%2SXvg=e5jdzgFUNK&v%Z*5NonK`iF`lR$MM+6g=qfZ<%6&2G%Q+~hC`Q3LF8@{F|abMOTS_QfKZ>^+=k=k%kn4K?PAjowju=HY-U zNYd>b);aWbFP^IdDLw5i-}|2Swn*42Q8uW6ltOe^Y4uyjQ;E=?ySUb!`P%sAlrhUY zyKL9Jk|JM+heP!+uRTSpP<+USv#rTY-%#e`@C~%o1xy3d$5dJ&*oFtb(jnNv%6^6! zmwja)@AZl#pviL0?HzN>ju06api8%C{Jz^3DW&U7gORGsdKnQ5OKGLvV>?P%r>w*e zCwGC&IiDfZuLsqT>x_NBEjBQI+D+vCs(ni-tN0w-rvNEln}Wu=IhfG+L*`z0q zAQ0nc6!Z7PP`mjf^$r0V=Jx&Ak(>VPP#Q=Sh(#2=$lzl!4^aY=EOWyn=x9@!T}piQ z1S^=tF-6Z?8_3Q5;eI(ZF4rp=hBUKq?5RysagH^>>9!J6S86f;NCKFWX2+;KOHoKa z$eD_~iX#%}n0swZd@-eQ{i}xhj-@?d(*DVR9?8x@oveBbS6-k_en&T{0{@sx5*9X z-&E_B*o~C!TcO{FJgReC&3p}48!&v=y$iLsuk~#P&!k0!4~t;&E9GCT4hHGAh$p&@ zQ2p-#TEI{RgVq79TM`4L2JtkT$7tzCQ&x*87B+sZ6&(ZV;y{r21FwZ`ckOsQDp?M~wK9csL_rr3+=ndlZ-{UM37PyHsIt%`1-;`UY_) zuWa%ZfzAS=ARJzSE4ggP;e`BrCB7Og548gX+D~)}5x<}K)!EF=F8s!Tge2)B7CZXU z;?!Yq<$v3p=(}zq<)outR>EOcFY71L)$?59P{?|)In z>Z(`R8&6q3q)n4^uA|A&N1 zRzts8$WyUKAdPXxIF?qzFM!rMUI+D}i{D6XNjA^R1!DiHKT_~kBw#2~90P4@NEpOJ z!4ej8H*RBBClyWyJvvv%;5g_lQjXd+(!EpWUj|`qwz7w!U;xOs+rE1GRXOxXts8$E zorum`I|P{LiD6DC;gs`qCIu6j;RJ}t?-@+u!(Oc;6FmY&l71tX$ja?|EXEKyd}ac6 zg&*e{Be&RLX0YBZnU&~B(scS$b(;P`XO&oZIP`xfvbSFgPdJmU+kStsMGS9Lh=p4A zeyKQO{{)*rKQWf^sFKIO`gUA6(_kazBoG=p>VblM2F;r0UuhhdW0E*O~=_ITzAgO{i z`;iR^`wSG(m`5R4*A@&pcbwe2;_Sf5-DOnd_^kdbE8fZYg4j|~401M6kGHMz43b{5 zSI3w=FSr=p|DAZ{@NN(U}RKnV9>fSPL)wfX1fi@YI@7wG#o2)%0a|JLH za$3#GuIz+8vf((5U~i$+^G&kj2n+%+1li<1(&h^2R`LUZxpMCn2cI>w~W3DrE4culKkKRYS<>dhODC2A8Wwzt#uXSkF60V zh53{!Wt48rF;FpRXb;-s7YU+_tMTM;M=ZX0f(Y-*mwx=lb|`XuXN#pMy+OAZP`-@{HOM_HuxQ4f$+)c^feH>L2=WLGF1m7w`zH!L^LrZavnr_ivrzc9f5m;7e%%2fD-W)t8&#m+>~!ZpsQ%!`j_e~jzAwg< z2c?0Tij(JFD=!~|j`Cs|)E*k|widHu)czrdA>FT(6u6T`<^2tz$wEXcCWQN}x{Ft_)FEv)8&{o6Dn<8qn zajaom5Z|K;UO*D$e7hQT-26|)dTVays?Xh3iW)&_&y~IJ2OA1dD%$Mdt7(qig5xDT zIm~(DC4@u+R=n_9$KoR0Q}-l1)_$Z<9x|(X(vn1cN(|^nSSh12Y9w@xzTIZ> zZ4;cC`l@tNNjWBD=V&&p!bR0^_EL!$9pD%h=aCE?z**So+#197XG=(Vx07j-I$*5L zlkrMTq-LD&qlLj%@Q?jG%eGwBs&39|cyWf7BI!P43Gy+k_tdOVX{e1pbnsUf#D`qH z5iBp5i`$nmH7Y05-ynD7ELm{rHUarCPuB8+k}@~OPLKr}kSrCZtRoTQ(|zMd_KRUL zNAFymbl^tSCi5V&Tm16?w`iT}@xO->O3zu=^{*K0N1;qx8C##;d`lOE`ZOnv9 z^A_?0&Z8pS&Oaqd?fPyY+jZ_+kw`qRHY^J%WXE~A_JFjm1rNg&e-zbYgto@<~jnd z!rM4s7hEm8ixzM=4TJEkA%vj0xfjESA_K)~U2^;FkuOg8USd9;asLhES_p|MYu;|ON_>WX|ML@3Rc49ut1x>Q>_X*&ESHtHn;wp)A zQ1(x?fk0r}@R`nO2kozvwAtR3@_Ke=DSE{+Y1 zQdC1@o%OBNh%0Idx!0K}5Q6b&m4oygVT`t38HbpZm@^XURX$|t`qV|i+8~4vK=}=+ zJUYCW-i@25z1&+u*d8L&QOWG8KJOh))sTM5>pZs(?CEv2&`e(%iOiFDbST3CA9-N| zLSARcr(RZF(s@7Qn11`HJ{+4qm}*|hwAVP78Gx4K;$FMkkPqU!&VtnvJ(+Hp3yGW@ zoaDF@D*KHbo+Q~x5vmte#h>VZ8{yac6S|&5Yg?VvPz0X(DS>$-)uM0|sWdxYoZJL@ zH<`fn4&Xb!d}D!9JmszStnHFoh>kb$Y$^bHuF!kdAPI)xBK`JSSey@sp{51tVbRSX zyxpOX@eZ_8qmlRu;tbBZs6Y2LorC{FhKFBMfB?b^>M%g zDL(>!a!)JceAix<+O03Mm-j-Trj~Qo%%|^Sbh!5HAojpblD{7OB?TJu>+=%9;sLz6 zuE4|)d56k>Xfg><}ek9 zlGMLX;~rkW*`7Q+Bt(>bP2i_ zkBU33aNtM{yrlCcP;lX1w#IP)jMQkFP4Rm$-vE!7lHWZf-AUs;NAnKZ)6?+ksnl) zSN&bvAy%n`I$6Q%dT#1W7MX#r$`UkjYxPSyxWt2K4|%)tWu616{yBU0{yk^Qo!zmT zxwBaokZ8g|&=zwxl6YEpir9He%;>3%RKzbR3R0#(Bs9Q!r8`4e?hKf8noo%qP1f_7 zEJyeVfK0iWfw@()xS4{$I)nz3SAY}uVh)QzCB+mX4tesAd&%_{+6D}Gf8PNi#lj>T zmis-$7IeEN&#UPj-rWORu)&#<;puinutTHw#mZ)5W6^p1W39t>fcz4YXM=PM1_JNl zuBM?2SaNQZls@x`1?w~MPwZKrKl6#?wK;Z)m}%1?EeS?2;Pg~ah!)Y$PJMyHRxXPU! z@}yv_wRuJ3um*XFHNm}hGI-@TdjoV%bwEtGPN2Y=gl~L>$FW#vR47)-zREF5GNpiJ zBabtvCDJG=gVExcTPImbu*!ME&P8bghw2oHWom$#BrE+TePtaJ5K1p-&5_Jj6&4F8 zu_PDO#4_(?Yn_{TLk%_8D(VWVEHKr;-k8!91s8CYIHtgg`o=Uo}-lMb4~r=umk|Klit2ku#K!~$vvnu=XRl4Y zqvx}$p?$aDl5Zrfut=)=%S73LnKy!nVjo$TEgT~5_fVd*EDY=hatn`7pOYDikO6Q^ z7-KOWL*dAG7KFbmM)lj&qpXWd5?^1ET=N$g>sn#uBF41JqtE`Qhf7t2z||Z$G!Lph zT8hoijKngmiM9F5fcI5quTbf@O+wYj<5~`Y+_W&ySWePzBO};FKBtx8cVnyG=vJ8v zZaiBf!ovD~?EcP^7PadF_z}rj$oOg3pu*H)|7da`7C0YXySgAix1;Ek>~+lDgd5IT z7AYx|{PmYl^*<=otLl#7u>^jByJ#4y5}QACCT4l+o`TCK94=$TgfMvb%?*oox4^J0 zKWO2rS6vw!WIv?xcV4561{6UiK$(@yz6?7!2$s+nuNY8^Ga)&V>#wZ&-LzrfTFax9 z(&FM!IY8e8+-cqkAh4h%&6jYwM`6v|`V7fVGb?}j1=U;C zfH!#AbDG3{)rO|PPX0y9ljsLkZ)YaWu({>(0~u4)G?Y%HEfP6j8W3`=$bbZkd&*v^ zB?Ph!lCUtr6TF2hSE6)hJj}rHOVLJB;rDqN##oLjF=&m-Lil&JV^Wgql;vqDOCpJK zx2G+$9#$LUU8q>Dhf^|M->%JehAzaNoj~+m)N$PWKWD+b%Fy%OZiM&)u&d4IJ0F*C zWdHr~Fym}-TZ6yE56GRl`!VOELgp~8b^o3_cUvGaucIOzsK2FThoZDT`jMxQM4s&< zDEwx3>pd5j{5{B##A%BZN*>uQiIZ=KkAKKisRJz}aqkhmcV!uAz+~8SAn8|Zq?QsNy*}8v_WP!mKJ96% z!kL=Z!e+6)J8`13WIC|=!5tP|=zqBXfvBJA20EL#cz7tNQbH7J34w;DVNGAZ>OB84 zuwP}q#2N)Md-=`U-r(#m!mmXo-9mus2!-KJVS&&s4pxZ|)R9fV+q+rla)G`1F>pQ< z?{VHRNRt2o>hOsj7Hx}xE6Zh{J4v`oJc2zA6lj`?g{on4gu@uakMAq7nm0?jDsCGo zaZh)wS3~FurfzDYA}x?<04P~k%cj6aLU}XgF}-^f!qSz>*i-;_7iVJ3O(o<8;&h?F zqV0#W$h!xU||pm@+SeDeQn`AVK?<0Td28!JO8$<&ZS z8h9Q$WPi#kY^3Edr?!Sd_RKKoBTJ$4)0k6Y!opy(g-@|^c^#w$T6H-z6UYCGkIAab z_e;JP7uI+!*~7EP-fi;AsRhZm^C@iQU@+ArLt1~=v$vTV3}pG_K{>}>Jj&$4vH~K> ztcPdL%)jSjfjkA>+&h|fygS$Wy5o6%aL`heTZh)-U&Ym<4Y~4rQ_JJ?ODh0RD&BBL zLTIj4BzvwQCd77o5cNl{kr-qwy`Uv_Cv4;Z#o*=GbUPlTEU0Yi>@qF+HRf*WKIF(Z zdIt3?@LtQ-%njYWI_8YPZ9QZBpWpPB*K*c6Bh*EDT;%pQ|18Cily1llPc3VEe`r>6 z+Gy4&neJ`97F{yL4)OwbK5He9^tVV}<8jRHeIL`P#OX98QLmdiQI}cr15+pPK$|8OP`(KJR3j z-cg0{T6|eACm1KCdvS-KGjiRnDbx%j8PvzU&Ea4OYK_{|7A1a)d6@CFu^cTFgPf78 z=_^4aY7&CzbH37DdGMedv0@XRx*`goOPw$&aY;@s1;sgImdyu=z9X;RqtEns>MTh9 zf+5K@y2~9LqO69*Nh(d@gLFTk2})e-J+iZR5bksTH zif`j$%qF=cSI3(nTNsfKsaW4N2Wmjlas$zFX&xz95;cSgQI!I-vnsKtMS5lHu8<)= ziTG3M`aoZ09OIU%iR=D=m)^B<8(a2EN=%(B{@!Uh82Am_1tA`I>?d?h_a6R?4_*tA z0>mSl@$_mbh?KGKjS@38_U`gX4AUYF=Csda(k98DwJqK1EY0FcuQ!tuu(?XK{!g?F zDDKK`!L4EXkiFa>@E|vNoY2`D-aqR`T#r9vf$j2AW-t;b3OD z8gH-OHnQl4v3QW%#d0;Cv1__gzBH*CD0#{V417;!mfQgeL0!;Y*JeWq5-rA=;p~#~ zy?TFn1UCh=UIhkJXjgC#6_TfTeucbyhG1n}c>jPisbh!{NJXpUqP6rPs<Cg$#B14#__lulw1$=-qEsbNvcAWl6^DY{xFf?^ z83`tG>aThQ+=Tx;#1$B!*`DjeVaDdlkFjfc33uoPOksfRbf)cz#d>kR`tAOy)+MAR zd7JlHv6;oOvQoZ1U*nXeJ@ zqDvfFAS3nqGV$q_x9Fv@6^K3$;r3|l-(r*m0nv%N8&SQTprp)TQcS7IDvYx4qA(Uj`Yem0J3uWbN5d8Af8FgVJ z*dDV=IHnpZaRrRgp88GhMi&KkI(cD-u9|ADHX?d`*V^uWl|uuW;edYQi6R%3*OczQVN@-G#^_b5)j`thM;F7A|NBOwSnh3%-{>5xVN7dZU@ZvFbJCNB=3j0T$}h^MSF>P&Pa)O4R7Lpf4aY#t%neSP%*V4b}scGOmQn=ar^TN6{`^H zE?<1~?K0F=JL#UZ{+C=7=^wBQ@YFu9H-D#W$lK&aPamUc0G|Bl=McZi0-SNs$|egQ z-OxvA;F`tNBPTy~xE3YY)mnevL2ytWe?8ArFqO{s#n)XseyJa_u4cu8GUTV_CL7UA zJ5gVc0ELT6_?k27me!(xm+Q4wzV<<(S92LFGcuL}_U{hph5E1c0q>0r++$474IqOE z$;qKpd3a!gA>&c-R9_qz_qPfcRnzsl_2|wEIcOniapdmz6h2Qe4e2+ZBhhJZwQDKq^*${R@aF3x?Q8+`S(cOL7y8 zIAD3w_q|N`T1u+-K{nX~aLT5|esqp!^H5;Y(0W<@ALwhX?`aI!#o4G#G(Rx;R<2Rx z89DnHnrx%=6!q4SkC7Mb4x*@HVv#nzcS84fa~fs|N%-1{jStt#yxMZnGGAmS@sL6c zEfNc9=}Nkm=Wo!n;zxzA6dyetkDSd@xek|U(-j3=Mlc@UcYr~XWe@AXH^zTyKBMAV zB=$#jfQw{msxMb_Rm-%GT$bkzTW0WWC(LpbUScSg$3!X#B3y2q82j~SCrTO~N%IPj zmzeI$k261!CzG`ra^&MeQ3fJhpYDAppIKsmdAv|itglPi!?|}>Jkmq<*dSp=!8`4c zqtMo~EN9>eJMTS_31lW`nm3*ovt9zvAN=^u}`W39;21WLqJ^p=afnxny z_-D_%X!1p2*@%#RLxz=RNsupZpj{Gu|EzuCV^}7Akh?pADAiZzl>|6(c{tY+Tlft{ z$2UWW#_ZF4;UZm^t1iIbQ4rRn9LXc9is;6`_BuV2Xv}lN7z&bNIlOMb_*w~5lPA7lTjG&QF!5TvCu%@J458DWf$ zK7(X$+t06;*$zXgGS6AmzV zGdE&oW-w)9E@EkJ0)c=4WJzqSqJLcUIwI9GHBVfkjMhiE&eeL#41LA z)UYBGWFoJ$;Dd|os6@kZlA5?1?cg^Rzbf=$jH_9MG7!>e$cvQf?moVrKvSni6M-Z+_GWg z>F1+@%0SMO*CMipVN~eseV<}LIl5LUAN(Uv|CUK0`s!z#;`9mcN#S^$X$pXc=~2oz z$ci);Wsr><`w`VD|AU)N@tOJ!6fZ?ELde=-%Tt)Xi1^G391T#ky%hCmIlJ|-87bT}ws(_0&J1$3TcftEm zPd;}UZ5aL%Nk5MUyo7_joA{-<1eQS*mu+046i&rR@wYDd6%_MGPrGTc=_Bxf#Usrb z3n0*dnGq{OQ79~Gr+zRc-j-H$y3@B|%P7azOakU9nJ5SsV>&4Nj1tSUA!|(Dmxs@T z&&7ld#Q==oRAG}e?9fGwDyIn6Z1eiCvGl)oI|^H8yUGjgb9eT85moIPWJ*X=n4Sgl zCVaJv_G#AS9;nOgt-%BW^M{PMK0s#lrJ@v6WSZDq|n|>x658lWns1 z$4{IMV}l6M_ZMEIRTS8Sl9ccFo!ffcPrgiXc_!Pg^BJzN9~W`mn>d}J`$N9JPBW7w z?Y<=7T-0V!CQyIz;r5lz(Wm~|4p-!8DylMye_ZPEh3;Osp7?|#xRe+wI&li+a+C;h zPbpeS;x0h}T%Bb}o3xsf038H5EEenGqukDb{fnER44W_~X`es7#^^>vg03w|j#>E0 zw~6y=+0E0HcOJslM@8nLvdwBI)g=tzi_%ncd{J=kzw~+zJRayQ{+ z$T3DY_(l0fU(ZX(c-y^Y=Bx%WNP%h$1R&5ME{Ff$Z(=NZ{No>NL!DJN)FoRX77&_Z$Y@T! zAY$kSF%u<1+M|XaS{#u0BELEf#Y5haAhQtPZ=%{FIvvL2%edQJfC=)0o0VBoX7K(6SoqR4r8j+eG~0Y~ zbT1g1k<5dqUUaGoLW!?4By98<=j0Gx8Dk%i1Y^zo++ZeJs|QWLVKCk@PlI z_>QKBTQ}bPXK;r+Mh)&GlN#)VfR2`c%-!;-x;FV9>U-_<2}O4}C|f!O;)t7$abGf7 z8`u7MIop~KD9@kCGk(%GgPu13t2WxHvAgp9n2zsY<6UF7pzcHznB9@-0Z`Q$WQcxY z`;itBpfY6P=F@zT$*i(NcyF!@5VET=M*Bith7+vPw@(tWZS%(@*vuc(qUQr5emHtj z1Yl~h#>*TlId$&>jYfwUo_%^F5RqAu5S^(02p!=hv}(IEJt~=0Oq9cG}Z1`)Dj`K^JXd&G|3P2Ld+ypu;ZOKBCt$m4T(03q2jZF5tkB z_0q6gQ-NlQ1@aw1u~0ksk$r@vTp{y}-o_(-(&oftXaqK%isW$c^i6%T<=9GK=_q-2 zoQI#%(X82UZ5_yjS`}x1=~tdR%93s*utsM^TzT%CH{{9el~bZZ5t52mj=1??g~?Y6 zB&nQsy@@@4qU<%mzSbsRm~|%<#^{s3zd!j?+#YczQNFelkRc>8A?;?IU{vkYY1;;< z=h+)x?k+{s6RgF&$BbJT@*|cAs9D}ok&j+`nzT+&$-ou23`Y^tlX3e(=#$qqP!9?W zk#5PeP#FytyYj0W;Fp_oFAsF%{+`z%3!71-sw#`!XJwEiHw>ftr`%+tm_`X9 zg(r2xB8no>95*fWF0DmMU~OWby2 z>G4`1|Ed9ZtH>iKHRn`7A?_citKYU!iyZJ=^cT6~7r(D5WW&2l`gq2px| zb*2Fg>+D9K8b`g{l3lS*m0s6q@vC!{u6646b1E{XErXkJVsYv2CQ6;~Gwxb3B}}s5Y{|~CVDQiC7$@G z<__e8T_ol~C3Y8#W%vI^5f@oQ$yS1}UlstO@?x^)5GfQ)k@3zyqtQulfo-pGAkg5;#>E;*c#X_lF9m9oq&?g(^#8?Kq?`|HBO%xdHW%pCKX+N7FmUqgK7 z@a-Z@42;8D;A*eG^B9fyKsATUg@+S#5`Az1=6}BrnLheReqx>VgH+VRogL|7(LsLL zyK_Upzs&RSRO4WJ^5uHsal-(b>JoQebuBLyi`GTDYynF*_?b4>3+h_jM zew+n=>hzhyS+VTN;K4SHN79$5gc4d88C1{Qxy@<@3tGF6;44d4t(JtMitds>x5<^z zA&@*dng}tl%50@7puOtQgtV#FjPf468VH2oM`fY6_h>Rmk87#>4O*9Xikd4H+dT8> zc)S?HDgvPZLjR#W$nK*z#OQ2a!TX-&V^M#Q@^;*Qmp$=Jc*orcCUSB3E6_M(MwgVsW7d|OyJUA^h%6JsCogk-MP436_LN;@kH@CW{`bfQAxiFl{nK6dL}62d*rnJ! zvEqfhmyN9U+qCI&NVq9pPn>qNDQ&f$U1ODZ)i{bbwlwagC$@r8T4@P&`?*rF?RU%; znw#IsYqT3`cIlg)(%+(P*mZUXR|g2VcoOWtk$s&Mep*MxQV=WM%AWM@haw~{{?mvk zuvg*N1ec{?5h4$>Ua*ZjbhB5@WLh|DZ4IhlA1M!{lI*Tb7Gkg}6wMTiW)LN4Yy ztq1QE@lh%tMBB|>>QB?--a2#GoG zFs#5@%cbBR$>?4NilMXIIhR5h6Qn7*kfojkkaC*+ws_+B7u%(Dh>hh^Zdy-_o$T7q z@x=AWMU|Ha?+{4^^zo%TVIsiDu~o&{Axl{}K+((Dwpf6%n2}1MaB3JTcpr`Hd^=nF z?&`OWPH34EiGd6Ud0N58iDn8V($!5WfyfS!=$`I?F+M16{E&=}LC#&kuS>2-D0!fF zRIt0lpP?!BSK8xtY%?dFKW`bw7ioLT$^+E3e$AHZjx?|4M;wgc8zjP2|+$t(-A!xnPOPK`zKz6(9S@8p_r z6iE|j@929Rnx1~KXQEEd9P}A3$SA8vdj}6h-SSISkaE83WBk@@bedFoy;Y}rx!yCq zx3)k<3RS7^Q&PVe?Pp7Tc>h_=bxp!I9rZOKVVCzO_#7IltZMd}mb0bPza3TUp+ORq z)Et-4(n=5^o$LvTa|ZIduRwTyRNNa0lou)l5S9+&Md6!dqYs;&?XGt(%gAdQg!}__ zDa|~e^QXSOE>qlurKbx};sh{YsO*C7qK{Z<{19$?O)BwMD_;MwbTWU$$+(zaSH=Hn z=Wcacs)gU+Ac%WO#o3ZGYtzL!$GGE|*OL9QqiI*`q1!prh7!-qD&ddQi z&zHUCQg+9{c;yd&*g{ax?Y<3=I?dJCvdA(1HWmm7lRX@vTgZ=y@J6{9p3ngEkfb0(OG1Hd%E4^V2rA3+xcQi?BqLo{lmOH92ZH{swM0*hQN2|fUl1$9 zEhLQ&%$6&N$%Q>2h1VI&BoEq1CRS$Is;DM*l=X{Qb$eZ;p&av7r`m0TLV8H_r6aA@v9Na5B) zD1G`mr-*csRG>-@ZV@w`cFD#TJ!x$4IZ^Ptd3r_-4(DS7=V!|Auo`qx=kLJ6Tpj#F z3}OHLdq#~WRDPPC*4^^MRGpaftkP)zIbTiG^FR)alueAjFyv>7xT4_b{)VhbF7RCy z+?xa!^vmL)1&W)U_uiLW9?J$z3~qn05t2kt>^ZN0gC-pNP*)*41&Q2~?;OaA%OSgh zrG5LeMp7}_oAvhEZ;6_9?xVJIAlkOkd;36uS~+qkT)r_^Rm@*$ zLQx~QS^w^)mlwDjYzh<#s>nNFOx955)pjE7Lwzb&8c8I2vqvGf z0iNT0ds3^@t&?7qREBEKn_93rA} zMOj)Sbtr(EE4!oeYyX1(OX^7kr){siZ)P5TT#`i7J=i3X3Eu{g7@!}o(K&?apcl*Or3jB{smsa z@i{hPOv%ZGQ@F*4FnZnor&eXI4X9AxKEJk(djKLKb1WTcSQKMsOS& zp1(jV9G|Ye3KCho&ouk+mx}2zrmB8rId{RdPgf-ZQmV=$Q%~|H?T-=zz9)$zoKd(r z({dBQ@R_vIuLD>=r)5ehe^iiOyUQ#ZB9iWJejml$^gI@`O7&iy;X|9shr^M=Zh&L- z!Yda~A^t@HOynNljQ|m4IJ!xT-1Q0w??n=5rM1ON)UY7>NWw-;r%GKn+<)##~f`t=> zk&k`ER}wTHO61m~%O3dfnfj{YUCOobf7!MQ)LVRJVurzl?)-@Nwlv%#23m=?*~NUcF@lTrLtu-FAaiT)5J0;K3B1-v$$1_kaUWL+y7Qc*;SZJ@ z#PL2VSK_1(j%qv8*@iu09X{{(0$y|};+ec}PjB%L|K>puSMx{6wya!!Hlram@gDLv z)Ri&*Pz3C3TRpoVe?%BF$VhtC`dGq!8xJ^fERh^~r$fHzEB|YF}#t>jdZID4ia(J27nwhfr-W)cxEe!}b{f8xeu0`aR zY3E0IAzD4vj!k>JaP~7u#52F!-pP2#q~{Y^qPtZQ+DQn4o z`gkE*H9dESPI9cNqlBArZ)N2gq+z3I(F4|w)e)B7N~a7WA#~Fm!W-p*$bPjCmVw<}QE`P=^AvgXe@8?O5E~^+%psFIxO~)TAy5}xukkA-PsWR5x37+2UL-IK3TZvn!YWuJ8rU z5L**1Iym#@hu|ZXPP|hOvMW-eUj=fC5g7+%KE8Lte>DIX26_rug{)^~80gM(3fWaj za3o6Z%RCU>Z(l8BclPy-x7x#OcX@KlInbN6t16Q`AgIMc^SQQYW+|0ZCYsDh8t$1v zvT)ykM*LGuZy(gyy(23MoBtj~ZK)fqScNatzbf~{qk+OUzf zZxA`_BXmUbyG859rF(x2GKrOk3ay-Ka+F`8{z<2DTBc0w1?mgOnYDxw5uM1)S*1x&@xvz0pFAqE=~xrr|-9|hgS$gD>2#=e8 z9s0Rj0pIy{*=5=adzbf6nessj8e6mdHa-*D3Rw?L)oy$A71I<$;1o*?WB zKGdl!+$fPvK;)Gxzz4c;p##vcktRQWvsog=h;Crw97hG(cT5f2YuBc`!i1qOyt=uYs5O8L`5e+oG%{P8Z4(5IU#tCrt}v|ao18h?D7mg?Pv&!7qtAo&zGoR{md^m-Zi zC(m1`M$^A%rI+(P&3i-{GJe%vYmf(29@%q=do4R9IXFB3D9NC5yDj$p9$q$tfF$^7 zaTv~kw(D=ZvrU{^E-rzY%|g@P0}W;ctCdjRtzS9T%)}l!%!sC0U%eKN<(MV`0Jk=9 zZ{1#o(+L7^S>&x;;G*4Wx#d$Dh?U`$>b+IQ;mR%Eml^6-Q~y+2RQ|;b#2~n>IdFWh zXsUs7(m-(2%Has=V17n`F<>j#i zwYZ)-b|bdY3@WMZ^q?L7vY}hLnsHfEg!8^`uQt~kSlcMFnr(VAK7i3*E1u-0DzSW_ zH);)tsaGC&(VJQY__6Bm{E_yj3VR z8j!kfUC{cwTG4k1=)Ss9x7_22pi*4D1U}$N)GtFBqOf;wcOm?4Gjc)m*BofhtEu_E zhC=WJURG8+an%vZna+V5F`FKB7a2@~GxqEiZnY{d)HnkdTRT_{jn{*uKZ8UaRMf_3 zEdN|DCH7l&T_R>f@j^pm97UIs1^hc*>Eas3Aa|w#jmS2nmNeNLNvqMqj?mOi>a|V2 zK=wV3wsaZpo(h$TKmwGul&y|0GFXHY-L4G6caj6R*CQP)H|QQG{V7=Cvd8pKoKO}5 zDeYk6j0}bqPD#0qwXd)g?+KKxV=dgkUiB#q%c%pQ8?f|iIxq16lSSLb2YoYzgo6SG z;$A0%&>0bq-p{XHBg7PDuY0TvY>HNo4vXTeletug9ObG^!H@Ox?Jpj6`)z)72ql1N zoSRawIxBt;^1P50bHdB^J;%D>sFR-i8&hVJYnE2*`EU$p_%vki5_CQ6xhwoh zN(-To5~~A&(eX#+J{HX%HTaVVvp_Z1YL0IM*E(Xgv~oKYPqd9C6kH30$pWA-;nv4P zTV0(~N1gYK-wvgSKEEr35)nJLhMN?PExha(OWmRMA=n7OQkq_NVRd(~1Kc+{W5Wx& z)`WyLTWrwzip{b$QRZ)=+{mAm-+4$!7xeNrSRxcxj7&`($xuiU1lpEbDl>uw-=D1Z zHb)l)4{^?!86Y7&$kz4@cmn_e)Xv>rr`i5H<5jm6PAsu6gu<`ikC5ay@%dWjD34u@ zP<+xxgllYwWPq;U933C_^LOmDjFeSC(4}c!7RtC>oav)1zpAw^l1}aI2FhP9=546+ z659k|pMRN+xPKS>0mgR}3he>M*-hpcy%FfH*Jk=F?OX!L*hqXenU=>i2k(fsmoIR6w}oUdwF#;-vhsWP{vFLB=34St%jIwaEQO%js-nm5+J8y~FE)OP7%RtWz8U?o?dlwjk(@^;#a3Xo4kTsppdD8AhLY5DA?hMBjp$E$ITza>z^YJdr z<{~iQB?vkJ2p=?vx@Hq#S&*!L2n#Y~i;LU18GFrkA25|@&;fDK__LA%N{maO4nuZG zkF?v3Ny*SA5=!-KGYZ$Y@K18K>9yzIrsdl@zqyJpTu0tvb^)7E*)~b$3P*N9d!M`D+K{1Qfb>IzSnL7l9V${ z${k=w!MSPOngiKXIBU{y3+~c-OwayMPj`BeKc`L1DJvm+$gcHTXq6&T1S|%+Uu|@k zMNH6{yiGW!AnV6VMe-9f80sIr?*L>e}9H>AZkqUuI%yp6ZeR zk8}M0?w*H

`UC0c3_lN{buKM1@q*gd9cB&lgF4`lL8*W&vk{MOQoNDF7%%4O=c? z(r)43N`%-JEv3lR_rs(cijpf)qjJJ{p3*%=dj#cYED_lu1ocCNd=7KlU`iP0!Tm&$wK#g88fJSOjAMZOMx znl94;M|LG&0K?6Ev5g)>6b9%R;r@XXr9kLiJ-xlv_&D@Ls88zdw@(U(-`p1*M!jA(fHpWQ` z?1W6)0zZm385lf7OW1;WnW|s9C&LyCJ5xnP8n=jmrc@IFlbl6Mb3XxlXDj0?P2Hp$ zJJ5>IRdzzzXg;;pe8a&*IEW)sm`lny4byb~Te5;OFuvNr=?6PJenq7};2oVXTHqYv zkEOjNDS5(X2H5&ja2$6>0W$90s-W-+T3o?;GWmn*?Dh`xrd53^lC?|}*8hpqDxN2# zqGu)Dd%hTID6LW%;HSy`AwrgNxTd2wx`ln0hcLkgZGcnzT{;6ZP3+7vS8xR~+o|!Agb4VK%U3oZ|l#GT5fPh`DN}xW|+ibRX z`v;#Np2Vg8@?0DweKUeD$Dbp{Fi(yMEp0PUw+bs@z^^UC)=t_!Z#mx)en}sAUlq0J zJK;xp%g@49`#BgEQeWFekCG%Fd!D5*()^~^o246DfIKOTH&z2$kD78(9e*Bk7u1|2 zMfxTe>APjS!Z)MxZvP&*bghf)YeEKJTswV2zFMI| z1rxK+{t>(UrNO&@b7Hto8UPzXZWNe*CVh`tAaW$E+rL=3sTqdwId-gd_7Kb7VqP*_ zo{yWaSPXwaZb4~av}R$6{_2sF-;k~en}p1bAW6+d@tRL-=#AJ#FE*Gj!vlK>&4!bb)4^)@S^j0F)vq#%4J6A z%Q4?U!!Y{oBn&Qj&l2@V8;&~73NqJ#A7$gZd{`5W2=tl@Srm zSMb`5zi)FkC_0>ovqH~`OUvp*s35kWKb@7v@B z^sM6w3dG6;MXm2N2{jNQ2ZrO1|Fkp+Y?n-hfKxrHv)hELIdWP71!q%+(OG@PsDD z7yrA45)r1Ox1xAq(@dYvERvDlWrPF;4QMmDW*`qYwA{G$BvXa@!EML{Fl!sIWtm%w zl@@|j;bxhXO$1e|K_}~>=uepcQsPao`iTy{D+nXY*@e6+whu1T<7aHbzs{00Fk;Z{ zp>g)frY3S(!TsE%^Uh+j-H?&k%z5FIUYmFNTllIbz*=|X!b+h5m}}({ZyyXJAZq!E zM+q-68nUMM(59;gAVu_Vw8wHDBCsbhTb@PdZQV=Uc1`pBu$exl{7O|?tW&=7xhDXw zY!9zhBp11i1!o~<=BRodGrNd-lqZ3E< zuWvL6G`IV(a8X(_`!Or1D3-E!N|J3Rj+cO2V-6G=Ulv0TU9u1^HrZ-0%sx51iJ$-4 z!xe)!ky@R~N?P({ZV}w{f2Y`$W}SfL)}06A`3XYD_qrYtjoo1ESa){6R5ksl9d7+xW7{Wer= z>5jFyMP1*j2cT#v;t+i=vvyQ@endp}jTN3xyNhj2)8s5Yxtb z8~kfCCRexhzp9!VVoKrHL`smHn={xVquLY3xuAKKMI)s9pMs*Lw2iwC$=3o>Rg6W$@%kcp@8ri##u9OP!i!nU7Tg)Pf_BVZU4IUc9;i!mF@f2jd0zm z2*3WbIO=#c<*8rHs8isN_Gus4yOiI6W0H;=a-=MBHohJ=oiGkssy;_m%!5Hc_)byT z9)ZWuTc92|G8D^o`p5NSxC_jkByN3A%foaC1dQ{8V55Fy`TH@07b^WEsQa7pcG$tI zOAw(Vdl-xV7E~X7AltJ=_q*^#W>?Y-c{U^bmkToZZ5L}l z`%QEl@}r&m=)56SQ7Igxo&IbnjCI>W>nz{9uJhJV3a=_>n0Dm)IjaI9)!x>6E z3K=%4VXNXnQh80J#BWV1TPu=Ao<8%!fpWOzMv3cq&&-dY<7v=|1M8X*SG;D5Ozoza z?Cw_jeudgajIrmm@e*2|=HUb&c-mm>(6o%zqQIfp_IDnhSXE6WDfIFpg9%&czzbFcHV8G{>a{P;rW~3cB#cKTXELHB$ zh?XDWmb|VPLA*XfJBc*#r@lHt4`x~V)G#a03}mKEtS@}XM~Kw8gnffV;2KCx+YLK( zuy8WC@h`AJ3}#?0(*@o1%P3x*SX1-aD@hA;#%W&9ML2c7NW6fKwzBI>(&MB5>wb+n zHq>hH{A55u=%01$|oL=X_1ny)(w2 zb$g9d(h(Tm=sMDnRx`?;NT)D zt5}UcFspAF^gY?qESixbq#K*0q;%stS^_5VaF%Z z8)xa6WpHZ=>|A>NyI&tR3JMxSr5E+I_ih~dtdY5n(FfcW*CPJ>-)@^QdI4yoo#yzp zpxvWUZl@a@Lx&+|@H7x_dSpUE zBM8pl_9SZKm{S-s6<9;50Xs36*C8R6OC6!Fj~7H=jM3H4bSzC2oJ=b3K+4kD9X}F+ z)_u?zFLj(!LeF!;;74axJ{V7^AU$gVJDQf1gqMwVFZv;&ama1#;;+-a|>h>6*qn~HGD0Ug6U zL{X>(NXEZ>X9N9PA@QryE2*qMsOYTAZ$`v7tg}r#dp*)Vv`lq(&|l|;Yvg>gE&0Mt zcyWc7S*b@ioCByLp>_Ck`^)ZR2IvkU0y#?IP?Mm*Pcm<7nVK*)IOSw_D!c2(Pu43v znAMMWms2Ox?3Jf|K$Vg14$EG=x2U0%^xz6-rF z>WLS1Lm2(-j#^D(F+%$q@WVdclU=j2JQqcVt2>coGEZv~nkMo;YIdGg~RofTXx$< zS>VCLAaiMn5(Mk@QK3C~{)0;{g*>7#C2pcaZBYZzf(P#72sx%#+EfoVHOqAsx@xPrJ2nfUO&wcIsE5yO1p3sX`%*%*4YDW3THzroU^H%Vi#q>Y!+jBRT2 z=HqrE0Fpt|B}{z&729Lr8RFsw6@d^;1$EBY1}KaTi}DjZzF_e_uD~syN}!QTP?!66 zfR``iH){b#%e!neEV&Ai)Am@bIYs-hV^O^n|D9uu{g&kut-db88xIl%ThvHg6a9}6 z+(|HNPrnpfyKBZ9K7m~boWXtznl_Y`KM-RU>1L8Jg7cDu{?m;%3jh z00GDEn>#J$8+vWX^qN%-PdXk>4320H^5^3J%7h|M^11Lp>RK0+t#=Ye2rT*IU#xBV z7tN`YCxqQO?5qi3%ouV#V4i-98{SUG7*ne*fUbw06X2R}3NznSKC>^t@=cqMK2sYw zP5qEIm%wHxX>=1)b|9M^oCM0%He>Q)5gie4hJrCNX^UJ>|CuwD%cPqDN?#d{M@FiG zF%BWMf+!ic@Na;Coo#?{D>6LeUs_#8Gsu=lz4sRr#`p^8vN0Lf5@-f&f31qiETWx2 z>xeF6iTccc^6X+&dI=us5s1b>pu>hph4&Q;2!uPgJ%`&CAJ`N451}dW#Vpmu7DZz| zI{*7Xcq$O6oKmnB4jr6i3-GJK8TrAj?!Vv{V~eyr(G59Pu8yb<|L|m5f;J}XJ^0^p z32s8hr)B@pJA160NQ#J80#+$X^dQ@a?8Vw~Kn-x<)NwG}6Iu*CSC$VBz zZAh_A3eWhuN|7;KgFG&3tr}pCw0001nYV#>^^(Im1kTeLrh|(lWv`?b)mDQC1`r2l z!m2Fk`!J~TCFV*J<`fSP(TZ{-e^y25%zaxu?qA0AFbLo#cl$TGVm+u<62T02O3;ox z&(SOWr^dnLqNAh=U54M6pNuRE`b&3Te27T=e8c;4uFEW+$RA*?)+mSY$qAKkE`eNs zk`KQ}jXSjJ)}DE2+RQ_aONa~c+rJm_?ZSB@Z$Pnx@TL}JJ56cCden6v5b^l1Tdi=z_Y0S#sR zYpevV{iY`S(-p-Vco?59J7q@peUEb)cg>B}*p>CVua6|Ii> z4v-$s?~i&#vBRGAGMO>@V6HW8{3otG9@ zblYO`XPhDv_3E8T5uv*MT?#JWw?YFkVxC=gZ+Bh2-k!J1$n5oRO~65t~b5 zhh9yxiSuG`gpx^5YIk>&rdNu=oRHFN0q|Fl@~OHlR{}U%^TNCLr0pPhi@#@C40P97 zqnZkH%k%}bzXA3{Bv1bx=FyI)p=Ua8pzM0ANLt`^?-HR(-0@q5uyi?#xLb9y!AiA) z;~_jMpu0GU3q3`sl>JF$1-KCTR~J^1eQF`E#sM-|+$&vAYTQzc zFm%o&yMpVbGArivdF^WdigY6GVzu6k3qm%0nPnugd=YVG!t74_e2Exn`@$*y;uwBc zcq|3~-Z_6-#IrV56ZbKNR`K!YJ-luj0|Qwl0RtYrAkggvv+^&c@i!DzBNTT2HH4Wb#@gYJ(HQ`Lv_xia9YVfH&K)AhZ^}1$hgy zNmb*NM`Z6N^Pg~y|1p^sjzj(!jJEYv2N&ZUo@17)4e0vb_LqwwzY+@2$m(B_n@9zY z0QZ&!_X9&(I#{+x+)x}64Q*Ka4N{?w)uNeAfe;-LI|DfLv!;#)uBGq?HMVyFm>xx$ z6~intG0tsy*XiwQdjrS$I5erB5u*C`vTKy2j+EeG{O?4cr>I$f4#pLcHBafQwZ3(6 zF2!1VOnKD`o%tCxU;LqPu?r_2m-Wmj&Ff)bI2*g4^@Dm`%roCa+dcRB5g_bHQuf+R z1)y?81|%XTU0|qEK*{^)K6;u&4!5>HIEuZ86EZ<;`c|edEkw(lHOib846cp@Em8DqYRhv7D|oRwk3;li zAeR2@pV}UZ44QR~{ujJAsM^3>aC?6tq3NV}6BcW%S24z6E{Cgb7(>63r9uVyGKjVs zCneQVcJ{7dCNinZHJZ5-&!l-6NCn;UP7OYO36%^N(%e$n=?SoIb>k(pi$dEw!ktd{ zFzY)!za6&@N+2i;cTGj|fNPiDlEyDb6|J8*bZKVlg|uSqU(2jgMyR|9X#ZNvcK6;% zvUR0dC*cUGfL$X(WZa)iwp-_nP*CQc@1n$jiPP{pCUo7W`jIX7@Re_Oa7E{HrWA^6 z#sjG5XbAY_ud9_(i4c;u@M$!7LfNY1SfVIo?VV3iB988=@;%EYTGCeP-i7yZ+~~Jv zGuFqc8E}Msh2`D-z@0)AgO{pd2dv9yelQJtIWcNrjoE;H?-2 z`Me92n|Yz>bB`+AkD-sI0SkH^Zyy^4@8xuU5RiA+rPp4E#18_R;Sz0DoONoUwBTc) z%EAiY&9yoTaGC~|QnqmJ`v9`Y=$HA?Rz-kKC5;fs`yZbw^L_x>hj_Juxl~S?RvW`- z5B~ebS|UVCK*qVV*v#CVWECujbY!@NBE0Gd=6cE}HN*2J(!#Rawo>R!|@&gEne_ncwbilFoe?Ni=I*oGMe_n16eZtYU+Yqr*UDX|Fz=k%)={9RckPF zBk3N1SHp&Wikup6Y2-ia`{BOlyJ@{={W_YF`1nB(iS~I?8eik5IhFGXsqhcL=$3<4 zo8O+LC#OE?)(JW0&MP{bun!PFX4z2GuZ3f8{W z-XCO1mw|v{9ee&~ZO9ryW^M6+?4G2CO}D)MH?-C4Ybmg@gSH?*tY?oo);gmV49nrlIx=ktC+Xm~}g{RQ$z z5VD;H&T@Lfovr$?Xu$!>U3YgKDRC%^Zdp^w>OpmA{NzSxwC<@by^s6fRx`Jg2r`Hx zSf3aFgz1B}cb%nEzeq~`^f1pO1%ZGQ4lsB$G%{v0Vl`$iVrgyyfq(!`q(r``h<(qH zGm9Evt@7c6763Lt$-mR-Gj6&?*rs0XfYi2)-T+YJJ#YIGQvK6L$Hmj5?$(1-PmWPiaz&h^(&uJ^@SaHyEKx=wR6>^spO~>3XKCbW!Ae45LD2-bM~ZPimlCL zU09lRb(c2BSCH?DB;Ht1a+pH@c{YQS>Rx4(N?EV~^9y?m=>a86GIE1bE=vc41_J-J zVr#4rxHL$;pRck$g@0ut+70O>$$2^SYHiHij;bS1vP0FUskV)9^vK)q61s_n8zpC; ze|U^&!*;~EtrAkET)o*M(NgW@YL){yZqQ#3VO{@!sY5|GNV%st{tRf*#j}GM!S*oa zA_5mS;B$V)tF(vrlWpJ;7tkT>y@(;gcBfBaFM`_Jt{=AwwBAPfD<*^h(!Z7JM$bIn z>nF0C`icr^-QM}Ng4*>TKw3?KTr5tRJN5{Hv= zYdx%ne-4t;OH(0^Np`Xat}K9!b2K|>l?N8<=v+YITRr;EzS!G?2(&*d@mFc78W#New zP3qv-4j_{>JM5HyxWKGo9w-RT&H^*56N6Sdye8Lh8zLKN_FC?b1}HZ{-yyR^fTVlI zt7{#ghV5_GR=_EWH&zQyfIf}6`xhi^{nh=;D+gIM&+gG$9m1Jh5wWTsxhFf zp7E~}r1>lah-AwFnqd8wz!mRlxv*mWPJVG!0@Gk`N=OZA1!*j84>Ie*WXVqUT*&a* zdzQ{eUQTa^?pA$TKSBY|Z+x#X$jeIqdGR%q6%HhDQJCHCVcjG2VcNGhjtApob%=|q zn76Aha9Z4XWhLSIAfC%huAmi zCfU(?DxM^<$}|pW|Ji8qhuT@kS=-?a(rdKcQblR-1YSRJrrkL2j16yIMl>;|+F&Cq zuLNCG;l0MYd{CmyqP|!Wr~I&YBw8I)3+8k5?bP?^MS{HQT_;3xw{->0akcz1A7OOo zU4?jFoVd!!!=$Ik0sWuoR5U(?{l5EmC4aHvfJ;j5+;!)7IBI^eCJ*h)5U5xdmS~l7 zuJMFuF`&thDPiem{RWi&VKI|Hos*9ls^R-2%r!xwg{nuh|LNzbRtYlzW_vz7*bE5$ zad=;JIJ~-hM);#UHo3Jk$xF&GLxqy~qLyq*1xEb7**(pEP266Zkc^M|8m+*P>msph z%0uSHo%Nz@#-*8gl_tZUqarUXn8P9g951Ve17#hdeT=bZdOI6LfdbMb<#GxI>y z>4^mc6OtK@oOw?RhmzsA91siWr9!plpdLmBe*stUsyG?R_i33*>x;MdPor?sB+*6o zEWX#O+7hvd)Kd}^QzGDb&m=rm=K*BrxL_Y(!fkm1&Uv`M*flCEjR-)*y+(+n9#ga} zilfp;)G*xz;?JJ{=D7a8;l@E|)DG(rOSLeFBj%+H+w;(n>Q2==n(#V_(vC^_$2 z@?4XD`bbeH0cyg{CN3`u);#A;vj63y%K3CQBdL)#-88>Vq@j6IA-nxfJ}i`Jgnu{s z);J(`g1xrn0zBBGqz;4tegm@+xHt9B(D-5=aymR6c|1P+VD?AZsxEd)=6vVJn|vt~ zP^UmKNHt<0?me#vF=f1vvKwmJ?&>8mEL4Wm?of2ij8Y$$Kwe4z(E+IBKlj;{u^ zZ}dxLcUixJrfE%du}R~mQaTxl<^X;6+?VTx{^AN_6(JYvSs0bCb_-gQ)2=()uL{jT~9l2q}O2unh^J( zgb?YGYKF(@rd*0v}p}-5by^OCN zZ#+I3uV41Es`7K0PbK4&1HR~Ye>yW1o@n&_xt$~(twGn!&4`R?ngK49BRLa2{m`h- zTffJee-!Th-~OsM+k6p}n#CXYS~$CTi6B(R&tH2W8S99>59zB=kIC};92=CitZoKL zY8XO`?8#LJGo&10{ihr;&~Vxj)BIYov!oOI$)5+iE?-h#53%z9nby(C4h<2m;W^C; zi^UgdZt1E{sfg!3>|VW@*g70HEjV0WZTv;XU3({)i#A;?5?L7u^TFe01e6-w3?xwa;LxeobF-RNGIbS$a~}?YckI;lH+0 zST|LQb00a$t=+$Yo(4Cr;loomc1~1SqIbT za$6}4pxsuy0J>aj>R85o^Nfp1o^98jdQEtb=Q80|6_al}IX4i>5BeESQ$Yyq`b#G} z6s&u5I7=XpwAA+BLdI{z`|Zmq;2Igs%J1>U02MK%LdB-N`0RX#8KhGXs5K}XHl)%7|{{5M7lMCpdGZV8s zAv<_*&g3?NVBJp-+oaU!SJ)z3@qH@vQba=NyzDtw8|ucUb=>Wx>Ix3RBd)h^Gsd=M z(y;VPu%sF6>zHW*CO+osq1<&kG0>c&k>F0Qbjh^MU7f_PG;a02EmEf!lH@@n*c(=t zH;W2dU@-5#_4qv@ zhsP`CH>^Sb9bkXBvf%P5go#D7C|zr=yAU_y1%rH&7CxL0{y{|&wNcvX*SvGk`Wsjy z0=kjB4uT_~Ouev$K8zPEY}-BH6(WxXdcg-Oi@2$osb64-HysK+-w4qwtAPQJ#ptIy z-g(M8#YBS2P-h{98b*i~2DtnV9oZGTleor#X$HLQ$fuHK2bLwsXRqN=49oZM-T(zw5LpaV z#}E=+w@j||)HMP($Jnl_0$BjZ0A=9N)V;ae?JMjsaMeHJ4y?LSt_0ddY4S!!_AU8Mz_j{G7b8YJ+<4$$_=8pq%wx{a60W}TK=?A7(30lK&A}DGo($+k3!NxPD zxayv>nUbNOAz*;dhkeRCVU>ez%{FeFT+Gjti0XWTYCvrL@8I>wB*Drb5z4jS)P~Z? zgZA?7J$jQgPNKpoGBvo-2kDf;iU(OhrgUiY;3mWAeBKLtF(|RE9`8Eu7 zYVw{7#Z7AijSpcb<(z6z&$5iw&be@kyq+%?Sz5Q8Dpi6?^qX4_r4B*E0SLK=&@>r! zm)BVI=~@HwQee}Jvswob8S8FnvsI8ESyB2SnN`$pd!-LM>STB6HVTF$xtBJL^kzu% z)CS`4Z=L4&i9a)DB?`gpm6L(r>#&gzpU}W}VvKBj>e3k0@6l_k-~3cvn+n56#gxnQ zv=7;NL}QU(vow#+)%FlrV7^onQagJ3;m04|W@KRNW74shdb9j;^o`JVKx`3H+e!F0 z1cfh`N0e7nK=&QJzvXu z7$!oPvrBi2Omn(m$AeT=aBkfyhk-?i*R)d!57bW?r@yC;#!IrP5+(vC7Ln^DV#wiX zRCfM}PaE7W4}q1~eEEK!`%<_$Dg-32B@u-`x1qg)aCB<+I;<^ojaNPYiQ^(0COHS2d1 z%-<(bMqKxqH#{)0&}qUJ3HY(5aaiPLMP8#X1~!kvz*BgF&?AF(=YR0Rp{|F12AjNl zErv^?_rTZD5DY>=y{=uV77jE#NiF5nt?6oG=kKv&u?S8%LLy_pO;iCAK_W6iOgOsY zdlZrt!^7VyrPCKV=aWAV8^{_(GbkDwJUzYUMJ@Pv?4J`A-M^}x*a8GI7@*rOYc8*d z^vv#U0*uYIxa%1n9|z1tdh~Ab@?<^}BF$-Li?$ePO7>=`Zi)~AAHc0)<$s0Gqsb&M zAv!{(UVGXLFNH^OrD4R#Hc(V$FU@~*S_VqD%O=&v6DRnXPd{B}F3`E$JY?!(%1o!D zM<3mlrdK=rpKkb5_a6FbyhM5RyApu}D^nF61i~AV$;{|jepDe(N}`ScQxkK2Cl6jG zX(!4mz(;Z|j)GzID?|G>LWh_hq=egCL2RdM8twE!A^G`AMcm?8vfpHaejHzjAMxJw zW^~TzJk9n475-y4k(iuUGod!R?tv00Fndg-9DDi=fazv@6*SbSgCKmtCQhcmF4?mP zE0@Lh#Q{IW(@9<6K~DsVgaQy3z*XMW{S8~1j%n_VJ`mih9|rjqyL{GNQyUT%@jUhU zw%4rKc<$rr4@OI~-Xx)L@^BK6j6pZ@m8jasXEB@A;v)0YMsruqT3%DM@HiuKeP zx0m5e@bXoRLcdX>Ai~ri5pg?DXOmdjx7?JXWo-!TE1kflZjXwKajs>H)7XuQO;9Q< zVOw1+f}(|72m9Q4-R;;~{vY3AxY?MF$*dG2v7(*1b5OJxsiOcMktJ>< z{1{-3Qx-gw5^9tPQoC-DCrj|o=Kao%zMQ0;aD*!FaiPnzCM=BH`4e_6`jf<3Lw~x!#W#{8>S)E%QXQAK$yn<2`iC)Y9*8WO^u=>11%l!1n19r za`#PcyIZmyD3$e7c7BLVJ4}LIU9nHGY~36LarF-w@R)t^y3@W!+k{G>6vcVPXk>XX zctDTh>FxgOQ{gN61X&18E;+w(99evB+uQHO_W{j>(f-QnyB3rVqOVK0oU1)uMz;;3 zr5Fui7Y7kLnb`Bp>}jz{K~_YyFCY--?>ZXq9^YMUB8UDOd^;ai}i?u{#zj2gPe(Z=Cb#Mi-aqkm`jh; zE3;m7uaBVeSxgBUReG2t^U{971Tc=@aDwH9k}VXWLBImQhww51{|uj8gtLiX9E!@3 znGTXQK4A({E&ncv(^rdC>F-FF;$C_Bf5+;Jp_uww!D^djMRM;s_HDxuRvR#BQGz=e<^|ozShUB3|-*je;A>8;c z9+V-W@=90HflDxqcF2iyVx{~AsBP7#5t+&yD3zL3B;_qzm9?&mBWS>?Tu_cpH0yV? zdQRBz`ZunXDBFTqH$-M0Ix>79~<{wREO+H(}+|YJu+QXJ@W)o;f{K$W8h9| zoB8|hZ$&k+o~!KmK>-wLt`5QNU(4NcxH`TDuWajZ84NY0axgluCNn`tlaV;1{QLR= zEjIBSi)CNk<^_D1frl5QuSg>U+XGkeHkBm};)4fm=;UUav+j-nzt;W~WLqUQpCqpE zBevYsg6q3eO^l=^}9D0CEmbJY@Q1?h{Gc^eHB1Zk4 zy+^rxe-Z<+G3SK{yILQ+Pc~w6xWVcL19~aJveoo;^_hL?380rJbg#$i0HQ?w8X=>R zwHj8qxuh~&cHyHHx0Y&RTBV9!R+6L>o&k#p!(oB&SMl)lBz1u|6E!@F^q=Jo+Ihh= zOjJEAUuZpLMKTBcDUp#9Pg^NK9}sDSrwuY#vOyos}B0sEJZ;H82`oVuFmSoC&W? zrMd>4)X`oFNmjzjhnKn_y%VfqNQ$DIsz=-8GE%;fC+4-QZy_G7od_pKDx!9WC4-=5 zv$R-QV_@d<+j@{_iC^h*hr<&6h|)X+$1=EONfUCruK2BBmf;p^gUZ=Jp(KtM@lIvc zpO9PJU4US>kMCjp0Elh~1Z8k9YY22Z=la6l%$H^=cobJ%F$2*;?#kOG$d~axb5v0# zetV;XXXt~E7^{ieS9l?piu|I{)Y$)LER`!bF%d$XaHFcvk@E!9U;)=;QbCI$Sl$r@ ztILe`POwzZ2lt$X^V~NMp0cR<-Jj=!`QADlTzvER3H7AB;pxirsRIp9JGE}Vu- z=#DO~oxH_t1w!u^MB*=No*$xYq6IlvtY>$C?Vn%tA{K(!i(GFn(n0H8-FAG!?gkHO zx%0FF*+g8d=gqM!m={LXE^rHtxiUUrtqWw)GnO`qC#Q7oK6?Q>9t(4!bjE-s@*13!o1Wwi^IL)XO;#%dRfso+X|N-U z^NMKdr}RFtS-v8%?idHwV?Yaeh}jUC7maT@*Af$E-nDv(V=_517Cl`oo+VA|S;tY7 zIBoX})43h~F_$C=CaCBN*!-)mmi%9Pi2HmXK)@PrcooB^o9PT1DUd3X&d#G*w8|u! zM9xLU@x_M8;SxN%=8MYQfNmluT7Wo2_w{3$aWLVd>pf63**S$ch;WFM;Dg;M53?s~ zcGFO0+C;|nOQs+nf$S}Dg`1ZPM@?}Z+AMP(!maJCc0X%^CnL>hoK8E;A6Ac%PeB9g zVN_p7i~7WLKi*{;43`a$v76t^hbpcEO_`UUhj|zXXOHoqJ1>LjdV&wy$Qxv~=vN!<}fjtS)Q!Oen;73165B_st zaV&R{G-XK@-nsLAZSt-<`?|f+5M_~(8)uJruLd=(CB}mHmla?;&gT1BQ8#V<-;-9g$(~RpJi#_zw z3pEbBzSf0WHaeE|U7wn9T6;9^@f=Pt{YZNM$j}H$=bDTQHxHc4HpnVqWq_`7Fd{Y8 zvInF<|AQ$sfmHjmd&$2?h0qldcvluf(-?#(q~-v7uqD&Fn75F}5_2`)MyZJTnHU)* zCj7qvuXMx@gCb~r+*EX}_G`bgUi%Edmr)(jQzi!zkWFWrOOo~3>{+J_At8~?><$~sCtldGhXS<+2 z#C|3qa_If&A(ZHGPa|Hs)wscXI#Zr2@Gh>1_-F;#V{PrUJ;$kphGs;hH}_ebZe!ci zfi)Alr#3*cYY*jZC)7eAwlO!gl7RQ3n-fjg8n4(-QMi?1I0FL;K^dUVT(sDqhcXFKcMqm%s(~e$PiOwnOls<(f1e0FK z#hxhXcBQ&uis5J1^WW!iOb}@x!3G5N1iK4xJVGd+C*FskNC95E0Rv8e*>c6V$XtgT zoEXdHJp+*m_dO=1jU6ZJL_)v4_`vn9)JwG)5=qSs5pyhKoE>pncbvqS(DoPq#s%V= z4^)U&Df~bwlXy>(6z&}2o?ghyE1$D)6o$Y3>_a)iI+bhSrW&8 zK^4jVZ&d_BfaS*bY3FhE!Qu8`5cS7`raR$A9(rU7x)s0HL*P?qS}4 zRS6q%Q|;O2f@{*BluL2;D!rYMQsVFuClhMc-%82Nl34HSO))vSDaM|j z=mkL8`Jgo%bS00Di4nM|sETFK7XnpCV+(CDn`OFHbpv|vq<#_Vc0;Ka5HHB6FPDoq zX6ZWw?oZbdbvfZq(zBSzZ^!tH2s@+)ssr;P3Og0wHR(mHBMzob$16b1O@&*+#FrcQ z?rA`E-AtvsYPxjLmukLQqQteHW##CmW`Rs=dbOUZG^r@>=)l9`+qNNxo}BUA5T5vQ z3hQdX28L2hN}6fV!#Xg17f9X>J`P9&f3ME0cz3^W{Co@kAEB|Q?#(6A`L2W-bpquj zF^or^3e)6Oj{4BbBr684$f*lVE9z)qIM|uU%a&9YbK$5%Nw+_F&?y$MlQ%?JDxJwq zi>q?O#iZHCO~%_EG_>ckNxC;9C>KG3e)APp5r>1RWNUKGcO-I^_55MB?3d80T1ms7 z`@bj^<-VL5WXBcE?v$8;G0-bu3e8nNyDlS`!B0@K zb_L~>;4!N5Dn-rmfja}fx5~5AIahfBYQAh18f>Kp)m-l41>Z-%LiAw&*oC0+O)$x( zTeukqbX8^+VwY83wqB-`q4}p5;XO_`s+0u8=~0Nhp&u_XRB0A`$+2^-cmZT5o^qS6 zROa;tN3RS>#H1h*#{1S2M$Xr4&%a5UCHhkooc(-sg>pU{P*1U6m%jjJte{EYrRI*zJW|t~uyzJW(vzDGTEhMe|YV@Qg>KcS5X+ zE~2HrwD)FOcn&Jm1)u(CIsK4HN9XmVXUHhbmQ0u5ySGcJUrE662d6pP#3f=&o^I*a4#QffF+8 zc%_D*eQpDS#7x2XVxq9=?|qt)kqS9u+~Qum*+HQV**GdXpS@g<>W0kEl5+0$X8Nv` zJd#rN@K(2;r2?&j1d!>g=E6Pux?J?K=VM;h1R=EYO*yMR_Td=>=6+ZB9X}b8|Ly?- zd9%PXPzgk>0Wtl{^3JEvzW#ZVq%Xx^Irq>Wd&7jx0#mULZeMGMR2XmXq|D8n!llaq zm1=zjiPG*E?m50KIh`kV($7!{v+)i&h{xD&Jgu!$R{vC2QmPpMbuABqrM!tWOHAft-?T>`dH<2Hey9DsR{cgT&6FCy5xjcI0)>=v&Yb4I?Pg!^J* zqA!JgH4LJ?UoPWD8rD@jC`hIE4xc$ztNSt2la`bB$K#Rf9=LFkhuhcnNRIyg zj!W{|WUfDs`9XoDX`4~;!*~=Xjq!?}H<3?9K&gDG`FOe*I4hKd;BvCxqFfS7^ud?i zBm6Lwh=N;&AYt4rGC7k+_h#dnF=|b(w{SYu7<{+sOW+ih-V05W9VMSuV3%3by0Xc& zF0W^M;Pz>ltq~UPJsJASd0@;i(kK)$%jN?A@;xY^N4enwnr<{w8TGgsLc1n?sZfzV zV6WM?E0kw2zPc8RmWMx{v^c`N$nNO>k&Z13EbC@s0nAs{#}J9`JZBB9t>+kMHc|gQ zRwXczSX;VKbwuNtQi{$mO5d+7-Q2JnP6yToQlwWBEVAW;kU;#fXEk)vvO%>sz`V6N z&{8Tfl;&ZFvB0D!SmL@IsYgK*g@p(~_oBl5x`ZA<@z2s$n~^lkbJmt<&9M28XZZuF zoop904V>kzo(Xo81N6u~^YO|y-)psX_w$mxFE9|UVC{CQ77FTmB3?9Wh3-Ne0h_$w zso2tWrh_{?6A^3`d01=;(~n{tf6>oe!iDk-c7w z3?>YT)w9_@>yCF$SL_BWS;-n5qYo?7gu8XAit{W)cuN*qJHE#Nb}A!6MY&kWecy>V zh>n*vJL1d(m@7Uux_=?oL5^E3$QxbZS;oY`J#TQ^)-sE{>xC_c1dt_P;vhREM?|+M z!mO^++yio6V$48fHyk>COb{`4CdVZI&ewwGNac(GOd|sBB{xSi$PgCSB`k14@yAj} zUl!MXvPq&z?ftE%z_!D|+a(5v#eh6%?uj6|!)nnU^9ZxqQO{{9X3ppLLPGO~`aOw& zdLqn~Nc9Nrr&rk&83@_7Nh-BlK3iNoV-FF%xwvh{7@xp|C^=C*1JVn>*?#HW$)9f9 z;(N4hq=Z+r zXMG|@!f@X%JN6hpYBGWdIa5Nzmh!pnzR*izR+BKrzf(zNZgj!P$qEH+fM0{t_)@mW z+ho@MFjzS~cVM6AFDLS;VsvY1SMMmRohYLdSlGx_i%5BE4ZK0S#o)kP*jqYr!$!w# zKZO*95qBU0C`s?i5T%DB(Gf!$bfUfMCS>y7(d$VcizCY#l`_oEAiwdEqqmw+_1e%5+HRFebO;JXkjF1Afsn>e7;rd^KiWhdXB*xrZ< z#=@25VE=n3?2@XS*qUqwjI$htaZ8o0Ri_~?SDJYeX2Z_Ns|e~1cpWge&!NFRb6-Z3 z+>OFA@v++^MW$NLyN8Y;DOu2@H^qY5z^0CqGNuz=s`H|2SAIa;ifg2>ehovI!+Ei3 z^tnv(hS4wwj|CIW{l_EcQ|mv3ut3Psi)pJ~=WyVY*(qs#~~60x|C@z&!;ZrL+4H_# z4tp6Y@2K@|MsS+mQmgagjESma+IERIiQG`}(jtFDF?J7Ev3Aa~26%*&U}?*7^2@`; zpBdx39HqMgoEv;rb&H*R$JVXe;rWr=*kVum=^#~8^1BK^;nuaMx*`l zHKK#UeWl<7KJYgGVYr!8@X(OHbbM&Etg{aP@D$uJ&kIFsL2A%4R#==@?H60#Wh)VJ zVruLl@Ux!hEIc8aNj(&QW*E;yf7QxXREf<_Z+vPA>7Fy)p;G^ggVI!a+Uh|zF+41{ z1t6-s%4Qyv?KctN31e+7kg&^|qoP}gc@GW#_B1Vj$O_4rQ_dm31?diek5bdv%HN93 zt)P27U3QYhZsh$;S5Fn4fNB||+$A=YvjrT%`SAJk@*~I$y4{Rgsijag$!>|(s_)`Z zyv*dKBalG7l&Gx2jTe>>4Z3dMnIFX~wdrT4#g6<=t3C19`vPy{`Xl50b)QfEWkiKA zri0RiAk%Piuk|5c$MZi`Wg)T%jb}Xs`4hb3Dg1Xl=rZ)xO2fZ&*qxh2*|^svWdBS` z7J#pz4W}i3?lO8Bb`yK#HfXlsKT)=dBUD-O2kX7 z0>_et5Akgb9}Gf}a}WL!UDMPZW}iZZ%9Xnjt&RgOLMx$Oxa6!A zIz6C6l_FY(LH&iA)N$5|1<0A(2y>dF)|qR59Qh^7TRGu&8P^n&6y0^h|yGhFoM z_m)Icm;B?XJigjY8CxHARViXW@HPDEnR<5@uO|7_?;OEShYydVQ*rWw< zd6Kyqi$&gPpQp_fHCsq$!4G?(3lD@Q&L5MGKO}e~;o|xDJae)q>RKL2-9SsuW4X-$wLS)86+{f$ z08n{5I(Q=l$&cB?_$hz|QX<0ip(o>Vl|8SGC&Cp88NX7O1$sIzi{c(OXnFW)5nJ2PFwMJF@@Mz5n9WN>5 zN8l4!0l~|;{OUFYgmW-Lgfcm}ML`NCS;sB0HtwpgnClip&5pJc^D>HW(|r{B=jpX; zNY-&iaZobV1CgB;?1!zF2YGyJ$?lr>4ZUt^`EaK{LD%k=?rx zF-HI0?>TB~p-f6#`Hs!W=l4m$(*RZ_+Rf|2Kco2iH=UwY z#o@YcvSQHE;1i9fw0Ga=^6&=!M!Dfo9l|s>%GQ(%6SwQ5M*H?=baA z+Tzi;I9PJ7K)RZ0@B2zsjryif3_)_K2r4cBj4+E2K~|TDcEosXV@dr+DG8+(VwQne@GFn}3z1v22$IDeoXW()5@FPHCaR2)-p^I#$dznz$RQYm} z!)w-e*&NXe)$39FY^crgDyW#>Oavb{?D}ddaIY~6yhA< zUP`0+JLeLWJXS@~=K{sOYe-Ti{e}_OGvzG-ZT|BSD})QlGKd6$e{Rh)}I=&IyD{LcoaTqmI06dX>Pa5+lmrPE%zMz?bC;0EDcke zuHQx`->BN3L4H&*7LOU$UeK7h9g~c-(y`q~N%{aM;!o4&v{|g((Pu6KAYzcXjzn{} zcL&cnX5I7bIn6HRLYYv`lhU*O0~SPM%mlQf@{p>EE)bv62GaaHRgA1D*uRo&{7Ws~ z1!TdQQ+d%G7qztW8magM2T=5E5o1X5J2lfH)mF`F%+`_&v$L*9>$z%ieD<*n=OIO0 z8HOaIr3j4c`d^0>>BZf_qWK8tkA_#~Lmd1p4`!bYVn~l|-6@Mi=LMto#1eAeq`WDJ z>SNf(9a;u|{>O-7Y~(0n&EKdOR+g+)%FYd~LB8^HA@08&{HDc`8<3DV6gf4UX+=3A z{1HqeX22~x0b2KSD*D2nN5**OS*rexIxL)lU^BIID^^Tk{Cqh?QR+<{vfOQ#s2zqW zp1d_RKPlt!_DgZ`39jr=%wZ$d z2teNL3H#J)*czS<;)Ggt>rHVuDiJhypeOP`)Gkq!(wnS|a&GQ|80G0*c)DvovAJeu z1+T1YOl2y~$PqLIJket3SHXmWM)@LupLSudZuZ7i=1C_CY9qef->HmIXLR0KY&i*S zf+uG6vgUuf*rAL`Bt~0heMU^%w<}2Y(h5!lhrjj)dJ9elPG4O7)*3aiF(%X)TF}zv zaj|B)Nj?0e}$3-W!$fOepdN7>^TdAi}dTT&|xxF{VL^n|;5NSHq7 zXK}L{6v_YRZGR>+V^KI=4h-k(G{p0yt&8vJOuQsbIWrIXRaAS)RW->D_*{yr_gAXA z@mXlp1hgTS`0CBpuli?_b&_Bw$ed+$^^L}!Ri*ta6SlB^haKnE zJy@Z_+$}O(+SsDs+MG409H$&y9zr0cx**DEvFLGhz>6_{LbUJovQXfqdQdCrtPN4E z>o97h4_|uj)}5EANB#>Ow-k2|zbza2{6y>ocK&J7txWjP8iu?w=_^yEea&O`;D5U|C{$x^+KxkCeO;gI(!)S9 z;7cXBR9i4)mjr}QMr;LSSRT|duiz!K5380AJ} zOu$MGHhME*$jDOT+X+gI)5qd8bY#VSE(OuicjwOOp?cFACR_@{w!ujb-BMBEyP|Zh z*JJ?7Lo{bT<3bJ7i0!?E26#h2q#X~01GsivR63SOm*P^=qDHtV0kVHr0K^HzG(%p9 znYbPXnxgz(3+8BUv(%g)CdI&7S@Jv2lYy=hsR&oV(Al8`$6S;uV|==utq?xPy7}Mw+pYhO8|4j&fVM#7{Fi%>A*Un2Gli;r@yJG_2OJ4$|G(x(%=Q?N7WX9;+xE z3ln0D{QH}6eE>iEKwinnP=Jk73Rt2Am>=|iwF&UaPBn@Gm+uEJoHv)E6m75-#hm(D zQ_?iI<}X9HP&MHM_~k?g-6kQUrMV&H9$4y?Y|*Y><6{}rXO5rp3Ib1`8484{giG_B z|M`1#s0+4sQ-#N%-?q@89^E#0%_Wg0a+V!PX42xsO8EwO<(In&AkKGtQYjMtrrA@sZTl7EydWFT4SPFhU2CCtS} z5&8RXOQN>l23WqMu)O|&}d=Fh1=UY;{|>P)tsf1x&fIxirxUY9WP9G~-|4VC|u zRr&=1l-8p`uXea@>Mm_PeJYTA9>$~l?ynfQyrpG8FABWOXY?Ha&)LplXaSKeC{;c#}nHJ4ZO4glQy8LbK`B{ z|4CU>tujf*WG;BPWD*%PlSQ|`2s@Lt4ofvT-m>MjX>kfiM}tyPjmYQ>dhAp2EObqL z1d!NhRwV#{X(WEYk~cZgg5&68mHL6NwO2l5HVRE^X*Lo6FY98wBcS-VGrGZm;@{g8VGD(hlBX&nq*e5y1G;jr}9)Gw>4aycSus&R(u?j4jB zzhQ($%IHs3VS}b(POS5Gzw>W&PMc%dI zH^Pj6(`pE!LPoS=Rhx{C{wS4iu z4$<1rhYmaNXasiG84Rcm)HlS0{H@%JeKaQ40r-88 zKe8R$E3Y3(b>X%LyE}m5d`-Y$3BfZQN5EO^sMmRVcjD*Ksm+H`KjifG#k*`EgVKN> zL9R0lR}st9CI2@I)kf9T6p_`ubb0Cfb5Ic4Tl=Sx(V#x6r@&&fQ%^IJ`YW-zBNq(U zh6b>c0Ex0<7}J}A*FiAC7rl+4YKewyTBwq1lVNRg^U3YZ@8{F-7u}6dv=CBFR@OUT zan=t!6qI@LYu^uXavq%vDGRB+id=%FLk!|o{2hV<=7bU1Z1S0vci*?Fg&BA?NCAt9 zlyR8-_O?%Y@s0YIm=h~t(WXz3WNJ(mgN{8^;UxyN#xCKJ`{#_&;$NCxIuM>Sri|S> zGRTJKrvN+Y?Sq!j8k4^zQ{ff65Gw&G-S6Bl9YYD(97gG0ks%TUH}8s~x3gU{@2--k zpC74SbEcnPXd-yGFi9rybQj=&7!0wcYRuxyppsAF=?o0*XVGgZmFG_+Ppw-n zW!B4qbBe!5z;GRvc0+Ko6 z0K<}-DHgNT;d{%e{+Lm&h9p&fI;Nv93v%`hqb${goG6QZqa?V_`TV&{Qq(tNdQ-B> zy9oQ>xQ8ve8s&0mZp0G93?w&oLg1qnmr9l?--N~I2&t?w?`R`o9DKju!0b1XQj(Hy zKLG;T@6R_q>A3e1s@9K0Ja=lCb1}mhPlloF)dnmo8(_(xaO>j}O9B0^r+?|&%7&fL zeu5}FXx;M${>!@O10vxh(QqPy;R}efO6U?jXU4rTs~}!6S?tju;;RkN0#L@wx;3DL z?lH7`zkHn~OzxV$RHpOp8dGIE&VL(UeSlGOSjoS?bMW}^SR@Sn7HV=spCJm@6`Q~; zuYdpquQl8*ab-~1Y@dxCTJ16(Mv29TsGKL1_oILbskTCdXho_)Bo-404U5w3QwG4D zyE#!#RCB^YR$44D)Wj||q<6xIb_bhy-n>C(-sqGG*~BclBp3$rOCN%?a{4toQ&qe} z1%ZGQ4lsB&G&naoGiG8gVrgyyfq(#M>ek=TT_`PRJ|Lg4bVO`c(xR?>mGVDa+FCoV z7vhGqNwL+FmJRiWTl4dNZp@H}07XE$zjX6on8EZ5hdM#Y)zgQt!FvVMJjaKzLq4rn zzSFg^3K;LyPp!hM@)Em5v2=Z?yoh>`PLw@6si2h`#d< zdb6|*vSrzOUVYc8hcK;Cx<6glOED~76h4b#`_3NUC@rW=egZlmume8agc6&U%)gS+ zFecRsGSMQ%Dc}-(Vv0T|;&00+O%)K_ZBK%tXF#rIYQ&~>+Rpyj3cL(M;$4Kh75agm zo~O5ofdRyi%7{i%NKGLZVT+V8v4p<;f@j18O$HFkRQMU?D#Z1y>7>W}7kPD`?q}61p^Eh3^!d6% z5g@;13R8-t=c@o@nTgUuvvUt)XWnI)XBDgTV4!w5B8wo+X>FTHLH*+&t#tlj8e8@O zv+K+pDNIFljTbd9(~_C;Va!`N1`A1P`mbk1SPlXI7 z$0*BSMNx-47&d!4r9{@(hDRsj7L@V4x1A$Hw;4ft(HYoQU1@I7 z<)#h;1eE)Iz~Emkgh84z$RB|M9HAr-uH2yY>ed}0<7X@-*tD^>9>9+325pbEPDCZw znifF>^j#T{6;_nq>l~k~HZs=l9en4Az;H_& zd@~nSD^D76PA<$g&1E-&!us?yH^}Y4Zw{HqG|zwZwZ?lxR345Cex}>a%ZqvhV=bA`p_o(^q-1`dHGaKQ2xm=!njV zro{ApF1*^*GgjC3*kS*;NPBK1C+*d!#tFDiWEmW93twd16)m`twL(+~cy0Q>J1j#U z?Zt~5QKWRohRGP^W$QvVc{(-5lEj7!M?R5Am#HYnvJ^0EJjm(7qCaw`vr!&>qzMZB9WKTwqOdqt zaZqXe$eU}(DEk}Icda!@BTmZ_mfF3@c^y0o?IE7jRC)+%$Cv+~-J-t5&L**%6q=1Jr^*(^d+M`d^6rMwuG^lM*PK= z3F^i;PD#6Bo}0{qy60Hyr-)j5bW)^4ce$c}H0PIZlG@>bzI*-|XwpF5`BLDWl94nB##0wdQgiq72rq1Zd7XI8lD_MzSE_TU$P^&bPw zQQBg1FdsqI3vS(j&Tb{7IKd#$Ij6e$o2ioWfdu4=(0B*q5v0b?gH3D7TsB2;VG_9e zlKZ6l6eS^CX0ND~{SQV-5D8_z!#P>bL9c7BMho~sGDGY|Y{_MFhpOD>KmS#Gd(jXM z11G)apbscKajog6OoBVf z=pMDKq);XM>JC~|{A%G?dl{cb6&gy$d~`Y&fLvnP)5y2&LtS5L3P{@=r3|Jy4`W@=HCv#|QYcdIVd>Vk4~cvr4=_2Li&fMtGe?bC&1jhgSmKzy=)G!bQ- zH@D$e)f?+e9|93Ru)buwC%Yo9+R|9M^yXEYw-La(*1FoglTTSEX%L`^da0b<8w4!}+oFL9v&o|Ld$y5_5r^xm#HUv;K7@#4Xo|K(rO?Gh zR&_v2`1`N%KB8eL79W}oAb+XZDZsz7PUUY<1yH->0J#>5pJ&^8dUNnA(lp-+E=YuD zsCn)Ymaxim<{6C~7IO0a-~%{+4NrP_C<#JsBOqe2&WMo5U?0krGTAiaN8?N!Z=YE@CU4z?8ns8oQhX&l%v+YI|n@sDH$zuvjW6(-@Ib9GR<2g zWnPd|?0UQ%W)q4bgB9T;8mfWu=@tf1KP-#A-o4}^Xp|7S)B)paZjeyS12EIy&MmiU z<9h@s?+B;psKPAb)7pb3x>v{`(OK4Zl2lbQV%i~Kq$<*_rkZ^~P&JoJXMFjo2pd*; zAGehAvh3c8@yon*=wsQ5j0_|{rdY}CP`95`I`UD`fZJ^7dHm3!a}*maN{bm2fZ2kj zL4n(7Hz`SonGuA358<}eDO<(IAz5GdE=Hm z^0U5nieaVG2l)4{-TzXP3b(|a2#|=Lz)}R?@A-#_SLn2c91YV&esPhjHuS^U%2P%>if*92X#@)%>fz3d$b5h5k$qP!RQgYSEx5glNeQT9)?ge9ZmiayI`> z{GVt}shkRIvrcrl#;e;~48a96A2N~p&l3HB^E~_ysm=C-?{al%wbjE@T0q}AodTU!FY{>ds#dAyk&z~U@Z`)>4l}q|`!bcps68L)=BkK)yq)TF9 z)mSThtPu5*;=&_H&(N@Vbsn4Bc&cbNwv~a{Q0T%H!W<2?i<0a5TrCqrcEJ4;ILz;! z9!Ir}r6O$3NY0q)@MM2oj5XL61avwLK~#l3aWWqma?lF%TQN&HFI<$1k^p0%x>E3p z?J`ZOZB#0v(!3DZSuplH!R5-cqkGO-(a54{MmULe4H1w}s3U%i?x^NzA3626p*$Ar z$>nh0lOs5jtdTdR(0VbUrNVJd0)>ac%xwOhiA3}Q8EbZdZq-G4a@E`?P=rQydlWh_ zVULK)kLFFt$YL>l_>x$F`x+G4emQ%urg*onXbPPFK~r>-Kr5&cSSlG@c6QTxc>>w#aK_v_-V>y5ui3OxxSE-Q0!T zKa`QS??r{mewguWD6I41Dr%N8duEx^yk=&OM$8 zCIX1uWlHx(A!%}<$?6s~b%emg&2X^GS9Z_=_L$O4WG7{7J*REa#o-JcC(!XFky7?! zzpn$-P}(x7&LW& z!hq6Q+x|InVqLSpa4>zA1{-LOTBcUI#tnFSXbx$*OA&)}$NlVt2S~vyZppg8^gTIC zW;U+<52{p5-fLTjHx16Fx{^k}6QUO~)Qf4dc=khT>@FnnJvQyzujS~1AEWnaq33hS zgSOkcWj5$5D_A6F5!Z$J*U&NTbkNZ5(LBMkJ}oR_%>|=Du7Rz=7!V98GyPPkM6`Xr zTUbMx@>VfG-SG&PGDxDI_1d+~?afiodMsY3b&eDPr_7evJBjB(eg;bM_w^I#CNK%L z_Jj}8<>EpSS<8zrSZpaNP)_E%ZpM+RP`CD+Jlm9s!|FbWwmFtHdW&>&^VCpwyP*Pb zL}>uj9k=g|)nnSBNIumkZAqkx^Z{I}kh_N{F2t?5hga8(d+p2|4k}Zb;4--wZBVgQ zE6bE#p;M{#R$R7b=6C!d5oZqmprDlR`|8}(ERe`3v+MUT8oa3h?@T=fIy}mN?NEO(uAuWa`a)L{36R_VXgfs0(kq)Kv zXtrr*NcnPVRh5`vFg%p*Y5q5z;HM4T!q)#y6Et6Xp6s0hLyu%NrswDyb6o|a{E1cC zX$*3&_!=wrBXhqE@N?JnSnpy|@rMWD_cArs<(VP$@gn*{Xf575!TZB1uxFd&BHaOu z@831A8nV`bKtvPBUE8om@%8s`bww1x`w_jwAW9Fr0vgJKBh1KMBH`Y543s!-I)33! z!(_0+uEGmo2{pD@Cwcb**CH#JzHi2GL+l*v`f9(-tk?bjxeX?4nO(~GY=sWiVn-*6 zqC3|LmE^$|7M#8}Lb`XX_s>)`0-oeoPKaR%-^>#4Gd}~Q@6NR% zCu`e~IWn7S+)8Q{I)C<&{?>!bDX#5pyEi`!%mEi#$SAoziI31Z)YrlqUgdT=MZap1w1;zWuBzv`?B6;XBM0Qly&^b=nG2|Q^K56- ze45%G!tY?hK@f;lza=?A@rSy=o1xR6{4`w2BT*#UJuhbE?etVo0W`4>W~c&DlCwA= zlpsR_@fD>iYN;>yR>gxiwr&dRb`W-*(gQ(a=%c6HlS*6ETU16K)w)hU;dzBckW}!N0W$}w zLYyz$yySp>#L(p#RCAs0J{ms z>EbmE_g4R~UA3(M89X*r!GNBaC~k+Jcq3NTa2B&z`>TSyn~QQwh(qf~Q~9;k(i>^Q z$5VuxP#v&)i;;H@fng700%Owc82%CO>mDth-8tjgOOcQ;sTca_0Si%?b4i_?aJYVu zK{OX~c zR><_SrVa_smJzZ@?54CKrGm;IB-~mjlO`+M6ile3KoHN+N5c_o^d%XC+n0=MKo--; z;CUx|09tfy4TUR5g4>M79)*{i(L!oR;EKDFs0#jTj&U|LjyKpw#}Umo>Fah+Kt$wr z3(pAa-PcH_02}|7CVNLj9OkRser?=-D6hQsSzHy{7K$z+AeY`n+-u9ZGylZbgy6?i z<>QHAGJjM{nJ4@?|2fIaC4iOLU|7`Xa>Uf=qxaKrk0ftawZabW`)s6%&77Aj6OVk* z*o$1lMi@P>{h=Ws4?LY#b&}yowHK{RQ7rsdgX8sZ{9xhgaJgrp5?QxkaRNz_g`T$? zb+LipD*{yBZ*^7ctEQwxC`ASSb;Wzkz+YZU!?_!-g>2n9+Bn_$&Qa>{a7eM6Z%()m z=G@TsK6?1?);}y`nw!a*tNI!A(|m_miaY$7ic(Tr?uIXn76JXygJsq(Fp_D%(~Q%x z7gdot_$iUV=_&bt3TL7l(1WvTu`xQIx>z5wXe|?o^8tK5CFA=XM1p#mZlRCfWTKT9 z`gB<($Kt%{bY;@ru--bIZw(tUh`OD|avxEcR|!&HBK zuIh`;b=c;Z_yjH-#OS^JN@w^x^v;=4*XpZlTv>Kz%+c^WQ<|w&EV;c}_1a-LfwJ)Z z8n6xhA%Y?LO^EXA6D%%|Vm_MDUwacrBduz;5MkDK1Y92&Z$243xGMD#QveUS`@J(h zhDy(E(r+K4S1#Qd0mWs8Vu@Qq!FIy^lxJ)_myOg_CFa*;xsGo2DC~l#8eAOTByJ7^ zgo&jnv8%>rgBFE~Nf)dzYEXYO*CVp<@;g667bS5>V7gH3+R-3?K0Yk!V?=e>K^ag|8jYQp(uT56IoIp^nN0HTINauOLPFoSA z=7y0avZ7|D1;wKy`8sXpDtq;v{R1K_dK-72qMFri21gbYeNQL-&)%lEvp;d|+>MWF zg*WHgrIVXi18e+&h7S>ez52NtdQ|ur*-+H=Fcu9f+x6IGO#LkCNWj%qiL47S2WtwI zfgxD2dkf1Y`KF4a;3T;65JCQ$q7g^1>glE40j@B#cei?Bs8w_Zwu)pTv znNyUv4+kBa7C>O@;q%l|W=!eaS)3|5f7dsvpvoj))Aqnh8-iNtvSiv~>kHSs?At1A zdFfBpkO>pFFfrCjD5OIh_KGH*aNpj5ogSoHzWcJ!bE0(iFK(7a;2b3L)L{!k?2B$$ zQ|4KqSbj~R#01tptt-#yj=5G=rb|P5(lMsU{ITX;-mbui(x=tAm<{f!)2cra31>V- zw#5*DEuAO$vobpSs3MY6!TF*!JMsDi=|)4O2G%?XT0+aLZW!V#=cpSf@&y7$3oFG9 z+YdY#fo&E(egcK0r)FED!QgjfeDGB_ymareLepElp@i|nTG64qUgDj)Z!zX?U5rG9 z;eu=z#;f%=#EPOp$7voH6W@zlP{E;&;($67bT;=p-dI&Ele~V?nbe$L>%9^yg{NnM z%qhD66qK?zL!7tXx^*MhYMEEe9%DQoFl6wrKdmm8OMkt1&8rO$_2621d{s*Bj<_T8 zy2VTUzsXvekpEVY*9L`o_>M|LrbsP&@Xau`Yh;FJbjIDt`t;C=WOSI#spWSQt$Xy5 zUY~EqR`nX#F*r`qi|w@#rL5O^x} z9L^7{%={_xc6@kiOoOeCWlihPa<&xKX;N<>2p;NALvL|3{B$ZZ7w?+-E5?fmkI~C> zF-ob!)`vCEv;Zr|vovPLNd>IcXvZ_P^TnrpsNDvJ$f%VY&FG8n3b)mvY1j4)FdP#L zdh&C7n#b~B)e|z@uP=EIvVtLw6Stf9Ahjd)CbEx6uPnvn)zN4f4#DmVq9HBZmcOW@ z8hR}M$$Rh7D982tP(tLzY(E&shh$a6D%BK=#T{;vXvo5``MguN$6a?#N$N#6=oXM~ zoP_+brK)ejX(=s3F!qB1i*QSLt_K)7ZFCWNJcsHK;H^PX<|J>hFpT^`r}~jKJ!J@P z6vCLcA{!`u&l^DZEVfRhejfq|5RDIpkmLkRTD=|eT?~FjrU8AGq~#_a+0BcWF`?+EYeIU(2?3>JWFnxuCKhyRN(S z1$;0zSwvEHOEjrBUhaFdHup&a_`$IS>$x2 zCUEuyI9z(1@CF2#FE#Fwi-&yuzsc?@-Akxeed)tC`Cot<0lcTg8r{P?=UyNbR9fov zDTBA*LB8#Cmicb~+FSw4zKlWi%;1asYt`bk>fXq|9Zu+~Y3xpyuz{1)OVnTQ;rHY0 z^m+m_MGVAS`+SuDQ7?L;h%4q54*MUdMil84tb4q%=y-3NaP1^L?WRrWw9gbj+ObI9 zwSvpBGYNKoadvdZDpc~r<1q(D$mX~I!DG3huZS#w*UB(bGQoX6XUeyR;7NV`C=&oU z@Mo40v;gJbaXAr!C$R#6x#?MMJT@jo6~?+c8roR>w%-`xzY+fuXzSEX8J$T;^tQV& zJ?RpwSV~xWN(Qfw(Y#D0EsE1wd%KoQ43Z z>{Znk_3j*-D7Tf5&^)=#1J0+-W%~OxV@VSbZ7Z;w34s~isRLtE1D}IFE2_oW8=1j?S;Qp9&2u8 zhn_RaWV<>hAfMu-;>$9QFr-6DAPr{p>Y=aNc)QOA<;NBa`I~V{6zF+x3NZftYk^Ag zx`(MPQk^jVoV)m_nl5i_l#fF>H!kb#0u;B5?9I6(J};t`YoBqkhZ!$R#5A;DJM3X~ zg2V;Kv0|b>9qe`(&tSCe`o8M!qnJz-*D3g8^nxFT2s(02VL}2Dlha^aHB>%2KjuPY zkNYdd_W}F}r}LbQxYn7OYLUH|!-n*V!Cl{hj^kz_a4a1J8~}UA29aJNZ-_R0Ufz>D zA2ep5bLK?9eCxA@t!SMt6an-5fxfl^WpfFe&g6R9sZ`)NB z8uTI!zRaSrsSSTrthV}dp^_NxROkO;rD?O({LD0`K(+vdcH986lj~MsjD@uE0$rGW zky`+>a3pOoOnlFT`32^9ddCS}A{^~fm~j%j156SH0sAi0@7+WNz3!nD0k_3_(})CP zEow#j>}!D-8A%tso4TK3gd)RbBN%}IPgGC8lr$!1iXU)2mdbY0h=Ag z$#%RbQZ}z1mHCpu%*hkDo|*bY+eljBc7TAp1;MsQ>%hd=2vo;5Oh?77N`oPPIxgNMAVTp!I$voGEVjq&7DZm_26cubpNd`gf+kF(&7nL2O>d-@t=!Z1l z+fyaQZN}~b>#a5%pZgNBAOKc@Yos{R!KVBL8u9iu zFIvwX6wdfs%qV`ds$iKKOA9OYe2Ewke~bH{l)Ub?mNvbDaC-(OpK!{jqxv$>OFKU_ zDa}$orGJWYt+w4%&RGG4=cE52y;O%$OzoHK^ylb77wt6BSq0O5+A#JLy!{cZEST5A zn5M*5uw$4>`A&VmX-GD~sH-m4m)9Xu;EN6zQG}SiIq9Q29P~z(#Y0C!31bb)iZc4P z&xK8y#>mu}C+;3n)rh!B4;`dGrIC4>d-e(RXv_-tgfcQ+9_7m^g$zCDiciHbQ8aCo zUj%1uNH(?J5ntP}%x!7}t=g$wN&c+C(5Ubb&xn|N67*D9%wCUT zNh{%#*Ubf~UBEZ%c2^!dN6ud}zQIA=levcBeBmI1Ry-h{bIx`41Q|Y?l8p3SSIcsn zq6ddQt%P7CNlTKwE0|GFa3Qu5#b_d4r^UV8KV75TLs@{&!y^)LUb6F8+c1ya(u?Vn z*zV@s;|(?(ewh>)qyR1*KZiwqJL>Vovs zh)8*&!-vkDtbi)pV!i{IoG!`H`X@wTDnf+foNSS;7T-qqLMvnuh3BUo7y%?NS8Nix z#x9Yqzm8e7a%WZTl(~u)$ZzO;2zF7>jEr}O`*_|okn1pN8vUYhT)^MUa+Lj8Db2cF_LJt_SMUCw9Ry>}mK@xF<*~nsW`K)kim4w; znUSDj%b8p#y{2RuiU1|gFZ>KKq-^tN%et{mwBrjqRW0*(=3QLf@e@U?=g5Y8otJ3p zIaLJL3M|ui5DaCa&cc=sc(X;oAXO9%s7dM7ic{P`t&g*P?GD10hrNAMpq8km=m|+U zG#=;r{%+{ZAzL{CWFAN%ni`IEzEZXP$T8ah5p-sw6HLFl8#k_mSqRc$3a1srfvBHc zR91u>w9m@_n~KRK+}R60_8GM>}A?N?XF zI03NVHW7BB4ula!Cv{ar#C<{v2=%iPXlG*z0;MWuQkg)(q^^b3$JPFx@sUFz?o7`J>RNXE}2C7t^YsJxFC?I{sP$+cjb z9jpNcoFMtqkEbFLN`3|2q;KIXe1k?^Rp{4A9P~w|SImIbNTlvjmg*RA}Sf~|0|rh068@rPA8*ux@RC~hgsWK!l+UeCM1xWoX4Qq59A zFqzB-G6wdwB@CIZyZIs1n+NPOVw3F+$4^{)e0u=QfEptEgBYiHxtW!Aa)hlCF3iuh zN;ASRwo?Y6<~ceNpYXDUbLW+_5MSv{L~oA`F( zwb2gI9;n=jZ;xu7Ci^|dSTS*ffSS->JQQ70JW; zT}*B5;XO{7w%Hz|C0U9euKU1*57_y8rxzw1e3 zhYd)T%kx7APO^OgElD?Yc|;kP0r{7C>2gQg;RA;fT!z;PuR+(`JU&o7EKk2(%o;l@gYAwj5a!3R3CWu2L&?a+ zp{?}oz06PE^H7C^m9+VnfQEk){C#7y+p^MV32KmyIWGbM>bJQom?mw(O;+)TBF87m>Udk)v5DU?CuGJXYiKj?UeZJc*+(pi-a z0s>F14E;Tjj!RK7hu@RGgnj3gG-d;KE#NUcO;OLa>P2%9rK*FCgu}m7)dGMOq3eZW z)Nf$Nz_nc6^$E@t7h|1XjC4s>WV)Nn$hyI5D{m^@43nZ62ttJZ-N*>S%dU4?;IPLW2K6jy7*he#UChxY4_rQ0BJ@r>F+CIVYWSxoyBE*0}?J&3mkH6hKJ3VR~r<(-i z0@|b|+}|rf|L|WvwsOkbi7G;t4V)Sx6V=YHzSOxG*viu0Omc=T?v&a zfYD-{QCA`^4ked|X0jufB={reVpG@Purqwxb`&)~#Y=r!%<^Z=#(i-vHH=OGyb{`Q z)SXJqox}w0(`T4KVs~>Iro1DGRM7J)nIqf?=6)`o$1=gR);mW83pBNnpnDg z@eiX6+^Z0qF#6KPG$| zv5axHqixvCAO?v5jn$g)yUm~t_JwXFAC&WpVyrpaPdl*X>*N!7$wVBvv7?_Q%bmjs zQj?ijTufoC8f@{&5Glm^fkaiRNW~N2dmp$>bo-#*BhxYRf6}OSxGbO>2t}P4ajt?K zBa-cm0LmFw>8J*q+*dT_Y;^?WbM-}kk65x{lFd@L9FOYQ-Jqrv)0C6b1AZNh9IM8a zDcED7DDC+;8EE^u-Dl!%QH5APtRr>5*;=SmU04BZbj)##@FgdsD|@}5LA$pVH>7Ki zjUfu4mMo0{L#sM@#yDTEXKwwy^q?x3B_)V3ltGD^EWA0lEfNaN>Wx5Aa#~Blx_G z*1ewm93;a$pc$#ZXyzNYil(W4DpJ*E;+^xo_~K4-uv@K>p@!x9J$(DQR7p1HmNx*2 zf2QjDNc;}>@PB2Dhz~g}PoZOuQm@Afms9Fbbk|{9stV$k+MKs^<|kpFidDtJvzLYb z$>yjz-uBgasJ(aU#h=bQyJjz>NP2#jq@pH@@ZN&wLYtW5x){?QS_6bl``gX^Md@o2 zZ}gUkP~*JQVk*+>wJT@0@YmBpXMR}u#SccE&ZQ{2p}Hk1E`iYZF-pPyrUxIUu7wFc zHfV!IVa~%6_+sb(8yt`bUgk)DCQ)G`e2epWDg-V{KZk8Io^_1#>u;fN`z|uSV~M zYRZ%RcE=jcMG`9Wce$CJ!p{Gc|03}3_phzG_^9)JT7h}T;@uG4mV3TH;5*e@0eT4D zEN4Nr`la0Nd*?E$4lTv7mOFt^yH!Zt(R-^~Z7CXYM~n(oUXYVq<2wQOq;NAKy~2hL1owdiC?QFV z9&Bjd=>?tQxd^1Li}cmRfKHOQWjDGF9Z2cuk^&fHasa}TxGa%QR=u#2Z&dbwC!@MH zC8ki=64v0UkOxsgzdFWr>4cm-Ln34w6!ywL+je?!p8Apg)uGTvQ)B!s!J$bCbY9gU zD@Y)WTSs z8WV33REC?3aegX}l$S@Re{ROJu7j(1Kve<9SO(^*is)(OC=aa5^PCIUR$U0V%rJ`p zFN7T>DkT?blL4I`N#1yn`5ooJ$OyWk+Z)uB8GRs2)?($?Kg)e>my4iTBOBC>dA_2;;-=zuDc8|643cujp z8tF}Rkp$IHVibnnXH|UG5FW;a*bH09&Bf(ml4qK|%`rW~T|M~D^i(AjP%Q&m zKVt?eO{;BqQ&Ng^kjp>jF1S1;@eC>9$FB6hb@c5M$9F48$kp2qRt9wr!&+tGv>!S9 z8~{{}Dby2U84vk+z{&~D!1wdNQ0^+zkpBeJYli`DTya{ls8O)R;J^bPd{77uF#fiVc=y)m0r_JhgDT=60*k#BS9fdzmK2oqSZVE>< zdq0K}5otC3JEPE##Fe}D(7(aKy@0^e-2vb#Xw9R>G$h>Be<4l57~{ozklMP$LzPGz zm{nA^5FUW@p7U}st1Vl^mVp=ZlY*z57MGU0m;WL40cP&;Rc&3P?`(+wK0u^vyl2UxxGQO-%hQbaJG4C}szI zHsZ-mA!JtTklCzW7@qo4-$_ocF&ib^?dB7aMhyCaK9n#*$D>G#7d!O-3$zhpWHf|O4C2}- zw@Rw~rUB+rxw?C0{hoBfzsg3WnX4d?*Mv9$~J z_XF}<3rZvj%i#$He){=hQ?u&IM^IqxnSWD3;mKAUuMNX^pY>PvxMk_DwjEn^*j&AM zjxhuKUfpD;!h%n`1Lgyp=I5q`6xPn~g?P&yBzN6cmkR+xb0|;-OH$WA!=fPePq7aIcs6P~heFPr-XN zNFs}w;xPx5qDUWKA3lNy`uHeJAI!kl3viA{@=_Lqm|SZ64d>qOIbtD^+US_}H2h&` zcarmE&V9ABU75~lrYBFmI`_ZG?ox|)t$;S^0VF#P;?GrBn?P>87m}rSo#w#8f-`(# zj2%v*o0Yw^ZMi_R80sT=et5vT#nA?%QG{r~emF)UWwwam)fBR6oYN1B+qVdsvGKTd zXo9x#sQhLS6_;x>PkAAx&J`z8>RLuzmP{yxID){C4YwoDJfk|P1vz@azqHq_{Sw_E zf`MkInO)A9F%yppiDCb?&L-8Pl`?hIKDSPJVpj=52 zto9$Xe<5uj?7J(0XRTr-v0%UMGNPRlK&ut;2K06|W57&uS}G#<+E&gWX+sSE9!za7 z24%qEt)~y!Uh*vU02ZBa?|S1UJ&;CAD?$;w#CSeqOR9|0oC!0F`YC_J_1%I}q`CwG zFjFgYKzrM!h@9`@}MMK$6D~Y#^pPv2VXpEE}Yi zKvT8D;M?UqC^4YV@3GP2YPQs^lf9OQR$|$%L2B$dcI*}t3cF#Q+RD={YyWCY0C(c{ z4i%Y~+$oyL0QcOL!-~%*E9ef zl7qmTRlBP#^R&q0vY?JwX%6vUq(9B|yqg4UsjhF=DO8>Ci1?k}xu@D1@rZ)Nj;6%; zYQbrm#C4W$hK{iEvlX~qg{sJKcn+bD{fg`$a@z71?PMRY`k%>-{}f>omo1&< z&gMnf;L@W27{J7z(P`$A;sRartmUp0NFm3*h{CZa&3gF313hSP7k z{2TlP21r+eBF%-P@BSQKOt85NuS$;ccg3BW<vP!%EplKn_%VU0-5OM?~2`K4}$Z?2;<(hYfrF5KedqeOGF%a@_aOr zg<)LHm)E{4eUXFfI+UDwbS2T*2eG`vN1d8YuJT@?kVQWd_b z)7F`Yn~wX<2;-5ecPw zT^y^)kkoh>Ea-Z{RgP{v3QuxD3E<0a)cP0j*pH*;ClaW_ZG2PXX91#@GDEjh@c_X! z+^8z(ZlS!DQ|1R)r(!)?3qoz08B8{TUAd`#4b^t?tJULO4~zMo@;27Be{{w_aki&L zH67C7^Y#ZMe1-J+M{Pz}fpm(M5CYhgk}?RHsm3*neFEe;IMnr_0jz(k1>(QCp0t zn#>S)51qgb3g)(s;Y@%wkuZ8Sjf_{TFyo@b1;;`tcl&#VevCIX$Em$ViY4!s1(h$% zyinBBiw{%EMwTsKY5sZcn3nr$+(NYg_X^XzJFOz%F9fD&Z?G}JqPRi#I(&QV9Ek#= z4LC=TZ6H+3Qk-D+_O_@^m979w5EC$U(F@OH^2t5TOb@`=@ZWU&0|-!Q>;`g8l>ZAf z?Em~biSqKnLW0P=oG&CrRjY>N`!dXR*KKo)_OCYJ@75eD=c7h7Nb@_HLDnOxZwhg5 z(f#6ZEw-ujtxbe8_=WMijQ`Dyyoa(w{`3|YC7m^O_4YwWx0CPzg1RtVD6M< z)K`T*(GKeO(`~$ydIjmx08(11XT!rBsmIsmbs0TVN)o#DyBLLdDli z-tesEP3}?LC$+;w!e$;441`-lQ(Sr!*x|Ri|7kK@yc=Ij{ECI*oRQ!^e`33dtU|ga zyV&XMGd$H~$T7nSscRlAA{%|F$&}vD1JS6VkHlw1mqjQlBQk3#4a*&uc@(w%@MVbq zzrC$3Dk_{+k!N%67ebpgsqzAJ^t`E9kL;0l)W>Xci%^u0i=kWEvMyUmCe$yc>&z7JBv-wsq{hTvN6b0&=3p2#SKZxhf;rvH;{GdS>EZFr6LY4?u1%ZGQ4lsB*F*7(bH)1v}Vrgyy zfq(!FrI&VO%Gk1?PBXCi_aZq9swA`QKVGTxNsBt(;FPm1QJil&>KxhQHdF>=Ubq{U zIsl%SozZM6k6c@l=S~bBf!-3N-Bz|Wx~1E-yiiiy9K@f1^6az{F{L8V8a)6=K)1iJ z=Fspm$5O)4Bjf2z>zb9{7Gi(Ve~eMdjnX4@{Jtf^qYbsw6s?9p%#*N3;dT#|;1OMs zsR!lF=#L~@g@#+8!*mH6QQ6H{%x2ua^?oA7u?)EuiqzgJi zUKWP(Mo_fh37v2rGWr9T=2_W(!Qx*tw1@u!&o2sr=)NUxk+8IWPhmbhqolDalPrmT z9Smfk-Es_r;@1?SyuS%r*We{*(U-QyP$XJ`&ew4E%gi8=(wYi@*Oie<#C%WspYC%1t+82&3>$IW|o$m-#Z;X^4 zkK<0Hd)WF4M4LaXqvF6_-d_(XW#Nwi(h>nAJLmUnsJl|nX1wTqYf8W2?vqrE#%CAc zsixjVd_vZ4JlOWIp6QGz!M_WJQlKlv5G54HgSf^djaf@=_6Y_o{amnj=g-^;gf!djwV|rNWU-ppb zLGVIN?h=&IB(Nq&!Hnk$HQzLP-t;FgsmG=>rB{mp`uv?^bnoYZQoMW@?yboqbS~0Q*p)*ZLp4ZOH4Pjn@W>?QCTmo7yFJqg zacy;-Kx#$#5bC-_;jY#tvt>>;z-?VLlnEQyebkh8=5?v3-PSZaavh;Q+yjzpI%ua) zjB@;LRF?mvuhUr|q5Rj|+ZrwyXY@IKPmqTxUKcV(x4pp@S-USN zhOVZ^iV9a5U79s*>4hcyu%O_fEKv?|8=Mn;R%-_2GZRz(^8}l`DP$V&qx>tK9jmeR zhChUF>(sdE6qxjy8-US(E5z{5m;oFH-a4zQKN2{XkPO{GzZSG^0slL_eh9kK6z8ea z8iRNRt7m}{obUKiFaGe|9;|4CuIuDrz?KPUuF}aV0|v6%1BTohxAj!|ki% zAhQ!NHGblF%mz7)08nzwN69r^|ETYm)_L=fR~Wx1RZO4Q~7`HDn#?kzg*65 z*V15|IzWQAz+(*>6{MEueiGz3jFmS;8}Gakp_SOdmSYhOc~xkhB)F0>Ldf@mr4_Q1p*%C4*Px^yg`~Ye~kelCr?!@Z+eKT&K?fOBN?~ z1jM+bAS1H(ieC^%goSdKKGRjou+F5mN1k)w;?JfstN`v@r7Bf?Q4-&&* zDW_ULv_T>|7-&W1&EBdyuwttj?KfMUZP_v42f8d6>3SFK-s|XhImRR7!)rVL6%ecU zCkJ2mFhe>Wg{0ST`r7nwyzPvK$Fsdl!*KbyeLN95dWyyh|H%EVg-FedmWil?5(ua* zdAb$rfmB3?!)D0)@~tv@OByNs|fUK(eAcedfNtsqREd5K>S~RNa8W`4wSr4VQ!X*2Lbb0_j?rqLuKO?KBtL2 zn^OT_S^oF@P~idH3qXpoxi_}?c%xF?0Yn%K3@~^N*H(|r`WGl;ko3PRByL_NGiG=0 zRnzN*Ex~m4F_X*k+j2*M96Y5Fh%jj#+NHvGI%q31JK^j}GbnSo_1R+=<M#&p7R)&T(>;i$XJ*FzgX+geIwSRqJGVKPAW=avyt;)U!)xcY|Q{* zH%Ez{th^`6Tzuj*zNx>0<@c5=$G}y zWRfz|TlnAR+$kwUiCn$D>Rg%Ibn(4?a68==gjViZK$Qq4(S6cj`Yz!Dzxk#=lQD5C z*y;g-^_BG#b=vE^FZs{k(RON;3u%#2=%KBudcF~r-Wk-TFdW}B2sd>W2mtAFV=rXc z$Ee4Fi9p&QIVk3?)Sr(S=p+l;>#L$oV`2Y%pe->Fn;^*i;CFJ@otrDWq0yzHs#U!$ z+^%}xm;Wj{?V))N)_MsxWJDy4Hk=l@oPe9q-!C)>pUf@Lre)o0d|yH9BI?CK!SnJR z1rDzgO{UTbaVP^wOy)s$PWMM#b~`=uVzL~|E(&d<3T-jN2DuZPBo(}S99s6+{!Xa? ziXE4id0-&sZ9f6r{r?K=Nqas9L_fL&DWYy=UP$?h$D}ri$An9p8|ok?t)&S9T!Ori zN@zF#DsNbz%$U$nPYPQwg6Hl?8{A~@bU1^a+rwuLN~@7*Z>jYw6Gp!o$+OFp03+dTq6w&C$8v8D%#HE&s|CjokIOA zI#|?0Mvm`F9%4{ulN!`0TVe#XGx!sdrCxq3)||l!0Ra$}%T0$|$||u6q50kG0!sKO zjF|FLN?;KvIL+u%{7^JGWOjacoc`QJUI`7 zSQq(!XK%pc1%HM{S$?st){GSXbGKg{WL5Z)_yQD@1fmxAQclb%HHcS9TY&s+)$AML zQ-1gk>x(_qWn(NWnd5Nt&|{M8@u`dCBnzXW{B>Z6!v+FoG1!=R13-iWT7i0t>;Z0E zIO#Po4ytrj3l8z&O^t@H_0GonjJl@0pdaeN8W|DF*K<+q9%_E7WF#tdqke>=tDE6|Y~ z3P<<5r^MdK-mFd_k16Z1UY_eTFS@Yb(Yg)ZCWuWl8e;uT>n*`btG3ltAETmKDpX7_ zCP$Ac{RO`nq|fs+;>3AR@zJlc!;4yKpo&8BNE-l>`KVs6xF@w9y2kS1nR%dx)zqQ> zrE*D#VJ`S%(X<&{5dMXT&?^ECxJ#$+!cQ%Ep~py*^BbvX$G`brg8y&pX1%5V!XIJJ z>R^3pva3@G1CBG^){NY52x}3N+owd*=g;*YG&`+CzzPd|;mhFG8sS@`j$TlP-#+l8 zR^LpIr(MOR5Ibu)^OZWl11muho2#3ro~3Ez&~O%?1rL{7f_Ttmz9=aU^d|~t(;A+U zJ|IG7X}jpnZC?eMi#IuhaC)g+`AisK(fcO$(NLWoL)e*eXQe@QnCr1$3?J6| zPn4*N!lVR=mcjnL1W^pLC{Uu71gK@JGCzyaU;(jKC{l+O2=^(Y(x{o;&awy*>= z`_?LUwnw&1m%%HcI()wLC7VbrD%MFe%j&SX_YEIxIR|h5O92lQOVHabT(#HJC z>`FZk!gbQ%p?4{637aNu3d4#$e4cFZslspM{B*5`P$fALK7~sep9k@0ol8;0M=tRz zO~2q;`v1zg=*efXg*8#%ba`hl<*$zWB53|IU~wRbN`H!M_sP=jZkgcBUzYJW2rR^a zouRaeAjU1eb&8_E>32>@vpTP)$+E^k6RdfSs~sOtT{PO6H?`Vq0u4|UhRwMkig%O} zfttKU5x;XpKfoxyOH4V7B_;7mV@0MTozK_$OBEVc@DW2cVVKQ`rUpx!&cq9e1(d9y zVJ75ASBv}%lft$RFuy|E1G6A{$QT=m#|No|Bhc1j6@PX_>#<+ry_uU)6vQ}XtLJga zaqAuKmiskdWCzaBqHncZH39(l4=wJC1U3qDa-rLQU^nm@>`W0)~Cn!y3MGo1WJNdn)P%yp-~}jQc9s^fXnPL4r73yP=cw(Wh(|tI@!(rj|N&UpGqqboF-yN_b+iWLDa8 z*Ble?3+wYQF$BKUEGuPB8YIPAfZ5QWTQ;D06Mp`VlTx^sY1E!1~ZJXDi$65FwDRkL3v90 zjM92nLei1L$MN5u6U)7+id#NQ8x=cjrU4G@_bfQOwt0(xu{%H&LjjM+!$`ZQv#(5b zv9uP|jhek0&uZjH#Y65{0KE@I+~1(fMyi>zB^~XNMoNy9YXES!hQDV&S;-M=5l!d< z-PVzCVY1@wh=p4!5@;6n41U9~1eWozYfuRYzyh9wGlsvo3!o=d6r2t0Q7unQtQR`qpfQmT~Z^_8Rk;Td`L&foV2Z5^@F*Ak}<%ofxePYO9Ue*RK6r_SV*~ zSe@S>hL23g27z1gVWK6S!?5CkVCMS_T_5VVH3Zd_`)sE5hZk?HgqDZrw2lk#!Q*Pz z2`gp&SYg3=UQRpJ33eZot8DLjf0mW4d7$vm0a#|MtQJexH`h^WMnOswHqMS=KO?Uz z4<({P)!bEr!BB{t&3>M)O&!bQ-cahrr&37k-u{-bSl{qSkg>mE$n01RhTJ~(_XV(9SSDt#9d<->pI-X3WD3GS}O#&hl%$W@tO9*zVn66%EP%a z)3umRD=M>HI{4P)`Sad}e>^-Kj+b;FmLs&$NL~WGHfkpKh8B!#d4*s=-Z?DPA|QFo zJ+-R0)IaCL`QKwft{ai#sCoF@7?;_NNHPezKOdzHnPtu~td@Dx{p~MRCaVaRrhGD_ za}G#UUk*bd#^lnCoiKmm-p1u;Q@nz*X5N&P7OM1*=#q4bq7DiD=sBRqu%<3Gl~K?L zqNniR(ed?K)ZXjBK5iah0uwEp4pW#~M>kY~v1Q1d}IqO}Kr#3ENxnpvR47(d|I6QMGvgtEuG& zEC#9)d5kbLagI*pn_<}aC0Fg_DK@eYu7MM3Rn6?X-7BmEZ~E1bb{Z4dI_MfkF;-_m z*FR9D6?6kw#P*-Q>bz^1k2Hgcb=yWM_eMkqy7L=r*#6Illo z5p`9OP%zbW>(n;_cdQHl5@8JPvP#&6~1(hJlBo6yt8ZMflPk?n0Rk3LoOAwB)%kDU{+km=noI zs}|R!a!$21Su4Dii~Qh-Mp&A2r+K*F5;}g0Fz64LMQw_;$H4{kbkkW-huE|0YMJ>c zE2Z+zg7DwpUWssrbbiV@iphW{<(>M0ywIA>qhpxOie)F$G5w(#E2iHIqMz5(STyw* z9!--?w{3Cg4MPJB)1VLbo$pJuoZ*V1Kn&Oq<&~ZVtu+K5j!Yz~$K0a!c^h6I{kUyM zfSR?8!GlY3K$(sJM$>xdpM7zMbGGju%nitsR1G38bHfbtwk~3*d@(0~eiS1!bK*QG zgbWuR>|^T5$#~d2zJf+R=3vUU#IVDA9qb)MkF@11c)f4TSdSO*x{*ld7j-BloRI~iAVSH-!E$#H&sTdPGV2L@_!Vd= zlCjdsPtE;OB_h4lcum0KkIjmOr)X%PJ{b1|Mv<5M26OGk3(Cd{uX|!5+~)YB>oY-} zb=GRr7bzo?!!1Q{u(^>wvptMgM@bN;=cDIF3M$z{VzYj!e|BCCKwa9$ zepY9i@qpk;%{=gnJ`|#%Z-xFYF@EhrgLi&k&Wqir!f7?Ccv0O)J9U^hEp=wG0peTc zVv7*3JQ!*Yj2f@)-okLL=T2XCA0Ytt#8McjnRgfs$IOCqRcqZs-MF9kYZpb%k=0nU zIu=-lK8w*!I=+;(k6mw&o*n&J2;1v?A)B|-x60mYrd^npDWKV1`vvm6meBvHU>m=6 z6I2k66JdW@G4<9+{ap`Uc*VmKuMfSA67k-v7b%n)p8Z}G4$~l%nsa}a~)Q|+VLE^^PCY0DVFp(jR+XPX{PrU3uu3e8c z%%jPj5)HbJ*#2}}HU?tk zDe7I8;O@+;(w*uIg?&Dg1{EZPtM7q1@GcLD=c?xMsHGa41z3m0d|)~ z+#G2W*sy7=D_nf%wk{!z*!5}LfOo3IS0em&vF&UA)nKW}_?g}$i8qr@u{t7|2ndz- zomr3dOi%E9n^S=31&VMpz$t-bbMyCKF+26R%%zcbG0POW1hP+t@h%{A;{I>0m{K73 zfWeJL_=-L&skWBcV-0aM3k`|qiu$2NkO7tX*CN7PA!}{N6O?pd;3;!#W5r?{Me|Gu ztmY>JK~uw>Mdr80`UzMj8cn^+S4vi-UXVdao=7&oRFM2jJI)UZjiAnvU54<;_if{; zzS?C5v3@>Bcx~zN!FQp5Mvf{)4B({`R_@G$Xo;h$V}PgxwC6nmRRo}OnfXnmg=)dZ zGO(xEa3>L<`wJoO0iHfD8_~wJY)@k!27TUc07>}xF4Xuc^Bg8?$i@_Qj0{{N@sFy- z%fDWn9!&ff7VuNg*lVHIkiD1qLM3eY2%NJPLSD=hng~UGWp*zCev^D{$62hGJon_c zH_BSaGL7e%) z_6$86QGlAH(|z#>t%6|Rx9A49LP$B!rzIvyFQ{Dhn-BDUuMII*xhrar1%}T$4~@xF zG!Rj~$~dBB%3{Xn&*SwEH*0_tY7IXkN4ZX1&0?0dMuC5T;6cQ8E%3wv!0N&X;0f5S zD9NpTQeZ8%@^>*5{54IkbY?N&ba}i42BY+yBwqlbP688h9=s9ih{dU#icvW8JKAiR z@waOT+dpXssv4md_XEn|F$aE;5BRrvgZtUzj<++=73EWMc$dX|&0&`~ez&b(j(Jl> z2u)HxPi+}Kr|5%zs0o)nHBw+C!)dv>uPY`MPiHR`?U+7F+ElJ4MRt2a2MZjmgJ?@6 zeyTb=`{6l?4TEmYZR`g>m>465_Y5o!txTL1mqFnS!CR$x9UnJ=+aDy#aF-$vsOno` zYN7;?y^Cz}QI1QpSJ;FLI{WeH8--3vO=8X+|80KNF7}WXWu1q5jcmga=voFE$m}T$-mJd{!K88GSI>lrGe{=O4|O7g#>TpyP;~> zxt6Wm=W7muvv{UP+;g9pq-?be_eW=ZdcV)_Mgm0}vxRet?|$K=6{W5N?JmP@c8uYr zc{-y8&KCFMTINcvV=~9`%&O6>LAplD!Yc|c6?))hVGMgx8Tq%-p<#~Lcx;2hx&M&NnxF= zp&+UzHEoi9uq$E`V&C^Tm42 zZ500j2{c$~=Z#?{gObA0@{}s&V;2Z{DrL3k3R+*eQrgj&@8?fKwh-R&Q5tb+p3oB% zsu1~xU^%xU2+z;CTe5Wsor=(KC#9ng`CEL@u!H*9x=W@~LIycj_rt93(W9bORRru6 zZ;gHBm@naLkmt8Eb%?5u$^J+?Ma$@VS@Od08h)UMvtk6U`nwx$dGiiM$8O94kB;1b zkyt!VbCem#_*g6*)gA*$KI=_GmQ}4^@n;`U;H=?WsC4^yy-T8(sD6&5gZ12?&-u{& z(zX9qwA8b%|A>kLH(sg*xTe3;@8BXrdr$U0#i=A$CXaUYuxKxl?xD?I1{X?li-8wv zumZqT7vif_D4FA4N%9*2pwivxiv5k}hzAB6mT8psA-1xTPq*j>_{kY_*K=2-z(i<) zTHItGKX)YJ?s?tZ7`sziz7~GFRzydf{p$u>UQaLU#-P#RrgB4#{YCltddJj?rcnot z{OP-BDLq6mMlw$U#d!&YqbUQ2ThPzLB5mPGFetvEE_{3E5r$;m|E`I+RXgV{Ma5Wr z)xbm!b=tN^?48}E#I=7@azgJPmd$_MoGwQbu%C=>YB(i>E~amS0L^{+%XEoA9#FU! ze4R_Nuv~v_==RLvf>YWH_kVh-$t%)@1_!N zaETmBcQfdxTp3DQePLqv%tR?+>sN^z0@?9It7H>{uDO zLc9~DsG$EWBV8m+*Q**yXFAB_HoNt)6&?b?cDlo1FVa46)A83kEx%J8Cpm&^hYzyg z3k2jU%ZVf0eJCQdhmk0+U^(=M}4<-^F{Mg(1F=JZUcD} zo_9}n9nK7n5sOBT*xe+$EVU+9l#o55Y&O6r}<)g79fY-jRw8DTp#DPxHmjPaQ4U^4P8qhsE;KOeu zQfdAIN{8E`DDzYo&NS4wa|K<@KW#Vm7a>vSh*W~kDhV)(%Y|Hs^b%0h@sal@TZu?y zISyVWG90i*kH&~*Gz)*X-pl>Sa=?se@_O0mzT*Nz1Q(rfeZ)kX(6MNMc_=PV_puvr zn_J(C^dDxZ={~GMyZB4CB(HHn{2-n9LXeh@>f2x=EA4bcLtvHor>+s^;?lwzgma!W z5VlXQgat;wd|skX&);xi9mjzX=5bI$k#sZu+fKi(f4D(`lZHh0I)*RiMHPbh07tG3 zbBWe&#id7y5(96t08$yaZtD*qvG8S2k|{JDc6dtz+Z5UV4uGkmTQ{*HHcEaIF92M& zTT*b=y2l8!Z-b$l$_sFqmC8QFaqPRC{%Gw{H>OqOrWeHukfIDMktc&_Z|7}9VG!s3 zZipVCg3?s>2w2hsMt!5v;AwL&+~0W4#L4~uBq`I{6G5D&H!M$#zY5|roK=Efz@#B$ zsxjV+;f<|VP4U+NRCI7jlNUKp)a!Qd5J`{iGgM(iA~d7Co^HyYL&b2p%Ut}G?P9UV zv}{=%87aNE1jVfv#Da7n?`8TKoG9Vcsz&}?;cv9pN{F=C#faUdd|fNxuQJJ)(htFJ zE^%jQ3EBqYtk7Z<8l=)5F#fEWpqr^&x2LOLKA5{~by-(cRhB%`G;?94OyiZ7?-%l^ zn6aHY84AFJ@OHT)o7$nL@N^CB@T17*%Y?DIgkn2yG=tZW?Zwg~dYJZ-;i1EdyI)>Y==a0P8{XO*$$j;IJPQO{{2 zdgH*P^pstpJbC(yl1rZXt!$b%H63dCi1y#vCq2tIEGE#*=+ll2qbM5FEvn5m%{(cV zyILR$HQAy6CEvwgSY9kpQSXy~FnsUzMo_@)3je25{app|T$Sc~2S){Zn2v&Lmq&A& zmKjad5eg^#p)%QziE(+u<_4)JhyDpty|jkvB5PAl768zDiR|*UE`ZDAyd{8>y5k^( z?qff zCQKH!ck`G3nTkJbU-?J-upk$IvYvPO`3{y8aH%K2o%KHTsf^H}twq@o77m+n+MyVj z)!hiIxs`SHisT#GXrLuqEu5GX4!uOrATg4be!_esz zw1l=g;N@t4z%`x1KB+@jl}W6cHUvdc&91ildI7Z@xV!nlB^j8)>+cCP7-lE32t1WYrZXV|GT zqB1;|`v_N{8)%L*9IV+;t*{%3=zNJZeUr;bb)8t@bu$-yKMlhY?}&k?Y1gq<$kM5> zmd~S3^7C->0BN(4#?a?+kt}}gegk+epMSzJ$(hnPh=*w1`|EWLac@ZAE-+15D+&8> zNyHa{>$uLE|8zCd`)LZ<@__}lc3w$Z-PO>)qdIszzJUoseivUaFe>FbYhKaII z(*KS}G1yE||JKrtMrgl)s4}#$jP*ab7UpwWe~(i4!nueiq=K zF^KKFzq4jY)|&f}!yXg+bh{C9hFNlqu5DboFCTHSq3a6%W2EM4PKJt3y`iPGxOJUg zGeHAd*gse#KNNs6Z@#6PL3o`8#WgZ`rKXglF>zA|@H-#!j^6F-m!9ve|JQIrGQzzG zA4Y}&U&C84LQH#5lU-a2XRIgNNk?0^_yGO{%F9CcHRgj+qxyB-tOmJ&h+@!be6y$I z7;(=EEjv8>o>uGD-oO%Y8fZ`!^zg$qOm>1vd7cDglI|0yuC62-)?QF6&F)g8zE1sH z>k};$KB|&Y6|JG?A=yX+UZqlHxx-}CqmH#5ZSSizL$TIAolH2ocrs|=CXhXSNL7nA zUy%uJHzmztRAqUqMSK;Xd;M7aWb2gZFAP$_JOTMRPaq4#T?P0p`utI({6wh9oPu#U zKZ`7RN%Rnz;;q-)KMS#Kk4LhmYLP+({M@?vyjzY-jgBZYC)^(^dFY%^Bz3^GuHZKc z+RbEvws3ljs+x_nWI2_MVOetAetMncU*HdZL50V^shxzQ@%^M?kK8E*pB%>1Lc{`G zf*9A^XI+9saRS+9fRm|yV^d6tCxi?xp{+S!S9rji+)3q8oo9}tbZAuJxG7pWEFeJC zt0FBN?gKr4H^f<(l6{2B}%&*bQS!xUgMQv`=XvVBs_j4t6%v3 zBcSv_4qe2rC&Qlojtup=k*1fHO>O-`KzPLm*$k{#xbBY@MD6d}PI3q0PYulk+#?v? zmqAUF=n?_)U9d7P-_Rbe2ISEH)FIsYXJ^uvN%HWh(4ENEiQ*IU2bxmJmpRxY?&NVS zg%Eml@%7PEQILjyVT~fq-eP36bR9kh8OzU#{llOZ# z_f{mMqk;m%ywDfXtjzF{6Sqll6bzTunYY}9HnI$9x9UhO%m>6m zp+=rpH7pk}LZvovcM>sMxUZNPrGVW<7^@!`QNeIP8$0B^lGKnnr5~PJN_kYn!C%J3 zZ=pZ!8(q+%C@O!+leh!v{HBPKaoiO7jK?P275KUv5nVl9eKgvzV-#PFk_cq5scPXd zZx7?plS(#Qqmfzg`AjjQFz{s(QzXI?*G=1gl4MO6ik^9-QZ|;t_sMd3T(f{$`={}E z;Vh?AP(VFtvx-`uWU5DX{`ia1h>*i~i^utTyqZCmijv-z8%42B3!L2|rbKpz&^p*$ zdS+eITK*_i(dy-g7eTzB-&KAyk4*Ov^EH;j1ca9?|8rSQ|23DDx7w4_LVJ`$~G6@n z8+Lf1JnJb218yF|ZpGB0IRuts&x$o*GBm?U+MY@&Yp_l5Re^Vi*(uVU4Xu}aHDJ5V zr&J5(s<4gd8w6pI84}M9q^R>9i>afna2LBD5Ps-Sup9^yb;ta*2B(RHWeTlc0~D4f zn0+W9xamWE8rgbuVW%3x5`;}O9TZnJ&m|LKC&L~Lr|eBcFmxqxQA9y9rS z5QG64OzD)7p${4y^ z$1~y6EaOhH6iOJ*ss_77+;`kDuRhlSe2n*Zu2X6E?sO=%ON6I&)D`I1P)GNk;;IaI z!OJdEiwQxWhi${1G!mlN-@f5R7G60%hIrmVn>~S^C&h!56|^GED#I=VE(dd)Bgg0^0=qpOi`(^?Nep4n$fynK z&E%tB_pS1j=iyD&h%_EPZyiJ0OGdHMCJWts$$L6m8bLk+zawR0zu;ElZ4WQma{;xik=q9`@++B8)Y7 z(J_RPg5WRf&fUUoH4QUfvP}~VqFN$& zI8XoT?Ec9nf^T_$AZ>izyUMZemohVn4|k7Psg#Fb*bc>Z^(wVNxvkEZh2ox($i_t{ z@O0YhHO-mIL3B80CYPMv=NlgK+yviAeEC0pwOSw@nx}b4=@@f=91{R3q{(j@zxt@Y z_t&8FZ;Kq87!&vAwUl_xHem$B=W}RuqlkBS({|Mqf72^7Usuca(23sc8>ZMieYF=~ z4>x_FiCx=@+&^zb|LFW^RvsBkU^=&1CZsDYgAxy!N|3h}tIFM*75`6(eh?swnw+%e z;j(hgvpC<>Nxn)rjSpV9}7^Iu6~J zaT-(aj>tlsC59dQz6m*FnnN=u$WaLPdYy_8Ps8D52=|@g0?0b@!vV_kLYPNH5IX`y zIo8443LeXSkf*XYE;}%C9c|FNTF0*72L{ou{i4buxmegHWAs0sI!F!T`dNsRouWj7 zeJUa_^$KKd#?Y|~#4Iys0|VKKfu#=(g$!-+WDuig&Y6#9PeGpa61wCDdo1`6ja+aU zmfsjU(z^0us4ffl;{0&3AxunS+!>~(t8ZY$aG|4klg2CH1cpSFI~t38gc}>iPx{x_ zxpTHgXT;Vsl9^j}w`~HoloyUQ`PMd2(TN&~jP4rbj4q!dv1z!#-KDWl9~n&89CpIRsgr;vF5yyL!mVrZGorZWkrYFz9h6^ zp<*=kfPeLV!_13JNHse9f$6oA{n2k?ozM$|KH^>q3FECLPXW?3)bDImCQU=h4~ zSv0f+eRP3ZxpWi|DPJ4jD4psp$OE4I_V{%KV*^GI7A3271Xx2C(j=CdpupX#0uHrUktTyu2kW_pwiup`_8Y*PZs51wfEyrQ4BSxSy7V3Th?eF3S zJ!7PW7smkZR1_X3Q(<#x8oC%1n4O;i3zL?OjHJ;2jr+7GK*=<7$|3&zHxFOfb+6rAMbe=8SiNoBaXB z`(la-{HIM>X1FA$xyZ&3H!laNAe}^;g3&04ziwP#g*#dLMSw5HWc<+cgoCBWw=M>E zYoh`H8D|k9q^H+y&atixz8--Wr#kzMy^Rxh{nxFvTz;cx3WQT#pnr4i-J7Gy5dOwnc|j z9ED%4YA+QrG{JtCq+vP|!NWsmIW9I3ZxOxWUW}|oH&kR=&4I@y!eUulUt+a+$ zrm+|2%nHG0zYv;3PF%>%dMuH1Dj$zNXnXQNTxJ_hb!TeS&XU2Kv0W7!jBXsq`Vj`gr}vm6+)?b;z!T6R??sTzM&i6`qj>f8zMV1X4qIc>l?Y20uOd;j7qVQOe zDy}F07aNlRqrJ{?jSS0CBq%Y|fyVvx8?6IiyO|$70)>zo3oOgyTO`*=*R!k-Q{mFl zKYL<4D)!a!Inhm*E<-~_L{_SkS%jua>Jew4wk z$l9zxrtV#8|HZh`|6O8xg&>s_USEa7A{%Hrp>&L+ zw+e>)FA3VMzvuG>-S-vND)D+YgVC072_5~nmt~!Wix*hxs}{&#!<7u%wN>1mLp!fRhy?@EmM>v<0+!R9$BVsW0`s0PLWhB#{9FJmF)Ofl+dpL{TJmmsX237}zXS zf?yZE_{Q;Q*V_llvAaI|I3Y2T=vef!9AeH6<_8^Gy=cYWJz5K8rO&O0*^@6EMS}&? z`40+(eg*8Bys0krB0QW8Lp0<1j7CXNc<*`+gv`E8KqM+zr^yRc%P4!EJZVYocY9TeJW zNankE??y_7yG1Y$f64%;e-x>w2aphRUp==NESUd=}c%{{1Kv55j0 zDjz)9A&Jlnm+@pOL=tIJ0Gt`}5)-94T41|1J&__43JZYuLwBG9W6x={s3E7+_GS~Z z^ksYQ_2+k3-m>~L8VdNit^vzHL81=5`kR2Q{e$mFD#UJhJ$?InF+J&H$@SXB)RIkb zgkV2z=Y2dHznD}EnAl~tN_$~n9ek$3Lo8@%TK}TwNMd>!U^yB6N4J!Ty7laZ>=%QM zOXvf(ZtY7fxRWhGij$`9iL}WmNZ7vEuKXVBDu=nx|LpJ}{6(sRUQEa56wn!do$(3m zA{-!AUap}i357T?jR31*bbBvb`4E(Ug7dyLGErMQ=Aiikp7PYPyrVyJ9722tx8s5u z{3`>2idE30mr=Zv@GBEh-iyiS_&lMGa)6z*3o?11``ikg?k630p;JxBrnsN zMLOX&)#6uy32)Pmq&k&^rquk8whpO@9U_!dZF2{dooF>xPA>Tp@eh2EQ6e0c#c;O8s7oN5BqW! zeRJ?-;VnBG|9U|W9v<9AQ~g$y%=`DEk>FCQ!Is_6NVIg$!!8B|RKTbz`aWnA_HR6} zjVnGlWXe6^d(|T>Wv6TCZN?FcjSS@xSz@pbbSt7j1bM#dpUs2M-R8&lHMnQ1jz98U zI#hbVsIs+d@l=GVDHYlhZX>|+W8nFGwKbdQ1QZJTS>5-{!p0}Can%>SA12q(34#~Y z4{L_B^VpA`_4MnP2T=|dhRPu_tTd>O8Uc zM_M2OI-haXi7+rPzEKN;Wq&+|*NyONpQ74;8TlOL%;d>@u2!OhBAH3B3O>I8ZxsdR zi#5}D%Fhf)X@(@5Pn@3QB=gJ`y4v8#FN5F06E?KU-xdV)E>wcB3h=v#W-{ytdzO8} z%7&I;>X)UlbgGT15mrZoQfb);PS1PU-(h4 z*3GI{hS)H?YNq##x;)>;M9)17Z}k&2`MQ<)vrjNX|99B5Vtq z_RM`Xr7>~1?!e}zRl$F&IO7esI(Xb~$a%iflR|#@W_gP`3DT{e4~_S1RSDt_iQH?9 zMJjq|FUu4(CB&1+cFhQy_Nfm!c1HdS5Rq2OcQWiZ-AEwit$;=nnvHyuWL51noYeh; znIbvE6Lv+w`pUsn$q>XW`BZ7KSZCo+bI>l_EAd8d)n--+TnG|6wqa!t(3d=RA4S5x zAEaBamcd6!lly7Xk{CEV1u4NS>@6%s9YGfYooi7mrLD5nGl;Iel^$_!IiuDd{q{ov)&r;E#%IAos z+FE~mFJ~zTGXte*#y#s-pQf)D1iNWjz`((P{5F^-#I*#}^N%~;gCa4^1djuUnwlDb z;KbMn5$*uaJ>#OB1%ZGQ4lsCRVlgyfVPiKgVrgyyfq(#x0{m@P+HLzmu_?5_c|Wab zb5()w^mBL$^IWnmH}L5#?0Jgsb2f~yM`Ce#MORS3eM95zWX1E&U|Al&qAu>5!* zN9VJkx|}S@R7FG)uo8#k@J8T;fq@?q?JFW+D`rAv<%;^F)=6v0agS#LsAijr# z+&g$*%eH2lFaP-=(ZtCHHK4I;VJnF`TN;)O^S5IK$`Hh2X}6nDgX!cL_QZMUd0 zp)o*|6UdyT)`#S}Cv@bdsc%(Oa~_}xCLl$)b^9<^w*o+i!rZRHpJPq}Sd))jGaVE& zH-K&fC2;g>xlZp>9`r%}<*7z2gJV{)17x;2s_O@CN^%uH$PK(v_>#P#r+6osv4s_=|3@% zN~ih9;PA%vC$;1+eR|Iry6Leogvyh)kwHgAh?Ka=X(<$cLiX<{_H#67RoBo#rG1n# z=GK&?6+02ah09bOTdJk`p*cfe47YHfqOS`BbX(Po%=$D>Ca23ag*M$L5HZ0GrFfP>sa^J%Khav{M}`Dy@GakRmX4!o^g^s2yvQ%McxM zjTz|aWKcSV#=g6PLwIh6qq?1FD0(Z;piWzaKmc?yxH&-Z@wAu?BX>FDPIvRefp^Rn z+r$O0)^TNN33EK&QOtW>t1^X+=<}9Jmhun@XLZTyX_qL7ZLTN+{A^{&nKr*EyH>gz zna2%P1oqr~_0rMrbnTMzxzXS>6yrYV)%7nfU*_us;rS>%O`kQm;FKJnLX-Bp@84O( zJT*r;e%85^-3@A^zSAF|dLs#$MDsg!#qX49{Y5kgXAS7LEx#uH&4;aQ$vUDucehTEdzE#j5#sO~Ad+Bm0e-{Hthmw7TgsNki_l&i3H$ zkD8#}MP4eXGUCXBiTfkN50AWV39rbBM>pB2VW{}H23LyZn9HdsQ_6gc;}xTy#RPm} z><*xS6q#U)PdP8e5B5(q1~!&!0pFcxA(rruU%Z=#N$X5v$#4{S(%Nq=j4u*apcoJw ztNLw6+PLDW>-yAu_B0<$tWzD;mlLI*JK=|mZ|8r53dfXqV+>iS$xgCau0T$~-REt> zG=n&S(MyTRPyF=i2`EzX^8^d1^2(f_a%EvtP{amJ5lT!SiSOwDWr-ErLcXgLmfWk! zZ55x`3re5v??Ry%3PcRG`aB|%W=z!?8f$6i4lH>a|`J|gYBsjLQ zjdZ5^U~B>11NKdktj`IDrh$N-9ZR}`V&!}B#*1Eb#G(mudFvG)f)2Sth0(HQ7FrSK zafFL0f!_j*yv?zKoo+by4+M=~J_Z9PKtcr&-T5N?cD;RvOUzoKYI8U0GN*;+a>Te- zA*h|;IF_3hcQH$`kE^3T5SR4|WQqKJS&5Qhe&j;e90nOTR;o+i9&a0->RK_9ZQ)-9 zSk*vVZ}N|iYA6cIA*|PAy)ULVVNtnWs%AR+|3|vWC^~=TIlb4DBWFeru*h^n(h|3? zIau#Rr5~&w{!vZh-X2}F7y{!1qBpHl5#6q2ND$5OW1p%8iA&xxLqzAUnBC9o&_i~B zb(eaMEd{2o=D1ejYtXlBf5VmvWpgq57vytKwpSlPI|&N9f*437wF-|d$%sj1K)`N&aHBvCxgttiMOTaMv5z}P;gXJW za$F6pGYtxSa-hxTfk;fUg7|&3PN?6>*zX@`5o3K&`uu7{E$%9Wb#?z^P0ndf*-yDe z|Gaz!2Rm*INskCh);}$0vL^Gh7n2IfcPIweTF3)7bVI^-chaKl8?H>Yiui2lURz^t zP^s=m;+w{otdfzgpp}roZtgyKq+a0kavnNtp9N)#a&3!^eoMVd-o%BVWr3!Sx*h;- z>`C*W4|%eS{s(@xz@B#g1}AX#YVzM`H!j*}K{n5i|<%o|_0<4;m2(zg;| zjT5pzCAVyXt`>I)NTGm*DT?@Guy74t*|BN1%r&kr|(v)Hc;!WZs7rdpiTqSs>Vt(b|``moOh;GH7p~kM%9? zB-&#=ZPdL zjt1Uquwy1SbCYGk_(uY7IL9OZ%k&{dnj{OBM%p4#?cMN0@OfMa*|VU`Ze>n9&mz}P*hrVPyz3!?7CpLj*)y6?iTJz`9V+^Q zv7c`}{O#%m7|UwMmT64^(T|#!YCaSMVe&Q$R_(T#a>+Ur$YWDOE~%=sQT)h=n@X>b zDBZy~L0qvtH9fvwMBJilaor-t-M`Ilb-%g8({wlU3Ek7ZiNHV7>>1B?FaghSbA69w zYAsbD%9h$dD8Q5Lv}&wAfkJ}s)u$D@CrWTuSk5JHXHC(z&kU=^aby8KS@SA$AaP;e zB6vJi^XocR2}lH}U4xg|HvU1Qs#ME_A=?xcxA70e`BaGS4)Mq7%`*&bQ75;FnkoXP zOXU31yP4`5K^_B09UY&2h z6M*FU87jX&ds?FMjf8gJH20pxgM_K$!1+iZwYo!RLVcf7ec>dvI?TUmy50&Di6%}@ z?;;DWGRje3PhEE|+hrFDn{hHmnMJ zFmPfo28}3@A7%W41wRm+uJvEMC9XcXH&45?S;Z3Ll{6WnWMXNBRCRK1FVFe@r8|xX z{#1&(G4EImV>?AfNl}p;0#e8ZTk4A0F&)!cD%>1BB?gY3eKTeeJ<_9~K5V>j%dZij zVz4Eo@X}}4o&FBUnaHPg1Nv{JZ9hWYL|BlF9?u7f;hhq)<*J%DG73>XpPt5*eN@)m zu>Q=JV0#|j)Zf=>J|sHcV76yfGppzKp}FC`jp4YVl?rhb%o^yhN;fky!5rMp;@*7k zH@7G@1NQP^F!U>-`zON-$_s(m#@4}obZyu=%T*W`*BDp=1y|VDfU~vaSpw4we>^2o z;ltvtC$oB%W3Wd$1{LA_+-YW7 z&a^=AzaQC4_(+?4qr~{@C=ra)7!u>c>@)TT{|>n)SDwC$FflDh^=rgLnmOX)mw&2n z#riCA!-oomzOm2OUghtakNeG_G3~}ZeKWRtGLxyZ8DSs+5Gb9n+9!J0c#&LQ9RA?TmY~Rai z3UJ3pP<2P0JFbAgT&9rMs(O|V1e{vexJIlZ_6-n@v!FmFS+E5HL+J|`p`LL=AXZAt zYjk$W+J(dkyu_Q{N%Pgun~ zBK{f^ayT}P5jH&G+(0mL(=*-cb^%e|+q*5MQ<{ja_EOrl^o}N@B%`K=mrD~E;)^{i z&ZW<1KTp=Cz1Qkbd*LOOCLYV(HvEtKl~0Ul!VOYg$0W3{Q44AnzZ3xUTNQ$@joHF4 zDbPg#hZTs$6b@6X9RzwAHb|t(S z)K7AayUpR>*Z!xM%%l-gI*Br}v`wjlc8S~l)@toYfU*qwa&X-h^68aR;bE*Fe^xv) zPerLXKYPXH_UmQ#ThK^r;arxh?A}Gct79mp#GS-ZC5zC7e$SLpW5KBTQR-_m!FX|> z6lz-VW>#ioY70TTL+lq2r;s;SlQ$z=3Sl{oIwhKclai7+TPdX0QkbtAGx)%2qC73e zf5=$Ms0JJ+jRlzf?L;|OQsX0bTIoiQ?h%V7WJn6QE{G@|jBVIDsyRdOJ0XPPG?fPK& z&G9~6yu@81JCjQTjD>^zn;1!aUph07!ywNw%Q2v+Dj03W5aa9H^|*rc3F4*3KM9{7 zk&?05vi{|rWL1#KuEd&4@+xsc54rm;rjdTSCAJ6Y4zx^!Xq#ra^hJb=+7p=aw(BM` zGwb;I$b4T%De}Y^F&tKR81?}YoZs>qM|ehc0{dh(a|QiSTgNa@9n5aL>9F*qWB1f4 zr}Xm>DGo@?)wml3{l6?<5kdll-_4D~Z3zjL;ZL1Z{l$ENUzw*s3q~FeMAmsGYv{B+=J4Ba&Sauq z=fnmYItP+PifW9FWLZM3JC#wC#SJ*Nm&;d*WRR=?Y%T^}cb#OEN5aWot%clX`}G*twqefzBje+_MQ8^`=hs2wDj>7=F#Q#KBS$QmS={g5lh20UX{U6s zSSYsiFNZ1up3x$Q(N750HPS-wPNBygJYK{WEMx?$AIClyQ@Sy4{*~FaE#B741DOu6 z7dlkCnHk>wksJqt48N31yWpc*b7=K_Koc!QQ()s$_N$I~WE~+whQ>I-?5D4_zCDtR zW{}yGTK6gerBtF$Fw>J}s}$(MyDB4P&32BtXa#K|ccT!C0oqH!z|foKfbyVKGemd> zc5|_f@ws4$M)bTrUps}zl;%j7nx{|Pxv))FH72G!cuARvm@kr`i~mnR9n$ocPxkDz zT!*jm6%jJ#w3q0$Y)&r=z9pO?OS0Cb^nWcW6p1WHHqv!)>2WMDm>cBt?{J{kCGF#M z3@B0o%y3aYqeb@2(Fc;&Gm*w{jjk^y9)BGD$QMZi|pr)&qSGm=iueL<~ibn6{^MQJ<*cGDMaW@5!75b@uhXs(jK;O3tX-=))T}b; zx?huNJ6>IbBS0Je0&hOVUCTf1{v+r}TP{vX)nA{>TAjbd0T0@shJFa#L6;waSQ;74 zf?D@qCO&G==zs|Lm6D)+J5fe%*e%>Fghc+sRat(Yph?4^CNZ_2PAc?u&ugNYv>8d0 zknKtZEt}QCo-wLXg7oqse|zWfW%om6oA+H45|3fDBG?L`8Z9obS>wn}HLN%9fNvv1 zo5-y*jvTWY4+|HqQ4RoXz0s8<4)H=$jIok8SrASRAe)!vT*-TvYY}C`!z{DG=d|2K z{Jto;koOIcJ3l3PMg8}fciVZmS zKwNtu^AKZiy{;YqwipUaF5l#3rk-ffLXb=YLnr+5<$V>}=qwOFKt%zHDPlxjyQ}^5 zN-Y9DfJP`uFx{3E^_LLxV<=9xB8MrfC2?}mOc4Y|$~AxQol$o$DxcM*5uYksQOzC_ zUwX^iBB*@^=uPfCK|eDnh)rATNS~bQSZgdfL!KFlm3drE3*P;062R(vn4vMr8z4R+ zv7fozEUs!LQw?IVU)?fvrc;0i$>h~#-!YX23{cIv zGlINJ45*@cj>DaK_tx1s(V|IYIR|jGxakpBCz@wOuD*~@Q{gZ)$aPMqF?7lgKkTDO z13|179vvqHkR9~IvMuEfi+La$PT<8y%9>MrjO;d6N&1ke~E`6R=DKn`|9E>`m`2v2eaEVeh3RiI|< zJnSrCxKQYhM6-ugqFY#r1nQtpe=h1o|0?E|gC2yK3{F~eFRB*5<;KbmNXO(axm+=^uK_hd-;=oxx?|a46pz2$PD^rpJV9;zJ1}(L>01O{=8;vi7>-C+jaqa=o75O|o>A$yOG~$ZFoI#ByL2>+{DEkdw*Qn)5PX@Mztbx|$| zpV)Q8(WB<92@%pPE{V8`65anmf1#o!(0;O8Za)P%)=HhQlU&WVZLh+aARfB@L#r5vwvHLr5Ih|4Nes!;hc4bvdmmRZRQ zFGk~FCM(>gkQ+Re#Wk1)DYN9+Pt8XnC8QKsJ#nU(Nl2gg-frn*3!=myIV3Dbc+W|5 zkPxsInid@F9VY%4^%zADpW`d!%?;}DPXvg;|1cQ^Q^=+a`i8#VP{>hvSh22=5h^H^ zO#o;r&@b0HB2&F?d+NE6!}oECXJm@{62lg3lrX8>a#lW66aPj8iVdA}1)6+Q#PA+U zZI|`v&6&U`Oz%dfpJG|yB#$3peu)sj&GD?{R(C!TX$r*6apX;Sq853nBSPjBEjV1u zv#1Alp1!OlSL+)^58aU;eG+^e7Um$$s#l6cEOwWO6{igEI}fV(ut}l@wvnD22T4;s zwg30rEi`14&I&`tYJKMh(jex48bhvuRkJrL5<`z6J6cC+|5)y zuB|l7AIDz|Nic0^%c5d?BZifwpaJMqYsMkHDst z*m5n_j#wV}{)#(CIGbx7^PCp^N(!eH@fanw==sX#`=e+(K9FM27l7dHmflrz7j#{9 z0a%0Rt-s;)3kQu9ksGA5kE|FHn!Bkq(xsc$s3TT91}Z2Paod>TJP+v;)q?0|L0J38 z3Ho45aau0cKAK%~1;vkL}a{Wr*XNoUD!z~B>C8* zKwH^d(u;o|j7leehhj9kN=ZhwHC1T{zZSuV%N}aQQd-lV2pPXq+hfwNvw~81 zRO*7pNGSw5&hR+J%&Y;I1?}!nG7x8me08zP<%wmuvyS^W%+EEhHefH@Rn@}Dr=`9~ z_3eoxM2~yx)LAK%RgRj!w6@{RY;vH0_@IP4u?#Uq{(-j+a5u0GEun=o{jga@y;=7Q zPHd!FzX=X`8 zw9BPyyK!ICjtKOWM9^w!Q_7f=CX7)X62pAcSc~q@kek) zvwP&|b_AopBaL?E@5u3@r6?RgSo>9#CK82Nl74q2oS4!0W{$QA##YH)z_8vS(ZR=J zH=E}X%1XSw%>~tgGXBUNoU{QYpP~FfZTmpY(FPrZ?BpLYg~-=zv1dU=kRzbex8+T< zeK^g%=!_D-T#6hv^K?dfyiVYt>Z94YQx#cS<(wP*4!S}8fbT8lI?sTsN}Zf6rUX&b zaCZwWr2R@2JKoiYZ7V{iTz8$3V>LFnKuc2oeCcPq0Uk!<^e0j-*!?H6t@;!Jgi+Zu zJh#%@7GAPW0l+cN7SAX{d!im8!4-v#|Fk7DQd~U3D*R`kiVPs4^<) zj4{TEeDI3-sY_*_X^ou|dAAVB&Aa?bS}U=~?l43}_U3i5xGmd<>aCgBa|0B$L!QRH zU_QOfyaMa2*ePh@qK`BdCll2r8n|r6K3uM?XsZlYOx)#bM9xu`E7A>pWTmd7zHfRa0Lx0%|L9Nu=7f@j#25$?d;gki9L2QH4Fb zOEi_At;>&89ndJx|J*5<1b3mlW%Ix_=qV?Bz3ipyE=8MQ8dRwX^{FMlrp#rwdsT+B z!nmEZA3)!nXK~)y&U(BXppUr|++)3~5po)BJ;<JqMQju-4Cb4VpEo{sJU6}4lyVeRGGWI-G-58uL)oDBam!T)3$m?< zFV$p&`Y#+@y(u z61nwD8fv_Qw%Lzz2})+j2Cdr2Tzb#st~-_ly&l^bipg1^#xUfkSK+A(!?zr*wgcuZ+LixSJhBUPVr{s*TH!( z3b2?~ICH4Vxby12EPWb{4$m|njb*9R5#J(ON^z14TQA%t7@>m=sj=T1tyWvE+;9A< zOKJ=+x|P&d?N2*(f6xoxqv)|Ni4QvuSUpCM8~9<8`-Yn0vxM?mW;D@%$`<$AIPVgtm>hUQo`L}>CR$I?meD7#oBsneu_=(1wM ztJYMjP69|s=>fXvWC4{RKU|o$ONu;aoPHo%<061F#ZdtKs|sHAW{4ehE%7v}xZ6fj z#p=gne!=6TV?Q4e{D7A#0v;;3O6~O&gj)adgd#q-wfUF|(DSPa%NF|g!{E@+>x!kI z_$Mf1Q*x-mh!PG;l5oOh+gMyq)cT5x7)uB=uT}GqH+o@>&$0N0Ih1 zjhx(%&NN;9efG)2cq-BZS3VM)*mIp-eqOTR%K%Iqa)U2 zEz58dY{hPgD$nD*s*~Wm^^VrC#VApVB(5c|+sa=NvRHe2`#pq@5NEn)OYG&+Y^T3XACNc|4M>f=w+ z@ur9=yq5LvcXZsh8uYVbLPYUCQ;|4sZO(Uy(PO29k ze41jt9^H*SZ+7qLM%)CMM{~Fi#0<@7srYsFg>G}TM~NM65)u2%ZhOG=^X#FP?VL7Q z`ANgy)f9PdXtO#?mB@6vYQg3qE}XiBBkhw8=yn7D>qbb*#QGw-%T1NM!~MngoLKh@ z-4S&RaPg2<_-^Sm|7bodj^F||SmH=UKtt!)_GlOIRp$xthQbZR!Two|7{|mL+DXKd z2PzAUCihqjov&DEX8&SdVW<1}j(Zf0~s79EHiAXDQ3&9flm11X+-vlBF2X_$s33peR|s^x!9<` zpUSUM8&jg5Ya2OwkH#<%_($Rj zh(>N<+V<&Hr^|C^+blfw3)kC-=tDz>V9AG@UM-C^vUWSIyEt^@@)Jc3_HqnE!TI&w^ z=z|-wddM^-2Y1FEYz1N{1^??fqSGb5^qJkfpHM^DAB?OT1e>J`K-E#d>4jr{Y+#B& zJXdXE34tnM0>S;o0zSoKpeD?K=mN6`ol@&D&r5bM=SI(~ICu|v7@)|*5aTNyDTO81 zB@+;zxz0jDO(J@?3n&Mnof+)wlJAI1`eThT{f+x+*iirq2;y$mnMSG3th_b<*vq#( zQ0ipSfI*)j#eJfxP4IYQFI=?`oDo4@-{h57H| z-!QPlWPiV$6}xB|DOnUstOC-E*Apr*2;cPjqD&UWD;CKqA>A6yueik9g&x-@aMR6cD~$vU*gghG7IyTcw+o9${=VcbYJyxtqVOT(0e{Zh6uC< zon@wuQw~rAi8Ir>Ds!4nfVPfQaaQdf$Iid!^Wx6;`+q(4RET>+#W&f)6ArUqab|ib zPrLS0G1^3BzPs_Ys@{eOMtyjK4oRo*`k>3oHMVKCQspH1V7#BPqE#-=j;td?AOO+a z8<(<|BZA44^+Q5uY!jM7>B48cpI*}*Ne(YYnV0r8( z3trN0*3UmS3;?j{Gd6FNV4kE0>?;cIDl$__eDK`0?PMtT3Zn^^``6losdctx1j$^Q zElDK&7YfGHwC|&M94soZMeYwd(zj^p%z#0sMZhs|W6`?4u8d+cvnzERE7ccMM(SngGI)$jk&vhFPWC50_z$-Fns7PXO<-}sSWOO!t6 ztCsA$y7%Az06R3x#7$t9IN1g4#LI2_iFaB`Km!yced<4xdIILjsO zbQ$-wa|jHP72VDp*s&WL9z}3!@3TN(UsEn5qOxy<{%pbybt2=F4JL93Jd4V_vEl|K zBCx3n0pr>>C=xSs80arHZHtF_00WI9HDFU1Wl#mqsTJ*uPRvb zZ~gDx3$WCOiSRCIII}Y&<3?ksMK*JNl!M}EI>b2Hk(_H^@W+nnUh~p)@aq{^#DV~- z3!{wXg3ci(gSa5D9XTl$GsE4mrdfXxe%}=^w;mJAO^fZve{us~)}%`0*>wPQVqO)g zCs(a(t&Lxkjy_h4REb!;3CWF%X;+|%totrm0UCZsrf@+($eq%Tf|IV)HU_LsHb~YA zd44X`x-~VqaO`JNw$So4Ol>!=IdZwp2?&C_3=r)0rG~pg4+i9Z9=@1~Zy5oK)D&Q8 zgIBSsGki21Ml~SIla)U)^o*zYRnJZaquUaoCcSxLb}*XRsJKW9m=I7Q569|O3#7*)3UZf{gGc44NRdn1ST<+3ui}>%mXCnB^RHDR} zk%6WjO`ZJR6@m2Su{Buxo*q738h3Bom^|0MlQuur|7FP^n4haBXGfY1vVh>iknAcV zRqS?t{~NiBS0)tM7=n5CO(@=Cs7&IM&|y7n=!bG7JB&2O zaq2F-yYeD*uN74WF!ce)RN&dgbfEpH#D6w}VL1u0Kcb29GI0>+ebbQ;$5vl4_NcK+ zNP;m70omH#_eqD4?4Qx2?^{iVkqi3k9i*2|^*5mAUS$eCYwx=b;O8XY(PA?HEodzE z)r}!#B`C}tCBo5pRNE7+hEH~mW{&X0ZXmn#dXV{yL;p>?EOzw#-1 zqeHnrl_0SGV5zxQM8hT~kc>@_7)C)!mMObhUBy3?PM+C$@P!X=wsx_ak;Npa?Fty) zM%N1B2zRbS zocNxbR$=*F7>Byyia87I>$T76JLW;>gyuhycN~L4bm_vQIs}4m^bcim+kGg->~n2; zfPCBS5${74RBL52FoZ@O6^&xd2 z!v26T>Kfb5O*`uKQh#CVc4L|QmS|UTqC$QtGcU6qbMo{t_qs}Z4hh-%9b86TDWP1c z;tE$UBvHR((Z!XN?et&zvs}LvUlHGNFSY+BMR%|AA&_J39m<74SoIQ2v~QsSRws)L znTVl1lGiiaI9dlPKOI2^w8{xyD)JYDRQ%8wdE0l$H^8rX{M|&FZstcO2yFh{2V0(& zw2ZTIKJ0glSpUEg60C-6)8vh;rS$uw&;$QC;PDAX{m@9Ik1$+ZZ*sbbZR@MVo99WN zi>7zB-QQx0i^7-`9=45lu23xHaYM7PBBYT|vRQz8h!yGs(`MDi+n&flEDf|9ZO->#Ju0v%DJhql~aui@qXj>BvLs zPksz&&9G-BK$XqV!TnNfjX$4;0By;ALAW(#r;kos|j@mp-h&b|MYC)3QMpf<9fM@(J&6Pu;B2b z^X+l<_eAa|S|AiN#K+1Un=8H}ytHxe`kue5%L{jEb^9?6pCk3v_$JV(N~};vrDxGK zxvODI-ojj!O|Ger5et*wn%BitSjZ}tIku^R0qeR_1gbzZY7&p+4c@gbPtfG49rxM; z&7p8p(r5FpbqXPX6(dr!IW+%KNm18j@qmJ-b}`qbvwWWgNq9RAP=e8QUigJ+XTjnE zfZGaQ$p)A77E5s^4ul7*SmYnm{IKcY-8fZ$CTe5-Il~l3i;^&Q6ro_{2{HPdRJyu# z^o(S73J`#Uf2TYvo5|y`z-b;rsTMuY@Z*;wa=|&8k!Vlsv$Pe1{d}0cT0JFyEGj}j z(1L<wjDCT|afWf8kZAT9&V&MSpAOnf#1F zmwcO}K5NtzQOJ~~fao^uU3CJ(I7$No%;GtyeMx}NoF zhWKjA1~OSdPBz>!8NN`@gY)ciH=Pn>yRe7Niz9-pWBRXJsfHLaB%&MWx zyr23H#AjM90+7~YUpTKJK_gBCB?sko6`@ZVpAqYj?gAY2iU=Sw3aux~4SihRt-Hzj z3nd(E!ZD=R zFcetemVlWVcjWUH&loYBl9_ywq&Eo9WGE*PXxN=u2Y2?)=}M3GP&LA&m1G7bjFMyR zv*ibCH`(H=?hYKpR-fk}a%YQs^X*+fgp-a5j-n2gc(-49HkaEw3gM4|!id{>KF>jN zlV#cZI)cVY`&!5->VW_S(TFc*Oa@|Kds})<)y!zqRkzAM%yV+%939AcD_k-Vzp-SZ z4n!e?k-2in9I0)bN8-tVt`Ssu0* z#?Q&a7r-wYNdqv+pE4ngF^WRQpY;j2{%Wy~LMDv$|0I^%0IVRMsxaOVI6?%W-74hA z3PlwLFO$>AyCo&T8N$7TF3*B{u;~?#uwZmPN=%HjNsU?JtWPRczImDmiLJa!D6pwZ zp>n#Ib;R_H70mGQ`SYD%u<%lZM|BlGygk)$=Oq&+P$ATG-|=oSwWdj(uijXN&OG`y zF>kDI$S_7eXpS}H+rvmL_04N z)>AQ_RP`?pF1G@LI5>GXVPkkc5dVx`{uMCI#gSCsBHdpiQvL~QCEK&ITy_gjGh_|r z*W|xzZB280Oq3i@#`I3bViOB(cH4Fp$xvvD+tx|v=UZaoVt%ltJPJW862;lclRoo0 zejRQQ*5TFRmHa-njc)k{$mCnt^k-BCIZg|FU}te3c?QvI<^jDTB-#B+&eMt;*_Rm$ z4h@rE8tOI>BC>ix60*eLW7KQ2BZcNuh(?ThpA1?z5}nAYoN-eHd1Cuym`*Am&$EJ# zi!rx(&T~-O0j*tTJrLl;d(v_2L2@%%!SUI1qw-jJ5m@^Oa zdhQ<&6-{nCVUry1DFD_ z(oL!buS_`yGU|Egx8D)=mc- z4pk=DpmE$Yxx;s)J7$p$h8MU3JC|kkmC%zQ9kxoVH~8Q{>gcraK@)-91DzWU$2zs< zEr=36z+qZv%7FZvXFktNC5DAFbp47GmQByN1Rfm3XPSNu=QH zPZ~2=&uU!!c}nZpkefA36W;z?C*>$X6p1!rUh)kHsvs>}uBWfP z0@t>3B7g7r;J?ej22((mh0?c+*tSbI!Rvyx>Opi$^#)<{Ee4d)D)!VUqn*8W8W1+`5`Yd?YWn&Q$ssNuuBjK^80%rFCLM|)weRw!J54Co4C4^(wo|_mLJsBrVdMVfNfhyy z6s-NR@6pX>idfaGjkxr+5?Wc2i@_29E1}X5I21GZR6)ny9z{*(9`*tvbz|=kp6+Zq zvxbM?_kh$NrmJ%QP}c~R5q6KwMts@v6aLJ9ob(8R zaDNDvuIzain9uJ)bg4;3|C=}HqP(}BNxrY$n=h~}q;Vzy7kN&(SHd44#yK0nV+`-p zJe4Op$E^pAO`~8T$QSM3QlJv2smdTGbl0Fj7ngz&i<=8k5%1`CpglEz(!t{boOTLY znR3{B9e`zzuZbDq%hz@rhli z3sa*ycAXoYc@=I>H|a(xs1uUdLJAVF$l4#ao=kGPr4A zZFOG*VVIyCLqlQS>rv##tsEqi*&%JXH{zIoxn2)$#)LRkm8)wBFH3^Rc@ItIGs4ze z$Lkpjs2JqEBjS0deyH5FKT@x>p)3Z1RFn1|M~~W^-^xIKFFy~ZRGWyC$^QO7R_p}@ z{ij-{o|L!JpG^ei{VnScqJUg{8Iw;n+Rd~ot1A>WzpA7Dk}wcrY6~8qhLk|h=3n(T z-c;-H8TxF$102P9C70nb-R+&`Dze~eye3K0X}E(1;9u`cMNM4+sKJ4)yhG0|9kmb= z28{|kqRsO)dN&s?$Itprvfl3e%yEXJ{+(pBb+o?q(JdjCkNC_Qj6@$5ueydY@4<=w z&%}`J!-80R(MKfaab>pLn_wBRHx3b&t41j>?kM+W9!1MW?pGK<`*e~!7wV2r9RriNR&wQ>_q^g8N?+(yE+twBwss=1q z>CF3Yr3z@eC(0$!J+IaCXfh1vpP?y`v*7E4`ge>ImBn8Id6BTXw^E#gRQGr}bQdev zjGGw#&s1`fs~Te1TP3rqU@`cH!@r$Ku^r55AzitYerLp|u5I2^CUHwCxmfLKFKQe^ z81DbXuGG@q9%bsA!4nhmMdGA4BlLEoaYJM#?>nOrPf7}*Bavt;Q*Y;%35t~4A$v;C zYB>V>NFB~pDvp$ofIxw4&T*#q2bM=i{iSbJlr4=xb#S3x$AeT*Ny2=r@E)lg4`e#Q5_?}euM7*t z3PB1TLSvr?9itZBM;~<&gJ~RxsABwd(_hLrqAcN#4!&NHYQ5A5acEiE(qrYCmLGnn zu5z512AMrYpp&lMbJ{p*iB~2dmIUJP>1I9K>|FB|PwMb=>KLBCR->s%s=Xz1!{w=R3t6t#NNn9fXgx^mwFH1|fWc#N z`y2Sg`)WbU#D?*q59T89f@u`N1$zi>ma+jrFMS^qTG$w440CZ^J>HbK9 zy5^A&kDmzi!QC5s;;=FNs6mHp6ET?qFB|d%F=UFNr7iu{@5@L#fGF=%6{ptw1Lv_B zg3uw#2ss0%M+BM=5T3SRQ#%wzPj83PSy7}IS$y36@@!p2j%kivL9hL^xaX6H1c{QA zcCtzm9(jv?sX2g}_E=K5!sfmIWKj#@o%DRE>pbX6H>3griM|7Gn$l)V1$+{6Z*69C zUvp({bZKs7Y%X+obOL88O;A}aQcq1rQ!P|QQ&cTbK~hvnR7p=xDhdizXlZjGX>%Z9 zAW~0FMpGbgVRCe7bZKvHAarjaVr3w8b7f>8X>K4*X=8G4Pxq!FGVI*Of$2r(E!PFc z9;9D;hSwqs6_wF;_Pa_JLM+_-vF%vU(l#5VYWfRO0dzPEVQ<|x{qNBK40}CnyJ_u% z1O^?=ZkF}J+&$18VOmhvoVdx@PN4xd4Yhyl+VO&vjGY$2jbb*2<#)wgNQ3b03t=c- z{$d>bU%o`QBmqpJ*!^Sx%|Q@_KgVWWRdSb2rR%Q@v)l%{C?E%7t+{?e%(eE#s+Y4s zD6<(b!c(ht55+35v?o9K%wN{4Ye{Jr=|xvlsQ|aTPzNF9*py`HD{EC$*I8CL5o2*l zKw;Y~nHA6ueKSMk0CrWVLDoXFcajt%-VTctbwurlI3r+wPH@ZRX@B7-1fPql;QbL{ zN~;D@2SfWq#myyFJd+EMLYe_%xae^BVI!Ql&ER*hLYmcT`9)5qW$AsG2E?>_`QCyF zf3i4*K#Kc8JX@g&%0Y>NzJnP#k)x1Gk8dIsao7$Z9+;mLcnd~Y)T~3ZWh(%jotwG9 z|C}yy%)%*~No3a(chu|r;T_@y6m3{P9P&__8OK2bIy&^~`&C+SJsa(9sfT)1j$R1@ zMVCWKw*J2t-Lut%jYUuSH;Y2)fuoWpttAo(*Jd_n@J493mT5g=6|FoN;oBs)-JqlA zGRrF;B+8rgCZqd$zGp=5Z3TLDHfudUnEu0v;fTn!0@zpgIi}Se!z`pJB~IcxToRdSBtn;j&U5gXdem*i#%=Jpg9$a^zXt!^ zzKe=J4$;+!8lM71d*TA)3QibPqd&n1;RA?1oP%I+%!T3g%U?O%yn@plQl71)taO1h z1o3*%Nb2^RkI7KDs;QDfF548Wap!*5Hb*;s=jajWV7)rZQ3&LS)MR<_r+V}JuZtlD zX?V1o^eOez9^F?y=1j-{*QeB8iSjEk0C89F(}>flrtZ90{O+*3bcc5hib3%m=@<5W zedQO>#9E|?IbQF4f~k_9825H4Gl@X@Q_X|o9?v_$;rj#w#uG!$o|K(Py`XV+`iT)> z_bldFek!zBy>Bt__#pn8{c{y@SGLgI=C3Awa%Gxk=t!IR3KwH_5E|LzlE&7I;L-@1 ztp{QNS$%%4Mo#k1XPZ8Aq`R5$^w#^nu^>=*o(z{07z_WuERFU0D$9NndSKK|pOp={ z^$uRURmEeEjK1xt<+0KcWU1>P20zYLG^@`-JVQwf`ZSn zXtov_f#rn7_;97)pddZo4{PaR`_Lb0xcH}5M4Avzt}3xW^45k!6$TBNBT4vj%VHf9 zQO+obJ^rUInd@;i9k52iV^c|buCiGi!e9I*&(@^BRP-z`5VZkt!^7w+rW-;-uMPSn ziB8k3r%Z`IMFF{9<(+pS*6N;F+)<(d#=m>YI-tiy{suO_!2x=E`1)ppdGE-BxvcjO zrU~@kF~&9gFSEj`%qC38FrBtBpNVM%w&B+mXU;&#L`FXW7(GUD<~QL+EEbm+cb@F# zwG5f<8UyjZ#}~8zv}wDK?Y)h8axMRtd#X7-QJv8r9v{pG|5NkphF7*0OqQ+4b}=qd zZ*qIn-Hr_lM+!XEPd{+RtjGM|6!@6mT`GrAlJ+yeODeRV?Hy6?%+K1Egcc$2%0A!3 zq3eSv1%YlQ`ey`Lpc0L`3OghTU!1WoR5$;bNji^|H-Q7&_!P3FLB zt?Riv`L_p^T(x`-Hgm+py%&GM=)UDRko2v{@#?fi>*h_=7R?linxxU8GLYHZ=+qKY zgM~@{LPN!*_$zo?`1O2KNP$qkZk1_0$!nZ+K_Ep+o$OVx*8bk3^7u#VyAaNfe zm{ZUzR$5aPmf=G$`)Owl?!?6PBVXnx`(QtxOwlx-9krE4w-;7HXWKYX>Rd^?>WypC zWWj$1YX^QHfZIx=auHhycxqG!JUHBG47P*Zt^E&4jQn%45$3@pf;dkX+dpbP=LTCI zgr=qw7^FJUDq+jOD_hg`MLV_^OA(Uyk!52!!G1wEXq$V+vs^sb;um9zJbw5B0uG(S z`7p}`C_Q>krh8}JeOPNj(E0K&G}@;kRi-)4_Fe(28F($OKz**7yS9jynE8%b5$m)U z1LJEFTf!xJgO3&1C-}iJ%F;qjyGiVYFXs7fNq%<)BX8z+ehd=YBjV3tVSYZludxL- z0RAmGgqUR&F$JF+w>h&4E|lm8QXP$X9Wi1dI_7W$O|!}k59dpp?4jXlC-V4WdV+Z; zjP?I6L%~5iz7gAW9YwJud{~0BOp$@R9}Cf^=koC4zfvz%{*~m`IotAJe#R*P*>_$~ zZCWIy(s|S@B0$+FIr9X%7~-i4W^*#tqK(6}^L2Y3w#stL_|=7rlVBOm|LDRN40P_* zpEq86wZFG0GO+EbzaTQ>0MNJ8JX+_OlHm-7ITQ=5#9VZZZtlqNp-~rh`#73A^GF*G zu4Lr1Bgo3jN-IRiJ#&{Sc$mCn%1L|_PTQV_s`;PNfa~CT6^4yQj9|(GH?EQL7CQnl zby@DDQFXet5f@4XjyGM&bU-xbyE zN5vE-kBQB&eaQ=FrnV|>;1hOnH9m_R4|Ve8Qnmxr;{EW4Wf>M$hKB8HOkluPjp@h3 z)30K@`co42K(1QVn95FCj)}=C70-v}nKMpso?COeU|N?EqZsld3b|_dwR6TKR_bi3ceCntzN*PO7~&G`&~ldsZ0z2QzDZXZl5z2rNZP zMeNJ6qxm=QE9cRpR#cL|&{+Lfc7%uq3r)JjcLOUCd=OI@VqdeRg+9MhfkQ zce+MFB=@>nGy)@|;jOP?g0q~ijAtN1VM>J1%-j$$maPYSH*9N7kdUkZ>DE~dgNnBv zrLjDvFW>6tUh!k4=2?+xTk#ntw#bI%8uLp<=IzRQ*4LOYR()qOE7E^GgfO77Ac&jw zV7TO}teQ+y3BOfn@*brvZ>TxZA&<=d>v^p1&ci$nl36&4k!#TkC!1reHC_ojzg?iY z2#EWyzQGcrU#gW;8VD4&Y_zr0@;I+AbsTcpq)4&>8_dn6LeI@1ulks2f^+fX&065L z{t@-UKm3WXi5UxMO_#n^T{Or?uhoaYQ5+fRa!r#``vBoP5int#X3s36b%b5>Cj_)Q zuRGtKJ6W_`PGgy=CX1{$*KG^KPun0`;{tg=&lrS^rgMlX_Jl%v8?;Mj?e3lywhxY2 zS)ghs|2u2t!|@QC%g^`l|;fh1mf z=j5y}J=R~rMrNzVpLZcnex5&pNd*wB*(c_8!c90QC3Guw6lzI9Am_@a9p-7-{lYxE zV=eJ_h&{CcUM#s`EnZN6BI5($0WlV7kTHt#_NF`=69Vwwew}>_WO+ye+wjIf&bp5< zhhgOiYZ`5#6`bb*j|X+H_?|`ZEVAIj$j~{G|lkT-DVLm z(ySZ_tDfR#!91HJcLO}7JF(%qG(Dyk7`jDr2~wT$$3+OuDq;zogYjN-6r3nL`$1ji z_**di>k#;T+y{<));|_eqH@K)G2yB<`3}N~f?Cib;*u$7_u&O_;|(gH1xYvGl!IyU znC_XAfVNFM??vjAF0G8Ee*&67XFOhlGL}heX#2E_2gdBq@geh!k z@#GsoC-Vs4Rv**RFUakXWgG=#j9G@{v@eAq8$d?kW8+H5CBKwnJ&UBrVpE$aDGcG|Q>gHq~F+HK#qY@%b@pmg^@ z=>x#95w`OXM}SJGcQGm?;)^+qYCA@$lqN0XR?EEeyr8~s6S?$+FNnCkH~3E)8~Kz% zwhCL&$0U!6LO4gud*i`GOxNj(2`$xNXws#j=s+Bgw4J<}xGq@} z^$lu1^JB^xf~R#SV&lD$nRxTKc7;QOe?EWeY?(9%E7U{Gv}tk@QS{AVWqTCwU_tuv z=Nx9s06bBn;HD8*ypna|<#PgY=A}Gr{SPtGgKHtQmV1bFx@{GKA_L>N`uVOL^ud>C z-~bmrS*rO8|H)pK1T{Xo-~5YW(`u=LIxg+|phQomh}P0z<?Lycu8oDXHP;T6j0YRZqeR9emS!yRf{5AUx9y zhrE{)cGf<;gau)O+*yPl-&uC*WP!=+qx{m2ywiCyoue-${*ss14x>h~$&6>=uG+b% z1dJezO3)Ui;FeM?EOT}+8t$gS?xNB=5EEm^TelXIpXjmXAf!0TrRxX33_w_9R$#-@ z!9E@T(Q4~k& zTNo_Rgs=7bCfd7Fu89RU=m`rK?Vjld$H0ez*{X&#jf0RT_pIOe(Ad+)%qf7gu>5`BV#2=$vPAhNtk?uIhm!Pjd-Rk|b=#|NDEsaYK zdQ#>p!7l3kQFy)V_b2oKCmt+=?wGOEvQL`e-O*3+fFOBs@f35u`G4~A<}|YstTG=I zyV2|F9qi;fZFx{vr6Cz8_7&TOP{SP%881uO<8LG2%pn|Y;e>k2reun#w8V>Y<0zsa z32<%u%BX`SXP9EesdvxSK27y!C$IW;>KM#$cq>9xib-gW_xG~ksu_%vi6f^HfRAG` zU$3viILgE<6`SfTppLFYtNIcwRHY%DwFHID^h~=T<}t26Y;;oQvsu;iL_pJg5e2y# zvOtW$>C$2p=m+Fk`9@&qK~jqZ96bUzqh^;$p* zS0z@2lAyY#*;hx7!@t#}gR%KY3X31D6F0UK^Fr8#l4lo)tGTzD^ooE&qDT41+Oc%3 z2=>4I>Mk2z$NNLvjfCxAr<7Lg&fHH_Z4{sL6w8dgy`#{9mBzc$%;xqvf=Xl*Jk{Y# zbb@%{H8gEii9jsrNU#Hja)Ka8%W-~!*B5BtoWpX^Vnfnn2SV?stHcHrn8`jPo&Rr? zJN4}ojOGzT;V)Xz+jNP^3*>q zuTk;-95sL$=yBFMWijap8VG7MKwly{?5H$DRZzO?eSY2i(5cYoLuj_We>KKX&Frzm z-EX$oS|Axj77WxiO%0B6YZqB!AT#;$YjwQK34azyh|eu*Aqeza`W43%jMmy`J4HwV zy0lKn#DqRD%R<%K25b8JxWO}1Tf|VaQ7!;0FLbBfqDAt8woaS)C7aePWNE1zJ;fxx z!FIp%O?=<+oe{0Gfrnhi+>2ZfoYNd|?@A82TJhI{Y!FrGYnV7?Y5H7ga8gvD)s&*d zIun^KO@&E^V%;~AlBKI2Ind0PMB)?D+>gUMg}(#5T>nknQC0`E)La~QCXOV`7L3+% zOD7nHR4o~_AvV8Syv>;4x|>#%f)Ii>CPDlB!e1-*yv)d$S#R6{B+By{1RzK~T+{)5 zd9{!a0Q0d()5kkRc>|hZBA(HIHQpk8T`U%7`Z=L_m@UK5G)o3~AO3%kPoT_EkVT5$ zBd6<`B#^_%YNnM-NkE0z-M2@GmC0@ZYu{rzHcJlsiJl_a>}uLsI}LI4($Q!a z_@~QSG{Hf%NeKh&js)y{>Dci##AWvR*FVofMeSvPYQdTEVz3R%GBT}lg_}d>=~_1; z1j;PQH2sK#mJ7dTG z%;@Ep7(JPHzk~MuqcCm&2uI(gw;vjIz<8`lDTe;lE_3hj;mn}Xd^+{UIP3Zb!XA!{ zz8G0-foTWWzfu`9pM~Fur+iM@r?svaB=$AVZ0Y(bJI|~0<2A{_(LRS<5XtIoHvO(4 zia;G%8P}k)yDvO{Xa>2XJ?So$jpxA3N+LCbc0z5sDFp-LdQWQidF0xKEd9KNf0BY@ z@z8v%6DV`3qCtzjpA-(MJ_&pe+_aT8Jq}AHBN(zey7bj{*;1JgtJ=%G*$^=}X5k%U zto4snNqg`~M|~p&yx|@DC&{12>5Cp8am?j%f?73DqDw2<2~g90*cK@feSV-uIR>^5 z6lPbiqY3{NYr_Hgpm)RNJhkfE(FFG;_8bqD>dmF7?M!ve$$IHf6Th=~XvMgD? z1wMoayu@u2;EW>nY>Q{8uywV*0hpm&6qrd+S@vt-936Pjqceh>%>!oDtJu$m7bIzl zR*(``@{jW&%&Q8$(E+Xzjq1_8Xf;e)R4tNW*n$^Jrnkdl)>nJ(7w9nFdEvEHeVCfG z#aH~`$eC)y7nU~A&Ug)Vi(YmZLwWS`DzpE>;$`Twgd1j)7Y458g$A6!0wl22P^r>w&vM#}ii8 z!D?5{Z+$tFO=33I_!<%W3)K~NPE_kcbRtG7l1KHbIjl3I{u>2VJ5l`@rA&c(0(oIX zd1C(@?eorOA2_yoAinW`o4 zK){Ul5y^a7@p7^G`Om$3ikm`H171QRKibu?e#>)r)Ex)`Jw3>5)}zK2XA%*wsy{tI zD-rFL*n+{EYjmLRkHi&+Lxar-teqZ!QPL`Ho zXiqo{>fcu(^ZVH~fTvejKRs(YLX8JcEq&Z&sGntIze7veqZ6aZlWF(Wb<>rSXRu9C zYKLNo!&nvi<9V3v#esAur`vA<->~?Zav}WF-z-&q1e{F1;8uPaA&qL_{Vo`a?i>vc ziGL@-$-=Yw9Y{EKXovlX@G2o|-^ypwAAVm5Kch}_0@^|VX?f|_{=?$rM^N-#)P1%# z8-Es#Qi4u(T6U+(iQ4(VUB%s#1e*gLFJ*gU z*;JfMjnMfIUu1k?=J_3dnwbXK1$_SwQgVOZsp

c5WbYT+xykh}0G>qr=(|ADsXo zwd(;TNGXdu3#dc`+C05lU+>U6CeY8v-{+~rRLRO%S{BxPsF{9)J*!gBcaFpmYqC4~ zjosyZsr~828`-PNam1W!e!~lsJ%=+>RupTRi>LbO@KHp^@z}_PX#U{kgRkf`ELxd{ zLzhfJZfe8`5gt;tN^Q0C+C z4Ybq+Oas!#R9Yd}h6ldVA=tsneuf#BePti-^@=2*$#Tu@9dpc%5E&PsOSfqJzS|Zl zrRz+Ck*dpj84(LhX{Fv{J4#rmti%r|cY(|~pCQw)2i1`4jD5c?HZXqLP2~QneM>2; z_#E4(04ZLZg2uWzn9%q`=3aN?*@QoPJ~v z@xx=#m&#EBhBl5=!HviwgFHguMOT1eAS_A?RXwMc!=AUU5`(wDlg-|9s25*hGX%>C znMiY%UJ^-8H=?d7F{N?+tA_fHr9EKM{>gtH z$<9HYta=MqUZ75XM>nYg|Cmb>7DJ!+vc0toTI{;I4m``Y#KKO?{6O&iD4OrUdefLt zI~7qYjKUa~STff$&0p3CAJ`q4{%CdIRO^-4jg;+Mq2Gr*s&ib;d<|C{Fnrg&3$?ee z^=$^vq(y`ei(v69a<6#I0=i`9+QYX#SqkGnlm)rRLgquao3#jL(rfGt2*bWz{`5}l$jQEjwI3r=D z3uVQ76qqSqCJZyXRAF$nGCH^IYOk z$a=6kvSv)2{arm%`+Pgy^tO_Ou3qTzY6^50pNo6->ugd|dO z1YIemJcA<-)k4LvI~so47VM9ab z$~kzpnpvefmhP~KfI+44os{f<5`)uTvJ@m<2K$(njl5=`JUEMOxo7Kcm7-c*{z^-C zZFM_fKN-n~yYmt5l@-K)DN`h{08x+&eMX~cIg88o`#Bdumaf%@MVKzJMXOpcf#PpT zlxVdk*d{w7t+On|o$PfDW&_m!hlEO2L%&(bQ?W)Mjd8{}mR7Qt(zJU?@@?18r+a7{o)t5*BkeZev#`6;1~|I#j@mWSy;J62 z24QWsvWKEz0LZu7zIysqIrK=a8-E*}h|XI(1eoWEVNNLFl=E~Z1rwR!1c=D*8BF5C zUacb&Jpx6Nej}I2%I$kB#t=DtW&(DFALkk)x7cB3u-+}1mFP&)box_un*Kp&l~{N< z^nWL^w_giSIFqg0et)q=3~yA3glcclU)ps*ElC$9{d8tSzH9>ENkT8&ef+N9=X|NBo zFi&)5a{0_+W>uPjW5%|8{^b7xUHE}v`70)mYo;>i_DMlKveq`N#9Q{t{PA!jFW|$E z|L6tgGBF2E;{BMj!-#!N+t49V?IdC07EC_Hk=ABT2E4Jg*Bd!#>Q7kAASo1+x)RM| z<>}S^Fy7}Z&LL49rMzGd1!uL->*-tK6)XH02M^koN3EP zPa*NpJST*3bL%o*p-A738IXv@#JtvEWUVx2=B_5e*DIEC~|yf zi=|sv4FrHGbFJOfce|wx@SOTSecbi8cw6mZ0hPv?u8a)!e3a(A;fC>{_1{lav;&5F zqGX&|8){@}-s>?1k8FUFJyrGc4>ljmM5FCT-B@?sg(9vbhq7PDg1{vn4U z-LI4sxRX9~b39C+O|;UnE;f$)Qx=M3(q^1&!#q$BlcL?Zmp-3+YaX5ru=}Xfd)KLO z(1z39waRSC-Id|XvC~up?~i(B5cP$1lH({0%#K_xe7W*)l@)Lrq{#sqP|c*OfMCld zQNph#3@w5D&bwVPoAbCJR4YfcYAtcN7Hv|lT)_3L{9j;|l^VI&=zi&(o{z(gi$>89IyX@MRDjB5Jd7tYKRa-=hj%KoaD9yBc-e{7=PtYi{SN&)rpu z8bN8#mA&r=8wyY=+U(z}X^!23<0U*f%z5G^ghT^YyzpAb;v(Hs_ar>lexy(1sZ%UH zr&&``L*yL)zv~8+NW#TrW6`DCXajUaw znyBvN{Hq(?lQ=*CCDPf+Dd})ch!?ioKV^{@0*-LV9|!MW-LZAih3XnDZ; zkqjKbS=i~^8pHNyOGtUQlWCGVV64rP@k&jkW}NS%g~3+vkNrH$wp`Y#Zq90WafX&6 z={{r$@-eIT)T~fxsEs~!@K+bahg`i8EH9Xg+m|slDksz5Aa~>}S#aq#0r@ab*7Aaq zGB?IfkOdl$EET4#BN5}%ed9;=i(xTG?_8X8;6~LZ^B}TY{PO^}Xr1cvMpQz_c%oP_ z?#|ddC_MnUduRfhxC`PKI5bYG_P7G$C?X!$tYTXuJ=qNxLic^T#RiNgtDd#`q>mwO%!Eqw7V-nmqaxhU2h7uQLoMiP6!}&(SyevP1qcpD zEGXiJt1negK~FKS>Cqp&Ppda^*6L~% zvYzlG&(F{MRSf`FtOCBVluZGZmJ~6xL#0V^#8HlEo;Bf|;N-05MH%G8^3X5@m7&h3 zT9-!+ATWr;rTAMz;h4b3DmlLz1Fh-8BcE!M&glt67-MvNB=Y@iUY0}Kf!3;2r6u#r zZ8qd01D9}>Xdk!E*z(_NSyPJUIs&f3+c;krTrIqd7H~KXgYc{&grK>(7sH1l1I1}w za{KL(FHZPgVpGq&2CXu5dcoi~wP<6mG-e4&MUd_9H%bVo&50cMv+anm=*zMAk5qL< zK(6L?Vm`_RO|tO!3E+WO!{;vIDv5LCAK_$4)f+fmpdZtD2Y=qwEP=gt3Yt1&TfWD3 znc_B|iplh`C~?aR@Q0~1-b?b9=)J~AH5CWCUssN&`B)c=p}>#As{f=JThgd5iaAZ7 zc$yOWw@BTc)8z=P9rKYtxnILBjtz`bR6}E(^{v&2D{2Y3*O@2~g7IjTgY+C>jJ94G zhnSR@GZN}mK4j_o)J4JCAcPM<`32fhalis8KLUPoPb=em*It&|tuM2e_d=kimUGt3r|)8P zxc2NI_P|Y&zaIQ01se0~^Af@00ld1dz{C)Fk1H$8o+hFSN;xnOI#T_&RjIlIayQn- zuIEhU`B>ljxi-_^FoIKtVPOZCTBd+na`u`gk=*0C)!>I+;GULuZsP8KdjENZDBO>z z0M08YbP&dRHi0@z$?VRG;z?+dU|x>Sc$aq zC=rYFKnq+?53kg15GL1?N;cy8G{AbLJ40FS448D9Pl*;y*7KPxNB9VUOu3nXxmB~cnS#JNga(sWfD`v(4vRr0 z#S|hAdGe2Y$@Lc61`K$A-vJ@T!Xz7(`#r@Lbh{?ctLYuy-2+>&!I_fb>2^f0L!of6B>{*{b^NHlOId+Md zY11JtJmvdor9bGpqp{S1rX6v4V$FjBc|x^bNXRSL^=!=F!HP5p8OSyF?j&n9#9Tir zmd<84!!M%1ec;z>H19VPxKuow! zpun1hZ+wNvu~=tRC|1e7$}vhZrGRB4k29zx(kLo}(c+j}Cs|3b%6Y@iMQH+u>J*A) zYJiy}EBz&XWgQa`N-t>5k<3;V77Hh^@5UHiwGCGRB*Q=03OG%I;|@{1c&yA8wE`Ca zFqn*ha!NSg=+y*MhvGX1DgGwX)Sw(M{?)5$Fg6e~AqT=%U+oLnnG4k_2>e|e`zm^r zDwR11aTG}KFY-;|e{mGls`QvB>qI(l`S)Zzq70IwoT}e?ETHNK39Gt552+ z^g}0#A9G!qJk|%?F#Zo~eqb$U3u;XmYBfN+;MnRCf1o;#&7-E^@5^u39){KOP+woG zp7N1XCT=Hy6FW1u`U$f5=isz-_pr^#au9mi9yZO`(=~z?MQIRRJ-QsScsq+dFfDRE zV60V_5dhnY>U&4T#{>nlbtm~}uT8z9=d-J!eYfC}ZzQd-NUHnGMA?9uH-d;_A6b_z z93t-bP@c0a4D1GS3y)8qlNpMT0dPzhV=*2>;mCIugug3B_1n~=tcyz$Utf}3^A{NF zT4CiP#W<;D1b%_LXc(#zn?H0W zW_jwKg3BiyE@Q-mFnIUP4U2ZSz_2VoXyL0@T^So>Kcw<^UZac#6hS6HnU&1G3_Cao zme3ck7*LBdAvuxjudMmqv|-;`%cGRi;^I&_K;H!1Y2FDSu%IQ)mvFg9AjV&^;?l+o zt?$e6jp(85Nk)MB49QM2D}VU~)mznoH+b1|n#6w9hNi$y{zc1^=m%AAXC}?Cx#jW$ z8B^3Wluo2A5;oZfc?;Es$vdC|Os_rocu*c{AlP zy?YeG(v`~CQ~-AuXJX7vCFBO;bfLha?T50+yg8K>Q7R8@F#+)T9nG+CT^Sm8WPp*r zXP4VqBp@G-;X6jnsjpc;Tzj_DMH;4Uy=VU%jrQ*19$Bm02Wzfh>To}))lf1hcW!Ej z$L}|cN{o>nGml)YS+&%#Pl2zWJ(n9o2Ny+(dVQT{B%h0~ue09XixZHO_)!ofGLF=| z4XP~ycL)k2+U!1tK;QUEbt#W!xFX&7>5)sM<1-cFrCXUKR!Xk*y;X+G3!y~MFy~Fs zYv9Y7SoyKn!2sJb#vlO36UMt68AYxs0shZeDG^>3vYv%8M1{Pa0`P$N!3t$*RluOTHHu)_5)1!?VZUZSu;g1#nqz?x$=8c%j5G)D*#U_-f%`jXs%Txd#)iS#CCfS^+&Fe7-THHpe1%EY~%pN z;N{qKJ07GgsBG%&GA;Qv=5Fdf|`Oa8CH3VjsU`TPSpW#gCGq=cdZp@aco+A(Y?K6h38yUrMqAG+P0M( zPKK0v_iM^{EO&pOn*MGX$LJ(J?_`?ZQHAhYd|59i7$>EBafhEXa^0;d)C?mT)W^Ne z;a~`AjoQ=}C4P!|nDMo-94!=soRO>PD?uY_5`yS+zS3QJ@Sq&AViTUaA_|~OoiHhJ zNlq;V#W`b^%?F9TBd^}0&-8feEJ*%>A;~qm%N-n|tcJu%Dox>obU&d9N?hzcva@#( z?sO|KqOW#{$W%nltK!c9v`bC7+B~3M+d5*JHf=+Jel$jYLV3|R4&8l0oHzWl}g}9`jQxKpR`1)*# z3CMeW=(kA;k)-8yS$en;z@DKHWezxsZ{uRjCb=Y6$D1Kr7?BUDSl=}VYCzI*1JQD6 z9w}H7HG~OKl>)P~DzT?UdS&abkRd;b_*3foKwo7X;8b3-nDWYTlPyzOr0$L z-f1})_zl|yAs%?_Cv;8s9{!9EUJH=|#3P#V^lB-Hl(FxP5;HaS?(#?s(;^M#w9jMG zCdr?*E#2xY&EiR~H}&IWiN{l{uB+U}m`*Z?E1qvgn7gc#zx0ay6c@Yr0atG^rXWdCCY3 zd{1VU+yMzeUC>?EW>yhl1R=Bg0u42_|ytuX+XCg#SFm6&Rt}p6kP5#^%b8v1@q= zcjyI7VSwy(rtOKvdU3w`?f$9OC8Q;JoA+6cPQocQ3f%lQvZF!**vLqq3`2%7FB?}xcu zoA_Ntf!rI9!2}`)L?Q6VwK24^YHK@;W*KH`@D7OfWxZ7KOyms}L-AFvDX)IP5_f2VB7 z+vG)0AERjip8V+N5WmR+oN>^~CJP?j&_`+Dn#I*4CqH$#7A4r#T7TX_a8Mq9J$O(C_CcXna~Uf$ zGL{1N?+)mN`mgl??~M%HV@%HtAcF|W$)QtucwmAd<5BQbUmO_sw+a_k)AhRb=*|l{ zXd!8F>A zyiicAuS?m(xp!7P(nI#xAYnzpJMEC8(AKjoXW$7t?>&(TWF}{tH=Y->UINb_{Ly57 zYCJ9M82qOC6|CR}MfRLM{(WkJV*Oh9XV1H6@(3lhLvVXkS}kbT@ro&tbO5Q zSSEdtyE}p?)mP`01UPYdIM)(e_zgwJH$#ZV?9+VVB3+lOF2LVW5Z0p{$s?+Y=*GbI zIz5wU%yYvS3X)>5m(L`VT_JGgJgI?1wR-F5AlOpRrjNBEiNkO^7Beot=N|0 zP`s|A{r=1(tdtu2PzV;WrXEy7ULP{iry!VM3D2OvpwpbC^1uB-48g2gC+@cH>z_mJ z+vK-z{;a3VbB_^k3tNo|LHjV;Aip)=Lp`j!$u~1bmzTL0VrBz#-w`&To{)e&{&W)X zxpFNo=g5e_ZrBBGofBPh6sm)=`I{V(dqe%~c{T{xJ>2Dn@_Qup$#=BCoXI zgNy8_M8k5Dnz$S7;5QY&D)eEDt67BOoOYRA{IROsw|pJG5cx>hM4{3B2QmPsJ`>Svtd^a<}t;dq>B3V?^{QOY;SiZm8wkc}Jr z5!EXHgPTq9nfeVBFGVm!2=MEV`xmv8$F^(U9Bh*^kLN-^P6D(jGxC-R(zpnFQzbKn zUeNzyF<#n03bp$%=PSLHN-Z?I&3rN1`l9$|btwA_<@_zo@lcH^oF9Cc917syO)NlY zNuj(6stSRTSz0TfWnfpKV!kbE{)UFK1re}sp(2*2mKoq~|1@6id&`IXjSd0X z!bqQ)@6mP&aP`Mt$1s7uZA`}M?=)DF_lfftC8t)ZfQvReE=O#4!TV58K6e>y82%DT zKaU2ygoC}C_@%i7mO&JkZCs)hPQ^&^w=Ve=6!S<=yJ@lMBk+L5Bh48LAkcuB5i3Gb zC@gBHelR88mR5AS)3;&ED96@J0_G{1C$63eq8YfRpkhtGr0#e@yT0F2*M zVUslM&_#?YrwG<;^ZKx{^uKmH3R`Ep$_wptclLV`RqYyNN=Q?fo(1wIe6@@CdrELq z!~X&?#*~0B&P68JS&YOZZbGdnNm^3!t;Bvl~sl+V;}nKglmD5ZL;^rPn->7g9y?0 z7ha@Q6xf84l<)VQ+j`tjzD#j>Cfly_8LqG&7jfO2IGv&UL%zRGGm|Clz9isW)Mim8 zP=E2^_La`jr~cUvSLA3asxpdyTHaSG*fln8N8DOyS5EUf~MdqQh&1xsrB@Ez;(o}PNQE>0S^m-0F9_TFoo&_`CpQpADjP$z5F-AA|MfpZw z&r8U7>gdIcbldp?ezIz~)fb^CH!vn)d+7l^d!4n^=x>)hG|3HQqFtg09D{{C=tH;b z`s(`TtOhYifoco{AkZN$hyUMiVk~<6;~#88omDo}C0ijD5Sn7hXimN$V(11j6D300 zqlO<^9FX`Tzd8-YL*9`fvk>2JqS_)l9meC!xZ9I7gWPv>wV$CIgi~^jE3U8(qpnT< zkB+Bm`Rmk7tiy+5=q!C+S^Yv(*(uVf#O0RE<5HQdu;9gB|3yL%U}ubuaPhJRqQ&JD zhU@7$pg{ZtY7zs8Q}DqePWM%S3G#!Rm040|@cso@_|i3{H+^_C+kA3#FBqDU%!8+1 zbgChk$^R~*`(Mee^U9K7h3vx6%j&rI_vK%FD0Mc%w!+Vvj_>)ub)?TAPzXp zO|_^n-&&jNF~zX#o+_vtm~MzO@)7pS+9`g&r}u7sEY_K1SkWkv^fp!aj;4oOH{Seb zaEClb4ele88tjCCj+TJT-SVlrHu)avd+qcIMRzzTTRH{eh?|aaUou%6*Zz4q+nNt3 z&!5RNe$qCBo;LrhHrlDNyYl^*j_+XOU1PVP?nD%r-I3`5P}Lb^h<;)Fkroo5GGyWA z(|nN0tg=IRZ>|gwva2yh`$AoY6Rgs=PZF_h^T#CE%pcRD=K~^sIC@b8U}~|(%N#2? zb?*X=Mu!-leR?Ahky(-uov8i@9pNOjYP&N%DN~Pu>(yGfYE=VUqE0}TRm~0erH&}( zK@rb(+S4)nXe??$7j0q9`7g`|0yg}h!!FuBqSrE&fu)!WJsrI+;J}de(y&`ofo6#X z@*P33P&@dMeT1c4A@hvh#v^{x=EP%Y1U8@~o?)+S$=bte?Y=##&{KlxMK9&sj7zP1yPAtW*(?Pi={RPEGh+XkrT*&AN&E=AK5 zti`;?j9VD;BbErLS>92Rk6wG4v`$aSz!kR)M-kGKar;8(lh-v+4+;#CZpqY8bF{vg z95ME_%lvT>NrhiGvpvrU-h3*Bjtq{+>MQZ?V2b9O{X;Vk3S2b?vcLgsBg8?$MMNTLVzbUOBWppSC_zvP&43*Z+f&gu zeYd6NziDRnw^f4yU${z8mmgXL%6X}(DvRA`WsoE{45Rv|++?GeMhPK>V44ga;03?# z;1^2&jAP&ZM`xnR$K0l?Pm1jHA-_u5Ys^a;+1{A>&!Dm=u=Z=pfQ7?a>*jzz1RAl= zwz4f(M2gt!)R2@SiXzb*H!buotwl;;ZE#2*ZyWX9R*kK=tu=n?ish!`k0b{4XWA|g zU-VUZuzPFj`D)9XW7!_}HTCdn>7(v#pmI=Je1_N1@gzCTay+e}<7E>_(p& zN4?yVU9nD;Ue{>xt8f6%#H^=&kD=@Jd zw{%2*DjEcV)s^e;zlJ;1mVq(YO^3U^Q2R4sUEvm{kG!jg+D3P~&64d^Ytr}aGfc}z zn%K(I6ghp7^Nd4&;MfB<4XS zb{C9g_y0x_7gGbmYHvrvdk^+2y}iMu9@ij>%zj!YUBjW9P^plq?&7ALwx7(?IKJJjKf>t zYOlZZ7>)NpHHXWEhZA%XeQ*Khf4>izKKe+0Vx9GaRMf+r9qD4xL4Me~b3?$t%=7S6 z<6wI7<&9_`GEO3arV$ClUVW@_!Ah@3uGT01fN%b5H_or{rtN4X8c&rf?tV-s)_b3? z-|L+vZd5yZy3E%c@E@w(x7}o$A{8aSPCH?Y1>ke!!(|xZNy?^n=jIT7VuvR%gSBdb zae*YIZJBKC8To!{?~@|shI0jR{*;&}QeTyyiv@Kbxa=G7@#w4wcix>$XHtnYncQ>0 zPAOS^JA#M{qD)ygrI8Di-52r;nA zY^5roz3R|}w5isN@*ce!2!!BAWudqCXfjBTYpMGUT9JDf3@Un|Xo26>p32#zDZ+dy(BNx!GdI#ZqEITFnt^=`hAsXFd)|Q;R zWOreREEB^gFLQlGULHjDlwD+x$EM8w_s9hyO74IC(_QyOVN-+HrPw^N;)T1HjjZR%6 z>6@L>-=c2Vb#@0=2MD-$670W`eVr73T1UlF5G&ovp7idAA|x*U(}*arTSPS1*^ZrH zr&toz6EG!3qT&lBM=h=q^{|6Hj{gyDSHcVNPjtk!1!>ibFHNZJ^tP#+#K%LnVjv&- zXZkm?1alMiHK}!lHsptSJ01fL(j3v*U&FoD*E78KVz%Tmpm$V&p=-Kl6lu3!mhRr? zg-0WFvscYzS~zQM4XR%sDG#KQ?5<1}Vz4R{%@m7f5G82jBU~y(;>(_`2k#W|Q7Ruq z+s$3-Pt)VxI&;{bLddLtc3~4RhKG{~NDLPHyS{5LPtATGyRKvp56mSntiW2!rQjaP z=w1ejp|jjMmqHj5q$#|Nc;fgM+og1fjpb5qT2G9f?Ap%p#P!HUm6r$a z5J?5}@ufRqBEZP8RmIsMOIbKT(aYJkSb(vZkxHR(Y8WbbAC2pLJ6rqi>bH(gXqgj< zfeZ(ETEWMOW(pzlnbQHxP--D)CnMKW4X?JI&;aw0 zq##2}LV<0{!EDe7D$DY?`IxIDBVAXN0Mqpcg8r?wL{E%Sy;T}t5G%tiB#jQtmMe(K zg*|5^=yLwp;kuwquhWD$+#kmNwr6|=6Qo6UJ^-J9N=O)d+xo8D@TWkqK;6S3H$Si& zY~605`i2Kgdai=<^|5l;6iC( zBnYG}cB&#-cudqONRXXrrw$=~#J5Ot8KWVUTpGO@j5>R8XzoEs;nqVaefm15h;)%u zph^yI5i^~3$;K8vX>9O0QSiHYdPWTn=VJrsXUgxe8gxa4@zUXnd3LLsP zK`2^$oS$c>26wJ=EWnL11wbkDS9Gx*JcF+a2wh18Nie_y93=~>q8C~jDQV?tgQ3XC zr>eaLRgHgy1^5I|U=OtePL5pv1PlNG20n3V#yH}*qTuNMhO9^~@Ld($n*5l0;AJIj?_%CLH@vS0OtEiQJR#9LS2xA-jU5efzUUQZd?_ z_4e9tlu`%x&QV@;nw2>DbH5^}T?Zj}x|B3v_h^Gn2j2G!QBKe~0cV5Gq9K^sC`nM6 zB`vvRd`wq1-61sJET{wP{#G6CqqcJ(+P2Yq`#^wNIdUjmzA;x-%wK6jQ6son|L&%j z7q}a23KR;e$U9(6)==ivb|URVeJWQQmNf7Dp$yO1+?$Wl-NP7)@~4S-4HH6K90l4t zuBYXx-u;_KUxecJmk+YYK;u<|=%-Kv1%PriCSIUW6AI7jzuwKWM^}`Vq$<|6U&!T419zZ-c)C3c<$8Z+RI$D%M z^u`yv@J=b$G+I+;Aedhq4T>lh)aHF5W;+(H%s3=#?}C7ujiYKfW5P2DU|#!cGqMIR z$jWk;ZlI@dAxbfeL9R90Mpl!RerPGDYLz#mN-py@TexO+S3hGx9b7Wcyf6Ig z&_}Am!6BArz2xe1Nm_%pzV7F^w*G+{ms6LFjwE%~e2L`Gc#`IA+pnXjUp=LG$;p>j zGH3@K76LkgVVeIFjpt3xCUB-Sy8axD07*X18m3ksU7tIr?U9+i#?p6KL3vyFYbW8Y zkpXz}o{gWliyq8~x!k>5p;{lq@^w0~w65T3h};p@kV?kXej5bFw*~z*xE}$?i1JQ) zE`!scZO0H34i|0CUziXUf#Zi}%GIBO_wSRQ2n|DVYa|FN&K z1SwsG)ZR?4DlCsnlCKI;5Ru@H2d(Eis>&l%Px2=1j}imECy67RQMftNaudMtnY7Zc z16V(&WlAZ3RFGb~%PblqlJ0MQAI04CJQlM`^JrbHDXA+-4r`n zN>{mu-Eah>Hy97#e?-RoIr|=N*f`H*euV*MbpSD^aE=N#EM`yu9}b1CaWfw|?DRn5 zr2nEbTq^I#kcF~cF9T&Q+GWZ)SIi5^tnS<%$#{K#A+xVhQWmH{D}ADvn%7aKK$>vNQe|UuCNHUG~6Nv zT8X#W#eB3ef{XP-V2g?%b8GJqK)VPDyw*p_c^)EhA6E&w^PHsN50)Fm@jfb7;-n9b zYCF@}hCN~(KJWJeUUVtqnY?dLZ}AWR=0Olw^GC?GtXzIJqaii%9`ZKSl`;NM1ng{E zJ-Z-(L>M#3NP5-!Si*f94>)lwksNxbL%!%M|7&>UoMEy>Z?2w@LtMDlH2*p98tx}> zPv@lMMtt4<>tS8S5MV`ZkU>Inc$wFlnX>ra95%Eq4G1{>hb4WkMdX)h=SO)VT0PZ{ zO?$g=_A^MtGr!y3$#}@5=O7&EB+4J!JA-ZHf?ffuJN&O*5qrcZ`AjVZsGjn3f<;b4 zf9(?H{<;tR5%6IO1mjoYR#7%NMgr?ddm|+%Ao_zdJ5XAinQUMJx6_{F?X*$1Cn+P> zW#!ryBkhaH`TM36c<53oMDN#k(41<#dwrguJ zJB@|Ed+oH6Bm!%GT-G*RWDN7}RIM>3Wt8-q1{y{r{9LwA&_3rWYsr54cp+OgJ$Hvr za;&MNgqv}1W#t;AVWVi#1J;h!5tiOcrwk$?bkj8?oXf$jn1`a^@xe7%>Hcz?GhI;v zH%Wic=Z}H+R@ceEfrNi}<(7G7`d*->9O=>fTOC+F{c7T&JHQ(F1JB?0=m4F2ibWk? zy1v87Si{`SZkODEe>a%6uzWW_Q51JRUPd+(z#JDMp zezgylf!$qEafMg&6nXT2M??(}8zoE3A(K0}eAH+mP#0aV@jw;N1eIh`yhYMmrR1a4 z@2l~7VBD+VUZBBmqO* z;3Jhzyi*UdD^j9g1#*fJ83$!PzIVcZH2@a|dJ0&DtY>8y=+1Kr*;Pq!BuehfJP_S) zUoB*J_Vtao+QV#jd2-7+(3`cZDw8}QsKr9_xwdF#DV0+un#@QV?wLWdaNmGN{8LPC zAJo{rBSKjxNG7fZEWDK(EXh9Cjc%AJD7@cwr5i+j*rcn4Yaz1@)St5=arJ!Zd3r6Z&wv-z88`n(=V{=At!0?nu#vWJ5IO53bVT#J zMeD|;dw&e$!G~Pod$$YP&LM{)kQ~-FaMQ&am#6E&`}~%&ea2@nQXUSp^0@B0%1qma8Lyo;uD?SyaM}x>S8rE6+9q z-CzVuoYpD}VttbQU2UZ6dY3YTlpTV(vbIPCoaoEC1)kcy(L#-)R%wId)J55^^>lUwHm>c?Ktwr% zj|T2*(S4lJ|D|JN(~y~n*LaF@gFALQ3U7j3|G_lfMm!%`dgPJ_kDGrT`ng*H-}!dg zW!egRm-kSa@<9q3TeJN(J`>ssSr1LsZhQ0<(-cGC7RR6UsZiB6B|Mbq?Gv&%@B^u% z3{p5ef1JJOMCO>|&St7-VZeZdZM+Mq*QUF|grP6Iy1z{8YjrqE`O`vw z3OOnK@h*|jr<*IQmfwc7UHkGHe|(#k>fMCTpb8Qo`4l&tm+P_gdKvg9&s(TQ)4yk> zm-9W%dqf#Be$`!TkOx#A*>j0|EjuMSI6MF-$)Iw(E%yB$UN(e)B=~7@7|wvU>uEHZ-Cl;%2?B3f z2Q$Ah@kLaD1<5s)2ISKycH_ z;Rxzrenx=gFXae}p#F1i>fgsjm{w`B^5y`%WPX-yN;EsT!q^by<*@{{xSl$8Beu~D zDyi-CpdJ3Qp_sfW6xEIz_g;T7ubfv9xnYD86Yd zg|CTNeqic_CLC^-$o_60Ir<9Jr+)yy>kJFIRZSG=?!N31qxJo>$Xde))481%Y1T=c ztM%m@+mY2AHb>Ea=bJ!DZM>(w2cd7AM;mhasU)e6Lp#VMCC*wX6=uvDkh*SN(E7Vt z(RT>wzPeGj+~bL$Qe3?RKHy2zFGCrkuy=2FA^dJLazXRg9B9s~srkN!LhuA$R#rQ4 z)e*{>&Vd^-n;vx+8BBsR_UsjIwJI*uI0F}3J6H{k*Mp=#gG3!v)W&Em|6DI6_FHvb zB4$JJLPKL5MVFEV{5xIg;u^*vccuZ2$Tp;wG}#+TtI@)a(9})pwN1W2_C1cabQ$iR z3YCdK0+hCtt&T4;ScDVZt_;F=k^{KcBONR^=pHBiDOlmM$MjE}P!<9y?O@}K42Bg> zNx6-+udo#F36!m4E!@Cf^(hR?sRN-Ku=Hy>FYy4AMcc&(eKUoGg8~NPUMGXl84-@& z&#zr0#1v+)d#ns>idK&fi{h)3xm1W8<*H1nB|*jF;BuIzG?r;4o}E0`e8!<6#HtM(U`S11UqBLl zRaTJ{Gbwc`{AcytbziFSH3A;7Kj%PvK{@RBpmo0vWfELCt!D&sBGMtUQUKQ{7^Qhu ztDez2+kA0PV7OX9623$Vvo}XYKi4;!dz9u)3}f^FfWUs?JDK5CqKnBIGxWY_YECus z`5&VDs|opY!prtO$GYIClb-t%~;KsDEDj&B3kI%2i7ayu1Iw2dVcTnmKB0-!MA*2hCzU7b@$o%f92 z4yA}bzbk|i5j(bqn-q;LyzCcC-J$g%*a*N+&4O7!wb6BgoHI)Y|#0N z&9XI7=5M0h$e)$pc}PbW^zt@XA{1AQOidlhP)HF3+Ll@>GlB)*pRD#aM;8STan6|; zAR#@-*7gi|0{{Zl&fQ+8+5S7@RksyREU_7y*asQA ze;4}!#&;A7?E%NxP39QA5$LYhX8J4SIrUj+^nduIePBtMeBW)>uXvQNBEyS*Lr##0 z{U9Dba5!m|COQDyrUNIvQHF$o)pk>1tYdbh6g@h;2eA~4`32s!}> zA2f)%W)ompkgR?P3o>Mji`%#vd(CzqFqLS~0ddgyvyuWzj7y*nLv~1ywA+nI$_d5FvgS$Rt6S$&S|{j@gXiAW>ix zc)1QLwT@7})?m@flOxuH?84{X0ti=@g+ z1KCtKYtnEF?$UZp&;C$PcY2XOr%lW$D&HvvFAaE(OR2@99XHACbNv7Ao`;E0 zkf4eIWQIdZiyO^Eg;ddm97WI17fF8lq&RJ60cV0mS3Bt`04PNbTP|SIZsFfbgxD4> zrO4Fx!=xLEk}FZ8a>96?(mh6d1m$Ne5!oRG^;hZ2hL50Z%kzq%Sa~6J)&t=>LL((^ zL&CTVLshX!IXZ(!WF}VW&FNDXh(s8P(I`Zh%6s6&j~@y=ChuZJz7BqxF4F-=b|qf` z!_9oLjUGc32Iv^!{(%&wK{Zbz|1aHEdPh*n!z zEZU|x#8z2RBU4l0?Qof+Yo7?_;pU#bfyK^hMJSCCEUh=RS2uRKLLAZE8{Co-J}~k(2CGic0$=` zKDE|-!@)y1h$B*%OUgJ6({%n@vVt-&zS_X)2Rl4|MWsLB9i1>*;2hzPrM)C6dBSD} z*!oj&9Ct?nGVa}~pzsP>T)}!W`Ge~0_73x=RedUwwM-P&|B2Hoo+qTDXC>Wxz8GpK ztx_4_r^)>xLY8v4rlU9HBVTCXYM%D?Qkmbf_&)a?y=z1yR3_(_uKnF-*QEWJ{{Z^9 z*)`u#-!1Nx-zw-a;r2*U!p$S}pt6L&u&FQ&@O)1Iyud4@8n35XqaIRkuz4Emd&IBgkq zt~$KpU;{+4mI0QrC0OjuJhK1|Qb&&-6mBCOgbq^@tO|Lr$V|$|F4{;x2Lmo|4X(t3 z#F2Nj!2_m6XVQ{Q>>^!H83z2**n{8ismUa7hAbMI4(EF=vN!+KTozY-#{H|=@_5QX z91&ut!w?n};mO_0B$hQD3E}W(2(K;P6tof7LAyrvI*`29K~>zRXSY=89Ws_*<6kr7 z85n{pM55KG6Vig;Rnz$%hmZg1gUwrAc{rGqjD`q+fL*Ripgz;vY_@m%2cI9F#HIf7 zTpT2QGlDP2pCiUFPmTyJZ8K1}3M*j1uPwvYPTD?iIo}a}NgsG$6}9L);YWGP&%#yv zIT#jFU)x2Gk|Z8`o~1C-{HE8Nr5js-JSmJfRs&j(nsQMce;#ue)SM(oB%jVS66#Y* z36e0?#8&qhZv!3b;hwzdyJfq=H>2`y{~ow>t&8hxLIz)4JAFdFTA@M(6SL3$5xe}Q z!MlHRVz^Ek02@JW6qtV|eUDinawM$VzgW4c8HVsVcC2;w5X;_TUNT&skDISp41Yjw zL1|#LW?_l`>XDP*kgf@vgv^Z~NzFy^non!!jo42gP2^1sBQ~_MKlH}jC#4c#1?m<$ z;Fy{}wa>#Rsx`<**X``%6K#H#6a-iQ+LqVsq$FIR`kWk%@BG2cPMF#7Ez z3@&-k67@$LjylZ>GS`3~W#hVhSQCv1^qLD<6pfgLs7x_Jubp0gB3k^0R62vY>z3Nx zi(|zItoz+~u>2Z(&ZH7A95~&mNwK3q#3!#|=apH*@n<5PZ(_gx2+#{#t2GGE0hz69Wa@4Nv0BfKvxp&Y<%&o9LhU_HW(6 zY!b(~+!ar4?n8JF;xI}uO{0^Z^y61GjpZ2=lQIjU;5eV4`V_KF<3oX0@Y;;OZ*w*% zI-H2JNa=B^E`-bFOxPw1srDdG`c)A7*iL;o0MBc)KOycBK{NF4+vElGtm6s_#L5Ik zt?x7mH6Y7_;uLPrxCNLKjyF^F+&0w*aiPhDEZf#vB8jht?2(AN!{lcT;XJ(n$`%0Q zBm66m#ROyWeRa-Mx^AYF;3GNQ+}&tP@YdANMp>p!qTQsOX+ELWcY_^TbfO*ePk9G2 zVeFQg?v98^gVaAtzTWVmLOl@PfJ)}2jV017RunW&3ckn8)ey4qgeJxp|GS0~5vHTJ zqIhA`OrOpyl9AqJgaic*XfwHHAP+aR+_?25Q-%7$ZO8;LYa6g-nOlmL7J^jaW|@>t z1XZg+C+nf;PniEw;!Usmi4MLi2qVkcg}f@Z4=&T=XKcd1&XP1RV$kiOarVikCURN9 z{oJJU&SJ9NkdfHTdEt~^n|JzK_^KwrT6g2ZN}&OmYvmGe9}FWPYWayr2`@1kvZnXY zrmF`aMf7j9$8sJbuqQEFo<-+v-AmkdP4oS*nLeicN>y5{Q@--KCjhQ&53f}u7rBfD zX`Fy4!}$A#TdDK2(bh0Bpk6moDShn8n+m<_IK^NiJVw8z!(?Hj6G!#0Z!`!rxBIYg zQCc(mF)OGjma=zBl5HlAmw;Pi4ip++7DEqRvJfsd*=jJ%J~_OJpa0s!6@xdCTAj*D zTJmIW5#02Dr`VNdoq*-mod@Ij2|~yBx*idY-C*okcXq#2HT|dMWEu?Km;*-g7}x1~ zI@^p~u;Rc@c_;{Hi222?Iwq;j>L*2lE_1dmUcHf zsb9>fQ{a#GX&>3Ul;413l8zg4q%3kaz8*K7Fb-O(K1Wr|gF!#|PEpw&fydBWpdL6f z6w7t`$Ms{l3(TD)ZhcS7!*mD)jPrwFqkd%h`!RzTD*Ys=`M&(|Z35nt=2T=&H?=W)Y#s$GdGH@=@9Hh+k}4R`AAnhV z7>oZFR3CjH+p|UYyYNM3SJDi5HY5C(3p${goj0eTAR6hGyYAs_7i&NJO>`Xcqn-QX zzWZ)yk?3_?WUOQ?pFGKh1x}o zvFEh$5?Y?-;RGOf+FF#pPcJEe0TX&3XqC<=67z~`58{ELrfq#ZcLYW(voRqoG-mLK7kysj5P zygouZi8S!1zB)kN+^C|;ddQ}fv?NegqvXuUevLUc)N1hjWI#dW zSVMP9lr(uUdDtpoYY!FFKktw#{@aH7W<*(Utue6$eOlt@d`;B7Gsd5FdyP}l5g6X% z*e1?VuA=UC0$2d3IaC1y_ix8IPPt6?QtcV?9Y4_Q7E8XFc?93!;36rjSdBk0t8W?f zJ=xMMnvo);8=Iu0bmKZ&0w(e0959^c-y^a!1Ue49h}(K@J^ZEWt@b_}XX%+`aBB(d zTzdVxUmrFK3K~PD7xlFFZXEfnk-3i12iz6cBL4i}ZksTA0cfM0=J>Us-J?-&mNW_u zbw)R6pMLWMJqFY*7QoWN24Of!_D89w=z=Bd8`i`_haqP0G!SokWI{qC2+rU3Bx>WB zQy4N8SVO4+J29BoAt9Da9igv}7err-(bdm%EKL-gOe*g{%F@{#KN5o0eb5*$b(~T{ z&vU}yM`u<(7*D7mJ!=6wnwFG=my&oHU$O7gSk3Om%nAU+0BuJ1ykC3%xSxi5GQ482#;z zT1{dxLi-x<$KWskr@6e?Be_&x(@D0C&%?0o@k;_Tr0|aQJgZNzK~>oBapEiQM8V=4 zGF@+0a@17MvOTYDC0Gwg{K3kSqK{c|K2qtq8Xi%<9i1+pA3ADy1i;tofw&4_5%dC5 z(ESz&{2B2q-Jsi#^P7KMR8Qctu|HORz-%aDEJhNGkpX4$GWs@GB*ZmK%jc+n}k}0!}XL~cH2f-;K9Qnb7_eZ z1nc!tp*?y2gG(-jJfbiqZlXhNQ3KF|2kzqtIi^?IR1Y>a%Zc+V+|f3JODFMBQIWK- zuu(D{Ure3;lEt_onC#LBA!}6hE<7#3vL19ODB0ooCt#5pf@J`Z(zE)t6~5 zpD}oRF`HZ%FJCt8IlW08g5uI42$}m7&6{Idt?jtwF+K%1`{;pnj1UbBUk0z~zMTiS zg18Qu`0&29+%-lK!+hZjQ&KzG7<#oSp7{E|!P2BRNn^02jhE4kZEEu7<8~qdl0nlY zOnm+o+hgDv;^GDsfe=gubZm-}~smoMZuYXL^f zyKFQpxeAff_E@YrMf^xs?CTS>H-YA}Q_ev|joDyF7r)Fqi7Jp|vs6IcjJRz;fm=l^!@2;l!&2o^H zq8j{Nr-;M)pP*MYAfovcv|wLE*6gKwJ|jC!uTa9bpoX?e0b#6x!{=!a*DT{v+!b_~ zj!*BxacYj)L;q*l9Za;#ogMT&Tmd-2{o)i4Z)&tnODf@Hy(w?#15Q1lbgne^N&1Uv z1&JqB7r^hV&faEV`jG#SJIGm%VSvSE;e#56Q;Ww$((XNwtiQe})N8{~Vdd~2sP90K zbNkT2%iTrFB4sUE8~j5_9w*3N5A&sPg!3{z^?xb5^Jcl|;0*-gX3x9;0mtu~J1yoL zdTq$`npF)?Iv!38j%W_@=i>j$gd$J!x$r>hS{Ib9cM?VjEcxSKtZn)i&8d?ogxxvp ztO;Pu7;-&eo_>oP-cH9DQ>!k3u7{oz;F@m=Gv8D`voFB%O`DHCQyVx<{g5`7z-A|D zbQ4o{Ae$VV1j^MmWAb7V9T9Jaf-y2_i(F6tnKPBkq?-XsUm1=^Myi4_4k5LIC>gi# zZ-9WEZGdnqGCbp7T3tpn$d*UF_ZJh!_zLK|#}V2_ESYh{iym!-hzO_Z15WggduAhuan(*c12j;Id*@MKzoHYV&n_}_8~ZbHW6`G(wV zF~lDX4OkXn{+o2N7W1)>mV1TZTw8e@T>;TR+Gi;Jj_Ly1mifR!mkek&8MdI0$|YcH zKgbu<0^wR8CpSb>HSsXUo3-G2RAg=%4J93$6;nV-u}r=?6}P`9v0_(kNU=={&-l7Z zkuh9@JT7Xj8eopJb`Tqxw}E)|lEXO!&eIvDgNr0(ucQ{$R)Yry5C><%sx0aIFsSk+ z=1LOg6b}#4igF`=Rz>N|eOo>5U&iz>2;e4n`!~8`J*ZX^!3=jw(2hLM(JTF@#=+#G zqofL5hToT;j4TWKOLt#f1%dxi9FM7d%JqwOH~=Gtqh7yc z=7~Q?NG#xM^P|Eu-bi94>-Wi-K`k%G!&0ilK8IJ8OBI_&*7#gUXo47eicLd{@XR?w9Z=T5=?MAn( zjL^-yYYgup}PHD3NGKbLIW{ko?UitemT`^V@gAH`{jMQ6Dggn-|>zaE`KCLE;rjz2)EhE zSa|!68ATtHNYkoc*D*|{v0T?(TXGI>#0BX;_mJV-oUGLDGJ#D;orbjlM%c%K<@bHk*?Dbn@eJcUQM!z^I~v> zl1Wc$cXyMfSBk-$kkV`c@K=!Xsk$v!0ytXp!n^mR?I3uIzh_wtbk|v5}``m@mqzkbUBN-TXnL*O0|OHAv`LeyEusp zJw>RL{Yhj6z4Ogty@q`S7Ri?g`Vn4N7gmvdY9X)20Ww(JD_u`&+)|7%bj~EZg6pL+ zE9Ufh?P~vubRzCzwcd;iLN`wc9i5O`6!YTgZ7=BlHECv7GIe%Kj zvo=-}_c4W5@$u(9ylxr;16d^j10KB~(Cr1Y@-L(NmgA>E~w4>;XIWa1LH`}lvv=+Suc?+^hRpXRLWbY>P zpKy);F_{*QL;e_yw)Is97vmhBW0tE8==$FFmy0035(?1B>R*wYNCl1n_m&0s14CLm zShh#pP#h8sZCLvaQlXC3qM1#B5FHUa132@urj7=#rSJzews!%T9z~fI!z?l}&TVFsKJ1IPI|G^w8vqWbo-Ym}spl;C0f??j)cs9Apw#ubq@PwA_*zIAaf#aeqzdDRM? z`584|{Go8M3nw0z^~@;E>tSCw8@r$NgL+)dGv7qpJ@@$$AnZs|_S#GZpmIe9BqAnV zV5m|+$@}O&dYVKIzb*1X>}R}ay=De}UeM#Tr)iy(<7;lQ7Hh0mF~(sohpTQFL%))xLIwFUh_)IhCDl@P_O4(i zGO5conz<9tqN#a4L*Jel?)fs+)~-;39xQ;<0Z6pMKZ9k&ij zASerWO-1s6YnR@V#xF+|t)Dn_X=dt$v|{XE%dApHsJsYh|60p-_ufgeb){J+;RvaK zT_Zwd+@DLfTjz{WQ0AWRqQroS)9^Ybbls==kuCS|m2Y=&Mdx#-6pCxc1E}X{2>9i% ztCdoT5R$g=X*763*{bANq9|nTolj9Bj_#`RJ!8^dQ0{`i-K>@C-%#}}p`i8$;& ze{)w6msHdr&+WEDeTd5*C|yF`G;dlUI&YW42j#xqNHd4z;Z04&ksl7dHw^l+s7dZ( zUE=|f%^ol#{>>yX%}z}@FVX`EJ&QBpRcHw%6#|%jap~#*x;5HwenAX|VLgXhOH3w? z_bYm-gE+5y$uZk=htR~@OB`kn)bp2Su*`<>VjOSabc$awc_f`!z)cyYcO*o=^lVr!-jr} zoEmRwr#|V{ zLS4Ua+ OouyO1NJ{xo^q(r``h<(qHGm9Evt@7c67Sri7Zn{O-re5uU)V7V@08rySZ~GEb{nJLr z#nYqi)`L?}cI7@)JD}s00D)JEKKE+%E1y#Jg&laiG?K@)bJe7&&knf5l-dIp_m_q+~HiMJuUS*U@S+D@}3wsOc0VPW^ za)VMXO9z7n0{^vQYpf8sG)TRlud+Uce`O-t4e2Dwc{%iIZOq(`sv}RbL)E9LwvBM~ z$lLD{x`~AwC1;?2c#LSncEq`@5>lpIz1bqsQtjnxmIF9$&|eQ>UH^ZnLqRu4xu-b( z3~15Cvx6DI_Auol0v9&mbAHCFw1@YTZQv0X&>`%-h#|sur%z!og4)}zAGZp$-bVQ= zCWHXezm@7n&ph7iC$gLRiVA7n-uboVXPJ_WK@aHv>*QYJOL|*@l>kXvY>5*w=s-hS z+cH@Vc&0!ufgTU8b04}JA$y_~m48n&JqGv`EsOhm&$^J*3iY~MA^Kb? zY56yNs0|9c3-ETY27}Te><^+_oubp#E?;x>b)_|&^)33z6p2KGCrw9{YA$m%6Voe$ zU~Si!3Ak}8Xj_HYr;Ls1Z|$y?2Nvt-TtMMlJ^IhS*xQ2$v_C8IL}HE5&GPU^RpA?K zO5_$E?zIs`u+yM}p|1koaT4}vnr&z-Uik_jJk>LDa#oefPogD_c=1vzS4eyO&v{?WaM+An*uj-}7GSj~3`r0*d82gMH z(Kio5A*%1Yxv^-0#pCj&?H{sfmfP4TBr3oizGY>dt_ZtxVZYVvh0K}mx5xXrU6@kRGVdo^H%HxoiS4i$5n{>*AM_~W!fEVf2_k6- zGd7*xie|T_g(0Bf1!cSe#N^wI;V*MhK|Qa`UMIg;1+)G?f}^K9a7Sqmf#oE^{&bcf zEj<^N>7(Rj;fWMY>fqQ8Ad@sZ?391Fz^q{&C+N`zaV;@5!6cVs60-l+|i=vG;5I5z>i1LJ&iT1^y{{ombkfTzyrm^hF+Wz{aKU} zomCc{F1W=(SpS)Q1;JtuXxZtOgy5I15=BFmd;0BPH%_qR()DOLIKZje6KLb%S!)w@imhb4kU0NP2x2rF3THJYMCE_uqcHg~Bz-lxj$#TlM@<)4qnzw=ZD5-5PyU?VH91YK0&y~ewIP@>GDzE}~b{IGWzWaA2f3f0#OG@tCb?0|DYJRaM z5ADkks8|-3Xq9rV@q}nGpvjLZVd-Z529*9`F_S=@laCpy;rk@aH9?_;szF20c z2{Qm@dp{(@ZFvIDdAPsWH7YBO z2tdTWMu?;yQ?xCLqtZvzFx>^>vv`s_*y~~!8T1P9AUhT`LIU_g&tkaD&z%6`eyO<# z_Oj;1FY-|+IqzHYT$6wLNKq#NYQoJXE-wn!Jm*ca|K+2~`E)iTsgX9_G`~%xp?Ojv zyZuf+ER<=4e>eKpI3RX{y|&~6JlLb84uk-H1G5phH}%iZ_+lP%Iy@bDJU;wj_D9*O zE_O=heCNlTd?^!9t34xM8p!)c>Iu1kWt0t}uqf~-_sOI==C~x3= zp$9(Nb~09uuLiVl^h;%TS-*m&X-#yoN#mwcIvI)P0Dbn{m+OW8;tFFGAs6deAf@yWoMQxCSu#5)-}y0xxL|2V*)kRqHddA zPdjg<*I@;k5ci;j5b2R>hR5lrmrz{KM`CujOpV*3;GiCB#`8;&ZIO(LVT>m|Z%t8q z-ryLazzes%jISMUJU$t(U-q)9@^hI_CF7I>zUX&U5P z0WOpyITJno(5TN_zsH(?6z=`s{;D_Id=ZqI#UJ-tIJxjJ%>8ntW z$@2Rg88ehti03}+UcH&vIvh7GI9y(B{6)rK1zu&sBz4n_lwR$t z)eQHpEj!LAC^M5}m#Bpm$5c7JSMhCWs_VHfoK_manc8C|B58SOWEk4|$vnneb3irJ zMVIM@d-)v!@Arl_+`f3{@%qmitN&WW@%N%?Ess;?pC7 z1zJ}62hyC{V6Kx=5Oc8+SoqKsLn^ZR{g=$u?CHaLawx+7H$zyqXN$@R-eNH-LtJiO z<$Kq`%RT$S0CF973>{)wGRZuvNc_n4&LF42sI|#TK?9Oq^B%@13qwbeWt}_VeOGl9 z2`}|lEou~FBBeI4e4q1S4%eLV5vpq+&mLY7Q!{#$z`&9I z{h4r+3+SIS6SF)aJ9u!;^WB(>c*yZ-0h|6 z3J$^}uD5S9#HGnoME16rI)LS8 z)efobG(y|;_&p(q$1CPHtU>=BV1Kx>;PNSiiAA$0U2Cqp5I5rmgM5+}KAaE!K}8a^ zQQGO(ymQd{8(1R(y@d^}Mr16cBL>&;(e`4^=NYU9JC-^#_gX0kex|D6(Z7rApN%!^ z4(K7TRh4pD4UaZIctPWEmvG;7Dh42LBm$lyDb>)r(IYQeGOid4 z=3nsxzBT)E+2d$K+)E@c&t;H8MHZ6^)}SCQOOSRW&~>*@d;FInP(}l``y=Z5A)@<2 z)<#k8O#>T$53c!Q)??f|^?9jGy|9Hoj2A3y+dbeFB98@n!3QdfxT%?`Utowg9SS|) z2+=F6fdP-j=%+j0dCEG)M1smtXCZ|eMu-*$xcm+s*%iB!xWEHl=lO3*On!u|4W><%%cfECGdjQ0)%}etA2;DwC_tl}M zccDxwJgb1Qw|N8+99og4_S1K7juKz?&U8cHc_@Q0Df$v4k1uilhj(FTCUv!wX6$!M zE`R-f&8$Cm&}bc|uAZljdv+QG%KgG-ZxW2oL%ZrR&#LHY2E6Uar;=s|mL2Zs=C4>pRs{^+Va^u8 zN!aHN$yOzNENM&WCPmd4xghLXi{g(^FfZ;MX$tOl+5R8G{Hlu3F)H{5;#q~C7F`DXz=ol(n~^-v|8{##feH#h_LdzGkjZR;fCPIdg|j{|bHr|P@`H4V_|2c}jDTElQ6 zC~7Fu);w~-#xth4>YlTilA)g=V1Unueabvxm4j`~Hg23;%+HgE>U@G~Ky3Z*;PuEP z!O9;I%C+CrhSJD`_VVsMdXqFxsG2IOivi*48~S7uzYXRgfQ9QTid7Rn%{Lr4KvmWOwN{ z3Wg-Pmo|;`W=QhX2IBB7^lY!ssu#pd+(7<^{J_7GTLzEl%ZJ9_%z#~N`F@`JQn)!P1SGE|5rsH$y&j(q=H}xD>zQ@B z(`#;FNZpz$8hS3?>oSuC2!!V)#c&Mbx($MG-3lPx)4tn*WTfg4h_XzFD7bmJCaIOJ zK7QdzkmeeKHN|}`;k)yyd@ercus_v0GS6OG<4a34puZ^q2tUJXO!<_{3reQ&UxVm! z#YUU;BuWW2>vt2(-zQN__3yOSmb6!UZXDtHjl!1t!=@3CXC2u?XdB4fZ! zQ~?q}A~HctIJ)9{6p|Ig!`~{U(-%4ClRpm|$Qng6C>k0(J-y~dE%lq#&2h2oz^ltI;WIhuj&1q(fwiszj_GYMViVy)G zz^!5Be}&JZ$s{i!Izpvhd)f;xg-3CvVZ_KbP*i0v&3|)R21>WfCe_9hC-|68KV4@o z(7D_^Wa?tdOsAtqAKjIvS3CQkZunF89{Op#M0xeQ5`hFOQxzQq!W)vw%;;EtR3T4F zqK*Jl6LWnh4_+o|C(0_oM{+HWf?@P4L;E&DhnOCugxg#}Y^Q4)?esw*`T0sk+~QcW z-(-S*9AAhZ@!s@ibk68J&GrKo{$n?hn4DKLp*Fhiff6V%drYJpd-@H4>1KQtG}Ner zAbi0lPNu&u*|P{Mm&NzR0YAjkNnPMUPXvmD0uUF#Ro>P84O^OyY3_|a5ZtOC2Kg1c zeAZo48xj`rJoWju*R0rh?&IkXMoY8aB%?Ogg`5?kdxN6F3g#X*iUP2OUV^sl^qwch z2v)P6CX`hZ6DNzSF~I(5J)+!g^GVZ{BQ#wPm1 zz(ay*_8Vpc3nP$yWpdB!+JRdl_10{k{`SY@G0qQlKgZEm11b$o0@7Z~tQ9hY&U|r( zzV(qyq31dHZ|*7brLHNnA5mH;QA-v_j;JehAE74aV+37Q+2!DkTB~{`40Auzmj)W9 z`T@1dx(R}c_0$=+m*GtC@>PsNzfqzf!qguTaXU|ElUUie+?1kaZ3yfuoxr7TkBW-s``mfm?burWAKziP*_e*WtP~=#qMf;OP_!7SqW~U} zC2l0-T{XRPI;cdcNXr+ZNlbi0HZkOem%{t_}{%XAU5v-19;Esc?8C}Z+D zg79~3w;-(OFX;6A7+{T47Ce*^YLp04yKaytOYqL-{mzZPoTQy_gevcGq06%-ER5Xw z6Lu~7lfq83G9|(mx(GH%d()!cph3qR%Qexs z;C_w(a+t2;@@P};M)^#}S6j0U@@F{;AlQw=Ivb4}rXkhKH30}fn8yAIE0KO`C6oM3 zjiMt1EgkR#=g)a^_f2lQTe2M}mGx70euzvvOoClqu}`sV-5dmQ^$!{Fn0@iO)4oRA zgi4?k#d*bOWO*=nK#$_-?f&ai;Vb$CSqM!oIlplnS$uBW+waBq0nLQb{>tjR7L*R6 zuS>U_t36#tw+*7D7!6?;2N64&*z?TnX|YK`Rz$TgAQ0#8IvVdD-(77YhyEJmr1h4; z)Vyv--CGk^0{W7<4w{KdxwE}}ey;pLzR?J3)dVY{dt;jF(Fb6Q^@xN1TOiznoQZkn zviF3Gge#qxOOMnmvtD$skD&5dObHrQdYB~h(tg1NFpl4Fg5`yhEfk?azyiRCd9gE# z=L{6n6?jy3D2XEUe?=OkA zAqLXM+pG|c)}l2czE?N}6!C!-9la+x32&Q^_o_9_QuWX!O;htssHw8xcCqR)f4PNL z0{3UGOJrS=3uHZ9qJR(lqtnZ>01@w~I-hQLC-T1b8@iaMr2=WREgqjXIlR6%B`o(f zXjrHQVX7I4FU|+H$j0~`(aMS8XS8+PB|9x}Dt!Ug57)wH)SA2GA{2~vzKLt$7^9}$ z{70_F?x8@Eo;zLc*9_feEr^5?CW6iP;4a}SQUvK#*eOmmWi_C(U4$?+3?G#Bwry92 z|Z=OE8Rf$cb}erThh`ZPlm|naUg}m6}x~xMB>vyzzPT27JIgL6+@r8fghB`5Va=ToGXu#x}t~5Pkigdmdyif0z#bQ&I zC|7TJUYwJZHU6cZajpv0X$a5bhvPSAC@5v3ac7zUuSUBc8~0sQhwMnxh*TUsGF?4Fgma% zGeJj_kvOCL`}zSbHt`&bWnbOq1$>x+hZm%;NFxK=16T1jl_d@0g9mNsJrHWowlB5%!0gDL3VS(^h@$mE{b%8e% zH9U&+pXCkOdBHSHR6Q(TXgy^`G6($SPIY&y!*yJ`*+v5;3)=DL@2~Xc9(vU;*B?t@ zmqae#)8h+dt=$vw3lLuY@gW&}!)=F{87ZyATg|mdOmHzNe+n>c9!$EOl^U|BiBfJg zFdAKAf{d!139n40x(1!p(OwEkR>I1Mm%1Rm6RcrKilUsVN898wQofKU=C!MDAs(%r z2q#A>qIQQRgP>-!v{+eVVCM7NdXQ*|U+Hp(!xH_7(mVvmGPq?)6LPz*_^n`;;TCFx z%Gp4nB#syHPG!}fkXzhcfMB6jxm_1JOe6 z%G)K#m+?MxR8b~=d!vJA=!1_KtBKlIcp;aH{G!p+*#BoNl`A+g5kj1BqpHu5^90mj z0oP%Z&C;uvE_n_nd|E+&2!MvZ(pppXY=5-Z~syeDnAT^`yMv>B{q| z0}W3*yX8U|9@r}9!oto{~(z`pIfRddxNmQ2l zR_+8y2xOKnoQ6y2jxMg9yv1w`Ca0`vOGCp9f3uMtVmNtner*!T@EDjQ@q%qHumsR%KTc}KAkYB!@8qXu71!l*^`xsoEt(@# zdVY68yYd8|#3%CJluN-D^VRd#@-2{fm>ptpYmEy`b$1KWUS~t=;gd3(p5zVlTY>sb zRx1Nlh&P65up^7}ifHPm^ggj!z9O;i7zfs4Knr<@*$|l*jc+;E5))?LwR(wTGC499 zJzXrGB~9yD$5E6xZTAb)xgGv7mm~-#sOSpV{Hv~({9k*B`+Oikz#4CO6~m{S=?oev zkSdbS&ZAkh$|RaZ&PB!X#fHh@5@9JHo0khmO>rICEOQ>ht?jLLKWl;~Bh6@> zPCLvWR*#WSK?CbyR9{Dn`owfU-enpLmkp1xo8QZaDy{=fnU|l3c^EJy(deQ1(Yrh5 zWZtmzfy1SO7j&p{Tjw~dkD6opXBa-Hoc*7gBP}kCqD>%t2`R)zmlVD-$m7##`7^bb zYHzEvM(Yv@a8*`5v%0(_Z+h-!r7@6BI9WkUQBuwY-LvIl}oPR+%%psu3A) zp79e96{VfGl)jjedW7O7%WdPeqe1u=5s;jeeyk{yxqy{K4^Bge+8C;{)ces6?{^@? zsBnKC0bJwNm{5A|<5T0$hbQ8BkI@@PVGC5%qAch_%$H>)NXqw6y+l<09!BVM}IxWRimQ=TjEF0P39Xa(3~ZSAx@$Ek#d zW<;bn_gS27W82e#H50n0HbAm#59Mtq)IuS)F*mi6fcK)C6HV9}uh>shm&2oFBC8i) z&{bJj8A9*VZoz$Qy_1U6`vS_z;&*)m#Wri=v@E9s94e|6cqh}zXFKu}vACSoB~D&M zlT}7;=$8;ze2^Y@)=7+!kx*8*w`9eiNGqljZs=pe-)xjgSwy|@3gT6WvRJauBfS>p zycjET!$N+7|Z271Ca^$Jtn1%9VhEVLchKE!1b=wOSKsiNzDxrb1Y+=9dTQC zoWz*W_80%g1>&0zRESn7{6Hy_d9O>rmn#9Yiy}ofWBs$xwZpRr76P@qGJE*b(GeC5 z9UoDNmp>p`632f*70Lc@RRltS<;M4E=W+GH;r3t<^~ZvyJK;tidSnW^6~GQXURyXR zK4OUJ{FO)UVcvgL2^(@#?b+snYto;TOL6upy`7L!;_wnD6KdAqO3D9|6`F$+Efa2% zSnumiF*&&@#-5(&1wh&PpfwzHC6A7Y5xA+Sie=Cj0#!(33vDr*Wx7>$1A6eJei7<+ zL#Y-JFUY7bmy0)M={p4OPuCH3IpI#yvzW+l$M}l~JERAy1M?yZI~CtG=|!v~4yH}V zD?rUngGf<>;nnflO<9wVtUosVML0z{BF( zwjqa}oblWcp7?SK>uSIThEhyQnrYC(Ixu|~NZt)T4oCxkugS$m%*qO-7mQ)sV;iyANw?BE% zDHgDkH$+(~oykp$t8&A|q}j(!#@ik=wCA!(x;G;z7eRu4^A%PRhl8nPYjVwZByyGY z{9(52m(Z$ONyDG}zbF;uzML6k#}&-(l$e1r&?{gH%~f%BBm+X`YdSC2#Xh3`GlR;0 z(}C@~E+d%1Pf)UU1?80BF{<(^Ma}YoI|IJA%CplsS9t+yzHAm6Y^4X)T<+op-$%bf z^kDzkg`n|GFv+G{xETj@Rc01qmsMW2UZ#|x`KK4*Jx(~Plmx`-QHZ>uA1^UfX%>9R zv2(3>0c0nha+|JH=Jf_guM9}Uq#zN-`_>ai&ev?uze$=U`co8~{d{zVay}bSPqAN@ zzW`>epk0gN8Pa&2d|MVXv$3SWaIUypprAr4KHqA8$0CEb8433cnwf?P)|)4l%K?S)&eIp}OWQ7qUg3*!?- z^HJ&Wj7Ow*Lad4|qNTpH_hwpn4l2|IpZ;h${g6pV=k=s#$SBN~Oqbugw@ayCNx<<3 zr#aiiC1OjSLL$&!{Q0XUT{vHDIkUcicL{Ke8NE(q^F!61>-H!JpuY8egFrRf72J`h z=HN}(0iyDO6Ef>~rG}t=ZUchEOu_hKqOj@jeVUPx3OQrk;$FPjL7@%VI4U}yy{G5yQ(&Zp15{&|w5FU4Ou_s|}D!-UKNQ?U+iUu%a{ z7;o>S%*~v_rON=7YJCNX((V}UIle79ohNqE&rk`o@eVnN$JlN>t*uj5|5R5}su=)v zEf0dHm-SFrtm_o6IXhi}m(0cdr_s-`WrtHQ5L9jWhvD_V+~>ElwV8K!6#fpYaE@H6 zwi}Yj<%U|_5Hv#2(XK3F+ryo7$JFAdxp>$miO{xuR$0nH#tV0pGCJ+xaf4g#%t6v} z5iH>*jnxO)l?1Awbl?k|W^|sy?-uePql*1q0=7`&Hi4iVfO(L2$c>FJBG|i)X=ah^ z7Oxs}M!ukg`(k3EFNJ+I45GbXF5^ZT)>S+xNTv49z0C29AUW*Auh4PF9-j{g3ROY+)eu0M|XL4l=dn^Ez@coZg$@rs@|kxxcIseGyVc)A!kE0l!b za~CX5*PLYE7@Va5~i(e7EUK;1reK3r&+9 zC7)Jcms!)gvdOhBuV;JU_Gy@{5f<(}8T!h3V9YSmC=@Zv<^uonJt&|@x#0quZZuLE z^|%>AyC!|9P?0`hui3XNlxHx$x)zI;hd-XQIKsTh?&$xKjx7r;>t)s*+^`!?2i68sq*oFwvgL!2K>V<0 zHFVOlLA5u)ytO&dQYtZ&=3$4iz@#Tw;<_BEM?n*Xg$P0SqQd;TgdRch&(c<#ku=P6 z)|P0^u=$T?`2(t*Y!@^QoaL>a33ikN^vFK*@ya&eYqfRv^OC$TFc7X_?RKgb3hH_y zUNmck?m`>^o4nwu*w})RX>D6et-wuHH`j)E?wi(;9J3_#{;>~gb)Si7+@*Yd;CVzN zySxG_lI>l0Vv3$JsU7jkx#>e4(NqWE#)%>@WuFE=;@}Y$r>G_4=dAzyLG9G^DIPoOBPx?zQ+J| zDkDNgxmd`3--$Sgj+Zq%;>-h>D?T>5e<9XEj$19r8(ra9#>BuqZ*bezGK;+Hg)N5! zkR@N@AUh>TM7Joytgh1B19Dzs%s^x}96EkX5HWTp$0YyG*MjCq<%|GKBLeOvH%Bwb z5Ej=ZEO0{c$5KaM7T12VNuo*Z{jI0Kw!^{OB?gDZfIMmLi6FVdYSA9^2(#Hy&uJ-U z&gb_+Li2|DJ&AyNBFvRY^$6~#SJ@O92-&ttDz#faTU~Ng<#O*b6 zIu1L?Y_STtRpVG^eIiD}aNjOF_82~DGJ*&>Q$oX*^11E4&`V-glQ6}?3=?Jz>GC}vuWtQKjroN(@KPhhWkB1_88J3*Fy?aKgiE=1I7Ft zdWMY^hxvCop6R+Yv91<>V#PIvOk?%6rvry(v^nt2jt!_LU72pz6BK*-UHX{%o6a!@JSL{yuATd+^E z4yy#(s7?-(RgY-HWM53O?XZ<$x?Z`eTewF8G4Cqmj8{i)$j6e+Td+tv2o-#8t@U0= z&4H@Jj>7-h^S)gUdl@S4sP%3}aGKsytMlTFiK=7Tc8NEM+)(k-B7Z|Mb`MvvcFwW} zc!ZQ-Y0Gi)%frQ=8RNSgrMm*08+=xEi=BMO)~(y&`H|e%Vo&<%AXQWHy9z+z*0ra) zLZasPPPS){H&MvFP<@(Ln9XWsF))r$?}USss3Zh){X`V|yy`9|IX>5zE+Ph!h?AI;LvyI_EDOXV#fmi?@1D zN58vlem17ulFjioqJzSHrQicT@HYQpxS3S&(2%}#d}y?+vkw386x=b-3q@-|YS1!P zSe#ew7hB$CD-m&GYV08Jv!3THJRzD%JrsXt7|%n0)yh^>iOo)Ld};~lo-^H{QvZvC z(o}ic>OnR!JS?{bAga5{W*(I7Hxb|oV{I*vu*;gGqFac04-NkIG%bI~3dxvL&LO`A z=?;L8Qq$PV--^wxpnE-Cc9O(y1U_Kj{HumJ@MH40&nB` zBjf#bpHKc}M1?S>gVKZ`({OUH^&wx!^FLK(A+iXKXFUY@6TIUo{C7O)GW69-!@qRc zots73xYs0P|4d32fUltqrzL*wGI|X?e)$y|r>53__4|5B?He)6^YipF)MomAetG zjsq@2E1_PvpnQML=`6bI+Kq%ZZ+*n)m zg5oy<-^D*OT=eGmmPAvR{Nt!RzS>L~TOW2+DPm|Z{?D5xGhv~%!uzeRa0ptVRK7?3 ztaVJoBI{%zif71>&zaxc9|u};S%u<=!q7GLwtdXS4tk}8Qe|c}(1@Z|uD2#lt+<%r z=ggIC05&4nqy=$#lDQd+Mc!$jr_B^KTS#ZY4||~t4}>PpACrzhBzPm?;`#VIbFwGu zS{_N=KugYMKZu89Y&S|_Rw*$DeeMT4KFag8(nz=_W^awuv54za!O!50W2n zV*8IIIx_T9OlR#$hjaMc4ilYFFAqU8e3h2EXxAN@hTl%$2yy)4c0pB$uvl2{AgY$g zJrpnXk)Pe+7O0G%fKVXu+CADz1R`Eksn(Nc^p=393_V80W2W#Slpw=P9YHwtMV8Db zPHfreI9LTgxSdZZO@-Mj;Q@k2tt1At-$7G{>H z$*_gn^OeX*sht4_2u1F(o9_3F;4`zHqveD)6O4BVi%%BoR`y8!aegzI1(SW5C$dJh zMpgUpXw3>8FDc|l;1gH@!OOY)>NW&~b1*`LGC8lQ=Jj9uP})^SF0P%_m6k)0Oohpm?g=Z9QNJi901#EY%&l|${!YF9c#jd!nACNWPS1(<5?4WdqNb>k#`sA)q$J2z7mpjbRh zdL!0Avx*DdZ+ymF3abz-6CzZ}yVs6I;T;m6%8G15`v#8jyt_-Xpmy-PXiUDRro^?b z1Vq@rY5tveW;%?r|-Hfdyoj7?7-4xBQYK~fvp@F@r|6Ye$ zIKSaAAHQ>$qaWt2&6@|$9t{O;ONOpd*f_G@JBvU7g%j0y1BMOB>{GD4GHu|a=w9Qp zztJ3QH4kmAx#hoYmwCMsx9Tt4;>X|yS;=Fote0H$G^sGFbPu}uj?KyE_esIi09Ga1 z&FjKHqxkwaouXC6;ks_JV$jmy6OE^|ci-sp@CN=yx#3V9!ZbI^)|3kqx9g)vCSaH7 zh{!i^&&~qxF!f2=;?cM`SaPmFx|(Y5`$|=f`le3|L2{`GDlPzwFpCdCR+or&#CUCE zB`Ym?Grwk`tf+EyB4m9<+I~J_taWymrZ60XE!N~8HZ=Mt4XRz=b00>!;+NKz*Kh7s2@#Vrk3Q&<)Qr5yp!jpvpBk|`H67h}6h3K|0gwM_Znw(YiV{mL z_Z<7}(}!U!4O5%0-$o|isM?=FepE3Qj~Uip(3rR#lZ>>|vE4^W`T!^5Pt)eKS*+dB zXD$LDVvx9wM02-y2hTWW-Sg}@%`W9anNZG?(zE>o7DQvr1hk{_kgAF<5TDWp()>GB zjI1fxzmjbHOD*07WWkwJdC?pfwY2jZsrUp3Q1omOV@UElHPa&1R?TY6){+dfv#v<% zxoUBI_OT4-Aw^snh9sk<2#o9cUxyUw#ofW8`3UEahF9i89Q-T~W}gjWNRMsZDT_qs z1*7)F5^~<8yeWw4W7x(WS_Xgq$B1HV4T>maJ9E&JC?WzVdS+?!O)Urp1vP zkdQbOIW?PUML8n;5lkayz%4uhTK97*`of+^#(3sgs{V~SES!O0GqrOoR!m_0d^to> z>P;Q8+-;Yr9fm2MyfrmHDdX|>OL6fG2xm?C92bbDQ7E;Eth|AyKm2My-Pp{EM;VM+ zZLD3FJ5gTT1(SRARX#GyctWuGV%BiTmwKFqg)|o+T^MH@Dy>6x zuS_lJv_2TjVI$QDK;G>M`_yXK8lDZ}gj#j$O>sCX5j1z8C-OklE>V=yo2-m-Ztj8@ z<>_5`x@$hMxn^bsudHiKWh%|c5i|rm(PHOU!GwZF`67Uyc44n>_QqA_NhbO(9-2{vhKS)jp$dTf})H>dn@#`e%}Ll60xd78qoMm)n^lI~pr`!W0ih@K~4(P#2TD0>5>4gODjUIneA*$t=v;gU4;0<{- zpG$k$S;(P7Wt9L4#o&e1JdpXcL!e4A9z5H0a?tb(_A6P+bVfjuHcl|krmo&!O4XJ! z3(PpS6?m9yWt@&!r`rIg|4@}ZBS$d7z>~ST+O9jFcwEHQp4t#*(shAVZkwBSgCRIY zJ{XGK>>|J+u@qQ_SOYcWWy|`rtu9KEsE?LvzNQMIZsTeU>c-B^Q9;nEVA2OpdxlY0 znfgGNq*rln+$#$mLz0u!I(NL8y6Fa`E9gjJ>KnbBN}pp+c)MpI)ehUcJHUg}IH_e_ zKhJ8w64A35Z%qI9j-WB|%TG-p2JLJiZ1?Y)Esctb#>9S?*9xOQArI+jS6;!@J0Mz|;e zvVT_q#0kVSLtcoPxE=j5I)DfV14LmPPOGmnz4_DtStGCa$C8?Pcu5q{js5#iT4uW{)zoGtk$p&(%sp* z4X^j@PrN4{t0)``6Jm_~`!+%H50(UgCc9{Yk%*93#`TK86qPE`#SjC(&L7_p2)_jVy4eT-hv|t=$QzCHH<7%Ax!ghB? z7ud)Jx~p^oWTGL}b=8l(t$>FMTdL}@Yhqb?okf$>?5zQ$v(ks_M5n<`v^whO?U#ck z6Tc0n1v#*AfgWoA?ygrO#H=gHD1l&=H_P?551v*n{2^{LFrL@7YJe%xv@W9rJMR(X zmoJ&+6%SqM2)oOc*dk(qH;(Hz)2zHr5ZWo9es>@DzKo-=pqOtzhWp*DOvFCejA zmoV}ipYx&(mH(Ah`UL@$)}ukMcDQcpE^R%1Dv*30#-sc0uNb(zrDZ@dSz3kz!JtF= zR6B_;03hcgpF|vU25|ySC%yf|F>o{c&$=7Qt>hcLjQ`Ji5@C{Ib9bWMB&kl1KeB>;eFB!0k>H#yOQtehk zp!l~ly1{_r-u2ioCR?NP6b%B#1YgAuNR0ub%l+ujMX*v~^r_&GdK5TsuI{yZ+WwN( zn@v6LK1N@@QKO*5;dBKIGkO@cM{Hgdj_@Vp_(A8>hC@0uy)^7#_>6sZFK*kb4Fy>ta1=9SmK3sx%7Wu=Li{FRQ6? zIU;JRafa&d9h4QnVT4YZ39QfWYFE*~yv&h84jAcpLqAB+sf9{xVeW)Drk7J%EOf|A zvt=%~IDD){-nHU4!i;~@Y6zl2Mzms8n~aYBwy3b!|B$l9VeyzVRr0V#hi8>8Jkg!0 znEs|}XSQv%eDS^x(b~_44m3d6?{a-})bSToIqg=C$hX994W{#&*Ee-6*MG$u4S)ivAc5o3!ePkeZy{Eiw zr%hr|I9Z&)(^1V##smE(LdFih1x2AyK1x`@M`sA7_2FGw;crha-NsEKD*%f#+_LKf zb8Q!w)T4K@Tv{(+x*pRq3eqVJ$+$A)>j2GS>B$szU%krHnX@nbNExjK-dge0H^hYe zt=x-!G$z&o_y^W!2iH2-isFG`wVQq5r$?eSV=hN^P z-HlJQ5K>K6);nKu)(<=slzH-N-w$ze9-Rv*3#q+|T!N)T4B}P%9fAVpgb~?n@|l%) z-?yrT8F)2F0gH*0ahUz~woiKTjry0E6DwfRrcaP$YD^V_jy+W2B?h#{F5!{;=Zw+f zUz%Pz5S}xpjNLji$cE>q06XdJgO<-4lfNZX;T5|OD*-9p@7ymPLkZa&M(JLWArb^P z?~0#Q?kmt2>al;hb_7q<#K3l#1g{{BsX^G58l9F#f0Rr0Z&o@2kxc3pN){jIycWRh(F~b;7hN0}$1}rKYV9B6x>*Etk z0sXG0f9c!GhMmxUf+#y^-SYQXe4Qms?wY?;rt|I^Q)N5Oe;Z$YfKhW;$-lpI@c8gpBnxpK>ek=TT_`PRJ|Lg4bVO`c(xR?>mGVDa+FCoV7vhGqNwL+FmJRiWTl4dN zZp@H}bn{=B!SoAN_( zi4506i}cTip#|WTjsL zF)UsbK8s=d&K}<=EvQU>0y-eD13uh@5}TIHzmm}~Ce;cu(IUku;1YadiasaeZ_6l6 z6%gEQPlBRnK(1zL#HMxH&i>g7ybMF)U4*+8`hlLFr?-iL0mP5Wh(=OKO(7Ryi&zS}Oht5!7d0=_l9}>h%v)-w zEoL{+k41Az;fs3P;rG6`t)Be(M^R{&xU3*gg$yRgD9d0)QHMJiHhVgyMAp}aMrVaxHl>2?a;9o6-L7FniAAtfK zp(GHl+@SU9)*T_^XDlVyw6V4xz>ew$ZI881L?zdn7C{8%Zqxdhj6{?++s19=+XxQu z>mjDY5SNPdT^W!SR+QfB9G|Q0ilh|YH3i z#YxCUQ$P6)ZfLowk4-7getnhU*a?F=?FmrjAi9@Wn+5Lj2fl{ht%?WB9Yx9rV-PI% zEZcpmv(TObs){-bu_gUiQ);Q|S+50U@Ndu?C*X=Q<*eRHQLHR^IyJ_U#D)t;K9NY5 zsVK*?6fkT&$mznOKXRtCQ67Dy2@3rkF2*RLusBw6P-*Cm)Pc7UxYmJYK_oT>yw{9SY>w64%${vn>AS zeJY?W@tTcpF+w@Owq%eJAEJ;uZzf-Zc~4PGs_rmi+5on`i4|P^VdScGC5Mz{=&a#u zr%Nwr!DPF#kP$ecp4|v-g8g1*Z>X$|f)V3_!b1sFpXGJyytM2C)Pg|MRtk*zUx@rh znHv6+5-6MK>#Yi(hC&|^Tc~1MqKQyl%0`JgNwmEQ>c%)uNxNd6o6Lf`=UD5fh+2Ad zQlvw7xuSnJk_Wg~yjSqw?CZ}lS=PoM9C}!23!*;7EcA<}y?5X@uhBMLjKg00 zXo+`x<_F*seSZFBN&_h*j{+}xP>VAa;77x5RCqlCM9XOeks7JA&UG=QDjRi1HJlDW z=G?bd`H)FboJaP5u)$epR=Df-q228E;1_@O9|O!$+G27rA3@d&Zry;+ZY88R!648% zr@Hx@sgm-61mud)cn9MVq{h#KO>4?rHbrn@61e-4`=t96B_Uj9uc(#%4@OB431z;+ zIa$s@uWPPG3;01YL+nLt$z^kgs@|5bZ?(GU&;C%y|qW=bC}k%UM4tWVdeYE?Cy z2u>Yn-=A&gpbd6a=OHeS2Kvv;157uAfI;AwWV8#%4pmLVu#BXlA?MAgeRJS)TDGIG zwOw^1J;Qry4R*`uUB^!r){!nfXRl2^SJkZa#u!l~kVOxn7}?7`BSU$fl2#tLexCY^ zFGSwTlwd_1Izq$#`EkeNzjH`%Liwxf1|XhHf;-9R9<{8bP$m274q8YN$hYl7U0-SnNZT7q1%*eG?S9T|7BM2ZG=XC`=WeQ~t`2Q9yLxDO z-Ke_8gv?^XJ>!2{vXXbEQPDUKfo?LiIttu!Z{B4n9YN9m+bcJxt1i{* zf^u|tSFU#T;t&LYWqxk$(}iS>n(x9ue6oKu5oMe=x8YaS8|zCS0ues2zGS;6yCSaI z(pb9m=2e@w5x}|Dy4t;yPgy5v7Ea{HYj0(bV?p$gg%azV`#W=d8FBG?shrT6>K zqJass$)faowvmewhwH1vr&ljNgotBkinc_h(8WYnbwEq_`>*jnqG2c&ADRsyf2r9i zz`wFi*u%1l2N)i!1P7B9(FecQy>TucLGye-90Izs4E=mL)tODuxJI zdxcZ=lj)HidS?+#h;zT$H)fNfD85>T%aRkTVn@Rpt5y-EfSqV?+bc@KI8hhy2dtRv z$JEoDidSxwqt?AU2R#fa87*_O0>pFQykfL6&08d8UXWAldb}NG6N({&72zWqs)6z8 z76wp1EQ`I~z2qWjln}Yp0pn_JkWkD6Fw@`8Ew^gpdju%&2&d?%!Yty`+Jh##SI8jI zS=M%vR8=!#+96=1D$=c{nteb}HJ3|geEF#e8&-KAx0Lg;?B0p-%e-~yW7&y}3?x6M zSjp{Bx1Uow@=?)%+id80{LrCu6dNo`iy0Gu*@C4(f!k;|DM^T#5rlpZ;kMN&Tf2$Q zDkgN-TjIC3ab+(xN*C^EdWOFh^?ANHWyJz{%?zzX&^q5PDk7=HABwmK!ExPm^t26;5 z=OKSbc`m^csNmrqGqr^WW^0dR!IO*V4;=~sL?{FvSMI^;dn(<{0cZ{!7a*k7{HwkS z${xIh{!9)~5cPg)(V!@VXvq#*miFv?%>CqYHvdihpJ-00oC<8SPIS1&tJ_-)!38rP zGLibv68(VlJp2!-&Gv)ua&>97)x%R-K->MURrbb9kg_=P(x8#)22_}OYg;31$ogBw zb4&iupCJ-&+h$UgOZs%eM;y8m_6>kW0JOJZTwSSx(25cQJc!Xrq}(6D%Q9-G^E zs%SR0m4Vn$=)x7k91XRLlI!|hEfYg_!2J_A%p?$%(PV8yCVSJk=e_Iaqz z!EA>#o(kt&Xfjl`$ZKS@MYQp{@9J)Q_A0*KsYO7}(~X>y^->J~J0guukj zaIni)cF+O#n9@vSCuM6rr)|;2;S3!o(D5aaQubryHM)c%wbIH_J%(B-kTVuX+3JoK z)Is^>c1+(Wp{KS$X&yB}c(I7`pVS`Yb;y>`25R&G=>&(RQ;rLS{wZ{s^^?=Sl%DmN ztR<3B-Y;m5h3^whV=eK*syor^oGvuo+)ptWG~-s#HwgYg>mm4bG;zl19H1 zq8BpMi)pfW_CspyE+p|iHtpN5<>-PRqxWf{=X1$}w%fX8Hs~uWSR`i=*M<4l&@t_F z(9rJDJi)U*Ei7Wq1*1W(fvv$95DY0Z{Zyz#w0*u?SVNieRxv=`@d%bONTQ$h+O^H? z%~8*KEMBN}juZi>%$C?YiRVFn21@bw^%LkOFbTExgb&i?;zAKw%Zo2qY$+*FPUgFA z#*wK|xAvSo+mwmJ>OP3JIhHkgi*$1H)KGT2p#pG3X#mt6x9^SBW7?reKGi2}Nu-MO z0bHw)yN4()#I3o9SJ#Yt?aUkwDpQ%@GPxLSP_b1j%amTBQ>pb>gTN-@Rt|!yR_4QuZc8sOplctKdJqzB2Mi71*pHErup?f=JCaCmv;5DJ zHo#k&IbuPdYypV-#c2}=Uj z8CX^ND4GTUl8?4`yjOD?F6l&tK@y6JBXTqANv;5BwrOWb`EqJim6%{KJe2Nf{x_ZA zrw!b~*8fctG+%n2?41Hbk7PBb=ja-9T?M23iB;NZ405md8Y}iAbH5GnbJz4(?_yH% zhX>*JGBwuanIZJ?BKksTE#5o9`@<@*XPe_9-2sg6-!-oqvetn>L=(te+ptFQ_4jaf zMHIpN5xvA9N)NjN8p?tr%*b6L;of%)lsIlWe&J5TWU#`n!V6#tHMUqMdG`X>A}g4_ zZ^m##>>TX+YQM~^*Zu#w4JK@vUCQ`ug$~wYM<!1`8Pj~`oAMFq;adX4`|uumOA3>_7Ua=;ghL`yAv%u+a6 z;70i9qo>@HN?X)hR7M`vx=uggd4)xgRPdGoGY6?coG;wGIa%K_GgC; zZ|^lED;+el+XVkhMw%@ z<7P?4*E!Letk>IoQfUY-;~>W|%DTqS$U^!6hF+*lhvGB(aJdi^Vk(@iY!R8bto3Tw zgKMxuPM)+JX#Z4Kpw7p!S1EU5MY3J5Zx=J6_+3%Lf1oQI+)Z}VEfTIlo;~xG5Bav8 zalC~I*h1exfS;p<-IT@)y)^_5q(b|kZshA$WLd^pkn*PExp6dcfR1nj#QmztHhmeK4HwxCGbwT-ct>q9(0EO23y!o! ze8^azV8bMVei&Ngi;7`>T{x_IFu)(1Ft=JStGaYP(|-pVpcMJb&xlZ-uSO|5i7CTD zM5?|+L=Plf2iE!7ue_8J)GeWlLu%s*tYh^bTYRgdKQfM0vL<*$gole{)872Aj2R%k zRLjn8AyHTX$f+M6R4rt0=0VB!{{M|gLuy-F>#qM+$n>(N4hhYc5wb|^rnDiYg32Ex z+*&7-CM(+%OsJ$l5YNy@!x3xrB^iU;myByb7SqV!c_(`ST6Apvm5-MC5i0&j{Ijdq+eZ=BwO( zZQOn+ue|nITov0EiY_7`m)=F(YsgR^R} zF*={RSRb-zEfb0J0en6s=!6v z`~nYv#cvnl+{wM(MU6AkeSCvUFIf<{8UKL8RDXP~>Wj^F*yfn{1TGxJ=)L?(XZSnx z&Y4ly>Z@y9S$1a3(eOJ{nyFPRxxHHT+F>|>vhe*Hunqkof+6}%i1OA zdlN?^t!lRrVb*p8Tpt*3J{df?D)kXl01vtQy)!MBS6GO;)~~Kv1qnk=0{J=XtOwM-~))PbdA)-ln*-KXL8cjgM-DH|N@=lbcrqYy5$R4-tXA`nei< zRQMR#P}KD>77Z)g_1I-h{VeK8z|~cWtP3y)YYLTtAy~0{3(F<>ri!EBB)IYrLH?Se z5l67<>80KQt}wKBw|ZfyRdN~AXA=ji%Z@d$zvkhYQ{#(V@Fu;+?v0G3IYwj6{Xuf@~MYtMxa;ilRZsX&x67--}yN z!J&@gfI1X(HupQ;SXC^OynfP|)SO@Iy%H;hr)Po8DZ2j@l(IKNoVVV(btBhmnODpn zV>}-)Wbm&)tuB{Kf4z9ks|^qJ;97coRZ8xTxFhnq#Y_CZ$y%9^|5lLK28DU}j!HwO zNG*Ht%`moWWQJ#S#@)#J^w5c9bePSl<#!XUd-RcBpMzJx;i+2M5_|YBtV)DGa7YhD zkbV}P_)3%v{t%&57tAaB9g)LIb3*ac$Rz=Q`!(WLeqzflt5=Q&hk_B>w)3m`!GQNi zocNso`QUnJgYbwhOEmJ4O>REy_HnmD6Oz8GK>b*Y!HH)QwU3X1M>P0u`7Lae8g#59ks&B$+DJ?@V_JaY7a7%cu z2N*eRbP;(xhw2dEtwB-dByX@VjQl~T`jIs~We9E*!kD%q8z_Cx8$kCgwoase9|8yv zjSq&93&y3^>98t4=~$>+ZHC9r#&;F6T06*1D98xQgD9ce{Dk)lk>kP!0|Oi zOM-`IV^?=Z8QKcl84PrEd-j^{eU7e3t&FglJNrb|%EMd&0SUB0sINpBy^&s;Kwn<3xq;kAzf)Y?!Zpm0Lu;0(BBbpUw|3`yr;w(-NQTQULX`yTI%#EgSX&8zU_0C`ELK(Tmj3zj6w9w z;EVig)#9}3-pIclPUxy>>`s@kfs@lq)L-x6_v7sJdIB>=48&Xee3buDFM6VgE9Mmr z`yZ%A6zLVLd%UsecyF6yd(=~-?( zHYP+B#=1Hh+F1Rz-x%S)5&sfs>(osdok>Xaw!1Jr=@P40N?3YI2Ct9Nyi6r7J)K(Y z3`=Q2y012P-QD|Av-ypIa&nB&*|2Qom~66&laPhlOIDb(uS>&rBN`}ixLik>Rj*(0 zE{A39DhXR1_{EEQs`2Ojv?E?Y%ZC!JGm&Hi`n`4xt=0cIqpZZjA8)a3&i?*qb3jyZ zI@CYVF@^e?GK@8O!1u<{g4Ht)kc>D5Kxn(1h5)MURn->t?i`yax0TbZO7Uw`5~jTj z#gb{1ly8dvek_6BKDK2W-{IhAwsMpTgf>`?67ZT7%Jm@<0l>?|5FY*B8}Tf-y_*Vj zXZdoplCTGgwA%QB8Iq(e#| z4QBM}p|9F_yUzyY#}*6un{i4M=y`7nF#i2(flBhahp8=6oiP5KyZES@E^lm|u3+#0AH(Vxm7C>~V6^S}zUuCy zm`oJcDfnabf**zmI&w{6LIM(#(_maRR6aUC=0aqT`zyuw0sIK3^PG&h)|r`Vk-eD1 zhV+WTUEhI@<7OdnEFA3sHsDZlg|zVkU6_55TL80gByBKEe9wgW1?G5q#|d2` z9PLt=aT2=&OcDhF`!3Y)-9!bw?x7R`x5azYhy-ITYDN0&Yk?Yzp8U$a6-e6MH1vh3 z*#bEV1z{J=HRL3?LaJ20`uHeH7Fel^vt%&_j*rhcw^YQzgZ1#_j^^tu`E=`x3Gs09Ju( zq&U*Sru+pO@&CI>;h&(U1z&5d+>vmVje#=Np7(YyTF)I6&iGo)D1Ni5V3`_A3oG?} zi5L)ni~FCHyzaJ^Hobyydj=+-aLT8n`ZCW;J3llj%~C$4e~NOgw%t_DSpkLTqyHhj zREJSa?U(HI=jcHf?KII@1=D@nF!mF?{SmAznAgIXro>jTW0*<#PJO>=NH)Q!t1i}; z*CA5iiw+o3gqXcK>7zRw^hTD&Lq|giV-3oRGWxd9g-w~p$kdr9?jBOrh`31)9i%^{ zk$IYX_6hW8%nJ8}GBRBr<;y9B3_a+IPsK1%G;Ne$1b;;$Abg|_aQQohMgJH`HnrXn zU)!}7lz&Go5R~|b@&R;XW z!9m@VxrX6<;UI!mJRqKP&UN+#89tkmjPzYs%W|8d2ZuhbgkU5|OOm}Sm{Ct~A+{35 zXd+&x#l74=U8CGXS%A;OBNB05vh!HmFpu5Ri|Lct?&jR%4K^Y!*5ux??Z2I{3siS- zdC^&2qrf{{IgG+954V(%XuD$S8UKQDdz+?VavhVK(3v z-D{pMgzcO1=`BYwqaQt3Sk~c_`NN(UNFuW1g6ao~6VfD#+dSsKP4I$-#jprtxd0dj zB?;X;cqp+F*S13AYgV8qPly&C7r&+wBF&RSKGPTu!1Q5w47~)Lp6}+ngf?ae`=Y#K z=zfNMtjN^($M@fTe;rxBgD|S+f`8SvQph$@s>=vF8pbFtOmBQ5^982E2T3&L1|boo zxatLGQ_{Rl^fJ2+q&(lm>}S4<=E?tGHvmx(yY1IE0?YmPG|D;VbaQv)psJ$W1E;^B zKJwImBbrFuDQi{tvQH6MJ$SJiEmm{bGVeh_CWiXay7&Z7)ShG{vIL^SHg0N=0*?>x z#(W8xY9MHCOV_B(0D?Ai*?E?Dd|5T;tiB7R7!1ze6{XxLxSI%`qgr%H=zKZ=*mJ|b zAyj6|9ehQTJ2OIGPX>jEkg3j8692r53=$UVg7njfNO_{eht8d>fGXQ!z5|$?F3HjQ zCq!W?LWJU+Y>}-N-$wUBD`XLc=cgSQ0VFS1Y!bT0E|IOjj#;#FXI1T#xr!FZZ|Hmo zc2UrbjCY6oc-}OS>o95>{i1MOz~9Sqn!O6=RcXoYl@s7So&SS7Y^c}S%C0^ZL-It~ zxdF48>$kwrzNSPi`w;gfQBlim_i70Vpe)PIB{Ir0?DAKloFURm z4!;hh?mdD%&|Q!IB_Bafs7-oeHwO z!zIOar?#(14aBGX`$>dL!ezzmn^1q>1z}@!04ybN*1>34@ZDdnk&WrH@nMyNWz9W2 zPb?D(I&Y|Y$B^5DW!3P*Po>U!E(BUQeioO)@oU^GZo4!7k&VW1FG=0v_4K*ZH|c08 z&AMIoljhx5@BW@01Y^#Y9Nd59vA>CCfQx2|sUJ$2k)UDAnOrEnreqq5042{a{0uRq zZ1ZQ!y0J~P;|n`gE%SHgU0mJq6Gg1&$cB5JmuTuaRRq@xEYo)o3}vFu!j=wrvqiul zRTK@VN$J&!Q`|tUkF$O44#Jj)y?s-lmZ+uZ2}w9K9_RZ0Zs^P*TR8z_9!MdY8jf|o zQnmcZG1~wUbY`OyOuxDtH?D+P2-0B+rxnA2sGnR^R)ie1&&vPgfm^|N#$p?SykSws z7%lTr7b)%e&w)zs84Z&^Ijq=JI#PTxp3}PRS69b40kGdT5q6>ugb_t2byY*eeL@Nd z^|KOaXJZNir7C7pnLxp$u7%X(r=B?n1VPgtM>H|=t7S~1GQJKkn`?;|G-cl+>bgr# zTp$)*>hGi&w|pc>#?K=qo%;@`ypI>{DG^4=wP2bZtN{j`Aozbeg)p7Z{aI^ zgGOCd=m$3U672m+K!ckAfW&@!Aq^ZG8}L`#SF7B{&DZVIKKqta-soUCON89=jC7mH za){y=8}Tg+(9Wp0d6a57UZ0|~2U;tDFGmVz*cj8@B8K|>Udl0A&+NU|t^ApSt#GlX zdTe^}hgCV)!y;TLZYj%TQsz@$&%436!~lj;%~C=znal<<2KKcj44JLF`61Ms2kbLq zlkE-1Ph5L^djQOU8Y26H7^ir-nU!{Ogsl=T%+Iw-Gr};oQwE^sIXV*MC3;Rf5ws2w zB!-{ zn$YN+r?2!nvN6l3#@x($2fuCsB-kmaYm8A#_vPsGi3w3Bqfw}x!ApMEdKnB6Yd2Mj z?uLF9Cntnfq8h5M=RRiz)84{$pU-7LP;P94#-~590jkFOivk64+8WAfqUJeCsECd3 z!T{tcbu!yUw#R!{$81bZXqgZvQ(Pb4sl9y_$;0|xOl|GqJx-an*&d@MS&AR9>-*!$ z6j<@ql6n>aKc2qjc`J`2@&Tt^Xo$G@042x2>q%pW4M>&C^Fs$tvV8$9NjG$PL>ZR> z`K@K0Z%~~Gk;3B3C0<-QFZ9u3H)9bj6J%V&ZQu75+6k90A0!?P>-I?y_Gm?5KT$EQ zJFs92j5{%uW3O!CUhF{ObA}#S@78+S4^R~d9wS`zI12XP`%g^uz1!(B`>7wSY65i` z&TOrxjIt|*=~fWVCI`JlunR&19MZh7YY=re)!ZSWE7jq1Ix>Oja!1?Y1BVh^hSv$N zLD$?oK2SU?PrqHv8ape4?T#%F=Et!K$(rdy$;ic_t@Q1^%un9)P=$n*wE35ShJO?M zePgrRvg2=%g1)+&D5*KGCD1oOf^1S(Oa}0#B_B{XLM5OHnb0-;=+Dedm=l zW&?ID;4wT+QO~vNMRO3Ps)LP$!@pG30)Q2v>xE*}Z(zs3wOrlx3C6jt(kSgp6T!=ENF9}#?|$DLt=$2CGJAhMccEI?M>BOM@3xHhz;}f` z^;a+2KEd&1or(n_#DGEVFt`Mdzui1LJ!%}Mn*`(n+N37j-z!1?@LxW*a?0C@DngbG zoEjn%)y}TI)VUbtT%~ktL}rrf=gmO?<~`6|36&^-(PErYS0XMBC6|Y0vLlxy_#@|H zQ`g|IGkn^16g5A^OMP0*@@LJ)eQ_=|j7|W&654Rool4A|#02isXP7`@cXJx1yd#NJ z(DN&sBfioI`Q||HEtN=}>DtFuhc&ku1?6a(Sh{@i52Fm+s}P$o`qITTdBjD%>sZGc zxmOXuLoCT7Uy*UaX7_g0JxfGoMZ#TEonr*8-sOu{_%@P8nGIzY`P8pR!MD@@Wg&T9 z_o}A4-EN&{T(iQ^S1qGHPCuS?x7^PX-}ymX<-7;+HvoI4k(FSedLBCRZgO)uFg0Os z^mGr~yjT_JM$z3MJ5kbBj8U|cK3KyWG${uCIy2_6jB&Q3ZP?5p28jTT)td0T&7ckT zg>EDtl=F*XtU23HJFw;J#%Xqn{@gjX+UyT1&yYMI0(mWLru0X)gm873OAe{7G1+!|rG= z!riW!Ws8$E5sriznrm@usv#t|Qmp`L$JbU&)KN&*y`KCWB*Q$Q8L7W$<{P()rm21^ zQq^YSo%6l;;!bj~Tdk3yhUNM_eEYdnNjB$}Hvox$rt14h{0{f>JQp<|9x zug40PQ|eE2*I`?#3gVX9oVRr5Ct;t8RmH-ymxcbx=BPQ|_SJZ(y?5%xpUykGW-p{j zdVZFqq9%&)-h$^so0#Le7}FkF1B6Zc+s*w&>1z^i^p=QF(a(w_Oi|8_={aH55!jY ztN{!{B%W3vJmO$&puHmJ*~9`XaZQka4W=By8Q2YK%9H$d#~RH=5-Rg|xtX29&i|DE zBJl6`udTZHsPlbVfqBQ`-4NZDd%i&6JJnkOdI;VuXF;|4rQGg&=Q64eEyb{wJAqKU zRY={@d#hV*DH?G{j0#jc$2b3?8$EtZ#n&%qfjYu4;-cv$Wl6EZ6(EqOJZg){Il=>>r-GUk5?=MgtZv0FDl8gb{XUv zUwbIXxlG9u1iB65I|28ka5Evj!iEk6_kjc`AxVrLY-rx;1)bu#2&Ary^wq?GPLjA~ zH@XcSNa^U30vKd+0K$^EERjxDy|9vRRQ7)-qq;UFrcl@t*5Ij-2T?)4I>vPAgq%D> zB4isB_R2roc6xE1`jP(Cq0mNCWBe__p-BpKUezEgOkG5FhLj;g1-P8ftBnO-U)?}$-8g0w+2#LqkfH>{PI~58 z_lzERsYo*OR5r;#ciC>Yxh_rG*e^AA8JkPg!dROc6K@ezhMSCWekzWXmq(|6ZpO2& zgR6KzRRPCX2Ii`Y=xOCB53I}coD0`hT?n|$FpB^$gdHU+B^POv0i7O6-guDt9p%8t z2)d%%8`P8;eIQHLV&&K4y3T@BSqo85`D25kCNiJ632o6%x_KI42r{d%Ft`f3a3HJOT1RBej1uheuLXY3~M5m)*mscJ+BVc;JE(jG|-IZP9zynJaZPtrrk_-YAs zxqlIbFMa*G*?6c+6!3u}0m`r6r3uq^kG1j&zu?^(=}mNz1l3St6o%esReaVE9>#>& z3|q*}#pPj=XPUjuF+IXvJ^0S_RNBS2JVo5tk@RWLE2t*{og|p88VXNlvaY z8ztQB<`a=d4Gzd7Guhf1q~fECHBW^*A-$CUi+NBWJ8WP=v3O_Ot*_tM(4qprcuBk= z^Ti6B`kmW)k+u`bHwnDu3mjNjC5Ls~dLSepuq^sgV6j5tvzF64mIV(s4!Vx3u+^LO z9ojl32QA4ylrTcaqezPvJM{kxv=L%tG=xwL;@T*;N~-*(0p?M;x_f2)o^-;$%0{G_ zt1GrYHg3q2b&j#sHG3gZ%X2cRw)9?00Y5F%fm-8>Sea3-6G@ie*MsUYj<6OdNG&#v zERX-bsc4WdN06)|4k7bywh#Cc4qk-n(9PkowF~w41M*u7N+b!(;RywP`uSp0v+Bx6 zP+;wue^Wu>$yOY%4a0b!^;h<|W$CZB9b0tRT)lXXF$4Qv-DIc2f={~x<^!AN=ca`e z*3R#Rc*`9mcimT*2y4rpzJS?BvZ?jgVQWd@7Ie;`=SHisytun5*z3zscdw05Wq?gF$a{QNFQGxK7t4O_$W*t z%)r+RaE?dvQWk@lTx$Cb=icr)Vj+>*=$Q62{9$N!lJjNGeYLY)na*jZCr`aP_rJ*Q zQj2%3fHvs?Bs&h`&sA8PKyJMklBIW@=D@;&Gkju<9ZsT~mA$lWxj?fR>LYo6c)+^F z(FUVYglNEiI7T65wus=>6tZcY(+`W=w+NcC@wjzpg0}Lg{ALgpmuoXmc_F6G6(>^a zT1H%!Oelpog20dsw6ORgsXu!{OZjp;w zit+wQwA${e0>+#O@UY&Hvtw&-6q#wUY{inKQuWT04uxcq%LQ2*;Eg#YWVp$$LKTl+ z-l%$%ZoWqryE)#=EFh~w*#R9_~!WW)ivux_KvLPW(VEU`Exc= zu`ux>Yvu`?GR=AE&w#Q+@gRizwrNc%d*a)Wx(OBrw`d)@+|cL7M*bKdgCQM zkVZ=@LJ_;fcs^rGs*KW{2{ViODSyQE-GWl2x+Q#H#-s6XERg$w1BkS>X>z=90G znC#0Rr}6tJ!!LuWJ%He(&^H|)S-ZmYr%oP7Z?a(%XHe|)%X!0|v>@==ge|&VwNwdX zh5UbeaTgk{IJI)icY)EiD|XF<1=w^h&CS1x1pfg46Z^C`6n5lE)8hAf`L9Z@*G38>E#$Q?=qLWyJ4N$%F``t|7uJCcjERA6`7aZDVoUu_uQ4kiIIC{{JV7} zDvqTfo+&gp_XxM1d4(W}Cek}%KO1yjl`13GGyolvgTR|ryQ?kpw8-PKppIB+4)I^4 zKh5>Ln*?mBu5Z^VRGsjM_?_Olr`j6vh=Rn9ro{Ma!Eh}+HSl=Eb(U|2jpD74xx|zitHeA+VU3dWFN5lpUI8?6k!sVEuH4h=0(`x(xU(vz{H=?Y37pR0$uW~ z<*pP+A;-Rm!m%gN31mP%6$+i9;2-`oy-b-j zGa>2zeXd^C}TVO-6Z*S;%#k%Q|xl$?2VCDGaB zyZO#nTkd9nc>Q%WtfAdMw6S@R$iT7Iv{JY)-^xmfVc@cuZhyNcqB?PReT&Y_7aZ)0 zp+$hj@gnUWkj}$NQeUom6cI#h$Un~Cc8n*GEG0yJ45YbsXHQ4=UouE%RCN>f;Ra8x zoe^j{(eCU!1-_3pzg3!FUCF_N1!%=9G=q=B!x=%XA*+MFS$ zvF7;gL8PESo{f~EFz&lFyhU|+rvRK?6$PhK6~3v{)|rT#j{D6B9IMHY)OZ*y=z76bj&3{(PjW#C z;LC2*`WNunkE7-%5~#v$d{g6R0iu^OL$_1$0Kqlfs4D1gp}dt-<_B1(Vm(?5LT#EE zOg4dCxv72))pqi$)#F_ci}{`MHrBO&bjCk%wx>lk9n#_R_6H>7nW}QO{^7^V0o@f6 z1dUtHz_;?NF>5XH>NFT;z6_F2y{{pJ7cZLK#Hye=(A`0~EM#4|kGJx2-h5%j|50|W zRDEzSd0N*1UhCSw;h>yV%{7H}d^<@sYmVxQ@!6}WaOKD~(~1yy{_8nCuvOK-+51G% zMRV+jSqipPr%++oe_aEA8FA64%hLAJCH_oNTa2ih%n)}EoxlwW=C+REOn^3#FnTqO zj905Lwr1g2v$JP(`` zmy98B&n`jbH&_bPX&Iycx1h1STB`2FkMhl~aR+(SSA{;&4(j*QZNH16WJl(LXQ05z zSH7Z`fyBB4DUjBsRFK@M$?x%7U?!!+g&MFz#n(*U@T}%d?or$)wZlZhW*!m@gj+*X zTzVAP;kUW}X);{A8(&NOiiP5wk>EdnV!Mf~Lb@fp*y-#uJk?~#F~bR|YaT2j8-1zC zl-|z+(Ws%1#Aij9MJOsGGHWRf%N>__6t(^EWr+X3y{#=ODx6i3XLIfsLYpxoDrI&VO%Gk1?PBXCi_aZq9swA`QKVGTxNsBt(;FPm1QJil&>KxhQHdF>= zUbq{UIsl%SozZM6k6c@l=S~bBf!-3N-Bz|Wx~1E-yiiiy9K@f1^6az{F{L8V8a=S) z(C{+HQo_+A(pj8Vyr(j#>Iz9qt=4Yku0t%g9%ldwnOb`O={5nYj~ z2j$J^k0e`#hFhS+bO{%O49tyq%kr9HiDI9~K(%7a*X?3!YKEyH zx1+k4C*8H$*GI2Jaen3l0|8wxxGT0~dko&_P!D9dxI_K4qlk!JKYm~WYjTI!+te-v z7TS4yk?zuvfbh^&ENdjO5TjBhZ&IOafjL;@V{}=0-&qWUQ#Y_W_Ie4rQvw{M3pzqx z7KZXhP_*9(op2s9`U98dS=oNU;$JhghyMc4FA9O^z9nvvu(WzX@8`;3a3#m&Q?{9jjwCgII@m_cZ8U&E5}}?3p8=p!^XM6J!IQ z+JBvDd9@?F1}rq=w*g(t9u5fr$qF#jO9acmkjFv*n3}kBoNQ>JR>;e2;>}v&l_g zc2oy>xKj>`bHY&|yA526utBQd-JQ?5rQ}vRY!b9I-{+S=(Kq9Q3x&}m;yM$NM2ttM ztWfJItjL4^9z@vOQ<8g-WJMArM+k)Mpiil%3FaL!3Poy;E_(`S8z!s(S)P+<05M4BzYB&^pew}?B^1YlxWs`zT_@zGccSr~VLDk!-m5Zv zd|J}aw0cLZS4aRZv8Y$K=MpnzVyi*0%L0+4w@!F>W)V&3`VL#}w~BtTxz-|vf;HQy zJ;ms51AV%dHK{D4YWTk1Mqc!y{|g-*rz&&Z2IcnhH_VIYUx=IcE{ctRj&K*xwoYHU zD?ESnBIZjzHV`OuUgzz0o8D$QGgfx$rjOPk9-!yvO^d~Q=$EGu z?NlY@UeEpKQKpCDbLh?wc|t&cQM>QQj(!@L?F(Dm-C|(??@01=DG@oX4WcuUjH=z0 z%Rb;`_7Q?=)63m_mOhDn$kLe0l>Z?)Ts}YIj9N=`MKW{Ss74lqiQ;c*oG?azA*fk7 zkMtz~#qF_$2XDiLIwq|}Ob70FsNfHRIfE?Y#^>buCHRn?63@l7jo5pjbBZ*mUVsM< zWP+2H-Y9CfZt5s(dH}B_E)N@-Y6NjmEL->=GdVZSHBf5Gkl}a?R#?plTkRaOSD?qq z;Y~N8qFDs=5S`R*-%{1`V!?2uDBe|5amEjAu#R?1OgFQeN|Sx$mvPo6YgBf-J<|wr zZFQYMYDM`F>bgYXuGS^9WllE0ZCx~!2^-jb)RcDSb*ZP_)-*eE9icwl1CnbxXs1t% za{O*omj9!#(^(*){MXyt8ZH=T^f`V{kd?8(@d!=IV`nOx(sHj}7cxe-y}=e)yDun) zuBOL|3Rf9jnl)|dg(dv3px~h_Q4Vn%oD+RkYX;;q6I1^41e?4mWE$_I{41RutFiTl zKZI}V)VS#snDm+(fYE>}#PH6T0UQS2I;*Nb5;&KT4BbG#7PM{w|2w^Y2)fb~=c&^g zgLnn2XMqx&@Ay$K{_x!%tZ0L->*Qd-mI-LC(#a_U2D0LMO_9Lsycz|aD`5k}?W^M; zvlB2ie&Tq{204uYP;$&i$veMjlPP8OvTC`P-qikpmyqWK9cy<}`G4*zMDxqPT+VOT z(qNoAK!UfxV+|S=q?YG?6683Hl{Z8i@4OMAmDs_SV-XE`RcM|hzY8TS2w$TAFIa-K z3Bu8V=ILgvKIu9F!7VuPTaO)3^p9*MgIDGB=V`cWNye;_vcTf-R>K_WUBXhr4C-l{sVVyhYLH(Q-;*)iY;x-1y!dKc~9>*#km#v|jyYdilH5Uckm z2VeIvLpmLWq}Ooz+VpR{?Tm-Vv%O2haQV1>JP|s2ipC26$o;K_NX?6uiKv4T2&gT2 zx)tkzR78ivX2|>UtulH`8ai@C!1dBRUsZAVFab@=mh-C{(zCDMY0mYpa{f)1-OuC} zYmBq22=r^w?zUTc+XjT9$&U#@{9k@Z;xY3Ml)O)2ZkCA$0rOe+dldmgW#bh-r-?nA zQvqLD{`dS);Q`$XK#H-sH@5kBqf*@gL>LSVFnA5uR*%g37bs(p^uH@4ZeAucW_RvY z)9ZyT!F2U8lgskkaz}t1Jf#tcFlimyrNVbQXe%>2;p|B>D08^=*<%;w(gMVAv0d~6 z!BK*q^B9+0w>;a(SeO96SnJSzBi5s$e$B5q1MwCvOJ0wS)&AW55taj2Z~Na{od{n) z1UmU=C^*rGq<)joHC-3d=kr$gl{!}QqU|Ylc58@OP^NEADoSCqk@H|*q#Xrp%>ZCG zM~R-SyeGPT$$T+@x6U;JKYw9R_<9ql?W!$ebQk1F5v>d`KCXUF>x!{ z>H&iFmGu*K+UvY8`On|cc50OiX^~Orp{=TVz7dt)8PugP9N#nuH+2>W0O@jLFJ#%r zsKtD;R~VgG!fEin+AAjtgScXHRAn=8Aa(WRoQRlP0T zu6o~>|0+7|p?MD0dI>gUL?nzhoEEv9fSb_YFEj|B%q`HSW!-ChUqS04>cv69^YR@9 z4zChTrqT&kbZ85_Jxf7ct6})>KTK3rfPN@Kj z9haARU?ApgKLOnR{|f9$dp-t4Ke_}dqHbkgNcoD#q&A7igiD(n>L4bqr3nIDg1nGQ zXgB{VZ&;wrn9xv93R^IO=k7=w++^&XJn`XRJ=s?$FNEJfqw|Ch@NtVvTo76^RUITa`4t<&kQJEkaUQ5RieCLHftV+H zB=xEN(*_9C9}j(Py#l3P<0u&L#nMbDbybnDY(GlJtxucK^1oO#Mrm@DD+c@m&7;R9 zH=$BQOlK74{P_-)e^W5aMJ>Q1@O}JWL1MOsu|-T=90@8?_I^~ZFH7Q3XMhyZ^l>Il z9VLXoDmq0WK)tCq*ap%$_R^%}ZFk@)I~oAnFJ^ul;h9_*ycDYwFs3IWO$h(y5O;h7 zeCM0QPS5v-A08zJQriEBk)pN*`+#1zCOKxXyZw%Fyv;z3G>;@>wBMl2DuINcB+Ro(9T}lg`Lj5c{ zSkyyCj_*nyVo+z38q_FTVg$4^_!E+)UVbaqoWThJ0T7nUO@~~{DzOTo`Q7URO86*@ znDSCeU=b)d&FE76P)0hd{)OroLQ?HeH&kU_odC!-ylLzM`^3ixCOfuds1CBMKDIvG zy@0q;NJw_hA&Rrrzk0u+-3q89g3PRuDah*wElfc$OM>>J@z ze)taSi#^n3V=OC~<8brPW0LFfsf*+!3!|d^bzq3Y1_EX=*qC?&K!gKYfqIMV0d8D4 z=`}D8s&rKg4)NhljfSuF&c^zTx~9CKAL_vx84=3Yb5ZObYJRF@Br0^HeuK2xC1_&5=QHr+FWy+m6zwE5#vrC=)Ty10FScTS+0xyinweZt&Wh1=oA?g$>(2*Pp zNB6s@#NNo>tWF@0DeJLbp6fI(y0G8Tx((hYh)pvZV*O3)Ex}5ww$)T0qoP?VR7@`> zM~^A}1-}`j&+{|l#CcEg(XX<@i&|=+ibC>88vv5|s9vwQC$%2B#`58rd7y{Y)S>>R za!H6`F8E{7v>99w{)LFpD*_Iwi=zxiH*|8MJNy`}%cA7Rhx zV0~(`t5XRBjx*lYjNESsYY~#$r$o}{&-EWPJFP^(3JZMU%iz`;;aj7QUQmYLKJcSf z-%O9EUB#skJ8L-el{&x!D?t&PtDC2urD^2Qa2B5h50_hlc+h0NC@Bu~CkkfM8lI6p zAVOwoyXehrUj>>#<)9AJ+O$ zl&Ffrqy&kU!T!AjQ4F&vP@#O(;*4Roumm*w z)+%ae-@4IgYd2XFsPTXNbQ7@tI87h{&v#{A0c zN<9z4b<*IWcPVZOnm3g-aQq2k~c}OHsu~F7Yc( zzu;Q>|H`@O$!D>JHBsMmd1o)>ua5g7X#O)`aUh6Fe~N7P$mBZv`!!!=2hPxiXmvLs!CV-rU~H|d9^73^P1x!hr1!2h$-+yi2Xy3)PSaQe&V{+sn~$k@`>*9M2N zO_#!0&(6~_e45I5=EJy{GR@oqhJDq;8oqOzp6KzxG6r&d24T!rQ#g-iiWjl86oV|w z%Uh%TKf(Rt?N-EZgV)lXdX{Ot4*3i37KUQ3A#LBMaX}5#s91W6h_%P1Vjs_b%(i+E zvn2qxUa>WU)${DOL|MV2`T`c4Pinx=+p+$xnxZ}W&;-r=wNl!&D}&bh)~wiR@F5!O zEt2ZIl=8WZ`zqM+nIcw(_+R@!pc z924&g>+>%$UE&3jEN`K~J<-H(5VX5R104fMQ{Zn?)E8e(?@EGmAXdy4kfF2DOzDGs zCc!j$0h6#@+>`Yph;XPNp+Ie9qfEE}I=8c8FU3r$Lb>e*GmNk*79IRB%)lE#c}n_> z(t1}y(vic*@!y^k%e|?JTRux06+3LE0S@fLA+vOG) z&%@b5P&z^O zWymv~`bNKTg6x1tQZ3;Vu8$&;>`>l3I4{f-N{{@eAi+XQwJm`Nvb602F7r@4lAh|F zfMEUhft2X_)@+8Baqz148uOT2u~##JX*O39at06})qHuK7_AFxtBu*$uKio~*4D3B zo!=mak4(n~fm`umq9vWfu;PJW=KBm?AL_R?1l5)MY^L>x7jLbEmWSuGjtlU?<7(Fl zD`ovyVZnJ`PCL~Jb{~_gZ0~x1mX)n}pzzNDSZ1rN7E9MR*HLOlK}r)g&W>R}Bd;qD zC89#r+*N|XP>7t(ex9yP9n0h1Q0m2}Qb_CG{+6&<-|$F~vA~I(elAe|pF=7Hg zg8TQD?_(Ai+S`orS}!$6*kN1ZJ?H^bfpxgWrR8Ns5-Ol2KiCxdn#Q@KjlHOfv8f(| zQ_po(>0h^1m?@IeUb9^t3MT2qU1JyPI^4(#g4?HBD+IWQiT4-rnfAiI^M%dI!?`ij zwU|yTDzjZW_}1k4^WKJkJUkqZmvkSNBec;-UIM%}Y9{xF7L03ogZLHYG8dFYe;|^v7lP7gexP7|`+gtFU$Bkyu?Le?mwRr!lspSVO z2C5Qyj4(8Dj!xv8Vc7U3SMB5}HnI?|ffH&~&Fs6~E35-=`qhti8WY$$=o&^bR%b!i zKTxF=bOTt#_Mm>4a)+c21tMB=npt&-*u>-&l1#+B(Xga&ho+xPn)ul*5}S}vQRZ+F zjNPYT)?`BLcuk0aM*OMw`7%qNOom+zh^wji)QQYc+;YqLvEHlT)h83!KaD_ARNcGl z_bP0_rnTfYa-Xcb-F}8(L1@pp(XkO_%dMgPK$^g77*pP$W4LEY>N*%i1~YUnq!Wz~ zv$8d|n} z;dLzF9g`Q;Rl7c+j+P|0{qLd*^xT$AxCSh}`IORL7;NAS69=qDC`D655<&bESqBsm zbybm2Fx7PH)HecmtPB4VKIp$w%*^qkxbkC;%A#yf;qJ^8zI2T|*NwTnvuosmQOKvX zMC=l4@OR~X@sXBgDIBA5#VrZAO0g>8@O|Bf=+owTcYTs97au`Xh#+p)g(t++e%uk# z1`4$-ByY=z2tC7Q@QYS4n~QN_&!$=dQ%G91$q~53_PpJ1Om;n;647|`N&r|N`!*XD zTppFuj*}3%GyM8k9CgqpM;4H2jCX>%!CrEvW^RX;snt^v-hvxFGO)ya>(VIBiNNhS zB@-Ny2Rn;SB6F>V>HQFKo5b|P|6b|Te3!=c=@=C#nYE00lD2QoYxp3WsXf=?PfV7( zqQE*a< zGG?@jl(^Hlj7LJ==|2%)p{uuJ`D`QcYukvm{*gWILYWQe#$zE$$%&2o%(^i(3;JoW0=i~Whc`y{h=8vrr!&qpV!k^H1!xB zO_NQxZE@%gLjw)dpbz$)?@P3t;fkU_4A>9lm7WEyH3S}xOeCtu+@kh*8(ts%xNS#( znzf9O~kW9rGtc-TC?f<`{(V9K_{u)}*D>>Wgr(z`ZWq6E}_K+C7CNt^#*6&5vvGaMJ) zcp3tYhphVP@9jBwy>HA|j~DQ|kx1tkbtomAkp-h5LdnI!a(58VS9>8c>j``K6=*4v zvC_#;&HYj(BE8gjO~B%h&5DJmXlS55821E5k(c`hbM3|p%Ek(>dtxHo=J=!QGeMno z)@suiDI=4^Ek$s!xsg7zJ&adJO#~LCiE8y5MXwRX5!GiK$!mnH#i_oQTo zX~V*$K2w1gj%18Rqvu8nD%nF~vwo?6c3urYUE0Tf zR%e>=fZ$5aJn)M?6r!PTh5jxve(geocYa^ai`}QfX*H^NQQb#7b(l9Tb!M>v;#=lo zix96o7-|lT8n5l%!f>tUPG5E(AprKoQW&V2cNh)F%z|=NYu!TKxS#iH7e&sI)mXDS z7FdQpi_uLwzLd3(U2l+{9sOAd+v|KGo43-p%HC_HU6__BpxIsf1@gR>(Eq7m8^3iE zR1l66VSiaM_0~xJT@PM(#lsS>550{N@!v}3XVf0Z_GPzp+A4D&)%(dT*$2moz1U^l z7sUL@=i+j8g~f6jHA}wAIhH}GUWskgkOZ|s;>Oq}l-M>fks*!S1X0RQyzD=&U5_=) zqsh6cego`yAbfxMBMkMaS=DauFMWYYm{dEE=(WY6zK9B|)=CQ!4Z4ol{&ZY624dtX z>Rp!L?#!#wo$3sQeLj;06(oeK_P@`N17~ZqinBrm@8UfxEO%^1Z&Gm#f-H3bc9%um z9BC8SuxYF-Tzu!YE+LHA^=aIIcdEo!BK&r-?Q8zkV5!LXncgIcH2j^Y>pdJN3BCrIB_q%M`f;vQLKbE+BN`{%@|BQXuz$ z!Hq@uiasl;wwBpr4RJIJ4T-#^kU>eFNH)Jzko-$K&JPNWpw5w9hVaPuZR4rF z+GPf@em+NdZRzpBccFhqjw(eA;H48*?#zQ|iKD7xfT#qt=RE;c1fX-7`AwvSYQe@b zu&3B?ClR3g3nA|To<1)d(Z;lFPh%ejeco;WN%;6K)c7j%942eX#uRpp3|u1dkE+GX zzh0akO#BxX@Kev&YoXSVy_fhxC2aTzoU;}}Ud$7k2t|Emb}s>blYDK*S*({l_vE)X z%4x1&D$B{#Km8;r?u>KDYAWtu900R|a)He<5gBEw;ha{}VHDhUd%A2R5TdfyFie1I zD!tcm^z~kHPc&*}U`(bY$`e0^gt{5-n^BmR-we{8XlkOuP z!l+Bx7KBPwn-+qa{7wsr;-W_K`mjKf4>=C2N>%J;sF^QvKwEu1KIM~Q=G#C)ocY1_ z3_TlBfSRP!eenpbf?(gb=mxh!NIB1^B_>HPs9g4&5A=So4KY`_D{7DhhR-?=jmc9q z5K+F$IHG0BV#eps4BxlUZoVwSZ=fq#JDLBw_~@WcVY>cR-%3D~YE z$*p}-U@f-tcQF+FHBGK`W-;J&dAtM$qx795UjU&_0uyo`ybWc&8gKo`j><2%X7$b)F3@i?;$LE#L+Tcvm%A2)&9A0*0fmm&|S>RVxI zq6Cn=i)`{yj!Uvv*n|r@`|;=-g-%LMV$L1^ZGP1*_K+53orijjY|U146XnrsGKh#L z?3k`Tjf1#i?yhUAv+sDZJL{671UUr_JNfEF28T$(o=p(jwc1>7(UOqmiX-n2wgo#sS&o4uX=!KFBln*}SD;wo<7{iy4dEFk5pdbfOC!|K(7v z|1`q)BvXF|`U}k+HjHUn)|;vFztJK7O)!fx(83g@f$NM)+Ww7&1aIWKp=#K< zmaW|9YYu_4c&0|&bDx-`Y_$ycM`wI`zt8VR0!16Mg>#DUe&M7QrLF_*F2ikhjNzqu zI->^87Wd;?=1Q$&GRN}Fs?n@Lx<<;vD+(?Zdtuh|4~gA>>+3ja93d{X&54&X2TkJ0I)rK~=ZV>u0XIlA7%F*?wd9)vzE|aY|S!ljYheYy?WuSFsr#1u%WVg z$bC2~$G|c+(rQWw9F~0W9(q3!YxS<}fU(EV`YPKY2CswMrb7~P$F@%{fNY!d#d^(c z6#oGUG+1fpjbSB&lETsQlq%(87YKPOWwq!ET3@+R+R>Qr=TAbm5Z>`o8gXf!&=VA@ z5c!5+IkzGR&(FDAvULcZiqLQ;rK1n|TYS*4gZkOJOQupn202#u!>sSoqoP$+1nd=W zjeX^qFX3yD=eIL;h^mjt{zyAT%jkMp^1|>MexQf5Vg#=GyBltK^A1JFZp;CXj@*Bd zSUgU1lo`nQSS%gY9s@}}>rF$JRjps~XCF}Dtl?Xzbo+R{OQM&kevYJr_1vJ(`Oy5* zwf|PM)U&Sth>8L?UaAGSroYqg;37hMPxd~=sU%k>k9PI2XfKiOq0L?f7fN!Affs79 z0>D%k;;U3Bnd4qb@*4o4(%tEb{f*~{2L>CKX_WRMwz85>x9A4=$r*Fkb62FmL}-Cp z++-g=cO>HOdEML?yHi@e7Jj={L`R(c>jqn1PcQ4npwZ!`azl;%Mfv%9$JC0ZQ3s9u z>APqtJwz}@GEV`;c?pE0DFcUF(9gpnZQ)5UD88XCe0%2+hGgFVu8Fu+JLfJ%#aMjR zz(fvp+O|jRo!zCxwSQA`Lhm1z&41jSE=LovpNwv5I3Tz_ro_RQgeQ`!sne|oCPE7FDJlROsWWRc7@xA*oLIh->K&)Hg?hXhyYZ3zr; zi5yCIGw7#W8A@7xXC4!C61jdHcDU%NWk?LwF;Eku=u?pI53myS>?TAUuW5$tSQ)uO zyc4CUp#Ll*T_jA`s~SmXI>_WUyY;aZ9s2tYELCb0w;NuFGJsgOo+l*FcuA{YR3%)lj7U0YLNI4^*S@Z_!hk!(flkqv0bY0wlg(8c&^V6ek@qKCiAZEQ z4qhcP9I!@@#)xJ#3xBuX%l*i5z>H|}dfDi{;{rnj7oBi@#6+6Vv1ovKC@xR;u^Vuk zTi=TGA7-fOKCD5z_)E4VuW>>AAf5O^kd}_>+h8Lr?Q}y!V3qi%t`X+q(!v^qbDlI1 zwok5v1xCMoUZPIV-*90a$AJ*$aZo~$bTj_jPQR{yxIuxFhD7!{hA-wt6@vHxN3IQX ziPmn#rALVp18=ebQW>~z>klBY@MTYuDKs5+cuND@6xsg{fT^NeH?bl%N`4bB09>|P zQgGI~#|X1;gQ1$r3vif~%09($?7N))Xzfuqrd8yo7sU#Yq6{pNCxd8j=WRq`5a<4G zh#sMW(p2^cSkePVeWTIfX>%{!-+0c%$^HN&Dbv~$L7b*HEKiKT3gR=IRf1o@q#vr!DNssL_RAECRG^4zpZpxoS#c;XHT>O>oVzI}x zY*`!`DZRJ^#jO{_f^;D7W%?PMDB;wqM*dvkZ?xA+h_ukE+n7eFsSyxq6mORolb77@SKErzMNiE4MPXRkU^xQBDn7CFQ4_<6t&G$ zPm;`6|I={q6ZYTuTr*|`9MGfQwh;OjD490{!jpS&D{_#YA zYA|`}LQa(wHi#A)##GbhhO&~alfC6KIuMZek?fruNYij;mXstvKVYC&c#sEdfU9Y#i;j2=c`|ZNF^;q!v`xRp#Mv1#NC;m9gfIs0c4n&uJoh zql(s?9acJSmpD zS|AEF*`fa>-^E{8UMx^i?~{KpeDC!}P{8a8|EE*^T?O%6mF9Z~M+JJAj)H5KM{}B% z8BNp?3Mc%bGTD!bae2e$2B{~9{s~gOw1(;;Yg0}Z0ML7h?DDiOfXn2(C4iH<;~<3Z zDB#K$w**)qG6`J5ue)$p%|Fe z-3Y6>m38)tdJASu-YxBV+|>u%3#pK0QN3VQE3R={#TZ1x(CHMk zgtj{19t#JZDOS z7eE8NA?sbxWtj@whNymgz;m>u9*FaP_`efCFAF5nQIJ7)uIY`EzvBS}Of#Qn*r_w3 zGCY?12v?vRXpS=+tl3blup5c!e2FxDlgmhTomk;@GZ%b64Z{-eh=HeR*RfW}(y6eP z&!bNA^KkP3X|s{W(C2ZHEPm~N19&cvaurZ%E)SFilx23HxwK z#210LeKpd@1WrvNrsB2J9s~JdPoY088S3H9pyfiLy`9 z|Bgp7*i2FX*3ykeXup7{GPJOa^*^{4=5tzqk5c!-xrisEhk9s3q~>Z)hKf$Tp{2FBb)8-_ zK?7RYKUgF`6o4{szNMN$c%25tH8Oamrj(;GaZ?BIJ0J3n-tFs`p6{&x*Kk5I!o3I| zMuq`j!&@*yOnXq1U0e!htS8$^M_ae}0R9BZ%R=`x=7UkA`gPr`2DyNUV$f-Pv!~=3 zanA}ZJ3RZIR_oT@z!Gp8XiygP@WVAsc7jQHo&;l(?h~i3t|S}QUQjE|?oy+^PW@Z! z6D<@zs*+I^t)b^3*+>IkrBY?N!(`N>jy+p(3{t^70r@#kAPdA@1^6xc{86O*M5xJ}f^j%M zi!6Cb^bncit=HQ>3$bmFN3y1BkwOLh+`9R^TaHVOjwmxH+#f4>=$ua^b-=Z*;5Q1| z&18YLaC(cXnvJt$IhBoJS#sQddY$B7;17O5g~z|CorI(D{iI@#+$ja09LCc^!~$G` z7}wipU4le$0@-GOlc|1VQ%s2`gbXgBtvO&lCBtgX}jX2ruC0`&UzyQh%64%`Z}Z$>8VAMFFYFT~O1m0#75ubbpS^8?AXVRBR^6;q8oygXS;uG@+no`M^IoKoavH>(_j@?^ zRwSdNf&#<5&=>h6eWCM?K(p9c3kjHYV&e@q_Nd{L{TPm!x7>v`vJ7dr>PRij2gE|5 zMxIwSEEh0Br8aPP5;0r2ub3F6fZatHs~;Fq!EituJLJ8R)Q~x)AD&uDc~rx}U&h66 zp+D^#UC^Q^Du2n7xC80@rihYp+!XnY$0pnr__`YrT|HfWG}^FZ6km;!2xPFSYT+?& z59832N;X@gky-HhOfjM`@MRKHB*GHcP1}BwWK9=}o_VBFHkQKo$#QvIvw&Lrr}22< zET>dZKs{-*idvs!sz-JH_>0nrki&P2$N74^nn9O}lHQgZMX^o`oZTX(M0SSII@nx# zW?j=-{wP(^>g9(QLA;>fRem#%O!pA;HI~8zgqJM;b6HLQHJ6p-v5hQSjvLt0lk}n` z((@+@xDV0qKp#Pdb{qa_IznR2UZXUvJ#nhoW1eRjYiZx&|G{Z^So=Pceuubq)fp>`6Dbk${t(SZ?V7twy zR14;+u#M;&1YwaG63-5#sPi3*siUoM7rP%2e&|oI90(G1$NaShr-_7R3awrP6qY8K zeJCKf=|g=Q*?M$gry9Z%giSOZ6jwFRB@-(rMHwEL`$Oay9V{R8ZL;01WOfM&iPGx>WE zgaH{$>6DS74;mcxU{*$h3du*c(ScgaMT0ik>&U+)`!)J4`faz^N*KUvG)G}F7B~!xdy2_;utiwlHkmivp}$lo)*db!qc{4t)7Ta zUe64o6A{2Ft%agyVtF>kv^k}Z0T3DAbum-JTnFYwMDLbW#^i;lDw8hEz7Sd>j5T=C zF@%wV;4kas^KYeAJCOPVAgPDxGG>OtV!h@&{t~KXwwcrWVuHZ%<8-Ke%U))jjFd2}$yjY|AxLRKz-!&k-NJ1(4KrS{O%n{FS|WHj zPyg!d{>dhSZ+U+pZG7Fk%CYa4GBb$}caK=9l!sr~4#jr$Dz!nmt_`8y@o91m8(~`9FQNS|A;or+G-}7;}Fd696fs$!{9J`l!D5 z*P!!niyWI66Zht|lz7cHVFbkIb7*vKW{|;==^9_9vMquI=5LSq$@0g5)YY5khd1A%H5k4|4)g25Fm@1oV4cU zO6TUgCev5!0-)gO6cExt0wcVz$|rcFH>6ya4>o6qS~f}L<3i8Xh~=4J(U9Oe4&9e= z8dL9%$U>YYh8_F92{~h$Lo+AHQ3&>Wor(}o!{KEJ_nqMa$U5@F0m}12m`6kqI|4*G z*1_Bg9?N}@r?NLLJ1}w`ZP2?~$FAT92GOqlqRJwAgMNpGS6+keRVTGM!NnY5mycNd0_G;76mg^jzR13Ho6M zsp$PqTO^&n%L-?=3&g>1SU<*eD_5Fbz#|c`c^yNK^7YQl95`Uil0DkfLrDBp6JHQD zpagu`;dZ1Y^RJ`!=7$Hi@IO0S8dDjmrNPN{5xjd@ zG_(YLbb(sAbQBONUmM;io$4*f1D^c$_;mzh14a-QC98D=SVI@mB$m5;+UA)D+6K%n zNQbbO`i8`?9;h>};x=ZiHuBbxRD3Rq`AmKqDrC>7GY6Y3$6>7_MxZhl>V5(3@8SkM zW2A)_#{lkB6dos2VRL92x)>CgqX4vH_Zd2gD4d{@=X|pH5>0^CY=iY_@9(yQ%|cT^ zA=P#eO%?hNPcSs$q6>xXT}FZ69T$ccU)xUOYM61)m&b}sFwhRAN2fmKjCX;X{Q<@M zVu}g;r%hRAxFn~!$i@#hF9)e0okW|0(I|(%Zd_l5J6ZZgfG@^m{Lu4+gQdr}E(UjN zqXGaKXAvT#r`K)Hv91ih9TxZAnbCMIkOBQq3eST z!jS=kVEcFbt&_9XUzx(e_?A%abg@>>_eyw<#;TGk6esh3l)K_Z8ME@p?9c(Ux!t9sReLWu1kK7g*}67RX=2l?>apRou3bb7>DB zPU`&y`i=P8XACwe8Q_~XT468pN1^S5luNn&Z1_$O=by{w`_Nbi&e}qRw9lI*y-c3# z9}XpelNBTI9Bh2F1+;opU1tWVFZH|t?4X?_kpTld;bWG8QF53>Q6zSkR*N(k*eq0n zU>Cml#_?#^+Xu<9yFU9kAu*EZSoE?SV$Kfc2OV0yXvN+=S_@^R&#i~qlP?@ag9X$1 z4+@2T1?-x4ZII?i3MYWOuwtJMxS_gXxID`p6xwM> z=DT?BMoNaeMKBM4$^fW;6se~NkPvfUJ+~O-3gLvf6<s|@nkAQ5@}NaoEh>G6Qwy?V7oOvks=cc3xM}Scc24f&uO%%A*a;#W)rgX zWqa=R=XY4%vidU`3i!FM0n0!^q7J?Kn}DtTgYQTx#BO*!efxSbJ?Ue~_1eYMl1*@g zU_Wl>eLNb!m{bgy*k!d!dtqN4e5S%fENE$3|DxweVtN^1IT`&&x0H#x_3VZ07lV#V z=mWNH?Mp1UlPy7tlcw&8w8D`MXG~dOvmRG&>4Q6@d@lA z93WO+uAwLig*Y&c0IOkidoNr05R`v{^S(7QQCmCap!oux^3<}tqd#*TLVO3e2Vb0q3vKbru~MVBSpuht^(L7CN8wy5%j6$l(b{#Wt{i_?Ad!>Q5hb4t9I%Q2*LR zI^j0e;#YwQZ_|#XI+cW`)cnwU-*$(alXPne>|Idpf-L0Vf}A@JS+Cy>#%*<}YC3aJ z>O`HYoH@CNs`2DtLb}B+$A4#>Ceqj;-$Dw056HvkO9FQ+gPdM?j#m#F-vP!C`*Ie2 zbMR&1Ejt_kdO;5!9^6J#{Z^FB`}d-e;8Lo=mfg=tv~UuvbAgRRD`K171|PRBf#@x;Q4&DHJj)J6bkxT-S^DG#wW0G)fc@VCfCpjf)~^e zYlgJ**pHs|^y`-gQ4SX5Rj@`5dv+u}r7OpJ>~BcVmBAv)W+1q{9faRF z!05ybC5H$S${Mf`n8mX>jU4i>ANcE*VHu1l2!Vdwfb6?g=ImK`ZNlt&)LzG#UKcDA z`GvL)KZqOtA5q-_TaEnF)&C|HpsaaXS3nj52F0na?%xWp(}`nBMd1e?lk#F-#82W* z!q10Tw9u`#GHuds_P3rRFpuF!1Gs4nz_{25m=Q5`nT0Y2wXf_@jEh^bCu`#BJhAsj zS|9;BpK;ZRFfcE^Q44})e>{fQjqq!qqS}BN`5fiUgan4>MrjQeTxr6JW@I`UK74vA5K97 zEurAfl-7Hz+HmZ8tw495q4sdR_mp{$B-Y@J=bf*H44{AWw8s4gc7@zS8dm0sY8sE9 zF<1~uk!E`|H{Ixz&;&5Yg!Ll;F-d#IS)DQL00OE5VidH^b$!=z~-h^!GEhb;|;euc-(KudA`z&v#FNN&%?O(IsSi1JM*a&BkygrgGVC|qNFe2{fJPFUjeL`2RqZsK)cu2* zB00koc16JY%E4605X3C`RB5qTXW>tC&@S97@kVadW>yJY2ogHBVPy}{mppbKMZ&%x zq+73+!AD7y?F>u0^knBH+|;%WOv!&05-oT$OiyEpEy@T&Uv4awl%sUuxT*VU#jX>~ zaYPtzSq;Z1$EBndim0NZGFI0<&P|0u(NX}E;TWLx5c6${^$u;M$CE_QQqiEw=ZK}+ zT7P>lXDJ9X1Ep!kJ?mGWrmq(SyJ=a#z`=n0Hkc;FwFK1jk2~IjA~DPaj{}FAni_!M O#MlTC?f}j`xpv0{m@P+HLzmu_?5_c|Wabb5()w^mBL$^IWnmH}L5#?0Jgsb2f~yM` zCe#MORS3eM95zWX1E&U|Al&qAu>5!*N9VJkx|}S@Rj(9 zeBF(OV7BPe?U(oJXgd8MzK4U{J9uBqysN=9{J}9$YKP#flG>#WBDvI=3eimBg-8|< zIgTSXcm+xncf@=du01^>Rc z5Z*87`-azCWd9=xh*|Mtq!~VibLi9v;C69H|{%9FN{K}SW1l(@-h zDHMM~_U|b6b2MmG*U&+weUvih)|8|bI}yT#%Tyg(s-^j%IYVCzw{V}LuL}coTh)xr z`!P2LueFTDbE7i|--A=zqz2TF+*x`nGV2ZstDnHf=7^R6o6Esajl}>xfi^F+QynWR zt$!krA~1Hs#Z<AUb;;^! zmnevBt|$WhY-PxqHoqymR=OLR#|>2k_S}5+($Vj9?UM4j(cm-`<38xs^)D`8=IaFE z`6xY2pEbDPlpLQzllHss-&w>wHAgyr*14434Qivl(;uLEBMF&A^E-9L@04l%MKlO! z4d}Nmzb5_7hplYmhgc~B;k(@}uD`ArNH3-D90{3z%gTB66!kPias`++Jz`f5S z`;D6Xt7vqzy6G}WL+-QA_TcW1nxNf9UMi?E;>d!D`y;~-kGyUPugHlr7(Fa1?mb+HWn4FA`Ru7!Vw*`fW$rxZQytZp6Q!R!;fITF=YN9= z$CP+u3|XkjPO@39Ku*Ek=WW6?gE)cFONq%({PgMxC{ptC1PiG0%AB8aWnoiL#0E_f zN=zS#@96(!i51&IzN-_K+^flL6`$A(N}ulULZw}Nq9tHVEt>9dcLlGh@4!J^w(XwS0S`p`Qgo`PG-vW%h&9Q==ZaDW31dU!k1_LKRLIn`r`6B#w zy?uvE%vzypb2sWTr-kNn#JE=>sGZ$<)j(Ts@{f;dC<@9Utk-0{FQztOQMq2KW;*)+N4m%; zI)CLkz1Nc?XGRaO$aF)}61T58SnouoAFLk!QBC6B9$mB;0^86=6~Xt<6DWUSza@=NPh0u50EOn6skM@gboM5&dbP*;8K|& z{FW#;^HT2`PCp~MwA19e;)vBfc^bfwAL2Rd;pe8c3Xd(xh)HEYz;1nTqd*P0B1&6D zSBxN{&3IIN6_iD>k2^==Uf}d{9y)BF1!anI zZHtY5OT9|o#D$<`fu@eS9sq9aN%No&d9sWC2Y$A|o_78QCvf&`c^B^)`AQeQqMkmE zlNN56sWs8e8(+BNPf{n+w-R8D6S6-gw`_v07Iz3pp@4-ciuhyXxXI$V0q7$ouB>Dk zQH2W&!nqS6K76iZ9Pm6dP_G@k@Xrw)1yo$;4KThtw7wD7A525d24wYH5wN=~P)B0M z%^L+h4X=}GGX4FmGH2P5UF{&Hi>EUbfy@t01M7KVjrQ;Xn4&u=^Ia`M?p(Xk37kQ` zl5qhUWs}8->eZ0>p?=6<-f7&S=L-R{I!V)qnMRwdm0zy;xs7(;MK)gu7o!ESBLivf z6rxnR|AE>rpI;=+d2|J;w#z!R9i8(ujaupseJ)W)pob`t8K0TdHq~8Z-iH-?I|I*I zAlQ%5+LyMMFdtzuXm6j7^)2os+G9O!)Xq^6W>#V+j_Bm77qU1?dj`E?iDA$?I7j}} zAW8#K-<>fJ^axrv<=!Ml|Lx$82HtD1VBQs5~=qV{O zXF+y@K9NM4ii_d_1OhhZ{Qy{2$;2i`+9FZy-S9*3d0YtDv!KjwWllWLBG*sYNSC#| z>mh;`J-T$+Gn!9{_`DGvD*A%4pKm?%WB7#X-xsqkD8ZiJ`@CD@-_=r?Y5b6 z$vPCsV^c#esj9P4{K$x#O0SP7-N83OT(LbhJ-%K<+@fo7-6F-^zs+uSzq!NHbT{(} z-P65^z(3OL8P9ew0nc!AeUD^nEma`OmfAolz?1E?YOFqiLW1wrrxm&pE5mNCc=|gO}Mh{z0RvRLg`R+Y}YI@ejoL zREX~m@yF@SGYoA}C%1{3DgviV&7bXk+Uma@|1vQXfaLlaD!)H_TB7oegm&LF_nyUrgsJ1e`A8tOx%)e>6-U<|nCQeW9A`7iD%28iWU3V_qWfuyYaWY>?!v0XQ5dUB-9ZXHn z`vF!@^bM$>WnLwOAJ^aOA{v!2aAGe8jVO^HW&DB#KMWbMh9n)DV+#Ee6 z29BP6GiDJz(xae0Y`k#GuMwbPuqCAM(r4J6{tn2Q$ftD!`fsIeKSJF^Sdfe!&j*R& zof5L;s+u=43Q<0vp2n4ZRMy+wOdmi1?-`8nABs$(;wr5o{tLOKjx#7Ky;kcod z3UL(78tAY}H#0K99Nf*~-hA&jw&rZP+@?RTvl7 z7+3-YSJ>Bpv$f<|0@DkBJS9=#!{V+dvwD_euu@eHUSb;wnbb8(zN}+3BnED1Z#!YK z#Ml-YF&zAdHnPtK72*8cX=Yl^v_SB`AK6RzNSl14#Q5td5scFq663<`Gxi4m4!I{+ zp1zAPF)c^+Ys5sFIpX4%f2wcA`Ydw8hYE$hvCr3DA zVITnzD4np{D7nrd4tM=XaVJTIx0YGgakpzq5U+C`TaL#o&L-+Hw!kL-MHPi5QRp*l zbPW^hq4lE1ae4t%XdbF;-^*zVaK}babw`~$u7JN>rjXaFdX^3ZoLbkoMyw+C4G@mA zpg<*Aumu7`=?fU4o^eAUR!Yljbau(wg~SQG#GBqqKgyD2{_UW#rkT%$s@HT019wYi zndrVeiWVps zxSjOU_2Lf;icAPM24Gz#4u9m zyI*pt#Z)UJ(X3jd_0yNc>{mW1>*k11+O^*M=9+Ed-YWg^&u=Co*@UV#=I%`p6as5B zi_q_F`|enE@ozF#LABq!Cg&i88XZO{s%+iQE0wYVAmX zvJCoiaNQO1>6KIAVXPm2Ry;CKMX5MHd&TAU>t*&^&`4|HT$Zcs-bKHwV<@J?oy1Wk zi_nFB&y-MO!KnFB>T5H>cyXT;YFhASR%T>s3qiX>>=zKHkT+M8HzQmMVL6RDC7OYg zl9D)EDWuj?n6DZ$_`qtSJT1n5$XLp#1{@}h1(^NqL^)Sd<0E!j=|+$45sM~dND81Pg@gQ?7)g9zIx~;MAkQ(&F`%d_ z7;VK64B zv`mC(n`XK6MTCpm6PWV0>n1WY>-hP|d|yW?^28W199DN2_5l){-|`wqct&*s`(!qA z1^rN4$1qPF%x=8tu=J#3_tYt;^z#rY4oJ+^xElohzbs!nRzeg}nwh!XW@2O`3rOER z>wzG;?%7L=a{EfT0mP@w*R?+dp|cBdhqqDZJ@5czOgc|L;@3j*K~H?x*vZzKOFXH<#stS+OakfLIQ-}&5grt2?>?qPn}f##e9KZnWsPt zMjj4C)_Ept=(IiN@Y`_CWTIZ@#0DBV2a-jKYK)C!SwgKll~I(%4LG)!%U6nIkgNb~ zE(CK=TPUWypY~ct+hypNN>J?LS(oVCqdYTCu~;3R^jop@zH!4#GAw#!Yer;~dWL)0 zxt{ic&Lm#kvk4jG#byMqjVB=Hv ztB!bN9U(%7#yG+3r?0iXJ(7%OklB=4_bLIURH9BW)01Ya6zIacDkEjhc8Dr%&9uuuWGrCZ;@i zNtuY4FOr~(|4%?2()5;3_UyD=hp+J!5i;hqm*}-@PA?0-C7dBkveu>ae=R8#i7ZDp z(sgj@aV#*H8|3rvaG=*E?c;O|C{h8;a8W*^MfS|m2a?t^k;ZV1t}iAYh4>KQ!w;#< zovFDPDccLI7HUWbA4+y|DDbkk_Q)>kqLwQEP<%X{MJ6X5NsbtkKZv!@Mg|w~Y~?rmL1$ zxz(DlwnYAlM(^eGfsAOB(DJo2Ou(Q_Hi~tAVVF_&q}&qQyA0H^%H0m-tfDPN$YDUi zDpd@x)220+U!Tib zoxj8Z589xHehAz_mmh&x8X3)kTK8WjK5EeDfC%`NlAwJ%QATdqE!-@GME=87S$?0O zNyDHfF}0sgD)e>FYoeL78A+3n?MekLo7KXeF{)95^ztEpd*|?F_d{iy_gxbbk72YT z*b1N;EiSKFt{wlj7z#@+-{fVco@mfQkW2$ZC;al| zeHGg1ED%3HMFEN_VnkiLtNrv!EdoA(Mkq-z-If&fmk{z}C{DH_hbgNiadOd25d=oc zHGl7&QFkvYpVg%ipDJ5X%^nh8ddu4)sC@?LP3}BFKQkzZOq2XM5w=@C~anrB3= zzK~B-;V?AFbxx--bjlAu?4w8nL97-Y9VY~k9rVPqE#(g6T81hq%N+h@xWZUc2G_Dr zg7O=b1v*7}t(F=Cu~n@c+wpDStx|cR&P#>7N!B$BXchRD&+uvc9=C{n*N=Y?&Nf{p zK*^bf)C!!(nEk~MQKBm%9Ge3ouN-QCI-d)=I$Wzo;bcQJ(1~yw`&?_HzpYP2J5mCg z6L>cWBQ-IH)t4U})AlnlbZUF4tk7yEWw&{FtcPSNq3f%kdtpIK^g2P#Y#07I}XD=;U)9F6We+60V=tkBMv=){>Dvb)mI7fLpV4jF*h zOCBb^?4T{8jVMIhgiUyFiHGCn>jrd?vA#t@uTq2KbAa3GF7FTFb9o*F&=?{4B*TC} z4t7K?R`V?gPjUq;wl|Yipl0km>?~opQ0R_FBt`c*fIx-K>g}uPn*aKuTUdz%>Yz@4 zF6u=8D(04h9)y?-PFiy>su(QK!T2c4Df2u_d?{r@`vi_~j72M{y3N!iIV>OAYTeL% zcntl?lcg_$UBe55?fr;XzB?~0c0p)2>t+Fck0rD8CnLu78)L#uvl6IP z3|$JBDl#fPpUM1jz{8D;;q)9Z7O!ZAsOsp6kcQ!7 zw{iA+pwn}&+#~D_By(O(FLJM9y2}RuQVMPXk+x&o%PJRmhOXD&Z`MNs{ z28JtEI~?m`ncLP{%v#?t{11j|jDk|)T)GsB`{Om6)EIQ0QR%l!OSgY8f?=V%bTo|o zhu*p1uFOi)(@LjLz_4+4m+QW-Z-kS?s-(q|(i)pWU6>msMNza2;GaQXYZnnvRofO} zzK}1h9~`B>TB&|FAee>eIBq|QhL6E1#0}7Op|VpVl4fP z?oa4aUV`rdsr6p3Ihl(Tg{rmyKyA63TR3k!rO_*$As%~yOzUc1VnG}HwfeSxLaafZ z-D_@>)VNzd)U4&n+I$pV@wXQWtjW-#K9=ANieM=E&G&F&_or~89%o7*d)OaEn#0v= zphZtDLa=&LxF$Vmfhl-(Q7#Cd*mcCwqvos$5z;I!iMWaq-Ty#;p`sMFJ4@-<6E8M1#8$6Z8HJAn|v*g)N%|{|7q!d^^ai*9_ zNT2xLZs}tSqQoCLBrHaF&q;HT5U>`S798vyCjJ-o7)1}C<16IN4eIev1c<@^Fc}3? z$fgYXhQ8iV$WeJ%v96I3Dkzmr0B9=EFV{IDQ@w6`>ba1^_i>45WQzI{!xn6mFsa;f zRz6e{|3(Cg4V`lZntW5l@E%HSm-Xn)nZPJa??$GdVp-rMj~`%ui4ecd@vP)lcRmqm z3dGHE@7I~>7Lgo}LI9$uKs0VhQzN{rz>l;N6-H{)C5_}vM<{-_gSBgX|c9)11 zrws2q532aENuma}k)9g|NmD(w|M%Q2G-Q*`3PZ(eedh+!Am+p`3nwMEzQ5=Q-78U( z0%x7iC_(h$Ju~h8=kEE~K&f`pZ*^ZtjOras$nq41G%on$2>EWwUgt!e?+ReB0l*cL zI7Xm!xO&Ks*TQsT{xS9|=H^XoXxxP2;Y~hR@{5=(MRK#cA1Xp;4fTkKxWwUR;^5Xs z$Bo8ktyOV(MMl{)5f}buFYR3ZlItB1x+!|cEA!JKAHR$ODrRF6LysZjg9q;CgVlon z^&H@mG!sKPHtu)IY$6pLdFl}ecqGEd^hLxqD2i#;5U5EQymh0s7 zB^k2BL-y}oXHw+LiaW*jE6I*YgAIMcITJb{q^dF`q-&I2P%76dh^WCXGm#5zW%*S| z?4~f=m_HJ}&6Dy*UV!kAz^0YhaxK=5SRVNPiaSR*n`<5OoEH2_3a1tE7$vpn`O4<| zqi8!mkYdmmfZ*+x-c@oJbX|1;ScB-Tzv1)?2aObw8>F+3tQZoSyQwtNrJL5MBUU^H zDkv6l+nC`z59t%tg6L*JSo_Bb`d~|OS}xW;nq70`(d*)CVG1 zV*qRYIWbn!6wJ8ciC?<-B!= zY(eWpWV~gkak;cz*hwlR`PidC>FS27d+Rsugs|DwEb40CV1+E>yHZ=@MlW1zvL9P< zblb)XaxWPVz_8$?>s6mjDVPtpE>Ujld|PAEi+>-CN+*AZVl=u+Nk+9bRcQ#n7Qu(h z9%{u>TGO5g8NXB8W74m)f>L-?>Vn2dDFiys@HoZHtO1t=?e0%95NC#bb+O9jiDkI6 zj{7&v&o!<#U@zQN)xycArM^h@?TI5qk9+IXSt*oNj+(!;w&Bfea-e|tpoBZI3^7Ii zfwvBDH?R#Yp@lR3uvtaDS@#Q0Y@}Mh2^)m1qiHMq#@kdxbKQU2wI<>*%7^hksY5M^ znJC(Y`D3;t?LLbMZVLyV4g0H56E-Xuc&)>5LgDJ86L0MoTdk|?c*y3HkKEo zH6v8^;t_S$m8s<1w^%cX0(adIyaIjE0($F%{FBlrIoLbCsn;HPM-?&bLf8f9NBsSGMRRmVH8@F}L3&A#K>4Pi-}C!?PkJy%{GF!&uA!o8O#dU)G<& z%go`&E@D5`|fkes?6C zn9=xVj@t!u-+lj!N+1Zo97YAO1!UAiv;ifbq5ME?`#{an1|5U! z1VqE z9!BHzCsHoh{U@@m`V;|#QQ0#*x6<1dUb0RBz%kAi&nQECq8=f^6|tyJ#5XVlbJhw( zHZYA(ZE&)=CTGiIfIFxoFF0w!8orZ$pLUERt%sr9upsSUJ)_!KU}8Eou3r^mE1ATF z&PJsx;l0t(hSGdp}e!jfMmgywV;sTr&*-NKVu$|6p|ym6n*+vHDSkjj3x9iA5(F` zvPnlsXwHmv#31`(^X{L1cx<)CCRhR>r4Fjgt~%8Mr6|zrJX!;JppMm5Q(rRzYAbO` zq}sLdK#QEo?Y~`+y)A}Og+04VG?kyN%a2qY&?wLU+$op@ccHvx^T0LeDJOir?4|22 zMVnw6RH+H|sU^Rr%w@KFRfe;|xSg~gK;N8aao*X^db}H;kGT`vW4)^pavE(t$go>3 z@yC`yw}}t=e7=AX4&3w3yi(|tA=9%>t_go8=oVx}Y!oaE=9`e8H$HYeH@(u7auiiE zVaGu$|4?7Q7Jw}fk_+gU! zhMMBDgz{QuG|_+MP|Gnz_AgHJ$qXK7AlKNuLJa6AY2ygQ@Uk&~$(Tt<(8-gT5baCf zfVu`MzYlP`P8AabQL0UZ3bBJTnENL%?{gr$djNZLKrYpkY2#PkuXtwrcJ`tWY=!jX zWd55Or?Jf-%l~p@)06Xip7d)49JMd@yOF|Tl%lNr4SlMAPNOvCda?#$1H{XQ=2$gE zX!0h<(n;Nxb0!T>d0lMg90hJ&>T$r{?iackWejr=pB7if+ zQ2_j_3SRYQh#hn-@ieNq+eT8w>c?Y#!Q-Q2KOYhNfR`!)9xAv>?e!FdTL1EdB0jgZ z`IrjO^Q#HV7W((Y;Ly^;(HDv5IZ-S`?l~k@hi-oZOGjG+q6D_Q}I|D$)a2J`$YRbDdp&Ub5iJ08AWm zhyC<{6Lfe>8srao1i@d=11fC5os~SQ;be=uL(b!$Q}}$?SlV$bf9(Gf68X5_loC&b zDT!z{)zmA+TRTW$ZuvL1kU$8miYv?j9@z4;1T#Bc*^^MJOjPzrRB3p!@tA_V?6SlK zno@7lW51ijPQS!L-@4l|F-!g}%WxBH#cqiz&*QwRli<7cj@GcnC{c;!t zthg_QeeCbia0_;V1=QhA?5p+rHZuYc_Im_O=l${{IfI%=?10sBy0wiqTlfl~o;FM^ zVf0BfI)_+VTFZ+_{SLwE<4@A@ridxyxE>NjZ%wi;q$q7V-@zZrr!nrK{4!N%`xlL` ze;IdA3gd_420W)tBddH)suv!7nqs{k-HknOcJJv%+yt3NbGQz~49#e%_;vP$ZgaIq zi5+bc5&O(;d%*Pb?4g$JoHkkcNyFdO6nSoFvpP$a$aK4E!R8?@oVtZ0?UN4Zb_4(G zMo7xU`Xai^O_jXE{l)j3SoaIv5p@i3@sL*dZs|1tXg({B-~u*S;z&h6L+9A`XczER z=LztJ!VSd1{#lI}$HW`jNyL)}DhrGz_gD;_uUKei|6*QYr~CNi9}P5GaFNe73>I+( zPl&3VlY%xdi*uEi*OORJCJIVQCYU2yP?|)&#FMe>2mUt$85|}oGi<6UX3MRCPWa+! zMD~gz#)mY?8;BcydfO$r*r>ms%CAuyQ=*=08##K9#xNLy79I3sVyG(3MYlVabr^#N8$>IMs8u+_UTop%X4SjEJv78Jn!0H_hczl1^s=J{)anG z5SUz<>XrS&bz3X;vDq7n@x)o#gx&mAIsTy2WGg6(Sx#D>kj$ogB!AX$TTGfcg7xU1!5=#|LZuS(B6_zACSWP?L7ySx$%pB>ni-$7kT{WhXmKm|Xv&o>xaQDj zpzZf#&FgxgDZs5^ny-e;nKzD?A)SA-B!%MK9+p*V?Ak2uDx|LYiu!y7_3K`3{?g$p zQk4B;hR3lJFiK@O4KESxH!Oc7il&HYg-XD-tn};>Dd;nw&H=>`oSP?bdN3fYUKm$dp<*k2($*BWu}f(4p0M$Gt;^%bDBrtD+=!_GE+)?@Z7cS zWGMFvqY0P$*V==rb+%;$$y}N(NhJIi3dYm4@1uAeEGn=??hiTAw`l6jfI+84z%g)R z(Yn5_jAAphD|H+z)gumfqw}KA%di3+PK-1)RlYDY$&#cEy4ttV9>*5f)sdQdfMOm6 znwhn_6_jJFXop>_C_Q+ul$d9(4-IB-sVjWZf{`jZg{Z~PbsG+30hXx1D>7)PNMdj0 z!%xGm8z+L`Em~Fr%d>$aaQs}1)A#LN{#ivcIKFC(3Xj;|ZxRAojx-s0zj0_*?0pJ4 zEa9-ViaxCO>CS-cYNRAfls@OHmh8K__uu~lu}TSMck{RfoOWFBiT=cN=%Ku_50uJ- zZDNAjSHLLb*!rE6RH4%~%}V1o)PbIP9CsygQZdwKzi5)((E|7BeUi7yC6zq{rk!^< z9M+C-a+SBk9-vR-P2g2H%O&n~8TYhv2n>-G-Oe1?u^Sp5MR02Gvp`>8Q!XT;vTua` zY{Cw8BIA<{CUOWoi^{yQ;szulu&D|GG)>s2*1nY8>}lf3G~@ybtc{qNliu+)c%@GfaMvoj;(Mq{W&HgkQHgW_m9#5mcJ zoNHh3$ByY<^U`$i>ls+Yf&i)uqm1Q(&LJj)xFE0{IVl!1!`-l^S$`3J-xV;o9uvz= zi|xmMasyx1q)O!3bpUl@UKOb)SFLQVjbD?FK30oViCDY|$&HI?SD=cl`z~1l8h%Hn za6v%GozjkildjY@2CPjsNY)B@elFCyH8r_#>}OK8(DF1)Z8xqta=Fb32!gu|5bXA) zhPy%!2IPJozL<$`83Bsa6kutCSFx!xd^8L6JlDRHHb2(? zWyv3ypQ|TlN16??fZ)QA>?$Hv>~?DCKTBif{>09VXsGNt>AKvIU>PZ@?8r} zDBfbIOyZN!VLfcMp#y@*;Gv6;%c>^#R9J;Mv7=p#7-Ce>Q_*ISH{p zqKWb{aS-Qy(~%IzR$nppsIf{&f-wsL+1lRsNr#Z^pV6c5TTOCjUiOb2VluUG>4>_b54>TuwUN?{^W)o?f#%-J{*q{_Q{Yxg! zkA3f#D+{P&alZ+nb);Os@+o?wL%BbdAh7;mskv4}!zLz>j7^UiMnOrIDZ5%-#Xpoz zp4oZug%5AGcCng~#U!Zh3K-r-*COrnFkfG^U8+-?E|B;R#4WCAW-p5iu|LanLf9R% zsBdj`)oX+KF)fH@;ffPX;HHDdT<9wd;I!p*Q$O4N+z6u3&6qcUZFV^Z_w4fVL9~(z z3t5uwc-B@sK~BP}zP0KIcdkR6_@0|qVfkGchq~d4IScLUwa@80=0WF#=0A~l9D_o1 z>B6Hr1cGn$4`p%NeJI83b8UKneB132??V+gPe_`u(W10JwXjgHfLVhVTFS8zV z^7JwHx=MQv3EBD`Tt;0fpAQNLr+#g&xp^k4e3T)z}w5#Mnywf`nXcdzmx zkYnu~%7sB#^%6|9Z=nHJCyNZ3h@m}_*E8EVS_djW9YF@P$_ZX7@)v_t{LmPA+jqz} zz^{4y-9(yh=0_$7Z2sK`Tb`D*jI(k+?01Y<|G*IvtcGjT#M|@=SiQ7rgyg8-(rf3!k82uwvBhLP%Pzgu|qM(#;RyKXhFJ= z_DE6Z$G!?4wvA7P8e(5x!>u8*L3pAGx}--@{@J>ovW_(Bo9d_K0Ne+5_h*}H1;bRg z$vXk)Ut#pA#raE|m1`elpKBBw^csvr7VI!xLBYAc8)y16${Zed=Kc9vGZOMH>xHV;`k4{_U zCicj$*|| zsz5Yq5|88!-nA}I(B!Ed_u2!^p>R{uXY;Rh3L$_MBT}XuWX&yqU7Cq1Ke3-pjJtcoEDndWdf`W6Bj0UrYI&%$y;4pt7StSg!=!eXsj!-e{e_QWe zKXtf&;Z>+wmam~je{1KN{ER`De4C>_Yt$4`$dsjk=r-+LbppdUN&^DS;yI|}$fzS^ zs98~6%_?lPxIx-vgDqCNp7m;m_-e`qGFd=QHrz59zEIDD^Xzgrof2fbu!qfyBZ91B z`mb84j7<#`>nzNnMooZqc!=nP*#s0moVrOH^vBzdu8OFMj0B=Jl+BqP5V+sPty4X=r1GKn?+hIrk7@YPM&J06fE18%^9$_1_Q= zqvwefL8eIwKKPZbBF1bbf5h4kf5T|T3IoC@lI1vSwwzY(cjpsDkzYkl#k$E0a7EXr zFT8Od@{qHeQ97D?s_3xFttZM2eO%tHyUF3h%aVL24Y`(TY62^%xKe9 zx5_@ub8_Px9mshrTrv>9v1FnSL?MHbxpK%HscoD`;>m!n5mbsq0~U^+-GvQ1x@>64 zf(>~Fh)LTjy2@l!$>Xl&3pC}PZyda)=$poWYuwb zwx89>*zvPjTR+9kj7%1OK>h^QQ!$=Y^)C-Dw*rDVIC(c=V|YFg|BPP#6)?@kkyPIz z-CrV7{t0R&+q1G=xqlpIjT^iIWM6ANv2+jbSnP-u$V)=B5* zTVmm2ez2uH3PCFp#o5V|KJz+$9c~cT;nm@l{64jfZuthtNXG}vU))hvc%wH)N8XNh2~R;MvQu&3|co5 zoye)2aZ?3(V*6y6PAVVIvx1I`F}Hcnb5Pm=tzBk45a7gn(sArTay9!Q1aCHi1Dda; z@UdMc0QadhNCefS>7SrX_N|q=9X31DvO;}85c zy(=1;1iM)v8FX*h!J}07@my{;^ZVD)86S)PCi|uBYOT<8A7b62ECxEJ6f{kAzNc%z zV2aMSJRj9qIiIb!6kslxGY|86?jH{oOowK|DEknWF8&K9rvw+(4K`!dZiDq*h#1?u zf{ghy&}WHzE5aQnd>4WPm;$lVO{ytbI>AULT@&;;M0wd#b}7V%s{8ed*d}|$S<@3& zQZ2>myUs8B9fY(kA6RJCP6rzfRVLV=aojVx!*`@RW|0ks7q|jDmu2;p(32n?wo0ov z_~1b5=(O-b6M@|Wof{3uI<@94h!Q@)VOnR(fc%?hKF>@ghJ`b9{fbPOhzvvYT(IFp zqb*bE_96(ohQ=V3c&WKbq~PpN8Z%hWYFzwzO6%B=n>9=m-u_!B6eSP#8AT3+2r?0&N*S2yZfA9F>wctBfe;o5=(GjT3e}d}te!>Z(VUX|* z#ahQVH55O`oQbPED~S4I#k%eOBtoMzt*s(E1QmvA7y;i+^HQ94?N^vyfDjwahI#ib zfcDs+s%#Kz4GIjNB(@t2@~Hai6w>lFL3B#>24V9p29(h% z_S7h&oxOG%5H{};fDTq{d{rfCX;y!P>8hUYrMXOrTC@M1rqf8jl+Vm&Mci}TGy)-% zq{K&M0y?@incj<4uk_X+wep_@ z3f1m_0mrs>2W|mWGmK=%C;HvRY20e+j5=t^Aub=TsTbZD>tU%T9f~2f@A9QPO(@n3 z;}Gq(Q@;^H4(0w~*=z^a!u2 zjKeIdB*~52?#-G|gb+)59{M>mj2*g{V|8(W1)`ra0&CbFSm84Z;I`YN^(8!a8XM<} zfHbsgGpKy?g|Pl-afREJwNC-fWJ^Mc(V2;2W&qi!7u#lb+{v(;YSdR8?*{8dwbEa7 zF<6DoP&kVKk|(ROX^WX@An&1Ye+ZVY?0FZM&+kEWsYyovn>Xm9ytkf7zOUVzFR(47 zaV7v4c}}@k!XF^UIUB%Z4DZuCl_xsKtp|-wqhKM(7wz9tpc1F4${;3m*PuWbmx2+C zn+s79@91}+JvD#Q!Q%p)b_!aVa@czvfMt)bi5cO`*uY<;gA7Qeuk2mgu_|VNX@;<6 zE;$x%+I<6VLKX=^9LI=vLtabYb=3+$m}2pvfOa&EdDnd_M6wm1{$*{3*Kjx`=KnA# zdzF5pxAN0&X+EsEH+#_WiCw4*Q=>X|og1Bb6>d&9=|(B26O!0M3KFo$+8?%_Lb8hz z$v~(E3?3+Y^`h3|-#mmexM^T*bzcKvn4la(Lt)lq8E805Vp;(4cjsNA(bQm?e3ECzy9llC4*kJ_By z%0PZEKM$o;n~0Oi{{BB!>;(n=r&^|-l(*8KO$6orE$a`WfLweTlTS6;&9o}3D-<=q zs-ymrFc4yD3m%|`lt9nsU-dTLRO|5>`fR@g9L0Ghm*Fwp?VaW-vfygGCP~w2xPu1Z zU++srO?afMUGC4iT2CMkz4vDEM#_ z)IpiCor5(&L)cfE0>njMH&~|qZY5}t&*ZHYajn$A^ZA{hMynLqFYpevnAqV}_lHK$ ze6Ccas)R%D4$wc_))pM91}sSp3TV0~$|cb~uhsKtG7RURp(&8F;Om3>cZ?I2 z#a{w>k+8bAQk;WS_joyU7c19{n;8DjRC1E58e-U6C9|qvG5ChVznw_29n5JVUAdHg zXT+zjZQfHRaZ4$=SnX*qY8*ot?*GKD)Y9D^W$K&36BF`9;-oht^me0hLu4lJJEIX# zN(!MPk!UMZZ|9Z?ij>hN^x7@ogYvjq76H9AYd!K9_@{EB$0y(M$Qnp!Hy9f^~Q@u6Us29zJ&E^>hESY~ev zS+3SdY~4X_kQIRmFh1ey;Jp0;39I}}AvZ->%ZQKT4IeBAx= zY+Xf;X^ve%ul=;R=aYv7iIS9dvPu#jd5eCjIe?n>SW>vc=Dq)9Q48Un^n9u7Jm^X{ Oqyhqoz5{QX(q>D5$;9;l literal 0 HcmV?d00001 diff --git a/tests/assets/romfs_source/romfs_sentinel.txt b/tests/assets/romfs_source/romfs_sentinel.txt new file mode 100644 index 00000000000..6ff9311d70e --- /dev/null +++ b/tests/assets/romfs_source/romfs_sentinel.txt @@ -0,0 +1,3 @@ +*MPY-ROMFS-TEST-PARTITION* + +This is a ROMFS partition to be used in MicroPython tests, usually for CI. diff --git a/tests/ports/qemu/romfs_test.py b/tests/ports/qemu/romfs_test.py new file mode 100644 index 00000000000..dce847d844f --- /dev/null +++ b/tests/ports/qemu/romfs_test.py @@ -0,0 +1,55 @@ +try: + import binascii, os + + binascii.crc32 + os.listdir +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +TEST_FILES = [ + "0x30d83fe5.bin", + "0x37bef0eb.bin", + "0x442f3b5f.bin", + "0x648793fb.bin", + "0x913837b6.bin", + "0xdb14aac7.bin", +] + + +def is_test_partition(): + # Make sure a ROMFS partition exists first and that it is the one used for + # CI tests, otherwise skip the test. + + try: + romfs_files = sorted(os.listdir("/rom")) + except OSError as e: + if "ENODEV" in str(e): + return False, None + raise OSError(e) + + # os.path.exists isn't available so we have to be a bit more creative. + if "romfs_sentinel.txt" not in romfs_files: + return False, None + + try: + with open("/rom/romfs_sentinel.txt", "rt") as s: + data = s.read(128) + return data.startswith("*MPY-ROMFS-TEST-PARTITION*\n"), romfs_files + except OSError as e: + if "ENOENT" in str(e): + return False, None + raise OSError(e) + + +valid_partition, romfs_files = is_test_partition() +if not valid_partition: + print("SKIP") + raise SystemExit + +# The last entry is the CI romfs partition tag. + +print(TEST_FILES == romfs_files[:-1]) +for f in TEST_FILES: + with open("/rom/" + f, "rb") as h: + print(hex(binascii.crc32(h.read())) == f[:-4]) diff --git a/tests/ports/qemu/romfs_test.py.exp b/tests/ports/qemu/romfs_test.py.exp new file mode 100644 index 00000000000..672e08f95cc --- /dev/null +++ b/tests/ports/qemu/romfs_test.py.exp @@ -0,0 +1,7 @@ +True +True +True +True +True +True +True From 2752d398ad7a2059e6f93a1bd25e3a96d6680511 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 9 Apr 2026 12:33:06 +1000 Subject: [PATCH 1959/2098] esp32/modules: Use "from machine import *" instead of __getattr__. The esp32 port has the machine Counter and Encoder classes implemented in Python, requiring a `machine.py` that extends the built-in machine module. That previously used `__getattr__()` to delegate lookups to the built-in, but that means any failed lookup raises an `AttributeError` instead of an `ImportError`. This means (among other things) that certain tests like CAN and I2CTarget would fail because they couldn't skip the test correctly. This commit improves the situation by using `from machine import *` instead of `__getattr__()`, which puts all the built-in functions/classes/constants directly in the `machine.py` global namespace. That means an `ImportError` is now correctly raised for attributes that don't exist. Although this takes up a bit more RAM, it's now a lot faster to import from the machine module: what used to take around 100us to lookup a name now takes only 5us. Signed-off-by: Damien George --- ports/esp32/modules/machine.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ports/esp32/modules/machine.py b/ports/esp32/modules/machine.py index 9cfda12f177..9a7240b3f9d 100644 --- a/ports/esp32/modules/machine.py +++ b/ports/esp32/modules/machine.py @@ -3,7 +3,7 @@ _path = sys.path sys.path = () try: - import machine as _machine + from machine import * finally: sys.path = _path del _path @@ -185,8 +185,3 @@ def phases(self): del esp32 - - -# Delegate to built-in machine module. -def __getattr__(attr): - return getattr(_machine, attr) From 50348ce0eb8f0db27a0af0c3930ff3ca5972752d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 13 Apr 2026 10:13:19 +0200 Subject: [PATCH 1960/2098] stm32/mpconfigport: Enable machine.CAN if CAN2 is used. Enable machine.CAN if CAN1 or CAN2 or both are enabled. Signed-off-by: iabdalkader --- ports/stm32/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index bc9e94902ff..19d4ef8ae0f 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -123,7 +123,7 @@ #define MICROPY_PY_MACHINE_BITSTREAM (1) #endif #ifndef MICROPY_PY_MACHINE_CAN -#ifdef MICROPY_HW_CAN1_TX +#if defined(MICROPY_HW_CAN1_TX) || defined(MICROPY_HW_CAN2_TX) #define MICROPY_PY_MACHINE_CAN (1) #else #define MICROPY_PY_MACHINE_CAN (0) From 036bd81636aa6068a1af46db9506d58546104522 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 23 Mar 2026 12:28:29 +1100 Subject: [PATCH 1961/2098] stm32: Fix mboot build with TinyUSB-enabled boards. usbd_conf.c unconditionally includes shared/tinyusb/mp_usbd.h which pulls in tusb.h when MICROPY_HW_ENABLE_USBDEV is set. The BUILDING_MBOOT guard that disables TinyUSB comes after the include, so mboot builds fail with missing tusb.h. Guard the include with !BUILDING_MBOOT. Also add forward declaration of mp_usbd_ll_init() next to the MICROPY_HW_TINYUSB_LL_INIT macro in mpconfigboard_common.h, since the function is used in an inline function in mp_usbd.h but only declared in the port-specific usbd_conf.h. Signed-off-by: Andrew Leech --- ports/stm32/mpconfigboard_common.h | 1 + ports/stm32/usbd_conf.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index e21f474d7af..30314a38a59 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -262,6 +262,7 @@ #ifndef MICROPY_HW_ENABLE_USBDEV #define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_TINYUSB_LL_INIT mp_usbd_ll_init +void mp_usbd_ll_init(void); #endif #ifndef MICROPY_HW_USB_CDC diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index 5040ab2889f..da0e71a7e49 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -32,7 +32,9 @@ #include "usbd_core.h" #include "py/obj.h" #include "py/mphal.h" +#if !BUILDING_MBOOT #include "shared/tinyusb/mp_usbd.h" +#endif #include "irq.h" #include "usb.h" From 941cc31db2b90980c15ea95b462dc35aa8f8a652 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 8 Apr 2026 16:40:54 +1000 Subject: [PATCH 1962/2098] mimxrt: Convert port to use new event waiting functions. Convert the mimxrt port from the old `MICROPY_EVENT_POLL_HOOK` macro to use the new `mp_event_wait_xxx()` functions in conjunction with `MICROPY_INTERNAL_WFE`. This change should be functionally equivalent to the existing behaivour because `mp_event_wait_ms()` and `mp_event_wait_indefinite()` are equal to `mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); __WFE()`, which is what `MICROPY_EVENT_POLL_HOOK` was. Signed-off-by: Damien George --- ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h | 6 ++---- ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h | 6 ++---- ports/mimxrt/machine_i2c.c | 2 +- ports/mimxrt/machine_uart.c | 8 ++++---- ports/mimxrt/modmachine.c | 2 +- ports/mimxrt/mpconfigport.h | 8 -------- ports/mimxrt/mphalport.c | 2 +- ports/mimxrt/mphalport.h | 5 +++++ ports/mimxrt/sdio.c | 2 +- ports/mimxrt/ticks.c | 2 +- 10 files changed, 18 insertions(+), 25 deletions(-) diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h index 2a1fb9660ce..87c44eb8c69 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h @@ -3,10 +3,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-1070evk" -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - } while (0); +// Do not use WFE when waiting for an event. +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) // MIMXRT1170_EVK has 2 user LEDs #define MICROPY_HW_LED1_PIN (pin_GPIO_AD_04) diff --git a/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h index d72b02435ca..3c4fe8223df 100644 --- a/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h +++ b/ports/mimxrt/boards/PHYBOARD_RT1170/mpconfigboard.h @@ -3,10 +3,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-phyboard" -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - } while (0); +// Do not use WFE when waiting for an event. +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) // phyBOARD-RT1170 SoM onboard LEDs (red and green from phyCORE SoM) // Carrier board provides additional RGB LEDs via GPIO diff --git a/ports/mimxrt/machine_i2c.c b/ports/mimxrt/machine_i2c.c index aa128e6ff6f..6d6522265fd 100644 --- a/ports/mimxrt/machine_i2c.c +++ b/ports/mimxrt/machine_i2c.c @@ -170,7 +170,7 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si } // Wait for the transfer to complete while (self->transfer_busy) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } // Transfer will not send a stop in case of errors like NAK. So it's done here. diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index ab5f9b76aa0..a0633335842 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -527,7 +527,7 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t return received; } } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } // Get as many bytes as possible to meet the need. nget = avail < (size - received) ? avail : size - received; @@ -558,7 +558,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ *errcode = MP_ETIMEDOUT; return MP_STREAM_ERROR; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } // Check if the first part has to be sent semi-blocking. @@ -581,7 +581,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ return size - self->handle.txDataSize; } } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } remaining = self->txbuf_len; } else { @@ -627,7 +627,7 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint if (mp_machine_uart_txdone(self)) { return 0; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } while (ticks_us64() < timeout); *errcode = MP_ETIMEDOUT; diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 491e21ee708..be1be9cd083 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -133,7 +133,7 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } static void mp_machine_idle(void) { - MICROPY_EVENT_POLL_HOOK; + mp_event_wait_ms(1); } static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index b76f459e2da..01461b9a5c9 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -223,14 +223,6 @@ extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_USB_PID (0x9802) #endif -#ifndef MICROPY_EVENT_POLL_HOOK -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - __WFE(); \ - } while (0); -#endif - #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #define MP_HAL_CLEANINVALIDATE_DCACHE(addr, size) \ diff --git a/ports/mimxrt/mphalport.c b/ports/mimxrt/mphalport.c index 67612229aeb..1aac6197fe5 100644 --- a/ports/mimxrt/mphalport.c +++ b/ports/mimxrt/mphalport.c @@ -71,7 +71,7 @@ int mp_hal_stdin_rx_chr(void) { return dupterm_c; } #endif - MICROPY_EVENT_POLL_HOOK + mp_event_wait_indefinite(); } } diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index a3ce4cbc483..5987081c844 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -50,6 +50,11 @@ #define MICROPY_PY_LWIP_REENTER MICROPY_PY_PENDSV_REENTER #define MICROPY_PY_LWIP_EXIT MICROPY_PY_PENDSV_EXIT +// Port level Wait-for-Event macro. +#ifndef MICROPY_INTERNAL_WFE +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) __WFE() +#endif + #define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) #define MP_HAL_PIN_FMT "%q" diff --git a/ports/mimxrt/sdio.c b/ports/mimxrt/sdio.c index 2fa0f228fe3..184b886119e 100644 --- a/ports/mimxrt/sdio.c +++ b/ports/mimxrt/sdio.c @@ -259,7 +259,7 @@ static status_t sdio_transfer_dma(USDHC_Type *base, while ((sdmmc.xfer_flags != xfer_flags) && !(sdmmc.xfer_flags & SDIO_TRANSFER_ERROR) && (mp_hal_ticks_ms() - start) < timeout_ms) { - MICROPY_EVENT_POLL_HOOK; + mp_event_wait_ms(1); } if (sdmmc.xfer_flags == 0) { diff --git a/ports/mimxrt/ticks.c b/ports/mimxrt/ticks.c index 858a5c574f8..b29fa669f19 100644 --- a/ports/mimxrt/ticks.c +++ b/ports/mimxrt/ticks.c @@ -136,7 +136,7 @@ void ticks_delay_us64(uint64_t us) { } ticks_wake_after_us32((uint32_t)dt); if (dt > 50) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } } From 1f601e89878b2c60a9b193a7a9d7e47a7627c869 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 13 Apr 2026 23:21:52 +1000 Subject: [PATCH 1963/2098] samd: Convert port to use new event waiting functions. Convert the samd port from the old `MICROPY_EVENT_POLL_HOOK` macro to use the new `mp_event_wait_xxx()` functions in conjunction with `MICROPY_INTERNAL_WFE`. This change should be functionally equivalent to the existing behaivour because `mp_event_wait_ms()` is equal to `mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); __WFE()`, which is what `MICROPY_EVENT_POLL_HOOK` was. Signed-off-by: Damien George --- ports/samd/machine_i2c.c | 4 ++-- ports/samd/machine_pwm.c | 2 +- ports/samd/machine_spi.c | 2 +- ports/samd/machine_uart.c | 10 +++++----- ports/samd/modmachine.c | 2 +- ports/samd/mpconfigport.h | 6 ------ ports/samd/mphalport.c | 4 ++-- ports/samd/mphalport.h | 3 +++ 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 50548b62a7d..cdaf953f10c 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -224,7 +224,7 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si self->buf = buf; // Wait a while if the bus is busy while (IS_BUS_BUSY && self->timer) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); if (--self->timer == 0) { return -MP_ETIMEDOUT; } @@ -240,7 +240,7 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si self->timer = self->timeout; while (self->state == state_busy && self->timer) { self->timer--; - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } i2c->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR; diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index 468e34a1653..9f24e2ffec6 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -287,7 +287,7 @@ static void wait_for_register_update(Tcc *tcc) { if (tcc->INTFLAG.reg & TCC_INTFLAG_OVF) { break; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } // Clear the flag, telling that a cycle has been handled. tcc->INTFLAG.reg = TCC_INTFLAG_OVF; diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c index 744de2000b0..017a94bcb95 100644 --- a/ports/samd/machine_spi.c +++ b/ports/samd/machine_spi.c @@ -328,7 +328,7 @@ static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8 int32_t timeout = 1000; while (self->rxlen > 0 && timeout) { timeout--; - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } spi->SPI.INTENCLR.reg = SERCOM_SPI_INTENCLR_RXC; } else { diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 5be38e961c1..ac0c1be581f 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -581,7 +581,7 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { // Wait for the tx buffer to drain. #if MICROPY_HW_UART_TXBUF while (ringbuf_avail(&self->write_buffer) > 0) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } #endif // Wait for the TX queue & register to clear @@ -699,7 +699,7 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t return i; } } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } *dest++ = ringbuf_get(&(self->read_buffer)); i++; @@ -751,7 +751,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ return i; } } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } if (bits >= 9) { mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); @@ -778,7 +778,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ return i; } } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } if (self->bits > 8 && i < (size - 1)) { uart->USART.DATA.bit.DATA = *(uint16_t *)src; @@ -822,7 +822,7 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint if (mp_machine_uart_txdone(self)) { return 0; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } while (mp_hal_ticks_ms_64() < timeout); *errcode = MP_ETIMEDOUT; ret = MP_STREAM_ERROR; diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index c20ce8d641d..c70cdc75abd 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -99,7 +99,7 @@ static mp_obj_t mp_machine_unique_id(void) { } static void mp_machine_idle(void) { - MICROPY_EVENT_POLL_HOOK; + mp_event_wait_ms(1); } static mp_int_t mp_machine_reset_cause(void) { diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 73d40592dfa..74b694ac80a 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -168,12 +168,6 @@ // Miscellaneous settings -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - __WFE(); \ - } while (0); - #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #define MP_SSIZE_MAX (0x7fffffff) diff --git a/ports/samd/mphalport.c b/ports/samd/mphalport.c index 327ab436a71..1bfacc0b7d6 100644 --- a/ports/samd/mphalport.c +++ b/ports/samd/mphalport.c @@ -48,7 +48,7 @@ ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, // interrupt handler) and there is in/out data pending on the USB CDC interface. #define MICROPY_EVENT_POLL_HOOK_WITH_USB \ do { \ - MICROPY_EVENT_POLL_HOOK; \ + mp_event_wait_ms(1); \ mp_usbd_task(); \ } while (0) @@ -71,7 +71,7 @@ void mp_hal_clr_pin_mux(mp_hal_pin_obj_t pin) { void mp_hal_delay_ms(mp_uint_t ms) { uint32_t t0 = systick_ms; while (systick_ms - t0 < ms) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } diff --git a/ports/samd/mphalport.h b/ports/samd/mphalport.h index ed07953016c..d20fca3d416 100644 --- a/ports/samd/mphalport.h +++ b/ports/samd/mphalport.h @@ -41,6 +41,9 @@ #define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV) #define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state) +// Port level Wait-for-Event macro. +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) __WFE() + #define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) extern int mp_interrupt_char; From 8a56be66601e5f21f15a76bfc932c9beb8f7cdee Mon Sep 17 00:00:00 2001 From: Duncan Lowther Date: Tue, 7 Apr 2026 16:41:40 +0100 Subject: [PATCH 1964/2098] extmod/moductypes: Be more defensive with uctypes_struct_agg_size args. Eliminates read-beyond-end when calling `uctypes_struct_agg_size()` on the length 1 tuple (uctypes.ARRAY ,). Signed-off-by: Duncan Lowther --- extmod/moductypes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index ed865f4263f..7cac41dbcfc 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -155,7 +155,7 @@ static inline mp_uint_t uctypes_struct_scalar_size(int val_type) { // Get size of aggregate type descriptor static mp_uint_t uctypes_struct_agg_size(mp_obj_tuple_t *t, int layout_type, mp_uint_t *max_field_size) { - if (t->len == 0) { + if (t->len < 2) { syntax_error(); } From 06f27ec853328dbba2be586c631d92f722aa443b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 2 Dec 2025 17:03:16 +1100 Subject: [PATCH 1965/2098] stm32/boards/STM32H747I_DISCO: Add STM32H747I-DISCO board definition. Adds support for STM32H747I-DISCO evaluation board with: - STM32H747XI dual-core MCU (Cortex-M7 @ 400MHz + Cortex-M4) - 32MB SDRAM (32-bit bus @ 120MHz) - 32MB QSPI flash storage (MT25QL512ABB dual) - USB High-Speed via ULPI PHY (USB3320C-EZK) - USART1 console via ST-LINK V3 VCP - 10/100 Ethernet (LAN8742A RMII) - requires hardware modification - microSD card slot (8-bit SDMMC1) - 4 LEDs and user button - OpenAMP support for M7-M4 communication Software configuration: - RTC uses LSE (32.768kHz crystal) - lwIP networking stack with SSL/TLS (mbedTLS) - exFAT filesystem support Known limitations: - Ethernet requires hardware modification (ETH_MDC/SAI4_D1 pin conflict) Signed-off-by: Andrew Leech --- ports/stm32/boards/STM32H747I_DISCO/bdev.c | 45 +++ .../stm32/boards/STM32H747I_DISCO/board.json | 23 ++ ports/stm32/boards/STM32H747I_DISCO/board.md | 8 + .../boards/STM32H747I_DISCO/board_init.c | 71 +++++ .../stm32/boards/STM32H747I_DISCO/manifest.py | 8 + .../boards/STM32H747I_DISCO/mpconfigboard.h | 275 ++++++++++++++++++ .../boards/STM32H747I_DISCO/mpconfigboard.mk | 21 ++ ports/stm32/boards/STM32H747I_DISCO/pins.csv | 137 +++++++++ .../STM32H747I_DISCO/stm32h747_disco.ld | 63 ++++ .../STM32H747I_DISCO/stm32h7xx_hal_conf.h | 27 ++ 10 files changed, 678 insertions(+) create mode 100644 ports/stm32/boards/STM32H747I_DISCO/bdev.c create mode 100644 ports/stm32/boards/STM32H747I_DISCO/board.json create mode 100644 ports/stm32/boards/STM32H747I_DISCO/board.md create mode 100644 ports/stm32/boards/STM32H747I_DISCO/board_init.c create mode 100644 ports/stm32/boards/STM32H747I_DISCO/manifest.py create mode 100644 ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.h create mode 100644 ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.mk create mode 100644 ports/stm32/boards/STM32H747I_DISCO/pins.csv create mode 100644 ports/stm32/boards/STM32H747I_DISCO/stm32h747_disco.ld create mode 100644 ports/stm32/boards/STM32H747I_DISCO/stm32h7xx_hal_conf.h diff --git a/ports/stm32/boards/STM32H747I_DISCO/bdev.c b/ports/stm32/boards/STM32H747I_DISCO/bdev.c new file mode 100644 index 00000000000..16d27d38612 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/bdev.c @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * 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 "storage.h" +#include "qspi.h" + +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE +// Shared cache for QSPI block device +static mp_spiflash_cache_t spi_bdev_cache; +#endif + +// External QSPI flash uses hardware QSPI interface +const mp_spiflash_config_t spiflash_config = { + .bus_kind = MP_SPIFLASH_BUS_QSPI, + .bus.u_qspi.data = NULL, + .bus.u_qspi.proto = &qspi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE + .cache = &spi_bdev_cache, + #endif +}; + +spi_bdev_t spi_bdev; diff --git a/ports/stm32/boards/STM32H747I_DISCO/board.json b/ports/stm32/boards/STM32H747I_DISCO/board.json new file mode 100644 index 00000000000..ec4b31b56d9 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "DAC", + "Dual-core", + "Ethernet", + "External Flash", + "External RAM", + "microSD", + "USB" + ], + "images": [ + "stm32h747i_disco.jpg" + ], + "mcu": "stm32h7", + "product": "Discovery Kit H747I", + "thumbnail": "", + "url": "https://www.st.com/en/evaluation-tools/stm32h747i-disco.html", + "vendor": "ST Microelectronics" +} diff --git a/ports/stm32/boards/STM32H747I_DISCO/board.md b/ports/stm32/boards/STM32H747I_DISCO/board.md new file mode 100644 index 00000000000..9c6c3ab60c0 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/board.md @@ -0,0 +1,8 @@ +The Ethernet interface requires a hardware modification due to a pin conflict +between ETH_MDC (PC1) and the SAI4_D1 digital MEMS microphone. To enable +Ethernet, the MEMS microphone must be disconnected from PC1. See the +STM32H747I-DISCO user manual (UM2411) for modification details. + +The board includes additional hardware not currently configured in MicroPython: +4" LCD touchscreen (DSI), camera connector (DCMI), WM8994 audio codec, and +digital MEMS microphones. diff --git a/ports/stm32/boards/STM32H747I_DISCO/board_init.c b/ports/stm32/boards/STM32H747I_DISCO/board_init.c new file mode 100644 index 00000000000..55e673ec18d --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/board_init.c @@ -0,0 +1,71 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * 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 "storage.h" +#include "sdram.h" +#include "qspi.h" + +// Micron MT25QL flash on STM32H747I-DISCO (single bank, BK1) +static const mp_spiflash_chip_params_t chip_params_mt25ql = { + .jedec_id = 0, + .memory_size_bytes_log2 = MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3, + .qspi_prescaler = MICROPY_HW_QSPI_PRESCALER, + .qread_num_dummy = MICROPY_HW_QSPIFLASH_DUMMY_CYCLES, +}; + +void DISCO_board_early_init(void) { + HAL_InitTick(0); + + MICROPY_HW_BDEV_SPIFLASH->spiflash.chip_params = &chip_params_mt25ql; +} + +void DISCO_board_low_power(int mode) { + switch (mode) { + case 0: // Leave stop mode. + sdram_leave_low_power(); + break; + case 1: // Enter stop mode. + sdram_enter_low_power(); + break; + case 2: // Enter standby mode. + sdram_enter_power_down(); + break; + } + + // Enable QSPI deepsleep for modes 1 and 2 + mp_spiflash_deepsleep(&spi_bdev.spiflash, (mode != 0)); + + #if defined(M4_APP_ADDR) + // Signal Cortex-M4 to go to Standby mode. + if (mode == 2) { + __HAL_RCC_HSEM_CLK_ENABLE(); + HAL_HSEM_FastTake(0); + HAL_HSEM_Release(0, 0); + __HAL_RCC_HSEM_CLK_DISABLE(); + HAL_Delay(100); + } + #endif +} diff --git a/ports/stm32/boards/STM32H747I_DISCO/manifest.py b/ports/stm32/boards/STM32H747I_DISCO/manifest.py new file mode 100644 index 00000000000..103a5a37f19 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/manifest.py @@ -0,0 +1,8 @@ +include("$(PORT_DIR)/boards/manifest.py") + +# Networking +require("bundle-networking") + +# Utils +require("time") +require("logging") diff --git a/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.h b/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.h new file mode 100644 index 00000000000..9e78b2dd711 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.h @@ -0,0 +1,275 @@ +#define MICROPY_HW_BOARD_NAME "STM32H747I-DISCO" +#define MICROPY_HW_MCU_NAME "STM32H747" + +#define MICROPY_FATFS_EXFAT (1) +#define MICROPY_HW_ENABLE_RTC (1) +#define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_ADC (1) +#define MICROPY_HW_ENABLE_DAC (1) +#define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (1) +#define MICROPY_HW_ENABLE_SDCARD (1) +#define MICROPY_GC_SPLIT_HEAP (1) + +// Flash storage config (external QSPI flash) +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#define MICROPY_HW_SPIFLASH_SOFT_RESET (1) +#define MICROPY_HW_SPIFLASH_CHIP_PARAMS (1) +#define MICROPY_HW_QSPIFLASH_DUMMY_CYCLES (4) +#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) + +#define MICROPY_BOARD_EARLY_INIT DISCO_board_early_init +void DISCO_board_early_init(void); + +void DISCO_board_low_power(int mode); +#define MICROPY_BOARD_LEAVE_STOP DISCO_board_low_power(0); +#define MICROPY_BOARD_ENTER_STOP DISCO_board_low_power(1); +#define MICROPY_BOARD_ENTER_STANDBY DISCO_board_low_power(2); + +// PLL1 400MHz/50MHz for system and SDMMC +// HSE is 25MHz +#define MICROPY_HW_CLK_PLLM (5) +#define MICROPY_HW_CLK_PLLN (160) +#define MICROPY_HW_CLK_PLLP (2) +#define MICROPY_HW_CLK_PLLQ (8) +#define MICROPY_HW_CLK_PLLR (2) +#define MICROPY_HW_CLK_PLLVCI (RCC_PLL1VCIRANGE_2) +#define MICROPY_HW_CLK_PLLVCO (RCC_PLL1VCOWIDE) +#define MICROPY_HW_CLK_PLLFRAC (0) + +// PLL2 240MHz for FMC and QSPI +#define MICROPY_HW_CLK_PLL2M (5) +#define MICROPY_HW_CLK_PLL2N (96) +#define MICROPY_HW_CLK_PLL2P (2) +#define MICROPY_HW_CLK_PLL2Q (2) +#define MICROPY_HW_CLK_PLL2R (10) +#define MICROPY_HW_CLK_PLL2VCI (RCC_PLL2VCIRANGE_2) +#define MICROPY_HW_CLK_PLL2VCO (RCC_PLL2VCOWIDE) +#define MICROPY_HW_CLK_PLL2FRAC (0) + +// PLL3 for USB and other peripherals +#define MICROPY_HW_CLK_PLL3M (5) +#define MICROPY_HW_CLK_PLL3N (96) +#define MICROPY_HW_CLK_PLL3P (8) +#define MICROPY_HW_CLK_PLL3Q (8) +#define MICROPY_HW_CLK_PLL3R (4) +#define MICROPY_HW_CLK_PLL3VCI (RCC_PLL3VCIRANGE_2) +#define MICROPY_HW_CLK_PLL3VCO (RCC_PLL3VCOWIDE) +#define MICROPY_HW_CLK_PLL3FRAC (0) + +// HSE in bypass mode (25MHz oscillator) +#define MICROPY_HW_CLK_USE_BYPASS (1) + +// Bus clock divider values +#define MICROPY_HW_CLK_AHB_DIV (RCC_HCLK_DIV2) +#define MICROPY_HW_CLK_APB1_DIV (RCC_APB1_DIV2) +#define MICROPY_HW_CLK_APB2_DIV (RCC_APB2_DIV2) +#define MICROPY_HW_CLK_APB3_DIV (RCC_APB3_DIV2) +#define MICROPY_HW_CLK_APB4_DIV (RCC_APB4_DIV2) + +// Peripheral clock sources +#define MICROPY_HW_RCC_HSI48_STATE (RCC_HSI48_ON) +#define MICROPY_HW_RCC_USB_CLKSOURCE (RCC_USBCLKSOURCE_PLL3) +#define MICROPY_HW_RCC_FMC_CLKSOURCE (RCC_FMCCLKSOURCE_PLL2) +#define MICROPY_HW_RCC_RNG_CLKSOURCE (RCC_RNGCLKSOURCE_HSI48) +#define MICROPY_HW_RCC_ADC_CLKSOURCE (RCC_ADCCLKSOURCE_PLL3) +#define MICROPY_HW_RCC_SDMMC_CLKSOURCE (RCC_SDMMCCLKSOURCE_PLL) +#define MICROPY_HW_RCC_FDCAN_CLKSOURCE (RCC_FDCANCLKSOURCE_PLL) +#define MICROPY_HW_RCC_QSPI_CLKSOURCE (RCC_QSPICLKSOURCE_PLL2) + +// SMPS configuration +#define MICROPY_HW_PWR_SMPS_CONFIG (PWR_DIRECT_SMPS_SUPPLY) + +// Configure the analog switches for dual-pad pins +#define MICROPY_HW_ANALOG_SWITCH_PA0 (SYSCFG_SWITCH_PA0_OPEN) +#define MICROPY_HW_ANALOG_SWITCH_PA1 (SYSCFG_SWITCH_PA1_OPEN) +#define MICROPY_HW_ANALOG_SWITCH_PC2 (SYSCFG_SWITCH_PC2_OPEN) +#define MICROPY_HW_ANALOG_SWITCH_PC3 (SYSCFG_SWITCH_PC3_OPEN) + +// Using LSE for RTC (32.768kHz crystal on PC14/PC15) +#define MICROPY_HW_RTC_USE_LSE (1) +#define MICROPY_HW_RTC_USE_US (1) +#define MICROPY_HW_RTC_USE_CALOUT (0) + +// QSPI flash for storage (single bank, dual-flash mode disabled in driver) +#define MICROPY_HW_QSPI_PRESCALER (2) // 120MHz +#define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (27) // 128Mbit = 16MB (single MT25QL bank) +#define MICROPY_HW_SPIFLASH_SIZE_BITS (128 * 1024 * 1024) +#define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI_BK1_NCS) +#define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI_CLK) +#define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI_BK1_IO0) +#define MICROPY_HW_QSPIFLASH_IO1 (pyb_pin_QSPI_BK1_IO1) +#define MICROPY_HW_QSPIFLASH_IO2 (pyb_pin_QSPI_BK1_IO2) +#define MICROPY_HW_QSPIFLASH_IO3 (pyb_pin_QSPI_BK1_IO3) + +// SPI flash block device config +extern const struct _mp_spiflash_config_t spiflash_config; +extern struct _spi_bdev_t spi_bdev; +#define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) +#define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) +#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) + +// Flash latency +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 + +// UART config (directly accessible on Arduino header pins) +#define MICROPY_HW_UART1_TX (pyb_pin_UART1_TX) +#define MICROPY_HW_UART1_RX (pyb_pin_UART1_RX) +// UART REPL disabled to avoid slowing USB HS REPL throughput +// #define MICROPY_HW_UART_REPL PYB_UART_1 +// #define MICROPY_HW_UART_REPL_BAUD 115200 + +#define MICROPY_HW_UART8_TX (pin_J8) +#define MICROPY_HW_UART8_RX (pin_J9) + +// I2C buses +#define MICROPY_HW_I2C4_SCL (pin_D12) +#define MICROPY_HW_I2C4_SDA (pin_D13) + +// SPI config +#define MICROPY_HW_SPI5_NSS (pin_K1) +#define MICROPY_HW_SPI5_SCK (pin_K0) +#define MICROPY_HW_SPI5_MISO (pin_J11) +#define MICROPY_HW_SPI5_MOSI (pin_J10) + +// USRSW is pulled high. Pressing the button makes the input go high. +#define MICROPY_HW_USRSW_PIN (pin_C13) +#define MICROPY_HW_USRSW_PULL (GPIO_NOPULL) +#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_RISING) +#define MICROPY_HW_USRSW_PRESSED (1) + +// LEDs +#define MICROPY_HW_LED1 (pin_I12) // green +#define MICROPY_HW_LED2 (pin_I13) // orange +#define MICROPY_HW_LED3 (pin_I14) // red +#define MICROPY_HW_LED4 (pin_I15) // blue +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) + +// USB config - Use High-Speed via ULPI PHY +#define MICROPY_HW_USB_HS (1) +#define MICROPY_HW_USB_HS_ULPI (1) +#define MICROPY_HW_USB_HS_ULPI_NXT (pyb_pin_USB_HS_NXT) +#define MICROPY_HW_USB_HS_ULPI_STP (pyb_pin_USB_HS_STP) +#define MICROPY_HW_USB_HS_ULPI_DIR (pyb_pin_USB_HS_DIR) +#define MICROPY_HW_USB_HS_ULPI3320 (1) +#define MICROPY_HW_USB_MAIN_DEV (USB_PHY_HS_ID) +#define GPIO_AF10_OTG_HS (GPIO_AF10_OTG1_HS) + +#define MICROPY_HW_USB_CDC_RX_DATA_SIZE (1024) +#define MICROPY_HW_USB_CDC_TX_DATA_SIZE (1024) + +// SD card (SDMMC1, 4-bit) +#define MICROPY_HW_SDCARD_SDMMC (1) +#define MICROPY_HW_SDCARD_CK (pyb_pin_SDMMC1_CK) +#define MICROPY_HW_SDCARD_CMD (pyb_pin_SDMMC1_CMD) +#define MICROPY_HW_SDCARD_D0 (pyb_pin_SDMMC1_D0) +#define MICROPY_HW_SDCARD_D1 (pyb_pin_SDMMC1_D1) +#define MICROPY_HW_SDCARD_D2 (pyb_pin_SDMMC1_D2) +#define MICROPY_HW_SDCARD_D3 (pyb_pin_SDMMC1_D3) +#define MICROPY_HW_SDCARD_DETECT_PIN (pyb_pin_SD_DETECT) +#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) +#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) + +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pyb_pin_ETH_MDC) +#define MICROPY_HW_ETH_MDIO (pyb_pin_ETH_MDIO) +#define MICROPY_HW_ETH_RMII_REF_CLK (pyb_pin_ETH_RMII_REF_CLK) +#define MICROPY_HW_ETH_RMII_CRS_DV (pyb_pin_ETH_RMII_CRS_DV) +#define MICROPY_HW_ETH_RMII_RXD0 (pyb_pin_ETH_RMII_RXD0) +#define MICROPY_HW_ETH_RMII_RXD1 (pyb_pin_ETH_RMII_RXD1) +#define MICROPY_HW_ETH_RMII_TX_EN (pyb_pin_ETH_RMII_TX_EN) +#define MICROPY_HW_ETH_RMII_TXD0 (pyb_pin_ETH_RMII_TXD0) +#define MICROPY_HW_ETH_RMII_TXD1 (pyb_pin_ETH_RMII_TXD1) + +// SDRAM configuration - 32MB (256Mbit) +#define MICROPY_HW_SDRAM_SIZE (256 / 8 * 1024 * 1024) // 256 Mbit = 32 MB +#define MICROPY_HW_SDRAM_STARTUP_TEST (1) +#define MICROPY_HW_SDRAM_TEST_FAIL_ON_ERROR (1) + +// Timing configuration for 240MHz/2=120MHz (8.33ns) +#define MICROPY_HW_SDRAM_CLOCK_PERIOD 2 +#define MICROPY_HW_SDRAM_CAS_LATENCY 2 +#define MICROPY_HW_SDRAM_FREQUENCY_KHZ (120000) // 120 MHz +#define MICROPY_HW_SDRAM_TIMING_TMRD (2) +#define MICROPY_HW_SDRAM_TIMING_TXSR (6) +#define MICROPY_HW_SDRAM_TIMING_TRAS (4) +#define MICROPY_HW_SDRAM_TIMING_TRC (6) +#define MICROPY_HW_SDRAM_TIMING_TWR (2) +#define MICROPY_HW_SDRAM_TIMING_TRP (2) +#define MICROPY_HW_SDRAM_TIMING_TRCD (2) + +#define MICROPY_HW_SDRAM_ROW_BITS_NUM 12 +#define MICROPY_HW_SDRAM_MEM_BUS_WIDTH 32 +#define MICROPY_HW_SDRAM_REFRESH_CYCLES 4096 + +#define MICROPY_HW_SDRAM_COLUMN_BITS_NUM 9 +#define MICROPY_HW_SDRAM_INTERN_BANKS_NUM 4 +#define MICROPY_HW_SDRAM_RPIPE_DELAY 0 +#define MICROPY_HW_SDRAM_RBURST (1) +#define MICROPY_HW_SDRAM_WRITE_PROTECTION (0) + +#define MICROPY_HW_SDRAM_AUTOREFRESH_NUM (8) +#define MICROPY_HW_SDRAM_BURST_LENGTH 1 +#define MICROPY_HW_SDRAM_REFRESH_RATE (64) // ms + +// SDRAM FMC pin configuration (see pins.csv for CPU pin mapping) +#define MICROPY_HW_FMC_SDCKE1 (pyb_pin_FMC_SDCKE1) +#define MICROPY_HW_FMC_SDNE1 (pyb_pin_FMC_SDNE1) +#define MICROPY_HW_FMC_SDCLK (pyb_pin_FMC_SDCLK) +#define MICROPY_HW_FMC_SDNCAS (pyb_pin_FMC_SDNCAS) +#define MICROPY_HW_FMC_SDNRAS (pyb_pin_FMC_SDNRAS) +#define MICROPY_HW_FMC_SDNWE (pyb_pin_FMC_SDNWE) +#define MICROPY_HW_FMC_BA0 (pyb_pin_FMC_BA0) +#define MICROPY_HW_FMC_BA1 (pyb_pin_FMC_BA1) +#define MICROPY_HW_FMC_NBL0 (pyb_pin_FMC_NBL0) +#define MICROPY_HW_FMC_NBL1 (pyb_pin_FMC_NBL1) +#define MICROPY_HW_FMC_NBL2 (pyb_pin_FMC_NBL2) +#define MICROPY_HW_FMC_NBL3 (pyb_pin_FMC_NBL3) +#define MICROPY_HW_FMC_A0 (pyb_pin_FMC_A0) +#define MICROPY_HW_FMC_A1 (pyb_pin_FMC_A1) +#define MICROPY_HW_FMC_A2 (pyb_pin_FMC_A2) +#define MICROPY_HW_FMC_A3 (pyb_pin_FMC_A3) +#define MICROPY_HW_FMC_A4 (pyb_pin_FMC_A4) +#define MICROPY_HW_FMC_A5 (pyb_pin_FMC_A5) +#define MICROPY_HW_FMC_A6 (pyb_pin_FMC_A6) +#define MICROPY_HW_FMC_A7 (pyb_pin_FMC_A7) +#define MICROPY_HW_FMC_A8 (pyb_pin_FMC_A8) +#define MICROPY_HW_FMC_A9 (pyb_pin_FMC_A9) +#define MICROPY_HW_FMC_A10 (pyb_pin_FMC_A10) +#define MICROPY_HW_FMC_A11 (pyb_pin_FMC_A11) +#define MICROPY_HW_FMC_A12 (pyb_pin_FMC_A12) +#define MICROPY_HW_FMC_D0 (pyb_pin_FMC_D0) +#define MICROPY_HW_FMC_D1 (pyb_pin_FMC_D1) +#define MICROPY_HW_FMC_D2 (pyb_pin_FMC_D2) +#define MICROPY_HW_FMC_D3 (pyb_pin_FMC_D3) +#define MICROPY_HW_FMC_D4 (pyb_pin_FMC_D4) +#define MICROPY_HW_FMC_D5 (pyb_pin_FMC_D5) +#define MICROPY_HW_FMC_D6 (pyb_pin_FMC_D6) +#define MICROPY_HW_FMC_D7 (pyb_pin_FMC_D7) +#define MICROPY_HW_FMC_D8 (pyb_pin_FMC_D8) +#define MICROPY_HW_FMC_D9 (pyb_pin_FMC_D9) +#define MICROPY_HW_FMC_D10 (pyb_pin_FMC_D10) +#define MICROPY_HW_FMC_D11 (pyb_pin_FMC_D11) +#define MICROPY_HW_FMC_D12 (pyb_pin_FMC_D12) +#define MICROPY_HW_FMC_D13 (pyb_pin_FMC_D13) +#define MICROPY_HW_FMC_D14 (pyb_pin_FMC_D14) +#define MICROPY_HW_FMC_D15 (pyb_pin_FMC_D15) +#define MICROPY_HW_FMC_D16 (pyb_pin_FMC_D16) +#define MICROPY_HW_FMC_D17 (pyb_pin_FMC_D17) +#define MICROPY_HW_FMC_D18 (pyb_pin_FMC_D18) +#define MICROPY_HW_FMC_D19 (pyb_pin_FMC_D19) +#define MICROPY_HW_FMC_D20 (pyb_pin_FMC_D20) +#define MICROPY_HW_FMC_D21 (pyb_pin_FMC_D21) +#define MICROPY_HW_FMC_D22 (pyb_pin_FMC_D22) +#define MICROPY_HW_FMC_D23 (pyb_pin_FMC_D23) +#define MICROPY_HW_FMC_D24 (pyb_pin_FMC_D24) +#define MICROPY_HW_FMC_D25 (pyb_pin_FMC_D25) +#define MICROPY_HW_FMC_D26 (pyb_pin_FMC_D26) +#define MICROPY_HW_FMC_D27 (pyb_pin_FMC_D27) +#define MICROPY_HW_FMC_D28 (pyb_pin_FMC_D28) +#define MICROPY_HW_FMC_D29 (pyb_pin_FMC_D29) +#define MICROPY_HW_FMC_D30 (pyb_pin_FMC_D30) +#define MICROPY_HW_FMC_D31 (pyb_pin_FMC_D31) diff --git a/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.mk b/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.mk new file mode 100644 index 00000000000..72c9eead382 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/mpconfigboard.mk @@ -0,0 +1,21 @@ +USE_MBOOT = 0 +USE_PYDFU = 0 +# For dual core HAL drivers. +CFLAGS += -DCORE_CM7 + +# MCU settings +MCU_SERIES = h7 +CMSIS_MCU = STM32H747xx +MICROPY_FLOAT_IMPL = double +AF_FILE = boards/stm32h743_af.csv +LD_FILES = boards/STM32H747I_DISCO/stm32h747_disco.ld +TEXT0_ADDR = 0x08000000 + +# MicroPython settings +MICROPY_PY_LWIP = 1 +MICROPY_PY_SSL = 1 +MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 + +FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py diff --git a/ports/stm32/boards/STM32H747I_DISCO/pins.csv b/ports/stm32/boards/STM32H747I_DISCO/pins.csv new file mode 100644 index 00000000000..b2badc83a6a --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/pins.csv @@ -0,0 +1,137 @@ +# Arduino Header +A0,PA4 +A1,PF10 +A2,PA0_C +A3,PA1_C +A4,PC2_C +A5,PC3_C +D0,PJ9 +D1,PJ8 +D2,PJ3 +D3,PF8 +D4,PJ4 +D5,PA6 +D6,PJ7 +D7,PJ0 +D8,PJ5 +D9,PJ6 +D10,PK1 +D11,PJ10 +D12,PJ11 +D13,PK0 +D14,PD13 +D15,PD12 + +# LEDs +LED1,PI12 +LED2,PI13 +LED3,PI14 +LED4,PI15 + +# Button +SW,PC13 + +# SD Card +SD_DETECT,PI8 +-SDMMC1_CK,PC12 +-SDMMC1_CMD,PD2 +-SDMMC1_D0,PC8 +-SDMMC1_D1,PC9 +-SDMMC1_D2,PC10 +-SDMMC1_D3,PC11 + +# UART (ST-LINK VCP) +-UART1_TX,PA9 +-UART1_RX,PA10 + +# USB HS ULPI +-USB_HS_CLK,PA5 +-USB_HS_STP,PC0 +-USB_HS_NXT,PH4 +-USB_HS_DIR,PI11 +-USB_HS_D0,PA3 +-USB_HS_D1,PB0 +-USB_HS_D2,PB1 +-USB_HS_D3,PB5 +-USB_HS_D4,PB10 +-USB_HS_D5,PB11 +-USB_HS_D6,PB12 +-USB_HS_D7,PB13 + +# Ethernet RMII +-ETH_MDC,PC1 +-ETH_MDIO,PA2 +-ETH_RMII_REF_CLK,PA1 +-ETH_RMII_CRS_DV,PA7 +-ETH_RMII_RXD0,PC4 +-ETH_RMII_RXD1,PC5 +-ETH_RMII_TX_EN,PG11 +-ETH_RMII_TXD0,PG13 +-ETH_RMII_TXD1,PG12 + +# QSPI Flash +-QSPI_CLK,PB2 +-QSPI_BK1_NCS,PG6 +-QSPI_BK1_IO0,PD11 +-QSPI_BK1_IO1,PF9 +-QSPI_BK1_IO2,PF7 +-QSPI_BK1_IO3,PF6 + +# FMC SDRAM +-FMC_SDCKE1,PH7 +-FMC_SDNE1,PH6 +-FMC_SDCLK,PG8 +-FMC_SDNCAS,PG15 +-FMC_SDNRAS,PF11 +-FMC_SDNWE,PH5 +-FMC_BA0,PG4 +-FMC_BA1,PG5 +-FMC_NBL0,PE0 +-FMC_NBL1,PE1 +-FMC_NBL2,PI4 +-FMC_NBL3,PI5 +-FMC_A0,PF0 +-FMC_A1,PF1 +-FMC_A2,PF2 +-FMC_A3,PF3 +-FMC_A4,PF4 +-FMC_A5,PF5 +-FMC_A6,PF12 +-FMC_A7,PF13 +-FMC_A8,PF14 +-FMC_A9,PF15 +-FMC_A10,PG0 +-FMC_A11,PG1 +-FMC_A12,PG2 +-FMC_D0,PD14 +-FMC_D1,PD15 +-FMC_D2,PD0 +-FMC_D3,PD1 +-FMC_D4,PE7 +-FMC_D5,PE8 +-FMC_D6,PE9 +-FMC_D7,PE10 +-FMC_D8,PE11 +-FMC_D9,PE12 +-FMC_D10,PE13 +-FMC_D11,PE14 +-FMC_D12,PE15 +-FMC_D13,PD8 +-FMC_D14,PD9 +-FMC_D15,PD10 +-FMC_D16,PH8 +-FMC_D17,PH9 +-FMC_D18,PH10 +-FMC_D19,PH11 +-FMC_D20,PH12 +-FMC_D21,PH13 +-FMC_D22,PH14 +-FMC_D23,PH15 +-FMC_D24,PI0 +-FMC_D25,PI1 +-FMC_D26,PI2 +-FMC_D27,PI3 +-FMC_D28,PI6 +-FMC_D29,PI7 +-FMC_D30,PI9 +-FMC_D31,PI10 diff --git a/ports/stm32/boards/STM32H747I_DISCO/stm32h747_disco.ld b/ports/stm32/boards/STM32H747I_DISCO/stm32h747_disco.ld new file mode 100644 index 00000000000..cddc2f018cc --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/stm32h747_disco.ld @@ -0,0 +1,63 @@ +/* + GNU linker script for STM32H747I-DISCO +*/ + +/* Specify the memory areas */ +MEMORY +{ + ITCM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K + DTCM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K /* AXI SRAM D1 */ + SRAM1 (xrw) : ORIGIN = 0x30000000, LENGTH = 128K /* SRAM1 D2 */ + SRAM2 (xrw) : ORIGIN = 0x30020000, LENGTH = 128K /* SRAM2 D2 */ + SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3 D2 */ + SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ + SDRAM (xrw) : ORIGIN = 0xD0000000, LENGTH = 32M /* SDRAM bank 1 */ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1536K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ +} + +/* produce a link error if there is not this amount of RAM for these sections */ +_minimum_stack_size = 2K; +_minimum_heap_size = 16K; + +/* Define the stack. The stack is full descending so begins just above last byte + of RAM. Note that EABI requires the stack to be 8-byte aligned for a call. */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve; +_sstack = _estack - 16K; /* tunable */ + +/* RAM heap extents */ +_ram_start = ORIGIN(RAM); +_ram_end = ORIGIN(RAM) + LENGTH(RAM); +_heap_start = _ebss; /* heap starts just after statically allocated memory */ +_heap_end = _sstack; + +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + +INCLUDE common_basic.ld + +SECTIONS +{ + /* GC blocks addresses and sizes */ + .gc.blocks.table (READONLY) : { + . = ALIGN(4); + _gc_blocks_table_start = .; + + LONG (ORIGIN(SRAM1)); + LONG (128K); + + LONG (ORIGIN(SDRAM) + 0M); + LONG (8M); + + LONG (ORIGIN(SDRAM) + 8M); + LONG (8M); + + LONG (ORIGIN(SDRAM) + 16M); + LONG (16M); + + _gc_blocks_table_end = .; + . = ALIGN(4); + } > FLASH +} diff --git a/ports/stm32/boards/STM32H747I_DISCO/stm32h7xx_hal_conf.h b/ports/stm32/boards/STM32H747I_DISCO/stm32h7xx_hal_conf.h new file mode 100644 index 00000000000..a73afd13de7 --- /dev/null +++ b/ports/stm32/boards/STM32H747I_DISCO/stm32h7xx_hal_conf.h @@ -0,0 +1,27 @@ +/* This file is part of the MicroPython project, http://micropython.org/ + * The MIT License (MIT) + * Copyright (c) 2019 Damien P. George + */ +#ifndef MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H +#define MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H + +// Oscillator values in Hz +#define HSE_VALUE (25000000) +#define LSE_VALUE (32768) +#define EXTERNAL_CLOCK_VALUE (12288000) + +// Oscillator timeouts in ms +#define HSE_STARTUP_TIMEOUT (5000) +#define LSE_STARTUP_TIMEOUT (5000) + +// Required for OpenAMP dual-core synchronization +#define HAL_HSEM_MODULE_ENABLED +#define HAL_QSPI_MODULE_ENABLED + +#ifdef HAL_HSEM_MODULE_ENABLED +#include "stm32h7xx_hal_hsem.h" +#endif + +#include "boards/stm32h7xx_hal_conf_base.h" + +#endif // MICROPY_INCLUDED_STM32H7XX_HAL_CONF_H From a23f69ea68ce287eddffaea478f6a84e7cdc75db Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 20 Apr 2026 15:43:35 +1000 Subject: [PATCH 1966/2098] stm32/machine_adc: Fix ADC V2 CR register writes and timeouts. Replace all |= on ADC CR with mask-and-set using ADC_CR_BITS_PROPERTY_RS from the LL HAL. On ADC V2 the CR command bits are write-1-to-set, so |= can propagate stale bits (e.g. ADSTP left by HAL_ADC_Stop), violating the RM requirement that ADSTART and ADSTP must not be asserted simultaneously. Add recovery logic in adc_config_channel for stuck ADSTART: stop the conversion, disable, and re-enable the ADC before reconfiguring SQR1. Replace unbounded while loops in ADC V2 paths with ADC_WAIT, a busy-wait macro using mp_hal_ticks_ms with a 250ms timeout. Signed-off-by: Andrew Leech --- ports/stm32/machine_adc.c | 59 +++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index a6e960911bd..8968b3a939d 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -28,6 +28,7 @@ // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. #include "py/mphal.h" +#include "py/runtime.h" #include "adc.h" #if defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32U5) || defined(STM32WB) || defined(STM32WL) @@ -96,6 +97,17 @@ // Timeout for waiting for end-of-conversion #define ADC_EOC_TIMEOUT_MS (10) +#if ADC_V2 +// Busy-wait with timeout for ADC peripheral operations. 250ms is a generous +// upper bound; normal operations complete in microseconds. +#define ADC_WAIT_TIMEOUT_MS (250) +#define ADC_WAIT(cond) do { \ + uint32_t _t0 = mp_hal_ticks_ms(); \ + while ((cond) && (mp_hal_ticks_ms() - _t0 < ADC_WAIT_TIMEOUT_MS)) { \ + } \ +} while (0) +#endif + // Channel IDs for machine.ADC object typedef enum _machine_adc_internal_ch_t { // Regular external ADC inputs (0..19) @@ -277,19 +289,18 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) { #else LL_ADC_StartCalibration(adc, LL_ADC_CALIB_OFFSET_LINEARITY, LL_ADC_SINGLE_ENDED); #endif - while (LL_ADC_IsCalibrationOnGoing(adc)) { - } + ADC_WAIT(LL_ADC_IsCalibrationOnGoing(adc)); } if (adc->CR & ADC_CR_ADEN) { // ADC enabled, need to disable it to change configuration if (adc->CR & ADC_CR_ADSTART) { - adc->CR |= ADC_CR_ADSTP; - while (adc->CR & ADC_CR_ADSTP) { - } + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTP; + ADC_WAIT(adc->CR & ADC_CR_ADSTP); } - adc->CR |= ADC_CR_ADDIS; - while (adc->CR & ADC_CR_ADDIS) { + if (!(adc->CR & ADC_CR_ADSTART)) { + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADDIS; + ADC_WAIT(adc->CR & ADC_CR_ADDIS); } } #endif @@ -349,18 +360,26 @@ static int adc_get_bits(ADC_TypeDef *adc) { return adc_cr_to_bits_table[res]; } -static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) { +static bool adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) { #if ADC_V2 - if (!(adc->CR & ADC_CR_ADEN)) { - if (adc->CR & 0x3f) { - // Cannot enable ADC with CR!=0 - return; + if (!(adc->CR & ADC_CR_ADEN) || (adc->CR & ADC_CR_ADSTART)) { + if (adc->CR & ADC_CR_BITS_PROPERTY_RS) { + // Stop and disable before (re-)enabling. + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTP; + ADC_WAIT(adc->CR & ADC_CR_ADSTP); + if (adc->CR & ADC_CR_ADSTP) { + return false; + } + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADDIS; + ADC_WAIT(adc->CR & ADC_CR_ADDIS); + if (adc->CR & ADC_CR_ADDIS) { + return false; + } } - adc->ISR = ADC_ISR_ADRDY; // clear ADRDY - adc->CR |= ADC_CR_ADEN; + adc->ISR = ADC_ISR_ADRDY; + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADEN; adc_stabilisation_delay_us(ADC_STAB_DELAY_US); - while (!(adc->ISR & ADC_ISR_ADRDY)) { - } + ADC_WAIT(!(adc->ISR & ADC_ISR_ADRDY)); } #else if (!(adc->CR2 & ADC_CR2_ADON)) { @@ -508,6 +527,7 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time #endif + return true; } static uint32_t adc_read_channel(ADC_TypeDef *adc) { @@ -523,7 +543,8 @@ static uint32_t adc_read_channel(ADC_TypeDef *adc) { #endif { #if ADC_V2 - adc->CR |= ADC_CR_ADSTART; + adc->ISR = ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_OVR; // clear stale flags + adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTART; #else adc->CR2 |= ADC_CR2_SWSTART; #endif @@ -542,7 +563,9 @@ uint32_t adc_config_and_read_u16(ADC_TypeDef *adc, uint32_t channel, uint32_t sa channel = adc_ll_channel(channel); // Select, configure and read the channel. - adc_config_channel(adc, channel, sample_time); + if (!adc_config_channel(adc, channel, sample_time)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ADC not responding")); + } uint32_t raw = adc_read_channel(adc); // If VBAT was sampled then deselect it to prevent battery drain. From d1c61ca52a44d8fa9e0bc01c650b0d5e0283b7cc Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 22 Mar 2026 00:02:48 +1100 Subject: [PATCH 1967/2098] stm32/boards: Extend RAM section by 192K on STM32H723. This extends the RAM section into the adjacent ITCM RAM block, increasing the total GC heap size by the same amount (because the GC heap uses all available remaining RAM). This affects only the NUCLEO_H723ZG board, bringing available GC heap from 73984 to 266048 bytes. Signed-off-by: Damien George --- ports/stm32/boards/stm32h723.ld | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/stm32h723.ld b/ports/stm32/boards/stm32h723.ld index d1e934ed312..ffd69d6b7be 100644 --- a/ports/stm32/boards/stm32h723.ld +++ b/ports/stm32/boards/stm32h723.ld @@ -9,8 +9,10 @@ MEMORY FLASH_APP (rx) : ORIGIN = 0x08020000, LENGTH = 640K /* sectors 1-5 */ FLASH_FS (r) : ORIGIN = 0x080c0000, LENGTH = 256K /* sectors 6-7 */ DTCM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* Used for FS storage cache */ - RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 128K /* AXI SRAM (could extend +192K from ITCM) */ + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 320K /* AXI SRAM (128k) + ITCM/AXI SRAM (192k) */ RAM_SRAM1 (xrw) : ORIGIN = 0x30000000, LENGTH = 16K /* SRAM1 */ + RAM_SRAM2 (xrw) : ORIGIN = 0x30004000, LENGTH = 16K /* SRAM2 */ + RAM_SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K /* SRAM4 */ } /* produce a link error if there is not this amount of RAM for these sections */ From 9ca3b7412eb39f5df558287a2102fc1702786f52 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 1 Nov 2025 15:18:13 +1100 Subject: [PATCH 1968/2098] stm32/usbd_conf: Fix USB VBUS sensing for newer STM32F4/F7 HAL versions. Newer STM32F4/F7 variants (and their HAL headers) use a single USB_OTG_GCCFG_VBDEN register bit for VBUS detection instead of the older NOVBUSSENS/VBUSASEN/VBUSBSEN bits. The existing TinyUSB VBUS sensing code only handles the older register layout, so boards with the newer HAL silently skip VBUS configuration. This adds a #if defined(USB_OTG_GCCFG_VBDEN) branch that enables or disables VBUS detection via the new register bit, falling through to the existing code for older variants. Signed-off-by: Andrew Leech --- ports/stm32/usbd_conf.c | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/ports/stm32/usbd_conf.c b/ports/stm32/usbd_conf.c index da0e71a7e49..0c905e0dffb 100644 --- a/ports/stm32/usbd_conf.c +++ b/ports/stm32/usbd_conf.c @@ -65,6 +65,30 @@ PCD_HandleTypeDef pcd_hs_handle; #define OTG_HS_IRQn USB1_OTG_HS_IRQn #endif +// Configure VBUS sensing for TinyUSB on STM32F4/F7. The DWC2 PHY init only +// sets the PWRDWN bit but doesn't configure VBUS sensing in the GCCFG register. +#if MICROPY_HW_TINYUSB_STACK && (defined(STM32F4) || defined(STM32F7)) +static inline void mp_usbd_configure_vbus_sensing(USB_OTG_GlobalTypeDef *USBx) { + #if defined(USB_OTG_GCCFG_VBDEN) + // Newer STM32F4/F7 with VBDEN register bit. + #if defined(MICROPY_HW_USB_VBUS_DETECT_PIN) + USBx->GCCFG |= USB_OTG_GCCFG_VBDEN; + #else + USBx->GCCFG &= ~USB_OTG_GCCFG_VBDEN; + #endif + #else + // Older STM32F4 with separate VBUSASEN/VBUSBSEN/NOVBUSSENS register bits. + #if defined(MICROPY_HW_USB_VBUS_DETECT_PIN) + USBx->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USBx->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; + #else + USBx->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; + USBx->GCCFG &= ~(USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_VBUSASEN); + #endif + #endif +} +#endif + #if MICROPY_HW_USB_FS static void mp_usbd_ll_init_fs(void) { { @@ -172,20 +196,8 @@ static void mp_usbd_ll_init_fs(void) { HAL_NVIC_EnableIRQ(OTG_FS_IRQn); #endif - #if MICROPY_HW_TINYUSB_STACK - // Configure VBUS sensing for TinyUSB on STM32F4/F7 which have separate GCCFG register - // The DWC2 PHY init only sets PWRDWN bit, but doesn't configure VBUS sensing - #if defined(STM32F4) || defined(STM32F7) - #if defined(MICROPY_HW_USB_VBUS_DETECT_PIN) - // Enable VBUS sensing in "B device" mode (using PA9) - USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; - USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; - #else - // Force VBUS valid (no VBUS detect pin configured) - USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; - USB_OTG_FS->GCCFG &= ~(USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_VBUSASEN); - #endif - #endif + #if MICROPY_HW_TINYUSB_STACK && (defined(STM32F4) || defined(STM32F7)) + mp_usbd_configure_vbus_sensing(USB_OTG_FS); #endif } } @@ -306,6 +318,10 @@ static void mp_usbd_ll_init_hs(void) { #endif + #if MICROPY_HW_TINYUSB_STACK && (defined(STM32F4) || defined(STM32F7)) + mp_usbd_configure_vbus_sensing(USB_OTG_HS); + #endif + #else // !MICROPY_HW_USB_HS_IN_FS // Configure USB HS GPIOs From 1dda2314918887fcf8f07f200a8feef00d7a4ef3 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 21 Jun 2025 22:45:51 +1000 Subject: [PATCH 1969/2098] ports: Consolidate TinyUSB src include across build systems. Consolidates TinyUSB source includes across multiple ports (alif, mimxrt, renesas-ra, samd) to use a common approach for TinyUSB integration. This cleanup ensures consistent TinyUSB usage patterns across ports and simplifies maintenance. Signed-off-by: Andrew Leech --- ports/alif/alif.mk | 11 +++-------- ports/mimxrt/Makefile | 20 ++++++-------------- ports/nrf/Makefile | 15 +++++++-------- ports/renesas-ra/Makefile | 17 ++++------------- ports/samd/Makefile | 14 ++++++-------- ports/stm32/Makefile | 4 ++-- 6 files changed, 28 insertions(+), 53 deletions(-) diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 42b773e7407..469e13f3ee1 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -177,14 +177,9 @@ DRIVERS_SRC_C += $(addprefix drivers/,\ dht/dht.c \ ) -TINYUSB_SRC_C += \ - lib/tinyusb/src/tusb.c \ - lib/tinyusb/src/class/cdc/cdc_device.c \ - lib/tinyusb/src/class/msc/msc_device.c \ - lib/tinyusb/src/common/tusb_fifo.c \ - lib/tinyusb/src/device/usbd.c \ - lib/tinyusb/src/device/usbd_control.c \ - tinyusb_port/tusb_alif_dcd.c \ +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, $(TINYUSB_SRC_C))) +TINYUSB_SRC_C += tinyusb_port/tusb_alif_dcd.c ALIF_SRC_C += $(addprefix $(ALIF_DFP_REL_TOP)/,\ Device/common/source/clk.c \ diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index a57ff68a497..5bf2690c236 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -93,19 +93,11 @@ endif # ============================================================================= # TinyUSB Stack source -SRC_TINYUSB_C += \ - lib/tinyusb/src/class/cdc/cdc_device.c \ - lib/tinyusb/src/class/dfu/dfu_rt_device.c \ - lib/tinyusb/src/class/hid/hid_device.c \ - lib/tinyusb/src/class/midi/midi_device.c \ - lib/tinyusb/src/class/msc/msc_device.c \ - lib/tinyusb/src/class/usbtmc/usbtmc_device.c \ - lib/tinyusb/src/class/vendor/vendor_device.c \ - lib/tinyusb/src/common/tusb_fifo.c \ - lib/tinyusb/src/device/usbd.c \ - lib/tinyusb/src/device/usbd_control.c \ - lib/tinyusb/src/portable/chipidea/ci_hs/dcd_ci_hs.c \ - lib/tinyusb/src/tusb.c +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, \ + $(TINYUSB_SRC_C) \ + src/portable/chipidea/ci_hs/dcd_ci_hs.c \ + )) # All settings for Ethernet support are controller by the value of MICROPY_PY_LWIP ifeq ($(MICROPY_PY_LWIP),1) @@ -543,7 +535,7 @@ OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_SS:.S=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_TINYUSB_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_HAL_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_HAL_IMX_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_ETH_C:.c=.o)) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index d35eeed47ab..dc46ab1ec46 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -252,14 +252,12 @@ endif SRC_C += drivers/usb/usb_cdc.c -SRC_C += $(addprefix lib/tinyusb/src/,\ - common/tusb_fifo.c \ - device/usbd.c \ - device/usbd_control.c \ - class/cdc/cdc_device.c \ - tusb.c \ - portable/nordic/nrf5x/dcd_nrf5x.c \ - ) +# TinyUSB Stack source +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, \ + $(TINYUSB_SRC_C) \ + src/portable/nordic/nrf5x/dcd_nrf5x.c \ + )) endif @@ -306,6 +304,7 @@ OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SYSTEM_C_SRC:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_LIB_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) OBJ += $(GEN_PINS_SRC:.c=.o) $(BUILD)/$(OOFATFS_DIR)/ff.o: COPT += -Os diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index fed0f996598..4a76d9dacc4 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -171,22 +171,13 @@ SHARED_SRC_C += $(addprefix shared/,\ ) # TinyUSB Stack source -TINYUSB_SRC_C += $(addprefix lib/tinyusb/,\ - src/class/cdc/cdc_device.c \ - src/class/dfu/dfu_rt_device.c \ - src/class/hid/hid_device.c \ - src/class/midi/midi_device.c \ - src/class/msc/msc_device.c \ - src/class/usbtmc/usbtmc_device.c \ - src/class/vendor/vendor_device.c \ - src/common/tusb_fifo.c \ - src/device/usbd.c \ - src/device/usbd_control.c \ +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, \ + $(TINYUSB_SRC_C) \ src/portable/renesas/rusb2/dcd_rusb2.c \ src/portable/renesas/rusb2/hcd_rusb2.c \ src/portable/renesas/rusb2/rusb2_common.c \ - src/tusb.c \ - ) + )) ifeq ($(MICROPY_FLOAT_IMPL),double) LIBM_SRC_C += $(SRC_LIB_LIBM_DBL_C) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 9204317aa41..813aed53678 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -158,14 +158,12 @@ ASF4_SRC_C += $(addprefix lib/asf4/$(MCU_SERIES_LOWER)/,\ LIBM_SRC_C += $(SRC_LIB_LIBM_C) LIBM_SRC_C += $(SRC_LIB_LIBM_SQRT_SW_C) -TINYUSB_SRC_C += $(addprefix lib/tinyusb/src/,\ - class/cdc/cdc_device.c \ - common/tusb_fifo.c \ - device/usbd.c \ - device/usbd_control.c \ - portable/microchip/samd/dcd_samd.c \ - tusb.c \ - ) +# TinyUSB Stack source +-include $(TOP)/lib/tinyusb/src/tinyusb.mk +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, \ + $(TINYUSB_SRC_C) \ + src/portable/microchip/samd/dcd_samd.c \ + )) DRIVERS_SRC_C += \ drivers/bus/softspi.c \ diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 4c9366b8bdc..63ed974b9b5 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -461,13 +461,13 @@ USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\ # TinyUSB Stack source -include $(TOP)/lib/tinyusb/src/tinyusb.mk -TINYUSB_SRC_C := $(addprefix lib/tinyusb/, \ +TINYUSB_SRC_C := $(sort $(addprefix lib/tinyusb/, \ $(TINYUSB_SRC_C) \ src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c \ src/portable/synopsys/dwc2/dcd_dwc2.c \ src/portable/synopsys/dwc2/dwc2_common.c \ src/portable/synopsys/dwc2/hcd_dwc2.c \ - ) + )) ifeq ($(MICROPY_SSL_MBEDTLS),1) LIB_SRC_C += mbedtls/mbedtls_port.c From efe6d3bbddd241dc15f2b222b40a36915bd0db6f Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Sat, 11 Apr 2026 05:44:19 +0200 Subject: [PATCH 1970/2098] lib/mbedtls: Update to mbedtls v3.6.6. Signed-off-by: Charalampos Stratakis --- lib/mbedtls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mbedtls b/lib/mbedtls index 107ea89daae..0bebf8b8c7f 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98 +Subproject commit 0bebf8b8c7f07abe3571ded48a11aa907a1ffb20 From 6b97759d25f659d6bdfacb7077d40b05ff321a47 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 9 Apr 2026 09:02:42 -0400 Subject: [PATCH 1971/2098] tests: Protect trailing spaces with "\$". Makes it easier to see the trailing spaces in an editor, and to know that they are intentional. Signed-off-by: Dan Halbert --- tests/cmdline/cmd_showbc.py.exp | 4 +- tests/cmdline/cmd_showbc_const.py.exp | 2 +- tests/cmdline/repl_autocomplete.py.exp | 2 +- .../repl_autocomplete_underscore.py.exp | 26 +++--- tests/cmdline/repl_autoindent.py.exp | 12 +-- tests/cmdline/repl_basic.py.exp | 2 +- tests/cmdline/repl_cont.py.exp | 2 +- tests/cmdline/repl_emacs_keys.py.exp | 2 +- tests/cmdline/repl_inspect.py.exp | 2 +- tests/cmdline/repl_micropyinspect.py.exp | 2 +- tests/cmdline/repl_paste.py.exp | 90 +++++++++---------- tests/cmdline/repl_words_move.py.exp | 6 +- tests/feature_check/repl_emacs_check.py.exp | 2 +- .../repl_words_move_check.py.exp | 2 +- 14 files changed, 78 insertions(+), 78 deletions(-) diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp index 8ac408c16a6..b839de6d1ed 100644 --- a/tests/cmdline/cmd_showbc.py.exp +++ b/tests/cmdline/cmd_showbc.py.exp @@ -185,7 +185,7 @@ arg names: 58 UNARY_OP 1 __neg__ 59 STORE_FAST 9 60 LOAD_FAST 0 -61 UNARY_OP 3 +61 UNARY_OP 3 \$ 62 STORE_FAST 10 63 LOAD_FAST 0 64 LOAD_DEREF 14 @@ -206,7 +206,7 @@ arg names: 84 LOAD_DEREF 14 86 LOAD_FAST 1 87 BINARY_OP 2 __eq__ -88 UNARY_OP 3 +88 UNARY_OP 3 \$ 89 STORE_FAST 10 90 LOAD_DEREF 14 92 LOAD_ATTR c diff --git a/tests/cmdline/cmd_showbc_const.py.exp b/tests/cmdline/cmd_showbc_const.py.exp index a8be765c822..6faeb941d7d 100644 --- a/tests/cmdline/cmd_showbc_const.py.exp +++ b/tests/cmdline/cmd_showbc_const.py.exp @@ -76,7 +76,7 @@ arg names: 34 RAISE_OBJ 35 DUP_TOP 36 LOAD_NAME AttributeError -38 BINARY_OP 8 +38 BINARY_OP 8 \$ 39 POP_JUMP_IF_FALSE 44 41 POP_TOP 42 POP_EXCEPT_JUMP 45 diff --git a/tests/cmdline/repl_autocomplete.py.exp b/tests/cmdline/repl_autocomplete.py.exp index 8cf71bb447d..56b754a31f4 100644 --- a/tests/cmdline/repl_autocomplete.py.exp +++ b/tests/cmdline/repl_autocomplete.py.exp @@ -11,4 +11,4 @@ Type "help()" for more information. >>> i.lower('ABC') 'abc' >>> None. ->>> +>>> \$ diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp index f9720ef2331..43d9c24d8ec 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py.exp +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -1,41 +1,41 @@ MicroPython \.\+ version Type "help()" for more information. >>> # Test REPL autocompletion filtering of underscore attributes ->>> +>>> \$ >>> # Start paste mode ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === class TestClass: === def __init__(self): === self.public_attr = 1 === self._private_attr = 2 === self.__very_private = 3 -=== +=== \$ === def public_method(self): === pass -=== +=== \$ === def _private_method(self): === pass -=== +=== \$ === @property === def public_property(self): === return 42 -=== -=== @property +=== \$ +=== @property \$ === def _private_property(self): === return 99 -=== -=== +=== \$ +=== \$ >>> # Paste executed ->>> +>>> \$ >>> # Create an instance >>> obj = TestClass() ->>> +>>> \$ >>> # Test tab completion on the instance >>> # The tab character after `obj.` and 'a' below triggers the completions >>> obj.public_ public_attr public_method public_property >>> obj.public_attr 1 ->>> +>>> \$ diff --git a/tests/cmdline/repl_autoindent.py.exp b/tests/cmdline/repl_autoindent.py.exp index f45bf840f09..616ebe89989 100644 --- a/tests/cmdline/repl_autoindent.py.exp +++ b/tests/cmdline/repl_autoindent.py.exp @@ -3,20 +3,20 @@ Type "help()" for more information. >>> # tests for autoindent >>> if 1: ... print(1) -... -... -... +... \$ +... \$ +... \$ 1 >>> if 0: ...  print(2) ... else: ... print(3) -... +... \$ 3 >>> if 0: ... print(4) ... else: ... print(5) -... +... \$ 5 ->>> +>>> \$ diff --git a/tests/cmdline/repl_basic.py.exp b/tests/cmdline/repl_basic.py.exp index a1906847432..ea013f641a5 100644 --- a/tests/cmdline/repl_basic.py.exp +++ b/tests/cmdline/repl_basic.py.exp @@ -7,4 +7,4 @@ Type "help()" for more information. 1 >>> 2 2 ->>> +>>> \$ diff --git a/tests/cmdline/repl_cont.py.exp b/tests/cmdline/repl_cont.py.exp index d0d20adc496..1083eedc73e 100644 --- a/tests/cmdline/repl_cont.py.exp +++ b/tests/cmdline/repl_cont.py.exp @@ -54,4 +54,4 @@ two >>> if1 = 2 >>> print(if1) 2 ->>> +>>> \$ diff --git a/tests/cmdline/repl_emacs_keys.py.exp b/tests/cmdline/repl_emacs_keys.py.exp index b8b7b794f2d..52d6ac8bab0 100644 --- a/tests/cmdline/repl_emacs_keys.py.exp +++ b/tests/cmdline/repl_emacs_keys.py.exp @@ -16,4 +16,4 @@ Type "help()" for more information. >>> t = 121 >>> \.\+ 'foobar' ->>> +>>> \$ diff --git a/tests/cmdline/repl_inspect.py.exp b/tests/cmdline/repl_inspect.py.exp index 89ae142019b..66258a9faad 100644 --- a/tests/cmdline/repl_inspect.py.exp +++ b/tests/cmdline/repl_inspect.py.exp @@ -3,4 +3,4 @@ MicroPython \.\+ version Type "help()" for more information. >>> # cmdline: -i -c print("test") >>> # -c option combined with -i option results in REPL ->>> +>>> \$ diff --git a/tests/cmdline/repl_micropyinspect.py.exp b/tests/cmdline/repl_micropyinspect.py.exp index 504bb07d7d4..2789df3f857 100644 --- a/tests/cmdline/repl_micropyinspect.py.exp +++ b/tests/cmdline/repl_micropyinspect.py.exp @@ -2,4 +2,4 @@ MicroPython \.\+ version Type "help()" for more information. >>> # cmdline: cmdline/repl_micropyinspect >>> # setting MICROPYINSPECT environment variable before program exit triggers REPL ->>> +>>> \$ diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp index 2b837f85cf9..3165b863052 100644 --- a/tests/cmdline/repl_paste.py.exp +++ b/tests/cmdline/repl_paste.py.exp @@ -1,21 +1,21 @@ MicroPython \.\+ version Type "help()" for more information. >>> # Test REPL paste mode functionality ->>> +>>> \$ >>> # Basic paste mode with a simple function ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === def hello(): === print('Hello from paste mode!') === hello() -=== +=== \$ Hello from paste mode! ->>> +>>> \$ >>> # Paste mode with multiple indentation levels ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === def calculate(n): === if n > 0: === for i in range(n): @@ -25,109 +25,109 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === print(f'Odd: {i}') === else: === print('n must be positive') -=== +=== \$ === calculate(5) -=== +=== \$ Even: 0 Odd: 1 Even: 2 Odd: 3 Even: 4 ->>> +>>> \$ >>> # Paste mode with blank lines ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === def function_with_blanks(): === print('First line') -=== +=== \$ === print('After blank line') -=== -=== +=== \$ +=== \$ === print('After two blank lines') -=== +=== \$ === function_with_blanks() -=== +=== \$ First line After blank line After two blank lines ->>> +>>> \$ >>> # Paste mode with class definition and multiple methods ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === class TestClass: === def __init__(self, value): === self.value = value -=== +=== \$ === def display(self): === print(f'Value is: {self.value}') -=== +=== \$ === def double(self): === self.value *= 2 === return self.value -=== +=== \$ === obj = TestClass(21) === obj.display() === print(f'Doubled: {obj.double()}') === obj.display() -=== +=== \$ Value is: 21 Doubled: 42 Value is: 42 ->>> +>>> \$ >>> # Paste mode with exception handling ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === try: === x = 1 / 0 === except ZeroDivisionError: === print('Caught division by zero') === finally: === print('Finally block executed') -=== +=== \$ Caught division by zero Finally block executed ->>> +>>> \$ >>> # Cancel paste mode with Ctrl-C ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === print('This should not execute') -=== ->>> ->>> +=== \$ +>>> \$ +>>> \$ >>> # Normal REPL still works after cancelled paste >>> print('Back to normal REPL') Back to normal REPL ->>> +>>> \$ >>> # Paste mode with syntax error ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === def bad_syntax(: === print('Missing parameter') -=== +=== \$ Traceback (most recent call last): File "", line 2 SyntaxError: invalid syntax ->>> +>>> \$ >>> # Paste mode with runtime error ->>> +>>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish -=== +=== \$ === def will_error(): === undefined_variable -=== +=== \$ === will_error() -=== +=== \$ Traceback (most recent call last): File "", line 5, in File "", line 3, in will_error NameError: name 'undefined_variable' isn't defined ->>> +>>> \$ >>> # Final test to show REPL is still functioning >>> 1 + 2 + 3 6 ->>> +>>> \$ diff --git a/tests/cmdline/repl_words_move.py.exp b/tests/cmdline/repl_words_move.py.exp index c4d22a0d9a7..64d1e13d3e9 100644 --- a/tests/cmdline/repl_words_move.py.exp +++ b/tests/cmdline/repl_words_move.py.exp @@ -19,7 +19,7 @@ Type "help()" for more information. >>> # forward-word on eol. if cursor is moved, this will result in a SyntaxError >>> \.\+ 6 ->>> +>>> \$ >>> # kill word >>> # backward-kill-word, start in word >>> \.\+ @@ -33,7 +33,7 @@ Type "help()" for more information. >>> # forward-kill-word, don't start in word >>> \.\+ 3 ->>> +>>> \$ >>> # extra move/kill shortcuts >>> # ctrl-left >>> \.\+ @@ -44,4 +44,4 @@ Type "help()" for more information. >>> # ctrl-w >>> \.\+ 1 ->>> +>>> \$ diff --git a/tests/feature_check/repl_emacs_check.py.exp b/tests/feature_check/repl_emacs_check.py.exp index 5fe8ba1cd2d..2dfb2da58b8 100644 --- a/tests/feature_check/repl_emacs_check.py.exp +++ b/tests/feature_check/repl_emacs_check.py.exp @@ -4,4 +4,4 @@ Type "help()" for more information. >>> t = \.\+ >>> t == 2 True ->>> +>>> \$ diff --git a/tests/feature_check/repl_words_move_check.py.exp b/tests/feature_check/repl_words_move_check.py.exp index 5fe8ba1cd2d..2dfb2da58b8 100644 --- a/tests/feature_check/repl_words_move_check.py.exp +++ b/tests/feature_check/repl_words_move_check.py.exp @@ -4,4 +4,4 @@ Type "help()" for more information. >>> t = \.\+ >>> t == 2 True ->>> +>>> \$ From fa17886b634c19282b8b0a645cb9597c392b8cee Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Mon, 13 Apr 2026 10:11:48 -0400 Subject: [PATCH 1972/2098] tests/cmdline/repl_autocomplete_underscore.py: Remove trailing spaces. They are not needed for this test to function as intended. Signed-off-by: Dan Halbert --- tests/cmdline/repl_autocomplete_underscore.py | 11 +++++------ tests/cmdline/repl_autocomplete_underscore.py.exp | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/cmdline/repl_autocomplete_underscore.py b/tests/cmdline/repl_autocomplete_underscore.py index 98bbb699200..a0ad4aadf0f 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py +++ b/tests/cmdline/repl_autocomplete_underscore.py @@ -7,21 +7,20 @@ def __init__(self): self.public_attr = 1 self._private_attr = 2 self.__very_private = 3 - + def public_method(self): pass - + def _private_method(self): pass - + @property def public_property(self): return 42 - - @property + + @property def _private_property(self): return 99 - {\x04} # Paste executed diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp index 43d9c24d8ec..f2d3a44c9ad 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py.exp +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -11,22 +11,21 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === self.public_attr = 1 === self._private_attr = 2 === self.__very_private = 3 -=== \$ +=== \$ === def public_method(self): === pass -=== \$ +=== \$ === def _private_method(self): === pass -=== \$ +=== \$ === @property === def public_property(self): === return 42 -=== \$ -=== @property \$ +=== \$ +=== @property === def _private_property(self): === return 99 === \$ -=== \$ >>> # Paste executed >>> \$ >>> # Create an instance From 9b36eedf36139dca619b70d018618cca584eedd8 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 15 Apr 2026 11:14:17 -0400 Subject: [PATCH 1973/2098] tests/run-tests.py: Fix hex parsing of {\xnn} escapes. Signed-off-by: Dan Halbert --- tests/run-tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index c5c5dc740bd..093656299fe 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -489,8 +489,8 @@ def get(required=False): return rv def send_get(what): - # Detect {\x00} pattern and convert to ctrl-key codes. - ctrl_code = lambda m: bytes([int(m.group(1))]) + # Detect hex {\x00} pattern and convert to ctrl-key codes. + ctrl_code = lambda m: bytes([int(m.group(1), 16)]) what = re.sub(rb"{\\x(\d\d)}", ctrl_code, what) os.write(master, what) From 93a734020ea52ec14a3e97207f7bd09b6ca4b088 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 15 Apr 2026 12:36:52 -0400 Subject: [PATCH 1974/2098] tests/run-tests.py: Allow .native.exp files to contain regexs. Signed-off-by: Dan Halbert --- tests/run-tests.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index 093656299fe..392597c6d23 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -456,12 +456,12 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): if is_special: # check for any cmdline options needed for this test - args = [MICROPYTHON] + cmdlist = [MICROPYTHON] with open(test_file, "rb") as f: line = f.readline() if line.startswith(b"# cmdline:"): # subprocess.check_output on Windows only accepts strings, not bytes - args += [str(c, "utf-8") for c in line[10:].strip().split()] + cmdlist += [str(c, "utf-8") for c in line[10:].strip().split()] # run the test, possibly with redirected input try: @@ -497,10 +497,10 @@ def send_get(what): return get() with open(test_file, "rb") as f: - # instead of: output_mupy = subprocess.check_output(args, stdin=f) + # instead of: output_mupy = subprocess.check_output(cmdlist, stdin=f) master, slave = pty.openpty() p = subprocess.Popen( - args, stdin=slave, stdout=slave, stderr=subprocess.STDOUT, bufsize=0 + cmdlist, stdin=slave, stdout=slave, stderr=subprocess.STDOUT, bufsize=0 ) banner = get(True) output_mupy = banner + b"".join(send_get(line) for line in f) @@ -519,7 +519,7 @@ def send_get(what): os.close(slave) else: output_mupy = subprocess.check_output( - args + [test_file], stderr=subprocess.STDOUT + cmdlist + [test_file], stderr=subprocess.STDOUT ) except subprocess.CalledProcessError: return b"CRASH" @@ -585,7 +585,12 @@ def send_get(what): if is_special or test_file_abspath in tests_with_regex_output: # convert parts of the output that are not stable across runs - with open(test_file + ".exp", "rb") as f: + # Prefer emitter-specific expected output. + exp_file = test_file + "." + args.emit + ".exp" + if not os.path.isfile(exp_file): + # Fall back to generic expected output. + exp_file = test_file + ".exp" + with open(exp_file, "rb") as f: lines_exp = [] for line in f.readlines(): if line == b"########\n": From 9d989e4d8bb43d30afe2b4da228801d1024d6477 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 15 Apr 2026 11:14:51 -0400 Subject: [PATCH 1975/2098] tests: Remove further trailing spaces when possible. Most cases here have `print(..., some_str)` changed to `print(..., repr(some_str))`. This makes the empty string visible and prevents bare trailing spaces. Signed-off-by: Dan Halbert --- tests/basics/string_tstring_basic.py | 3 +- tests/basics/string_tstring_basic.py.exp | 2 +- tests/cmdline/repl_paste.py | 12 +++--- tests/extmod/vfs_basic.py | 16 +++---- tests/extmod/vfs_basic.py.exp | 42 +++++++++---------- tests/micropython/heapalloc_traceback.py | 6 +-- tests/micropython/heapalloc_traceback.py.exp | 8 ++-- .../heapalloc_traceback.py.native.exp | 4 +- tests/ports/esp32/check_err_str.py | 2 +- tests/ports/esp32/check_err_str.py.exp | 2 +- tests/thread/thread_exc2.py.exp | 2 +- tests/thread/thread_exc2.py.native.exp | 2 +- 12 files changed, 51 insertions(+), 50 deletions(-) diff --git a/tests/basics/string_tstring_basic.py b/tests/basics/string_tstring_basic.py index e23a3f06595..aa9de327b59 100644 --- a/tests/basics/string_tstring_basic.py +++ b/tests/basics/string_tstring_basic.py @@ -172,7 +172,8 @@ print("\n=== Interpolation attribute tests ===") i_basic = Interpolation(42, "x") print(f"Basic conversion: {i_basic.conversion}") -print(f"Basic format_spec: {i_basic.format_spec}") +# Put in quotes to make empty string visible. +print(f"Basic format_spec: '{i_basic.format_spec}'") i_with_conv = Interpolation(42, "x", "s") print(f"With conversion: {i_with_conv.conversion}") diff --git a/tests/basics/string_tstring_basic.py.exp b/tests/basics/string_tstring_basic.py.exp index 4fcb3a75834..60affaad499 100644 --- a/tests/basics/string_tstring_basic.py.exp +++ b/tests/basics/string_tstring_basic.py.exp @@ -71,7 +71,7 @@ Nested expr: Template(strings=('', ''), interpolations=(Interpolation('{}', 'inn === Interpolation attribute tests === Basic conversion: None -Basic format_spec: +Basic format_spec: '' With conversion: s With format_spec: :>10 Full conversion: r diff --git a/tests/cmdline/repl_paste.py b/tests/cmdline/repl_paste.py index 7cec450fce7..8c4f341701f 100644 --- a/tests/cmdline/repl_paste.py +++ b/tests/cmdline/repl_paste.py @@ -26,10 +26,10 @@ def calculate(n): {\x05} def function_with_blanks(): print('First line') - +{\x20}{\x20}{\x20}{\x20} print('After blank line') - - +{\x20}{\x20}{\x20}{\x20} +{\x20}{\x20}{\x20}{\x20} print('After two blank lines') function_with_blanks() @@ -40,10 +40,10 @@ def function_with_blanks(): class TestClass: def __init__(self, value): self.value = value - +{\x20}{\x20}{\x20}{\x20} def display(self): print(f'Value is: {self.value}') - +{\x20}{\x20}{\x20}{\x20} def double(self): self.value *= 2 return self.value @@ -82,7 +82,7 @@ def bad_syntax(: {\x05} def will_error(): undefined_variable - +{\x20}{\x20}{\x20}{\x20} will_error() {\x04} diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index 2c0ce8f5295..09fe5dce022 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -19,11 +19,11 @@ def umount(self): print(self.id, "umount") def ilistdir(self, dir): - print(self.id, "ilistdir", dir) + print(self.id, "ilistdir", repr(dir)) return iter([("a%d" % self.id, 0, 0)]) def chdir(self, dir): - print(self.id, "chdir", dir) + print(self.id, "chdir", repr(dir)) if self.fail: raise OSError(self.fail) @@ -32,23 +32,23 @@ def getcwd(self): return "dir%d" % self.id def mkdir(self, path): - print(self.id, "mkdir", path) + print(self.id, "mkdir", repr(path)) def remove(self, path): - print(self.id, "remove", path) + print(self.id, "remove", repr(path)) def rename(self, old_path, new_path): - print(self.id, "rename", old_path, new_path) + print(self.id, "rename", repr(old_path), repr(new_path)) def rmdir(self, path): - print(self.id, "rmdir", path) + print(self.id, "rmdir", repr(path)) def stat(self, path): - print(self.id, "stat", path) + print(self.id, "stat", repr(path)) return (self.id,) def statvfs(self, path): - print(self.id, "statvfs", path) + print(self.id, "statvfs", repr(path)) return (self.id,) def open(self, file, mode): diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index 536bb4c805d..a03176a3c44 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -18,30 +18,30 @@ stat /x OSError ('test_mnt', 16384, 0) StopIteration StopIteration -1 ilistdir / +1 ilistdir '/' ['a1'] -1 ilistdir / +1 ilistdir '/' ['a1'] 2 mount True False ['test_mnt', 'test_mnt2'] -2 ilistdir / +2 ilistdir '/' ['a2'] 3 mount False False OSError OSError OSError -1 chdir / -1 ilistdir +1 chdir '/' +1 ilistdir '' ['a1'] 1 getcwd /test_mntdir1 -1 mkdir test_dir -1 remove test_file -1 rename test_file test_file2 -1 rmdir test_dir -1 stat test_file +1 mkdir 'test_dir' +1 remove 'test_file' +1 rename 'test_file' 'test_file2' +1 rmdir 'test_dir' +1 stat 'test_file' (1,) -1 statvfs / +1 statvfs '/' (1,) 1 open test_file r 1 open test_file wb @@ -50,29 +50,29 @@ OSError OSError 3 mount False False (16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) -3 statvfs / +3 statvfs '/' (3,) -3 ilistdir / +3 ilistdir '/' ['a3'] 3 open test r 4 mount False False -3 ilistdir / +3 ilistdir '/' ['mnt', 'a3'] -4 ilistdir / +4 ilistdir '/' ['a4'] -4 chdir / -4 ilistdir +4 chdir '/' +4 ilistdir '' ['a4'] -3 chdir /subdir -3 ilistdir +3 chdir '/subdir' +3 ilistdir '' ['a3'] -3 chdir / +3 chdir '/' 3 umount ['mnt'] 4 umount OSError / 5 mount False False -5 chdir /subdir +5 chdir '/subdir' OSError / diff --git a/tests/micropython/heapalloc_traceback.py b/tests/micropython/heapalloc_traceback.py index 1743d4956d8..89fa37a48cc 100644 --- a/tests/micropython/heapalloc_traceback.py +++ b/tests/micropython/heapalloc_traceback.py @@ -30,13 +30,13 @@ def test(): # call test() with heap allocation disabled test() -# print the exception that was raised +# print the exception that was raised. Use repr() to make the whitespace visible. buf = io.StringIO() sys.print_exception(global_exc, buf) for l in buf.getvalue().split("\n"): # MicroPython on pyboard prints as file, so remove filename. if l.startswith(" File "): l = l.split('"') - print(l[0], l[2]) + print(repr(l[0]), repr(l[2])) else: - print(l) + print(repr(l)) diff --git a/tests/micropython/heapalloc_traceback.py.exp b/tests/micropython/heapalloc_traceback.py.exp index e0799aa2b35..fa30fa22080 100644 --- a/tests/micropython/heapalloc_traceback.py.exp +++ b/tests/micropython/heapalloc_traceback.py.exp @@ -1,5 +1,5 @@ StopIteration -Traceback (most recent call last): - File , line 24, in test -StopIteration: - +'Traceback (most recent call last):' +' File ' ', line 24, in test' +'StopIteration: ' +'' diff --git a/tests/micropython/heapalloc_traceback.py.native.exp b/tests/micropython/heapalloc_traceback.py.native.exp index d6ac26aa829..851eb5c7806 100644 --- a/tests/micropython/heapalloc_traceback.py.native.exp +++ b/tests/micropython/heapalloc_traceback.py.native.exp @@ -1,3 +1,3 @@ StopIteration -StopIteration: - +'StopIteration: ' +'' diff --git a/tests/ports/esp32/check_err_str.py b/tests/ports/esp32/check_err_str.py index 9efe1afa196..598db4e1e11 100644 --- a/tests/ports/esp32/check_err_str.py +++ b/tests/ports/esp32/check_err_str.py @@ -32,7 +32,7 @@ except OSError as e: exc = e micropython.heap_unlock() -print("exc:", exc) # exc empty due to no memory +print("exc:", repr(exc)) # exc empty due to no memory # same again but having an emergency buffer micropython.alloc_emergency_exception_buf(256) diff --git a/tests/ports/esp32/check_err_str.py.exp b/tests/ports/esp32/check_err_str.py.exp index d4162d1be00..db45d492551 100644 --- a/tests/ports/esp32/check_err_str.py.exp +++ b/tests/ports/esp32/check_err_str.py.exp @@ -1,4 +1,4 @@ [Errno 2] ENOENT (-5379, 'ESP_ERR_OTA_VALIDATE_FAILED') -exc: +exc: OSError() -5379 diff --git a/tests/thread/thread_exc2.py.exp b/tests/thread/thread_exc2.py.exp index 469516dacc0..b7085c7af9b 100644 --- a/tests/thread/thread_exc2.py.exp +++ b/tests/thread/thread_exc2.py.exp @@ -1,5 +1,5 @@ Unhandled exception in thread started by Traceback (most recent call last): File \.\+, line 7, in thread_entry -ValueError: +ValueError: \$ done diff --git a/tests/thread/thread_exc2.py.native.exp b/tests/thread/thread_exc2.py.native.exp index 9b2e715ef8d..8188dd3ed00 100644 --- a/tests/thread/thread_exc2.py.native.exp +++ b/tests/thread/thread_exc2.py.native.exp @@ -1,3 +1,3 @@ Unhandled exception in thread started by -ValueError: +ValueError: \$ done From d54e675e66d3b7deff9f490a3ae76bd558bcfc15 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 15 Apr 2026 12:22:11 -0400 Subject: [PATCH 1976/2098] tests/ports/unix: Remove trailing spaces in unix coverage output. Signed-off-by: Dan Halbert --- tests/ports/unix/extra_coverage.py.exp | 16 ++++++++-------- tests/run-tests.py | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 4746d20ab29..75d885290c0 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -68,11 +68,11 @@ sts test tes -RuntimeError: -RuntimeError: +RuntimeError: \$ +RuntimeError: \$ # repl ame__ -port +port \$ builtins micropython array binascii btree cexample cmath collections @@ -124,8 +124,8 @@ deadbeef c0ffee 000c0ffee # list argument helpers -TypeError: -ValueError: +TypeError: \$ +ValueError: \$ mp_obj_list_ensure same list? 1 mp_obj_list_optional_arg same list? 1 mp_obj_list_optional_arg new list len 3 @@ -140,7 +140,7 @@ OverflowError: overflow converting long int to machine word OverflowError: overflow converting long int to machine word TypeError: can't convert NoneType to int TypeError: can't convert NoneType to int -ValueError: +ValueError: \$ Warning: test # binary 123 @@ -158,8 +158,8 @@ unlocked 1 2 3 -KeyboardInterrupt: -KeyboardInterrupt: +KeyboardInterrupt: \$ +KeyboardInterrupt: \$ 10 loop scheduled function diff --git a/tests/run-tests.py b/tests/run-tests.py index 392597c6d23..e2ba9ba2297 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -440,6 +440,7 @@ def detect_target_wiring_script(pyb, args): "basics/weakref_callback_exception.py", "misc/sys_settrace_cov.py", "net_inet/tls_text_errors.py", + "ports/unix/extra_coverage.py", "thread/thread_exc2.py", "ports/esp32/partition_ota.py", ) From 2c37f0612fa7a9f6dfc8566fc2b0db21be9d810c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 16 Apr 2026 01:30:35 +0200 Subject: [PATCH 1977/2098] tests/run-tests.py: Update the list of tests requiring floats. This commit updates the list of the tests that must be skipped when the suite is executed on a target that does not have floating point support. Two more tests, namely `extmod/vfs_rom.py` and `micropython/const_float.py` have been added to the list, since they both rely on floating point support being there. Signed-off-by: Alessandro Gatti --- tests/run-tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run-tests.py b/tests/run-tests.py index e2ba9ba2297..ba6197acf3e 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -240,6 +240,8 @@ "extmod/uctypes_le_float.py", "extmod/uctypes_native_float.py", "extmod/uctypes_sizeof_float.py", + "extmod/vfs_rom.py", + "micropython/const_float.py", "misc/rge_sm.py", "ports/unix/ffi_float.py", "ports/unix/ffi_float2.py", From ca85a1eb2393d5e6173405f0051369bb1572e83c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 20 Apr 2026 18:23:14 +0200 Subject: [PATCH 1978/2098] tools/boardgen.py: Make per-pin content output extensible. This commit lets classes extending the base `PinGenerator` class to override the process of generating extra per-pin content when creating the pins' information source file. There are cases in which one may want to have more control on the part of the source generation process that dumps additional per-pin information to the source file. The current approach works fine if each pin generates self-contained additional data to be placed in the source file, but there is no clean way to provide a prologue or an epilogue to that content. For example, if one wants to emit a single consolidated additional pin data table it is not that convenient to be able to consistently emit the table start definition and the table end markers. With these changes all one has to do to achieve this is to override `PinGenerator.print_pin_source` in their PinGenerator-derived class to either wrap the output or to replace what is being output altogether. Signed-off-by: Alessandro Gatti --- tools/boardgen.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/boardgen.py b/tools/boardgen.py index 3723e7ce31b..6b295f484f3 100644 --- a/tools/boardgen.py +++ b/tools/boardgen.py @@ -475,6 +475,10 @@ def load_inputs(self, out_source): def generate_extra_files(self): pass + def print_pin_source(self, out_source): + for pin in self.available_pins(): + pin.print_source(out_source) + def main(self): parser = argparse.ArgumentParser(description="Generate board specific pin file") parser.add_argument("--board-csv") @@ -495,8 +499,7 @@ def main(self): self.load_inputs(out_source) # Allow a port to print arbitrary per-pin content. - for pin in self.available_pins(): - pin.print_source(out_source) + self.print_pin_source(out_source) # Print the tables and dictionaries. self.print_source(out_source) From 05740fd665ccbbac99a6d3473b8a8f9897cee3cb Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Apr 2026 14:50:32 +1000 Subject: [PATCH 1979/2098] qemu/boards/NETDUINO2: Change heap size to 114k. This board has 128k RAM, and its heap was originally 120k but commit e84c9abfc21f57fe93b4d9a05c1d123e3f333880 changed that to 140k which will not fit. This commit reduces the heap down to 114k which allows enough room for the remaining data/bss, a 10k heap and about 3k spare. Signed-off-by: Damien George --- ports/qemu/boards/NETDUINO2/mpconfigboard.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/qemu/boards/NETDUINO2/mpconfigboard.mk b/ports/qemu/boards/NETDUINO2/mpconfigboard.mk index ffa781f3399..6f8fa4c6b45 100644 --- a/ports/qemu/boards/NETDUINO2/mpconfigboard.mk +++ b/ports/qemu/boards/NETDUINO2/mpconfigboard.mk @@ -9,4 +9,7 @@ LDSCRIPT = mcu/arm/stm32.ld SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o +# 114k heap, because this board only has 128k RAM and the stack needs 10k. +MICROPY_HEAP_SIZE ?= 116736 + MPY_CROSS_FLAGS += -march=armv7m From 0124bd5b2b93acd5250ba03045c3462de60b9035 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 17:42:04 +0200 Subject: [PATCH 1980/2098] qemu/Makefile: Do not mount ROMFS partition on non-test targets. This commit fixes a mistake introduced in #19051, where the test ROMFS partition would get mounted even on targets that do not need it to function (ie. `repl`). Now the ROMFS image is mounted only for test targets: `test`, `test_full`, and `test_natmod`. Signed-off-by: Alessandro Gatti --- ports/qemu/Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ports/qemu/Makefile b/ports/qemu/Makefile index 16a53f52329..49b242603cb 100644 --- a/ports/qemu/Makefile +++ b/ports/qemu/Makefile @@ -224,13 +224,12 @@ endif ROMFS_TEST_IMAGE = assets/random_romfs.bin +# Mount the ROMFS test image specifically for test runs (if there is a slot 0). ifneq ($(MICROPY_HW_ROMFS_PART0_START),) -ifeq ($(QEMU_ROMFS_IMG0),) -QEMU_ARGS += -device loader,file=$(ROMFS_TEST_IMAGE),addr=$(MICROPY_HW_ROMFS_PART0_START),force-raw=on -endif +QEMU_TEST_ARGS = -device loader,file=$(ROMFS_TEST_IMAGE),addr=$(MICROPY_HW_ROMFS_PART0_START),force-raw=on endif -RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) -serial pty -kernel ../ports/qemu/$<" $(RUN_TESTS_ARGS) +RUN_TESTS_FULL_ARGS = -t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) $(QEMU_TEST_ARGS) -serial pty -kernel ../ports/qemu/$<" $(RUN_TESTS_ARGS) ################################################################################ # Source files and libraries From 09c0a3cb2268c4be3882b611b86d008feea2ed2e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 18:55:08 +0200 Subject: [PATCH 1981/2098] qemu/mcu/arm/imx6.ld: Fix C++ linking. This commit expands the linkerscript for i.MX6-series boards to let C++ code link with the interpreter core. The linkerscript now contains all necessary sections for C++ code that uses exceptions to be part of a user module inside the MicroPython image. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/imx6.ld | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/qemu/mcu/arm/imx6.ld b/ports/qemu/mcu/arm/imx6.ld index 76648616098..48c65e9a401 100644 --- a/ports/qemu/mcu/arm/imx6.ld +++ b/ports/qemu/mcu/arm/imx6.ld @@ -28,6 +28,13 @@ SECTIONS *(.text*) *(.rodata*) . = ALIGN(4); + *(.ARM.extab*) + *(.gnulinkonce.armextab.*) + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx*) + *(.gnu.linkonce.armexidx.*) + __exidx_end = .; _etext = .; _sidata = _etext; } > RAM From 2f52e1d6ad46dc5e3188f390c7865cfcfb9b0121 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 18:47:44 +0200 Subject: [PATCH 1982/2098] qemu/mcu/arm/mps2.ld: Fix C++ linking. This commit expands the linkerscript for MPS2-series boards to let C++ code link with the interpreter core. The linkerscript now contains all necessary sections for C++ code that uses exceptions to be part of a user module inside the MicroPython image. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/mps2.ld | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/qemu/mcu/arm/mps2.ld b/ports/qemu/mcu/arm/mps2.ld index 1597a7de316..a26a3dcb3f4 100644 --- a/ports/qemu/mcu/arm/mps2.ld +++ b/ports/qemu/mcu/arm/mps2.ld @@ -22,6 +22,9 @@ SECTIONS } > RAM .ARM.exidx : AT ( _etext ) { + . = ALIGN(4); + *(.ARM.extab*) + *(.gnu.linkonce.armextab.*) . = ALIGN(4); __exidx_start = .; *(.ARM.exidx*) From dbd3eaaa4c3972a0196e59cdcb00a997d3d9a25c Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 18:57:23 +0200 Subject: [PATCH 1983/2098] qemu/mcu/arm/mps3.ld: Fix C++ linking. This commit expands the linkerscript for MPS3-series boards to let C++ code link with the interpreter core. The linkerscript now contains all necessary sections for C++ code that uses exceptions to be part of a user module inside the MicroPython image. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/mps3.ld | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/qemu/mcu/arm/mps3.ld b/ports/qemu/mcu/arm/mps3.ld index 6a576f355f0..545181e9b25 100644 --- a/ports/qemu/mcu/arm/mps3.ld +++ b/ports/qemu/mcu/arm/mps3.ld @@ -22,7 +22,13 @@ SECTIONS *(.text*) *(.rodata*) . = ALIGN(4); + *(.ARM.extab*) + *(.gnu.linkonce.armextab.*) + . = ALIGN(4); + __exidx_start = .; *(.ARM.exidx*) + *(.gnu.linkonce.armexidx.*) + __exidx_end = .; . = ALIGN(4); _etext = .; _sidata = _etext; From 086534a873010ef7e81eefccdac4684b0a46adad Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 19:00:35 +0200 Subject: [PATCH 1984/2098] qemu/mcu/arm/nrf51.ld: Fix C++ linking. This commit expands the linkerscript for nRF51-series boards to let C++ code link with the interpreter core. The linkerscript now contains all necessary sections for C++ code that uses exceptions to be part of a user module inside the MicroPython image. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/nrf51.ld | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/qemu/mcu/arm/nrf51.ld b/ports/qemu/mcu/arm/nrf51.ld index 46d7201b32a..2232e165194 100644 --- a/ports/qemu/mcu/arm/nrf51.ld +++ b/ports/qemu/mcu/arm/nrf51.ld @@ -19,6 +19,14 @@ SECTIONS *(.text*) *(.rodata*) . = ALIGN(4); + *(.ARM.extab*) + *(.gnu.linkonce.armextab.*) + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx*) + *(.gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); _etext = .; _sidata = _etext; } > ROM From 65dabf8d1fc282cd2eac588b29af466ee652e6a3 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 19:01:38 +0200 Subject: [PATCH 1985/2098] qemu/mcu/arm/stm32.ld: Fix C++ linking. This commit expands the linkerscript for STM32-series boards to let C++ code link with the interpreter core. The linkerscript now contains all necessary sections for C++ code that uses exceptions to be part of a user module inside the MicroPython image. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/stm32.ld | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/qemu/mcu/arm/stm32.ld b/ports/qemu/mcu/arm/stm32.ld index 835f9bbe970..b19edf73031 100644 --- a/ports/qemu/mcu/arm/stm32.ld +++ b/ports/qemu/mcu/arm/stm32.ld @@ -19,6 +19,13 @@ SECTIONS *(.text*) *(.rodata*) . = ALIGN(4); + *(.ARM.extab*) + *(.gnu.linkonce.armextab.*) + __exidx_start = .; + *(.ARM.exidx*) + *(.gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); _etext = .; _sidata = _etext; } > ROM From 1ff9f9e3de35dc3dc5f7a7f30a805548c1112111 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 19:02:09 +0200 Subject: [PATCH 1986/2098] qemu/mcu/arm/startup: Add an implementation for "abort". This commit adds a naive implementation of the "abort" standard C library, that is needed by certain C++ runtimes let code link. Although the Arm toolchain used in the CI image does not need this, newer or different toolchains may actually need this (eg. the Arm EABI toolchain provided by Arch Linux). Given the limited scope of the QEMU port, the function simply spins forever. Signed-off-by: Alessandro Gatti --- ports/qemu/mcu/arm/startup.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/qemu/mcu/arm/startup.c b/ports/qemu/mcu/arm/startup.c index 5b8ae1958a6..c9dae4273ba 100644 --- a/ports/qemu/mcu/arm/startup.c +++ b/ports/qemu/mcu/arm/startup.c @@ -139,6 +139,12 @@ void exit(int status) { } } +void abort(void) { + for (;;) { + } + MP_UNREACHABLE; +} + #ifndef NDEBUG void __assert_func(const char *file, int line, const char *func, const char *expr) { (void)func; From 70bb728c09b5f6b3b9410e5f21df5e2cb8e7e414 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Apr 2026 19:06:14 +0200 Subject: [PATCH 1987/2098] tools/ci.sh: Let QEMU try to build with a user module. This commit brings the QEMU port in line with other ports when it comes to making sure the port builds with a user module to be part of the main interpreter. To not impact too much on the build time, only the `MPS2_AN385` board does this as there'd be just too many targets to test, for not much gain. Signed-off-by: Alessandro Gatti --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 81006ea4e7c..041a7a06b0e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -406,7 +406,7 @@ function ci_qemu_build_arm_sabrelite { function ci_qemu_build_arm_thumb_softfp { ci_qemu_build_arm_prepare - make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu test_full + make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu USER_C_MODULES=../../examples/usercmodule test_full # Test building native .mpy with ARM-M softfp architectures. ci_native_mpy_modules_build armv6m From 02d2e9fb7470849165d1ff5e13d3165458005f3c Mon Sep 17 00:00:00 2001 From: Jon Nordby Date: Sun, 10 Aug 2025 15:31:05 +0200 Subject: [PATCH 1988/2098] zephyr/CMakeLists.txt: Add support for USER_C_MODULES. Fixes issue #17878. Signed-off-by: Jon Nordby --- ports/zephyr/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 82142de2e08..6abb5e3c2eb 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -34,6 +34,7 @@ string(TOUPPER ZEPHYR_${BOARD} MICROPY_BOARD) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) +include(${MICROPY_DIR}/py/usermod.cmake) list(APPEND DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/dts) @@ -135,6 +136,7 @@ list(TRANSFORM MICROPY_SOURCE_LIB PREPEND ${MICROPY_DIR}/lib/) set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_PY} ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_USERMOD} ${MICROPY_SOURCE_SHARED} ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_LIB} @@ -153,6 +155,7 @@ zephyr_library_named(${MICROPY_TARGET}) zephyr_library_include_directories( ${MICROPY_INC_CORE} ${MICROPY_PORT_DIR} + ${MICROPY_INC_USERMOD} ${CMAKE_CURRENT_BINARY_DIR} ) From cfe9048693c8a37b967a28d5bba90b390ff8bfe5 Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 3 Dec 2025 18:58:36 +0100 Subject: [PATCH 1989/2098] zephyr: Add ability to set feature level in conf file. Adds the ability to set feature level in board.conf. Signed-off-by: Vdragon --- ports/zephyr/Kconfig | 24 ++++++++++++++++++++++++ ports/zephyr/mpconfigport.h | 15 +++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index beddb2b0b9e..c12e4bc854f 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -59,6 +59,30 @@ config MICROPY_USB_DEVICE_PID hex "USB PID" default 0x0001 +choice MICROPY_CONFIG_ROM_LEVEL + prompt "MicroPython Features level" + default MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES + + config MICROPY_CONFIG_ROM_LEVEL_MINIMUM + bool "Disable all optional features" + + config MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES + bool "Only enable core features" + + config MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES + bool "Enable most common features" + + config MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES + bool "Enable convenience features" + + config MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES + bool "Enable all common features" + + config MICROPY_CONFIG_ROM_LEVEL_EVERYTHING + bool "Enable everything" + +endchoice + endmenu # MicroPython Options source "Kconfig.zephyr" diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index f52b42fc2f0..ceaa6df43c4 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -31,9 +31,20 @@ #include #include -// Use the basic configuration level to get a balance between size and features. -#ifndef MICROPY_CONFIG_ROM_LEVEL +#if defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_MINIMUM) +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM) +#elif defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) +#elif defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) +#elif defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) +#elif defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) +#elif defined(CONFIG_MICROPY_CONFIG_ROM_LEVEL_EVERYTHING) +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EVERYTHING) +#else +#error "Undefined Feature Level" #endif // Usually passed from Makefile From 8f1a8d4ffa6862c59e41c092ebefad138f645b4b Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 3 Dec 2025 18:59:48 +0100 Subject: [PATCH 1990/2098] zephyr: Enable FULL feature level for rp2. Enables all features for RP2 boards. Signed-off-by: Vdragon --- ports/zephyr/boards/rpi_pico.conf | 1 + ports/zephyr/boards/rpi_pico2_rp2350a_m33.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/zephyr/boards/rpi_pico.conf b/ports/zephyr/boards/rpi_pico.conf index 6a0be1c3799..26f8be3efa0 100644 --- a/ports/zephyr/boards/rpi_pico.conf +++ b/ports/zephyr/boards/rpi_pico.conf @@ -18,6 +18,7 @@ CONFIG_SPI=y # MicroPython config. CONFIG_MICROPY_HEAP_SIZE=196608 +CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES=y # File System Configuration CONFIG_FILE_SYSTEM=y diff --git a/ports/zephyr/boards/rpi_pico2_rp2350a_m33.conf b/ports/zephyr/boards/rpi_pico2_rp2350a_m33.conf index 84601272470..fcb59395e86 100644 --- a/ports/zephyr/boards/rpi_pico2_rp2350a_m33.conf +++ b/ports/zephyr/boards/rpi_pico2_rp2350a_m33.conf @@ -15,3 +15,4 @@ CONFIG_SPI=y # MicroPython config. CONFIG_MICROPY_HEAP_SIZE=458752 +CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES=y From 0657a804bc1f3c016fdc6c45b958c0c8f66699fe Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 1 May 2026 11:52:21 +1000 Subject: [PATCH 1991/2098] zephyr/boards/mimxrt1020_evk: Use conf file to select feature level. Signed-off-by: Damien George --- ports/zephyr/boards/mimxrt1020_evk.conf | 9 +++++---- ports/zephyr/boards/mimxrt1020_evk/mpconfigport.h | 3 --- 2 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 ports/zephyr/boards/mimxrt1020_evk/mpconfigport.h diff --git a/ports/zephyr/boards/mimxrt1020_evk.conf b/ports/zephyr/boards/mimxrt1020_evk.conf index b782520514d..cc1fb290475 100644 --- a/ports/zephyr/boards/mimxrt1020_evk.conf +++ b/ports/zephyr/boards/mimxrt1020_evk.conf @@ -7,10 +7,6 @@ CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH_PAGE_LAYOUT=y -CONFIG_MICROPY_FROZEN_MODULES=y -CONFIG_MICROPY_FROZEN_MANIFEST="boards/mimxrt1020_evk/manifest.py" -CONFIG_MICROPY_CONFIGFILE="boards/mimxrt1020_evk/mpconfigport.h" - # CONFIG_DYNAMIC_THREAD=y CONFIG_THREAD_CUSTOM_DATA=y CONFIG_THREAD_MONITOR=y @@ -19,3 +15,8 @@ CONFIG_THREAD_STACK_INFO=y CONFIG_LOG=n CONFIG_FP16=n CONFIG_BOOT_BANNER=n + +# MicroPython config. +CONFIG_MICROPY_FROZEN_MODULES=y +CONFIG_MICROPY_FROZEN_MANIFEST="boards/mimxrt1020_evk/manifest.py" +CONFIG_MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES=y diff --git a/ports/zephyr/boards/mimxrt1020_evk/mpconfigport.h b/ports/zephyr/boards/mimxrt1020_evk/mpconfigport.h deleted file mode 100644 index 6c62ad5497e..00000000000 --- a/ports/zephyr/boards/mimxrt1020_evk/mpconfigport.h +++ /dev/null @@ -1,3 +0,0 @@ -#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) - -#include "../mpconfigport.h" From 35d09a7b1922dfee996e82fc52ec1250be564d0a Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 1 May 2026 11:56:31 +1000 Subject: [PATCH 1992/2098] zephyr/boards/xiao_ble_nrf52840_sense: Use conf to select feature level. Signed-off-by: Damien George --- .../boards/xiao_ble_nrf52840_sense.conf | 13 ++++----- .../xiao_ble_nrf52840_sense/mpconfigport.h | 27 ------------------- 2 files changed, 7 insertions(+), 33 deletions(-) delete mode 100644 ports/zephyr/boards/xiao_ble_nrf52840_sense/mpconfigport.h diff --git a/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf b/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf index 541ade2af24..9544bd91044 100644 --- a/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf +++ b/ports/zephyr/boards/xiao_ble_nrf52840_sense.conf @@ -21,7 +21,6 @@ CONFIG_BT_L2CAP_TX_MTU=252 CONFIG_BT_BUF_ACL_RX_SIZE=256 CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n -CONFIG_MICROPY_HEAP_SIZE=98304 CONFIG_MAIN_STACK_SIZE=8192 # Enable drivers for peripherals @@ -37,10 +36,6 @@ CONFIG_FLASH_PAGE_LAYOUT=y CONFIG_DISK_ACCESS=n -CONFIG_MICROPY_FROZEN_MODULES=y -CONFIG_MICROPY_FROZEN_MANIFEST="boards/xiao_ble_nrf52840_sense/manifest.py" -CONFIG_MICROPY_CONFIGFILE="boards/xiao_ble_nrf52840_sense/mpconfigport.h" - # CONFIG_DYNAMIC_THREAD=y CONFIG_THREAD_CUSTOM_DATA=y CONFIG_THREAD_MONITOR=y @@ -48,4 +43,10 @@ CONFIG_THREAD_STACK_INFO=y CONFIG_LOG=n CONFIG_FP16=n -CONFIG_BOOT_BANNER=n \ No newline at end of file +CONFIG_BOOT_BANNER=n + +# MicroPython config. +CONFIG_MICROPY_HEAP_SIZE=98304 +CONFIG_MICROPY_FROZEN_MODULES=y +CONFIG_MICROPY_FROZEN_MANIFEST="boards/xiao_ble_nrf52840_sense/manifest.py" +CONFIG_MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES=y diff --git a/ports/zephyr/boards/xiao_ble_nrf52840_sense/mpconfigport.h b/ports/zephyr/boards/xiao_ble_nrf52840_sense/mpconfigport.h deleted file mode 100644 index 03ec9206fc0..00000000000 --- a/ports/zephyr/boards/xiao_ble_nrf52840_sense/mpconfigport.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * 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. - */ - -#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) - -#include "../mpconfigport.h" From 00c5968b35de0ed1d729d413be70f1f2cc666fca Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 3 Dec 2025 19:00:36 +0100 Subject: [PATCH 1993/2098] zephyr/boards: Add some Bouffalolab-based boards. Finally those have all basic features, add them. Signed-off-by: Vdragon --- ports/zephyr/boards/ai_m61_32s_kit.conf | 32 +++++++++++++++++++++ ports/zephyr/boards/ai_m61_32s_kit.overlay | 33 ++++++++++++++++++++++ ports/zephyr/boards/ai_m62_12f_kit.conf | 31 ++++++++++++++++++++ ports/zephyr/boards/ai_m62_12f_kit.overlay | 27 ++++++++++++++++++ ports/zephyr/boards/ai_wb2_12f_kit.conf | 31 ++++++++++++++++++++ ports/zephyr/boards/ai_wb2_12f_kit.overlay | 27 ++++++++++++++++++ 6 files changed, 181 insertions(+) create mode 100644 ports/zephyr/boards/ai_m61_32s_kit.conf create mode 100644 ports/zephyr/boards/ai_m61_32s_kit.overlay create mode 100644 ports/zephyr/boards/ai_m62_12f_kit.conf create mode 100644 ports/zephyr/boards/ai_m62_12f_kit.overlay create mode 100644 ports/zephyr/boards/ai_wb2_12f_kit.conf create mode 100644 ports/zephyr/boards/ai_wb2_12f_kit.overlay diff --git a/ports/zephyr/boards/ai_m61_32s_kit.conf b/ports/zephyr/boards/ai_m61_32s_kit.conf new file mode 100644 index 00000000000..e99a379fde7 --- /dev/null +++ b/ports/zephyr/boards/ai_m61_32s_kit.conf @@ -0,0 +1,32 @@ +# Hardware features. +CONFIG_PINCTRL=y +CONFIG_GPIO=y +CONFIG_WATCHDOG=n +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_GETCHAR_BUFSIZE=256 +CONFIG_I2C=y +CONFIG_I2C_TARGET=y +CONFIG_SPI=y +CONFIG_MEMC=y + +# Disable networking. +CONFIG_NETWORKING=n + +# MicroPython config. +CONFIG_MICROPY_HEAP_SIZE=262144 +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES=y + + +# File System Configuration +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_MICROPY_VFS_FAT=y +CONFIG_MICROPY_VFS_LFS1=n +CONFIG_MICROPY_VFS_LFS2=n +# Default heap for littlefs is too small +CONFIG_FS_LITTLEFS_FC_HEAP_SIZE=8192 diff --git a/ports/zephyr/boards/ai_m61_32s_kit.overlay b/ports/zephyr/boards/ai_m61_32s_kit.overlay new file mode 100644 index 00000000000..59e9f3ad696 --- /dev/null +++ b/ports/zephyr/boards/ai_m61_32s_kit.overlay @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + fstab { + compatible = "zephyr,fstab"; + lfs: lfs { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<256>; + cache-size=<1024>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; + + heap_sram1 { + compatible = "micropython,heap"; + size = ; + memory-region = <&sram1>; + }; + + heap_psram { + compatible = "micropython,heap"; + size = ; + memory-region = <&psram>; + }; +}; diff --git a/ports/zephyr/boards/ai_m62_12f_kit.conf b/ports/zephyr/boards/ai_m62_12f_kit.conf new file mode 100644 index 00000000000..aa4c7c6b35b --- /dev/null +++ b/ports/zephyr/boards/ai_m62_12f_kit.conf @@ -0,0 +1,31 @@ +# Hardware features. +CONFIG_PINCTRL=y +CONFIG_GPIO=y +CONFIG_WATCHDOG=n +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_GETCHAR_BUFSIZE=256 +CONFIG_I2C=y +CONFIG_I2C_TARGET=y +CONFIG_SPI=y + +# Disable networking. +CONFIG_NETWORKING=n + +# MicroPython config. +CONFIG_MICROPY_HEAP_SIZE=262144 +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES=y + + +# File System Configuration +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_MICROPY_VFS_FAT=y +CONFIG_MICROPY_VFS_LFS1=n +CONFIG_MICROPY_VFS_LFS2=n +# Default heap for littlefs is too small +CONFIG_FS_LITTLEFS_FC_HEAP_SIZE=8192 diff --git a/ports/zephyr/boards/ai_m62_12f_kit.overlay b/ports/zephyr/boards/ai_m62_12f_kit.overlay new file mode 100644 index 00000000000..17cb2ffdc9d --- /dev/null +++ b/ports/zephyr/boards/ai_m62_12f_kit.overlay @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + fstab { + compatible = "zephyr,fstab"; + lfs: lfs { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<256>; + cache-size=<1024>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; + + heap_sram1 { + compatible = "micropython,heap"; + size = ; + memory-region = <&sram1>; + }; +}; diff --git a/ports/zephyr/boards/ai_wb2_12f_kit.conf b/ports/zephyr/boards/ai_wb2_12f_kit.conf new file mode 100644 index 00000000000..73ceb27bb71 --- /dev/null +++ b/ports/zephyr/boards/ai_wb2_12f_kit.conf @@ -0,0 +1,31 @@ +# Hardware features. +CONFIG_PINCTRL=y +CONFIG_GPIO=y +CONFIG_WATCHDOG=n +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_GETCHAR_BUFSIZE=256 +CONFIG_I2C=y +CONFIG_I2C_TARGET=y +CONFIG_SPI=y + +# Disable networking. +CONFIG_NETWORKING=n + +# MicroPython config. +CONFIG_MICROPY_HEAP_SIZE=131072 +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES=y + + +# File System Configuration +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_MICROPY_VFS_FAT=y +CONFIG_MICROPY_VFS_LFS1=n +CONFIG_MICROPY_VFS_LFS2=n +# Default heap for littlefs is too small +CONFIG_FS_LITTLEFS_FC_HEAP_SIZE=8192 diff --git a/ports/zephyr/boards/ai_wb2_12f_kit.overlay b/ports/zephyr/boards/ai_wb2_12f_kit.overlay new file mode 100644 index 00000000000..e6e7fdd5141 --- /dev/null +++ b/ports/zephyr/boards/ai_wb2_12f_kit.overlay @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + fstab { + compatible = "zephyr,fstab"; + lfs: lfs { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<256>; + cache-size=<1024>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; + + heap_dtcm { + compatible = "micropython,heap"; + size = ; + memory-region = <&dtcm>; + }; +}; From 56b168b0868450ca3edf021290dd1dc013cc7609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fin=20Maa=C3=9F?= Date: Tue, 13 Jan 2026 14:02:57 +0100 Subject: [PATCH 1994/2098] zephyr/machine_i2c: Rework I2C driver to make continuous transactions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop using `mp_machine_i2c_transfer_adaptor` and instead implement support for `MP_MACHINE_I2C_FLAG_WRITE1`. That allows combined write-read transactions like `I2C.readfrom_mem()` to work properly with a RESTART, instead of being split into a separate write-with-STOP followed by a read-with-STOP (many I2C controllers in zephyr don't support incomplete transactions). Signed-off-by: Fin Maaß --- ports/zephyr/machine_i2c.c | 81 ++++++++++++++++++++++++++++--------- ports/zephyr/mpconfigport.h | 1 + 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 24281b661d8..30eff870f4a 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -5,6 +5,7 @@ * * Copyright (c) 2013, 2014, 2015 Damien P. George * Copyright (c) 2019, NXP + * Copyright (c) 2026 Fin Maaß * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -44,7 +45,6 @@ typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; const struct device *dev; - bool restart; } machine_hard_i2c_obj_t; static void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -82,44 +82,85 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz machine_hard_i2c_obj_t *self = mp_obj_malloc(machine_hard_i2c_obj_t, &machine_i2c_type); self->dev = dev; - self->restart = false; return MP_OBJ_FROM_PTR(self); } -static int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size_t len, uint8_t *buf, unsigned int flags) { +static int machine_hard_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { machine_hard_i2c_obj_t *self = (machine_hard_i2c_obj_t *)self_in; - struct i2c_msg msg; + struct i2c_msg msg[2] = {0}; int ret; + size_t len; + size_t i; + uint8_t start_idx = 0; + + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + msg[0].buf = bufs[0].buf; + msg[0].len = bufs[0].len; + msg[0].flags = I2C_MSG_WRITE; + msg[1].flags = I2C_MSG_RESTART; + start_idx = 1; + n--; + bufs++; + } + + if (n == 0) { + return -MP_EINVAL; + } - msg.buf = (uint8_t *)buf; - msg.len = len; - msg.flags = 0; + if (!(flags & MP_MACHINE_I2C_FLAG_STOP)) { + return -MP_EINVAL; + } if (flags & MP_MACHINE_I2C_FLAG_READ) { - msg.flags |= I2C_MSG_READ; + msg[start_idx].flags |= I2C_MSG_READ | I2C_MSG_STOP; } else { - msg.flags |= I2C_MSG_WRITE; + msg[start_idx].flags |= I2C_MSG_WRITE | I2C_MSG_STOP; } - if (self->restart) { - msg.flags |= I2C_MSG_RESTART; + if (n == 1) { + // Use given single buffer + msg[start_idx].buf = bufs[0].buf; + msg[start_idx].len = bufs[0].len; + } else { + // Combine buffers into a single one + msg[start_idx].len = 0; + for (i = 0; i < n; ++i) { + msg[start_idx].len += bufs[i].len; + } + msg[start_idx].buf = m_new(uint8_t, msg[start_idx].len); + if (!(flags & MP_MACHINE_I2C_FLAG_READ)) { + len = 0; + for (i = 0; i < n; ++i) { + memcpy(&msg[start_idx].buf[len], bufs[i].buf, bufs[i].len); + len += bufs[i].len; + } + } } - if (flags & MP_MACHINE_I2C_FLAG_STOP) { - msg.flags |= I2C_MSG_STOP; - self->restart = false; - } else { - self->restart = true; + ret = i2c_transfer(self->dev, msg, start_idx + 1, addr); + if (ret < 0) { + return -MP_EIO; + } + + if (n > 1) { + if (flags & MP_MACHINE_I2C_FLAG_READ) { + // Copy data from single buffer to individual ones + len = 0; + for (i = 0; i < n; ++i) { + memcpy(bufs[i].buf, &msg[start_idx].buf[len], bufs[i].len); + len += bufs[i].len; + } + } + m_del(uint8_t, msg[start_idx].buf, msg[start_idx].len); } - ret = i2c_transfer(self->dev, &msg, 1, addr); - return (ret < 0) ? -MP_EIO : len; + return msg[0].len + msg[1].len; } static const mp_machine_i2c_p_t machine_hard_i2c_p = { - .transfer = mp_machine_i2c_transfer_adaptor, - .transfer_single = machine_hard_i2c_transfer_single, + .transfer_supports_write1 = true, + .transfer = machine_hard_i2c_transfer, }; MP_DEFINE_CONST_OBJ_TYPE( diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index ceaa6df43c4..695e456784f 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -75,6 +75,7 @@ #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/zephyr/modmachine.c" #define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) #ifdef CONFIG_I2C_TARGET #define MICROPY_PY_MACHINE_I2C_TARGET (1) #define MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE "ports/zephyr/machine_i2c_target.c" From 9d10e2945d3b223573d04caa461ab4e9d04fcec6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 Apr 2026 15:05:32 +1000 Subject: [PATCH 1995/2098] zephyr/mpconfigport_minimal: Configure MICROPY_STACK_CHECK_MARGIN. Stack checking is enabled with the minimal configuration, so this margin configuration must be set or else the stack can easily overflow when running the recursive stress tests. Signed-off-by: Damien George --- ports/zephyr/mpconfigport_minimal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index 83030804856..118b2e87ec5 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -43,6 +43,7 @@ #define MICROPY_ENABLE_COMPILER (1) #define MICROPY_ENABLE_EXTERNAL_IMPORT (1) #define MICROPY_STACK_CHECK (1) +#define MICROPY_STACK_CHECK_MARGIN (512) #define MICROPY_ENABLE_GC (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_AUTO_INDENT (1) From 37399d5bcfc56b9230995508d24cb92a3c29b9e6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 14 Jan 2026 14:36:45 +1100 Subject: [PATCH 1996/2098] zephyr/mphalport: Allow mp_hal_wait_sem() to exit early when signalled. A new argument is added to this function to select whether it exits early when signalled via `mp_hal_signal_event()`, so it can be used to wait for general events (rather than just a specific semaphore). And rename this function to `mp_hal_wait_event()` to better match its new semantics. Signed-off-by: Damien George --- ports/zephyr/mphalport.c | 12 +++++++++++- ports/zephyr/mphalport.h | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/mphalport.c b/ports/zephyr/mphalport.c index d744752df9f..98b310da0b6 100644 --- a/ports/zephyr/mphalport.c +++ b/ports/zephyr/mphalport.c @@ -46,7 +46,12 @@ void mp_hal_signal_event(void) { k_poll_signal_raise(&wait_signal, 0); } -void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms) { +// Wait for an event or semaphore to be set, with a timeout. +// Arguments: +// - exit_on_event: set to true to exit on any event signalled via `mp_hal_signal_event()` +// - sem: set to a semaphore to exit when that semaphore is set +// - timeout_ms: maximum time to wait, or -1 to wait forever +void mp_hal_wait_event(bool exit_on_event, struct k_sem *sem, uint32_t timeout_ms) { mp_uint_t t0 = mp_hal_ticks_ms(); if (sem) { k_poll_event_init(&wait_events[1], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, sem); @@ -65,11 +70,16 @@ void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms) { if (wait_events[0].state == K_POLL_STATE_SIGNALED) { wait_events[0].signal->signaled = 0; wait_events[0].state = K_POLL_STATE_NOT_READY; + if (exit_on_event) { + MP_THREAD_GIL_ENTER(); + return; + } } else if (sem && wait_events[1].state == K_POLL_STATE_SEM_AVAILABLE) { wait_events[1].state = K_POLL_STATE_NOT_READY; MP_THREAD_GIL_ENTER(); return; } + dt = mp_hal_ticks_ms() - t0; if (dt >= timeout_ms) { MP_THREAD_GIL_ENTER(); return; diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 7410204621e..97c319c2785 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -6,7 +6,7 @@ #define MICROPY_END_ATOMIC_SECTION irq_unlock void mp_hal_init(void); -void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms); +void mp_hal_wait_event(bool exit_on_event, struct k_sem *sem, uint32_t timeout_ms); static inline mp_uint_t mp_hal_ticks_us(void) { return k_cyc_to_ns_floor64(k_cycle_get_32()) / 1000; @@ -28,7 +28,7 @@ static inline void mp_hal_delay_us(mp_uint_t delay) { } static inline void mp_hal_delay_ms(mp_uint_t delay) { - mp_hal_wait_sem(NULL, delay); + mp_hal_wait_event(false, NULL, delay); } static inline uint64_t mp_hal_time_ns(void) { From c7187da689f4f918b0866aee772039e310e8dc9f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 14 Jan 2026 11:51:52 +1100 Subject: [PATCH 1997/2098] zephyr: Convert port to use new event waiting functions. This commit converts the zephyr port to use the new event waiting mechanism, with the `MICROPY_INTERNAL_WFE` macro. Signed-off-by: Damien George --- ports/zephyr/machine_uart.c | 6 ++++-- ports/zephyr/mpconfigport.h | 16 ---------------- ports/zephyr/mpconfigport_minimal.h | 2 -- ports/zephyr/mphalport.h | 12 ++++++++++++ ports/zephyr/uart_core.c | 4 ++-- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c index 81957c69330..291d135ebb1 100644 --- a/ports/zephyr/machine_uart.c +++ b/ports/zephyr/machine_uart.c @@ -217,7 +217,7 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ // wait for any pending transmission to complete while (!mp_machine_uart_txdone(self)) { - MICROPY_EVENT_POLL_HOOK; + mp_event_wait_indefinite(); } int _ex_size = 0; @@ -252,7 +252,7 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint } } else if (request == MP_STREAM_FLUSH) { while (!mp_machine_uart_txdone(self)) { - MICROPY_EVENT_POLL_HOOK; + mp_event_wait_indefinite(); } } else { *errcode = MP_EINVAL; @@ -265,6 +265,8 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint static void uart_interrupt_handler(const struct device *dev, void *user_data) { machine_uart_obj_t *self = (machine_uart_obj_t *)user_data; + mp_hal_signal_event(); + while (uart_irq_update(dev), uart_irq_is_pending(dev)) { if (uart_irq_rx_ready(dev)) { uint8_t _rx_buffer[32]; diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 695e456784f..7fc5196ec30 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -193,22 +193,6 @@ typedef long mp_off_t; #define MP_SSIZE_MAX (0x7fffffff) -#if MICROPY_PY_THREAD -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - MP_THREAD_GIL_EXIT(); \ - k_msleep(1); \ - MP_THREAD_GIL_ENTER(); \ - } while (0); -#else -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS); \ - k_msleep(1); \ - } while (0); -#endif - // Compatibility switches #ifdef CONFIG_NEWLIB_LIBC diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h index 118b2e87ec5..483fae2597d 100644 --- a/ports/zephyr/mpconfigport_minimal.h +++ b/ports/zephyr/mpconfigport_minimal.h @@ -77,8 +77,6 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM -#define MICROPY_EVENT_POLL_HOOK - // Compatibility switches #ifdef CONFIG_NEWLIB_LIBC diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 97c319c2785..dc3ee33558f 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -5,6 +5,18 @@ #define MICROPY_BEGIN_ATOMIC_SECTION irq_lock #define MICROPY_END_ATOMIC_SECTION irq_unlock +// Port level Wait-for-Event macro. +// Do not use this macro directly, include py/runtime.h and +// call mp_event_wait_indefinite() or mp_event_wait_ms(timeout). +#define MICROPY_INTERNAL_WFE(TIMEOUT_MS) \ + do { \ + if ((TIMEOUT_MS) < 0) { \ + mp_hal_wait_event(true, NULL, (uint32_t)-1); \ + } else { \ + mp_hal_wait_event(true, NULL, (TIMEOUT_MS)); \ + } \ + } while (0) + void mp_hal_init(void); void mp_hal_wait_event(bool exit_on_event, struct k_sem *sem, uint32_t timeout_ms); diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index fe8a2a51db9..0c1c5c1e00d 100644 --- a/ports/zephyr/uart_core.c +++ b/ports/zephyr/uart_core.c @@ -83,7 +83,7 @@ int mp_hal_stdin_rx_chr(void) { if (_chr >= 0) { return _chr; } - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } @@ -94,7 +94,7 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { while (len--) { char c = *str++; while (mp_console_putchar(c) == -1) { - MICROPY_EVENT_POLL_HOOK + mp_event_wait_ms(1); } } #else From 4814df9b75596ae71b5de5c76ace7707f0dbdd94 Mon Sep 17 00:00:00 2001 From: Calin Faja Date: Mon, 9 Mar 2026 08:07:58 +0200 Subject: [PATCH 1998/2098] zephyr/machine_uart: Fix stop bits config and swapped buffer defaults. Fix three bugs in the Zephyr UART init: 1. Stop bits variable typo: `data_bits = UART_CFG_STOP_BITS_2` assigned to the wrong variable, corrupting data_bits and leaving stop_bits uninitialized when stop=2 is requested. 2. Config struct used raw `args[ARG_stop].u_int` (e.g. integer 2) instead of the computed `stop_bits` enum (e.g. `UART_CFG_STOP_BITS_2 = 3`), resulting in `UART_CFG_STOP_BITS_1_5` being configured instead of `UART_CFG_STOP_BITS_2`. 3. Swapped default values for txbuf/rxbuf arguments: `MP_QSTR_txbuf` defaulted to `UART_RX_RING_BUF_DEF_SIZE` and vice versa (latent since both are 128 today). Signed-off-by: Calin Faja --- ports/zephyr/machine_uart.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c index 291d135ebb1..ad335961bc1 100644 --- a/ports/zephyr/machine_uart.c +++ b/ports/zephyr/machine_uart.c @@ -83,8 +83,8 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, { MP_QSTR_bits, MP_ARG_INT, {.u_int = 8} }, { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_stop, MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_RX_RING_BUF_DEF_SIZE} }, - { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_TX_RING_BUF_DEF_SIZE} }, + { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_TX_RING_BUF_DEF_SIZE} }, + { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_RX_RING_BUF_DEF_SIZE} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, @@ -126,7 +126,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, if (args[ARG_stop].u_int == 1) { stop_bits = UART_CFG_STOP_BITS_1; } else if (args[ARG_stop].u_int == 2) { - data_bits = UART_CFG_STOP_BITS_2; + stop_bits = UART_CFG_STOP_BITS_2; } else { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid stop bits")); } @@ -143,7 +143,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const struct uart_config cfg = { .baudrate = args[ARG_baudrate].u_int, .parity = parity, - .stop_bits = args[ARG_stop].u_int, + .stop_bits = stop_bits, .data_bits = data_bits, .flow_ctrl = flow_ctrl }; From 7515a495a0a298d5caba547a7249729bcefc20e6 Mon Sep 17 00:00:00 2001 From: rmouro_QCOM Date: Mon, 16 Mar 2026 19:09:15 -0700 Subject: [PATCH 1999/2098] zephyr/boards/arduino_uno_q: Add board config for Arduino UNO Q. Add board configuration files for the Arduino UNO Q, enabling MicroPython on its STM32U585 (Cortex-M33) microcontroller. arduino_uno_q.overlay: - Redirects zephyr,console from &usart1 - Adds a 256 KB storage_partition at 0xF0000 for littlefs /flash arduino_uno_q.conf: - Enables serial console, GPIO, flash, and littlefs filesystem Signed-off-by: rmouro_QCOM --- ports/zephyr/boards/arduino_uno_q.conf | 33 +++++++ ports/zephyr/boards/arduino_uno_q.overlay | 100 ++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 ports/zephyr/boards/arduino_uno_q.conf create mode 100644 ports/zephyr/boards/arduino_uno_q.overlay diff --git a/ports/zephyr/boards/arduino_uno_q.conf b/ports/zephyr/boards/arduino_uno_q.conf new file mode 100644 index 00000000000..17512a99392 --- /dev/null +++ b/ports/zephyr/boards/arduino_uno_q.conf @@ -0,0 +1,33 @@ +# Serial console (REPL) via LPUART1 → /dev/ttyHS1 +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# GPIO support +CONFIG_GPIO=y + +# I2C support +# arduino_i2c / i2c2 : PB10 (SCL), PB11 (SDA) — Arduino header SDA/SCL +# i2c4 : PD12 (SCL), PD13 (SDA) — Qwiic connector +CONFIG_I2C=y + +# SPI support +# arduino_spi / spi2 : PB13 (SCK), PB14 (MISO), PB15 (MOSI), PB9 (NSS) +CONFIG_SPI=y + +# ADC support +# adc1 channels 9/10/11/12/2/1 → Arduino pins A0-A5 +CONFIG_ADC=y + +# Flash and filesystem for MicroPython /flash mount +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y + +# Stack and heap sizes for MicroPython +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_HEAP_MEM_POOL_SIZE=16384 diff --git a/ports/zephyr/boards/arduino_uno_q.overlay b/ports/zephyr/boards/arduino_uno_q.overlay new file mode 100644 index 00000000000..f250f89472e --- /dev/null +++ b/ports/zephyr/boards/arduino_uno_q.overlay @@ -0,0 +1,100 @@ +# ----------------------------------------------------------------------------- +# +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: MIT +# +# ----------------------------------------------------------------------------- + +/* + * MicroPython on Arduino UNO Q — DTS Overlay + * + * Three additions over the upstream board DTS: + * + * 1. Console redirect: The board DTS sets zephyr,console = &usart1 + * which routes to Arduino header pins D0/D1. LPUART1 (PG7/PG8) + * is the internal MCU↔QRB2210 UART, exposed as /dev/ttyHS1 on + * the Linux side at 115200 baud. + * + * 2. Storage partition: Adds a 256 KB littlefs partition for + * MicroPython's /flash filesystem. Placed at 0xF0000, after + * the existing scratch_partition (0xE0000, 64 KB). The region + * from 0xF0000 to 0x200000 (1088 KB) is unused in the stock + * partition layout. + * + * 3. FSTAB entry: Registers the storage partition with Zephyr's + * filesystem table so that zephyr.FileSystem("/flash") works from + * Python and Zephyr auto-mounts the partition at boot. Parameters + * match the STM32U5 flash: write-block-size=16, erase-block-size=8192. + * + * Flash layout (2 MB total): + * 0x00000 - 0x0FFFF boot_partition 64 KB (MCUboot, stock) + * 0x10000 - 0x7FFFF slot0_partition 448 KB (primary image, stock) + * 0x80000 - 0xDFFFF slot1_partition 384 KB (secondary image, stock) + * 0xE0000 - 0xEFFFF scratch_partition 64 KB (MCUboot swap, stock) + * 0xF0000 - 0x12FFFF storage_partition 256 KB (MicroPython /flash) <- added + * 0x130000 - 0x1FFFFF (unused) 832 KB + */ + +/ { + chosen { + zephyr,console = &lpuart1; + zephyr,shell-uart = &lpuart1; + }; + + /* + * ADC channels exposed to MicroPython via machine.ADC(("adc1", ch)). + * Maps Arduino analog pins to STM32 ADC1 channel numbers: + * A0 = PA4 = ch 9 A1 = PA5 = ch 10 A2 = PA6 = ch 11 + * A3 = PA7 = ch 12 A4 = PC1 = ch 2 A5 = PC0 = ch 1 + */ + zephyr,user { + io-channels = <&adc1 9>, <&adc1 10>, <&adc1 11>, + <&adc1 12>, <&adc1 2>, <&adc1 1>; + }; +}; + +&lpuart1 { + status = "okay"; + current-speed = <115200>; +}; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* 256 KB littlefs partition for MicroPython /flash filesystem */ + storage_partition: partition@f0000 { + label = "storage"; + reg = <0x000f0000 DT_SIZE_K(256)>; + }; + }; +}; + +/* + * FSTAB entry for MicroPython /flash filesystem. + * + * Enables zephyr.FileSystem("/flash") in Python and causes Zephyr to + * auto-mount the littlefs partition at boot (before _boot.py runs). + * + * STM32U5 flash parameters (from stm32u5.dtsi): + * write-block-size = 16 -> prog-size = 16 + * erase-block-size = 8192 -> block size handled by littlefs automatically + */ +/ { + fstab: fstab { + compatible = "zephyr,fstab"; + lfs1: lfs1 { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + automount; + read-size = <16>; + prog-size = <16>; + cache-size = <64>; + lookahead-size = <32>; + block-cycles = <512>; + }; + }; +}; From 953773c3509628ce106c7c3650c86fad8aae2bf4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 17 Apr 2026 14:17:12 +1000 Subject: [PATCH 2000/2098] alif/system_tick: Document differences between SysTick/LPTIMER/UTIMER. Signed-off-by: Damien George --- ports/alif/system_tick.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ports/alif/system_tick.c b/ports/alif/system_tick.c index 4f161e6709e..3a04e4390a3 100644 --- a/ports/alif/system_tick.c +++ b/ports/alif/system_tick.c @@ -31,6 +31,11 @@ #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK +// SysTick is used as the system timer source, with the following properties: +// - SysTick IRQ fires every 1ms to update a millisecond counter +// - access to the millisecond counter is very fast +// - ms ticks will be lost if IRQs are disabled for longer than 1ms + #include "shared/runtime/softtimer.h" #include "pendsv.h" @@ -93,6 +98,12 @@ void system_tick_wfe_with_timeout_us(uint32_t timeout_us) { #elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER +// LPTIMER is used as the system timer source, with the following properties: +// - 64-bit hardware counter running at 32768Hz +// - reading the counter is very slow due to the LPTIMER being clocked at a very +// slow rate, and synchronisation to the CPU bus takes a long time +// - delays are tickless, CPU only wakes up at end of timeout + #include "lptimer.h" #include "sys_ctrl_lptimer.h" @@ -222,6 +233,11 @@ void system_tick_schedule_after_us(uint32_t ticks_us) { #elif MICROPY_HW_SYSTEM_TICK_USE_UTIMER +// UTIMER is used as the system timer source, with the following properties: +// - 32-bit counter with 32-bit overflow variable running at 400MHz +// - costs about 10uW +// - delays are tickless, CPU only wakes up at end of timeout + #include "utimer.h" #define UTIMER ((UTIMER_Type *)UTIMER_BASE) From b3806c5edb0313710d5920c05a9644583c6ecaa6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 17 Apr 2026 14:18:34 +1000 Subject: [PATCH 2001/2098] alif/system_tick: Add system_tick_get_ms_fast when SysTick is enabled. This function gives direct access to the 32-bit SysTick millisecond counter. And then use it to implement `mp_hal_ticks_ms()`. Signed-off-by: Damien George --- ports/alif/mphalport.c | 2 +- ports/alif/system_tick.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 04e21aa2576..973327577ab 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -133,7 +133,7 @@ mp_uint_t mp_hal_ticks_us(void) { mp_uint_t mp_hal_ticks_ms(void) { // Convert system tick to millisecond counter. #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK - return system_tick_get_u64() / 1000ULL; + return system_tick_get_ms_fast(); #elif MICROPY_HW_SYSTEM_TICK_USE_LPTIMER return system_tick_get_u64() * 1000ULL / system_tick_source_hz; #else diff --git a/ports/alif/system_tick.h b/ports/alif/system_tick.h index a380808b2e0..af662dbea95 100644 --- a/ports/alif/system_tick.h +++ b/ports/alif/system_tick.h @@ -41,4 +41,11 @@ void system_tick_wfe_with_timeout_us(uint32_t timeout_us); void system_tick_schedule_after_us(uint32_t ticks_us); void system_tick_schedule_callback(void); +#if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK +static inline uint32_t system_tick_get_ms_fast(void) { + extern volatile uint32_t system_tick_ms_counter; + return system_tick_ms_counter; +} +#endif + #endif // MICROPY_INCLUDED_ALIF_SYSTEM_TICK_H From 65399bc36e94ab4a0c8e6bef1fb395b5157e21e8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 17 Apr 2026 14:20:04 +1000 Subject: [PATCH 2002/2098] alif/mphalport: Use DWT->CYCCNT for CPU tick if SysTick/LPTIMER is used. Using CYCCNT gives much higher resolution, at 400MHz. Signed-off-by: Damien George --- ports/alif/mphalport.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ports/alif/mphalport.c b/ports/alif/mphalport.c index 973327577ab..ca364f6dad0 100644 --- a/ports/alif/mphalport.c +++ b/ports/alif/mphalport.c @@ -116,7 +116,19 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { } mp_uint_t mp_hal_ticks_cpu(void) { + #if MICROPY_HW_SYSTEM_TICK_USE_SYSTICK || MICROPY_HW_SYSTEM_TICK_USE_LPTIMER + // SysTick and LPTIMER run relatively slowly, so use cycle counter for CPU ticks. + if (!(DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk)) { + // Enable CYCCNT. + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + } + return DWT->CYCCNT; + #else + // UTIMER runs at CPU frequency so use it directly as CPU ticks. return system_tick_get_u32(); + #endif } mp_uint_t mp_hal_ticks_us(void) { From b5e1974f963ef5e0ccca9579406acf450c309c12 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 21 Apr 2026 13:26:11 +1000 Subject: [PATCH 2003/2098] alif: Add support for building C++ user modules. This allows alif boards to build and run `examples/usercmodule`. Signed-off-by: Damien George --- ports/alif/mcu/ensemble.ld.S | 8 ++++++++ ports/alif/nosys_stubs.c | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/ports/alif/mcu/ensemble.ld.S b/ports/alif/mcu/ensemble.ld.S index aeefdb7eacb..cbfe142e731 100644 --- a/ports/alif/mcu/ensemble.ld.S +++ b/ports/alif/mcu/ensemble.ld.S @@ -75,6 +75,14 @@ SECTIONS . = ALIGN(16); } > ROM + /* For C++ exception handling */ + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > ROM + .copy.table : ALIGN(4) { __copy_table_start__ = .; diff --git a/ports/alif/nosys_stubs.c b/ports/alif/nosys_stubs.c index a394ec8f1cf..82f3cabe3e5 100644 --- a/ports/alif/nosys_stubs.c +++ b/ports/alif/nosys_stubs.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ #include +#include int _write(int handle, char *buffer, int size) { errno = ENOSYS; @@ -44,3 +45,12 @@ int _lseek(int f, int ptr, int dir) { errno = ENOSYS; return -1; } + +pid_t _getpid(void) { + return 0; +} + +int _kill(pid_t pid, int sig) { + errno = EINVAL; + return -1; +} From bad1c609531b9bbd26fd0624b916117a8ef232d7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 21 Apr 2026 13:29:29 +1000 Subject: [PATCH 2004/2098] tools/ci.sh: Build user C example modules as part of alif CI. Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 041a7a06b0e..b1c71d195b7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -1066,7 +1066,7 @@ function ci_alif_ae3_build { make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HP submodules make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HE submodules make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_DUAL - make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL + make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL USER_C_MODULES=../../examples/usercmodule } function _ci_help { From 42141e94fee023a4f56f51e49201a8dd2b5fc7cf Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Apr 2026 13:31:35 +1000 Subject: [PATCH 2005/2098] mimxrt/systick: Remove unused systick helper functions. These legacy functions were copied verbatim from the stm32 port and never used. And the use of WFI in `systick_wait_at_least()` is probably wrong. And correct the comment in SysTick_Handler, which was also copied from stm32. Signed-off-by: Damien George --- ports/mimxrt/systick.c | 17 +---------------- ports/mimxrt/systick.h | 3 --- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/ports/mimxrt/systick.c b/ports/mimxrt/systick.c index 8b0f5eb7b2d..fe75397e38e 100644 --- a/ports/mimxrt/systick.c +++ b/ports/mimxrt/systick.c @@ -36,9 +36,7 @@ volatile uint32_t systick_ms = 0; systick_dispatch_t systick_dispatch_table[SYSTICK_DISPATCH_NUM_SLOTS]; void SysTick_Handler(void) { - // Instead of calling HAL_IncTick we do the increment here of the counter. - // This is purely for efficiency, since SysTick is called 1000 times per - // second at the highest interrupt priority. + // Increment the systick millisecond counter. uint32_t uw_tick = systick_ms + 1; systick_ms = uw_tick; @@ -52,16 +50,3 @@ void SysTick_Handler(void) { pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); } } - -bool systick_has_passed(uint32_t start_tick, uint32_t delay_ms) { - return systick_ms - start_tick >= delay_ms; -} - -// waits until at least delay_ms milliseconds have passed from the sampling of -// startTick. Handles overflow properly. Assumes stc was taken from -// HAL_GetTick() some time before calling this function. -void systick_wait_at_least(uint32_t start_tick, uint32_t delay_ms) { - while (!systick_has_passed(start_tick, delay_ms)) { - __WFI(); // enter sleep mode, waiting for interrupt - } -} diff --git a/ports/mimxrt/systick.h b/ports/mimxrt/systick.h index 2638905cd61..b3f6fcc9796 100644 --- a/ports/mimxrt/systick.h +++ b/ports/mimxrt/systick.h @@ -54,7 +54,4 @@ static inline void systick_disable_dispatch(size_t slot) { systick_dispatch_table[slot] = NULL; } -void systick_wait_at_least(uint32_t stc, uint32_t delay_ms); -bool systick_has_passed(uint32_t stc, uint32_t delay_ms); - #endif // MICROPY_INCLUDED_MIMXRT_SYSTICK_H From 829d770e3f4cf65d3a75ab71df7ae8bf18b745b4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Apr 2026 23:58:02 +1000 Subject: [PATCH 2006/2098] mimxrt/mphalport: Remove unused uwTick macro definition. The config option `MICROPY_SOFT_TIMER_TICKS_MS` is used instead. Signed-off-by: Damien George --- ports/mimxrt/mphalport.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index 5987081c844..3c8cc98d918 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -80,10 +80,6 @@ extern int mp_interrupt_char; extern ringbuf_t stdin_ringbuf; -// Define an alias for systick_ms, because the shared softtimer.c uses -// the symbol uwTick for the systick ms counter. -#define uwTick systick_ms - #define mp_hal_pin_obj_t const machine_pin_obj_t * #define mp_hal_get_pin_obj(o) pin_find(o) #define mp_hal_pin_name(p) ((p)->name) From eaa7ca6d6c4ab907b6a67a776c1875f61403a867 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 17 Apr 2026 00:00:02 +1000 Subject: [PATCH 2007/2098] mimxrt/systick: Clean up header file includes. `systick.h` and `pendsv.h` both use MICROPY-level configuration macros, so must include `py/mpconfig.h`. Signed-off-by: Damien George --- ports/mimxrt/pendsv.h | 2 ++ ports/mimxrt/systick.c | 3 --- ports/mimxrt/systick.h | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/mimxrt/pendsv.h b/ports/mimxrt/pendsv.h index d68c5aa2d5d..9dfa450efe5 100644 --- a/ports/mimxrt/pendsv.h +++ b/ports/mimxrt/pendsv.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_MIMXRT_PENDSV_H #define MICROPY_INCLUDED_MIMXRT_PENDSV_H +#include "py/mpconfig.h" + enum { PENDSV_DISPATCH_SOFT_TIMER, // For later & for having at least one entry #if MICROPY_PY_NETWORK && MICROPY_PY_LWIP diff --git a/ports/mimxrt/systick.c b/ports/mimxrt/systick.c index fe75397e38e..71c16a62dde 100644 --- a/ports/mimxrt/systick.c +++ b/ports/mimxrt/systick.c @@ -24,10 +24,7 @@ * THE SOFTWARE. */ -#include "py/runtime.h" -#include "py/mphal.h" #include "systick.h" - #include "pendsv.h" #include "shared/runtime/softtimer.h" diff --git a/ports/mimxrt/systick.h b/ports/mimxrt/systick.h index b3f6fcc9796..a7ff99f883d 100644 --- a/ports/mimxrt/systick.h +++ b/ports/mimxrt/systick.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_MIMXRT_SYSTICK_H #define MICROPY_INCLUDED_MIMXRT_SYSTICK_H +#include "py/mpconfig.h" + // Works for x between 0 and 16 inclusive #define POW2_CEIL(x) ((((x) - 1) | ((x) - 1) >> 1 | ((x) - 1) >> 2 | ((x) - 1) >> 3) + 1) From 7f7adad6ca62a02b889f4ee2860e17b1ddc674f4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Apr 2026 16:38:24 +1000 Subject: [PATCH 2008/2098] mimxrt/mphalport: Run events at least once in mp_hal_delay_ms. Per the docs for `time.sleep()` and `time.sleep_ms()`. This gets the `tests/micropython/schedule_sleep.py` test working when using the native emitter. Signed-off-by: Damien George --- ports/mimxrt/mphalport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index 3c8cc98d918..e14a7d92de7 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -123,6 +123,9 @@ static inline mp_uint_t mp_hal_ticks_us(void) { } static inline void mp_hal_delay_ms(mp_uint_t ms) { + // This function must run events at least once, so do that now. + mp_event_handle_nowait(); + uint64_t us = (uint64_t)ms * 1000; ticks_delay_us64(us); } From 586ad0dd706f9768f0fcc76006804f9f5132f8c8 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 7 May 2025 13:31:32 +1000 Subject: [PATCH 2009/2098] docs/library/io: Add documentation for the io.IOBase class. Document the three methods that IOBase subclasses implement (readinto, write, ioctl) and the common ioctl operations. Includes a simple write-only stream example and a pollable ring buffer example. IOBase docs are inline in io.rst before StringIO/BytesIO, following CPython's structure. Also fix a statement in io.rst that incorrectly claimed streams cannot be subclassed in pure Python. Signed-off-by: Andrew Leech --- docs/library/io.rst | 142 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/docs/library/io.rst b/docs/library/io.rst index caa59895227..257896dfa2a 100644 --- a/docs/library/io.rst +++ b/docs/library/io.rst @@ -71,8 +71,9 @@ buffered, they aren't in MicroPython. (Indeed, that's one of the cases for which we may introduce buffering support.) Note that for efficiency, MicroPython doesn't provide abstract base -classes corresponding to the hierarchy above, and it's not possible -to implement, or subclass, a stream class in pure Python. +classes corresponding to the hierarchy above. However, the +:class:`IOBase` class can be subclassed to implement custom stream +objects in pure Python. Functions --------- @@ -86,6 +87,69 @@ Functions Classes ------- +.. class:: IOBase() + + Base class for implementing custom stream objects in Python. Subclasses + can override ``readinto``, ``write``, and ``ioctl`` to create objects + that work with ``print()``, ``json.dump()``, ``select.poll()``, + ``open()`` via a user filesystem, and other stream consumers. + + .. admonition:: Difference to CPython + :class: attention + + In CPython, ``io.IOBase`` has a much larger API surface. MicroPython's + ``IOBase`` is minimal: the C stream infrastructure provides standard + methods like ``read()``, ``readline()``, ``seek()``, ``close()``, and + ``flush()`` automatically. Subclasses only need to implement the + methods below. + + Subclasses implement some or all of the following methods. The C stream + layer calls these internally when user code calls standard stream + functions. + + .. method:: IOBase.readinto(buf) + + Read data into *buf* (a ``bytearray`` sized by the caller). Return the + number of bytes read, 0 at EOF, or ``None`` if no data is available on + a non-blocking stream. Return a negative errno value (e.g. ``-errno.EIO`` + or ``-1``) to signal an error. + + .. method:: IOBase.write(buf) + + Write *buf* (a ``bytearray``) to the stream. Return the number of + bytes written, or ``None`` if a non-blocking stream cannot accept data. + Return a negative errno value to signal an error. + + .. method:: IOBase.ioctl(op, arg) + + Control the stream and query its properties. The operation to perform + is given by *op* which is one of the following integers: + + - 1 -- flush write buffers (*arg* is unused) + - 3 -- poll for readiness; *arg* is a bitmask of events to check, + return a bitmask of ready events. Poll flags: + + * ``0x0001`` -- data available for reading + * ``0x0004`` -- stream ready for writing + * ``0x0008`` -- error condition + * ``0x0010`` -- hang up (e.g. connection closed) + * ``0x0020`` -- invalid request + + - 4 -- close the stream (*arg* is unused) + - 11 -- return the preferred read buffer size, or 0 (*arg* is unused) + + As a minimum ``ioctl(4, ...)`` should be handled to support stream + closure. Implement ``ioctl(3, ...)`` if the stream will be used with + ``select.poll()`` or ``asyncio``. + + Other operations exist for advanced use cases (2 = seek, 5 = timeout, + 10 = fileno); see ``py/stream.h`` for the full list. + + Must always return an integer. Return 0 for success, or ``-1`` for + unsupported operations. (Returning 0 for an unhandled operation tells + the C layer the operation was processed successfully, which may cause + incorrect behaviour.) + .. class:: StringIO([string]) .. class:: BytesIO([string]) @@ -119,3 +183,77 @@ Classes :class: attention These constructors are a MicroPython extension. + +IOBase Examples +--------------- + +A minimal write-only stream that collects output into a buffer:: + + import io + + class MyOutput(io.IOBase): + def __init__(self): + self.data = bytearray() + + def write(self, buf): + self.data.extend(buf) + return len(buf) + + def ioctl(self, op, arg): + if op == 4: # close + return 0 + return -1 + + s = MyOutput() + print("hello", file=s) + print(s.data) # bytearray(b'hello\n') + +A readable stream that can be used with ``select.poll()``:: + + import io, select + from micropython import const + + _MP_STREAM_POLL = const(3) + _MP_STREAM_POLL_RD = const(0x0001) + _MP_STREAM_CLOSE = const(4) + + class RingBuffer(io.IOBase): + def __init__(self, size): + self._buf = bytearray(size) + self._size = size + self._wpos = 0 + self._rpos = 0 + + def _available(self): + return (self._wpos - self._rpos) % self._size + + def put(self, data): + for b in data: + self._buf[self._wpos % self._size] = b + self._wpos = (self._wpos + 1) % self._size + + def readinto(self, buf): + n = min(len(buf), self._available()) + for i in range(n): + buf[i] = self._buf[self._rpos % self._size] + self._rpos = (self._rpos + 1) % self._size + return n + + def ioctl(self, op, arg): + if op == _MP_STREAM_POLL: + if arg & _MP_STREAM_POLL_RD and self._available() > 0: + return _MP_STREAM_POLL_RD + return 0 + if op == _MP_STREAM_CLOSE: + return 0 + return -1 + + rb = RingBuffer(64) + rb.put(b"test data") + + poller = select.poll() + poller.register(rb, select.POLLIN) + for obj, flags in poller.poll(0): + buf = bytearray(16) + n = obj.readinto(buf) + print(buf[:n]) # bytearray(b'test data') From 9f396bba8d675ffb53f7fb047def21c7a581948e Mon Sep 17 00:00:00 2001 From: Yanfeng Liu Date: Fri, 1 Aug 2025 16:54:26 +0800 Subject: [PATCH 2010/2098] docs/develop/porting: Update session log for example port. This updates session log to be in line with behavior of latest ports/minimal. Signed-off-by: Yanfeng Liu --- docs/develop/porting.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/develop/porting.rst b/docs/develop/porting.rst index 5dff843a3a9..28d7b3dd513 100644 --- a/docs/develop/porting.rst +++ b/docs/develop/porting.rst @@ -241,10 +241,12 @@ That should give a MicroPython REPL. You can then run commands like: .. code-block:: bash - MicroPython v1.13 on 2021-01-01; example-board with unknown-cpu - >>> import sys - >>> sys.implementation - ('micropython', (1, 13, 0)) + MicroPython v1.26.0-preview on 2025-08-01; minimal with unknown-cpu + >>> def sum(n, m): + ... return n + m + ... + >>> 3, 4, sum(3, 4) + (3, 4, 7) >>> Use Ctrl-D to exit, and then run ``reset`` to reset the terminal. From 7466e2293c725d3a1e70bd410278865e896e8ca8 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:24:03 +0200 Subject: [PATCH 2011/2098] samd/dma_manager: Add a DMA manager. Used for allocation of DMA channels. It will be needed for planned modules and methods like adc_timed(), dac_timed(), I2S. It includes management code for DMA IRQ handlers, similar to what was made for Sercom. Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 131 +++++++++++++++++++++++++++++++++++++++ ports/samd/dma_manager.h | 39 ++++++++++++ ports/samd/samd_isr.c | 57 +++++++++++++++-- ports/samd/samd_soc.h | 1 + 4 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 ports/samd/dma_manager.c create mode 100644 ports/samd/dma_manager.h diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c new file mode 100644 index 00000000000..868606e66b7 --- /dev/null +++ b/ports/samd/dma_manager.c @@ -0,0 +1,131 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Robert Hammelrath + * + * 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 "sam.h" +#include "dma_manager.h" +#include "samd_soc.h" + +// Set a number of dma channels managed here. samd21 has 21 dma channels, samd51 +// has 32 channels, as defined by the lib macro DMAC_CH_NUM. +// At first, we use a smaller number here to save RAM. May be increased as needed. + +#ifndef MICROPY_HW_DMA_CHANNELS +#if defined(MCU_SAMD21) +#define MICROPY_HW_DMA_CHANNELS 2 +#elif defined(MCU_SAMD51) +#define MICROPY_HW_DMA_CHANNELS 4 +#endif +#endif + +#if MICROPY_HW_DMA_CHANNELS > DMAC_CH_NUM +#error Number of DMA channels too large +#endif + +volatile DmacDescriptor dma_desc[MICROPY_HW_DMA_CHANNELS] __attribute__ ((aligned(16))); +static volatile DmacDescriptor dma_write_back[MICROPY_HW_DMA_CHANNELS] __attribute__ ((aligned(16))); + +// List of channel flags: true: channel used, false: channel available +static bool channel_list[MICROPY_HW_DMA_CHANNELS]; + +static bool dma_initialized = false; + +// allocate_channel(): retrieve an available channel. Return the number or -1 +int allocate_dma_channel(void) { + for (int i = 0; i < MP_ARRAY_SIZE(channel_list); i++) { + if (channel_list[i] == false) { // Channel available + channel_list[i] = true; + return i; + } + } + mp_raise_ValueError(MP_ERROR_TEXT("no dma channel available")); +} + +// free_channel(n): Declare channel as free +void free_dma_channel(int n) { + if (n >= 0 && n < MP_ARRAY_SIZE(channel_list)) { + channel_list[n] = false; + } +} + +void dma_init(void) { + if (!dma_initialized) { + + // Enable the DMA clock + #if defined(MCU_SAMD21) + PM->AHBMASK.reg |= PM_AHBMASK_DMAC; + PM->APBBMASK.reg |= PM_APBBMASK_DMAC; + #elif defined(MCU_SAMD51) + MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC; + #endif + // Setup the initial DMA configuration + DMAC->CTRL.reg = DMAC_CTRL_SWRST; + while (DMAC->CTRL.reg & DMAC_CTRL_SWRST) { + } + // Set the DMA descriptor pointers + DMAC->BASEADDR.reg = (uint32_t)dma_desc; + DMAC->WRBADDR.reg = (uint32_t)dma_write_back; + // Enable the DMA + DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); + + dma_initialized = true; + } +} + +void dma_deinit(void) { + memset((uint8_t *)dma_desc, 0, sizeof(dma_desc)); + memset((uint8_t *)dma_write_back, 0, sizeof(dma_write_back)); + memset((uint8_t *)channel_list, 0, sizeof(channel_list)); + dma_initialized = false; + // Disable DMA + DMAC->CTRL.reg = 0; + for (int ch = 0; ch < DMAC_CH_NUM; ch++) { + dma_register_irq(ch, NULL); + } +} + +void dac_stop_dma(int dma_channel, bool wait) { + #if defined(MCU_SAMD21) + NVIC_DisableIRQ(DMAC_IRQn); + DMAC->CHID.reg = dma_channel; + DMAC->CHINTENCLR.reg = DMAC_CHINTENSET_TCMPL | DMAC_CHINTENSET_TERR | DMAC_CHINTENSET_SUSP; + DMAC->CHCTRLA.reg = 0; + while (wait && DMAC->CHCTRLA.bit.ENABLE) { + } + #elif defined(MCU_SAMD51) + if (0 <= dma_channel && dma_channel < 4) { + NVIC_DisableIRQ(DMAC_0_IRQn + dma_channel); + } else if (dma_channel >= 4) { + NVIC_DisableIRQ(DMAC_4_IRQn); + } + DMAC->Channel[dma_channel].CHINTENCLR.reg = + DMAC_CHINTENSET_TCMPL | DMAC_CHINTENSET_TERR | DMAC_CHINTENSET_SUSP; + DMAC->Channel[dma_channel].CHCTRLA.reg = 0; + while (wait && DMAC->Channel[dma_channel].CHCTRLA.bit.ENABLE) { + } + #endif +} diff --git a/ports/samd/dma_manager.h b/ports/samd/dma_manager.h new file mode 100644 index 00000000000..9fd511e3ee2 --- /dev/null +++ b/ports/samd/dma_manager.h @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Robert Hammelrath + * + * 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_SAMD_DMACHANNEL_H +#define MICROPY_INCLUDED_SAMD_DMACHANNEL_H + +#include "py/runtime.h" + +extern volatile DmacDescriptor dma_desc[]; + +int allocate_dma_channel(void); +void free_dma_channel(int n); +void dma_init(void); +void dma_deinit(void); +void dac_stop_dma(int dma_channel, bool wait); + +#endif // MICROPY_INCLUDED_SAMD_DMACHANNEL_H diff --git a/ports/samd/samd_isr.c b/ports/samd/samd_isr.c index 7c4c1d060f7..6888d8a1dc6 100644 --- a/ports/samd/samd_isr.c +++ b/ports/samd/samd_isr.c @@ -164,6 +164,51 @@ void Sercom7_Handler(void) { } #endif +// DMAC IRQ handler support +#if defined(MCU_SAMD21) +#define DMAC_FIRST_CHANNEL 0 +#else +#define DMAC_FIRST_CHANNEL 4 +#endif + +void (*dma_irq_handler_table[DMAC_CH_NUM])(int num) = {}; + +void dma_register_irq(int dma_channel, void (*dma_irq_handler)) { + if (dma_channel < DMAC_CH_NUM) { + dma_irq_handler_table[dma_channel] = dma_irq_handler; + } +} + +void DMAC0_Handler(void) { + if (dma_irq_handler_table[0]) { + dma_irq_handler_table[0](0); + } +} +void DMAC1_Handler(void) { + if (dma_irq_handler_table[1]) { + dma_irq_handler_table[1](1); + } +} +void DMAC2_Handler(void) { + if (dma_irq_handler_table[2]) { + dma_irq_handler_table[2](2); + } +} +void DMAC3_Handler(void) { + if (dma_irq_handler_table[3]) { + dma_irq_handler_table[3](3); + } +} +void DMACn_Handler(void) { + for (uint32_t mask = 1 << DMAC_FIRST_CHANNEL, dma_channel = DMAC_FIRST_CHANNEL; + dma_channel < DMAC_CH_NUM; + mask <<= 1, dma_channel += 1) { + if ((DMAC->INTSTATUS.reg & mask) && dma_irq_handler_table[dma_channel]) { + dma_irq_handler_table[dma_channel](dma_channel); + } + } +} + #if defined(MCU_SAMD21) const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { (ISR)&_estack, @@ -188,7 +233,7 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 3 Real-Time Counter (RTC) &EIC_Handler, // 4 External Interrupt Controller (EIC) 0, // 5 Non-Volatile Memory Controller (NVMCTRL) - 0, // 6 Direct Memory Access Controller (DMAC) + &DMACn_Handler, // 6 Direct Memory Access Controller (DMAC) USB_Handler_wrapper,// 7 Universal Serial Bus (USB) 0, // 8 Event System Interface (EVSYS) &Sercom0_Handler, // 9 Serial Communication Interface 0 (SERCOM0) @@ -261,11 +306,11 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 28 Frequency Meter (FREQM) 0, // 29 Non-Volatile Memory Controller (NVMCTRL): NVMCTRL_0 - _7 0, // 30 Non-Volatile Memory Controller (NVMCTRL): NVMCTRL_8 - _10 - 0, // 31 Direct Memory Access Controller (DMAC): DMAC_SUSP_0, DMAC_TCMPL_0, DMAC_TERR_0 - 0, // 32 Direct Memory Access Controller (DMAC): DMAC_SUSP_1, DMAC_TCMPL_1, DMAC_TERR_1 - 0, // 33 Direct Memory Access Controller (DMAC): DMAC_SUSP_2, DMAC_TCMPL_2, DMAC_TERR_2 - 0, // 34 Direct Memory Access Controller (DMAC): DMAC_SUSP_3, DMAC_TCMPL_3, DMAC_TERR_3 - 0, // 35 Direct Memory Access Controller (DMAC): DMAC_SUSP_4 - _31, DMAC_TCMPL_4 _31, DMAC_TERR_4- _31 + &DMAC0_Handler, // 31 Direct Memory Access Controller (DMAC): DMAC_SUSP_0, DMAC_TCMPL_0, DMAC_TERR_0 + &DMAC1_Handler, // 32 Direct Memory Access Controller (DMAC): DMAC_SUSP_1, DMAC_TCMPL_1, DMAC_TERR_1 + &DMAC2_Handler, // 33 Direct Memory Access Controller (DMAC): DMAC_SUSP_2, DMAC_TCMPL_2, DMAC_TERR_2 + &DMAC3_Handler, // 34 Direct Memory Access Controller (DMAC): DMAC_SUSP_3, DMAC_TCMPL_3, DMAC_TERR_3 + &DMACn_Handler, // 35 Direct Memory Access Controller (DMAC): DMAC_SUSP_4 - _31, DMAC_TCMPL_4 _31, DMAC_TERR_4- _31 0, // 36 Event System Interface (EVSYS): EVSYS_EVD_0, EVSYS_OVR_0 0, // 37 Event System Interface (EVSYS): EVSYS_EVD_1, EVSYS_OVR_1 0, // 38 Event System Interface (EVSYS): EVSYS_EVD_2, EVSYS_OVR_2 diff --git a/ports/samd/samd_soc.h b/ports/samd/samd_soc.h index 707d2f8edfa..2477775735c 100644 --- a/ports/samd/samd_soc.h +++ b/ports/samd/samd_soc.h @@ -43,6 +43,7 @@ void USB_Handler_wrapper(void); void sercom_enable(Sercom *spi, int state); void sercom_register_irq(int sercom_id, void (*sercom_irq_handler)); +void dma_register_irq(int dma_channel, void (*dma_irq_handler)); // Each device has a unique 128-bit serial number. The uniqueness of the serial number is // guaranteed only when using all 128 bits. From 6853cfbcee5c1335e771f4ef4914306769ef6d47 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:25:28 +0200 Subject: [PATCH 2012/2098] samd/tc_manager: Add a tc_manager function set. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are use to allocate, free and configure a set of TC counter instances. The SAMxx MCU have between 3 to 5 (SAMD21) and 4 to 8 (SAMD51) TC instances. Two of them are used for the µs counter, the remaining 1 - 6 instances are administered here for use by various functions, like timed DMA transfers. Signed-off-by: robert-hh --- ports/samd/tc_manager.c | 178 ++++++++++++++++++++++++++++++++++++++++ ports/samd/tc_manager.h | 38 +++++++++ 2 files changed, 216 insertions(+) create mode 100644 ports/samd/tc_manager.c create mode 100644 ports/samd/tc_manager.h diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c new file mode 100644 index 00000000000..10cad8060ae --- /dev/null +++ b/ports/samd/tc_manager.c @@ -0,0 +1,178 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Robert Hammelrath + * + * 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 "sam.h" +#include "tc_manager.h" + +// List of channel flags: true: channel used, false: channel available +// Two Tc instances are used by the usec counter and cannot be assigned. +#if defined(MCU_SAMD21) +static bool instance_flag[TC_INST_NUM] = {false, true, true}; +#elif defined(MCU_SAMD51) +static bool instance_flag[TC_INST_NUM] = {true, true}; +#endif +Tc *tc_instance_list[TC_INST_NUM] = TC_INSTS; +extern const uint16_t prescaler_table[]; + +// allocate_tc_instance(): retrieve an available instance. Return the pointer or NULL +int allocate_tc_instance(void) { + for (int i = 0; i < MP_ARRAY_SIZE(instance_flag); i++) { + if (instance_flag[i] == false) { // available + instance_flag[i] = true; + return i; + } + } + mp_raise_ValueError(MP_ERROR_TEXT("no Timer available")); +} + +// free_tc_instance(n): Declare instance as free +void free_tc_instance(int tc_index) { + if (tc_index >= 0 && tc_index < MP_ARRAY_SIZE(instance_flag)) { + instance_flag[tc_index] = false; + } +} + +int configure_tc(int tc_index, int freq) { + uint32_t clock = DFLL48M_FREQ; // Use the fixed 48M clock + + Tc *tc; + if (tc_index < MP_ARRAY_SIZE(instance_flag)) { + tc = tc_instance_list[tc_index]; + } else { + return -1; + } + // Check for the right prescaler + uint8_t index; + uint32_t period; + + for (index = 0; index < 8; index++) { + period = clock / prescaler_table[index] / freq; + if (period < (1 << 16)) { + break; + } + } + + #if defined(MCU_SAMD21) + + // Set up the clocks + if (tc == TC3) { + PM->APBCMASK.bit.TC3_ = 1; // Enable TC3 clock + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_TCC2_TC3; + #if TC_INST_NUM > 3 + } else { + if (tc == TC6) { + PM->APBCMASK.bit.TC6_ = 1; // Enable TC6 clock + } else if (tc == TC7) { + PM->APBCMASK.bit.TC7_ = 1; // Enable TC7 clock + } + // Select multiplexer generic clock source and enable. + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_TC6_TC7; + #endif // TC_INST_NUM > 3 + } + // Wait while it updates synchronously. + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Configure the timer. + tc->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (tc->COUNT16.CTRLA.bit.SWRST || tc->COUNT16.STATUS.bit.SYNCBUSY) { + } + tc->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER(index) | + TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | + TC_CTRLA_WAVEGEN_MFRQ; + tc->COUNT16.CC[0].reg = period; + + tc->COUNT16.CTRLA.bit.ENABLE = 1; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) { + } + + #elif defined(MCU_SAMD51) + + int gclk_id = TC2_GCLK_ID; + // Enable MCLK + switch (tc_index) { + case 2: + MCLK->APBBMASK.bit.TC2_ = 1; // Enable TC2 clock + gclk_id = TC2_GCLK_ID; + break; + case 3: + MCLK->APBBMASK.bit.TC3_ = 1; // Enable TC3 clock + gclk_id = TC3_GCLK_ID; + break; + #if TC_INST_NUM > 4 + case 4: + MCLK->APBCMASK.bit.TC4_ = 1; // Enable TC4 clock + gclk_id = TC4_GCLK_ID; + break; + case 5: + MCLK->APBCMASK.bit.TC5_ = 1; // Enable TC5 clock + gclk_id = TC5_GCLK_ID; + break; + #if TC_INST_NUM > 6 + case 6: + MCLK->APBDMASK.bit.TC6_ = 1; // Enable TC6 clock + gclk_id = TC6_GCLK_ID; + break; + case 7: + MCLK->APBDMASK.bit.TC7_ = 1; // Enable TC7 clock + gclk_id = TC7_GCLK_ID; + break; + #endif // TC_INST_NUM > 6 + #endif // TC_INST_NUM > 4 + } + // Enable the 48Mhz clock. + GCLK->PCHCTRL[gclk_id].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; + while (GCLK->PCHCTRL[gclk_id].bit.CHEN == 0) { + } + // Configure the timer. + tc->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (tc->COUNT16.SYNCBUSY.bit.SWRST) { + } + tc->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER(index) | + TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_PRESCSYNC_PRESC; + tc->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; + tc->COUNT16.CC[0].reg = period; + tc->COUNT16.CTRLA.bit.ENABLE = 1; + while (tc->COUNT16.SYNCBUSY.bit.ENABLE) { + } + + #endif // SAMD21 or SAMD51 + + return 0; +} + +void tc_deinit(void) { + memset((uint8_t *)instance_flag, 0, sizeof(instance_flag)); + // The tc instances used by the us counter have to be locked. + // That's TC4 and TC5 for SAMD21 with the list starting at TC3 + // and TC0 and TC1 for SAMD51, with the list starting at TC0 + #if defined(MCU_SAMD21) + instance_flag[1] = instance_flag[2] = true; + #elif defined(MCU_SAMD51) + instance_flag[0] = instance_flag[1] = true; + #endif +} diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h new file mode 100644 index 00000000000..9ab1af19b76 --- /dev/null +++ b/ports/samd/tc_manager.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Robert Hammelrath + * + * 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_SAMD_TCINSTANCE_H +#define MICROPY_INCLUDED_SAMD_TCINSTANCE_H + +#include "py/runtime.h" + +extern Tc *tc_instance_list[]; + +int allocate_tc_instance(void); +void free_tc_instance(int tc_index); +int configure_tc(int tc_index, int freq); +void tc_deinit(void); + +#endif // MICROPY_INCLUDED_SAMD_TCINSTANCE_H From f4b3b5802bfc3901630fb1e6c47bfc51152c45ea Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 5 Sep 2022 11:29:08 +0200 Subject: [PATCH 2013/2098] samd/machine_dac: Add dac.write_timed() method. Used as: dac.write_timed(data, freq [, count]) dac.deinit() Working range for dac_timed(): SAMD21: 1 Hz - 100 kHz (1 MHz clock, 10 bit) SAMD51: 1 Hz - ~500 kHz (8 MHz clock, 12 bit) The buffer has to be a byte array or a halfword array, and the data is sent once. The default for count is 1. If set to a value > 0, the data will be transmitted count times. If set to 0 or < 0, the date will be transmitted until deliberately stopped. The playback can be stopped with dac.deinit(). dac.deinit() just releases the timer and DMA channel needed by dac_timed(). The DAC object itself does not have to be released. Signed-off-by: robert-hh --- ports/samd/Makefile | 2 + ports/samd/machine_dac.c | 254 +++++++++++++++++++++++++++++---------- ports/samd/main.c | 4 + ports/samd/samd_soc.c | 1 - 4 files changed, 199 insertions(+), 62 deletions(-) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 813aed53678..150fc19958d 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -112,6 +112,7 @@ MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH) SRC_C += \ mcu/$(MCU_SERIES_LOWER)/clock_config.c \ + dma_manager.c \ help.c \ machine_bitstream.c \ machine_dac.c \ @@ -130,6 +131,7 @@ SRC_C += \ samd_soc.c \ samd_spiflash.c \ usbd.c \ + tc_manager.c \ SHARED_SRC_C += \ drivers/dht/dht.c \ diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 7dcc654644d..ee49691e715 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -36,32 +36,41 @@ #include "sam.h" #include "pin_af.h" #include "modmachine.h" +#include "samd_soc.h" +#include "dma_manager.h" +#include "tc_manager.h" typedef struct _dac_obj_t { mp_obj_base_t base; uint8_t id; + bool initialized; mp_hal_pin_obj_t gpio_id; uint8_t vref; + int8_t dma_channel; + int8_t tc_index; + uint32_t count; } dac_obj_t; -static dac_obj_t dac_obj[] = { - #if defined(MCU_SAMD21) - {{&machine_dac_type}, 0, PIN_PA02}, - #elif defined(MCU_SAMD51) - {{&machine_dac_type}, 0, PIN_PA02}, - {{&machine_dac_type}, 1, PIN_PA05}, - #endif -}; Dac *const dac_bases[] = DAC_INSTS; +static void dac_init(dac_obj_t *self, Dac *dac); + #if defined(MCU_SAMD21) +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, PIN_PA02}, +}; + #define MAX_DAC_VALUE (1023) #define DEFAULT_DAC_VREF (1) #define MAX_DAC_VREF (2) #elif defined(MCU_SAMD51) +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, PIN_PA02}, + {{&machine_dac_type}, 1, PIN_PA05}, +}; // According to Errata 2.9.2, VDDANA as ref value is not available. However it worked // in tests. So I keep the selection here but set the default to Aref, which is usually // connected at the Board to VDDANA @@ -72,10 +81,36 @@ static uint8_t dac_vref_table[] = { #define MAX_DAC_VALUE (4095) #define DEFAULT_DAC_VREF (2) #define MAX_DAC_VREF (3) -static bool dac_init[2] = {false, false}; -#endif +#endif // defined SAMD21 or SAMD51 +void dac_irq_handler(int dma_channel) { + dac_obj_t *self; + + #if defined(MCU_SAMD21) + DMAC->CHID.reg = dma_channel; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + self = &dac_obj[0]; + if (self->count > 1) { + self->count -= 1; + dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + if (dac_obj[0].dma_channel == dma_channel) { + self = &dac_obj[0]; + } else { + self = &dac_obj[1]; + } + if (self->count > 1) { + self->count -= 1; + dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } + #endif +} static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -92,10 +127,10 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ uint8_t id = args[ARG_id].u_int; dac_obj_t *self = NULL; - if (0 <= id && id < MP_ARRAY_SIZE(dac_obj)) { + if (0 <= id && id <= MP_ARRAY_SIZE(dac_obj)) { self = &dac_obj[id]; } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid id for DAC")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); } uint8_t vref = args[ARG_vref].u_int; @@ -103,71 +138,57 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } - Dac *dac = dac_bases[0]; // Just one DAC register block - - // initialize DAC + Dac *dac = dac_bases[0]; // Just one DAC + dac_init(self, dac); + // Set the port as given in self->gpio_id as DAC + mp_hal_set_pin_mux(self->gpio_id, ALT_FCT_DAC); - #if defined(MCU_SAMD21) + return MP_OBJ_FROM_PTR(self); +} - // Configuration SAMD21 - // Enable APBC clocks and PCHCTRL clocks; GCLK3 at 1 MHz - PM->APBCMASK.reg |= PM_APBCMASK_DAC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_DAC; - while (GCLK->STATUS.bit.SYNCBUSY) { - } - // Reset DAC registers - dac->CTRLA.bit.SWRST = 1; - while (dac->CTRLA.bit.SWRST) { - } - dac->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL(self->vref); - // Enable DAC and wait to be ready - dac->CTRLA.bit.ENABLE = 1; - while (dac->STATUS.bit.SYNCBUSY) { - } +static void dac_init(dac_obj_t *self, Dac *dac) { + // Init DAC + if (self->initialized == false) { + #if defined(MCU_SAMD21) - #elif defined(MCU_SAMD51) + // Configuration SAMD21 + // Enable APBC clocks and PCHCTRL clocks; GCLK3 at 1 MHz + PM->APBCMASK.reg |= PM_APBCMASK_DAC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_DAC; + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Reset DAC registers + dac->CTRLA.bit.SWRST = 1; + while (dac->CTRLA.bit.SWRST) { + } + dac->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL(self->vref); + // Enable DAC and wait to be ready + dac->CTRLA.bit.ENABLE = 1; + while (dac->STATUS.bit.SYNCBUSY) { + } - // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz + #elif defined(MCU_SAMD51) - if (!(dac_init[0] | dac_init[1])) { + // Configuration SAMD51 + // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; - GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | \ - GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN; // Reset DAC registers dac->CTRLA.bit.SWRST = 1; while (dac->CTRLA.bit.SWRST) { } dac->CTRLB.reg = DAC_CTRLB_REFSEL(dac_vref_table[self->vref]); + dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; - } - - // Modify DAC config - requires disabling see Section 47.6.2.3 of data sheet - if (!dac_init[self->id]) { - // Disable DAC and wait - dac->CTRLA.bit.ENABLE = 0; - while (dac->SYNCBUSY.bit.ENABLE) { - } - - // Modify configuration - dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | \ - DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; - dac->DATA[self->id].reg = 0; - dac_init[self->id] = true; - - // Enable DAC and wait + // Enable DAC and wait to be ready dac->CTRLA.bit.ENABLE = 1; while (dac->SYNCBUSY.bit.ENABLE) { } - } - #endif - - // Set the port as given in self->gpio_id as DAC - mp_hal_set_pin_mux(self->gpio_id, ALT_FCT_DAC); - - return MP_OBJ_FROM_PTR(self); + #endif // defined SAMD21 or SAMD51 + } + self->initialized = true; } static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -177,14 +198,17 @@ static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { Dac *dac = dac_bases[0]; // Just one DAC + dac_obj_t *self = self_in; int value = mp_obj_get_int(value_in); + if (value < 0 || value > MAX_DAC_VALUE) { mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); } + // Re-init, if required + dac_init(self, dac); #if defined(MCU_SAMD21) dac->DATA.reg = value; #elif defined(MCU_SAMD51) - dac_obj_t *self = self_in; dac->DATA[self->id].reg = value; #endif @@ -192,8 +216,116 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(dac_write_obj, dac_write); +static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { + Dac *dac = dac_bases[0]; // Just one DAC used + dac_obj_t *self = args[0]; + mp_buffer_info_t src; + // Re-init, if required + dac_init(self, dac); + + mp_get_buffer_raise(args[1], &src, MP_BUFFER_READ); + if (n_args > 3) { + self->count = mp_obj_get_int(args[3]); + } else { + self->count = 1; + } + if (src.len >= 2) { + int freq = mp_obj_get_int(args[2]); + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + dma_register_irq(self->dma_channel, dac_irq_handler); + } + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + // Configure TC; no need to check the return value + configure_tc(self->tc_index, freq); + // Configure DMA for halfword output to the DAC + #if defined(MCU_SAMD21) + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(&dac->DATA.reg); + if (self->count >= 1) { + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + } else { + dma_desc[self->dma_channel].DESCADDR.reg = (uint32_t)(&dma_desc[self->dma_channel].BTCTRL.reg); + } + + DMAC->CHID.reg = self->dma_channel; + DMAC->CHCTRLA.reg = 0; + while (DMAC->CHCTRLA.bit.ENABLE) { + } + DMAC->CHCTRLB.reg = + DMAC_CHCTRLB_LVL(0) | + DMAC_CHCTRLB_TRIGACT_BEAT | + DMAC_CHCTRLB_TRIGSRC(TC3_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->CHINTENSET.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + NVIC_EnableIRQ(DMAC_IRQn); + + #elif defined(MCU_SAMD51) + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(&dac->DATA[self->id].reg); + if (self->count >= 1) { + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + } else { + dma_desc[self->dma_channel].DESCADDR.reg = (uint32_t)(&dma_desc[self->dma_channel].BTCTRL.reg); + } + + DMAC->Channel[self->dma_channel].CHCTRLA.reg = + DMAC_CHCTRLA_BURSTLEN(DMAC_CHCTRLA_BURSTLEN_SINGLE_Val) | + DMAC_CHCTRLA_TRIGACT(DMAC_CHCTRLA_TRIGACT_BURST_Val) | + DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->Channel[self->dma_channel].CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + if (self->dma_channel < 4) { + NVIC_EnableIRQ(DMAC_0_IRQn + self->dma_channel); + } else { + NVIC_EnableIRQ(DMAC_4_IRQn); + } + + #endif // defined SAMD21 or SAMD51 + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); + +static mp_obj_t dac_deinit(mp_obj_t self_in) { + dac_obj_t *self = self_in; + self->initialized = false; + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + if (self->dma_channel >= 0) { + dac_stop_dma(self->dma_channel, true); + free_dma_channel(self->dma_channel); + self->dma_channel = -1; + } + if (self->tc_index >= 0) { + free_tc_instance(self->tc_index); + self->tc_index = -1; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); + static const mp_rom_map_elem_t dac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, }; static MP_DEFINE_CONST_DICT(dac_locals_dict, dac_locals_dict_table); diff --git a/ports/samd/main.c b/ports/samd/main.c index d4e991033c4..9f95f6e2668 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -36,6 +36,8 @@ #include "shared/runtime/softtimer.h" #include "shared/tinyusb/mp_usbd.h" #include "clock_config.h" +#include "dma_manager.h" +#include "tc_manager.h" extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); @@ -88,6 +90,8 @@ void samd_main(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + dma_deinit(); + tc_deinit(); #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 761eb8d1aae..bb7bc076aa9 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -69,7 +69,6 @@ static void usb_init(void) { void init_us_counter(void) { #if defined(MCU_SAMD21) - PM->APBCMASK.bit.TC3_ = 1; // Enable TC3 clock PM->APBCMASK.bit.TC4_ = 1; // Enable TC4 clock // Select multiplexer generic clock source and enable. GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_TC4_TC5; From 8b4dd110aad69a91a1dd852907370254aba181c6 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 6 Sep 2022 16:53:45 +0200 Subject: [PATCH 2014/2098] samd/machine_adc: Add adc.read_timed() method. Used as: adc.read_timed(buffer, freq) Buffer must be preallocated. The size determines the number of 16 bit words to be read. The numeric range of the results is that of the raw ADC. The call returns immediately, and the data transfer is done by DMA. The caller must wait sufficiently long until the data is sampled and can be noticed by a callback. No internal checks are made for a too-high freq value. Read speeds depends on Average and bit length setting: SAMD21: Max. 350kS/s (8 bit, Average 1) SAMD51: Max. 1 MS/s (8 bit, Average 1) Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 147 ++++++++++++++++++++++++++++++++++++--- ports/samd/machine_dac.c | 2 +- ports/samd/tc_manager.c | 5 +- ports/samd/tc_manager.h | 2 +- 4 files changed, 144 insertions(+), 12 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 665af6f6f27..afd9c5d29a5 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -31,6 +31,10 @@ #include "py/mphal.h" #include "sam.h" #include "pin_af.h" +#include "modmachine.h" +#include "samd_soc.h" +#include "dma_manager.h" +#include "tc_manager.h" typedef struct _machine_adc_obj_t { mp_obj_base_t base; @@ -39,6 +43,8 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + int8_t dma_channel; + int8_t tc_index; } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 @@ -85,6 +91,20 @@ static uint8_t resolution[] = { extern mp_int_t log2i(mp_int_t num); +// Active just for SAMD21, stops the freerun mode +// For SAMD51, just the INT flag is reset. +void adc_irq_handler(int dma_channel) { + + #if defined(MCU_SAMD21) + DMAC->CHID.reg = dma_channel; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + ADC->EVCTRL.bit.STARTEI = 0; + + #elif defined(MCU_SAMD51) + DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + #endif +} + static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -116,8 +136,9 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args self->id = id; self->adc_config = adc_config; self->bits = DEFAULT_ADC_BITS; + uint16_t bits = args[ARG_bits].u_int; - if (bits >= 8 && bits <= 12) { + if (8 <= bits && bits <= 12) { self->bits = bits; } uint32_t avg = log2i(args[ARG_average].u_int); @@ -147,6 +168,14 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; // set resolution. Scale 8-16 to 0 - 4 for table access. adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + #if defined(MCU_SAMD21) + // Stop the ADC sampling by timer + adc->EVCTRL.bit.STARTEI = 0; + #elif defined(MCU_SAMD51) + // Do not restart ADC after data has bee read + adc->DSEQCTRL.reg = 0; + #endif // Measure input voltage adc->SWTRIG.bit.START = 1; while (adc->INTFLAG.bit.RESRDY == 0) { @@ -155,9 +184,107 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } +static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { + machine_adc_obj_t *self = self_in; + Adc *adc = adc_bases[self->adc_config.device]; + mp_buffer_info_t src; + mp_get_buffer_raise(values, &src, MP_BUFFER_READ); + if (src.len >= 2) { + int freq = mp_obj_get_int(freq_in); + if (self->dma_channel == -1) { + self->dma_channel = allocate_dma_channel(); + dma_init(); + } + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } + // Set Input channel and resolution + // Select the pin as positive input and gnd as negative input reference, non-diff mode by default + adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; + // set resolution. Scale 8-16 to 0 - 4 for table access. + adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + + // Configure DMA for halfword output to the DAC + #if defined(MCU_SAMD21) + // dma irq just for SAMD21 to stop the timer based acquisition + dma_register_irq(self->dma_channel, adc_irq_handler); + configure_tc(self->tc_index, freq, TC_EVCTRL_OVFEO); + // Enable APBC clock + PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; + // Set up the EVSYS channel + EVSYS->CTRL.bit.SWRST = 1; + EVSYS->USER.reg = EVSYS_USER_CHANNEL(ADC_EVSYS_CHANNEL + 1) | + EVSYS_USER_USER(EVSYS_ID_USER_ADC_START); + EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(ADC_EVSYS_CHANNEL) | + EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_OVF + 3 * self->tc_index) | + EVSYS_CHANNEL_PATH_ASYNCHRONOUS; + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + DMAC->CHID.reg = self->dma_channel; + DMAC->CHCTRLA.reg = 0; + while (DMAC->CHCTRLA.bit.ENABLE) { + } + DMAC->CHCTRLB.reg = + DMAC_CHCTRLB_LVL(0) | + DMAC_CHCTRLB_TRIGACT_BEAT | + DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY); + + DMAC->CHINTENSET.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + NVIC_EnableIRQ(DMAC_IRQn); + adc->EVCTRL.bit.STARTEI = 1; + + #elif defined(MCU_SAMD51) + configure_tc(self->tc_index, freq, 0); + + // Restart ADC after data has bee read + adc->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART; + // Start the first sampling to ensure we get a proper first value. + adc->SWTRIG.bit.START = 1; + while (adc->INTFLAG.bit.RESRDY == 0) { + } + + dma_desc[self->dma_channel].BTCTRL.reg = + DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_STEPSEL | + DMAC_BTCTRL_STEPSIZE(DMAC_BTCTRL_STEPSIZE_X1_Val); + dma_desc[self->dma_channel].BTCNT.reg = src.len / 2; + dma_desc[self->dma_channel].SRCADDR.reg = (uint32_t)(&adc->RESULT.reg); + dma_desc[self->dma_channel].DSTADDR.reg = (uint32_t)(src.buf) + src.len; + dma_desc[self->dma_channel].DESCADDR.reg = 0; // ONE_SHOT + + DMAC->Channel[self->dma_channel].CHCTRLA.reg = + DMAC_CHCTRLA_BURSTLEN(DMAC_CHCTRLA_BURSTLEN_SINGLE_Val) | + DMAC_CHCTRLA_TRIGACT(DMAC_CHCTRLA_TRIGACT_BURST_Val) | + DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + + #endif // defined SAMD21 or SAMD51 + + } + return mp_const_none; +} + // deinit() : release the ADC channel static void mp_machine_adc_deinit(machine_adc_obj_t *self) { busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + if (self->dma_channel >= 0) { + dac_stop_dma(self->dma_channel, true); + free_dma_channel(self->dma_channel); + self->dma_channel = -1; + } + if (self->tc_index >= 0) { + free_tc_instance(self->tc_index); + self->tc_index = -1; + } } void adc_deinit_all(void) { @@ -175,9 +302,9 @@ static void adc_init(machine_adc_obj_t *self) { #if defined(MCU_SAMD21) // Configuration SAMD21 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz PM->APBCMASK.reg |= PM_APBCMASK_ADC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID_ADC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_ADC; while (GCLK->STATUS.bit.SYNCBUSY) { } // Reset ADC registers @@ -190,7 +317,7 @@ static void adc_init(machine_adc_obj_t *self) { linearity |= ((*((uint32_t *)ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; /* Write the calibration data. */ ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc + // Divide a 48MHz clock by 32 to obtain 1.5 MHz clock to adc adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; // Select external AREFA as reference voltage. adc->REFCTRL.reg = adc_vref_table[self->vref]; @@ -203,12 +330,12 @@ static void adc_init(machine_adc_obj_t *self) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK2 at 48 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz if (self->adc_config.device == 0) { - GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC0_ = 1; } else { - GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; MCLK->APBDMASK.bit.ADC1_ = 1; } // Reset ADC registers @@ -230,8 +357,10 @@ static void adc_init(machine_adc_obj_t *self) { } /* Write the calibration data. */ adc->CALIB.reg = ADC_CALIB_BIASCOMP(biascomp) | ADC_CALIB_BIASR2R(biasr2r) | ADC_CALIB_BIASREFBUF(biasrefbuf); - // Divide 48MHz clock by 32 to obtain 1.5 MHz clock to adc - adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV32; + // Divide 48MHz clock by 4 to obtain 12 MHz clock to adc + adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4; + // Enable the offset compensation + adc->SAMPCTRL.reg = ADC_SAMPCTRL_OFFCOMP; // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; // Average: Accumulate samples and scale them down accordingly diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index ee49691e715..f9d0cccd17a 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -240,7 +240,7 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { self->tc_index = allocate_tc_instance(); } // Configure TC; no need to check the return value - configure_tc(self->tc_index, freq); + configure_tc(self->tc_index, freq, 0); // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c index 10cad8060ae..fb0c3b77ff4 100644 --- a/ports/samd/tc_manager.c +++ b/ports/samd/tc_manager.c @@ -57,7 +57,7 @@ void free_tc_instance(int tc_index) { } } -int configure_tc(int tc_index, int freq) { +int configure_tc(int tc_index, int freq, int event) { uint32_t clock = DFLL48M_FREQ; // Use the fixed 48M clock Tc *tc; @@ -105,6 +105,9 @@ int configure_tc(int tc_index, int freq) { TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_WAVEGEN_MFRQ; tc->COUNT16.CC[0].reg = period; + if (event) { + tc->COUNT16.EVCTRL.reg = event; + } tc->COUNT16.CTRLA.bit.ENABLE = 1; while (tc->COUNT16.STATUS.bit.SYNCBUSY) { diff --git a/ports/samd/tc_manager.h b/ports/samd/tc_manager.h index 9ab1af19b76..23957e7be26 100644 --- a/ports/samd/tc_manager.h +++ b/ports/samd/tc_manager.h @@ -32,7 +32,7 @@ extern Tc *tc_instance_list[]; int allocate_tc_instance(void); void free_tc_instance(int tc_index); -int configure_tc(int tc_index, int freq); +int configure_tc(int tc_index, int freq, int event); void tc_deinit(void); #endif // MICROPY_INCLUDED_SAMD_TCINSTANCE_H From ac2f4520d80e9a633add4c4c0c236b15873184b0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 13 Sep 2022 18:50:42 +0200 Subject: [PATCH 2015/2098] samd/machine_dac: Add a callback keyword option to machine.DAC(). The callback is called when a dac_timed() sequence finishes. It will be reset with callback=None or omitting the callback option in the constructor. Side change: Set the clock freq. to 48Mhz. Signed-off-by: robert-hh --- ports/samd/machine_dac.c | 45 +++++++++++++++++++++++++++++++++------- ports/samd/main.c | 4 ++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index f9d0cccd17a..bd61137b293 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -49,16 +49,18 @@ typedef struct _dac_obj_t { int8_t dma_channel; int8_t tc_index; uint32_t count; + mp_obj_t callback; } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; static void dac_init(dac_obj_t *self, Dac *dac); +static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, PIN_PA02}, + {{&machine_dac_type}, 0, 0, PIN_PA02}, }; #define MAX_DAC_VALUE (1023) @@ -68,8 +70,8 @@ static dac_obj_t dac_obj[] = { #elif defined(MCU_SAMD51) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, PIN_PA02}, - {{&machine_dac_type}, 1, PIN_PA05}, + {{&machine_dac_type}, 0, 0, PIN_PA02, 2, -1, -1, 1, NULL}, + {{&machine_dac_type}, 1, 0, PIN_PA05, 2, -1, -1, 1, NULL}, }; // According to Errata 2.9.2, VDDANA as ref value is not available. However it worked // in tests. So I keep the selection here but set the default to Aref, which is usually @@ -95,6 +97,10 @@ void dac_irq_handler(int dma_channel) { self->count -= 1; dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } } #elif defined(MCU_SAMD51) @@ -108,6 +114,10 @@ void dac_irq_handler(int dma_channel) { self->count -= 1; dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + } else { + if (self->callback != MP_OBJ_NULL) { + mp_sched_schedule(self->callback, self); + } } #endif } @@ -115,10 +125,11 @@ void dac_irq_handler(int dma_channel) { static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_vref }; + enum { ARG_id, ARG_vref, ARG_callback }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_DAC_VREF} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, }; // Parse the arguments. @@ -138,6 +149,15 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } + self->callback = args[ARG_callback].u_obj; + if (self->callback == mp_const_none) { + self->callback = MP_OBJ_NULL; + } + + self->dma_channel = -1; + self->tc_index = -1; + self->initialized = false; + Dac *dac = dac_bases[0]; // Just one DAC dac_init(self, dac); // Set the port as given in self->gpio_id as DAC @@ -152,9 +172,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #if defined(MCU_SAMD21) // Configuration SAMD21 - // Enable APBC clocks and PCHCTRL clocks; GCLK3 at 1 MHz + // Enable APBC clocks and PCHCTRL clocks; GCLK5 at 48 MHz PM->APBCMASK.reg |= PM_APBCMASK_DAC; - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | GCLK_CLKCTRL_ID_DAC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_DAC; while (GCLK->STATUS.bit.SYNCBUSY) { } // Reset DAC registers @@ -170,9 +190,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK3 at 8 MHz + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; - GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; // Reset DAC registers dac->CTRLA.bit.SWRST = 1; @@ -318,10 +338,19 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { free_tc_instance(self->tc_index); self->tc_index = -1; } + self->callback = MP_OBJ_NULL; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); +// Clear the DMA channel entry in the DAC object. +void dac_deinit_channel(void) { + dac_obj[0].dma_channel = -1; + #if defined(MCU_SAMD51) + dac_obj[1].dma_channel = -1; + #endif +} + static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, diff --git a/ports/samd/main.c b/ports/samd/main.c index 9f95f6e2668..0620718d6f1 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -41,6 +41,7 @@ extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); +extern void dac_deinit_channel(void); extern void pin_irq_deinit_all(void); extern void pwm_deinit_all(void); extern void sercom_deinit_all(void); @@ -95,6 +96,9 @@ void samd_main(void) { #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif + #if MICROPY_PY_MACHINE_DAC + dac_deinit_channel(); + #endif pin_irq_deinit_all(); #if MICROPY_PY_MACHINE_PWM pwm_deinit_all(); From 0214d9392d6ace49cc9a1fc61d83fa9f2c32d484 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 13 Sep 2022 21:26:54 +0200 Subject: [PATCH 2016/2098] samd/machine_adc: Add a callback keyword option to machine.ADC(). Enabling a callback that will be called when a adc.read_timed_into() run is finished. That's especially useful with slow sampling rates and/or many samples, avoiding to guess the sampling time. Raise an error is adc.read_u16() is called while a read_timed_into() is active. Other ADC changes: - SAMD51: use ADC1 if both ADC1 and ADC0 are available at a Pin. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 120 ++++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 19 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index afd9c5d29a5..1833efc6862 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -28,6 +28,13 @@ // This file is never compiled standalone, it's included directly from // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. +#if MICROPY_PY_MACHINE_ADC + +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" + #include "py/mphal.h" #include "sam.h" #include "pin_af.h" @@ -51,6 +58,7 @@ typedef struct _machine_adc_obj_t { #define DEFAULT_ADC_AVG 16 #if defined(MCU_SAMD21) + static uint8_t adc_vref_table[] = { ADC_REFCTRL_REFSEL_INT1V_Val, ADC_REFCTRL_REFSEL_INTVCC0_Val, ADC_REFCTRL_REFSEL_INTVCC1_Val, ADC_REFCTRL_REFSEL_AREFA_Val, ADC_REFCTRL_REFSEL_AREFB_Val @@ -60,9 +68,19 @@ static uint8_t adc_vref_table[] = { #else #define DEFAULT_ADC_VREF (3) #endif +#define MAX_ADC_VREF (4) #define ADC_EVSYS_CHANNEL 0 +typedef struct _device_mgmt_t { + bool init; + bool busy; + mp_obj_t callback; + mp_obj_t self; +} device_mgmt_t; + +device_mgmt_t device_mgmt[ADC_INST_NUM]; + #elif defined(MCU_SAMD51) static uint8_t adc_vref_table[] = { @@ -75,6 +93,22 @@ static uint8_t adc_vref_table[] = { #else #define DEFAULT_ADC_VREF (3) #endif +#define MAX_ADC_VREF (5) + +typedef struct _device_mgmt_t { + bool init; + #if defined(MCU_SAMD51) + bool busy; + int8_t dma_channel; + mp_obj_t callback; + mp_obj_t self; + #endif +} device_mgmt_t; + +device_mgmt_t device_mgmt[ADC_INST_NUM] = { + { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL}, + { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL} +}; #endif // defined(MCU_SAMD21) @@ -82,12 +116,12 @@ static uint8_t adc_vref_table[] = { #define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS Adc *const adc_bases[] = ADC_INSTS; -uint32_t busy_flags = 0; -bool init_flags[2] = {false, false}; -static void adc_init(machine_adc_obj_t *self); +uint32_t ch_busy_flags = 0; + static uint8_t resolution[] = { ADC_CTRLB_RESSEL_8BIT_Val, ADC_CTRLB_RESSEL_10BIT_Val, ADC_CTRLB_RESSEL_12BIT_Val }; +static void adc_init(machine_adc_obj_t *self); extern mp_int_t log2i(mp_int_t num); @@ -97,11 +131,27 @@ void adc_irq_handler(int dma_channel) { #if defined(MCU_SAMD21) DMAC->CHID.reg = dma_channel; - DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL | DMAC_CHINTFLAG_TERR | DMAC_CHINTFLAG_SUSP; ADC->EVCTRL.bit.STARTEI = 0; + device_mgmt[0].busy = 0; + if (device_mgmt[0].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[0].callback, device_mgmt[0].self); + } #elif defined(MCU_SAMD51) - DMAC->Channel[dma_channel].CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL; + DMAC->Channel[dma_channel].CHINTFLAG.reg = + DMAC_CHINTFLAG_TCMPL | DMAC_CHINTFLAG_TERR | DMAC_CHINTFLAG_SUSP; + if (device_mgmt[0].dma_channel == dma_channel) { + device_mgmt[0].busy = 0; + if (device_mgmt[0].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[0].callback, device_mgmt[0].self); + } + } else if (device_mgmt[1].dma_channel == dma_channel) { + device_mgmt[1].busy = 0; + if (device_mgmt[1].callback != MP_OBJ_NULL) { + mp_sched_schedule(device_mgmt[1].callback, device_mgmt[1].self); + } + } #endif } @@ -114,13 +164,16 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p self->adc_config.channel, self->bits, 1 << self->avg, self->vref); } -static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_bits, ARG_average, ARG_vref }; +static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, + const mp_obj_t *all_args) { + + enum { ARG_id, ARG_bits, ARG_average, ARG_vref, ARG_callback }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_bits, MP_ARG_INT, {.u_int = DEFAULT_ADC_BITS} }, { MP_QSTR_average, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_AVG} }, { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_VREF} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, }; // Parse the arguments. @@ -129,7 +182,7 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args // Unpack and check, whether the pin has ADC capability int id = mp_hal_get_pin_obj(args[ARG_id].u_obj); - adc_config_t adc_config = get_adc_config(id, busy_flags); + adc_config_t adc_config = get_adc_config(id, ch_busy_flags); // Now that we have a valid device and channel, create and populate the ADC instance machine_adc_obj_t *self = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); @@ -145,13 +198,20 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args self->avg = (avg <= 10 ? avg : 10); uint8_t vref = args[ARG_vref].u_int; - if (0 <= vref && vref < sizeof(adc_vref_table)) { + if (0 <= vref && vref <= MAX_ADC_VREF) { self->vref = vref; } + device_mgmt[adc_config.device].callback = args[ARG_callback].u_obj; + if (device_mgmt[adc_config.device].callback == mp_const_none) { + device_mgmt[adc_config.device].callback = MP_OBJ_NULL; + } else { + device_mgmt[adc_config.device].self = self; + } // flag the device/channel as being in use. - busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); - init_flags[self->adc_config.device] = false; + ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); + self->dma_channel = -1; + self->tc_index = -1; adc_init(self); @@ -163,6 +223,10 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { Adc *adc = adc_bases[self->adc_config.device]; // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; + if (device_mgmt[self->adc_config.device].busy != 0) { + mp_raise_OSError(MP_EBUSY); + } + // Set Input channel and resolution // Select the pin as positive input and gnd as negative input reference, non-diff mode by default adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; @@ -194,6 +258,7 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f if (self->dma_channel == -1) { self->dma_channel = allocate_dma_channel(); dma_init(); + dma_register_irq(self->dma_channel, adc_irq_handler); } if (self->tc_index == -1) { self->tc_index = allocate_tc_instance(); @@ -206,8 +271,6 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) - // dma irq just for SAMD21 to stop the timer based acquisition - dma_register_irq(self->dma_channel, adc_irq_handler); configure_tc(self->tc_index, freq, TC_EVCTRL_OVFEO); // Enable APBC clock PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; @@ -241,9 +304,11 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f NVIC_EnableIRQ(DMAC_IRQn); adc->EVCTRL.bit.STARTEI = 1; + device_mgmt[0].busy = 1; #elif defined(MCU_SAMD51) configure_tc(self->tc_index, freq, 0); + device_mgmt[self->adc_config.device].dma_channel = self->dma_channel; // Restart ADC after data has bee read adc->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART; @@ -265,8 +330,16 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f DMAC_CHCTRLA_BURSTLEN(DMAC_CHCTRLA_BURSTLEN_SINGLE_Val) | DMAC_CHCTRLA_TRIGACT(DMAC_CHCTRLA_TRIGACT_BURST_Val) | DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF + 3 * self->tc_index); + DMAC->Channel[self->dma_channel].CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; + if (self->dma_channel < 4) { + NVIC_EnableIRQ(DMAC_0_IRQn + self->dma_channel); + } else { + NVIC_EnableIRQ(DMAC_4_IRQn); + } + device_mgmt[self->adc_config.device].busy = 1; + #endif // defined SAMD21 or SAMD51 } @@ -277,6 +350,11 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f static void mp_machine_adc_deinit(machine_adc_obj_t *self) { busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); if (self->dma_channel >= 0) { + #if defined(MCU_SAMD51) + if (self->dma_channel == device_mgmt[self->adc_config.device].dma_channel) { + device_mgmt[self->adc_config.device].dma_channel = -1; + } + #endif dac_stop_dma(self->dma_channel, true); free_dma_channel(self->dma_channel); self->dma_channel = -1; @@ -288,17 +366,21 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { } void adc_deinit_all(void) { - busy_flags = 0; - init_flags[0] = 0; - init_flags[1] = 0; + ch_busy_flags = 0; + device_mgmt[0].init = 0; + #if defined(MCU_SAMD51) + device_mgmt[0].dma_channel = -1; + device_mgmt[1].init = 0; + device_mgmt[1].dma_channel = -1; + #endif } static void adc_init(machine_adc_obj_t *self) { // ADC & clock init is done only once per ADC - if (init_flags[self->adc_config.device] == false) { + if (device_mgmt[self->adc_config.device].init == false) { Adc *adc = adc_bases[self->adc_config.device]; - init_flags[self->adc_config.device] = true; + device_mgmt[self->adc_config.device].init = true; #if defined(MCU_SAMD21) // Configuration SAMD21 @@ -320,7 +402,7 @@ static void adc_init(machine_adc_obj_t *self) { // Divide a 48MHz clock by 32 to obtain 1.5 MHz clock to adc adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; // Select external AREFA as reference voltage. - adc->REFCTRL.reg = adc_vref_table[self->vref]; + adc->REFCTRL.reg = self->vref; // Average: Accumulate samples and scale them down accordingly adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg); // Enable ADC and wait to be ready From 490d63aa7ebbfc19d5d797415bf62314a6415578 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 14 Oct 2022 12:18:28 +0200 Subject: [PATCH 2017/2098] samd: Add new adc.busy() and dac.busy() methods. These return True, while a timed action is ongoing. Side change: Reorder some code in machine_dac.c and do not reset DAC twice. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 7 +++++ ports/samd/machine_dac.c | 59 ++++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 1833efc6862..10dbb42c808 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -353,6 +353,7 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { #if defined(MCU_SAMD51) if (self->dma_channel == device_mgmt[self->adc_config.device].dma_channel) { device_mgmt[self->adc_config.device].dma_channel = -1; + device_mgmt[self->adc_config.device].busy = 0; } #endif dac_stop_dma(self->dma_channel, true); @@ -365,6 +366,12 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { } } +// busy() : Report, if the ADC device is busy +static mp_int_t machine_adc_busy(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return device_mgmt[self->adc_config.device].busy ? true : false; +} + void adc_deinit_all(void) { ch_busy_flags = 0; device_mgmt[0].init = 0; diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index bd61137b293..d0e51bb122e 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -31,6 +31,8 @@ #include #include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" #include "py/mphal.h" #include "sam.h" @@ -48,6 +50,7 @@ typedef struct _dac_obj_t { uint8_t vref; int8_t dma_channel; int8_t tc_index; + bool busy; uint32_t count; mp_obj_t callback; } dac_obj_t; @@ -59,20 +62,25 @@ static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) -static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02}, -}; - #define MAX_DAC_VALUE (1023) #define DEFAULT_DAC_VREF (1) #define MAX_DAC_VREF (2) +static dac_obj_t dac_obj[] = { + {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, +}; + #elif defined(MCU_SAMD51) +#define MAX_DAC_VALUE (4095) +#define DEFAULT_DAC_VREF (2) +#define MAX_DAC_VREF (3) + static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02, 2, -1, -1, 1, NULL}, - {{&machine_dac_type}, 1, 0, PIN_PA05, 2, -1, -1, 1, NULL}, + {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&machine_dac_type}, 1, 0, PIN_PA05, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, }; + // According to Errata 2.9.2, VDDANA as ref value is not available. However it worked // in tests. So I keep the selection here but set the default to Aref, which is usually // connected at the Board to VDDANA @@ -80,9 +88,6 @@ static uint8_t dac_vref_table[] = { DAC_CTRLB_REFSEL_INTREF_Val, DAC_CTRLB_REFSEL_VDDANA_Val, DAC_CTRLB_REFSEL_VREFPU_Val, DAC_CTRLB_REFSEL_VREFPB_Val }; -#define MAX_DAC_VALUE (4095) -#define DEFAULT_DAC_VREF (2) -#define MAX_DAC_VREF (3) #endif // defined SAMD21 or SAMD51 @@ -98,6 +103,7 @@ void dac_irq_handler(int dma_channel) { dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; } else { + self->busy = false; if (self->callback != MP_OBJ_NULL) { mp_sched_schedule(self->callback, self); } @@ -115,6 +121,7 @@ void dac_irq_handler(int dma_channel) { dma_desc[self->dma_channel].BTCTRL.reg |= DMAC_BTCTRL_VALID; DMAC->Channel[self->dma_channel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; } else { + self->busy = false; if (self->callback != MP_OBJ_NULL) { mp_sched_schedule(self->callback, self); } @@ -138,10 +145,10 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ uint8_t id = args[ARG_id].u_int; dac_obj_t *self = NULL; - if (0 <= id && id <= MP_ARRAY_SIZE(dac_obj)) { + if (0 <= id && id < MP_ARRAY_SIZE(dac_obj)) { self = &dac_obj[id]; } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid DAC ID")); } uint8_t vref = args[ARG_vref].u_int; @@ -194,9 +201,18 @@ static void dac_init(dac_obj_t *self, Dac *dac) { MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; - // Reset DAC registers - dac->CTRLA.bit.SWRST = 1; - while (dac->CTRLA.bit.SWRST) { + // If the DAC is enabled it was already reset + // In that case just disable it. + if (dac->CTRLA.bit.ENABLE) { + // Enable DAC and wait to be ready + dac->CTRLA.bit.ENABLE = 0; + while (dac->SYNCBUSY.bit.ENABLE) { + } + } else { + // Reset DAC registers + dac->CTRLA.bit.SWRST = 1; + while (dac->CTRLA.bit.SWRST) { + } } dac->CTRLB.reg = DAC_CTRLB_REFSEL(dac_vref_table[self->vref]); dac->DACCTRL[self->id].reg = DAC_DACCTRL_ENABLE | DAC_DACCTRL_REFRESH(2) | DAC_DACCTRL_CCTRL_CC12M; @@ -219,6 +235,10 @@ static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { Dac *dac = dac_bases[0]; // Just one DAC dac_obj_t *self = self_in; + if (self->busy != false) { + mp_raise_OSError(MP_EBUSY); + } + int value = mp_obj_get_int(value_in); if (value < 0 || value > MAX_DAC_VALUE) { @@ -261,6 +281,8 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } // Configure TC; no need to check the return value configure_tc(self->tc_index, freq, 0); + self->busy = true; + // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) @@ -339,10 +361,18 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { self->tc_index = -1; } self->callback = MP_OBJ_NULL; + self->busy = false; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); +// busy() : Report, if the DAC device is busy +static mp_obj_t machine_dac_busy(mp_obj_t self_in) { + dac_obj_t *self = MP_OBJ_TO_PTR(self_in); + return self->busy ? mp_const_true : mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_dac_busy_obj, machine_dac_busy); + // Clear the DMA channel entry in the DAC object. void dac_deinit_channel(void) { dac_obj[0].dma_channel = -1; @@ -352,6 +382,7 @@ void dac_deinit_channel(void) { } static const mp_rom_map_elem_t dac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_dac_busy_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, From cc42bf9007ab119e44c74ffcc1adca376bc08dc0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 26 Oct 2022 20:39:44 +0200 Subject: [PATCH 2018/2098] docs/samd: Document the extensions to DAC and ADC. Signed-off-by: robert-hh --- docs/samd/quickref.rst | 154 +++++++++++++++++++++++++++++++++-------- 1 file changed, 124 insertions(+), 30 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 781686d2f60..efab6cd4757 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -261,30 +261,39 @@ an external ADC. ADC Constructor ``````````````` -.. class:: ADC(dest, *, average=16, vref=n) +.. class:: ADC(dest, *, average=16, bits=12, vref=3, callback=None) :noindex: -Construct and return a new ADC object using the following parameters: - - - *dest* is the Pin object on which the ADC is output. - -Keyword arguments: +On the SAMD21/SAMD51 ADC functionality is available on Pins labelled 'Ann'. - - *average* is used to reduce the noise. With a value of 16 the LSB noise is about 1 digit. - - *vref* sets the reference voltage for the ADC. +Use the :ref:`machine.ADC ` class:: - The default setting is for 3.3V. Other values are: + from machine import ADC - ==== ============================== =============================== - vref SAMD21 SAMD51 - ==== ============================== =============================== - 0 1.0V voltage reference internal bandgap reference (1V) - 1 1/1.48 Analogue voltage supply Analogue voltage supply - 2 1/2 Analogue voltage supply 1/2 Analogue voltage supply - 3 External reference A External reference A - 4 External reference B External reference B - 5 - External reference C - ==== ============================== =============================== + adc0 = ADC(Pin("A0")) # create ADC object on ADC pin, average=16 + adc0.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v + adc1 = ADC(Pin("A1"), average=1) # create ADC object on ADC pin, average=1 + +The resolution of the ADC is set by the bits keyword option. The default is 12. +Suitable values are 8, 10 and 12. If you need a higher resolution or better +accuracy, use an external ADC. The default value of average is 16. +Averaging is used to reduce the noise. With a value of 16 the LSB noise is +about 1 digit. The vref=n option sets the reference voltage for the ADC. +The default setting is for 3.3V. Other values are: + +==== ============================== =============================== +vref SAMD21 SAMD51 +==== ============================== =============================== +0 1.0V voltage reference internal bandgap reference (1V) +1 1/1.48 Analogue voltage supply Analogue voltage supply +2 1/2 Analogue voltage supply 1/2 Analogue voltage supply +3 External reference A External reference A +4 External reference B External reference B +5 - External reference C +==== ============================== =============================== + +The callback keyword option is used for timed ADC sampling. The callback is executed +when all data has been sampled. ADC Methods ``````````` @@ -294,27 +303,66 @@ ADC Methods Read a single ADC value as unsigned 16 bit quantity. The voltage range is defined by the vref option of the constructor, the resolutions by the bits option. -DAC (digital to analog conversion) ----------------------------------- +.. method:: read_timed(data, freq) -The DAC class provides a fast digital to analog conversion. Usage example:: +Read ADC values into the data buffer at a supplied frequency. The buffer +must be preallocated. Values are stored as 16 bit quantities in the binary +range given by the bits option. If bits=12, the value range is 0-4095. +The voltage range is defined by the vref option. +The sampling frequency range depends on the bits and average setting. At bits=8 +and average=1, the largest rate is >1 MHz for SAMD51 and 350kHz for SAMD21. +the lowest sampling rate is 1 Hz. The call to the method returns immediately, +The data transfer is done by DMA in the background, controlled by a hardware timer. +If in the constructor a callback was defined, it will be called after all data has been +read. Alternatively, the method busy() can be used to tell, if the capture has finished. - from machine import DAC +Example for a call to adc.read_timed() and a callback:: - dac0 = DAC(0) # create DAC object on DAC pin A0 - dac0.write(1023) # write value, 0-4095 across voltage range 0.0v - 3.3v - dac1 = DAC(1) # create DAC object on DAC pin A1 - dac1.write(2000) # write value, 0-4095 across voltage range 0.0v - 3.3v + from machine import ADC + from array import array -The resolution of the DAC is 12 bit for SAMD51 and 10 bit for SAMD21. SAMD21 devices -have 1 DAC channel at GPIO PA02, SAMD51 devices have 2 DAC channels at GPIO PA02 and PA05. + def finished(adc_o): + print("Sampling finished on ADC", adc_o) + + # create ADC object on ADC pin A0, average=1 + adc = ADC(Pin("A0"), average=1, callback=finished) + buffer = array("H", bytearray(512)) # create an array for 256 ADC values + adc.read_timed(buffer, 10000) # read 256 12 bit values at a frequency of + # 10 kHz and call finished() when done. + +.. method:: busy() + +busy() returns `True` while the data acquisition using read_timed() is ongoing, `False` +otherwise. + +.. method deinit() + +Deinitialize an ADC object and release the resources used by it, especially the ADC +channel and the timer used for read_timed(). + + +DAC (digital to analogue conversion) +------------------------------------ DAC Constructor ``````````````` -.. class:: DAC(id, *, vref=3) +.. class:: DAC(id, *, vref=3, callback=None) :noindex: + +The DAC class provides a fast digital to analogue conversion. Usage example:: + + from machine import DAC + + dac0 = DAC(0) # create DAC object on DAC pin A0 + dac0.write(1023) # write value, 0-4095 across voltage range 0.0V - 3.3V + dac1 = DAC(1) # create DAC object on DAC pin A1 + dac1.write(2000) # write value, 0-4095 across voltage range 0.0V - 3.3V + +The resolution of the DAC is 12 bit for SAMD51 and 10 bit for SAMD21. SAMD21 devices +have 1 DAC channel at GPIO PA02, accepting only 0 as id. SAMD51 devices have +2 DAC channels at GPIO PA02 and PA05 with values 0 and 1 for the id. The vref arguments defines the output voltage range, the callback option is used for dac_timed(). Suitable values for vref are: @@ -327,6 +375,7 @@ vref SAMD21 SAMD51 3 - Buffered external reference ==== ============================ ================================ + DAC Methods ``````````` @@ -335,6 +384,51 @@ DAC Methods Write a single value to the selected DAC output. The value range is 0-1023 for SAMD21 and 0-4095 for SAMD51. The voltage range depends on the vref setting. +.. method:: write_timed(data, freq [, count=1]) + +The call to dac_timed() allows to output a series of analogue values at a given rate. +data must be a buffer with 16 bit values in the range of the DAC (10 bit of 12 bit). +freq may have a range of 1Hz to ~200kHz for SAMD21 and 1 Hz to ~500kHz for SAMD51. +The optional argument count specifies, how often data output will be repeated. The +range is 1 - (2**32 - 1). If count == 0, the data output will be repeated until stopped +by a call to deinit(). If the data has been output count times, a callback will +be called, if given. + +Example:: + + from machine import DAC + from array import array + + data = array("H", [i for i in range(0, 4096, 256)]) # create a step sequence + + def done(dac_o): + print("Sequence done at", dac_o) + + dac = DAC(0, callback=done) + dac.write_timed(data, 1000, 10) # output data 10 times at a rate of 1000 values/s + # and call done() when finished. + +The data transfer is done by DMA and not affected by python code execution. +It is possible to restart dac.write_timed() in the callback function with changed +parameters. + + +.. method:: busy() + :noindex: + +Tell, whether a write_timed() activity is ongoing. It returns `True` if yes, `False` +otherwise. + + +.. method:: deinit() + +Deinitialize the DAC and release the resources used by it, especially the DMA channel +and the Timer. On most SAMD21 boards, there is just one timer available for +dac.write_timed() and adc.read_timed(). So they cannot run both at the same time, +and releasing the timer may be important. The DAC driver consumes a substantial amount +of current. deinit() will reduce that as well. + + Software SPI bus ---------------- From e4a06097a4a233283b4206d4113c83490e3e531c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 24 Mar 2023 11:52:11 +0100 Subject: [PATCH 2019/2098] samd/machine_dac: Rework the DAC deinit() semantics. Since the two channels of a SAMD51 are not completely independent, dac.deinit() now clears both channels, and both channels have to be re-instantiated after a deinit(). Side change: - rearrange some code lines. Signed-off-by: robert-hh --- docs/samd/quickref.rst | 3 +- ports/samd/machine_dac.c | 65 +++++++++++++++++++++++----------------- ports/samd/main.c | 4 +-- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index efab6cd4757..4d6894eac17 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -426,7 +426,8 @@ Deinitialize the DAC and release the resources used by it, especially the DMA ch and the Timer. On most SAMD21 boards, there is just one timer available for dac.write_timed() and adc.read_timed(). So they cannot run both at the same time, and releasing the timer may be important. The DAC driver consumes a substantial amount -of current. deinit() will reduce that as well. +of current. deinit() will reduce that as well. After calling deinit(), the +DAC objects cannot be used any more and must be recreated. Software SPI bus diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index d0e51bb122e..a76aef4a916 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -46,8 +46,8 @@ typedef struct _dac_obj_t { mp_obj_base_t base; uint8_t id; bool initialized; - mp_hal_pin_obj_t gpio_id; uint8_t vref; + mp_hal_pin_obj_t gpio_id; int8_t dma_channel; int8_t tc_index; bool busy; @@ -57,7 +57,7 @@ typedef struct _dac_obj_t { Dac *const dac_bases[] = DAC_INSTS; -static void dac_init(dac_obj_t *self, Dac *dac); +static void dac_init(dac_obj_t *self); static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) @@ -67,7 +67,7 @@ static mp_obj_t dac_deinit(mp_obj_t self_in); #define MAX_DAC_VREF (2) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&machine_dac_type}, 0, 0, DEFAULT_DAC_VREF, PIN_PA02}, }; #elif defined(MCU_SAMD51) @@ -77,8 +77,8 @@ static dac_obj_t dac_obj[] = { #define MAX_DAC_VREF (3) static dac_obj_t dac_obj[] = { - {{&machine_dac_type}, 0, 0, PIN_PA02, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, - {{&machine_dac_type}, 1, 0, PIN_PA05, DEFAULT_DAC_VREF, -1, -1, false, 1, NULL}, + {{&machine_dac_type}, 0, 0, DEFAULT_DAC_VREF, PIN_PA02}, + {{&machine_dac_type}, 1, 0, DEFAULT_DAC_VREF, PIN_PA05}, }; // According to Errata 2.9.2, VDDANA as ref value is not available. However it worked @@ -164,18 +164,20 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->dma_channel = -1; self->tc_index = -1; self->initialized = false; + self->busy = false; - Dac *dac = dac_bases[0]; // Just one DAC - dac_init(self, dac); + dac_init(self); // Set the port as given in self->gpio_id as DAC mp_hal_set_pin_mux(self->gpio_id, ALT_FCT_DAC); return MP_OBJ_FROM_PTR(self); } -static void dac_init(dac_obj_t *self, Dac *dac) { +static void dac_init(dac_obj_t *self) { // Init DAC if (self->initialized == false) { + Dac *dac = dac_bases[0]; // Just one DAC + #if defined(MCU_SAMD21) // Configuration SAMD21 @@ -197,9 +199,6 @@ static void dac_init(dac_obj_t *self, Dac *dac) { #elif defined(MCU_SAMD51) // Configuration SAMD51 - // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz - MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; - GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; // If the DAC is enabled it was already reset // In that case just disable it. @@ -209,6 +208,9 @@ static void dac_init(dac_obj_t *self, Dac *dac) { while (dac->SYNCBUSY.bit.ENABLE) { } } else { + // Enable APBD clocks and PCHCTRL clocks; GCLK5 at 48 MHz + MCLK->APBDMASK.reg |= MCLK_APBDMASK_DAC; + GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK5 | GCLK_PCHCTRL_CHEN; // Reset DAC registers dac->CTRLA.bit.SWRST = 1; while (dac->CTRLA.bit.SWRST) { @@ -235,6 +237,10 @@ static void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { Dac *dac = dac_bases[0]; // Just one DAC dac_obj_t *self = self_in; + + if (self->initialized == false) { + mp_raise_OSError(MP_ENODEV); + } if (self->busy != false) { mp_raise_OSError(MP_EBUSY); } @@ -244,8 +250,6 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { if (value < 0 || value > MAX_DAC_VALUE) { mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); } - // Re-init, if required - dac_init(self, dac); #if defined(MCU_SAMD21) dac->DATA.reg = value; #elif defined(MCU_SAMD51) @@ -260,8 +264,10 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { Dac *dac = dac_bases[0]; // Just one DAC used dac_obj_t *self = args[0]; mp_buffer_info_t src; - // Re-init, if required - dac_init(self, dac); + + if (self->initialized == false) { + mp_raise_OSError(MP_ENODEV); + } mp_get_buffer_raise(args[1], &src, MP_BUFFER_READ); if (n_args > 3) { @@ -346,11 +352,8 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); -static mp_obj_t dac_deinit(mp_obj_t self_in) { - dac_obj_t *self = self_in; +static void dac_deinit_channel(dac_obj_t *self) { self->initialized = false; - // Reset the DAC to lower the current consumption as SAMD21 - dac_bases[0]->CTRLA.bit.SWRST = 1; if (self->dma_channel >= 0) { dac_stop_dma(self->dma_channel, true); free_dma_channel(self->dma_channel); @@ -362,6 +365,20 @@ static mp_obj_t dac_deinit(mp_obj_t self_in) { } self->callback = MP_OBJ_NULL; self->busy = false; +} + +// Reset DAC and clear the DMA channel entries in the DAC objects. +void dac_deinit_all(void) { + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + dac_deinit_channel(&dac_obj[0]); + #if defined(MCU_SAMD51) + dac_deinit_channel(&dac_obj[1]); + #endif +} + +static mp_obj_t dac_deinit(mp_obj_t self_in) { + dac_deinit_all(); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(dac_deinit_obj, dac_deinit); @@ -373,18 +390,10 @@ static mp_obj_t machine_dac_busy(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(machine_dac_busy_obj, machine_dac_busy); -// Clear the DMA channel entry in the DAC object. -void dac_deinit_channel(void) { - dac_obj[0].dma_channel = -1; - #if defined(MCU_SAMD51) - dac_obj[1].dma_channel = -1; - #endif -} - static const mp_rom_map_elem_t dac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_dac_busy_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, }; diff --git a/ports/samd/main.c b/ports/samd/main.c index 0620718d6f1..b0ee334db25 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -41,7 +41,7 @@ extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); -extern void dac_deinit_channel(void); +extern void dac_deinit_all(void); extern void pin_irq_deinit_all(void); extern void pwm_deinit_all(void); extern void sercom_deinit_all(void); @@ -97,7 +97,7 @@ void samd_main(void) { adc_deinit_all(); #endif #if MICROPY_PY_MACHINE_DAC - dac_deinit_channel(); + dac_deinit_all(); #endif pin_irq_deinit_all(); #if MICROPY_PY_MACHINE_PWM From e029a9a0717b1f6be7acd1784db3dc46db408809 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 25 Mar 2023 16:41:21 +0100 Subject: [PATCH 2020/2098] samd: Make adc.read_timed() and dac.write_timed() configurable. Both together require ~1.9k of flash space, including the DMA-manager and the TC-manager. adc.read_timed() uses ~700 bytes, dac.write_timed() ~600 bytes. Signed-off-by: robert-hh --- ports/samd/dma_manager.c | 4 ++++ ports/samd/machine_adc.c | 35 +++++++++++++++++++++++++++++++---- ports/samd/machine_dac.c | 34 +++++++++++++++++++++++++++++++--- ports/samd/main.c | 4 ++++ ports/samd/mpconfigport.h | 11 +++++++++++ ports/samd/tc_manager.c | 4 ++++ 6 files changed, 85 insertions(+), 7 deletions(-) diff --git a/ports/samd/dma_manager.c b/ports/samd/dma_manager.c index 868606e66b7..2ca7f7ba106 100644 --- a/ports/samd/dma_manager.c +++ b/ports/samd/dma_manager.c @@ -30,6 +30,8 @@ #include "dma_manager.h" #include "samd_soc.h" +#if MICROPY_HW_DMA_MANAGER + // Set a number of dma channels managed here. samd21 has 21 dma channels, samd51 // has 32 channels, as defined by the lib macro DMAC_CH_NUM. // At first, we use a smaller number here to save RAM. May be increased as needed. @@ -129,3 +131,5 @@ void dac_stop_dma(int dma_channel, bool wait) { } #endif } + +#endif diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 10dbb42c808..f497623a54a 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -50,8 +50,10 @@ typedef struct _machine_adc_obj_t { uint8_t avg; uint8_t bits; uint8_t vref; + #if MICROPY_PY_MACHINE_ADC_READ_TIMED int8_t dma_channel; int8_t tc_index; + #endif } machine_adc_obj_t; #define DEFAULT_ADC_BITS 12 @@ -74,9 +76,11 @@ static uint8_t adc_vref_table[] = { typedef struct _device_mgmt_t { bool init; + #if MICROPY_PY_MACHINE_ADC_READ_TIMED bool busy; mp_obj_t callback; mp_obj_t self; + #endif } device_mgmt_t; device_mgmt_t device_mgmt[ADC_INST_NUM]; @@ -105,10 +109,7 @@ typedef struct _device_mgmt_t { #endif } device_mgmt_t; -device_mgmt_t device_mgmt[ADC_INST_NUM] = { - { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL}, - { 0, 0, -1, MP_OBJ_NULL, MP_OBJ_NULL} -}; +device_mgmt_t device_mgmt[ADC_INST_NUM]; #endif // defined(MCU_SAMD21) @@ -125,6 +126,8 @@ static void adc_init(machine_adc_obj_t *self); extern mp_int_t log2i(mp_int_t num); +#if MICROPY_PY_MACHINE_ADC_READ_TIMED + // Active just for SAMD21, stops the freerun mode // For SAMD51, just the INT flag is reset. void adc_irq_handler(int dma_channel) { @@ -154,6 +157,7 @@ void adc_irq_handler(int dma_channel) { } #endif } +#endif static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -173,7 +177,9 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ { MP_QSTR_bits, MP_ARG_INT, {.u_int = DEFAULT_ADC_BITS} }, { MP_QSTR_average, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_AVG} }, { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_ADC_VREF} }, + #if MICROPY_PY_MACHINE_ADC_READ_TIMED { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + #endif }; // Parse the arguments. @@ -201,6 +207,11 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ if (0 <= vref && vref <= MAX_ADC_VREF) { self->vref = vref; } + // flag the device/channel as being in use. + ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); + device_mgmt[self->adc_config.device].init = false; + + #if MICROPY_PY_MACHINE_ADC_READ_TIMED device_mgmt[adc_config.device].callback = args[ARG_callback].u_obj; if (device_mgmt[adc_config.device].callback == mp_const_none) { device_mgmt[adc_config.device].callback = MP_OBJ_NULL; @@ -212,6 +223,7 @@ static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_ ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); self->dma_channel = -1; self->tc_index = -1; + #endif adc_init(self); @@ -223,9 +235,12 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { Adc *adc = adc_bases[self->adc_config.device]; // Set the reference voltage. Default: external AREFA. adc->REFCTRL.reg = adc_vref_table[self->vref]; + + #if MICROPY_PY_MACHINE_ADC_READ_TIMED if (device_mgmt[self->adc_config.device].busy != 0) { mp_raise_OSError(MP_EBUSY); } + #endif // Set Input channel and resolution // Select the pin as positive input and gnd as negative input reference, non-diff mode by default @@ -372,6 +387,9 @@ static mp_int_t machine_adc_busy(mp_obj_t self_in) { return device_mgmt[self->adc_config.device].busy ? true : false; } +#endif + +#if MICROPY_PY_MACHINE_ADC_READ_TIMED void adc_deinit_all(void) { ch_busy_flags = 0; device_mgmt[0].init = 0; @@ -381,6 +399,15 @@ void adc_deinit_all(void) { device_mgmt[1].dma_channel = -1; #endif } +#else +void adc_deinit_all(void) { + ch_busy_flags = 0; + device_mgmt[0].init = 0; + #if defined(MCU_SAMD51) + device_mgmt[1].init = 0; + #endif +} +#endif static void adc_init(machine_adc_obj_t *self) { // ADC & clock init is done only once per ADC diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index a76aef4a916..dcb6c020d56 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -48,17 +48,18 @@ typedef struct _dac_obj_t { bool initialized; uint8_t vref; mp_hal_pin_obj_t gpio_id; + #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED int8_t dma_channel; int8_t tc_index; bool busy; uint32_t count; mp_obj_t callback; + #endif } dac_obj_t; Dac *const dac_bases[] = DAC_INSTS; static void dac_init(dac_obj_t *self); -static mp_obj_t dac_deinit(mp_obj_t self_in); #if defined(MCU_SAMD21) @@ -91,6 +92,8 @@ static uint8_t dac_vref_table[] = { #endif // defined SAMD21 or SAMD51 +#if MICROPY_PY_MACHINE_DAC_WRITE_TIMED + void dac_irq_handler(int dma_channel) { dac_obj_t *self; @@ -129,6 +132,8 @@ void dac_irq_handler(int dma_channel) { #endif } +#endif + static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -136,7 +141,9 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_vref, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_DAC_VREF} }, + #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + #endif }; // Parse the arguments. @@ -156,15 +163,17 @@ static mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ self->vref = vref; } + #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED self->callback = args[ARG_callback].u_obj; if (self->callback == mp_const_none) { self->callback = MP_OBJ_NULL; } - self->dma_channel = -1; self->tc_index = -1; - self->initialized = false; self->busy = false; + #endif + + self->initialized = false; dac_init(self); // Set the port as given in self->gpio_id as DAC @@ -241,9 +250,11 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { if (self->initialized == false) { mp_raise_OSError(MP_ENODEV); } + #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED if (self->busy != false) { mp_raise_OSError(MP_EBUSY); } + #endif int value = mp_obj_get_int(value_in); @@ -260,6 +271,8 @@ static mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(dac_write_obj, dac_write); +#if MICROPY_PY_MACHINE_DAC_WRITE_TIMED + static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { Dac *dac = dac_bases[0]; // Just one DAC used dac_obj_t *self = args[0]; @@ -390,11 +403,26 @@ static mp_obj_t machine_dac_busy(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(machine_dac_busy_obj, machine_dac_busy); +#else + +void dac_deinit_all(void) { + // Reset the DAC to lower the current consumption as SAMD21 + dac_bases[0]->CTRLA.bit.SWRST = 1; + dac_obj[0].initialized = false; + #if defined(MCU_SAMD51) + dac_obj[1].initialized = false; + #endif +} + +#endif + static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&dac_write_obj) }, + #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_dac_busy_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(dac_locals_dict, dac_locals_dict_table); diff --git a/ports/samd/main.c b/ports/samd/main.c index b0ee334db25..7ea59a02c2b 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -91,8 +91,12 @@ void samd_main(void) { soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + #if MICROPY_HW_DMA_MANAGER dma_deinit(); + #endif + #if MICROPY_HW_TC_MANAGER tc_deinit(); + #endif #if MICROPY_PY_MACHINE_ADC adc_deinit_all(); #endif diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 74b694ac80a..faa0b6f446f 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -136,6 +136,17 @@ #define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_I2C_TARGET_FINALISER (1) +#ifndef MICROPY_PY_MACHINE_DAC_WRITE_TIMED +#define MICROPY_PY_MACHINE_DAC_WRITE_TIMED (1) +#endif +#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_PY_MACHINE_ADC_READ_TIMED (1) +#endif +#if MICROPY_PY_MACHINE_DAC_WRITE_TIMED || MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_HW_DMA_MANAGER (1) +#define MICROPY_HW_TC_MANAGER (1) +#endif + #define MP_STATE_PORT MP_STATE_VM // Miscellaneous settings diff --git a/ports/samd/tc_manager.c b/ports/samd/tc_manager.c index fb0c3b77ff4..b6b981087c4 100644 --- a/ports/samd/tc_manager.c +++ b/ports/samd/tc_manager.c @@ -29,6 +29,8 @@ #include "sam.h" #include "tc_manager.h" +#if MICROPY_HW_TC_MANAGER + // List of channel flags: true: channel used, false: channel available // Two Tc instances are used by the usec counter and cannot be assigned. #if defined(MCU_SAMD21) @@ -179,3 +181,5 @@ void tc_deinit(void) { instance_flag[0] = instance_flag[1] = true; #endif } + +#endif From 285b737e4466bbd27ddf78949541278445f51ab1 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 26 Aug 2023 10:43:09 +0200 Subject: [PATCH 2021/2098] samd: Fix init and deinit for adc_timed() and dac_timed(). Fixes: - Leave no half-initialized device if init fails. - Fix dac_deinit_channel(). Perform deinit only for channels that had been initilized. Signed-off-by: robert-hh --- ports/samd/machine_adc.c | 5 +++++ ports/samd/machine_dac.c | 31 +++++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index f497623a54a..0674f318fea 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -270,6 +270,9 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f mp_get_buffer_raise(values, &src, MP_BUFFER_READ); if (src.len >= 2) { int freq = mp_obj_get_int(freq_in); + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } if (self->dma_channel == -1) { self->dma_channel = allocate_dma_channel(); dma_init(); @@ -331,6 +334,8 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f adc->SWTRIG.bit.START = 1; while (adc->INTFLAG.bit.RESRDY == 0) { } + // Wait a little bit allowing the ADC to settle. + mp_hal_delay_us(15); dma_desc[self->dma_channel].BTCTRL.reg = DMAC_BTCTRL_VALID | DMAC_BTCTRL_BLOCKACT_NOACT | diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index dcb6c020d56..f19566f61b1 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -290,14 +290,14 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { } if (src.len >= 2) { int freq = mp_obj_get_int(args[2]); + if (self->tc_index == -1) { + self->tc_index = allocate_tc_instance(); + } if (self->dma_channel == -1) { self->dma_channel = allocate_dma_channel(); dma_init(); dma_register_irq(self->dma_channel, dac_irq_handler); } - if (self->tc_index == -1) { - self->tc_index = allocate_tc_instance(); - } // Configure TC; no need to check the return value configure_tc(self->tc_index, freq, 0); self->busy = true; @@ -366,18 +366,21 @@ static mp_obj_t dac_write_timed(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dac_write_timed_obj, 3, 4, dac_write_timed); static void dac_deinit_channel(dac_obj_t *self) { - self->initialized = false; - if (self->dma_channel >= 0) { - dac_stop_dma(self->dma_channel, true); - free_dma_channel(self->dma_channel); - self->dma_channel = -1; - } - if (self->tc_index >= 0) { - free_tc_instance(self->tc_index); - self->tc_index = -1; + if (self->initialized) { + self->initialized = false; + + if (self->dma_channel >= 0) { + dac_stop_dma(self->dma_channel, true); + free_dma_channel(self->dma_channel); + self->dma_channel = -1; + } + if (self->tc_index >= 0) { + free_tc_instance(self->tc_index); + self->tc_index = -1; + } + self->callback = MP_OBJ_NULL; + self->busy = false; } - self->callback = MP_OBJ_NULL; - self->busy = false; } // Reset DAC and clear the DMA channel entries in the DAC objects. From 92201f8e92738397e974803d2930081d8772c9f9 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 24 Oct 2023 10:01:25 +0200 Subject: [PATCH 2022/2098] samd/machine_adc: Factor out machine.adc_timed() to extmod code. After machine.ADC has been moved to extmod/machine_adc.c. Adding adc.read_timed() and adc.busy() to extmod/machine_adc.c with a corresponding flag to enable them. ADC/DAC timed are by default enabled only at all SAMD51 devices and at SAMD21 devices with an external flash for the file system. Add class constants for the reference voltage source. As far as possible the STM32 names are used, except where they should match common board silkscreen labels. Signed-off-by: robert-hh --- docs/samd/quickref.rst | 38 ++++++++++----------- extmod/machine_adc.c | 22 ++++++++++++ ports/samd/machine_adc.c | 52 +++++++++++++++++------------ ports/samd/machine_dac.c | 6 ++++ ports/samd/mcu/samd21/mpconfigmcu.h | 7 ++++ ports/samd/mcu/samd51/mpconfigmcu.h | 6 ++++ ports/samd/mpconfigport.h | 6 ---- 7 files changed, 90 insertions(+), 47 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 4d6894eac17..4998646ef38 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -261,7 +261,7 @@ an external ADC. ADC Constructor ``````````````` -.. class:: ADC(dest, *, average=16, bits=12, vref=3, callback=None) +.. class:: ADC(dest, *, average=16, bits=12, vref=ADC.AREF, callback=None) :noindex: On the SAMD21/SAMD51 ADC functionality is available on Pins labelled 'Ann'. @@ -281,16 +281,16 @@ Averaging is used to reduce the noise. With a value of 16 the LSB noise is about 1 digit. The vref=n option sets the reference voltage for the ADC. The default setting is for 3.3V. Other values are: -==== ============================== =============================== -vref SAMD21 SAMD51 -==== ============================== =============================== -0 1.0V voltage reference internal bandgap reference (1V) -1 1/1.48 Analogue voltage supply Analogue voltage supply -2 1/2 Analogue voltage supply 1/2 Analogue voltage supply -3 External reference A External reference A -4 External reference B External reference B -5 - External reference C -==== ============================== =============================== +========= ===== ============================== ============================= +Symbol Value SAMD21 SAMD51 +========= ===== ============================== ============================= +INT_VREF 0 1.0V voltage reference 1V internal bandgap reference +VDDA 1 1/1.48 Analogue voltage supply Analogue voltage supply +VDDA2 2 1/2 Analogue voltage supply 1/2 Analogue voltage supply +AREF 3 External reference A (PA03) External reference A (PA03) +AREFB 4 External reference B (PA04) External reference B (PA04) +AREFC 5 External reference C (PA06) +========= ===== ============================== ============================= The callback keyword option is used for timed ADC sampling. The callback is executed when all data has been sampled. @@ -366,14 +366,14 @@ have 1 DAC channel at GPIO PA02, accepting only 0 as id. SAMD51 devices have The vref arguments defines the output voltage range, the callback option is used for dac_timed(). Suitable values for vref are: -==== ============================ ================================ -vref SAMD21 SAMD51 -==== ============================ ================================ -0 Internal voltage reference Internal bandgap reference (~1V) -1 Analogue voltage supply Analogue voltage supply -2 External reference Unbuffered external reference -3 - Buffered external reference -==== ============================ ================================ +========= ===== ============================ ================================ +Symbol Value SAMD21 SAMD51 +========= ===== ============================ ================================ +INT_VREF 0 Internal voltage reference Internal bandgap reference (~1V) +VDDA 1 Analogue voltage supply Analogue voltage supply +AREF 2 External reference Unbuffered external reference +AREFB 3 Buffered external reference +========= ===== ============================ ================================ DAC Methods diff --git a/extmod/machine_adc.c b/extmod/machine_adc.c index 11f1123dc79..47bc7e10dc5 100644 --- a/extmod/machine_adc.c +++ b/extmod/machine_adc.c @@ -140,6 +140,24 @@ static mp_obj_t machine_adc_read(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_obj, machine_adc_read); #endif +#if MICROPY_PY_MACHINE_ADC_READ_TIMED +// ADC.read_timed(buf, freq) +static mp_obj_t machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t freq = mp_obj_get_int(freq_in); + mp_machine_adc_read_timed(self, values, freq); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(machine_adc_read_timed_obj, machine_adc_read_timed); + +// ADC.busy() +static mp_obj_t machine_adc_busy(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_machine_adc_busy(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_busy_obj, machine_adc_busy); +#endif + static const mp_rom_map_elem_t machine_adc_locals_dict_table[] = { #if MICROPY_PY_MACHINE_ADC_INIT { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_adc_init_obj) }, @@ -164,6 +182,10 @@ static const mp_rom_map_elem_t machine_adc_locals_dict_table[] = { #if MICROPY_PY_MACHINE_ADC_READ { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&machine_adc_read_obj) }, #endif + #if MICROPY_PY_MACHINE_ADC_READ_TIMED + { MP_ROM_QSTR(MP_QSTR_read_timed), MP_ROM_PTR(&machine_adc_read_timed_obj) }, + { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_adc_busy_obj) }, + #endif // A port must add ADC class constants defining the following macro. // It can be defined to nothing if there are no constants. diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 0674f318fea..815c36acaa6 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -28,17 +28,13 @@ // This file is never compiled standalone, it's included directly from // extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. -#if MICROPY_PY_MACHINE_ADC - #include #include "py/obj.h" -#include "py/runtime.h" #include "py/mperrno.h" -#include "py/mphal.h" +#include "mphalport.h" #include "sam.h" #include "pin_af.h" -#include "modmachine.h" #include "samd_soc.h" #include "dma_manager.h" #include "tc_manager.h" @@ -113,8 +109,21 @@ device_mgmt_t device_mgmt[ADC_INST_NUM]; #endif // defined(MCU_SAMD21) -// The ADC class doesn't have any constants for this port. -#define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS +#if defined(MCU_SAMD51) +#define MICROPY_PY_MACHINE_SAMD51_ADC_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_AREFC), MP_ROM_INT(5) }, +#else +#define MICROPY_PY_MACHINE_SAMD51_ADC_CLASS_CONSTANTS +#endif + +// Class constants for the ADC reference sources.. +#define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_INT_VREF), MP_ROM_INT(0) }, \ + { MP_ROM_QSTR(MP_QSTR_VDDA), MP_ROM_INT(1) }, \ + { MP_ROM_QSTR(MP_QSTR_VDDA2), MP_ROM_INT(2) }, \ + { MP_ROM_QSTR(MP_QSTR_AREF), MP_ROM_INT(3) }, \ + { MP_ROM_QSTR(MP_QSTR_AREFB), MP_ROM_INT(4) }, \ + MICROPY_PY_MACHINE_SAMD51_ADC_CLASS_CONSTANTS \ Adc *const adc_bases[] = ADC_INSTS; uint32_t ch_busy_flags = 0; @@ -168,8 +177,7 @@ static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_p self->adc_config.channel, self->bits, 1 << self->avg, self->vref); } -static mp_obj_t adc_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, - const mp_obj_t *all_args) { +static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_bits, ARG_average, ARG_vref, ARG_callback }; static const mp_arg_t allowed_args[] = { @@ -263,13 +271,13 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { return adc->RESULT.reg * (65536 / (1 << self->bits)); } -static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t freq_in) { - machine_adc_obj_t *self = self_in; +#if MICROPY_PY_MACHINE_ADC_READ_TIMED + +static void mp_machine_adc_read_timed(machine_adc_obj_t *self, mp_obj_t values, mp_int_t freq) { Adc *adc = adc_bases[self->adc_config.device]; mp_buffer_info_t src; mp_get_buffer_raise(values, &src, MP_BUFFER_READ); if (src.len >= 2) { - int freq = mp_obj_get_int(freq_in); if (self->tc_index == -1) { self->tc_index = allocate_tc_instance(); } @@ -363,12 +371,19 @@ static void machine_adc_read_timed(mp_obj_t self_in, mp_obj_t values, mp_obj_t f #endif // defined SAMD21 or SAMD51 } - return mp_const_none; } +// busy() : Report, if the ADC device is busy +static mp_obj_t mp_machine_adc_busy(machine_adc_obj_t *self) { + return device_mgmt[self->adc_config.device].busy ? mp_const_true : mp_const_false; +} + +#endif + // deinit() : release the ADC channel static void mp_machine_adc_deinit(machine_adc_obj_t *self) { - busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + ch_busy_flags &= ~((1 << (self->adc_config.device * 16 + self->adc_config.channel))); + #if MICROPY_PY_MACHINE_ADC_READ_TIMED if (self->dma_channel >= 0) { #if defined(MCU_SAMD51) if (self->dma_channel == device_mgmt[self->adc_config.device].dma_channel) { @@ -384,16 +399,9 @@ static void mp_machine_adc_deinit(machine_adc_obj_t *self) { free_tc_instance(self->tc_index); self->tc_index = -1; } + #endif } -// busy() : Report, if the ADC device is busy -static mp_int_t machine_adc_busy(mp_obj_t self_in) { - machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); - return device_mgmt[self->adc_config.device].busy ? true : false; -} - -#endif - #if MICROPY_PY_MACHINE_ADC_READ_TIMED void adc_deinit_all(void) { ch_busy_flags = 0; diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index f19566f61b1..2772162f9f9 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -425,6 +425,12 @@ static const mp_rom_map_elem_t dac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_busy), MP_ROM_PTR(&machine_dac_busy_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&dac_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write_timed), MP_ROM_PTR(&dac_write_timed_obj) }, + { MP_ROM_QSTR(MP_QSTR_INT_VREF), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_VDDA), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_AREF), MP_ROM_INT(2) }, + #if defined(MCU_SAMD51) + { MP_ROM_QSTR(MP_QSTR_AREFB), MP_ROM_INT(3) }, + #endif #endif }; diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index a6be13b2420..ff5b2d5e4a3 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -76,6 +76,13 @@ unsigned long trng_random_u32(int delay); #define MICROPY_PY_MACHINE_I2C_TARGET (SAMD21_EXTRA_FEATURES) #endif +#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_PY_MACHINE_ADC_READ_TIMED (SAMD21_EXTRA_FEATURES) +#endif +#ifndef MICROPY_PY_MACHINE_DAC_WRITE_TIMED +#define MICROPY_PY_MACHINE_DAC_WRITE_TIMED (SAMD21_EXTRA_FEATURES) +#endif + #ifndef MICROPY_PY_MACHINE_PIN_BOARD_CPU #define MICROPY_PY_MACHINE_PIN_BOARD_CPU (1) #endif diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 974a40f7aa7..e5709d6cdd6 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -34,6 +34,12 @@ unsigned long trng_random_u32(void); #ifndef MICROPY_HW_UART_RTSCTS #define MICROPY_HW_UART_RTSCTS (1) #endif +#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED +#define MICROPY_PY_MACHINE_ADC_READ_TIMED (1) +#endif +#ifndef MICROPY_PY_MACHINE_DAC_WRITE_TIMED +#define MICROPY_PY_MACHINE_DAC_WRITE_TIMED (1) +#endif #define CPU_FREQ (120000000) #define DFLL48M_FREQ (48000000) diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index faa0b6f446f..fa6b1541224 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -136,12 +136,6 @@ #define MICROPY_PY_MACHINE_I2C_TARGET_HARD_IRQ (1) #define MICROPY_PY_MACHINE_I2C_TARGET_FINALISER (1) -#ifndef MICROPY_PY_MACHINE_DAC_WRITE_TIMED -#define MICROPY_PY_MACHINE_DAC_WRITE_TIMED (1) -#endif -#ifndef MICROPY_PY_MACHINE_ADC_READ_TIMED -#define MICROPY_PY_MACHINE_ADC_READ_TIMED (1) -#endif #if MICROPY_PY_MACHINE_DAC_WRITE_TIMED || MICROPY_PY_MACHINE_ADC_READ_TIMED #define MICROPY_HW_DMA_MANAGER (1) #define MICROPY_HW_TC_MANAGER (1) From 21b3a51aae4603f7cf81843ee55631a2127a9d15 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 17 Apr 2026 09:18:41 +0200 Subject: [PATCH 2023/2098] samd/machine_adc: Fix the configuration with averaging enabled. When averaging is selected, the resolution is fixed to 12 bit. The configuration has to be changed to cater for the result shifts. Side change: Remove a duplicated code line in init(). Signed-off-by: robert-hh --- docs/samd/quickref.rst | 3 ++- ports/samd/machine_adc.c | 40 +++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 4998646ef38..31a91640a5d 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -278,7 +278,8 @@ The resolution of the ADC is set by the bits keyword option. The default is 12. Suitable values are 8, 10 and 12. If you need a higher resolution or better accuracy, use an external ADC. The default value of average is 16. Averaging is used to reduce the noise. With a value of 16 the LSB noise is -about 1 digit. The vref=n option sets the reference voltage for the ADC. +about 1 digit. When averaging is enabled, the resolution is forced to +12 bits. The vref=n option sets the reference voltage for the ADC. The default setting is for 3.3V. Other values are: ========= ===== ============================== ============================= diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 815c36acaa6..848b3502088 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -211,6 +211,11 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args uint32_t avg = log2i(args[ARG_average].u_int); self->avg = (avg <= 10 ? avg : 10); + // Enforce 12 bits with averaging. Maybe raise an exception. + if (self->avg != 0) { + self->bits = 12; + } + uint8_t vref = args[ARG_vref].u_int; if (0 <= vref && vref <= MAX_ADC_VREF) { self->vref = vref; @@ -227,8 +232,6 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args device_mgmt[adc_config.device].self = self; } - // flag the device/channel as being in use. - ch_busy_flags |= (1 << (self->adc_config.device * 16 + self->adc_config.channel)); self->dma_channel = -1; self->tc_index = -1; #endif @@ -241,8 +244,6 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args // read_u16() static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { Adc *adc = adc_bases[self->adc_config.device]; - // Set the reference voltage. Default: external AREFA. - adc->REFCTRL.reg = adc_vref_table[self->vref]; #if MICROPY_PY_MACHINE_ADC_READ_TIMED if (device_mgmt[self->adc_config.device].busy != 0) { @@ -250,11 +251,15 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { } #endif + // Set the reference voltage. Default: external AREFA. + adc->REFCTRL.reg = adc_vref_table[self->vref]; + // Average: Accumulate samples and scale them down accordingly + adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg < 4 ? self->avg : 4); // Set Input channel and resolution // Select the pin as positive input and gnd as negative input reference, non-diff mode by default adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; - // set resolution. Scale 8-16 to 0 - 4 for table access. - adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + // Set the resolution to 16 bit with AVG enabled or to 8-12 bit w/o average. + adc->CTRLB.bit.RESSEL = (self->avg != 0 ? ADC_CTRLB_RESSEL_16BIT_Val : resolution[(self->bits - 8) / 2]); #if defined(MCU_SAMD21) // Stop the ADC sampling by timer @@ -267,8 +272,8 @@ static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { adc->SWTRIG.bit.START = 1; while (adc->INTFLAG.bit.RESRDY == 0) { } - // Get and return the result - return adc->RESULT.reg * (65536 / (1 << self->bits)); + // Get and return the result. When averaging is enabled, the result size is always 12 bit. + return adc->RESULT.reg << (16 - self->bits); } #if MICROPY_PY_MACHINE_ADC_READ_TIMED @@ -286,14 +291,15 @@ static void mp_machine_adc_read_timed(machine_adc_obj_t *self, mp_obj_t values, dma_init(); dma_register_irq(self->dma_channel, adc_irq_handler); } - if (self->tc_index == -1) { - self->tc_index = allocate_tc_instance(); - } + // Set the reference voltage. Default: external AREFA. + adc->REFCTRL.reg = adc_vref_table[self->vref]; + // Average: Accumulate samples and scale them down accordingly + adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg < 4 ? self->avg : 4); // Set Input channel and resolution // Select the pin as positive input and gnd as negative input reference, non-diff mode by default adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | self->adc_config.channel; - // set resolution. Scale 8-16 to 0 - 4 for table access. - adc->CTRLB.bit.RESSEL = resolution[(self->bits - 8) / 2]; + // Set the resolution to 16 bit with AVG enabled or to 8-12 bit w/o average. + adc->CTRLB.bit.RESSEL = (self->avg != 0 ? ADC_CTRLB_RESSEL_16BIT_Val : resolution[(self->bits - 8) / 2]); // Configure DMA for halfword output to the DAC #if defined(MCU_SAMD21) @@ -448,10 +454,6 @@ static void adc_init(machine_adc_obj_t *self) { ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); // Divide a 48MHz clock by 32 to obtain 1.5 MHz clock to adc adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; - // Select external AREFA as reference voltage. - adc->REFCTRL.reg = self->vref; - // Average: Accumulate samples and scale them down accordingly - adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg); // Enable ADC and wait to be ready adc->CTRLA.bit.ENABLE = 1; while (adc->STATUS.bit.SYNCBUSY) { @@ -490,10 +492,6 @@ static void adc_init(machine_adc_obj_t *self) { adc->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4; // Enable the offset compensation adc->SAMPCTRL.reg = ADC_SAMPCTRL_OFFCOMP; - // Set the reference voltage. Default: external AREFA. - adc->REFCTRL.reg = adc_vref_table[self->vref]; - // Average: Accumulate samples and scale them down accordingly - adc->AVGCTRL.reg = self->avg | ADC_AVGCTRL_ADJRES(self->avg); // Enable ADC and wait to be ready adc->CTRLA.bit.ENABLE = 1; while (adc->SYNCBUSY.bit.ENABLE) { From f5af52985cb26e731a8437061c403e904ea5a287 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 26 May 2023 17:53:00 +1000 Subject: [PATCH 2024/2098] py/objstr: Add support for bytes.find(int). This adds support for `bytes.find(x)` where x is an integer value. It also extends to `bytearray` as well as the methods `.rfind()`, `.index()` and `.rindex()`. This allows existing Python code that uses integers like this to "just work" (i.e. CPython compatibility is always good). The Python alternative is a bit awkward, i.e. given a byte value, to use find/index you have to make it into a single-element bytes, e.g. `b.find(chr(n).encode())`. Signed-off-by: Jim Mussared --- py/objstr.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/py/objstr.c b/py/objstr.c index e8b2512a246..06afb91fc7f 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -770,11 +770,29 @@ static mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, b const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); check_is_str_or_bytes(args[0]); - // check argument type - str_check_arg_type(self_type, args[1]); - GET_STR_DATA_LEN(args[0], haystack, haystack_len); - GET_STR_DATA_LEN(args[1], needle, needle_len); + + mp_int_t val; + byte needle_data; + const byte *needle; + size_t needle_len; + if (self_type != &mp_type_str && mp_obj_get_int_maybe(args[1], &val)) { + // Allow {bytes/bytearray}.{find,index}(int). + #if MICROPY_FULL_CHECKS + if (val < 0 || val > 255) { + mp_raise_ValueError(MP_ERROR_TEXT("bytes value out of range")); + } + #endif + needle_data = val; + needle = &needle_data; + needle_len = 1; + } else { + // check argument type + str_check_arg_type(self_type, args[1]); + GET_STR_DATA_LEN(args[1], needle_tmp, needle_len_tmp); + needle = needle_tmp; + needle_len = needle_len_tmp; + } const byte *start = haystack; const byte *end = haystack + haystack_len; From b7e32206b1449f64c4fcd6ba3c76417e515df21c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 26 May 2023 21:54:20 +1000 Subject: [PATCH 2025/2098] tests/basics: Add tests for bytes/bytearray.find/index(int). Signed-off-by: Jim Mussared --- tests/basics/bytearray_byte_operations.py | 17 +++- tests/basics/bytes_find.py | 28 +++++++ tests/basics/bytes_index.py | 99 +++++++++++++++++++++++ tests/basics/string_find.py | 1 + tests/basics/string_index.py | 6 ++ 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 tests/basics/bytes_index.py diff --git a/tests/basics/bytearray_byte_operations.py b/tests/basics/bytearray_byte_operations.py index 48b08ab2619..794fe8cafa2 100644 --- a/tests/basics/bytearray_byte_operations.py +++ b/tests/basics/bytearray_byte_operations.py @@ -2,6 +2,7 @@ print(bytearray(b"hello world").find(b"ll")) print(bytearray(b"hello\x00world").rfind(b"l")) +print(bytearray(b"hello world").find(ord(b"l"))) print(bytearray(b"abc efg ").strip(b"g a")) print(bytearray(b" spacious ").lstrip()) @@ -14,10 +15,18 @@ print(bytearray(b"asdfasdf").replace(b"a", b"b")) -print("00\x0000".index("0", 0)) -print("00\x0000".index("0", 3)) -print("00\x0000".rindex("0", 0)) -print("00\x0000".rindex("0", 3)) +print(b"00\x0000".index(b"0", 0)) +print(b"00\x0000".index(b"0", 3)) +print(b"00\x0000".index(ord("0"), 0)) +print(b"00\x0000".index(ord("0"), 3)) +print(b"00\x0000".index(0, 0)) +try: + print(b"00\x0000".index(0, 3)) +except ValueError: + print("ValueError") +print(b"00\x0000".index(b"0", 3)) +print(b"00\x0000".rindex(b"0", 0)) +print(b"00\x0000".rindex(b"0", 3)) print(bytearray(b"foobar").endswith(b"bar")) print(bytearray(b"1foo").startswith(b"foo", 1)) diff --git a/tests/basics/bytes_find.py b/tests/basics/bytes_find.py index 75ef9796cd1..4a2707cc531 100644 --- a/tests/basics/bytes_find.py +++ b/tests/basics/bytes_find.py @@ -21,6 +21,34 @@ print(b"0000".find(b'1', 3)) print(b"0000".find(b'1', 4)) print(b"0000".find(b'1', 5)) +print(b"0000".find(ord(b'0'))) +print(b"0000".find(ord(b'0'), 0)) +print(b"0000".find(ord(b'0'), 1)) +print(b"0000".find(ord(b'0'), 2)) +print(b"0000".find(ord(b'0'), 3)) +print(b"0000".find(ord(b'0'), 4)) +print(b"0000".find(ord(b'0'), 5)) +print(b"0000".find(ord(b'x'), 3)) +print(b"0000".find(ord(b'1'), 3)) +print(b"0000".find(ord(b'1'), 4)) +print(b"0000".find(ord(b'1'), 5)) # Non-ascii values (make sure not treated as unicode-like) print(b"\x80abc".find(b"a", 1)) + +# Int-like conversion. +print(b"00\x0000".find(b'0', True)) + +# Out of bounds int. +try: + print(b"0000".find(b'0', -1)) +except ValueError: + print("ValueError") +try: + print(b"0000".find(b'0', 256)) +except ValueError: + print("ValueError") +try: + print(b"0000".find(b'0', 91273611)) +except ValueError: + print("ValueError") diff --git a/tests/basics/bytes_index.py b/tests/basics/bytes_index.py new file mode 100644 index 00000000000..08ed46e4a9c --- /dev/null +++ b/tests/basics/bytes_index.py @@ -0,0 +1,99 @@ +print(b"hello world".index(b"ll")) +print(b"hello world".index(b"ll", None)) +print(b"hello world".index(b"ll", 1)) +print(b"hello world".index(b"ll", 1, None)) +print(b"hello world".index(b"ll", None, None)) +print(b"hello world".index(b"ll", 1, -1)) +try: + print(b"hello world".index(b"ll", 1, 1)) +except ValueError: + print("ValueError") +try: + print(b"hello world".index(b"ll", 1, 2)) +except ValueError: + print("ValueError") +try: + print(b"hello world".index(b"ll", 1, 3)) +except ValueError: + print("ValueError") +print(b"hello world".index(b"ll", 1, 4)) +print(b"hello world".index(b"ll", 1, 5)) +print(b"hello world".index(b"ll", -100)) +print(b"0000".index(b'0')) +print(b"0000".index(b'0', 0)) +print(b"0000".index(b'0', 1)) +print(b"0000".index(b'0', 2)) +print(b"0000".index(b'0', 3)) +try: + print(b"0000".index(b'0', 4)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'0', 5)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'-1', 3)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'1', 3)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'1', 4)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'1', 5)) +except ValueError: + print("ValueError") +print(b"0000".index(ord(b'0'))) +print(b"0000".index(ord(b'0'), 0)) +print(b"0000".index(ord(b'0'), 1)) +print(b"0000".index(ord(b'0'), 2)) +print(b"0000".index(ord(b'0'), 3)) +try: + print(b"0000".index(ord(b'0'), 4)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(ord(b'0'), 5)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(ord(b'x'), 3)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(ord(b'1'), 3)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(ord(b'1'), 4)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(ord(b'1'), 5)) +except ValueError: + print("ValueError") + +# Non-ascii values (make sure not treated as unicode-like) +print(b"\x80abc".index(b"a", 1)) + +# Int-like conversion. +print(b"00\x0000".index(b'0', True)) + +# Out of bounds int. +try: + print(b"0000".index(b'0', -1)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'0', 256)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(b'0', 91273611)) +except ValueError: + print("ValueError") diff --git a/tests/basics/string_find.py b/tests/basics/string_find.py index f9fcad3e579..f37064ad291 100644 --- a/tests/basics/string_find.py +++ b/tests/basics/string_find.py @@ -24,6 +24,7 @@ print("aaaaaaaaaaa".find("bbb", 9, 2)) try: + # Only works on bytes/bytearray. 'abc'.find(1) except TypeError: print('TypeError') diff --git a/tests/basics/string_index.py b/tests/basics/string_index.py index 31f6900e6c1..328f2dff54c 100644 --- a/tests/basics/string_index.py +++ b/tests/basics/string_index.py @@ -76,3 +76,9 @@ print("Raised ValueError") else: print("Did not raise ValueError") + +try: + # Only works on bytes/bytearray. + 'abc'.index(1) +except TypeError: + print('TypeError') From 05fcc8c580c7ddb604c730458f7080b024cab07b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 23 Apr 2026 23:26:04 +1000 Subject: [PATCH 2026/2098] tests/basics: Add coverage for bytes find/index with out-of-bounds arg. Signed-off-by: Damien George --- tests/basics/bytes_find.py | 10 ++++++++++ tests/basics/bytes_index.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/basics/bytes_find.py b/tests/basics/bytes_find.py index 4a2707cc531..1ecbf13b0cf 100644 --- a/tests/basics/bytes_find.py +++ b/tests/basics/bytes_find.py @@ -52,3 +52,13 @@ print(b"0000".find(b'0', 91273611)) except ValueError: print("ValueError") + +# Out of bounds search argument. +try: + print(b"0000".find(-1)) +except ValueError: + print("ValueError") +try: + print(b"0000".find(256)) +except ValueError: + print("ValueError") diff --git a/tests/basics/bytes_index.py b/tests/basics/bytes_index.py index 08ed46e4a9c..d99b325192e 100644 --- a/tests/basics/bytes_index.py +++ b/tests/basics/bytes_index.py @@ -97,3 +97,13 @@ print(b"0000".index(b'0', 91273611)) except ValueError: print("ValueError") + +# Out of bounds search argument. +try: + print(b"0000".index(-1)) +except ValueError: + print("ValueError") +try: + print(b"0000".index(256)) +except ValueError: + print("ValueError") From 01df5a1f0512fdabd8d40e465b07eb12f0573744 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 3 Nov 2025 10:01:34 -0700 Subject: [PATCH 2027/2098] py/py.mk: Add LIBS_USERMOD to LIBS. This enables C++ modules to correctly postion -l linker flags at the end of the flags instead of at the start. Updated the example C++ micropython.mk accordingly. Signed-off-by: Dryw Wade --- examples/usercmodule/cppexample/micropython.mk | 5 ++++- py/py.mk | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/usercmodule/cppexample/micropython.mk b/examples/usercmodule/cppexample/micropython.mk index 0071d4fcc72..cf4c6bd9b3a 100644 --- a/examples/usercmodule/cppexample/micropython.mk +++ b/examples/usercmodule/cppexample/micropython.mk @@ -8,5 +8,8 @@ SRC_USERMOD_CXX += $(CPPEXAMPLE_MOD_DIR)/example.cpp CFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) CXXFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) -std=c++11 +# Add any necessary paths to library files. +# LDFLAGS_USERMOD += -Lpath/to/libs + # We use C++ features so have to link against the standard library. -LDFLAGS_USERMOD += -lstdc++ +LIBS_USERMOD += -lstdc++ diff --git a/py/py.mk b/py/py.mk index 932c47ef177..b37e3cf5798 100644 --- a/py/py.mk +++ b/py/py.mk @@ -47,6 +47,7 @@ SRC_USERMOD_LIB_ASM := CFLAGS_USERMOD := CXXFLAGS_USERMOD := LDFLAGS_USERMOD := +LIBS_USERMOD := # Backwards compatibility with older user c modules that set SRC_USERMOD # added to SRC_USERMOD_C below @@ -69,6 +70,7 @@ SRC_USERMOD_PATHFIX_LIB_ASM += $(patsubst $(USER_C_MODULES)/%.S,%.S,$(SRC_USERMO CFLAGS += $(CFLAGS_USERMOD) CXXFLAGS += $(CXXFLAGS_USERMOD) LDFLAGS += $(LDFLAGS_USERMOD) +LIBS += $(LIBS_USERMOD) SRC_QSTR += $(SRC_USERMOD_PATHFIX_C) $(SRC_USERMOD_PATHFIX_CXX) PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_C:.c=.o)) From 234bac45d33f410fb3b56a87cf56266e96a71c66 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Tue, 4 Nov 2025 10:38:50 -0700 Subject: [PATCH 2028/2098] py/mkrules.mk: Change LIB to LIBS. Same in ports/windows/Makefile to be consistent with other ports. Signed-off-by: Dryw Wade --- ports/windows/Makefile | 2 +- py/mkrules.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 8d371ed8ac3..6e206e7f23c 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -86,7 +86,7 @@ SRC_C += shared/readline/readline.c SRC_C += shared/runtime/pyexec.c endif -LIB += -lws2_32 +LIBS += -lws2_32 # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SHARED_SRC_C) diff --git a/py/mkrules.mk b/py/mkrules.mk index 6ac731b368b..f94bcea2ae1 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -259,7 +259,7 @@ $(BUILD)/$(PROG): $(OBJ) $(ECHO) "LINK $@" # Do not pass COPT here - it's *C* compiler optimizations. For example, # we may want to compile using Thumb, but link with non-Thumb libc. - $(Q)$(CC) -o $@ $^ $(LIB) $(LDFLAGS) + $(Q)$(CC) -o $@ $^ $(LIBS) $(LDFLAGS) ifndef DEBUG ifdef STRIP $(Q)$(STRIP) $(STRIPFLAGS_EXTRA) $@ From 416eadf9627398467cd1aea4eecf17ddedb8dbe3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 8 Apr 2026 11:43:41 +1000 Subject: [PATCH 2029/2098] ports: Use mp_obj_is_float instead of mp_obj_is_type. `mp_obj_is_float` must be used in case the object representation is C or D. Signed-off-by: Damien George --- ports/renesas-ra/timer.c | 2 +- ports/stm32/timer.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/renesas-ra/timer.c b/ports/renesas-ra/timer.c index 286bc4d0fb5..5038936a833 100644 --- a/ports/renesas-ra/timer.c +++ b/ports/renesas-ra/timer.c @@ -339,7 +339,7 @@ static mp_obj_t pyb_timer_freq(size_t n_args, const mp_obj_t *args) { uint32_t freq; if (0) { #if MICROPY_PY_BUILTINS_FLOAT - } else if (mp_obj_is_type(args[1], &mp_type_float)) { + } else if (mp_obj_is_float(args[1])) { freq = (int)mp_obj_get_float(args[1]); #endif } else { diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index a2f48139ee4..d70878267e1 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -450,7 +450,7 @@ static uint32_t compute_prescaler_period_from_freq(pyb_timer_obj_t *self, mp_obj uint32_t period; if (0) { #if MICROPY_PY_BUILTINS_FLOAT - } else if (mp_obj_is_type(freq_in, &mp_type_float)) { + } else if (mp_obj_is_float(freq_in)) { float freq = mp_obj_get_float_to_f(freq_in); if (freq <= 0) { goto bad_freq; @@ -545,7 +545,7 @@ static uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t percent uint32_t cmp; if (0) { #if MICROPY_PY_BUILTINS_FLOAT - } else if (mp_obj_is_type(percent_in, &mp_type_float)) { + } else if (mp_obj_is_float(percent_in)) { mp_float_t percent = mp_obj_get_float(percent_in); if (percent <= 0.0) { cmp = 0; From d6a1d371567ef264dfff1ae7154e4773ab2ab09d Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 8 Apr 2026 11:44:18 +1000 Subject: [PATCH 2030/2098] py/obj: Add assert that float isn't used with mp_obj_is_type. Some object representations have floats as literal objects (ie not heap allocated) and in such a case using `mp_obj_is_type(t, &mp_type_float)` will always return false. So add a compile-time assertion to force the correct usage. Signed-off-by: Damien George --- py/obj.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/py/obj.h b/py/obj.h index 4683f8a719f..c7933cd867c 100644 --- a/py/obj.h +++ b/py/obj.h @@ -117,7 +117,7 @@ extern const struct _mp_obj_float_t mp_const_float_inf_obj; extern const struct _mp_obj_float_t mp_const_float_nan_obj; #endif -#define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) +#define mp_obj_is_float(o) mp_obj_is_exact_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); #endif @@ -162,7 +162,7 @@ extern const struct _mp_obj_float_t mp_const_float_inf_obj; extern const struct _mp_obj_float_t mp_const_float_nan_obj; #endif -#define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) +#define mp_obj_is_float(o) mp_obj_is_exact_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); #endif @@ -986,8 +986,13 @@ void *mp_obj_malloc_with_finaliser_helper(size_t num_bytes, const mp_obj_type_t MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_str), assert((t) != &mp_type_str), \ MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_NoneType), assert((t) != &mp_type_NoneType), \ 1) +#if MICROPY_PY_BUILTINS_FLOAT +#define mp_type_assert_not_float(t) (MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_float), assert((t) != &mp_type_float), 1) +#else +#define mp_type_assert_not_float(t) (1) +#endif -#define mp_obj_is_type(o, t) (mp_type_assert_not_bool_int_str_nonetype(t) && mp_obj_is_exact_type(o, t)) +#define mp_obj_is_type(o, t) (mp_type_assert_not_bool_int_str_nonetype(t) && mp_type_assert_not_float(t) && mp_obj_is_exact_type(o, t)) #if MICROPY_OBJ_IMMEDIATE_OBJS // bool's are immediates, not real objects, so test for the 2 possible values. #define mp_obj_is_bool(o) ((o) == mp_const_false || (o) == mp_const_true) From a254bbca791007f15be4e5a6abda307cc526af26 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 16 Apr 2026 13:48:08 -0500 Subject: [PATCH 2031/2098] py/nlrx86: Fix nlr_push to build with Clang 19. This version is believed to work from Clang 3.0 to 22.1.0 (all versions on godbolt at the time of writing). Clang rejects the `(void)x;` notation for a used variable in a naked asm function, so do this only conditionally. Introduces use of `__builtin_unreachable()` with gcc. This saves 1 byte by causing gcc not to emit an `ud2` opcode at the end. However, the unreachable sanitizer (enabled by default(!) on Ubuntu 24.04 with gcc version 13.3.0) corrupts the ebx register, so it must be disabled. Clang does not accept `__builtin_unreachable` or `return 0;` here, UNREACHABLE must expand to nothing. Closes: #17415 Signed-off-by: Jeff Epler --- py/nlrx86.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/py/nlrx86.c b/py/nlrx86.c index 26bf0dc6ccb..82e139dd8e8 100644 --- a/py/nlrx86.c +++ b/py/nlrx86.c @@ -40,23 +40,35 @@ __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); #endif #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 8 -// Since gcc 8.0 the naked attribute is supported -#define USE_NAKED (1) -#define UNDO_PRELUDE (0) +// Since gcc 8.0 the naked and no-sanitize attributes are supported + #define NLR_PUSH_ATTRIBUTE __attribute__((naked, no_sanitize("unreachable"))) + #define UNDO_PRELUDE (0) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE __builtin_unreachable(); #elif defined(__ZEPHYR__) || defined(__ANDROID__) // Zephyr and Android use a different calling convention by default -#define USE_NAKED (0) -#define UNDO_PRELUDE (0) + #define NLR_PUSH_ATTRIBUTE /* NOTHING */ + #define UNDO_PRELUDE (0) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE return 0; +#elif defined(__clang__) +// clang on Ubuntu 24.04 enables -fsanitize=unreachable by default, but this +// destroys the content of the ebx register. + #define NLR_PUSH_ATTRIBUTE __attribute__((naked, no_sanitize("unreachable"))) + #define UNDO_PRELUDE (0) + #define ARG_USED(x) /* NOTHING */ + #define NLR_UNREACHABLE /* NOTHING */ #else -#define USE_NAKED (0) -#define UNDO_PRELUDE (1) +// gcc before 8 unavoidably emits a 'push %ebp' prologue instruction + #define NLR_PUSH_ATTRIBUTE /* NOTHING */ + #define UNDO_PRELUDE (1) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE return 0; #endif -#if USE_NAKED -__attribute__((naked)) -#endif +NLR_PUSH_ATTRIBUTE unsigned int nlr_push(nlr_buf_t *nlr) { - (void)nlr; + ARG_USED(nlr) __asm volatile ( #if UNDO_PRELUDE @@ -73,9 +85,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { "jmp nlr_push_tail \n" // do the rest in C ); - #if !USE_NAKED - return 0; // needed to silence compiler warning - #endif + NLR_UNREACHABLE } MP_NORETURN void nlr_jump(void *val) { From 5e94df1e5d2eed090742d1f1a569d6758bc108ca Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Sat, 25 Apr 2026 16:29:13 +0900 Subject: [PATCH 2032/2098] py/modbuiltins: Treat pow(x, y, None) as pow(x, y). CPython's _PyNumber_PowerNoMod normalises a None modulus to "no modulus" and dispatches to the 2-argument power path, so e.g. pow(2, -3, None) returns 0.125. MicroPython previously fell through to the integer-only 3-arg path and raised TypeError. Handle args[2] == mp_const_none at the entry of mp_builtin_pow so the existing MP_BINARY_OP_POWER handlers decide the result type. Signed-off-by: Jeongseop Lim --- py/modbuiltins.c | 25 ++++++++++++++----------- tests/basics/builtin_pow3.py | 6 ++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 6f085f2df38..9617b8fcdae 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -364,18 +364,21 @@ static mp_obj_t mp_builtin_ord(mp_obj_t o_in) { MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); static mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { - switch (n_args) { - case 2: - return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); - default: - #if !MICROPY_PY_BUILTINS_POW3 - mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); - #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ - return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); - #else - return mp_obj_int_pow3(args[0], args[1], args[2]); - #endif + // Treat pow(x, y, None) as pow(x, y), matching CPython. + if (n_args == 2 + #if MICROPY_PY_BUILTINS_POW3 + || args[2] == mp_const_none + #endif + ) { + return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); } + #if !MICROPY_PY_BUILTINS_POW3 + mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); + #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); + #else + return mp_obj_int_pow3(args[0], args[1], args[2]); + #endif } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); diff --git a/tests/basics/builtin_pow3.py b/tests/basics/builtin_pow3.py index 94e657bc441..2dbe0d33e4b 100644 --- a/tests/basics/builtin_pow3.py +++ b/tests/basics/builtin_pow3.py @@ -28,3 +28,9 @@ print(pow(4, 5, "z")) except TypeError: print("TypeError expected") + +# pow(x, y, None) is equivalent to pow(x, y) +print(pow(0, 1, None)) +print(pow(1, 0, None)) +print(pow(-2, 3, None)) +print(pow(3, 8, None)) From 72d4c4df45aac2cf62a8c72b458c5a5ada7402ef Mon Sep 17 00:00:00 2001 From: radiofan Date: Wed, 28 Jan 2026 16:02:51 +0700 Subject: [PATCH 2033/2098] py/builtinhelp: Add options for changing help('modules') format. This commit allow to change count and width of columns for help('modules') output by mpconfigport.h. Signed-off-by: radiofan --- py/builtinhelp.c | 16 +++++++++++----- py/mpconfig.h | 10 ++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/py/builtinhelp.c b/py/builtinhelp.c index 59d7e26ac66..ccbbbf91629 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -33,6 +33,14 @@ #if MICROPY_PY_BUILTINS_HELP +#if MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS <= 0 +#error "MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS must be more than 0" +#endif + +#if MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH <= 0 +#error "MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH must be more than 0" +#endif + const char mp_help_default_text[] = "Welcome to MicroPython!\n" "\n" @@ -93,12 +101,10 @@ static void mp_help_print_modules(void) { mp_obj_list_sort(1, &list, (mp_map_t *)&mp_const_empty_map); // print the list of modules in a column-first order - #define NUM_COLUMNS (4) - #define COLUMN_WIDTH (18) size_t len; mp_obj_t *items; mp_obj_list_get(list, &len, &items); - unsigned int num_rows = (len + NUM_COLUMNS - 1) / NUM_COLUMNS; + unsigned int num_rows = (len + MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS - 1) / MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS; for (unsigned int i = 0; i < num_rows; ++i) { unsigned int j = i; for (;;) { @@ -107,9 +113,9 @@ static void mp_help_print_modules(void) { if (j >= len) { break; } - int gap = COLUMN_WIDTH - l; + int gap = MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH - l; while (gap < 1) { - gap += COLUMN_WIDTH; + gap += MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH; } while (gap--) { mp_print_str(MP_PYTHON_PRINTER, " "); diff --git a/py/mpconfig.h b/py/mpconfig.h index 0d81898537a..f46e79911ed 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1569,6 +1569,16 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_HELP_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Use this to configure output of help('modules') +#ifndef MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS +#define MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS (4) +#endif + +// Use this to configure output of help('modules') +#ifndef MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH +#define MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH (18) +#endif + // Whether to provide mem-info related functions in micropython module #ifndef MICROPY_PY_MICROPYTHON_MEM_INFO #define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) From 88007335ff51fd74b8e47d8f3a36ca9b46597fb6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 07:29:45 +1000 Subject: [PATCH 2034/2098] lib/nrfx: Update submodule to v3.14.0. Signed-off-by: Andrew Leech --- lib/nrfx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nrfx b/lib/nrfx index 7a4c9d946cf..11f57e578c7 160000 --- a/lib/nrfx +++ b/lib/nrfx @@ -1 +1 @@ -Subproject commit 7a4c9d946cf1801771fc180acdbf7b878f270093 +Subproject commit 11f57e578c7feea13f21c79ea0efab2630ac68c7 From a6f90f38a62c4201cd35c03ed9e28b46cfb25456 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 07:29:54 +1000 Subject: [PATCH 2035/2098] nrf: Adapt port configuration and startup for nrfx v3. Add API version macros, convert NRFX_CHECK config to literal 0/1, add NRF_GPIOTE0 compat alias, implement required nrfx_glue.h macros (atomics, CLZ/CTZ, cache, event readback, IRQ pending), add haly/helpers include paths and new source files, and update startup_nrf52840.c IRQ handler names to match the nrfx v3 MDK renames in nrf52840_name_change.h. Signed-off-by: Andrew Leech --- ports/nrf/Makefile | 7 ++ ports/nrf/device/startup_nrf52840.c | 48 ++++---- ports/nrf/nrfx_config.h | 175 ++++++++++++++++++++++------ ports/nrf/nrfx_glue.h | 36 ++++++ 4 files changed, 208 insertions(+), 58 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index dc46ab1ec46..546c17709e5 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -88,6 +88,8 @@ INC += -I../../lib/nrfx/drivers INC += -I../../lib/nrfx/drivers/include INC += -I../../lib/nrfx/mdk INC += -I../../lib/nrfx/hal +INC += -I../../lib/nrfx/haly +INC += -I../../lib/nrfx/helpers INC += -I../../lib/nrfx/drivers/src/ INC += -I../../shared/readline @@ -99,6 +101,7 @@ SYSTEM_C_SRC := ifeq ($(MCU_SUB_VARIANT),nrf51822) SYSTEM_C_SRC += $(addprefix lib/nrfx/mdk/, system_nrf51.c) NRF_DEFINES += -D$(MCU_VARIANT_UPPER) + NRF_DEFINES += -DNRF51822_XXAA else ifeq ($(MCU_SUB_VARIANT),nrf52832) SYSTEM_C_SRC += $(addprefix lib/nrfx/mdk/, system_nrf52.c) NRF_DEFINES += -D$(MCU_VARIANT_UPPER) @@ -224,6 +227,10 @@ SRC_NRFX += $(addprefix lib/nrfx/drivers/src/,\ nrfx_clock.c \ ) +SRC_NRFX += \ + lib/nrfx/soc/nrfx_atomic.c \ + lib/nrfx/helpers/nrfx_flag32_allocator.c + SRC_C += \ main.c \ mphalport.c \ diff --git a/ports/nrf/device/startup_nrf52840.c b/ports/nrf/device/startup_nrf52840.c index 288b13820f5..f7696346e74 100644 --- a/ports/nrf/device/startup_nrf52840.c +++ b/ports/nrf/device/startup_nrf52840.c @@ -70,11 +70,11 @@ void DebugMon_Handler (void) __attribute__ ((weak, alias("Default_Han void PendSV_Handler (void) __attribute__ ((weak, alias("Default_Handler"))); void SysTick_Handler (void) __attribute__ ((weak, alias("Default_Handler"))); -void POWER_CLOCK_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void CLOCK_POWER_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void RADIO_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void UARTE0_UART0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void UART0_UARTE0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void SPI0_SPIM0_SPIS0_TWI0_TWIM0_TWIS0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void SPI1_SPIM1_SPIS1_TWI1_TWIM1_TWIS1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void NFCT_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void GPIOTE_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void SAADC_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); @@ -85,17 +85,17 @@ void RTC0_IRQHandler (void) __attribute__ ((weak, alias("Default_Han void TEMP_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void RNG_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void ECB_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void CCM_AAR_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void AAR_CCM_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void WDT_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void RTC1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void QDEC_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void COMP_LPCOMP_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI0_EGU0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI1_EGU1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI2_EGU2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI3_EGU3_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI4_EGU4_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SWI5_EGU5_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU0_SWI0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU1_SWI1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU2_SWI2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU3_SWI3_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU4_SWI4_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void EGU5_SWI5_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void TIMER3_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void TIMER4_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void PWM0_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); @@ -103,7 +103,7 @@ void PDM_IRQHandler (void) __attribute__ ((weak, alias("Default_Han void MWU_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void PWM1_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void PWM2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); -void SPIM2_SPIS2_SPI2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); +void SPI2_SPIM2_SPIS2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void RTC2_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void I2S_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); void FPU_IRQHandler (void) __attribute__ ((weak, alias("Default_Handler"))); @@ -133,11 +133,11 @@ const func __Vectors[] __attribute__ ((section(".isr_vector"),used)) = { SysTick_Handler, /* External Interrupts */ - POWER_CLOCK_IRQHandler, + CLOCK_POWER_IRQHandler, RADIO_IRQHandler, - UARTE0_UART0_IRQHandler, - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler, - SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler, + UART0_UARTE0_IRQHandler, + SPI0_SPIM0_SPIS0_TWI0_TWIM0_TWIS0_IRQHandler, + SPI1_SPIM1_SPIS1_TWI1_TWIM1_TWIS1_IRQHandler, NFCT_IRQHandler, GPIOTE_IRQHandler, SAADC_IRQHandler, @@ -148,17 +148,17 @@ const func __Vectors[] __attribute__ ((section(".isr_vector"),used)) = { TEMP_IRQHandler, RNG_IRQHandler, ECB_IRQHandler, - CCM_AAR_IRQHandler, + AAR_CCM_IRQHandler, WDT_IRQHandler, RTC1_IRQHandler, QDEC_IRQHandler, COMP_LPCOMP_IRQHandler, - SWI0_EGU0_IRQHandler, - SWI1_EGU1_IRQHandler, - SWI2_EGU2_IRQHandler, - SWI3_EGU3_IRQHandler, - SWI4_EGU4_IRQHandler, - SWI5_EGU5_IRQHandler, + EGU0_SWI0_IRQHandler, + EGU1_SWI1_IRQHandler, + EGU2_SWI2_IRQHandler, + EGU3_SWI3_IRQHandler, + EGU4_SWI4_IRQHandler, + EGU5_SWI5_IRQHandler, TIMER3_IRQHandler, TIMER4_IRQHandler, PWM0_IRQHandler, @@ -168,7 +168,7 @@ const func __Vectors[] __attribute__ ((section(".isr_vector"),used)) = { MWU_IRQHandler, PWM1_IRQHandler, PWM2_IRQHandler, - SPIM2_SPIS2_SPI2_IRQHandler, + SPI2_SPIM2_SPIS2_IRQHandler, RTC2_IRQHandler, I2S_IRQHandler, FPU_IRQHandler, diff --git a/ports/nrf/nrfx_config.h b/ports/nrf/nrfx_config.h index c38af62145f..29e32e21c14 100644 --- a/ports/nrf/nrfx_config.h +++ b/ports/nrf/nrfx_config.h @@ -31,6 +31,11 @@ #include "py/mpconfig.h" #include "nrf.h" +// nrfx v3 API version selection +#define NRFX_CONFIG_API_VER_MAJOR 3 +#define NRFX_CONFIG_API_VER_MINOR 14 +#define NRFX_CONFIG_API_VER_MICRO 0 + // Port specific defines #ifndef NRFX_LOG_ENABLED #define NRFX_LOG_ENABLED 0 @@ -49,6 +54,12 @@ #define GPIO_COUNT 1 #endif +// nrfx v3 instance macros expect NRF_GPIOTE0 but nrf51/nrf52 MDK +// only defines NRF_GPIOTE (single instance, no numeric suffix). +#if defined(NRF51) || defined(NRF52_SERIES) +#define NRF_GPIOTE0 NRF_GPIOTE +#endif + #if defined(NRF52840) // for tinyusb // #define NRFX_IRQ_IS_ENABLED 1 @@ -58,6 +69,9 @@ #endif #define NRFX_GPIOTE_ENABLED 1 +#if !defined(NRF9160_XXAA) +#define NRFX_GPIOTE0_ENABLED 1 +#endif #define NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS 1 #if NRF51 #define NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY 3 @@ -92,18 +106,32 @@ #define NRFX_UARTE3_ENABLED 1 #endif +// nrfx v3 NRFX_CHECK() requires literal 0/1 values, not C expressions. + #if defined(NRF51) || defined(NRF52_SERIES) - #define NRFX_TWI_ENABLED (MICROPY_PY_MACHINE_I2C) + #if MICROPY_PY_MACHINE_I2C + #define NRFX_TWI_ENABLED 1 + #else + #define NRFX_TWI_ENABLED 0 + #endif #define NRFX_TWI0_ENABLED 1 #define NRFX_TWI1_ENABLED 1 #elif defined(NRF9160_XXAA) - #define NRFX_TWIM_ENABLED (MICROPY_PY_MACHINE_I2C) + #if MICROPY_PY_MACHINE_I2C + #define NRFX_TWIM_ENABLED 1 + #else + #define NRFX_TWIM_ENABLED 0 + #endif #define NRFX_TWIM0_ENABLED 1 #define NRFX_TWIM1_ENABLED 1 #endif #if defined(NRF51) || defined(NRF52832) - #define NRFX_SPI_ENABLED (MICROPY_PY_MACHINE_SPI) + #if MICROPY_PY_MACHINE_SPI + #define NRFX_SPI_ENABLED 1 + #else + #define NRFX_SPI_ENABLED 0 + #endif #define NRFX_SPI0_ENABLED 1 #define NRFX_SPI1_ENABLED 1 @@ -111,20 +139,23 @@ #define NRFX_SPI2_ENABLED 1 #endif #elif defined(NRF52840) - #define NRFX_SPIM_ENABLED (MICROPY_PY_MACHINE_SPI) + #if MICROPY_PY_MACHINE_SPI + #define NRFX_SPIM_ENABLED 1 + #else + #define NRFX_SPIM_ENABLED 0 + #endif #define NRFX_SPIM0_ENABLED 1 #define NRFX_SPIM1_ENABLED 1 #define NRFX_SPIM2_ENABLED 1 - #define NRFX_SPIM3_ENABLED (NRF52840) + #define NRFX_SPIM3_ENABLED 1 #elif defined(NRF9160_XXAA) - #define NRFX_SPIM_ENABLED (MICROPY_PY_MACHINE_SPI) + #if MICROPY_PY_MACHINE_SPI + #define NRFX_SPIM_ENABLED 1 + #else + #define NRFX_SPIM_ENABLED 0 + #endif #define NRFX_SPIM0_ENABLED 1 #define NRFX_SPIM1_ENABLED 1 - -// 0 NRF_GPIO_PIN_NOPULL -// 1 NRF_GPIO_PIN_PULLDOWN -// 3 NRF_GPIO_PIN_PULLUP - #define NRFX_SPIM_MISO_PULL_CFG 1 #endif // NRF51 // 0 NRF_GPIO_PIN_NOPULL @@ -133,49 +164,123 @@ #define NRFX_SPI_MISO_PULL_CFG 1 #define NRFX_SPIM_MISO_PULL_CFG 1 -#define NRFX_RTC_ENABLED (MICROPY_PY_MACHINE_RTCOUNTER) +#if MICROPY_PY_MACHINE_RTCOUNTER + #define NRFX_RTC_ENABLED 1 +#else + #define NRFX_RTC_ENABLED 0 +#endif #define NRFX_RTC0_ENABLED 1 #define NRFX_RTC1_ENABLED 1 -#define NRFX_RTC2_ENABLED (!NRF51) && (!NRF9160_XXAA) +#if !defined(NRF51) && !defined(NRF9160_XXAA) + #define NRFX_RTC2_ENABLED 1 +#else + #define NRFX_RTC2_ENABLED 0 +#endif -#define NRFX_TIMER_ENABLED (MICROPY_PY_MACHINE_TIMER_NRF) +#if MICROPY_PY_MACHINE_TIMER_NRF + #define NRFX_TIMER_ENABLED 1 +#else + #define NRFX_TIMER_ENABLED 0 +#endif #define NRFX_TIMER0_ENABLED 1 -#define NRFX_TIMER1_ENABLED (!MICROPY_PY_MACHINE_SOFT_PWM) +#if MICROPY_PY_MACHINE_SOFT_PWM + #define NRFX_TIMER1_ENABLED 0 +#else + #define NRFX_TIMER1_ENABLED 1 +#endif #define NRFX_TIMER2_ENABLED 1 -#define NRFX_TIMER3_ENABLED (!NRF51) && (!NRF9160_XXAA) -#define NRFX_TIMER4_ENABLED (!NRF51) && (!NRF9160_XXAA) - +#if !defined(NRF51) && !defined(NRF9160_XXAA) + #define NRFX_TIMER3_ENABLED 1 + #define NRFX_TIMER4_ENABLED 1 +#else + #define NRFX_TIMER3_ENABLED 0 + #define NRFX_TIMER4_ENABLED 0 +#endif -#define NRFX_PWM_ENABLED (!NRF51) && MICROPY_PY_MACHINE_HW_PWM +#if !defined(NRF51) && MICROPY_PY_MACHINE_HW_PWM + #define NRFX_PWM_ENABLED 1 +#else + #define NRFX_PWM_ENABLED 0 +#endif #define NRFX_PWM0_ENABLED 1 #define NRFX_PWM1_ENABLED 1 #define NRFX_PWM2_ENABLED 1 -#define NRFX_PWM3_ENABLED (NRF52840) +#if defined(NRF52840) + #define NRFX_PWM3_ENABLED 1 +#else + #define NRFX_PWM3_ENABLED 0 +#endif #define NRFX_NVMC_ENABLED 1 // Peripheral Resource Sharing #if defined(NRF51) || defined(NRF52832) - #define NRFX_PRS_BOX_0_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI0_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI0_ENABLED) - #define NRFX_PRS_BOX_1_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI1_ENABLED) - - #if defined(NRF52832) - #define NRFX_PRS_BOX_2_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI1_ENABLED) + #if NRFX_TWI_ENABLED && NRFX_TWI0_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI0_ENABLED + #define NRFX_PRS_BOX_0_ENABLED 1 + #else + #define NRFX_PRS_BOX_0_ENABLED 0 + #endif + #if NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI1_ENABLED + #define NRFX_PRS_BOX_1_ENABLED 1 + #else + #define NRFX_PRS_BOX_1_ENABLED 0 + #endif + #if defined(NRF52832) && NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPI_ENABLED && NRFX_SPI1_ENABLED + #define NRFX_PRS_BOX_2_ENABLED 1 + #else + #define NRFX_PRS_BOX_2_ENABLED 0 #endif #elif defined(NRF52840) - #define NRFX_PRS_BOX_0_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI0_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM0_ENABLED) - #define NRFX_PRS_BOX_1_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM1_ENABLED) - #define NRFX_PRS_BOX_2_ENABLED (NRFX_TWI_ENABLED && NRFX_TWI2_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM2_ENABLED) + #if NRFX_TWI_ENABLED && NRFX_TWI0_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM0_ENABLED + #define NRFX_PRS_BOX_0_ENABLED 1 + #else + #define NRFX_PRS_BOX_0_ENABLED 0 + #endif + #if NRFX_TWI_ENABLED && NRFX_TWI1_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM1_ENABLED + #define NRFX_PRS_BOX_1_ENABLED 1 + #else + #define NRFX_PRS_BOX_1_ENABLED 0 + #endif +// nRF52840 has no TWI2, so BOX_2 PRS is not applicable. + #define NRFX_PRS_BOX_2_ENABLED 0 #elif defined(NRF9160_XXAA) - #define NRFX_PRS_BOX_0_ENABLED (NRFX_TWIM_ENABLED && NRFX_TWIM0_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM0_ENABLED) - #define NRFX_PRS_BOX_1_ENABLED (NRFX_TWIM_ENABLED && NRFX_TWIM1_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM1_ENABLED) - #define NRFX_PRS_BOX_2_ENABLED (NRFX_TWIM_ENABLED && NRFX_TWIM2_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM2_ENABLED) + #if NRFX_TWIM_ENABLED && NRFX_TWIM0_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM0_ENABLED + #define NRFX_PRS_BOX_0_ENABLED 1 + #else + #define NRFX_PRS_BOX_0_ENABLED 0 + #endif + #if NRFX_TWIM_ENABLED && NRFX_TWIM1_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM1_ENABLED + #define NRFX_PRS_BOX_1_ENABLED 1 + #else + #define NRFX_PRS_BOX_1_ENABLED 0 + #endif + #if NRFX_TWIM_ENABLED && NRFX_TWIM2_ENABLED && NRFX_SPIM_ENABLED && NRFX_SPIM2_ENABLED + #define NRFX_PRS_BOX_2_ENABLED 1 + #else + #define NRFX_PRS_BOX_2_ENABLED 0 + #endif +#else + #define NRFX_PRS_BOX_0_ENABLED 0 + #define NRFX_PRS_BOX_1_ENABLED 0 + #define NRFX_PRS_BOX_2_ENABLED 0 #endif -#define NRFX_PRS_ENABLED (NRFX_PRS_BOX_0_ENABLED || NRFX_PRS_BOX_1_ENABLED || NRFX_PRS_BOX_2_ENABLED) +#if NRFX_PRS_BOX_0_ENABLED || NRFX_PRS_BOX_1_ENABLED || NRFX_PRS_BOX_2_ENABLED + #define NRFX_PRS_ENABLED 1 +#else + #define NRFX_PRS_ENABLED 0 +#endif -#define NRFX_SAADC_ENABLED !(NRF51) && (MICROPY_PY_MACHINE_ADC) -#define NRFX_ADC_ENABLED (NRF51) && (MICROPY_PY_MACHINE_ADC) +#if !defined(NRF51) && MICROPY_PY_MACHINE_ADC + #define NRFX_SAADC_ENABLED 1 +#else + #define NRFX_SAADC_ENABLED 0 +#endif +#if defined(NRF51) && MICROPY_PY_MACHINE_ADC + #define NRFX_ADC_ENABLED 1 +#else + #define NRFX_ADC_ENABLED 0 +#endif #if defined(NRF9160_XXAA) @@ -240,6 +345,8 @@ #define GPIOTE_IRQn GPIOTE1_IRQn #define GPIOTE_IRQHandler GPIOTE1_IRQHandler +#define NRFX_GPIOTE1_ENABLED 1 + #endif #endif // NRFX_CONFIG_H diff --git a/ports/nrf/nrfx_glue.h b/ports/nrf/nrfx_glue.h index 56e1f719dae..af3d30d2dad 100644 --- a/ports/nrf/nrfx_glue.h +++ b/ports/nrf/nrfx_glue.h @@ -31,6 +31,7 @@ #include "py/misc.h" #include +#include #ifndef ARRAY_SIZE #define ARRAY_SIZE MP_ARRAY_SIZE @@ -43,6 +44,39 @@ void mp_hal_delay_us(mp_uint_t us); #define NRFX_DELAY_US mp_hal_delay_us +// Atomic operations (required by nrfx v3+) +#define nrfx_atomic_t nrfx_atomic_u32_t +#define NRFX_ATOMIC_FETCH_STORE(p_data, value) nrfx_atomic_u32_fetch_store(p_data, value) +#define NRFX_ATOMIC_FETCH_OR(p_data, value) nrfx_atomic_u32_fetch_or(p_data, value) +#define NRFX_ATOMIC_FETCH_AND(p_data, value) nrfx_atomic_u32_fetch_and(p_data, value) +#define NRFX_ATOMIC_FETCH_XOR(p_data, value) nrfx_atomic_u32_fetch_xor(p_data, value) +#define NRFX_ATOMIC_FETCH_ADD(p_data, value) nrfx_atomic_u32_fetch_add(p_data, value) +#define NRFX_ATOMIC_FETCH_SUB(p_data, value) nrfx_atomic_u32_fetch_sub(p_data, value) +#define NRFX_ATOMIC_CAS(p_data, old_value, new_value) \ + nrfx_atomic_u32_cmp_exch(p_data, &(old_value), new_value) + +// CLZ/CTZ intrinsics +#define NRFX_CLZ(value) __CLZ(value) +#define NRFX_CTZ(value) __CLZ(__RBIT(value)) + +// Event readback is required on all nRF51/nRF52 devices +#define NRFX_EVENT_READBACK_ENABLED 1 + +// Override NRFY_CACHE_INV to suppress unused variable warnings +// on targets without data cache (nRF51/nRF52). +#define NRFY_CACHE_INV(p_buffer, size) do { (void)(p_buffer); (void)(size); } while (0) +#define NRFY_CACHE_WB(p_buffer, size) do { (void)(p_buffer); (void)(size); } while (0) +#define NRFY_CACHE_WBINV(p_buffer, size) do { (void)(p_buffer); (void)(size); } while (0) + +// Resource usage tracking +#define NRFX_DPPI_CHANNELS_USED 0 +#define NRFX_DPPI_GROUPS_USED 0 +#define NRFX_PPI_CHANNELS_USED 0 +#define NRFX_PPI_GROUPS_USED 0 +#define NRFX_EGUS_USED 0 +#define NRFX_GPIOTE_CHANNELS_USED 0 +#define NRFX_TIMERS_USED 0 + #if BLUETOOTH_SD #if NRF51 @@ -149,4 +183,6 @@ void mp_hal_delay_us(mp_uint_t us); #define NRFX_IRQ_IS_ENABLED(irq_number) (0 != (NVIC->ISER[irq_number / 32] & (1UL << (irq_number % 32)))) +#define NRFX_IRQ_IS_PENDING(irq_number) (0 != (NVIC->ISPR[irq_number / 32] & (1UL << (irq_number % 32)))) + #endif // NRFX_GLUE_H From 7ac25f70740214288dcc30c188cd5c16af6ce52c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 07:34:45 +1000 Subject: [PATCH 2036/2098] nrf: Rewrite GPIOTE pin IRQ handling for nrfx v3 API. Replace removed nrfx_gpiote_in_init/uninit/event_enable with the v3 instance-based API: channel allocation, input_configure, and trigger_enable. Allocates a hardware GPIOTE channel per pin to preserve high-accuracy edge detection. Falls back to PORT SENSE if all channels are in use. Signed-off-by: Andrew Leech --- ports/nrf/modules/machine/pin.c | 86 +++++++++++++++++++++++++-------- ports/nrf/nrfx_glue.h | 3 -- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/ports/nrf/modules/machine/pin.c b/ports/nrf/modules/machine/pin.c index f46394d769c..7dbf88bab53 100644 --- a/ports/nrf/modules/machine/pin.c +++ b/ports/nrf/modules/machine/pin.c @@ -38,12 +38,30 @@ #include "nrf_gpio.h" #include "nrfx_gpiote.h" +#if defined(NRF9160_XXAA) +static const nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(1); +#else +static const nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(0); +#endif + #if defined(NRF52840_XXAA) #define NUM_OF_PINS 48 #else #define NUM_OF_PINS 32 #endif +static uint8_t pin_gpiote_ch[NUM_OF_PINS]; + +#define PIN_GPIOTE_CH_NONE UINT8_MAX + +static void pin_gpiote_release(nrfx_gpiote_pin_t pin) { + nrfx_gpiote_pin_uninit(&gpiote_inst, pin); + if (pin_gpiote_ch[pin] != PIN_GPIOTE_CH_NONE) { + nrfx_gpiote_channel_free(&gpiote_inst, pin_gpiote_ch[pin]); + pin_gpiote_ch[pin] = PIN_GPIOTE_CH_NONE; + } +} + extern const pin_obj_t machine_board_pin_obj[]; extern const uint8_t machine_pin_num_of_board_pins; @@ -119,10 +137,17 @@ void pin_init0(void) { for (int i = 0; i < NUM_OF_PINS; i++) { MP_STATE_PORT(pin_irq_handlers)[i] = mp_const_none; } - // Initialize GPIOTE if not done yet. - if (!nrfx_gpiote_is_init()) { - nrfx_gpiote_init(NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY); + if (!nrfx_gpiote_init_check(&gpiote_inst)) { + nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY); + } else { + // Soft reset: free GPIOTE channels from the previous cycle. + for (int i = 0; i < NUM_OF_PINS; i++) { + if (pin_gpiote_ch[i] != PIN_GPIOTE_CH_NONE) { + pin_gpiote_release(i); + } + } } + memset(pin_gpiote_ch, PIN_GPIOTE_CH_NONE, sizeof(pin_gpiote_ch)); #if PIN_DEBUG pin_class_debug = false; @@ -494,7 +519,8 @@ static mp_obj_t pin_af(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_obj, pin_af); -static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { +static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t action, void *p_context) { + (void)p_context; mp_obj_t pin_handler = MP_STATE_PORT(pin_irq_handlers)[pin]; mp_obj_t pin_number = MP_OBJ_NEW_SMALL_INT(pin); const pin_obj_t *pin_obj = pin_find(pin_number); @@ -513,7 +539,7 @@ static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t } else { // Uncaught exception; disable the callback so it doesn't run again. MP_STATE_PORT(pin_irq_handlers)[pin] = mp_const_none; - nrfx_gpiote_in_uninit(pin); + pin_gpiote_release(pin); mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in interrupt handler for Pin('%q')\n", pin_obj->name); mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } @@ -537,30 +563,50 @@ static mp_obj_t pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar nrfx_gpiote_pin_t pin = self->pin; + pin_gpiote_release(pin); + if (args[ARG_handler].u_obj != mp_const_none) { - nrfx_gpiote_in_config_t config = NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(true); + nrfx_gpiote_trigger_t trigger = NRFX_GPIOTE_TRIGGER_TOGGLE; if (args[ARG_trigger].u_int == NRF_GPIOTE_POLARITY_LOTOHI) { - config.sense = NRF_GPIOTE_POLARITY_LOTOHI; + trigger = NRFX_GPIOTE_TRIGGER_LOTOHI; } else if (args[ARG_trigger].u_int == NRF_GPIOTE_POLARITY_HITOLO) { - config.sense = NRF_GPIOTE_POLARITY_HITOLO; + trigger = NRFX_GPIOTE_TRIGGER_HITOLO; } - config.pull = NRF_GPIO_PIN_PULLUP; - config.skip_gpio_setup = true; - - nrfx_err_t err_code = nrfx_gpiote_in_init(pin, &config, pin_common_irq_handler); - if (err_code == NRFX_ERROR_INVALID_STATE) { - // Re-init if already configured. - nrfx_gpiote_in_uninit(pin); - nrfx_gpiote_in_init(pin, &config, pin_common_irq_handler); + + uint8_t gpiote_ch; + nrfx_err_t err = nrfx_gpiote_channel_alloc(&gpiote_inst, &gpiote_ch); + + nrfx_gpiote_trigger_config_t trigger_config = { + .trigger = trigger, + .p_in_channel = (err == NRFX_SUCCESS) ? &gpiote_ch : NULL, + }; + nrfx_gpiote_handler_config_t handler_config = { + .handler = pin_common_irq_handler, + .p_context = NULL, + }; + nrfx_gpiote_input_pin_config_t input_config = { + .p_pull_config = NULL, + .p_trigger_config = &trigger_config, + .p_handler_config = &handler_config, + }; + + nrfx_err_t cfg_err = nrfx_gpiote_input_configure(&gpiote_inst, pin, &input_config); + if (cfg_err != NRFX_SUCCESS) { + if (err == NRFX_SUCCESS) { + nrfx_gpiote_channel_free(&gpiote_inst, gpiote_ch); + } + mp_raise_ValueError(MP_ERROR_TEXT("pin IRQ config failed")); } - } else { - nrfx_gpiote_in_uninit(pin); + + if (err == NRFX_SUCCESS) { + pin_gpiote_ch[pin] = gpiote_ch; + } + + nrfx_gpiote_trigger_enable(&gpiote_inst, pin, true); } MP_STATE_PORT(pin_irq_handlers)[pin] = args[ARG_handler].u_obj; - nrfx_gpiote_in_event_enable(pin, true); - // return the irq object return mp_const_none; } diff --git a/ports/nrf/nrfx_glue.h b/ports/nrf/nrfx_glue.h index af3d30d2dad..04881868527 100644 --- a/ports/nrf/nrfx_glue.h +++ b/ports/nrf/nrfx_glue.h @@ -44,7 +44,6 @@ void mp_hal_delay_us(mp_uint_t us); #define NRFX_DELAY_US mp_hal_delay_us -// Atomic operations (required by nrfx v3+) #define nrfx_atomic_t nrfx_atomic_u32_t #define NRFX_ATOMIC_FETCH_STORE(p_data, value) nrfx_atomic_u32_fetch_store(p_data, value) #define NRFX_ATOMIC_FETCH_OR(p_data, value) nrfx_atomic_u32_fetch_or(p_data, value) @@ -55,7 +54,6 @@ void mp_hal_delay_us(mp_uint_t us); #define NRFX_ATOMIC_CAS(p_data, old_value, new_value) \ nrfx_atomic_u32_cmp_exch(p_data, &(old_value), new_value) -// CLZ/CTZ intrinsics #define NRFX_CLZ(value) __CLZ(value) #define NRFX_CTZ(value) __CLZ(__RBIT(value)) @@ -68,7 +66,6 @@ void mp_hal_delay_us(mp_uint_t us); #define NRFY_CACHE_WB(p_buffer, size) do { (void)(p_buffer); (void)(size); } while (0) #define NRFY_CACHE_WBINV(p_buffer, size) do { (void)(p_buffer); (void)(size); } while (0) -// Resource usage tracking #define NRFX_DPPI_CHANNELS_USED 0 #define NRFX_DPPI_GROUPS_USED 0 #define NRFX_PPI_CHANNELS_USED 0 From a9fc9e44583435dda79552c8e0d26ddaad51ed11 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 07:34:55 +1000 Subject: [PATCH 2037/2098] nrf: Update peripheral drivers for nrfx v3 API. Adapt UART, ADC, PWM, SPI, I2C, and RTC drivers for nrfx v3 renamed structs, constants, and function signatures. Notably: UARTE config members renamed and conditional on UART vs UARTE for nRF51 compatibility, SAADC value type changed to int16_t, pin-not-connected constants renamed, TWI vs TWIM pin config split, and RTC prescaler macro renamed. Signed-off-by: Andrew Leech --- ports/nrf/modules/machine/adc.c | 4 ++-- ports/nrf/modules/machine/i2c.c | 6 ++++++ ports/nrf/modules/machine/pwm.c | 9 +++++---- ports/nrf/modules/machine/rtcounter.c | 2 +- ports/nrf/modules/machine/spi.c | 2 +- ports/nrf/modules/machine/timer.c | 2 +- ports/nrf/modules/machine/uart.c | 29 ++++++++++++++++++++++++--- 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/ports/nrf/modules/machine/adc.c b/ports/nrf/modules/machine/adc.c index 9fa8005a228..c5acc0d77f0 100644 --- a/ports/nrf/modules/machine/adc.c +++ b/ports/nrf/modules/machine/adc.c @@ -161,7 +161,7 @@ int16_t machine_adc_value_read(machine_adc_obj_t *adc_obj) { nrfx_adc_sample_convert(&channel_config, &value); #else // NRF52 - nrf_saadc_value_t value = 0; + int16_t value = 0; nrfx_saadc_simple_mode_set((1 << adc_obj->id), NRF_SAADC_RESOLUTION_8BIT, NRF_SAADC_INPUT_DISABLED, NULL); nrfx_saadc_buffer_set(&value, 1); @@ -248,7 +248,7 @@ mp_obj_t machine_adc_battery_level(void) { nrfx_adc_sample_convert(&channel_config, &value); #else // NRF52 - nrf_saadc_value_t value = 0; + int16_t value = 0; const nrfx_saadc_channel_t config = { \ .channel_config = diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index 6c2b3e94838..ced53346414 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -116,8 +116,14 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz const machine_hard_i2c_obj_t *self = &machine_hard_i2c_obj[i2c_id]; nrfx_twi_config_t config; + memset(&config, 0, sizeof(config)); + #if NRFX_TWI_ENABLED config.scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj)->pin; config.sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj)->pin; + #else + config.scl_pin = mp_hal_get_pin_obj(args[ARG_scl].u_obj)->pin; + config.sda_pin = mp_hal_get_pin_obj(args[ARG_sda].u_obj)->pin; + #endif int freq = NRF_TWI_FREQ_400K; if (args[ARG_freq].u_int != -1) { diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 13d824e8667..4a6bfa86108 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -332,6 +332,7 @@ static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty) { static void machine_hard_pwm_start(const machine_pwm_obj_t *self) { nrfx_pwm_config_t config; + memset(&config, 0, sizeof(config)); // check if ready to go if (self->p_config->defer_start == true || self->p_config->freq_div < 0 || self->p_config->duty_mode[self->channel] == DUTY_NOT_SET) { @@ -340,10 +341,10 @@ static void machine_hard_pwm_start(const machine_pwm_obj_t *self) { self->p_config->active = RUNNING; - config.output_pins[0] = self->p_config->duty_mode[0] != DUTY_NOT_SET ? self->p_config->pwm_pin[0] : NRFX_PWM_PIN_NOT_USED; - config.output_pins[1] = self->p_config->duty_mode[1] != DUTY_NOT_SET ? self->p_config->pwm_pin[1] : NRFX_PWM_PIN_NOT_USED; - config.output_pins[2] = self->p_config->duty_mode[2] != DUTY_NOT_SET ? self->p_config->pwm_pin[2] : NRFX_PWM_PIN_NOT_USED; - config.output_pins[3] = self->p_config->duty_mode[3] != DUTY_NOT_SET ? self->p_config->pwm_pin[3] : NRFX_PWM_PIN_NOT_USED; + config.output_pins[0] = self->p_config->duty_mode[0] != DUTY_NOT_SET ? self->p_config->pwm_pin[0] : NRF_PWM_PIN_NOT_CONNECTED; + config.output_pins[1] = self->p_config->duty_mode[1] != DUTY_NOT_SET ? self->p_config->pwm_pin[1] : NRF_PWM_PIN_NOT_CONNECTED; + config.output_pins[2] = self->p_config->duty_mode[2] != DUTY_NOT_SET ? self->p_config->pwm_pin[2] : NRF_PWM_PIN_NOT_CONNECTED; + config.output_pins[3] = self->p_config->duty_mode[3] != DUTY_NOT_SET ? self->p_config->pwm_pin[3] : NRF_PWM_PIN_NOT_CONNECTED; uint32_t tick_freq = PWM_MAX_BASE_FREQ / (1 << self->p_config->freq_div); uint32_t period = tick_freq / self->p_config->freq; diff --git a/ports/nrf/modules/machine/rtcounter.c b/ports/nrf/modules/machine/rtcounter.c index a66a635a05d..66a57145d23 100644 --- a/ports/nrf/modules/machine/rtcounter.c +++ b/ports/nrf/modules/machine/rtcounter.c @@ -128,7 +128,7 @@ static void rtc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t /* MicroPython bindings for machine API */ const nrfx_rtc_config_t machine_rtc_config = { - .prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQUENCY), + .prescaler = NRF_RTC_FREQ_TO_PRESCALER(RTC_FREQUENCY), .reliable = 0, .tick_latency = 0, // ignored when reliable == 0 #ifdef NRF51 diff --git a/ports/nrf/modules/machine/spi.c b/ports/nrf/modules/machine/spi.c index f474c88715c..e3f72cba385 100644 --- a/ports/nrf/modules/machine/spi.c +++ b/ports/nrf/modules/machine/spi.c @@ -75,7 +75,7 @@ #define nrfx_spi_config_t nrfx_spim_config_t #define nrfx_spi_xfer_desc_t nrfx_spim_xfer_desc_t -#define NRFX_SPI_PIN_NOT_USED NRFX_SPIM_PIN_NOT_USED +#define NRFX_SPI_PIN_NOT_USED NRF_SPIM_PIN_NOT_CONNECTED #define NRFX_SPI_INSTANCE NRFX_SPIM_INSTANCE #define NRF_SPI_BIT_ORDER_LSB_FIRST NRF_SPIM_BIT_ORDER_LSB_FIRST #define NRF_SPI_BIT_ORDER_MSB_FIRST NRF_SPIM_BIT_ORDER_MSB_FIRST diff --git a/ports/nrf/modules/machine/timer.c b/ports/nrf/modules/machine/timer.c index 42a40ad2f26..48fefec99cd 100644 --- a/ports/nrf/modules/machine/timer.c +++ b/ports/nrf/modules/machine/timer.c @@ -143,7 +143,7 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, // shortcut) and channel 1 for capturing the current time. const nrfx_timer_config_t config = { - .frequency = NRF_TIMER_FREQ_1MHz, + .frequency = 1000000, .mode = NRF_TIMER_MODE_TIMER, .bit_width = NRF_TIMER_BIT_WIDTH_24, #ifdef NRF51 diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 4c75bf82098..e46ece7dfb4 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -64,7 +64,7 @@ typedef struct _machine_uart_buf_t { #define nrfx_uart_config_t nrfx_uarte_config_t #define nrfx_uart_rx nrfx_uarte_rx -#define nrfx_uart_tx nrfx_uarte_tx +#define nrfx_uart_tx(inst, buf, len) nrfx_uarte_tx(inst, buf, len, 0) #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 @@ -258,15 +258,24 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg machine_uart_obj_t *self = &machine_uart_obj[uart_id]; nrfx_uart_config_t config; + memset(&config, 0, sizeof(config)); // flow control + #if NRFX_UART_ENABLED #if MICROPY_HW_UART1_HWFC config.hal_cfg.hwfc = NRF_UART_HWFC_ENABLED; #else config.hal_cfg.hwfc = NRF_UART_HWFC_DISABLED; #endif - config.hal_cfg.parity = NRF_UART_PARITY_EXCLUDED; + #else + #if MICROPY_HW_UART1_HWFC + config.config.hwfc = NRF_UART_HWFC_ENABLED; + #else + config.config.hwfc = NRF_UART_HWFC_DISABLED; + #endif + config.config.parity = NRF_UART_PARITY_EXCLUDED; + #endif // Higher priority than pin interrupts, otherwise printing exceptions from // interrupt handlers gets stuck. @@ -288,12 +297,26 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg config.baudrate = args[ARG_baudrate].u_int / 400 * (uint32_t)(400ULL * (uint64_t)UINT32_MAX / 16000000ULL); config.baudrate = (config.baudrate + 0x800) & 0xffffff000; // rounding + #if NRFX_UART_ENABLED config.pseltxd = MICROPY_HW_UART1_TX; config.pselrxd = MICROPY_HW_UART1_RX; - #if MICROPY_HW_UART1_HWFC config.pselrts = MICROPY_HW_UART1_RTS; config.pselcts = MICROPY_HW_UART1_CTS; + #else + config.pselrts = NRF_UART_PSEL_DISCONNECTED; + config.pselcts = NRF_UART_PSEL_DISCONNECTED; + #endif + #else + config.txd_pin = MICROPY_HW_UART1_TX; + config.rxd_pin = MICROPY_HW_UART1_RX; + #if MICROPY_HW_UART1_HWFC + config.rts_pin = MICROPY_HW_UART1_RTS; + config.cts_pin = MICROPY_HW_UART1_CTS; + #else + config.rts_pin = NRF_UARTE_PSEL_DISCONNECTED; + config.cts_pin = NRF_UARTE_PSEL_DISCONNECTED; + #endif #endif self->timeout = args[ARG_timeout].u_int; self->timeout_char = args[ARG_timeout_char].u_int; From 77a9f3e6244babc771926af73d6257e11be39e63 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 07:35:01 +1000 Subject: [PATCH 2038/2098] nrf: Fix nRF9160 secureboot build for nrfx v3. Add fallback define for NRF_FPU_S which was removed from the nRF9160 device headers in nrfx v3. Signed-off-by: Andrew Leech --- .../nrf/drivers/secureboot/secureboot_main.c | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/ports/nrf/drivers/secureboot/secureboot_main.c b/ports/nrf/drivers/secureboot/secureboot_main.c index 8362852cd79..7870fc7ecc4 100644 --- a/ports/nrf/drivers/secureboot/secureboot_main.c +++ b/ports/nrf/drivers/secureboot/secureboot_main.c @@ -27,6 +27,12 @@ #include #include +// NRF_FPU_S was removed from the nRF9160 device headers in nrfx v3. +// Define it here using the known secure-domain base address. +#ifndef NRF_FPU_S +#define NRF_FPU_S ((NRF_FPU_Type *)0x5002C000UL) +#endif + // Secure flash 32K. #define SECURE_32K_FLASH_PAGE_START (0) #define SECURE_32K_FLASH_PAGE_END (0) @@ -53,19 +59,19 @@ static void configure_flash(void) { for (uint8_t i = SECURE_32K_FLASH_PAGE_START; i <= SECURE_32K_FLASH_PAGE_END; i++) { uint32_t perm = 0; perm |= (SPU_FLASHREGION_PERM_EXECUTE_Enable << SPU_FLASHREGION_PERM_EXECUTE_Pos); - perm |= (SPU_FLASHREGION_PERM_WRITE_Enable << SPU_FLASHREGION_PERM_WRITE_Pos); - perm |= (SPU_FLASHREGION_PERM_READ_Enable << SPU_FLASHREGION_PERM_READ_Pos); - perm |= (SPU_FLASHREGION_PERM_LOCK_Locked << SPU_FLASHREGION_PERM_LOCK_Pos); + perm |= (SPU_FLASHREGION_PERM_WRITE_Enable << SPU_FLASHREGION_PERM_WRITE_Pos); + perm |= (SPU_FLASHREGION_PERM_READ_Enable << SPU_FLASHREGION_PERM_READ_Pos); + perm |= (SPU_FLASHREGION_PERM_LOCK_Locked << SPU_FLASHREGION_PERM_LOCK_Pos); perm |= (SPU_FLASHREGION_PERM_SECATTR_Secure << SPU_FLASHREGION_PERM_SECATTR_Pos); NRF_SPU_S->FLASHREGION[i].PERM = perm; } for (uint8_t i = NONSECURE_32K_FLASH_PAGE_START; i <= NONSECURE_32K_FLASH_PAGE_END; i++) { uint32_t perm = 0; - perm |= (SPU_FLASHREGION_PERM_EXECUTE_Enable << SPU_FLASHREGION_PERM_EXECUTE_Pos); - perm |= (SPU_FLASHREGION_PERM_WRITE_Enable << SPU_FLASHREGION_PERM_WRITE_Pos); - perm |= (SPU_FLASHREGION_PERM_READ_Enable << SPU_FLASHREGION_PERM_READ_Pos); - perm |= (SPU_FLASHREGION_PERM_LOCK_Locked << SPU_FLASHREGION_PERM_LOCK_Pos); + perm |= (SPU_FLASHREGION_PERM_EXECUTE_Enable << SPU_FLASHREGION_PERM_EXECUTE_Pos); + perm |= (SPU_FLASHREGION_PERM_WRITE_Enable << SPU_FLASHREGION_PERM_WRITE_Pos); + perm |= (SPU_FLASHREGION_PERM_READ_Enable << SPU_FLASHREGION_PERM_READ_Pos); + perm |= (SPU_FLASHREGION_PERM_LOCK_Locked << SPU_FLASHREGION_PERM_LOCK_Pos); perm |= (SPU_FLASHREGION_PERM_SECATTR_Non_Secure << SPU_FLASHREGION_PERM_SECATTR_Pos); NRF_SPU_S->FLASHREGION[i].PERM = perm; } @@ -93,8 +99,7 @@ static void configure_ram(void) { } } -static void peripheral_setup(uint8_t peripheral_id) -{ +static void peripheral_setup(uint8_t peripheral_id) { NVIC_DisableIRQ(peripheral_id); uint32_t perm = 0; perm |= (SPU_PERIPHID_PERM_PRESENT_IsPresent << SPU_PERIPHID_PERM_PRESENT_Pos); @@ -105,8 +110,7 @@ static void peripheral_setup(uint8_t peripheral_id) NVIC_SetTargetState(peripheral_id); } -static void configure_peripherals(void) -{ +static void configure_peripherals(void) { NRF_SPU_S->GPIOPORT[0].PERM = 0; peripheral_setup(PERIPHERAL_ID_GET(NRF_REGULATORS_S)); peripheral_setup(PERIPHERAL_ID_GET(NRF_CLOCK_S)); @@ -143,13 +147,12 @@ static void configure_peripherals(void) typedef void __attribute__((cmse_nonsecure_call)) nsfunc(void); -static void jump_to_non_secure(void) -{ +static void jump_to_non_secure(void) { TZ_SAU_Disable(); SAU->CTRL |= SAU_CTRL_ALLNS_Msk; // Set NS vector table. - uint32_t * vtor_ns = (uint32_t *)0x8000; + uint32_t *vtor_ns = (uint32_t *)0x8000; SCB_NS->VTOR = (uint32_t)vtor_ns; // Allow for FPU to be used by NS. @@ -165,7 +168,7 @@ static void jump_to_non_secure(void) // Cast NS Reset_Handler to a non-secure function. nsfunc *fp = (nsfunc *)vtor_ns[1]; - fp = (nsfunc *)((intptr_t)(fp) & ~1); + fp = (nsfunc *)((intptr_t)(fp) & ~1); if (cmse_is_nsfptr(fp)) { __DSB(); @@ -184,6 +187,6 @@ void _start(void) { jump_to_non_secure(); while (1) { - ; + ; } } From 5a60aa2f00d7c1666c56d6cefc4633bc9cb9e7f6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 15:19:20 +1000 Subject: [PATCH 2039/2098] nrf: Modernize Pin.irq to use shared mp_irq infrastructure. Replace the nrf port's custom Pin.irq implementation with the shared mp_irq_obj_t pattern used by rp2 and alif ports. Adds support for the hard kwarg (soft IRQs via mp_sched_schedule when hard=False), makes handler optional, and returns a proper IRQ object with flags() and trigger() methods. Boards without MICROPY_ENABLE_SCHEDULER retain the direct ISR-only path. Signed-off-by: Andrew Leech --- ports/nrf/modules/machine/pin.c | 226 ++++++++++++++++++++++++-------- 1 file changed, 170 insertions(+), 56 deletions(-) diff --git a/ports/nrf/modules/machine/pin.c b/ports/nrf/modules/machine/pin.c index 7dbf88bab53..a3cbff44c43 100644 --- a/ports/nrf/modules/machine/pin.c +++ b/ports/nrf/modules/machine/pin.c @@ -34,6 +34,7 @@ #include "py/runtime.h" #include "py/mphal.h" #include "py/gc.h" +#include "shared/runtime/mpirq.h" #include "pin.h" #include "nrf_gpio.h" #include "nrfx_gpiote.h" @@ -50,6 +51,16 @@ static const nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(0); #define NUM_OF_PINS 32 #endif +#if MICROPY_ENABLE_SCHEDULER +typedef struct _machine_pin_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; +} machine_pin_irq_obj_t; + +static const mp_irq_methods_t machine_pin_irq_methods; +#endif + static uint8_t pin_gpiote_ch[NUM_OF_PINS]; #define PIN_GPIOTE_CH_NONE UINT8_MAX @@ -62,6 +73,54 @@ static void pin_gpiote_release(nrfx_gpiote_pin_t pin) { } } +static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t action, void *p_context); + +static mp_uint_t machine_pin_irq_trigger_set(nrfx_gpiote_pin_t pin, mp_uint_t new_trigger) { + pin_gpiote_release(pin); + + if (new_trigger) { + nrfx_gpiote_trigger_t trigger = NRFX_GPIOTE_TRIGGER_TOGGLE; + if (new_trigger == NRF_GPIOTE_POLARITY_LOTOHI) { + trigger = NRFX_GPIOTE_TRIGGER_LOTOHI; + } else if (new_trigger == NRF_GPIOTE_POLARITY_HITOLO) { + trigger = NRFX_GPIOTE_TRIGGER_HITOLO; + } + + uint8_t gpiote_ch; + nrfx_err_t err = nrfx_gpiote_channel_alloc(&gpiote_inst, &gpiote_ch); + + nrfx_gpiote_trigger_config_t trigger_config = { + .trigger = trigger, + .p_in_channel = (err == NRFX_SUCCESS) ? &gpiote_ch : NULL, + }; + nrfx_gpiote_handler_config_t handler_config = { + .handler = pin_common_irq_handler, + .p_context = NULL, + }; + nrfx_gpiote_input_pin_config_t input_config = { + .p_pull_config = NULL, + .p_trigger_config = &trigger_config, + .p_handler_config = &handler_config, + }; + + nrfx_err_t cfg_err = nrfx_gpiote_input_configure(&gpiote_inst, pin, &input_config); + if (cfg_err != NRFX_SUCCESS) { + if (err == NRFX_SUCCESS) { + nrfx_gpiote_channel_free(&gpiote_inst, gpiote_ch); + } + mp_raise_ValueError(MP_ERROR_TEXT("pin IRQ config failed")); + } + + if (err == NRFX_SUCCESS) { + pin_gpiote_ch[pin] = gpiote_ch; + } + + nrfx_gpiote_trigger_enable(&gpiote_inst, pin, true); + } + + return 0; +} + extern const pin_obj_t machine_board_pin_obj[]; extern const uint8_t machine_pin_num_of_board_pins; @@ -134,9 +193,13 @@ static bool pin_class_debug; void pin_init0(void) { MP_STATE_PORT(pin_class_mapper) = mp_const_none; MP_STATE_PORT(pin_class_map_dict) = mp_const_none; + #if MICROPY_ENABLE_SCHEDULER + memset(MP_STATE_PORT(machine_pin_irq_obj), 0, sizeof(MP_STATE_PORT(machine_pin_irq_obj))); + #else for (int i = 0; i < NUM_OF_PINS; i++) { MP_STATE_PORT(pin_irq_handlers)[i] = mp_const_none; } + #endif if (!nrfx_gpiote_init_check(&gpiote_inst)) { nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY); } else { @@ -519,6 +582,75 @@ static mp_obj_t pin_af(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_obj, pin_af); +#if MICROPY_ENABLE_SCHEDULER + +static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t action, void *p_context) { + (void)p_context; + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[pin]); + if (irq != NULL) { + irq->flags = action; + mp_irq_handler(&irq->base); + } +} + +static machine_pin_irq_obj_t *machine_pin_get_irq(nrfx_gpiote_pin_t pin) { + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[pin]); + if (irq == NULL) { + irq = m_new_obj(machine_pin_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; + mp_obj_t pin_number = MP_OBJ_NEW_SMALL_INT(pin); + irq->base.parent = (mp_obj_t)pin_find(pin_number); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + irq->flags = 0; + irq->trigger = 0; + MP_STATE_PORT(machine_pin_irq_obj[pin]) = irq; + } + return irq; +} + +static mp_obj_t pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = NRF_GPIOTE_POLARITY_LOTOHI | NRF_GPIOTE_POLARITY_HITOLO} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + machine_pin_irq_obj_t *irq = machine_pin_get_irq(self->pin); + + if (n_args > 1 || kw_args->used != 0) { + mp_obj_t handler = args[ARG_handler].u_obj; + mp_uint_t trigger = args[ARG_trigger].u_int; + bool hard = args[ARG_hard].u_bool; + + irq->base.handler = handler; + irq->base.ishard = hard; + irq->flags = 0; + irq->trigger = trigger; + + if (handler != mp_const_none) { + machine_pin_irq_trigger_set(self->pin, trigger); + } else { + machine_pin_irq_trigger_set(self->pin, 0); + } + } + + return MP_OBJ_FROM_PTR(irq); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(pin_irq_obj, 1, pin_irq); + +#else // !MICROPY_ENABLE_SCHEDULER +// Note: this path is dead code on the nrf port (MICROPY_ENABLE_SCHEDULER is +// always 1). It is retained for custom board configurations that may disable +// the scheduler. Limitations vs the scheduler-enabled path: +// - The "hard" parameter is accepted but ignored; handler always runs in ISR. +// - Returns mp_const_none instead of an mp_irq_obj_t. + static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t action, void *p_context) { (void)p_context; mp_obj_t pin_handler = MP_STATE_PORT(pin_irq_handlers)[pin]; @@ -526,92 +658,44 @@ static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t const pin_obj_t *pin_obj = pin_find(pin_number); if (pin_handler != mp_const_none) { - #if MICROPY_ENABLE_SCHEDULER - mp_sched_lock(); - #endif - // When executing code within a handler we must lock the GC to prevent - // any memory allocations. We must also catch any exceptions. gc_lock(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_call_function_1(pin_handler, (mp_obj_t)pin_obj); nlr_pop(); } else { - // Uncaught exception; disable the callback so it doesn't run again. MP_STATE_PORT(pin_irq_handlers)[pin] = mp_const_none; pin_gpiote_release(pin); mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in interrupt handler for Pin('%q')\n", pin_obj->name); mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } gc_unlock(); - #if MICROPY_ENABLE_SCHEDULER - mp_sched_unlock(); - #endif } } static mp_obj_t pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum {ARG_handler, ARG_trigger, ARG_wake}; + enum { ARG_handler, ARG_trigger, ARG_hard }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_handler, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none} }, - { MP_QSTR_trigger, MP_ARG_INT, {.u_int = NRF_GPIOTE_POLARITY_LOTOHI | NRF_GPIOTE_POLARITY_HITOLO} }, - { MP_QSTR_wake, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_handler, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = NRF_GPIOTE_POLARITY_LOTOHI | NRF_GPIOTE_POLARITY_HITOLO} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, }; pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); nrfx_gpiote_pin_t pin = self->pin; - - pin_gpiote_release(pin); - - if (args[ARG_handler].u_obj != mp_const_none) { - nrfx_gpiote_trigger_t trigger = NRFX_GPIOTE_TRIGGER_TOGGLE; - if (args[ARG_trigger].u_int == NRF_GPIOTE_POLARITY_LOTOHI) { - trigger = NRFX_GPIOTE_TRIGGER_LOTOHI; - } else if (args[ARG_trigger].u_int == NRF_GPIOTE_POLARITY_HITOLO) { - trigger = NRFX_GPIOTE_TRIGGER_HITOLO; - } - - uint8_t gpiote_ch; - nrfx_err_t err = nrfx_gpiote_channel_alloc(&gpiote_inst, &gpiote_ch); - - nrfx_gpiote_trigger_config_t trigger_config = { - .trigger = trigger, - .p_in_channel = (err == NRFX_SUCCESS) ? &gpiote_ch : NULL, - }; - nrfx_gpiote_handler_config_t handler_config = { - .handler = pin_common_irq_handler, - .p_context = NULL, - }; - nrfx_gpiote_input_pin_config_t input_config = { - .p_pull_config = NULL, - .p_trigger_config = &trigger_config, - .p_handler_config = &handler_config, - }; - - nrfx_err_t cfg_err = nrfx_gpiote_input_configure(&gpiote_inst, pin, &input_config); - if (cfg_err != NRFX_SUCCESS) { - if (err == NRFX_SUCCESS) { - nrfx_gpiote_channel_free(&gpiote_inst, gpiote_ch); - } - mp_raise_ValueError(MP_ERROR_TEXT("pin IRQ config failed")); - } - - if (err == NRFX_SUCCESS) { - pin_gpiote_ch[pin] = gpiote_ch; - } - - nrfx_gpiote_trigger_enable(&gpiote_inst, pin, true); - } + mp_uint_t trigger = args[ARG_handler].u_obj != mp_const_none ? args[ARG_trigger].u_int : 0; + machine_pin_irq_trigger_set(pin, trigger); MP_STATE_PORT(pin_irq_handlers)[pin] = args[ARG_handler].u_obj; - // return the irq object return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(pin_irq_obj, 1, pin_irq); +#endif // MICROPY_ENABLE_SCHEDULER + static const mp_rom_map_elem_t pin_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pin_init_obj) }, @@ -754,6 +838,36 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &pin_af_locals_dict ); +#if MICROPY_ENABLE_SCHEDULER +static mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[self->pin]); + irq->flags = 0; + irq->trigger = new_trigger; + return machine_pin_irq_trigger_set(self->pin, new_trigger); +} + +static mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[self->pin]); + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +static const mp_irq_methods_t machine_pin_irq_methods = { + .trigger = machine_pin_irq_trigger, + .info = machine_pin_irq_info, +}; +#endif + MP_REGISTER_ROOT_POINTER(mp_obj_t pin_class_mapper); MP_REGISTER_ROOT_POINTER(mp_obj_t pin_class_map_dict); +#if MICROPY_ENABLE_SCHEDULER +MP_REGISTER_ROOT_POINTER(void *machine_pin_irq_obj[NUM_OF_PINS]); +#else MP_REGISTER_ROOT_POINTER(mp_obj_t pin_irq_handlers[NUM_OF_PINS]); +#endif From 080e2ed1626f5d8d10a72b38d6cbcf7aa9deeb51 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 15:19:28 +1000 Subject: [PATCH 2040/2098] nrf: Preserve SPI config across init calls. Use -1 sentinels for SPI init parameters so that calling spi.init(baudrate=X) no longer resets polarity, phase, and other settings to defaults. Matches the rp2 port behavior. Signed-off-by: Andrew Leech --- ports/nrf/modules/machine/spi.c | 126 ++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 54 deletions(-) diff --git a/ports/nrf/modules/machine/spi.c b/ports/nrf/modules/machine/spi.c index e3f72cba385..9ed24bd9b90 100644 --- a/ports/nrf/modules/machine/spi.c +++ b/ports/nrf/modules/machine/spi.c @@ -188,6 +188,9 @@ enum { }; static inline uint32_t machine_hard_spi_get_baudrate(const machine_hard_spi_obj_t *self) { + #if NRFX_SPIM_ENABLED + return self->p_config->frequency; + #else MP_STATIC_ASSERT(NRF_SPI_FREQ_125K == (2 << 24)); MP_STATIC_ASSERT(NRF_SPI_FREQ_250K == (4 << 24)); MP_STATIC_ASSERT(NRF_SPI_FREQ_500K == (8 << 24)); @@ -195,15 +198,8 @@ static inline uint32_t machine_hard_spi_get_baudrate(const machine_hard_spi_obj_ MP_STATIC_ASSERT(NRF_SPI_FREQ_2M == (32 << 24)); MP_STATIC_ASSERT(NRF_SPI_FREQ_4M == (64 << 24)); MP_STATIC_ASSERT(NRF_SPI_FREQ_8M == (128 << 24)); - #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED - if (self->p_config->frequency == NRF_SPIM_FREQ_16M) { - return 16000000; - } - if (self->p_config->frequency == NRF_SPIM_FREQ_32M) { - return 32000000; - } - #endif return 125000 * (self->p_config->frequency >> 25); + #endif } static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_arg_val_t *args); @@ -266,54 +262,76 @@ static mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_ar } static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_arg_val_t *args) { - int baudrate = args[ARG_INIT_baudrate].u_int; - - if (baudrate <= 125000) { - self->p_config->frequency = NRF_SPI_FREQ_125K; - } else if (baudrate <= 250000) { - self->p_config->frequency = NRF_SPI_FREQ_250K; - } else if (baudrate <= 500000) { - self->p_config->frequency = NRF_SPI_FREQ_500K; - } else if (baudrate <= 1000000) { - self->p_config->frequency = NRF_SPI_FREQ_1M; - } else if (baudrate <= 2000000) { - self->p_config->frequency = NRF_SPI_FREQ_2M; - } else if (baudrate <= 4000000) { - self->p_config->frequency = NRF_SPI_FREQ_4M; - } else if (baudrate <= 8000000) { - self->p_config->frequency = NRF_SPI_FREQ_8M; - #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED - } else if (baudrate <= 16000000) { - self->p_config->frequency = NRF_SPIM_FREQ_16M; - } else if (baudrate <= 32000000) { - self->p_config->frequency = NRF_SPIM_FREQ_32M; - #endif // NRF52840_XXAA && NRFX_SPIM_ENABLED - } else { // Default - self->p_config->frequency = NRF_SPI_FREQ_1M; + if (args[ARG_INIT_baudrate].u_int != -1) { + int baudrate = args[ARG_INIT_baudrate].u_int; + #if NRFX_SPIM_ENABLED + // nrfx v3 SPIM takes frequency in Hz but only supports specific values. + if (baudrate <= 125000) { + self->p_config->frequency = 125000; + } else if (baudrate <= 250000) { + self->p_config->frequency = 250000; + } else if (baudrate <= 500000) { + self->p_config->frequency = 500000; + } else if (baudrate <= 1000000) { + self->p_config->frequency = 1000000; + } else if (baudrate <= 2000000) { + self->p_config->frequency = 2000000; + } else if (baudrate <= 4000000) { + self->p_config->frequency = 4000000; + } else if (baudrate <= 8000000) { + self->p_config->frequency = 8000000; + #if NRF_SPIM_HAS_16_MHZ_FREQ + } else if (baudrate <= 16000000) { + self->p_config->frequency = 16000000; + #endif + #if NRF_SPIM_HAS_32_MHZ_FREQ + } else if (baudrate <= 32000000) { + self->p_config->frequency = 32000000; + #endif + } else { + self->p_config->frequency = 1000000; + } + #else + if (baudrate <= 125000) { + self->p_config->frequency = NRF_SPI_FREQ_125K; + } else if (baudrate <= 250000) { + self->p_config->frequency = NRF_SPI_FREQ_250K; + } else if (baudrate <= 500000) { + self->p_config->frequency = NRF_SPI_FREQ_500K; + } else if (baudrate <= 1000000) { + self->p_config->frequency = NRF_SPI_FREQ_1M; + } else if (baudrate <= 2000000) { + self->p_config->frequency = NRF_SPI_FREQ_2M; + } else if (baudrate <= 4000000) { + self->p_config->frequency = NRF_SPI_FREQ_4M; + } else if (baudrate <= 8000000) { + self->p_config->frequency = NRF_SPI_FREQ_8M; + } else { + self->p_config->frequency = NRF_SPI_FREQ_1M; + } + #endif } - if (args[ARG_INIT_polarity].u_int == 0) { - // Active high - if (args[ARG_INIT_phase].u_int == 0) { - // First clock edge - self->p_config->mode = NRF_SPI_MODE_0; - } else { - // Second clock edge - self->p_config->mode = NRF_SPI_MODE_1; + int polarity = args[ARG_INIT_polarity].u_int; + int phase = args[ARG_INIT_phase].u_int; + if (polarity != -1 || phase != -1) { + if (polarity == -1) { + polarity = (self->p_config->mode == NRF_SPI_MODE_2 || self->p_config->mode == NRF_SPI_MODE_3) ? 1 : 0; } - } else { - // Active low - if (args[ARG_INIT_phase].u_int == 0) { - // First clock edge - self->p_config->mode = NRF_SPI_MODE_2; + if (phase == -1) { + phase = (self->p_config->mode == NRF_SPI_MODE_1 || self->p_config->mode == NRF_SPI_MODE_3) ? 1 : 0; + } + if (polarity == 0) { + self->p_config->mode = (phase == 0) ? NRF_SPI_MODE_0 : NRF_SPI_MODE_1; } else { - // Second clock edge - self->p_config->mode = NRF_SPI_MODE_3; + self->p_config->mode = (phase == 0) ? NRF_SPI_MODE_2 : NRF_SPI_MODE_3; } } - self->p_config->orc = 0xFF; // Overrun character - self->p_config->bit_order = (args[ARG_INIT_firstbit].u_int == 0) ? NRF_SPI_BIT_ORDER_MSB_FIRST : NRF_SPI_BIT_ORDER_LSB_FIRST; + self->p_config->orc = 0xFF; + if (args[ARG_INIT_firstbit].u_int != -1) { + self->p_config->bit_order = (args[ARG_INIT_firstbit].u_int == 0) ? NRF_SPI_BIT_ORDER_MSB_FIRST : NRF_SPI_BIT_ORDER_LSB_FIRST; + } // Set context to this instance of SPI nrfx_err_t err_code = nrfx_spi_init(self->p_spi, self->p_config, NULL, (void *)self); @@ -328,11 +346,11 @@ static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_ static void machine_hard_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { - { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000000} }, - { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, - { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; // parse args From ac0af105a9d770ff667aa14595ab07d37dafad33 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Apr 2026 15:19:36 +1000 Subject: [PATCH 2041/2098] nrf: Add I2C timeout parameter and fix disable on error. Accept the timeout kwarg (default 50ms) for compatibility with rp2 and alif ports. Also fix TWI peripheral not being disabled on the transfer error path. Signed-off-by: Andrew Leech --- ports/nrf/modules/machine/i2c.c | 59 +++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index ced53346414..e2b7a19a27d 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -55,11 +55,20 @@ #define nrfx_twi_xfer_desc_t nrfx_twim_xfer_desc_t +#define nrfx_twi_evt_handler_t nrfx_twim_evt_handler_t +#define nrfx_twi_evt_t nrfx_twim_evt_t +#define nrfx_twi_evt_type_t nrfx_twim_evt_type_t + #define NRFX_TWI_XFER_DESC_RX NRFX_TWIM_XFER_DESC_RX #define NRFX_TWI_XFER_DESC_TX NRFX_TWIM_XFER_DESC_TX #define NRFX_TWI_INSTANCE NRFX_TWIM_INSTANCE +#define NRFX_TWI_EVT_DONE NRFX_TWIM_EVT_DONE +#define NRFX_TWI_EVT_ADDRESS_NACK NRFX_TWIM_EVT_ADDRESS_NACK +#define NRFX_TWI_EVT_DATA_NACK NRFX_TWIM_EVT_DATA_NACK +#define NRFX_TWI_EVT_BUS_ERROR NRFX_TWIM_EVT_BUS_ERROR + #define NRF_TWI_FREQ_100K NRF_TWIM_FREQ_100K #define NRF_TWI_FREQ_250K NRF_TWIM_FREQ_250K #define NRF_TWI_FREQ_400K NRF_TWIM_FREQ_400K @@ -69,9 +78,12 @@ typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; nrfx_twi_t p_twi; // Driver instance + uint32_t timeout; + volatile bool xfer_done; + volatile nrfx_twi_evt_type_t xfer_evt; } machine_hard_i2c_obj_t; -static const machine_hard_i2c_obj_t machine_hard_i2c_obj[] = { +static machine_hard_i2c_obj_t machine_hard_i2c_obj[] = { {{&machine_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(0)}, {{&machine_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(1)}, }; @@ -79,6 +91,12 @@ static const machine_hard_i2c_obj_t machine_hard_i2c_obj[] = { void i2c_init0(void) { } +static void twi_event_handler(nrfx_twi_evt_t const *p_event, void *p_context) { + machine_hard_i2c_obj_t *self = (machine_hard_i2c_obj_t *)p_context; + self->xfer_evt = p_event->type; + self->xfer_done = true; +} + static int i2c_find(mp_obj_t id) { // given an integer id int i2c_id = mp_obj_get_int(id); @@ -90,7 +108,7 @@ static int i2c_find(mp_obj_t id) { static void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hard_i2c_obj_t *self = self_in; - mp_printf(print, "I2C(%u)", self->p_twi.drv_inst_idx); + mp_printf(print, "I2C(%u, timeout=%u)", self->p_twi.drv_inst_idx, self->timeout); } /******************************************************************************/ @@ -99,12 +117,13 @@ static void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); - enum { ARG_id, ARG_scl, ARG_sda, ARG_freq }; + enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 50000} }, }; // parse args @@ -113,7 +132,9 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz // get static peripheral object int i2c_id = i2c_find(args[ARG_id].u_obj); - const machine_hard_i2c_obj_t *self = &machine_hard_i2c_obj[i2c_id]; + machine_hard_i2c_obj_t *self = &machine_hard_i2c_obj[i2c_id]; + + self->timeout = args[ARG_timeout].u_int; nrfx_twi_config_t config; memset(&config, 0, sizeof(config)); @@ -140,8 +161,8 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz // First reset the TWI nrfx_twi_uninit(&self->p_twi); - // Set context to this object. - nrfx_twi_init(&self->p_twi, &config, NULL, (void *)self); + // Set context to this object, use non-blocking mode with event handler. + nrfx_twi_init(&self->p_twi, &config, twi_event_handler, (void *)self); return MP_OBJ_FROM_PTR(self); } @@ -151,6 +172,9 @@ int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size nrfx_twi_enable(&self->p_twi); + self->xfer_done = false; + self->xfer_evt = NRFX_TWI_EVT_DONE; + nrfx_err_t err_code; int transfer_ret = 0; if (flags & MP_MACHINE_I2C_FLAG_READ) { @@ -162,7 +186,10 @@ int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size transfer_ret = len; } + // In non-blocking mode, ANACK/DNACK are delivered via the event handler. + // These checks handle transfer start failures (e.g. bus busy). if (err_code != NRFX_SUCCESS) { + nrfx_twi_disable(&self->p_twi); if (err_code == NRFX_ERROR_DRV_TWI_ERR_ANACK) { return -MP_ENODEV; } else if (err_code == NRFX_ERROR_DRV_TWI_ERR_DNACK) { @@ -171,8 +198,28 @@ int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size return -MP_ETIMEDOUT; } + // Poll for transfer completion with timeout (timeout=0 means no timeout, + // the loop relies on MICROPY_EVENT_POLL_HOOK for Ctrl-C). + mp_uint_t start = mp_hal_ticks_us(); + while (!self->xfer_done) { + if (self->timeout > 0 && (mp_hal_ticks_us() - start) >= self->timeout) { + nrfx_twi_disable(&self->p_twi); + nrfx_twi_enable(&self->p_twi); + return -MP_ETIMEDOUT; + } + MICROPY_EVENT_POLL_HOOK; + } + nrfx_twi_disable(&self->p_twi); + if (self->xfer_evt == NRFX_TWI_EVT_ADDRESS_NACK) { + return -MP_ENODEV; + } else if (self->xfer_evt == NRFX_TWI_EVT_DATA_NACK) { + return -MP_EIO; + } else if (self->xfer_evt != NRFX_TWI_EVT_DONE) { + return -MP_EIO; + } + return transfer_ret; } From 7d3cf417d1c54a142705be088a32e9d84519ce7f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 22 Apr 2026 14:57:05 +1000 Subject: [PATCH 2042/2098] nrf/Makefile: Update nrfutil deploy rules for latest nrfutil. The old `nrfutil` runs only under CPython 2.x and has been replaced by a new stand-alone `nrfutil` executable, available directly from Nordic. Update the Makefile to work with this new version, and also update the README to describe how to install it. Signed-off-by: Damien George --- ports/nrf/Makefile | 8 ++++---- ports/nrf/README.md | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 546c17709e5..e39bf9639b8 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -449,12 +449,12 @@ sd: nrfutil_dfu_sd nrfutil_dfu_deploy nrfutil_dfu_sd: $(BUILD)/$(OUTPUT_FILENAME).hex $(Q)hexmerge.py -o $(BUILD)/stripped_sd.hex --range=0x1000: $(SOFTDEV_HEX) - $(Q)nrfutil pkg generate --hw-version 52 --sd-req 0x00 --sd-id 0x00 --softdevice $(BUILD)/stripped_sd.hex $(BUILD)/stripped_sd.zip - $(Q)nrfutil dfu usb-serial -pkg $(BUILD)/stripped_sd.zip -p $(NRFUTIL_PORT) -t 0 + $(Q)nrfutil nrf5sdk-tools pkg generate --hw-version 52 --sd-req 0x00 --sd-id 0x00 --softdevice $(BUILD)/stripped_sd.hex $(BUILD)/stripped_sd.zip + $(Q)nrfutil nrf5sdk-tools dfu usb-serial -pkg $(BUILD)/stripped_sd.zip -p $(NRFUTIL_PORT) -t 0 nrfutil_dfu_deploy: $(BUILD)/$(OUTPUT_FILENAME).hex - $(Q)nrfutil pkg generate --hw-version 52 --sd-req $(NRFUTIL_SD_REQ) --application-version 1 --application $(BUILD)/$(OUTPUT_FILENAME).hex $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip - $(Q)nrfutil dfu usb-serial -pkg $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip -p $(NRFUTIL_PORT) -t 0 + $(Q)nrfutil nrf5sdk-tools pkg generate --hw-version 52 --sd-req $(NRFUTIL_SD_REQ) --application-version 1 --application $(BUILD)/$(OUTPUT_FILENAME).hex $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip + $(Q)nrfutil nrf5sdk-tools dfu usb-serial -pkg $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip -p $(NRFUTIL_PORT) -t 0 deploy: nrfutil_dfu_deploy diff --git a/ports/nrf/README.md b/ports/nrf/README.md index 889ead5d4d7..b65683712c7 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -207,9 +207,15 @@ for more tips about using the BMP with GDB. ## nRFUtil Targets -Install the necessary Python packages that will be used for flashing using the bootloader: +First install the base `nrfutil` CLI tool from +[Nordic Semiconductor](https://www.nordicsemi.com/Products/Development-tools/nRF-Util). +Then install the SDK sub-package of that tool in order to get access to the `pkg` +and `dfu` commands: + + nrfutil install nrf5sdk-tools + +Then install the necessary Python packages: - sudo pip install nrfutil sudo pip install intelhex The `intelhex` provides the `hexmerge.py` utility which is used by the Makefile From 18e83d19c9b8847e9754518d5dbf6f6cfb80aa14 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 22 Apr 2026 15:03:24 +1000 Subject: [PATCH 2043/2098] nrf/boards: Change all boards to use "nrf" for sys.platform value. The `sys.platform` variable on bare-metal ports is uniformly the port name (except for stm32 which uses "pyboard" for `sys.platform`), regardless of the board. Change the nrf port to follow this convention. Among other things, this gets the test auto-detection working properly on nrf boards, which relies on the value of `sys.platform` (eg for target wiring selection). Signed-off-by: Damien George --- ports/nrf/boards/ACTINIUS_ICARUS/mpconfigboard.h | 1 - ports/nrf/boards/ARDUINO_PRIMO/mpconfigboard.h | 1 - ports/nrf/boards/BLUEIO_TAG_EVIM/mpconfigboard.h | 1 - ports/nrf/boards/DVK_BL652/mpconfigboard.h | 1 - ports/nrf/boards/EVK_NINA_B1/mpconfigboard.h | 1 - ports/nrf/boards/EVK_NINA_B3/mpconfigboard.h | 1 - ports/nrf/boards/FEATHER52/mpconfigboard.h | 1 - ports/nrf/boards/IBK_BLYST_NANO/mpconfigboard.h | 1 - ports/nrf/boards/IDK_BLYST_NANO/mpconfigboard.h | 1 - ports/nrf/boards/MICROBIT/mpconfigboard.h | 1 - ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h | 1 - ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h | 1 - ports/nrf/boards/PCA10000/mpconfigboard.h | 1 - ports/nrf/boards/PCA10001/mpconfigboard.h | 1 - ports/nrf/boards/PCA10028/mpconfigboard.h | 1 - ports/nrf/boards/PCA10031/mpconfigboard.h | 1 - ports/nrf/boards/PCA10040/mpconfigboard.h | 1 - ports/nrf/boards/PCA10056/mpconfigboard.h | 1 - ports/nrf/boards/PCA10059/mpconfigboard.h | 1 - ports/nrf/boards/PCA10090/mpconfigboard.h | 1 - ports/nrf/boards/SEEED_XIAO_NRF52/mpconfigboard.h | 1 - ports/nrf/boards/WT51822_S4AT/mpconfigboard.h | 1 - ports/nrf/mpconfigport.h | 2 -- 23 files changed, 24 deletions(-) diff --git a/ports/nrf/boards/ACTINIUS_ICARUS/mpconfigboard.h b/ports/nrf/boards/ACTINIUS_ICARUS/mpconfigboard.h index 87e9f8ed2e6..2d91456299f 100644 --- a/ports/nrf/boards/ACTINIUS_ICARUS/mpconfigboard.h +++ b/ports/nrf/boards/ACTINIUS_ICARUS/mpconfigboard.h @@ -28,7 +28,6 @@ #define MICROPY_HW_BOARD_NAME "Actinius Icarus" #define MICROPY_HW_MCU_NAME "NRF9160" -#define MICROPY_PY_SYS_PLATFORM "nrf9160" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_RTCOUNTER (1) diff --git a/ports/nrf/boards/ARDUINO_PRIMO/mpconfigboard.h b/ports/nrf/boards/ARDUINO_PRIMO/mpconfigboard.h index 6b1b4e0074c..d199c44fe3b 100644 --- a/ports/nrf/boards/ARDUINO_PRIMO/mpconfigboard.h +++ b/ports/nrf/boards/ARDUINO_PRIMO/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "Arduino Primo" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "nrf52" #define MICROPY_PY_MACHINE_SOFT_PWM (1) #define MICROPY_PY_MUSIC (1) diff --git a/ports/nrf/boards/BLUEIO_TAG_EVIM/mpconfigboard.h b/ports/nrf/boards/BLUEIO_TAG_EVIM/mpconfigboard.h index 417b49fce61..b667043d359 100644 --- a/ports/nrf/boards/BLUEIO_TAG_EVIM/mpconfigboard.h +++ b/ports/nrf/boards/BLUEIO_TAG_EVIM/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "BLUEIO-TAG-EVIM" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "BLYST Nano" #define MICROPY_PY_MACHINE_SOFT_PWM (1) #define MICROPY_PY_MUSIC (1) diff --git a/ports/nrf/boards/DVK_BL652/mpconfigboard.h b/ports/nrf/boards/DVK_BL652/mpconfigboard.h index fe77fe19574..7193bb9cb3e 100644 --- a/ports/nrf/boards/DVK_BL652/mpconfigboard.h +++ b/ports/nrf/boards/DVK_BL652/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "DVK-BL652" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "bl652" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/EVK_NINA_B1/mpconfigboard.h b/ports/nrf/boards/EVK_NINA_B1/mpconfigboard.h index dd64ffb8b7f..f9a3be59337 100644 --- a/ports/nrf/boards/EVK_NINA_B1/mpconfigboard.h +++ b/ports/nrf/boards/EVK_NINA_B1/mpconfigboard.h @@ -28,7 +28,6 @@ // https://www.u-blox.com/sites/default/files/EVK-NINA-B1_UserGuide_%28UBX-15028120%29.pdf #define MICROPY_HW_BOARD_NAME "EVK_NINA_B1" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "nrf52" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/EVK_NINA_B3/mpconfigboard.h b/ports/nrf/boards/EVK_NINA_B3/mpconfigboard.h index 98bd71eec87..d50250ed1f8 100644 --- a/ports/nrf/boards/EVK_NINA_B3/mpconfigboard.h +++ b/ports/nrf/boards/EVK_NINA_B3/mpconfigboard.h @@ -34,7 +34,6 @@ // Board data #define MICROPY_HW_BOARD_NAME "EVK_NINA_B3" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "nrf52" // Enable @viper and @native #define MICROPY_EMIT_THUMB (1) diff --git a/ports/nrf/boards/FEATHER52/mpconfigboard.h b/ports/nrf/boards/FEATHER52/mpconfigboard.h index 7dcc7357a69..1207c9b06c2 100644 --- a/ports/nrf/boards/FEATHER52/mpconfigboard.h +++ b/ports/nrf/boards/FEATHER52/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "Bluefruit nRF52 Feather" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "nrf52" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/IBK_BLYST_NANO/mpconfigboard.h b/ports/nrf/boards/IBK_BLYST_NANO/mpconfigboard.h index 6d1c5364b53..d5121391c22 100644 --- a/ports/nrf/boards/IBK_BLYST_NANO/mpconfigboard.h +++ b/ports/nrf/boards/IBK_BLYST_NANO/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "IBK-BLYST-NANO" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "BLYST Nano" #define MICROPY_PY_MACHINE_SOFT_PWM (1) #define MICROPY_PY_MUSIC (1) diff --git a/ports/nrf/boards/IDK_BLYST_NANO/mpconfigboard.h b/ports/nrf/boards/IDK_BLYST_NANO/mpconfigboard.h index aacb49e7150..1f614fa4712 100644 --- a/ports/nrf/boards/IDK_BLYST_NANO/mpconfigboard.h +++ b/ports/nrf/boards/IDK_BLYST_NANO/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "IDK-BLYST-NANO" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "BLYST Nano" #define MICROPY_PY_MACHINE_SOFT_PWM (1) #define MICROPY_PY_MUSIC (1) diff --git a/ports/nrf/boards/MICROBIT/mpconfigboard.h b/ports/nrf/boards/MICROBIT/mpconfigboard.h index 3f9f873ff7d..1306aa4e361 100644 --- a/ports/nrf/boards/MICROBIT/mpconfigboard.h +++ b/ports/nrf/boards/MICROBIT/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "micro:bit" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MUSIC (1) diff --git a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h index 499abbc4efc..dc155a7ff2a 100644 --- a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h +++ b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "MDK-USB-DONGLE" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "nrf52840-MDK-USB-Dongle" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h index 35b70d61290..7d2c1c0db81 100644 --- a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h +++ b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "XENON" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "PARTICLE-XENON" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/PCA10000/mpconfigboard.h b/ports/nrf/boards/PCA10000/mpconfigboard.h index a450c3c36e4..630515d3da2 100644 --- a/ports/nrf/boards/PCA10000/mpconfigboard.h +++ b/ports/nrf/boards/PCA10000/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10000" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51-dongle" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_SOFT_PWM (1) diff --git a/ports/nrf/boards/PCA10001/mpconfigboard.h b/ports/nrf/boards/PCA10001/mpconfigboard.h index 929189188ec..47a8c5e94e7 100644 --- a/ports/nrf/boards/PCA10001/mpconfigboard.h +++ b/ports/nrf/boards/PCA10001/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10001" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51-DK" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_SOFT_PWM (1) diff --git a/ports/nrf/boards/PCA10028/mpconfigboard.h b/ports/nrf/boards/PCA10028/mpconfigboard.h index df2e4e85d93..b05a2a477d4 100644 --- a/ports/nrf/boards/PCA10028/mpconfigboard.h +++ b/ports/nrf/boards/PCA10028/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10028" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51-DK" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_SOFT_PWM (1) diff --git a/ports/nrf/boards/PCA10031/mpconfigboard.h b/ports/nrf/boards/PCA10031/mpconfigboard.h index e74e83c663e..02add73b2cc 100644 --- a/ports/nrf/boards/PCA10031/mpconfigboard.h +++ b/ports/nrf/boards/PCA10031/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10031" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51-dongle" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_SOFT_PWM (1) diff --git a/ports/nrf/boards/PCA10040/mpconfigboard.h b/ports/nrf/boards/PCA10040/mpconfigboard.h index 2b1c4c7ef5c..7f8064fb99d 100644 --- a/ports/nrf/boards/PCA10040/mpconfigboard.h +++ b/ports/nrf/boards/PCA10040/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10040" #define MICROPY_HW_MCU_NAME "NRF52832" -#define MICROPY_PY_SYS_PLATFORM "nrf52-DK" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/PCA10056/mpconfigboard.h b/ports/nrf/boards/PCA10056/mpconfigboard.h index b3ad76a7f44..7923f343582 100644 --- a/ports/nrf/boards/PCA10056/mpconfigboard.h +++ b/ports/nrf/boards/PCA10056/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10056" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "nrf52840-PDK" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/PCA10059/mpconfigboard.h b/ports/nrf/boards/PCA10059/mpconfigboard.h index 97f6ccb9410..95494a963e2 100644 --- a/ports/nrf/boards/PCA10059/mpconfigboard.h +++ b/ports/nrf/boards/PCA10059/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10059" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "nrf52840-Dongle" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_HW_PWM (1) diff --git a/ports/nrf/boards/PCA10090/mpconfigboard.h b/ports/nrf/boards/PCA10090/mpconfigboard.h index 1989fd04764..ea25720a70d 100644 --- a/ports/nrf/boards/PCA10090/mpconfigboard.h +++ b/ports/nrf/boards/PCA10090/mpconfigboard.h @@ -28,7 +28,6 @@ #define MICROPY_HW_BOARD_NAME "PCA10090" #define MICROPY_HW_MCU_NAME "NRF9160" -#define MICROPY_PY_SYS_PLATFORM "nrf9160-DK" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_TIMER_NRF (0) diff --git a/ports/nrf/boards/SEEED_XIAO_NRF52/mpconfigboard.h b/ports/nrf/boards/SEEED_XIAO_NRF52/mpconfigboard.h index 1faa256d80b..ca90cd0b13c 100644 --- a/ports/nrf/boards/SEEED_XIAO_NRF52/mpconfigboard.h +++ b/ports/nrf/boards/SEEED_XIAO_NRF52/mpconfigboard.h @@ -26,7 +26,6 @@ #define MICROPY_HW_BOARD_NAME "XIAO nRF52840 Sense" #define MICROPY_HW_MCU_NAME "NRF52840" -#define MICROPY_PY_SYS_PLATFORM "nrf52" #define MICROPY_BOARD_EARLY_INIT XIAO_board_early_init #define MICROPY_BOARD_DEINIT XIAO_board_deinit diff --git a/ports/nrf/boards/WT51822_S4AT/mpconfigboard.h b/ports/nrf/boards/WT51822_S4AT/mpconfigboard.h index e9865cb4d66..0419ed72e0d 100644 --- a/ports/nrf/boards/WT51822_S4AT/mpconfigboard.h +++ b/ports/nrf/boards/WT51822_S4AT/mpconfigboard.h @@ -28,7 +28,6 @@ // https://4tronix.co.uk/picobot2/WT51822-S4AT.pdf #define MICROPY_HW_BOARD_NAME "WT51822-S4AT" #define MICROPY_HW_MCU_NAME "NRF51822" -#define MICROPY_PY_SYS_PLATFORM "nrf51" #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_RTCOUNTER (1) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index cad839d0e90..946c7f42d70 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -87,9 +87,7 @@ #define MICROPY_PY_ARRAY_SLICE_ASSIGN (CORE_FEAT) #endif -#ifndef MICROPY_PY_SYS_PLATFORM #define MICROPY_PY_SYS_PLATFORM "nrf" -#endif #ifndef MICROPY_PY_SYS_STDFILES #define MICROPY_PY_SYS_STDFILES (CORE_FEAT) From 6bbd7bcc60f6b258f2b35eca61a3a0a90484edff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Fri, 1 May 2026 17:24:09 +0200 Subject: [PATCH 2044/2098] nrf/boards: Use 64 byte raw-paste buffer on PCA10031. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the same workaround as added in cc7eb1a5351c0d7d60f084dc79ccd0fa047b259e for other boards to the NRF51-Dongle, since it also suffers from the same limitation. This change makes it possible to run unit tests against it. Signed-off-by: Daniël van de Giessen --- ports/nrf/boards/PCA10031/mpconfigboard.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/nrf/boards/PCA10031/mpconfigboard.h b/ports/nrf/boards/PCA10031/mpconfigboard.h index 02add73b2cc..9711b1ea40b 100644 --- a/ports/nrf/boards/PCA10031/mpconfigboard.h +++ b/ports/nrf/boards/PCA10031/mpconfigboard.h @@ -58,3 +58,8 @@ #define MICROPY_HW_SPI0_MISO (17) #define HELP_TEXT_BOARD_LED "1,2,3" + +// The JLink CDC on the PCA10031 cannot accept more than 64 incoming bytes at a time. +// That makes the UART REPL unreliable in general. But it can be improved to some +// extent by setting the raw-paste buffer size to that limit of 64. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) From a876a2acedb4fe51cc73c082081676949662f2d4 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 17 Apr 2026 22:30:49 +1000 Subject: [PATCH 2045/2098] unix/unix_mphal: Use TCSANOW for terminal mode switching. Change tcsetattr from TCSAFLUSH to TCSANOW in mp_hal_stdio_mode_raw and mp_hal_stdio_mode_orig. The TCSAFLUSH flag calls tcdrain() before applying terminal settings, which blocks indefinitely on macOS when used with PTY devices if the master side has not read all pending output. This is a known macOS kernel behavior (XNU ttywait loops until the output queue is consumed by the master). The drain serves no practical purpose on the unix port since output is written via write() syscalls that complete before tcsetattr is called. The input flush is also unnecessary since MicroPython's readline handles stale input gracefully. Two repl_ test .exp files are updated to account for the newline after Ctrl-D (end paste mode) no longer being discarded by the input flush; it produces an extra empty prompt line. Signed-off-by: Andrew Leech --- ports/unix/unix_mphal.c | 4 ++-- tests/cmdline/repl_autocomplete_underscore.py.exp | 1 + tests/cmdline/repl_paste.py.exp | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index b0861aaa774..d04e0b96e7e 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -111,12 +111,12 @@ void mp_hal_stdio_mode_raw(void) { termios.c_lflag = 0; termios.c_cc[VMIN] = 1; termios.c_cc[VTIME] = 0; - tcsetattr(0, TCSAFLUSH, &termios); + tcsetattr(0, TCSANOW, &termios); } void mp_hal_stdio_mode_orig(void) { // restore terminal settings - tcsetattr(0, TCSAFLUSH, &orig_termios); + tcsetattr(0, TCSANOW, &orig_termios); } #endif diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp index f2d3a44c9ad..54882267d9b 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py.exp +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -26,6 +26,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === def _private_property(self): === return 99 === \$ +>>> \$ >>> # Paste executed >>> \$ >>> # Create an instance diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp index 3165b863052..a95d9fd2f7b 100644 --- a/tests/cmdline/repl_paste.py.exp +++ b/tests/cmdline/repl_paste.py.exp @@ -12,6 +12,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === \$ Hello from paste mode! >>> \$ +>>> \$ >>> # Paste mode with multiple indentation levels >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -34,6 +35,7 @@ Even: 2 Odd: 3 Even: 4 >>> \$ +>>> \$ >>> # Paste mode with blank lines >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -52,6 +54,7 @@ First line After blank line After two blank lines >>> \$ +>>> \$ >>> # Paste mode with class definition and multiple methods >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -76,6 +79,7 @@ Value is: 21 Doubled: 42 Value is: 42 >>> \$ +>>> \$ >>> # Paste mode with exception handling >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -90,6 +94,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish Caught division by zero Finally block executed >>> \$ +>>> \$ >>> # Cancel paste mode with Ctrl-C >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -113,6 +118,7 @@ Traceback (most recent call last): File "", line 2 SyntaxError: invalid syntax >>> \$ +>>> \$ >>> # Paste mode with runtime error >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -127,6 +133,7 @@ Traceback (most recent call last): File "", line 3, in will_error NameError: name 'undefined_variable' isn't defined >>> \$ +>>> \$ >>> # Final test to show REPL is still functioning >>> 1 + 2 + 3 6 From 30781dce0a13b8e10d76bf02eb683fc0b2f38560 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 17 Apr 2026 22:31:03 +1000 Subject: [PATCH 2046/2098] tests/cmdline: Add Ctrl-C interrupt test for the repl_ test framework. Adds a "# sigint:" directive for repl_ tests that need Ctrl-C to generate SIGINT via the PTY terminal driver. When present, the child process is set up with the PTY as its controlling terminal (via setsid/TIOCSCTTY/tcsetpgrp) so that \x03 written to the PTY master generates SIGINT for the child's process group. This works because MicroPython's REPL restores original terminal settings (with ISIG enabled) before executing user code, allowing the terminal driver to convert \x03 into SIGINT during blocking operations. Test added: - repl_ctrl_c_interrupt_execution.py: Verifies Ctrl-C interrupts a blocking time.sleep() call and the REPL remains functional afterward. Also wraps PTY fd handling in try/finally for all repl_ tests. Signed-off-by: Andrew Leech --- pyproject.toml | 1 + .../repl_ctrl_c_interrupt_execution.py | 8 ++ .../repl_ctrl_c_interrupt_execution.py.exp | 15 +++ tests/run-tests.py | 96 +++++++++++++++---- 4 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 tests/cmdline/repl_ctrl_c_interrupt_execution.py create mode 100644 tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp diff --git a/pyproject.toml b/pyproject.toml index 55763552fa8..f528961b2c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ exclude = [ # Ruff finds Python SyntaxError in these files "tests/cmdline/repl_autoindent.py", "tests/cmdline/repl_basic.py", "tests/cmdline/repl_cont.py", + "tests/cmdline/repl_ctrl_c_interrupt_execution.py", "tests/cmdline/repl_emacs_keys.py", "tests/cmdline/repl_paste.py", "tests/cmdline/repl_words_move.py", diff --git a/tests/cmdline/repl_ctrl_c_interrupt_execution.py b/tests/cmdline/repl_ctrl_c_interrupt_execution.py new file mode 100644 index 00000000000..b125e2c16b4 --- /dev/null +++ b/tests/cmdline/repl_ctrl_c_interrupt_execution.py @@ -0,0 +1,8 @@ +# sigint: deliver via controlling terminal +# Test that Ctrl-C (SIGINT) interrupts blocking code execution. +# MicroPython restores original terminal mode (ISIG on) during +# execution, so the PTY terminal driver generates SIGINT from \x03. +import time +time.sleep(10) +{\x03} +print('repl still responds') diff --git a/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp b/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp new file mode 100644 index 00000000000..48baad0193a --- /dev/null +++ b/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp @@ -0,0 +1,15 @@ +MicroPython \.\+ version +Type "help()" for more information. +>>> # sigint: deliver via controlling terminal +>>> # Test that Ctrl-C (SIGINT) interrupts blocking code execution. +>>> # MicroPython restores original terminal mode (ISIG on) during +>>> # execution, so the PTY terminal driver generates SIGINT from \x03. +>>> import time +>>> time.sleep(10) +######## +\.\*Traceback (most recent call last): + File "", line 1, in +KeyboardInterrupt: \$ +>>> print('repl still responds') +repl still responds +>>> \$ diff --git a/tests/run-tests.py b/tests/run-tests.py index ba6197acf3e..eb6f9d6f9cb 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -460,11 +460,16 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): if is_special: # check for any cmdline options needed for this test cmdlist = [MICROPYTHON] + send_sigint = False with open(test_file, "rb") as f: - line = f.readline() - if line.startswith(b"# cmdline:"): - # subprocess.check_output on Windows only accepts strings, not bytes - cmdlist += [str(c, "utf-8") for c in line[10:].strip().split()] + for line in f: + if line.startswith(b"# cmdline:"): + # subprocess.check_output on Windows only accepts strings, not bytes + cmdlist += [str(c, "utf-8") for c in line[10:].strip().split()] + elif line.startswith(b"# sigint:"): + send_sigint = True + elif not line.startswith(b"#"): + break # run the test, possibly with redirected input try: @@ -499,27 +504,76 @@ def send_get(what): os.write(master, what) return get() + def send_ctrl_c(): + # Send \x03 without trailing newline and wait for + # the full response (traceback + new prompt). + os.write(master, b"\x03") + return get(True) + with open(test_file, "rb") as f: # instead of: output_mupy = subprocess.check_output(cmdlist, stdin=f) master, slave = pty.openpty() - p = subprocess.Popen( - cmdlist, stdin=slave, stdout=slave, stderr=subprocess.STDOUT, bufsize=0 - ) - banner = get(True) - output_mupy = banner + b"".join(send_get(line) for line in f) - send_get(b"\x04") # exit the REPL, so coverage info is saved - # At this point the process might have exited already, but trying to - # kill it 'again' normally doesn't result in exceptions as Python and/or - # the OS seem to try to handle this nicely. When running Linux on WSL - # though, the situation differs and calling Popen.kill after the process - # terminated results in a ProcessLookupError. Just catch that one here - # since we just want the process to be gone and that's the case. try: - p.kill() - except ProcessLookupError: - pass - os.close(master) - os.close(slave) + preexec_fn = None + use_sigint_kill = False + # Tests with "# sigint:" need Ctrl-C (\x03) to + # generate SIGINT. MicroPython restores original + # terminal mode (ISIG on) during code execution, + # so on Linux we set up the PTY as a controlling + # terminal for proper signal delivery. On macOS, + # setsid/TIOCSCTTY breaks PTY I/O, so we fall + # back to os.kill(). + if send_sigint: + if sys.platform == "darwin": + use_sigint_kill = True + else: + import fcntl + import termios + + def preexec_fn(): + os.setsid() + fcntl.ioctl(0, termios.TIOCSCTTY, 0) + os.tcsetpgrp(0, os.getpid()) + + p = subprocess.Popen( + cmdlist, + stdin=slave, + stdout=slave, + stderr=subprocess.STDOUT, + bufsize=0, + preexec_fn=preexec_fn, + ) + banner = get(True) + if send_sigint: + import signal + + parts = [] + for line in f: + if b"{\\x03}" in line: + if use_sigint_kill: + os.kill(p.pid, signal.SIGINT) + parts.append(get(True)) + else: + parts.append(send_ctrl_c()) + else: + parts.append(send_get(line)) + output_mupy = banner + b"".join(parts) + else: + output_mupy = banner + b"".join(send_get(line) for line in f) + send_get(b"\x04") # exit the REPL, so coverage info is saved + # At this point the process might have exited already, but trying to + # kill it 'again' normally doesn't result in exceptions as Python and/or + # the OS seem to try to handle this nicely. When running Linux on WSL + # though, the situation differs and calling Popen.kill after the process + # terminated results in a ProcessLookupError. Just catch that one here + # since we just want the process to be gone and that's the case. + try: + p.kill() + except ProcessLookupError: + pass + finally: + os.close(master) + os.close(slave) else: output_mupy = subprocess.check_output( cmdlist + [test_file], stderr=subprocess.STDOUT From 72222a63ca6e9cf9f0cd6b7edb219e386f6e9f7c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 8 Apr 2026 16:56:34 +1000 Subject: [PATCH 2047/2098] tests/extmod: Don't require constructor to raise for bad socket type. Some socket implementations (eg `extmod/modsocket.c`) don't (and can't easily) raise an exception for an invalid socket type. So just test that passing such a value doesn't crash the device. Signed-off-by: Damien George --- tests/extmod/socket_badconstructor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/extmod/socket_badconstructor.py b/tests/extmod/socket_badconstructor.py index 1ea5d750b3e..25d981f70a7 100644 --- a/tests/extmod/socket_badconstructor.py +++ b/tests/extmod/socket_badconstructor.py @@ -16,10 +16,12 @@ except TypeError: print("TypeError") +# This may or may not raise an exception, depending on the socket implementation. +# The test is here for coverage. try: s = socket.socket(socket.AF_INET, 123456) except OSError: - print("OSError") + pass try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, None) From c8b71b0e6cb6b198994117af41bb05d81d81cc50 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 31 Jul 2025 13:34:02 +1000 Subject: [PATCH 2048/2098] tests/perf_bench: Skip import tests when vfs module doesn't exist. For example, the ESP8266_GENERIC FLASH_512K variant doesn't have the `vfs` module. Signed-off-by: Damien George --- tests/perf_bench/core_import_mpy_multi.py | 9 +++++---- tests/perf_bench/core_import_mpy_single.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/perf_bench/core_import_mpy_multi.py b/tests/perf_bench/core_import_mpy_multi.py index 67deec05088..55aa379e2bf 100644 --- a/tests/perf_bench/core_import_mpy_multi.py +++ b/tests/perf_bench/core_import_mpy_multi.py @@ -1,8 +1,9 @@ # Test performance of importing an .mpy file many times. -import sys, io, vfs - -if not hasattr(io, "IOBase"): +try: + import sys, vfs + from io import IOBase +except ImportError: print("SKIP") raise SystemExit @@ -26,7 +27,7 @@ def f(): file_data = b'M\x06\x00\x1e\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00\x0cresult\x00/-5#\x82I\x81{\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x06arg\x00\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\n\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x04Qc\x02\x81d\x00\x08\x02(DD\x11\x05\x16\x06\x10\x02\x16\x072\x00\x16\x082\x01\x16\t2\x02\x16\nQc\x03`\x1a\x08\x08\x12\x13@\xb1\xb0\x18\x13Qc@\t\x08\t\x12` Qc@\t\x08\n\x12``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0b\x12\x0c\x12\r\x12\x0e*\x04Y\x12\x0f\x12\x10\x12\x11*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' -class File(io.IOBase): +class File(IOBase): def __init__(self): self.off = 0 diff --git a/tests/perf_bench/core_import_mpy_single.py b/tests/perf_bench/core_import_mpy_single.py index f472bb64762..755e4159487 100644 --- a/tests/perf_bench/core_import_mpy_single.py +++ b/tests/perf_bench/core_import_mpy_single.py @@ -2,9 +2,10 @@ # The first import of a module will intern strings that don't already exist, and # this test should be representative of what happens in a real application. -import sys, io, vfs - -if not hasattr(io, "IOBase"): +try: + import sys, vfs + from io import IOBase +except ImportError: print("SKIP") raise SystemExit @@ -81,7 +82,7 @@ def f1(): file_data = b"M\x06\x00\x1e\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x0cresult\x00/-5\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\n\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\x06Qc\x04\x82\x0c\x00\n\x02($$$\x11\x07\x16\x08\x10\x02\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04@\t\x08\n\x81\x0b Qc@\t\x08\x0b\x81\x0b@Qc@\t\x08\x0c\x81\x0b`QcH\t\n\r\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x07\x16\x08\x10\x03\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04H\t\n\n\x81\x0b``QcH\t\n\x0b\x81\x0b\x80\x07QcH\t\n\x0c\x81\x0b\x80\x08QcH\t\n\r\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x08\x12\x07*\x08Y\x12\x1b\x12\x1c\x12\t\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" -class File(io.IOBase): +class File(IOBase): def __init__(self): self.off = 0 From 551a5680f0c0bfd64fa04ea5c052e46e16231ea0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Aug 2025 10:09:29 +1000 Subject: [PATCH 2049/2098] tests/run-perfbench.py: Skip misc_mandel if target doesn't have complex. Eg running this on `ADAFRUIT_ITSYBITSY_M0_EXPRESS` would previously crash with "NameError: name not defined" due to the lookup of `complex` as a global. With the change here, that test is skipped automatically. Also, provide better skip messages with the reason for the skip. Signed-off-by: Damien George --- tests/run-perfbench.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 16182bc8a9f..686c3da7ae4 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -101,15 +101,14 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): print(test_file + ": ", end="") # Check if test should be skipped - skip = ( - skip_complex - and test_file.find("bm_fft") != -1 - or skip_native - and test_file.find("viper_") != -1 - ) - if skip: - test_results.append((test_file, "skip", "")) - print("SKIP") + skip_reason = None + if skip_complex and test_file.endswith(("bm_fft.py", "misc_mandel.py")): + skip_reason = "complex not supported" + elif skip_native and test_file.find("viper_") != -1: + skip_reason = "native not supported" + if skip_reason: + test_results.append((test_file, "skip", skip_reason)) + print("SKIP:", skip_reason) continue # Create test script From fc8857a4aeac719497ba9642738007404d8cbea7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Aug 2025 10:01:04 +1000 Subject: [PATCH 2050/2098] tests/multi_net/udp_data_multi.py: Reduce number of UDP groups to 4. This test is unreliable with 5 UDP groups because on bare-metal lwIP targets there's only enough buffer space for 4 groups, see `extmod/modlwip.c`: // Total queue length for buffered UDP/raw incoming packets. #define LWIP_INCOMING_PACKET_QUEUE_LEN (4) Signed-off-by: Damien George --- tests/multi_net/udp_data_multi.py | 2 +- tests/multi_net/udp_data_multi.py.exp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/multi_net/udp_data_multi.py b/tests/multi_net/udp_data_multi.py index 5d7b13e5188..a2ed33d292d 100644 --- a/tests/multi_net/udp_data_multi.py +++ b/tests/multi_net/udp_data_multi.py @@ -5,7 +5,7 @@ NUM_NEW_SOCKETS = 4 NUM_PACKET_BURSTS = 6 -NUM_PACKET_GROUPS = 5 +NUM_PACKET_GROUPS = 4 TOTAL_PACKET_BURSTS = NUM_NEW_SOCKETS * NUM_PACKET_BURSTS # The tast passes if more than 75% of packets are received in each group. PACKET_RECV_THRESH = 0.75 * TOTAL_PACKET_BURSTS diff --git a/tests/multi_net/udp_data_multi.py.exp b/tests/multi_net/udp_data_multi.py.exp index bc67c6ab0cf..a74ef42b730 100644 --- a/tests/multi_net/udp_data_multi.py.exp +++ b/tests/multi_net/udp_data_multi.py.exp @@ -7,7 +7,6 @@ pass group=0 pass group=1 pass group=2 pass group=3 -pass group=4 --- instance1 --- test socket 0 test socket 1 From 722aacfd581c03b7bdda8bd114414e32275576c2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 26 Sep 2025 22:37:40 +1000 Subject: [PATCH 2051/2098] tests/extmod/machine_soft_timer.py: Skip test on nrf boards. The nrf port doesn't implement the `freq` argument. Signed-off-by: Damien George --- tests/extmod/machine_soft_timer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/extmod/machine_soft_timer.py b/tests/extmod/machine_soft_timer.py index aa19becd8eb..4c0611caedf 100644 --- a/tests/extmod/machine_soft_timer.py +++ b/tests/extmod/machine_soft_timer.py @@ -9,8 +9,8 @@ print("SKIP") raise SystemExit -if sys.platform in ("esp32", "esp8266"): - print("SKIP") # TODO: Implement soft timers for esp32/esp8266 ports +if sys.platform in ("esp32", "esp8266", "nrf"): + print("SKIP") # TODO: Implement soft timers for esp32/esp8266/nrf ports raise SystemExit # create and deinit From 0b8fbe3f9b6db99b9c12c7b4c67f3f699bb30eb8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 26 Sep 2025 22:38:01 +1000 Subject: [PATCH 2052/2098] tests/micropython/ringio_big.py: Improve running on low-memory targets. Two changes here to improve running this test on targets with low memory: - Print the results at end of test, so if a MemoryError occurs part-way- through then the SKIP works correctly. - Reorganise the order of the sub-tests and preallocate the large buffer so it can be reused. This gets the test running and passing on ESP32_GENERIC with the native emitter (and also still works with the bytecode emitter). Signed-off-by: Damien George --- tests/micropython/ringio_big.py | 27 +++++++++++++++++++-------- tests/run-tests.py | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/micropython/ringio_big.py b/tests/micropython/ringio_big.py index ddbbae12a63..88d5d2cc165 100644 --- a/tests/micropython/ringio_big.py +++ b/tests/micropython/ringio_big.py @@ -8,22 +8,33 @@ print("SKIP") raise SystemExit +results = [] + try: - # The maximum possible size - micropython.RingIO(bytearray(65535)) + # The maximum possible size passed as an integer. micropython.RingIO(65534) try: - # Buffer may not be too big - micropython.RingIO(bytearray(65536)) + # Size may not be too big + micropython.RingIO(65535) except ValueError as ex: - print(type(ex)) + results.append(type(ex)) + + # Allocate a buffer for use below. + buf_64k = memoryview(bytearray(65536)) + + # The maximum possible size passed as a buffer. + micropython.RingIO(buf_64k[:-1]) try: - # Size may not be too big - micropython.RingIO(65535) + # Buffer may not be too big + micropython.RingIO(buf_64k) except ValueError as ex: - print(type(ex)) + results.append(type(ex)) + except MemoryError: print("SKIP") raise SystemExit + +for result in results: + print(result) diff --git a/tests/run-tests.py b/tests/run-tests.py index eb6f9d6f9cb..8585b1cc7e8 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -286,6 +286,7 @@ "micropython/import_mpy_invalid.py", "micropython/import_mpy_native.py", "micropython/import_mpy_native_gc.py", + "micropython/ringio_big.py", "misc/non_compliant.py", "misc/rge_sm.py", ) From 1d9b8b456eaa5ecbb552f135094365701ee2d2b3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 8 Feb 2026 23:02:36 +1100 Subject: [PATCH 2053/2098] tests/extmod/machine_uart_tx.py: Make string longer for more accuracy. This decreases the variation in the time taken to send, making the test more reliable. Without this change the nrf port is flaky at 2400 baud, eg the `ARDUINO_NANO_33_BLE_SENSE` board fails this test more often than not. Signed-off-by: Damien George --- tests/extmod/machine_uart_tx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extmod/machine_uart_tx.py b/tests/extmod/machine_uart_tx.py index 1ff9af64bd4..4e0ff37a8b1 100644 --- a/tests/extmod/machine_uart_tx.py +++ b/tests/extmod/machine_uart_tx.py @@ -37,7 +37,7 @@ # Test that write+flush takes the expected amount of time to execute. for bits_per_s in (2400, 9600, 115200): - text = "Hello World" + text = "Hello World from MicroPython" uart = UART(*uart_loopback_args, baudrate=bits_per_s, **uart_loopback_kwargs) time.sleep_ms(initial_delay_ms) From 0ead9671b1bfe7fc1b0b087b867940aa75290596 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Feb 2026 02:10:48 +1100 Subject: [PATCH 2054/2098] tests/extmod/framebuf_polygon.py: Skip test if buf can't be allocated. Prior to this change this test fails on `ADAFRUIT_ITSYBITSY_M0_EXPRESS` with the native emitter, because it doesn't have enough heap to allocate the backing buffer. Signed-off-by: Damien George --- tests/extmod/framebuf_polygon.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/extmod/framebuf_polygon.py b/tests/extmod/framebuf_polygon.py index da05be2c4db..91a64d74c30 100644 --- a/tests/extmod/framebuf_polygon.py +++ b/tests/extmod/framebuf_polygon.py @@ -11,6 +11,12 @@ print("SKIP") raise SystemExit +try: + buf = bytearray(70 * 70) +except MemoryError: + print("SKIP") + raise SystemExit + def print_buffer(buffer, width, height): for row in range(height): @@ -20,8 +26,6 @@ def print_buffer(buffer, width, height): print() -buf = bytearray(70 * 70) - w = 30 h = 25 fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8) From 98c76a9cbd518739402684cd53b4ef68d1af8a01 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Feb 2026 02:11:21 +1100 Subject: [PATCH 2055/2098] tests/extmod/machine_spi_rate.py: Skip test if bufs can't be allocated. Prior to this change this test fails on `ADAFRUIT_ITSYBITSY_M0_EXPRESS` with the native emitter, because it doesn't have enough heap to allocate the read/write buffers. Signed-off-by: Damien George --- tests/extmod/machine_spi_rate.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py index c41bf3ac2f7..27174bf95e7 100644 --- a/tests/extmod/machine_spi_rate.py +++ b/tests/extmod/machine_spi_rate.py @@ -6,6 +6,15 @@ print("SKIP") raise SystemExit +try: + wr_short = b"abcdefghijklmnop" * 10 + rd_short = bytearray(len(wr_short)) + wr_long = wr_short * 20 + rd_long = bytearray(len(wr_long)) +except MemoryError: + print("SKIP") + raise SystemExit + import time, sys from target_wiring import spi_standalone_args_list @@ -56,13 +65,6 @@ def test_instances(): print_results = False -wr_short = b"abcdefghijklmnop" * 10 -rd_short = bytearray(len(wr_short)) - -wr_long = wr_short * 20 -rd_long = bytearray(len(wr_long)) - - def test_spi(spi_args, baudrate, polarity, phase, print_results): s = SPI(*spi_args, baudrate=baudrate, polarity=polarity, phase=phase) From 9ecdeb626185f92584ec98c5cf62f98eedd7cc28 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Feb 2026 01:33:31 +1100 Subject: [PATCH 2056/2098] tests/run-tests.py: Skip tests depending on error reporting capability. Detect the target's error reporting capabilities, and skip tests as appropriate. For example, all samd boards are configured with `MICROPY_ERROR_REPORTING_TERSE` so need to skip these four tests. Signed-off-by: Damien George --- tests/feature_check/target_info.py | 8 +++++++- tests/run-tests.py | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index e95530023d7..ed91b27b775 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -36,4 +36,10 @@ except NameError: float_prec = 0 -print(platform, arch, arch_flags, build, thread, float_prec, len("α") == 1) +# Detect the error reporting level (based on the length of the raised exception message). +try: + (lambda: 0)(0) +except TypeError as er: + error_reporting = {0: "none", 27: "terse", 54: "normal", 56: "detailed"}[len(er.value)] + +print(platform, arch, arch_flags, build, thread, float_prec, len("α") == 1, error_reporting) diff --git a/tests/run-tests.py b/tests/run-tests.py index 8585b1cc7e8..f6a4a33adee 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -198,6 +198,19 @@ ), } +# Tests to skip when MICROPY_ERROR_REPORTING is at a certain level. +error_reporting_tests_to_skip = { + # Skip at level MICROPY_ERROR_REPORTING_NONE. + "none": ( + "micropython/heapalloc_exc_compressed.py", + "micropython/heapalloc_exc_compressed_emg_exc.py", + "micropython/opt_level_lineno.py", + "misc/print_exception.py", + ), +} +# Skip at level MICROPY_ERROR_REPORTING_TERSE. +error_reporting_tests_to_skip["terse"] = error_reporting_tests_to_skip["none"] + # Tests with known intermittent failures. These tests still run, but failures # are reclassified as "ignored" instead of "fail" so they don't affect the CI # exit code. Paths are relative to the tests/ directory (must match test_file @@ -353,7 +366,7 @@ def detect_test_platform(pyb, args): output = run_feature_check(pyb, args, "target_info.py") if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) - platform, arch, arch_flags, build, thread, float_prec, unicode = ( + platform, arch, arch_flags, build, thread, float_prec, unicode, error_reporting = ( str(output, "ascii").strip().split() ) if arch == "None": @@ -380,6 +393,7 @@ def detect_test_platform(pyb, args): args.thread = thread args.float_prec = float_prec args.unicode = unicode + args.error_reporting = error_reporting # Print the detected information about the target. print("platform={}".format(platform), end="") @@ -922,6 +936,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Skip platform-specific tests. skip_tests.update(platform_tests_to_skip.get(args.platform, ())) + # Skip error-reporting-specific tests. + skip_tests.update(error_reporting_tests_to_skip.get(args.error_reporting, ())) + # Some tests are known to fail on 64-bit machines if pyb is None and platform.architecture()[0] == "64bit": pass From 0a55311be5cad0e7a00f0e7a4f4c34115ddf4938 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 9 Apr 2026 21:56:12 +1000 Subject: [PATCH 2057/2098] tests/micropython: Remove dependence on exact exception message. Allows running with terse error reporting (and normal and detailed). Signed-off-by: Damien George --- tests/micropython/native_with.py | 1 + tests/micropython/native_with.py.exp | 2 +- tests/micropython/viper_with.py | 1 + tests/micropython/viper_with.py.exp | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/micropython/native_with.py b/tests/micropython/native_with.py index 9c0b98af903..37c385a42f9 100644 --- a/tests/micropython/native_with.py +++ b/tests/micropython/native_with.py @@ -9,6 +9,7 @@ def __enter__(self): print("__enter__") def __exit__(self, a, b, c): + b = repr(b)[:10] # shorten exception to "NameError(" prefix for target compatibility print("__exit__", a, b, c) diff --git a/tests/micropython/native_with.py.exp b/tests/micropython/native_with.py.exp index 7e28663f6fc..b8083a4c66b 100644 --- a/tests/micropython/native_with.py.exp +++ b/tests/micropython/native_with.py.exp @@ -5,5 +5,5 @@ __exit__ None None None __init__ __enter__ 1 -__exit__ name 'fail' isn't defined None +__exit__ NameError( None NameError diff --git a/tests/micropython/viper_with.py b/tests/micropython/viper_with.py index 40fbf6fb315..833a6babe7d 100644 --- a/tests/micropython/viper_with.py +++ b/tests/micropython/viper_with.py @@ -9,6 +9,7 @@ def __enter__(self): print("__enter__") def __exit__(self, a, b, c): + b = repr(b)[:10] # shorten exception to "NameError(" prefix for target compatibility print("__exit__", a, b, c) diff --git a/tests/micropython/viper_with.py.exp b/tests/micropython/viper_with.py.exp index 7e28663f6fc..b8083a4c66b 100644 --- a/tests/micropython/viper_with.py.exp +++ b/tests/micropython/viper_with.py.exp @@ -5,5 +5,5 @@ __exit__ None None None __init__ __enter__ 1 -__exit__ name 'fail' isn't defined None +__exit__ NameError( None NameError From 2cebe9f04f290b6c3d9373ea17d5f7e75962bb40 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 9 Apr 2026 21:56:46 +1000 Subject: [PATCH 2058/2098] tests/misc/rge_sm.py: Skip test if target doesn't have enough memory. For example, `ADAFRUIT_ITSYBITSY_M0_EXPRESS` only has about 20k heap and runs out of memory part way through the integration loop. Signed-off-by: Damien George --- tests/misc/rge_sm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/misc/rge_sm.py b/tests/misc/rge_sm.py index 56dad574977..9e74c3b7a79 100644 --- a/tests/misc/rge_sm.py +++ b/tests/misc/rge_sm.py @@ -78,7 +78,11 @@ def singleTraj(system, trajStart, h=0.02, tend=1.0): # compute the trajectory rk = RungeKutta(system, trajStart, tstart, h) - rk.solve(tend) + try: + rk.solve(tend) + except MemoryError: + print("SKIP") + raise SystemExit # print out trajectory From 10b624751edff9f31ecd0844d8fd918c2634add4 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 29 Apr 2026 12:15:28 -0500 Subject: [PATCH 2059/2098] tests/float/float_format_ints.py: Add another valid alternative result. Clang -m32 rounds some floating point reprs differently, likely due to x87 temporary excess precision. Accept this value in addition to the other values that are accepted. Closes: #19120 Signed-off-by: Jeff Epler --- tests/float/float_format_ints.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/float/float_format_ints.py b/tests/float/float_format_ints.py index 7b7b30c4b34..1f221bd5e79 100644 --- a/tests/float/float_format_ints.py +++ b/tests/float/float_format_ints.py @@ -46,6 +46,13 @@ if is_REPR_C and val_str == "2147483200.000000": val_str = "2147483520.000000" +# When using REPR_C, x86 and clang, 2147483520.0 is the same +# as 2147483100.0, the second being "simple" but rounded differently +# due to x87 extra precision on intermediates. +# Both representations are valid. +if is_REPR_C and val_str == "2147483100.000000": + val_str = "2147483520.000000" + print(val_str) # Very large positive integers can be a test for precision and resolution. From 9ee7ac8224a525d38814820d0f895e6868b70012 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Mon, 4 May 2026 22:11:29 +0900 Subject: [PATCH 2060/2098] tests/cpydiff: Document dir() not post-processing __dir__ result. CPython's dir() converts the result of __dir__ to a list and sorts it; MicroPython returns the value as-is. Document the difference with a class whose __dir__ returns a non-list iterable. Signed-off-by: Jeongseop Lim --- tests/cpydiff/core_class_dir.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/cpydiff/core_class_dir.py diff --git a/tests/cpydiff/core_class_dir.py b/tests/cpydiff/core_class_dir.py new file mode 100644 index 00000000000..e2b0e877cdc --- /dev/null +++ b/tests/cpydiff/core_class_dir.py @@ -0,0 +1,14 @@ +""" +categories: Core,Classes +description: dir() does not convert __dir__ return value to a sorted list +cause: MicroPython's dir() returns the value from __dir__ as-is, without iterating it into a list or sorting it. +workaround: Have __dir__ return a sorted list directly. +""" + + +class C: + def __dir__(self): + return "cba" + + +print(dir(C())) From 14b26b99c26a783712249ed692c02c4992a6c597 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Oct 2025 14:34:37 +1100 Subject: [PATCH 2061/2098] stm32/tinyusb_port: Add High Speed USB controller support with TinyUSB. Implements mapping from MICROPY_HW_USB_MAIN_DEV to TinyUSB RHPORT configuration, enabling board-specific USB PHY selection for TinyUSB stack. Adds support for HS-in-FS mode (High Speed controller running at Full Speed) which is the default for STM32 boards without external ULPI PHY. Thi STM32F4/F7/H7 high-speed RHPORT mode selection is placed in ports/stm32/tinyusb_port/tusb_config.h, following the alif/nrf pattern. Includes py/mpconfig.h to ensure board config macros are available when TinyUSB processes the header. Signed-off-by: Andrew Leech --- ports/stm32/Makefile | 1 + ports/stm32/stm32_it.c | 2 +- ports/stm32/tinyusb_port/tusb_config.h | 51 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 ports/stm32/tinyusb_port/tusb_config.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 63ed974b9b5..ae78649041f 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -111,6 +111,7 @@ INC += -I$(STM32LIB_HAL_ABS)/Inc INC += -I$(USBDEV_DIR)/core/inc -I$(USBDEV_DIR)/class/inc #INC += -I$(USBHOST_DIR) INC += -I$(TOP)/lib/tinyusb/src +INC += -Itinyusb_port INC += -I$(TOP)/shared/tinyusb/ INC += -Ilwip_inc diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 3ae1980f8fe..f528314a5e0 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -384,7 +384,7 @@ void USB1_OTG_HS_IRQHandler(void) { void OTG_HS_IRQHandler(void) { IRQ_ENTER(OTG_HS_IRQn); #if MICROPY_HW_TINYUSB_STACK - tud_int_handler(0); + tud_int_handler(1); // OTG_HS is always RHPORT1 on F4/F7/H7 (not N6, which uses USB1_OTG_HS_IRQHandler) #else HAL_PCD_IRQHandler(&pcd_hs_handle); #endif diff --git a/ports/stm32/tinyusb_port/tusb_config.h b/ports/stm32/tinyusb_port/tusb_config.h new file mode 100644 index 00000000000..4d74eec9b37 --- /dev/null +++ b/ports/stm32/tinyusb_port/tusb_config.h @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * 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_STM32_TINYUSB_PORT_TUSB_CONFIG_H +#define MICROPY_INCLUDED_STM32_TINYUSB_PORT_TUSB_CONFIG_H + +#include "py/mpconfig.h" + +// STM32F4/F7/H7 boards with USB_HS use OTG_HS on RHPORT1, not RHPORT0. +// Disable RHPORT0 and put RHPORT1 in device mode (HS, or FS when the +// HS controller uses the internal FS PHY via MICROPY_HW_USB_HS_IN_FS). +// Other configs are handled either by the board (e.g. N6 sets RHPORT0 +// to HS in mpconfigboard_common.h) or by the shared default in +// shared/tinyusb/tusb_config.h (RHPORT0 in FS device mode). + +// These families place OTG_HS on RHPORT1. Extend the list if a new family +// also uses OTG_HS on RHPORT1 rather than RHPORT0. +#if MICROPY_HW_USB_HS && (defined(STM32F4) || defined(STM32F7) || defined(STM32H7)) +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_NONE) +#if MICROPY_HW_USB_HS_IN_FS +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) +#else +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#endif +#endif + +#include "shared/tinyusb/tusb_config.h" + +#endif // MICROPY_INCLUDED_STM32_TINYUSB_PORT_TUSB_CONFIG_H From b001b71959b0d9ac03b9259ee9bbf4eb08e699d0 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 28 Oct 2025 14:30:25 +1100 Subject: [PATCH 2062/2098] stm32/factoryreset: Add TinyUSB-specific boot.py examples. Signed-off-by: Andrew Leech --- ports/stm32/factoryreset.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/stm32/factoryreset.c b/ports/stm32/factoryreset.c index 50854a908b1..1811ca1186f 100644 --- a/ports/stm32/factoryreset.c +++ b/ports/stm32/factoryreset.c @@ -44,9 +44,15 @@ static const char fresh_boot_py[] = "import pyb\r\n" "#pyb.main('main.py') # main script to run after this one\r\n" #if MICROPY_HW_ENABLE_USB +#if MICROPY_HW_TINYUSB_STACK + "#usb = machine.USBDevice()\r\n" + "#usb.builtin_driver = machine.USBDevice.BUILTIN_DEFAULT # CDC + MSC\r\n" + "#usb.active(True)\r\n" +#else "#pyb.usb_mode('VCP+MSC') # act as a serial and a storage device\r\n" "#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse\r\n" #endif +#endif #if MICROPY_PY_NETWORK "#import network\r\n" "#network.country('US') # ISO 3166-1 Alpha-2 code, eg US, GB, DE, AU or XX for worldwide\r\n" From 8d3597ca50bbf58b794c8585ea333ca941957d30 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 7 Mar 2026 07:46:16 +1100 Subject: [PATCH 2063/2098] shared/tinyusb: Fix CDC reconnect stall and TX FIFO. When a host closes and reopens the CDC serial port, the IN endpoint may remain stalled from a prior runtime USB disconnect (e.g. mpremote connect/disconnect cycles). Clear the stall on DTR high so the connection recovers without requiring a device reset. On DTR low (host close), flush the TX FIFO so stale data does not accumulate and block writes on the next connection. Signed-off-by: Andrew Leech --- shared/tinyusb/mp_usbd_cdc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shared/tinyusb/mp_usbd_cdc.c b/shared/tinyusb/mp_usbd_cdc.c index 961bdf89896..8018032d36c 100644 --- a/shared/tinyusb/mp_usbd_cdc.c +++ b/shared/tinyusb/mp_usbd_cdc.c @@ -34,6 +34,11 @@ #if MICROPY_HW_USB_CDC && MICROPY_HW_ENABLE_USBDEV && !MICROPY_EXCLUDE_SHARED_TINYUSB_USBD_CDC +// TinyUSB has no public API for endpoint stall detection/clearing; this +// private header is the intended interface for class drivers (all built-in +// TinyUSB class drivers include it for the same purpose). +#include "device/usbd_pvt.h" + static uint8_t cdc_itf_pending; // keep track of cdc interfaces which need attention to poll static int8_t cdc_connected_flush_delay = 0; @@ -176,10 +181,18 @@ void MICROPY_WRAP_TUD_CDC_LINE_STATE_CB(tud_cdc_line_state_cb)(uint8_t itf, bool #if MICROPY_HW_USB_CDC && !MICROPY_EXCLUDE_SHARED_TINYUSB_USBD_CDC if (dtr) { // A host application has started to open the cdc serial port. + // USBD_CDC_EP_IN is the IN endpoint for itf 0; only clear stall for itf 0. + if (itf == 0 && usbd_edpt_stalled(TUD_OPT_RHPORT, USBD_CDC_EP_IN)) { + usbd_edpt_clear_stall(TUD_OPT_RHPORT, USBD_CDC_EP_IN); + } // Wait a few ms for host to be ready then send tx buffer. // High speed connection SOF fires at 125us, full speed at 1ms. cdc_connected_flush_delay = (tud_speed_get() == TUSB_SPEED_HIGH) ? 128 : 16; tud_sof_cb_enable(true); + } else { + // Host has closed the cdc serial port. Discard pending TX data to + // avoid a full FIFO blocking writes on the next connection. + tud_cdc_n_write_clear(itf); } #endif #if MICROPY_HW_USB_CDC_DTR_RTS_BOOTLOADER From 693f33aa42bb09f7a6beec8646a9f3312983dca5 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 10 Mar 2026 21:33:01 +1100 Subject: [PATCH 2064/2098] stm32/usbd: Match ST DFU bootloader serial number in TinyUSB. The TinyUSB serial descriptor used a raw hex dump of all 12 UID bytes in sequential order (24-char lowercase), while the legacy USB stack and ST's onboard DFU bootloader use a condensed algorithm that selects 6 bytes with two additions (12-char uppercase). This mismatch caused the device to report a different serial number depending on which USB stack was active, breaking tools that identify devices by serial (e.g. udev rules, mpremote, dfu-util). Use the ST DFU bootloader algorithm for consistency. Signed-off-by: Andrew Leech --- ports/stm32/usbd.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ports/stm32/usbd.c b/ports/stm32/usbd.c index 35275cd1bd0..fec87c90147 100644 --- a/ports/stm32/usbd.c +++ b/ports/stm32/usbd.c @@ -28,15 +28,25 @@ #if MICROPY_HW_ENABLE_USBDEV && MICROPY_HW_TINYUSB_STACK +#include #include "mp_usbd.h" -#include "py/mpconfig.h" -#include "string.h" #include "mphalport.h" void mp_usbd_port_get_serial_number(char *serial_buf) { + // Use the same algorithm as the ST DFU bootloader so that the serial + // number is consistent across all USB modes. + MP_STATIC_ASSERT(12 <= MICROPY_HW_USB_DESC_STR_MAX); // 6 derived bytes x 2 hex digits + NUL + static const char hexdig[] = "0123456789ABCDEF"; uint8_t *id = (uint8_t *)MP_HAL_UNIQUE_ID_ADDRESS; - MP_STATIC_ASSERT(12 * 2 <= MICROPY_HW_USB_DESC_STR_MAX); - mp_usbd_hex_str(serial_buf, id, 12); + uint8_t bytes[] = { + id[11], (uint8_t)(id[10] + id[2]), id[9], + (uint8_t)(id[8] + id[0]), id[7], id[6], + }; + for (int i = 0; i < 6; i++) { + serial_buf[i * 2] = hexdig[bytes[i] >> 4]; + serial_buf[i * 2 + 1] = hexdig[bytes[i] & 0x0f]; + } + serial_buf[12] = '\0'; } #endif From d2e0d20f824a3477121b1e1fe34b61e5579f44ed Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 30 Apr 2026 20:26:30 +1000 Subject: [PATCH 2065/2098] stm32,extmod: Use full path for shared/tinyusb/mp_usbd.h include. Removes the need for -I$(TOP)/shared/tinyusb/ in the stm32 Makefile by using an explicit path in the two files that include mp_usbd.h outside of the shared/tinyusb/ directory itself. Signed-off-by: Andrew Leech --- extmod/machine_usb_device.c | 2 +- ports/stm32/Makefile | 1 - ports/stm32/usbd.c | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index f6e97f42054..e8303ef84f7 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -28,7 +28,7 @@ #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE -#include "mp_usbd.h" +#include "shared/tinyusb/mp_usbd.h" #include "py/mperrno.h" #include "py/objstr.h" diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index ae78649041f..7597d23d0a3 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -112,7 +112,6 @@ INC += -I$(USBDEV_DIR)/core/inc -I$(USBDEV_DIR)/class/inc #INC += -I$(USBHOST_DIR) INC += -I$(TOP)/lib/tinyusb/src INC += -Itinyusb_port -INC += -I$(TOP)/shared/tinyusb/ INC += -Ilwip_inc CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 $(CFLAGS_EXTRA) diff --git a/ports/stm32/usbd.c b/ports/stm32/usbd.c index fec87c90147..d8f8b3dd551 100644 --- a/ports/stm32/usbd.c +++ b/ports/stm32/usbd.c @@ -29,7 +29,7 @@ #if MICROPY_HW_ENABLE_USBDEV && MICROPY_HW_TINYUSB_STACK #include -#include "mp_usbd.h" +#include "shared/tinyusb/mp_usbd.h" #include "mphalport.h" void mp_usbd_port_get_serial_number(char *serial_buf) { From c5df9543f74b54feeab32e4bc4bbed5d87e6251e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 13 Apr 2026 15:30:13 +1000 Subject: [PATCH 2066/2098] stm32/Makefile: Provide definitions of CFLAGS_MCU & MPY_CROSS_MCU_ARCH. Selected for the specific MCU series, to reduce code duplication when referring to them. Signed-off-by: Damien George --- ports/stm32/Makefile | 6 +++--- ports/stm32/stm32.mk | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 7597d23d0a3..31c8a500c9b 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -116,7 +116,7 @@ INC += -Ilwip_inc CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 $(CFLAGS_EXTRA) CFLAGS += -D$(CMSIS_MCU) -DUSE_FULL_LL_DRIVER -CFLAGS += $(CFLAGS_MCU_$(MCU_SERIES)) +CFLAGS += $(CFLAGS_MCU) CFLAGS += $(COPT) CFLAGS += -I$(BOARD_DIR) CFLAGS += -DSTM32_HAL_H='' @@ -130,7 +130,7 @@ endif # as doesn't recognise -mcpu=cortex-m55 AFLAGS += -march=armv8.1-m.main else -AFLAGS += $(filter -mcpu=%,$(CFLAGS_MCU_$(MCU_SERIES))) +AFLAGS += $(filter -mcpu=%,$(CFLAGS_MCU)) endif # Configure for nan-boxing object model if requested @@ -190,7 +190,7 @@ COPT ?= -Os -DNDEBUG endif # Options for mpy-cross -MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH_$(MCU_SERIES)) +MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH) SHARED_SRC_C += $(addprefix shared/,\ libc/string0.c \ diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index a1532d2776d..ba052974d6f 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -86,6 +86,10 @@ MPY_CROSS_MCU_ARCH_u5 = armv7m MPY_CROSS_MCU_ARCH_wb = armv7m MPY_CROSS_MCU_ARCH_wl = armv7m +# Select the correct flags for the given MCU series. +CFLAGS_MCU = $(CFLAGS_MCU_$(MCU_SERIES)) +MPY_CROSS_MCU_ARCH = $(MPY_CROSS_MCU_ARCH_$(MCU_SERIES)) + # gcc up to 14.2.0 have a known loop-optimisation bug: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116799 # This bug manifests for Cortex M55 targets, so require a newer compiler on such targets. From a6144fb2335e5dbb01152fcbf408038cf65c19f8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 13 Apr 2026 15:32:14 +1000 Subject: [PATCH 2067/2098] stm32/make-stmconst.py: Point periph reg defns to S/NS on CM33/CM55. Cortex-M33 and Cortex-M55 CPUs have both secure and non-secure addresses for most peripherals. Prior to this commit, the `stm` module was not taking this into account and instead of providing standard constants like `stm.RTC` it was providing both the _S and _NS set of peripherals, like `stm.RTC_S` and `stm.RTC_NS`. This makes it hard to write portable code which expectes things like `stm.RTC` to exist. Furthermore, Python code doesn't really have a way to know whether it's running in secure or non-secure mode, so doesn't know which peripheral registers to use (either _S or _NS). This commit addresses this issue by removing the constants ending in _S and _NS, and providing standard peripheral constants without any suffix. The address of the peripheral is chosen as the _S or _NS peripheral address depending on whether the firmware is built in secure or non-secure mode, respectively. The information about secure/non-secure mode is provide to `make-stmconst.py` by passing through the MCU CFLAGS. Signed-off-by: Damien George --- ports/stm32/Makefile | 2 +- ports/stm32/make-stmconst.py | 44 ++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 31c8a500c9b..b212abae621 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -711,7 +711,7 @@ $(GEN_PLLI2STABLE_HDR): $(PLLI2SVALUES) | $(HEADER_BUILD)/qstr.i.last $(BUILD)/modstm.o: $(GEN_STMCONST_HDR) $(HEADER_BUILD)/modstm_const.h: $(CMSIS_MCU_HDR) make-stmconst.py | $(HEADER_BUILD) $(ECHO) "GEN stmconst $@" - $(Q)$(PYTHON) make-stmconst.py --mpz $(GEN_STMCONST_MPZ) $(CMSIS_MCU_HDR) > $(GEN_STMCONST_HDR) + $(Q)$(PYTHON) make-stmconst.py --mpz $(GEN_STMCONST_MPZ) $(CMSIS_MCU_HDR) $(CFLAGS_MCU) > $(GEN_STMCONST_HDR) $(GEN_CDCINF_HEADER): $(GEN_CDCINF_FILE) $(FILE2H) | $(HEADER_BUILD) $(ECHO) "GEN $@" diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index 5601eb0af42..c1394657c34 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -119,12 +119,25 @@ def parse_file(filename): m = lexer.next_match() if m[0] == "EOF": break - elif m[0] == "#define hex": + + # If the CPU is secure, skip definitions for opposite security mode. + if m[0].startswith("#define"): + id_lhs = m[1]["id"] + if ( + security_mode + and id_lhs.endswith(("_NS", "_S")) + and not id_lhs.endswith(security_mode) + ): + continue + + if m[0] == "#define hex": d = m[1].groupdict() consts[d["id"]] = int(d["hex"], base=16) elif m[0] == "#define X": d = m[1].groupdict() if d["id2"] in consts: + if d["id"] in consts: + raise Exception(f"macro {d['id']} redefined") consts[d["id"]] = consts[d["id2"]] elif m[0] == "#define X+hex": d = m[1].groupdict() @@ -133,7 +146,11 @@ def parse_file(filename): elif m[0] == "#define typedef": d = m[1].groupdict() if d["id2"] in consts: - periphs.append((d["id"], consts[d["id2"]])) + periph_reg = d["id"] + if security_mode: + # Make, eg, "RTC_S"/"RTC_NS" available as "RTC". + periph_reg = periph_reg.removesuffix(security_mode) + periphs.append((periph_reg, consts[d["id2"]])) elif m[0] == "typedef struct": lexer.must_match("{") m = lexer.next_match() @@ -230,7 +247,20 @@ def print_regs_as_submodules(reg_name, reg_defs, modules): def main(): + global security_mode + cmd_parser = argparse.ArgumentParser(description="Extract ST constants from a C header file.") + + # Options used by gcc to control CPU architecture. + cmd_parser.add_argument("-mcmse", action="store_true") + cmd_parser.add_argument("-mcpu") + cmd_parser.add_argument("-mfloat-abi") + cmd_parser.add_argument("-mfp16-format") + cmd_parser.add_argument("-mfpu") + cmd_parser.add_argument("-msoft-float", action="store_true") + cmd_parser.add_argument("-mthumb", action="store_true") + cmd_parser.add_argument("-mtune") + cmd_parser.add_argument("file", nargs=1, help="input file") cmd_parser.add_argument( "--mpz", @@ -240,6 +270,16 @@ def main(): ) args = cmd_parser.parse_args() + # Determine the security mode of the CPU. + if args.mcpu in ("cortex-m33", "cortex-m55"): + if args.mcmse: + security_mode = "_S" + else: + security_mode = "_NS" + else: + security_mode = None + + # Parse the input CMSIS file with the register definitions. periphs, reg_defs = parse_file(args.file[0]) # add legacy GPIO constants that were removed when upgrading CMSIS From a7555039cd4ac053452030799c775e512e553a82 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 13 Apr 2026 10:05:37 +0200 Subject: [PATCH 2068/2098] stm32/boards: Allow overriding USB PID for Arduino boards. Arduino boards are assigned different USB PIDs based on the MicroPython "variant" they're running. Enable overriding the USB pid at build time. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h | 2 ++ ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h | 2 ++ 4 files changed, 8 insertions(+) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h index 71097406b43..06a8442d445 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.h @@ -302,7 +302,9 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_FMC_D15 (pin_D10) #define MICROPY_HW_USB_VID 0x2341 +#ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID 0x0566 +#endif #define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h index e15d07dcb3c..46e2e43a05b 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.h @@ -227,7 +227,9 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE (3000000) #define MICROPY_HW_USB_VID 0x2341 +#ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID 0x055F +#endif #define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) diff --git a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h index 851b9f9d2f8..2f9a8f02704 100644 --- a/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_OPTA/mpconfigboard.h @@ -212,7 +212,9 @@ extern struct _spi_bdev_t spi_bdev; // USB config #define MICROPY_HW_USB_FS (1) #define MICROPY_HW_USB_VID 0x2341 +#ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID 0x0564 +#endif #define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h index 1c1a804c5ec..f4dc8d7111b 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.h @@ -326,7 +326,9 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_ETH_RMII_TXD1 (pin_G12) #define MICROPY_HW_USB_VID 0x2341 +#ifndef MICROPY_HW_USB_PID #define MICROPY_HW_USB_PID 0x055B +#endif #define MICROPY_HW_USB_PID_CDC_MSC (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC_HID (MICROPY_HW_USB_PID) #define MICROPY_HW_USB_PID_CDC (MICROPY_HW_USB_PID) From 2c57e9df65ff8f240341bc365b066ec16b3a02a0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 Apr 2026 13:55:03 +1000 Subject: [PATCH 2069/2098] stm32: Conditionally disable USB interrupts during SD operations. - This is necessary if the USB MSC device is exposing the SD Card, as a USB operation might read/write SD. - However, rather than disabling USB interrupts (and all lower interrupts) unconditionally, we can check if MSC is enabled & SDCard is configured as one of the USB MSC LUNs. This happens to be necessary to reliably trigger SDCard DMA alignment bugs from hard ISRs. However, it's good to do anyhow - allows interrupt processing to continue while the CPU is blocked waiting for the SD operation. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/sdcard.c | 41 +++++++++++++++++++++++++++----- ports/stm32/usbd_msc_interface.c | 9 +++++++ ports/stm32/usbd_msc_interface.h | 7 +++++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 3e755bf9f26..98bfe090f38 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -38,6 +38,15 @@ #include "dma.h" #include "irq.h" +#if !BUILDING_MBOOT +#include "usbd_msc_interface.h" +#else +// For mboot, act like the SDCard is always exposed via USB MSC +inline static bool usbd_msc_lu_includes_sdcard(void) { + return true; +} +#endif // !BUILDING_MBOOT + #if MICROPY_HW_ENABLE_SDCARD || MICROPY_HW_ENABLE_MMCARD #if defined(STM32F7) || defined(STM32H5) || defined(STM32H7) || defined(STM32L4) || defined(STM32N6) @@ -541,8 +550,14 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo } if (query_irq() == IRQ_STATE_ENABLED) { - // we must disable USB irqs to prevent MSC contention with SD card - uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + #if MICROPY_HW_USB_MSC + uint32_t basepri; + bool usb_msc_sdcard = usbd_msc_lu_includes_sdcard(); + if (usb_msc_sdcard) { + // we must disable USB irqs to prevent MSC contention with SD card + basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + } + #endif #if SDIO_USE_GPDMA DMA_HandleTypeDef sd_dma; @@ -586,7 +601,11 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo } #endif - restore_irq_pri(basepri); + #if MICROPY_HW_USB_MSC + if (usb_msc_sdcard) { + restore_irq_pri(basepri); + } + #endif } else { #if MICROPY_HW_ENABLE_MMCARD if (pyb_sdmmc_flags & PYB_SDMMC_FLAG_MMC) { @@ -635,8 +654,14 @@ mp_uint_t sdcard_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t n } if (query_irq() == IRQ_STATE_ENABLED) { - // we must disable USB irqs to prevent MSC contention with SD card - uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + #if MICROPY_HW_USB_MSC + uint32_t basepri; + bool usb_msc_sdcard = usbd_msc_lu_includes_sdcard(); + if (usb_msc_sdcard) { + // we must disable USB irqs to prevent MSC contention with SD card + basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + } + #endif #if SDIO_USE_GPDMA DMA_HandleTypeDef sd_dma; @@ -679,7 +704,11 @@ mp_uint_t sdcard_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t n } #endif - restore_irq_pri(basepri); + #if MICROPY_HW_USB_MSC + if (usb_msc_sdcard) { + restore_irq_pri(basepri); + } + #endif } else { #if MICROPY_HW_ENABLE_MMCARD if (pyb_sdmmc_flags & PYB_SDMMC_FLAG_MMC) { diff --git a/ports/stm32/usbd_msc_interface.c b/ports/stm32/usbd_msc_interface.c index 68236e5c512..29bf46aaa66 100644 --- a/ports/stm32/usbd_msc_interface.c +++ b/ports/stm32/usbd_msc_interface.c @@ -109,6 +109,15 @@ void usbd_msc_init_lu(size_t lu_n, const void *lu_data) { usbd_msc_lu_flags = 0; } +bool usbd_msc_lu_includes_sdcard(void) { + for (int i = 0; i < usbd_msc_lu_num; i++) { + if (usbd_msc_lu_data[i] == &pyb_sdcard_type) { + return true; + } + } + return false; +} + // Helper function to perform an ioctl on a logical unit static int lu_ioctl(uint8_t lun, int op, uint32_t *data) { if (lun >= usbd_msc_lu_num) { diff --git a/ports/stm32/usbd_msc_interface.h b/ports/stm32/usbd_msc_interface.h index 9d25a72a3a4..4980bb7b7e6 100644 --- a/ports/stm32/usbd_msc_interface.h +++ b/ports/stm32/usbd_msc_interface.h @@ -26,8 +26,13 @@ #ifndef MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H #define MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H -extern const USBD_StorageTypeDef usbd_msc_fops; +#include +#include + +extern const struct _USBD_STORAGE usbd_msc_fops; void usbd_msc_init_lu(size_t lu_n, const void *lu_data); +bool usbd_msc_lu_includes_sdcard(void); + #endif // MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H From 7a5eae80dfb8d6ed953cbb5b7e5302fd9d040c54 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 Apr 2026 13:54:09 +1000 Subject: [PATCH 2070/2098] tests/ports/stm32: Add DMA alignment test for SDCard driver. Rename the existing dma_alignment test to spi_dma_align, as this one uses the SPI driver. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .../ports/stm32_hardware/sdcard_dma_align.py | 188 ++++++++++++++++++ .../{dma_alignment.py => spi_dma_align.py} | 0 2 files changed, 188 insertions(+) create mode 100644 tests/ports/stm32_hardware/sdcard_dma_align.py rename tests/ports/stm32_hardware/{dma_alignment.py => spi_dma_align.py} (100%) diff --git a/tests/ports/stm32_hardware/sdcard_dma_align.py b/tests/ports/stm32_hardware/sdcard_dma_align.py new file mode 100644 index 00000000000..4af1ef543e4 --- /dev/null +++ b/tests/ports/stm32_hardware/sdcard_dma_align.py @@ -0,0 +1,188 @@ +# Test DMA read operations when the buffer alignment in RAM varies. +# +# Test requirements: +# A mostly empty FAT formatted SDCard installed in SD socket +import errno +import os +import pyb +import machine +import micropython +import vfs +import unittest + +from micropython import const + +_BLOCK_SZ = const(512) +_OFFS_WIDTH = const(64) # Should be at least the cache line size plus the GC block size +_TEST_BUF_SZ = const(_BLOCK_SZ + _OFFS_WIDTH) + +# More repeats = longer test run, more chance of triggering a cache coherence issue +_REPEATS = const(8) + +MOUNT_POINT = "/sd" +FILE_PATH = "/sd/stm32_align.blk" + +# Skip the whole test if there isn't a mountable SDCard +try: + sd = pyb.SDCard() + vfs.mount(sd, MOUNT_POINT) + vfs.umount(MOUNT_POINT) + del sd +except (OSError, AttributeError): + print("SKIP") + raise SystemExit + + +def verify_contents(buf, silent=False): + # Verify that each byte in 'buf' has the value of its index in the buffer + bad_pos = [] + for i in range(_BLOCK_SZ): + bi = buf[i] + if bi != i & 0xFF: + if silent: + return False + bad_pos.append((i, bi)) + if not bad_pos: + return True + + assert not silent + + print("{} bad readback values in sector:".format(len(bad_pos)), end="") + for i, v in bad_pos: + print(" {:#x}={:#x}".format(i, v), end="") + print() + return False + + +class TestSDAlign(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.sd = pyb.SDCard() + vfs.mount(cls.sd, MOUNT_POINT) + buf = bytearray(_BLOCK_SZ) + try: + with open(FILE_PATH, "rb") as f: + rlen = f.readinto(buf) + if rlen != _BLOCK_SZ: + raise RuntimeError("Unexpected length of temporary file ", FILE_PATH, rlen) + if not verify_contents(buf): + raise RuntimeError("Corrupt test block in temporary file ", FILE_PATH) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + print("Creating new temp file...") + with open(FILE_PATH, "wb") as f: + f.write(bytes(i & 0xFF for i in range(_BLOCK_SZ))) + + # Now look for the sector which holds the temporary file + # (assume is in the first 20MB of the SD Card) + for b in range(1, 40960): + if not cls.sd.readblocks(b, buf): + raise RuntimeError("Failed to call readblocks on SDCard. block:", b) + if verify_contents(buf, True): + print("Temporary file contents found in block {}".format(b)) + cls.block = b + return + + raise RuntimeError( + "Contents of temporary file not found near start of SDCard. Too many files?" + ) + + @classmethod + def tearDownClass(cls): + try: + os.unlink(FILE_PATH) + print("Deleted temp file") + except OSError: + pass + vfs.umount(MOUNT_POINT) + del cls.sd + + def setUp(self): + self.offs = 0 + + @micropython.native + def _test_reads_inner(self, buf, repeats=_REPEATS): + for offs in range(_OFFS_WIDTH): + with self.subTest(offs=offs): + self.offs = offs + slice = memoryview(buf)[offs : offs + _BLOCK_SZ] + assert len(slice) == _BLOCK_SZ + for r in range(repeats): + self.assertTrue( + self.sd.readblocks(self.block, slice), + "Read failed for block {} offs {} repeat {}/{}".format( + self.block, offs, r, repeats + ), + ) + self.assertTrue( + verify_contents(slice), + "Verify failed for block {} offs {} repeat {}/{}".format( + self.block, offs, r, repeats + ), + ) + + def test_reads(self): + # Test reading at all available offsets in a buffer + buf = bytearray(_TEST_BUF_SZ) + # This test is the most random as any failure depends on speculative reads while + # the DMA operation is in progress. We run the test more times to increase the chance + # of hitting one of these cases, but even if the issue is present the test only fails + # once per ~250 iterations. The other tests inject explicit reads and writes so they fail + # more or less immediately. + self._test_reads_inner(buf, repeats=_REPEATS * 4) + + def test_interrupted_reads(self): + # Test reading at all available offsets in a buffer, while an interrupt is + # scanning through the whole buffer + buf = bytearray(_TEST_BUF_SZ) + t = None + self.scan = 0 + self.val = None + try: + + @micropython.native + def timer_cb(_): + # Arbitrary read from somewhere in the buffer + self.val = buf[self.scan % _TEST_BUF_SZ] + self.scan += 1 + + t = pyb.Timer(1, freq=30_000, callback=timer_cb, hard=True) + self._test_reads_inner(buf) + finally: + if t: + t.deinit() + # print("scan count", self.scan, self.val) + + def test_interrupted_read_write(self): + # Test reading at all available offsets in a buffer, while an interrupt is + # writing before & after the DMA buffer + buf = bytearray(_TEST_BUF_SZ) + t = None + try: + + @micropython.native + def timer_cb(t): + # Arbitrary CPU write just before and after the buffer, trying to dirty a DMA cache line + # + # Note: we never write into a word overlapping the DMA buffer, because if the buffer is not 4-byte aligned + # sdcard_read_blocks() will do a trick to align it temporarily, and this will race with that trick and + # corrupt the memory. + offs = self.offs + if offs > 3: + buf[offs - 4] = 0x55 + offs += _BLOCK_SZ + if offs < _TEST_BUF_SZ - 3: + buf[offs] = 0x56 + + # pybd SF2 at default CPU frequency can manage >30kHz <40kHz + t = pyb.Timer(1, freq=35_000, callback=timer_cb, hard=True) + self._test_reads_inner(buf) + finally: + if t: + t.deinit() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ports/stm32_hardware/dma_alignment.py b/tests/ports/stm32_hardware/spi_dma_align.py similarity index 100% rename from tests/ports/stm32_hardware/dma_alignment.py rename to tests/ports/stm32_hardware/spi_dma_align.py From d2e87fd16cce187836f9d918b11ab73081435f8c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 15 Apr 2026 10:45:06 +1000 Subject: [PATCH 2071/2098] stm32/sdcard: Use dma_protect_rx_region on read operations. Will avoid corruption for unaligned reads, unless a SPI DMA operation is in progress at the same time (to be fixed separately). This fixes the unit test added in the parent commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/stm32/sdcard.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 98bfe090f38..f2714ab5865 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -574,7 +574,7 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo // make sure cache is flushed and invalidated so when DMA updates the RAM // from reading the peripheral the CPU then reads the new data - MP_HAL_CLEANINVALIDATE_DCACHE(dest, num_blocks * SDCARD_BLOCK_SIZE); + dma_protect_rx_region(dest, num_blocks * SDCARD_BLOCK_SIZE); sdcard_reset_periph(); #if MICROPY_HW_ENABLE_MMCARD @@ -589,6 +589,8 @@ mp_uint_t sdcard_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo err = sdcard_wait_finished(); } + dma_unprotect_rx_region(dest, num_blocks * SDCARD_BLOCK_SIZE); + #if SDIO_USE_GPDMA dma_deinit(&SDMMC_DMA); #if MICROPY_HW_ENABLE_MMCARD From e496000efc95894b62696c77863fd5566208ecdd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 7 May 2026 17:50:35 +1000 Subject: [PATCH 2072/2098] esp8266: Remove obsolete --verify option from ESP8266. Has been the default forever (or, at least, for a decade) and was removed entirely in a recent esptool release. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp8266/Makefile | 2 +- ports/esp8266/README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index b961c76fdaf..699d174f69d 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -196,7 +196,7 @@ FROZEN_EXTRA_DEPS = $(CONFVARS_FILE) deploy: $(FWBIN) $(ECHO) "Writing $< to the board" - $(Q)$(ESPTOOL) --port $(PORT) --baud $(BAUD) write_flash --verify --flash_size=$(FLASH_SIZE) --flash_mode=$(FLASH_MODE) 0 $< + $(Q)$(ESPTOOL) --port $(PORT) --baud $(BAUD) write_flash --flash_size=$(FLASH_SIZE) --flash_mode=$(FLASH_MODE) 0 $< erase: $(ECHO) "Erase flash" diff --git a/ports/esp8266/README.md b/ports/esp8266/README.md index 561c7714032..33a5eea275d 100644 --- a/ports/esp8266/README.md +++ b/ports/esp8266/README.md @@ -240,8 +240,7 @@ While the port is in beta, it's known to be generally stable. If you experience strange bootloops, crashes, lockups, here's a list to check against: - You didn't erase flash before programming MicroPython firmware. -- Firmware can be occasionally flashed incorrectly. Just retry. Recent - esptool.py versions have --verify option. +- Firmware can be occasionally flashed incorrectly. Just retry. - Power supply you use doesn't provide enough power for ESP8266 or isn't stable enough. - A module/flash may be defective (not unheard of for cheap modules). From cc502abae9cdd02241b9f89db8b97ccab4c913c2 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 19 Aug 2024 17:06:34 +0300 Subject: [PATCH 2073/2098] esp32/machine_pin: Add mode, pull and drive to machine_pin_print(). This commit adds mode, pull, drive parameters to the Pin repr function. This allows to serialize Pin object to str and restore the Pin object from the string. Signed-off-by: IhorNehrutsa --- ports/esp32/machine_pin.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) mode change 100644 => 100755 ports/esp32/machine_pin.c diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c old mode 100644 new mode 100755 index efe6733194c..5eb13a48ce5 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -129,7 +129,35 @@ gpio_num_t machine_pin_get_id(mp_obj_t pin_in) { static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pin_obj_t *self = self_in; - mp_printf(print, "Pin(%u)", PIN_OBJ_PTR_INDEX(self)); + gpio_num_t gpio_num = PIN_OBJ_PTR_INDEX(self); + + mp_printf(print, "Pin(%u", gpio_num); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + gpio_io_config_t gpio_io_config; + gpio_get_io_config(gpio_num, &gpio_io_config); + qstr mode; + if (gpio_io_config.oe) { + mode = MP_QSTR_OUT; + } else if (gpio_io_config.od) { + mode = MP_QSTR_OPEN_DRAIN; + } else { // if (gpio_io_config.ie) + mode = MP_QSTR_IN; + } + mp_printf(print, ", mode=%q.%q", MP_QSTR_Pin, mode); + qstr pull = MP_QSTRnull; + if (gpio_io_config.pu) { + pull = MP_QSTR_PULL_UP; + } else if (gpio_io_config.pd) { + pull = MP_QSTR_PULL_DOWN; + } + if (pull != MP_QSTRnull) { + mp_printf(print, ", pull=%q.%q", MP_QSTR_Pin, pull); + } + if (gpio_io_config.drv != GPIO_DRIVE_CAP_2) { + mp_printf(print, ", drive=Pin.DRIVE_%u", gpio_io_config.drv); + } + #endif + mp_printf(print, ")"); } // pin.init(mode=None, pull=-1, *, value, drive, hold) From f563d3c965b7da662e6817376baffdf832c21955 Mon Sep 17 00:00:00 2001 From: Meir Armon Date: Fri, 20 Jun 2025 17:45:41 +0300 Subject: [PATCH 2074/2098] esp32/modmachine: Add new machine.wake_pins() function. When waking from deep sleep, it could be helpful to know what pins triggered the wake up since the wake pins could be configured to multiple pins. Signed-off-by: Meir Armon --- docs/library/machine.rst | 7 +++++++ ports/esp32/modmachine.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 481820defc1..dad6128feec 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -163,6 +163,13 @@ Power related functions Availability: ESP32, WiPy. +.. function:: wake_pins() + + Returns the GPIO pin numbers of those pins which caused wakeup from deep sleep as a + tuple of integers. + + Availability: ESP32. + Miscellaneous functions ----------------------- diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index ec972bfc68e..c0c45f948b2 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -36,6 +36,7 @@ #include "esp_sleep.h" #include "esp_pm.h" +#include "py/objtuple.h" #include "modmachine.h" #include "machine_rtc.h" @@ -73,6 +74,7 @@ \ /* Wake reasons */ \ { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, \ + { MP_ROM_QSTR(MP_QSTR_wake_pins), MP_ROM_PTR(&machine_wake_pins_obj) }, \ { MP_ROM_QSTR(MP_QSTR_PIN_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, \ { MP_ROM_QSTR(MP_QSTR_EXT0_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, \ { MP_ROM_QSTR(MP_QSTR_EXT1_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT1) }, \ @@ -310,6 +312,39 @@ static mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_ } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_wake_reason_obj, 0, machine_wake_reason); +static mp_obj_t machine_wake_pins(void) { + uint64_t status = 0; + int len, index; + + // There will be only one wake-up source, so it is OK to logically OR all the + // wake-up source statuses. + #if SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP && SOC_DEEP_SLEEP_SUPPORTED + status |= esp_sleep_get_gpio_wakeup_status(); + #endif + + #if SOC_PM_SUPPORT_EXT1_WAKEUP && SOC_RTCIO_PIN_COUNT > 0 + status |= esp_sleep_get_ext1_wakeup_status(); + #endif + + // Only a few (~8) pins might cause wakeup. + // Therefore, we calculate the required space in a first pass. + for (index = 0, len = 0; index < 64; index++) { + len += (status & (1ULL << index)) ? 1 : 0; + } + if (len) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(len, NULL)); + + for (index = 0, len = 0; index < 64; index++) { + if (status & (1ULL << index)) { + tuple->items[len++] = MP_OBJ_NEW_SMALL_INT(index); + } + } + return MP_OBJ_FROM_PTR(tuple); + } + return mp_obj_new_tuple(0, NULL); +} +static MP_DEFINE_CONST_FUN_OBJ_0(machine_wake_pins_obj, machine_wake_pins); + MP_NORETURN static void mp_machine_reset(void) { esp_restart(); } From a8ba8fab30a9ec0e00ce4c04927872c27730dac4 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 23 Mar 2026 17:24:44 -0600 Subject: [PATCH 2075/2098] esp32/machine_sdcard: Add support for SDMMC power ctrl via internal LDO. A board can #define MICROPY_HW_SDMMC_LDO_CHAN_ID in mpconfigboard.h for an internal LDO to control power to the SDMMC GPIO pins. This is needed to use SDIO 3.0, because the IO level has to switch between 3.3V and 1.8V. Signed-off-by: Dryw Wade --- ports/esp32/machine_sdcard.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index edecabaac76..8e09d09c9a3 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -35,6 +35,9 @@ #if SOC_SDMMC_HOST_SUPPORTED #include "driver/sdmmc_host.h" +#if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) +#include "sd_pwr_ctrl_by_on_chip_ldo.h" +#endif #endif #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" @@ -313,6 +316,14 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args self->host = _temp_host; } #endif + #if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = MICROPY_HW_SDMMC_LDO_CHAN_ID, + }; + sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; + check_esp_err(sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle)); + self->host.pwr_ctrl_handle = pwr_ctrl_handle; + #endif DEBUG_printf(" Calling host.init()"); @@ -434,6 +445,11 @@ static mp_obj_t sd_deinit(mp_obj_t self_in) { // SD card used a (dedicated) SPI bus, so free that SPI bus. spi_bus_free(self->host.slot); } + #if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) + if (self->host.pwr_ctrl_handle) { + check_esp_err(sd_pwr_ctrl_del_on_chip_ldo(self->host.pwr_ctrl_handle)); + } + #endif self->flags &= ~SDCARD_CARD_FLAGS_HOST_INIT_DONE; } From 169678c266f49f2cc6edeecd07e4682dd8dd6feb Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 23 Mar 2026 17:25:35 -0600 Subject: [PATCH 2076/2098] esp32/boards/ESP32_GENERIC_P4: Set SDMMC LDO channel. The ESP32-P4-Function-EV-Board uses LDO 4 for controlling SDMMC GPIO power. This is required to use SD cards with this board. Signed-off-by: Dryw Wade --- ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h index b7a88784802..6a7ff83cec0 100644 --- a/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h +++ b/ports/esp32/boards/ESP32_GENERIC_P4/mpconfigboard.h @@ -12,6 +12,7 @@ #define MICROPY_PY_ESPNOW (0) #define MICROPY_HW_ENABLE_SDCARD (1) +#define MICROPY_HW_SDMMC_LDO_CHAN_ID (4) #ifndef USB_SERIAL_JTAG_PACKET_SZ_BYTES #define USB_SERIAL_JTAG_PACKET_SZ_BYTES (64) From dc44bdbac9607742d9b3f2364265bff2af788725 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 2 Apr 2026 14:31:47 -0600 Subject: [PATCH 2077/2098] esp32/machine_sdcard: Make LDO channel configurable from Python. Can now call machine.SDCard(ldo=...) to specify LDO channel. Channel defaults to MICROPY_HW_SDMMC_LDO_CHAN_ID if defined, otherwise None (disabled). Signed-off-by: Dryw Wade --- ports/esp32/machine_sdcard.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 8e09d09c9a3..09c41e26ea9 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -35,7 +35,7 @@ #if SOC_SDMMC_HOST_SUPPORTED #include "driver/sdmmc_host.h" -#if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) +#if SOC_SDMMC_IO_POWER_EXTERNAL #include "sd_pwr_ctrl_by_on_chip_ldo.h" #endif #endif @@ -214,6 +214,9 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args ARG_cmd, ARG_data, #endif + #if SOC_SDMMC_IO_POWER_EXTERNAL + ARG_ldo, + #endif ARG_freq, }; #if SOC_SDMMC_HOST_SUPPORTED @@ -236,6 +239,13 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args { MP_QSTR_cmd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_data, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, #endif + #if SOC_SDMMC_IO_POWER_EXTERNAL + #ifdef MICROPY_HW_SDMMC_LDO_CHAN_ID + { MP_QSTR_ldo, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(MICROPY_HW_SDMMC_LDO_CHAN_ID)} }, + #else + { MP_QSTR_ldo, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif + #endif // freq is valid for both SPI and SDMMC interfaces { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, }; @@ -316,13 +326,15 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args self->host = _temp_host; } #endif - #if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) - sd_pwr_ctrl_ldo_config_t ldo_config = { - .ldo_chan_id = MICROPY_HW_SDMMC_LDO_CHAN_ID, - }; - sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; - check_esp_err(sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle)); - self->host.pwr_ctrl_handle = pwr_ctrl_handle; + #if SOC_SDMMC_IO_POWER_EXTERNAL + if (arg_vals[ARG_ldo].u_obj != mp_const_none) { + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = mp_obj_get_int(arg_vals[ARG_ldo].u_obj), + }; + sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; + check_esp_err(sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle)); + self->host.pwr_ctrl_handle = pwr_ctrl_handle; + } #endif DEBUG_printf(" Calling host.init()"); @@ -445,7 +457,7 @@ static mp_obj_t sd_deinit(mp_obj_t self_in) { // SD card used a (dedicated) SPI bus, so free that SPI bus. spi_bus_free(self->host.slot); } - #if SOC_SDMMC_IO_POWER_EXTERNAL && defined(MICROPY_HW_SDMMC_LDO_CHAN_ID) + #if SOC_SDMMC_IO_POWER_EXTERNAL if (self->host.pwr_ctrl_handle) { check_esp_err(sd_pwr_ctrl_del_on_chip_ldo(self->host.pwr_ctrl_handle)); } From f640267d73f8c57418ea162c2d47debf5f5da46b Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 6 May 2026 19:45:06 -0600 Subject: [PATCH 2078/2098] docs/library/machine.SDCard: Document new LDO argument for ESP32-P4. Signed-off-by: Dryw Wade --- docs/library/machine.SDCard.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/library/machine.SDCard.rst b/docs/library/machine.SDCard.rst index c4a0d5d172b..1677a6e3108 100644 --- a/docs/library/machine.SDCard.rst +++ b/docs/library/machine.SDCard.rst @@ -24,7 +24,7 @@ or a non-standard pin assignment. The exact subset of arguments supported will vary from platform to platform. .. class:: SDCard(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, - cs=None, cmd=None, data=None, freq=20000000) + cs=None, cmd=None, data=None, ldo=None, freq=20000000) This class provides access to SD or MMC storage cards using either a dedicated SD/MMC interface hardware or through an SPI channel. @@ -60,6 +60,9 @@ vary from platform to platform. - *data* can be used to specify a list or tuple of SD data bus pins (ESP32-S3 only). + - *ldo* can be used to specify the internal LDO channel used for SD + card logic level for SDIO 3.0 (ESP32-P4 only). + - *freq* selects the SD/MMC interface frequency in Hz. Implementation-specific details @@ -188,6 +191,16 @@ parameters ``sck``, ``cs``, ``miso``, ``mosi`` as needed to assign pins. In either mode the ``cd`` and ``wp`` pins default to disabled, unless set in the constructor. +ESP32-P4 +~~~~~~~~ + +The ESP32-P4 has multiple internal adjustable LDO regulators, and some boards use +one of LDOs to control the SD card logic level required by SDIO 3.0. Most boards +will automatically select the correct LDO channel, but it may be necessary to +manually specify the ``ldo`` parameter as an integer (1 through 4). For example:: + + sd = SDCard(ldo=4) + Other ESP32 chips ~~~~~~~~~~~~~~~~~ From 4cf5c50c57ea264d2b6e8dd7ab015f5d1e9c8eb1 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Thu, 9 Apr 2026 08:58:20 -0600 Subject: [PATCH 2079/2098] esp32/modnetwork: Always enable PHY_GENERIC. Also enable MICROPY_PY_NETWORK_LAN if SOC_EMAC_SUPPORTED is defined, which enables on ESP32-P4. Signed-off-by: Dryw Wade --- ports/esp32/modnetwork.h | 2 +- ports/esp32/mpconfigport.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 68260dd19a3..47fd733c086 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -37,7 +37,7 @@ #endif // PHY_GENERIC support requires newer IDF version -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) && CONFIG_IDF_TARGET_ESP32 +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) #define PHY_GENERIC_ENABLED (1) #else #define PHY_GENERIC_ENABLED (0) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 938f26e27c6..a77e13cf3f5 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -395,7 +395,7 @@ typedef long mp_off_t; void boardctrl_startup(void); #ifndef MICROPY_PY_NETWORK_LAN -#if CONFIG_IDF_TARGET_ESP32 || (CONFIG_ETH_USE_SPI_ETHERNET && (CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL || CONFIG_ETH_SPI_ETHERNET_DM9051 || CONFIG_ETH_SPI_ETHERNET_W5500)) +#if SOC_EMAC_SUPPORTED || (CONFIG_ETH_USE_SPI_ETHERNET && (CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL || CONFIG_ETH_SPI_ETHERNET_DM9051 || CONFIG_ETH_SPI_ETHERNET_W5500)) #define MICROPY_PY_NETWORK_LAN (1) #else #define MICROPY_PY_NETWORK_LAN (0) From 2e04624709651b797906db80672bb89e2cec964f Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Mon, 4 May 2026 10:17:07 -0600 Subject: [PATCH 2080/2098] esp32/network_lan: Make EMAC RMII pins configurable on ESP32-P4. Fixes #19035. Signed-off-by: Dryw Wade --- ports/esp32/network_lan.c | 71 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 37f72c26349..cf4566f7896 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -54,6 +54,16 @@ typedef struct _lan_if_obj_t { bool initialized; int8_t mdc_pin; int8_t mdio_pin; + #if CONFIG_IDF_TARGET_ESP32P4 + int8_t crs_dv_pin; + int8_t rxd0_pin; + int8_t rxd1_pin; + int8_t tx_en_pin; + int8_t txd0_pin; + int8_t txd1_pin; + int8_t clk_in_pin; + int8_t clk_out_pin; + #endif int8_t phy_reset_pin; int8_t phy_power_pin; int8_t phy_cs_pin; @@ -115,7 +125,12 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } enum { ARG_id, ARG_mdc, ARG_mdio, ARG_reset, ARG_power, ARG_phy_addr, ARG_phy_type, - ARG_spi, ARG_cs, ARG_int, ARG_ref_clk_mode, ARG_ref_clk }; + ARG_spi, ARG_cs, ARG_int, ARG_ref_clk_mode, ARG_ref_clk, + #if CONFIG_IDF_TARGET_ESP32P4 + ARG_crs_dv, ARG_rxd0, ARG_rxd1, ARG_tx_en, + ARG_txd0, ARG_txd1, ARG_clk_in, ARG_clk_out, + #endif + }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_mdc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, @@ -129,6 +144,16 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar { MP_QSTR_int, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_ref_clk_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_ref_clk, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #if CONFIG_IDF_TARGET_ESP32P4 + { MP_QSTR_crs_dv, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_rxd0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_rxd1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_tx_en, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_txd0, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_txd1, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_clk_in, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_clk_out, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -148,6 +173,16 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar self->phy_power_pin = GET_PIN(ARG_power); self->phy_cs_pin = GET_PIN(ARG_cs); self->phy_int_pin = GET_PIN(ARG_int); + #if CONFIG_IDF_TARGET_ESP32P4 + self->crs_dv_pin = GET_PIN(ARG_crs_dv); + self->rxd0_pin = GET_PIN(ARG_rxd0); + self->rxd1_pin = GET_PIN(ARG_rxd1); + self->tx_en_pin = GET_PIN(ARG_tx_en); + self->txd0_pin = GET_PIN(ARG_txd0); + self->txd1_pin = GET_PIN(ARG_txd1); + self->clk_in_pin = GET_PIN(ARG_clk_in); + self->clk_out_pin = GET_PIN(ARG_clk_out); + #endif if (args[ARG_phy_addr].u_int < 0x00 || args[ARG_phy_addr].u_int > 0x1f) { mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); @@ -293,6 +328,40 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } esp32_config.smi_mdc_gpio_num = self->mdc_pin; esp32_config.smi_mdio_gpio_num = self->mdio_pin; + + #if CONFIG_IDF_TARGET_ESP32P4 + if (self->crs_dv_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.crs_dv_num = self->crs_dv_pin; + } + if (self->rxd0_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.rxd0_num = self->rxd0_pin; + } + if (self->rxd1_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.rxd1_num = self->rxd1_pin; + } + if (self->tx_en_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.tx_en_num = self->tx_en_pin; + } + if (self->txd0_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.txd0_num = self->txd0_pin; + } + if (self->txd1_pin != -1) { + esp32_config.emac_dataif_gpio.rmii.txd1_num = self->txd1_pin; + } + if (self->clk_out_pin != -1) { + if (self->clk_in_pin == -1) { + mp_raise_ValueError(MP_ERROR_TEXT("clk_in must be specified if clk_out is specified")); + } + esp32_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT; + esp32_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t)self->clk_out_pin; + esp32_config.clock_config_out_in.rmii.clock_mode = EMAC_CLK_EXT_IN; + esp32_config.clock_config_out_in.rmii.clock_gpio = (emac_rmii_clock_gpio_t)self->clk_in_pin; + } else if (self->clk_in_pin != -1) { + esp32_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + esp32_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t)self->clk_in_pin; + } + #endif + mac = esp_eth_mac_new_esp32(&esp32_config, &mac_config); } #endif From 2b5f5f5347fcd26d58105a82c14f5021f0aafaf1 Mon Sep 17 00:00:00 2001 From: Mo Nazemi Date: Sun, 19 Apr 2026 17:30:00 +0100 Subject: [PATCH 2081/2098] esp32/network_lan: Fix LAN event handler base filtering. Prevent WiFi IP events from corrupting Ethernet status. Signed-off-by: Mo Nazemi --- ports/esp32/network_lan.c | 53 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index cf4566f7896..e5fcdd8a121 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -80,29 +80,36 @@ static uint8_t eth_status = 0; static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - switch (event_id) { - case ETHERNET_EVENT_CONNECTED: - eth_status = ETH_CONNECTED; - ESP_LOGI("ethernet", "Ethernet Link Up"); - break; - case ETHERNET_EVENT_DISCONNECTED: - eth_status = ETH_DISCONNECTED; - ESP_LOGI("ethernet", "Ethernet Link Down"); - break; - case ETHERNET_EVENT_START: - eth_status = ETH_STARTED; - ESP_LOGI("ethernet", "Ethernet Started"); - break; - case ETHERNET_EVENT_STOP: - eth_status = ETH_STOPPED; - ESP_LOGI("ethernet", "Ethernet Stopped"); - break; - case IP_EVENT_ETH_GOT_IP: - eth_status = ETH_GOT_IP; - ESP_LOGI("ethernet", "Ethernet Got IP"); - break; - default: - break; + if (event_base == ETH_EVENT) { + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + eth_status = ETH_CONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Up"); + break; + case ETHERNET_EVENT_DISCONNECTED: + eth_status = ETH_DISCONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Down"); + break; + case ETHERNET_EVENT_START: + eth_status = ETH_STARTED; + ESP_LOGI("ethernet", "Ethernet Started"); + break; + case ETHERNET_EVENT_STOP: + eth_status = ETH_STOPPED; + ESP_LOGI("ethernet", "Ethernet Stopped"); + break; + default: + break; + } + } else if (event_base == IP_EVENT) { + switch (event_id) { + case IP_EVENT_ETH_GOT_IP: + eth_status = ETH_GOT_IP; + ESP_LOGI("ethernet", "Ethernet Got IP"); + break; + default: + break; + } } } From e28c15f0faf608ad64921fb0fe7284f6356438e4 Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Thu, 7 May 2026 11:09:35 +0300 Subject: [PATCH 2082/2098] esp32/machine_i2c: Allow SoftI2C to be disabled. To disable `machine.SoftI2C` set zero #define MICROPY_PY_MACHINE_SOFTI2C (0) in `ports/esp32/mpconfigport.h`. Signed-off-by: Ihor Nehrutsa --- extmod/modmachine.h | 2 ++ ports/esp32/machine_i2c.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 18ee229dc79..80e1a384e7b 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -88,6 +88,7 @@ #define MICROPY_PY_MACHINE_UART_IRQ (0) #endif +#if MICROPY_PY_MACHINE_SOFTI2C // Temporary support for legacy construction of SoftI2C via I2C type. #define MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args) \ do { \ @@ -100,6 +101,7 @@ return MP_OBJ_TYPE_GET_SLOT(&mp_machine_soft_i2c_type, make_new)(&mp_machine_soft_i2c_type, n_args, n_kw, all_args); \ } \ } while (0) +#endif // Temporary support for legacy construction of SoftSPI via SPI type. #define MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args) \ diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 9fb89660f74..2c59849d301 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -332,10 +332,12 @@ static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if MICROPY_PY_MACHINE_SOFTI2C // Create a SoftI2C instance if no id is specified (or is -1) but other arguments are given if (n_args != 0) { MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); } + #endif // Parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; From ea1e10f087686de019efbc4c7fdde778279049f6 Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Thu, 7 May 2026 12:25:12 +0300 Subject: [PATCH 2083/2098] esp32/machine_hw_spi: Allow SoftSPI to be disabled. To disable `machine.SoftSPI` set zero in line #define MICROPY_PY_MACHINE_SOFTSPI (0) in file `ports/esp32/mpconfigport.h`. Signed-off-by: Ihor Nehrutsa --- extmod/modmachine.h | 2 ++ ports/esp32/machine_hw_spi.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 80e1a384e7b..6d651d0bf6f 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -103,6 +103,7 @@ } while (0) #endif +#if MICROPY_PY_MACHINE_SOFTSPI // Temporary support for legacy construction of SoftSPI via SPI type. #define MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args) \ do { \ @@ -115,6 +116,7 @@ return MP_OBJ_TYPE_GET_SLOT(&mp_machine_soft_spi_type, make_new)(&mp_machine_soft_spi_type, n_args, n_kw, all_args); \ } \ } while (0) +#endif #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index aea6bd00fe8..7153084b1b6 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -436,7 +436,9 @@ static void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_ } mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if MICROPY_PY_MACHINE_SOFTSPI MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + #endif mp_arg_val_t args[MP_ARRAY_SIZE(spi_allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(spi_allowed_args), spi_allowed_args, args); From 6fa7d5be270fa4133f10112c1ed3b501ecbf69d2 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Tue, 30 Dec 2025 10:59:50 -0700 Subject: [PATCH 2084/2098] rp2/machine_spi: Detect RX overrun when using DMA transfers. Fixes bug where SPI can freeze if another DMA channel is tranferring to/from PSRAM. Fixes issue #18471. Signed-off-by: Dryw Wade --- ports/rp2/machine_spi.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ports/rp2/machine_spi.c b/ports/rp2/machine_spi.c index 680a3df2878..9a3bf3a708b 100644 --- a/ports/rp2/machine_spi.c +++ b/ports/rp2/machine_spi.c @@ -273,6 +273,7 @@ static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8 bool use_dma = chan_rx >= 0 && chan_tx >= 0; // note src is guaranteed to be non-NULL bool write_only = dest == NULL; + bool success = true; if (use_dma) { uint8_t dev_null; @@ -297,8 +298,23 @@ static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8 false); dma_start_channel_mask((1u << chan_rx) | (1u << chan_tx)); - dma_channel_wait_for_finish_blocking(chan_rx); dma_channel_wait_for_finish_blocking(chan_tx); + + // Fix for #18471. Wait for SPI to finish then check for RX overrun + while (spi_is_busy(self->spi_inst) || spi_is_readable(self->spi_inst)) { + } + if (spi_get_const_hw(self->spi_inst)->ris & SPI_SSPRIS_RORRIS_BITS) { + // RX overrun occurred. Clear the flag for future transfers + spi_get_hw(self->spi_inst)->icr = SPI_SSPICR_RORIC_BITS; + + // RX DMA channel needs to be manually aborted + dma_channel_abort(chan_rx); + + // An RX overrun is only a problem if we were actually reading + if (!write_only) { + success = false; + } + } } // If we have claimed only one channel successfully, we should release immediately @@ -317,6 +333,11 @@ static void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8 spi_write_read_blocking(self->spi_inst, src, dest, len); } } + + // Raise OSError if something went wrong + if (!success) { + mp_raise_OSError(MP_EIO); + } } // Buffer protocol implementation for SPI. From c4979d43dd9a711ed9094b23b9dfce4ea91b2541 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 25 Apr 2026 01:10:55 +0200 Subject: [PATCH 2085/2098] rp2/mphalport: Optimise cycle counter retrieval in RV32 mode. This commit reduces the instruction count for the inline assembler sequence used in `mp_hal_ticks_cpu` when building the firmware in RISC-V mode. The code used a two instruction sequence to set the value of the `mcountinhibit` CSR to a fixed bit pattern, in order to have the cycle counter always enabled. Whilst this works, it also has the side-effect of always inhibiting the instructions counter, which may not be wanted in certain applications. Since all we need to do is to clear one bit, rather than setting the whole register we can just use the `CSRRCI` opcode to clear bit 0 of the CSR instead. This is done in a single instruction and leaves other register bits alone. Signed-off-by: Alessandro Gatti --- ports/rp2/mphalport.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index d46360cb95e..32a1a645d2a 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -130,8 +130,7 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { #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 + "csrci mcountinhibit, 1\n" // uninhibit mcycle counter "csrr a0, mcycle\n" // get mcycle counter "ret\n" ); From aba71b3a2d7e78489bb5882b285e9397dcd52024 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 25 Apr 2026 01:16:16 +0200 Subject: [PATCH 2086/2098] rp2/machine_bitstream: Shorten time-critical function init in RV32 mode. This commit reduces the instructions count for the bitstream module's time-critical function setup procedure, when the firmware is built for RISC-V. The original code used a separate function with three opcodes to make sure the hardware cycle counter is turned on. However, all of that could be condensed in a single opcode placed in lieu of the function call. An instance of the `CSRRCI` opcode is placed in the function opcode stream to clear bit 0 of the `mcountinhibit` CSR. `CSRRCI` is supposed to also store the original value of the CSR into a register, but luckily `zero`/`x0` is a valid target so the opcode can be placed safely without disrupting the existing registers' state. Signed-off-by: Alessandro Gatti --- ports/rp2/machine_bitstream.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ports/rp2/machine_bitstream.c b/ports/rp2/machine_bitstream.c index 8411179f1ae..65ea1df00c0 100644 --- a/ports/rp2/machine_bitstream.c +++ b/ports/rp2/machine_bitstream.c @@ -40,14 +40,6 @@ #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" @@ -99,7 +91,8 @@ void __time_critical_func(machine_bitstream_high_low)(mp_hal_pin_obj_t pin, uint #elif PICO_RISCV - mcycle_init(); + // Uninhibit the cycle counter. + __asm volatile ("csrci mcountinhibit, 1\n"); for (size_t i = 0; i < len; ++i) { uint8_t b = buf[i]; From 4e6dc0b5690c30b9ee5cfb48c432537abda285f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 25 Mar 2026 15:53:31 +0100 Subject: [PATCH 2087/2098] lib/littlefs: Update LittleFS to v2.11.3. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- lib/littlefs/lfs2.c | 41 +++++++++++++++++++++++++---------------- lib/littlefs/lfs2.h | 11 ++++++----- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index 6c3c3ece76b..039bd0f0971 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -258,7 +258,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, continue; } - // pcache must have been flushed, either by programming and + // pcache must have been flushed, either by programming an // entire block or manually flushing the pcache LFS2_ASSERT(pcache->block == LFS2_BLOCK_NULL); @@ -286,7 +286,7 @@ static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { // some operations on paths static inline lfs2_size_t lfs2_path_namelen(const char *path) { - return strcspn(path, "/"); + return (lfs2_size_t)strcspn(path, "/"); } static inline bool lfs2_path_islast(const char *path) { @@ -1291,6 +1291,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // found a match for our fetcher? if ((fmask & tag) == (fmask & ftag)) { + LFS2_ASSERT(cb != NULL); int res = cb(data, tag, &(struct lfs2_diskoff){ dir->pair[0], off+sizeof(tag)}); if (res < 0) { @@ -1501,7 +1502,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { name += strspn(name, "/"); } - lfs2_size_t namelen = strcspn(name, "/"); + lfs2_size_t namelen = (lfs2_size_t)strcspn(name, "/"); // skip '.' if (namelen == 1 && memcmp(name, ".", 1) == 0) { @@ -1520,7 +1521,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, int depth = 1; while (true) { suffix += strspn(suffix, "/"); - sufflen = strcspn(suffix, "/"); + sufflen = (lfs2_size_t)strcspn(suffix, "/"); if (sufflen == 0) { break; } @@ -1761,7 +1762,7 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { commit->off = noff; // perturb valid bit? - commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + commit->ptag = ntag ^ ((lfs2_tag_t)(0x80 & ~eperturb) << 24); // reset crc for next commit commit->crc = 0xffffffff; @@ -3244,10 +3245,12 @@ static int lfs2_file_open_(lfs2_t *lfs2, lfs2_file_t *file, #endif static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file) { -#ifndef LFS2_READONLY - int err = lfs2_file_sync_(lfs2, file); -#else int err = 0; +#ifndef LFS2_READONLY + // it's not safe to do anything if our file errored + if (!(file->flags & LFS2_F_ERRED)) { + err = lfs2_file_sync_(lfs2, file); + } #endif // remove from list of mdirs @@ -3429,18 +3432,12 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { - if (file->flags & LFS2_F_ERRED) { - // it's not safe to do anything if our file errored - return 0; - } - int err = lfs2_file_flush(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; return err; } - if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { // before we commit metadata, we need sync the disk to make sure @@ -3485,6 +3482,17 @@ static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { file->flags &= ~LFS2_F_DIRTY; } + // mark any other file handles as dirty + desync + for (lfs2_file_t *f = (lfs2_file_t*)lfs2->mlist; f; f = f->next) { + if (file != f + && f->type == LFS2_TYPE_REG + && lfs2_pair_cmp(f->m.pair, file->m.pair) == 0 + && f->id == file->id) { + f->flags |= LFS2_F_DUSTY; + } + } + + file->flags &= ~LFS2_F_ERRED & ~LFS2_F_DUSTY; return 0; } #endif @@ -3692,7 +3700,7 @@ static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, return nsize; } - file->flags &= ~LFS2_F_ERRED; + file->flags &= ~LFS2_F_ERRED & ~LFS2_F_DUSTY; return nsize; } #endif @@ -4771,7 +4779,8 @@ int lfs2_fs_traverse_(lfs2_t *lfs2, continue; } - if ((f->flags & LFS2_F_DIRTY) && !(f->flags & LFS2_F_INLINE)) { + if (((f->flags & LFS2_F_DIRTY) || (f->flags & LFS2_F_DUSTY)) + && !(f->flags & LFS2_F_INLINE)) { int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index 77477aaff83..99fb3ce1620 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -135,14 +135,15 @@ enum lfs2_open_flags { // internally used flags #ifndef LFS2_READONLY - LFS2_F_DIRTY = 0x010000, // File does not match storage - LFS2_F_WRITING = 0x020000, // File has been written since last flush + LFS2_F_DIRTY = 0x00010000, // File does not match storage due to write + LFS2_F_DUSTY = 0x00020000, // File does not match storage due to desync + LFS2_F_WRITING = 0x00040000, // File has been written since last flush #endif - LFS2_F_READING = 0x040000, // File has been read since last flush + LFS2_F_READING = 0x00080000, // File has been read since last flush #ifndef LFS2_READONLY - LFS2_F_ERRED = 0x080000, // An error occurred during write + LFS2_F_ERRED = 0x00100000, // An error occurred during write #endif - LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS2_F_INLINE = 0x01000000, // Currently inlined in directory entry }; // File seek flags From 64f0394d80ca1cfbac47c0d0f9adeb6b483efa13 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 8 Apr 2026 14:00:44 -0500 Subject: [PATCH 2088/2098] extmod/vfs_blockdev: Use memoryview when available. This prevents Python code from accidentally performing an operation that resizes the buffer. However, in the case that the build excludes memoryview, the crash is still possible. Closes: #17848 Signed-off-by: Jeff Epler --- extmod/vfs_blockdev.c | 4 +++ tests/extmod/vfs_blockdev_invalid2.py | 37 +++++++++++++++++++++++ tests/extmod/vfs_blockdev_invalid2.py.exp | 1 + tests/run-tests.py | 1 + 4 files changed, 43 insertions(+) create mode 100644 tests/extmod/vfs_blockdev_invalid2.py create mode 100644 tests/extmod/vfs_blockdev_invalid2.py.exp diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index 5c7d248ac5a..7fad2c29a5c 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -49,7 +49,11 @@ void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) { // Helper function to minimise code size of read/write functions // note the n_args argument is moved to the end for further code size reduction (args keep same position in caller and callee). static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t block_off, size_t len, void *buf, size_t n_args) { + #if MICROPY_PY_BUILTINS_MEMORYVIEW + mp_obj_array_t ar = {{&mp_type_memoryview}, 'B' | MP_OBJ_ARRAY_TYPECODE_FLAG_RW, 0, len, buf}; + #else mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, buf}; + #endif args[2] = MP_OBJ_NEW_SMALL_INT(block_num); args[3] = MP_OBJ_FROM_PTR(&ar); args[4] = MP_OBJ_NEW_SMALL_INT(block_off); // ignored for n_args == 2 diff --git a/tests/extmod/vfs_blockdev_invalid2.py b/tests/extmod/vfs_blockdev_invalid2.py new file mode 100644 index 00000000000..8905f833cf7 --- /dev/null +++ b/tests/extmod/vfs_blockdev_invalid2.py @@ -0,0 +1,37 @@ +# Tests where the block device returns invalid values + +try: + import vfs + + vfs.VfsFat + memoryview +except (NameError, ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class BadDev: + SEC_SIZE = 512 + + def __init__(self, blocks): + self.blocks = blocks + + def readblocks(self, n, buf): + assert len(buf) == self.SEC_SIZE + buf[:] = bytearray(self.SEC_SIZE + 1) # Attempts to enlarge passed-in buf + + def writeblocks(self, n, buf): + pass + + def ioctl(self, op, arg): + if op == 4: # MP_BLOCKDEV_IOCTL_BLOCK_COUNT + return self.blocks + if op == 5: # MP_BLOCKDEV_IOCTL_BLOCK_SIZE + return self.SEC_SIZE + + +bdev = BadDev(512) +try: + vfs.VfsFat.mkfs(bdev) +except ValueError as e: + print("ValueError") diff --git a/tests/extmod/vfs_blockdev_invalid2.py.exp b/tests/extmod/vfs_blockdev_invalid2.py.exp new file mode 100644 index 00000000000..94274de1bb3 --- /dev/null +++ b/tests/extmod/vfs_blockdev_invalid2.py.exp @@ -0,0 +1 @@ +ValueError diff --git a/tests/run-tests.py b/tests/run-tests.py index f6a4a33adee..344adc8faa6 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -283,6 +283,7 @@ "extmod/time_mktime.py", "extmod/time_res.py", "extmod/tls_sslcontext_ciphers.py", + "extmod/vfs_blockdev_invalid2.py", "extmod/vfs_fat_fileio1.py", "extmod/vfs_fat_finaliser.py", "extmod/vfs_fat_more.py", From 80a9abbf494e11af59a66cfa024339af84f008ba Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 14 Apr 2026 15:49:19 +0200 Subject: [PATCH 2089/2098] extmod/machine_i2c: Add a deinit method to machine.I2C. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds `.deinit()` to the `machine.I2C` class, bringing it in line with both the target I2C class variant and the rest of the peripheral classes. Ports that want to allocate I²C bus entries dynamically can implement the `self.deinit()` method to add deallocation/cleanup code, otherwise this method is entirely optional to have. If no method is found in the port-provided object structure then calling `deinit()` on the object will do nothing, following what the `machine.SPI` object does. This addresses #19096. Signed-off-by: Alessandro Gatti --- docs/library/machine.I2C.rst | 2 -- extmod/machine_i2c.c | 12 ++++++++++++ extmod/modmachine.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/library/machine.I2C.rst b/docs/library/machine.I2C.rst index 635d5873444..a10c6cf6d74 100644 --- a/docs/library/machine.I2C.rst +++ b/docs/library/machine.I2C.rst @@ -101,8 +101,6 @@ General Methods Turn off the I2C bus. - Availability: WiPy. - .. method:: I2C.scan() Scan all I2C addresses between 0x08 and 0x77 inclusive and return a list of diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index bddf7752999..c0b868421f5 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -319,6 +319,16 @@ static mp_obj_t machine_i2c_init(size_t n_args, const mp_obj_t *args, mp_map_t * } MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_init_obj, 1, machine_i2c_init); +static mp_obj_t machine_i2c_deinit(mp_obj_t self_in) { + mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(self_in); + mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t *)MP_OBJ_TYPE_GET_SLOT(self->type, protocol); + if (i2c_p->deinit != NULL) { + i2c_p->deinit(self); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_deinit_obj, machine_i2c_deinit); + static mp_obj_t machine_i2c_scan(mp_obj_t self_in) { mp_obj_base_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t list = mp_obj_new_list(0, NULL); @@ -633,6 +643,7 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_writeto_mem_obj, 1, machine_i2c_wr static const mp_rom_map_elem_t machine_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2c_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2c_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&machine_i2c_scan_obj) }, // primitive I2C operations @@ -724,6 +735,7 @@ int mp_machine_soft_i2c_write(mp_obj_base_t *self_in, const uint8_t *src, size_t static const mp_machine_i2c_p_t mp_machine_soft_i2c_p = { .init = mp_machine_soft_i2c_init, + .deinit = NULL, .start = (int (*)(mp_obj_base_t *))mp_hal_i2c_start, .stop = (int (*)(mp_obj_base_t *))mp_hal_i2c_stop, .read = mp_machine_soft_i2c_read, diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 6d651d0bf6f..baa1d384565 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -161,6 +161,7 @@ typedef struct _mp_machine_i2c_p_t { bool transfer_supports_write1; #endif void (*init)(mp_obj_base_t *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + void (*deinit)(mp_obj_base_t *obj); // can be NULL int (*start)(mp_obj_base_t *obj); int (*stop)(mp_obj_base_t *obj); int (*read)(mp_obj_base_t *obj, uint8_t *dest, size_t len, bool nack); From e47c528fe58fd49a20c000854576d433efde0e55 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Mon, 4 May 2026 10:56:46 +1000 Subject: [PATCH 2090/2098] extmod/machine_i2c: Transfer WRITE1 address in single buffer adaptor. Without this, the register address is not copied into the single buffer, and so the requested register address is garbage when the driver reads it. Signed-off-by: Julia Vassiliki --- extmod/machine_i2c.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index c0b868421f5..6df040b370a 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -271,6 +271,15 @@ int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n len += bufs[i].len; } } + #if MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + assert(flags & MP_MACHINE_I2C_FLAG_READ); + assert(n >= 1); + // If set, the "first mp_machine_i2c_buf_t in a transfer is a write", + // so we need to copy it. + memcpy(buf, bufs[0].buf, bufs[0].len); + } + #endif } mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t *)MP_OBJ_TYPE_GET_SLOT(self->type, protocol); From 3288766533a2a726f209fa324675e200adbdee7a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 23 Apr 2026 16:56:11 +0200 Subject: [PATCH 2091/2098] shared/tinyusb: Add USB device qualifier descriptor. USB 2.0 requires high-speed capable devices to provide a Device Qualifier descriptor. Signed-off-by: iabdalkader --- shared/tinyusb/mp_usbd.h | 3 +++ shared/tinyusb/mp_usbd_descriptor.c | 21 +++++++++++++++++++++ shared/tinyusb/mp_usbd_runtime.c | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index d73cb5ade82..c455f0bc343 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -98,6 +98,9 @@ void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len); // Built-in USB device and configuration descriptor values extern const tusb_desc_device_t mp_usbd_builtin_desc_dev; extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN]; +#if (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) +extern const tusb_desc_device_qualifier_t mp_usbd_builtin_desc_qual; +#endif void mp_usbd_task_callback(mp_sched_node_t *node); diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c index d0c8845b680..0b3f5752469 100644 --- a/shared/tinyusb/mp_usbd_descriptor.c +++ b/shared/tinyusb/mp_usbd_descriptor.c @@ -53,6 +53,21 @@ const tusb_desc_device_t mp_usbd_builtin_desc_dev = { .bNumConfigurations = 1, }; +#if (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) +// Device qualifier descriptor for high-speed devices. +const tusb_desc_device_qualifier_t mp_usbd_builtin_desc_qual = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00, +}; +#endif + const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = { TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_BUILTIN_MAX, USBD_STR_0, MP_USBD_BUILTIN_DESC_CFG_LEN, 0, USBD_MAX_POWER_MA), @@ -149,6 +164,12 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { return mp_usbd_builtin_desc_cfg; } +#if (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) +uint8_t const *tud_descriptor_device_qualifier_cb(void) { + return (uint8_t const *)&mp_usbd_builtin_desc_qual; +} +#endif + #else // If runtime device support is enabled, descriptor callbacks are implemented in usbd.c diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c index ef6bd87edda..4a1de40791b 100644 --- a/shared/tinyusb/mp_usbd_runtime.c +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -126,6 +126,12 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { return result ? result : &mp_usbd_builtin_desc_cfg; } +#if (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) +uint8_t const *tud_descriptor_device_qualifier_cb(void) { + return (const uint8_t *)&mp_usbd_builtin_desc_qual; +} +#endif + const char *mp_usbd_runtime_string_cb(uint8_t index) { mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); nlr_buf_t nlr; From 64431309680c0579c49a8ba0076dd9979dae3fbc Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sun, 12 Apr 2026 19:32:05 +0200 Subject: [PATCH 2092/2098] tools/mpremote: Fix length calculation in RemoteCommand for arrays. Refactor the byte length calculation in the wr_bytes and read methods to use a new `buffer_nbytes` function. This change corrects the accuracy of byte length determination for various buffer types. Closes: #17665 Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/transport_serial.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index b3584945bc5..dbd47cffd18 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -536,12 +536,20 @@ def wr_s32(self, i): self.fout.write(self.buf4) def wr_bytes(self, b): - self.wr_s32(len(b)) + self.wr_s32(self.buffer_nbytes(b)) self.fout.write(b) # str and bytes act the same in MicroPython wr_str = wr_bytes + def buffer_nbytes(self, obj): + if isinstance(obj, (str, bytes, bytearray)): + return len(obj) + mv = memoryview(obj) + if hasattr(mv, "itemsize"): + return len(mv) * mv.itemsize + # Fallback for uncommon buffer providers. + return len(bytes(obj)) class RemoteFile(io.IOBase): def __init__(self, cmd, fd, is_text): @@ -611,7 +619,7 @@ def readinto(self, buf): c = self.cmd c.begin(CMD_READ) c.wr_s8(self.fd) - c.wr_s32(len(buf)) + c.wr_s32(c.buffer_nbytes(buf)) n = c.rd_bytes(buf) c.end() return n From 670d7753653c5e5a87aee16e5c50bc95bd6ab83a Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Wed, 22 Apr 2026 18:05:51 +0200 Subject: [PATCH 2093/2098] tools/mpremote: Add tests for array readinto and write. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/_test_mount_readinto_array.py | 11 +++++++++++ tools/mpremote/tests/_test_mount_write_array.py | 8 ++++++++ tools/mpremote/tests/test_mount.sh | 11 +++++++++++ tools/mpremote/tests/test_mount.sh.exp | 7 +++++++ 4 files changed, 37 insertions(+) create mode 100644 tools/mpremote/tests/_test_mount_readinto_array.py create mode 100644 tools/mpremote/tests/_test_mount_write_array.py diff --git a/tools/mpremote/tests/_test_mount_readinto_array.py b/tools/mpremote/tests/_test_mount_readinto_array.py new file mode 100644 index 00000000000..41b2576744a --- /dev/null +++ b/tools/mpremote/tests/_test_mount_readinto_array.py @@ -0,0 +1,11 @@ +from array import array +import os + +with open("/remote/test", "wb") as f: + f.write(b"abcd") +a = array("H", (0 for _ in range(2))) +with open("/remote/test", "rb") as f: + n = f.readinto(a) +os.remove("/remote/test") +print(n) +print(list(a)) diff --git a/tools/mpremote/tests/_test_mount_write_array.py b/tools/mpremote/tests/_test_mount_write_array.py new file mode 100644 index 00000000000..68748115811 --- /dev/null +++ b/tools/mpremote/tests/_test_mount_write_array.py @@ -0,0 +1,8 @@ +from array import array +import os + +h = array("h", (0x4041 for _ in range(50))) +with open("/remote/testfile", "wb") as f: + n = f.write(h) +os.remove("/remote/testfile") +print(n) diff --git a/tools/mpremote/tests/test_mount.sh b/tools/mpremote/tests/test_mount.sh index 724ae1417fc..21541c735b5 100755 --- a/tools/mpremote/tests/test_mount.sh +++ b/tools/mpremote/tests/test_mount.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +TEST_DIR=$(dirname $0) + # Create a local directory structure and mount the parent directory on the device. echo ----- mkdir -p "${TMP}/mount_package" @@ -30,3 +32,12 @@ cat "${TMP}/test.txt" # Test RemoteFile.readline and RemoteFile.readlines methods. echo ----- $MPREMOTE mount ${TMP} exec "print(open('test.txt').readlines())" + +echo ----- +# Test write() with array returns byte count, not item count. +# See https://github.com/micropython/micropython/issues/17665 +$MPREMOTE mount ${TMP} run "${TEST_DIR}/_test_mount_write_array.py" + +# Test readinto() with array returns byte count and fills correctly. +echo ----- +$MPREMOTE mount ${TMP} run "${TEST_DIR}/_test_mount_readinto_array.py" diff --git a/tools/mpremote/tests/test_mount.sh.exp b/tools/mpremote/tests/test_mount.sh.exp index 616cb75bcf5..7a5884b56f0 100644 --- a/tools/mpremote/tests/test_mount.sh.exp +++ b/tools/mpremote/tests/test_mount.sh.exp @@ -8,3 +8,10 @@ hello world ----- ['hello world\n'] Local directory ${TMP} is mounted at /remote +----- +100 +Local directory ${TMP} is mounted at /remote +----- +4 +[25185, 25699] +Local directory ${TMP} is mounted at /remote From a595bbba67271550735cf6d1be533b81fb676fee Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 23 Mar 2026 12:39:17 +0100 Subject: [PATCH 2094/2098] tools/mpremote: Add an alias for codeberg repos. This commit introduces an alias to access codeberg repos from within mpremote's package manager. Right now packages hosted on codeberg could only be referenced by their full URL, unlike other packages hosted on either GitHub or GitLab. To make access those packages easier, now they can be referenced as "codeberg:org/repo@branch". Signed-off-by: Alessandro Gatti --- docs/reference/packages.rst | 11 +++++-- tools/mpremote/README.md | 1 + tools/mpremote/mpremote/main.py | 2 +- tools/mpremote/mpremote/mip.py | 55 ++++++++++++++------------------- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 5b5f626d452..adc9f95e01f 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -38,13 +38,15 @@ install third-party libraries. The simplest way is to download a file directly:: When installing a file directly, the ``target`` argument is still supported to set the destination path, but ``mpy`` and ``version`` are ignored. -The URL can also start with ``github:`` or ``gitlab:`` as a simple way of pointing to content -hosted on GitHub or GitLab:: +The URL can also start with ``github:``, ``gitlab:``, or ``codeberg:`` as a simple +way of pointing to content hosted on GitHub, GitLab, or Codeberg:: >>> mip.install("github:org/repo/path/foo.py") # Uses default branch >>> mip.install("github:org/repo/path/foo.py", version="branch-or-tag") # Optionally specify the branch or tag >>> mip.install("gitlab:org/repo/path/foo.py") # Uses default branch >>> mip.install("gitlab:org/repo/path/foo.py", version="branch-or-tag") # Optionally specify the branch or tag + >>> mip.install("codeberg:org/repo/path/foo.py") # Uses default branch + >>> mip.install("codeberg:org/repo/path/foo.py", version="branch-or-tag") # Optionally specify the branch or tag More sophisticated packages (i.e. with more than one file, or with dependencies) can be downloaded by specifying the path to their ``package.json``. @@ -52,6 +54,7 @@ can be downloaded by specifying the path to their ``package.json``. >>> mip.install("http://example.com/x/package.json") >>> mip.install("github:org/user/path/package.json") >>> mip.install("gitlab:org/user/path/package.json") + >>> mip.install("codeberg:org/user/path/package.json") If no json file is specified, then "package.json" is implicitly added:: @@ -60,6 +63,8 @@ If no json file is specified, then "package.json" is implicitly added:: >>> mip.install("github:org/repo", version="branch-or-tag") >>> mip.install("gitlab:org/repo") # Uses default branch of that repo >>> mip.install("gitlab:org/repo", version="branch-or-tag") + >>> mip.install("codeberg:org/repo") # Uses default branch of that repo + >>> mip.install("codeberg:org/repo", version="branch-or-tag") Using ``mip`` on the Unix port ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -89,6 +94,8 @@ can be used from a host PC to install packages to a locally connected device $ mpremote mip install github:org/repo@branch-or-tag $ mpremote mip install gitlab:org/repo $ mpremote mip install gitlab:org/repo@branch-or-tag + $ mpremote mip install codeberg:org/repo + $ mpremote mip install codeberg:org/repo@branch-or-tag The ``--target=path``, ``--no-mpy``, and ``--index`` arguments can be set:: diff --git a/tools/mpremote/README.md b/tools/mpremote/README.md index 2bf784be291..abd62f9dceb 100644 --- a/tools/mpremote/README.md +++ b/tools/mpremote/README.md @@ -82,3 +82,4 @@ Examples: mpremote mip install aioble mpremote mip install github:org/repo@branch mpremote mip install gitlab:org/repo@branch + mpremote mip install codeberg:org/repo@branch diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index b31186ba2e1..b57de0e25fc 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -246,7 +246,7 @@ def argparse_mip(): cmd_parser.add_argument( "packages", nargs="+", - help="list package specifications, e.g. name, name@version, github:org/repo, github:org/repo@branch, gitlab:org/repo, gitlab:org/repo@branch", + help="list package specifications, e.g. name, name@version, github:org/repo, github:org/repo@branch, gitlab:org/repo, gitlab:org/repo@branch, codeberg:org/repo, codeberg:org/repo@branch", ) return cmd_parser diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index 0d12ae651ec..3748fb980eb 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -5,7 +5,6 @@ import urllib.error import urllib.request import json -import tempfile import os import os.path @@ -14,7 +13,19 @@ _PACKAGE_INDEX = "https://micropython.org/pi/v2" -allowed_mip_url_prefixes = ("http://", "https://", "github:", "gitlab:") +# Since all URLs are accessed via HTTPS, the URL scheme is added by _rewrite_url. +# The first three format parameters are assumed to be the organisation, the +# repository names, and the branch/tag name, in this order. +_HOSTS = { + # https://codeberg.org/api/v1/repos/{org}/{repo}/raw/{path}?ref={branch} + "codeberg:": "codeberg.org/api/v1/repos/{}/{}/raw/{p}?ref={}", + # https://raw.githubusercontent.com/{org}/{repo}/{branch}/{path} + "github:": "raw.githubusercontent.com/{}/{}/{}/{p}", + # https://gitlab.com/{org}/{repo}/-/raw/{branch}/{path} + "gitlab:": "gitlab.com/{}/{}/-/raw/{}/{p}", +} + +_ALLOWED_MIP_URL_PREFIXES = ("http://", "https://", "codeberg:", "github:", "gitlab:") # This implements os.makedirs(os.dirname(path)) @@ -44,37 +55,19 @@ def _check_exists(transport, path, short_hash): def _rewrite_url(url, branch=None): - if not branch: - branch = "HEAD" - if url.startswith("github:"): - url = url[7:].split("/") - url = ( - "https://raw.githubusercontent.com/" - + url[0] - + "/" - + url[1] - + "/" - + branch - + "/" - + "/".join(url[2:]) - ) - elif url.startswith("gitlab:"): - url = url[7:].split("/") - url = ( - "https://gitlab.com/" - + url[0] - + "/" - + url[1] - + "/-/raw/" - + branch - + "/" - + "/".join(url[2:]) + for provider, url_format in _HOSTS.items(): + if not url.startswith(provider): + continue + components = url[len(provider) :].split("/") + # Add https:// prefix to the final URL. + return _ALLOWED_MIP_URL_PREFIXES[1] + url_format.format( + components[0], components[1], branch or "HEAD", p="/".join(components[2:]) ) return url def _download_file(transport, url, dest): - if url.startswith(allowed_mip_url_prefixes): + if url.startswith(_ALLOWED_MIP_URL_PREFIXES): try: with urllib.request.urlopen(url) as src: data = src.read() @@ -101,7 +94,7 @@ def _download_file(transport, url, dest): def _install_json(transport, package_json_url, index, target, version, mpy): base_url = "" - if package_json_url.startswith(allowed_mip_url_prefixes): + if package_json_url.startswith(_ALLOWED_MIP_URL_PREFIXES): try: with urllib.request.urlopen(_rewrite_url(package_json_url, version)) as response: package_json = json.load(response) @@ -131,7 +124,7 @@ def _install_json(transport, package_json_url, index, target, version, mpy): _download_file(transport, file_url, fs_target_path) for target_path, url in package_json.get("urls", ()): fs_target_path = target + "/" + target_path - if base_url and not url.startswith(allowed_mip_url_prefixes): + if base_url and not url.startswith(_ALLOWED_MIP_URL_PREFIXES): url = f"{base_url}/{url}" # Relative URLs _download_file(transport, _rewrite_url(url, version), fs_target_path) for dep, dep_version in package_json.get("deps", ()): @@ -139,7 +132,7 @@ def _install_json(transport, package_json_url, index, target, version, mpy): def _install_package(transport, package, index, target, version, mpy): - if package.startswith(allowed_mip_url_prefixes): + if package.startswith(_ALLOWED_MIP_URL_PREFIXES): if package.endswith(".py") or package.endswith(".mpy"): print(f"Downloading {package} to {target}") _download_file( From cc62017317da5af7deaf9cab28ec2e2d825b063d Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 30 Apr 2026 18:42:02 +0200 Subject: [PATCH 2095/2098] py/modbuiltins: Let "dir" be disabled if requested. This commit makes it possible to disable the `dir` built-in function from being included in the interpreter image. The function in question is rarely used outside of interactive contexts, and in production code the main use of this function is to implement interactive debug/management consoles over a serial port. However, outside of that scope, the space taken by such function could probably be better used elsewhere. This feature inherits the same configuration level as `enumerate`, since it performs the same operation but on class members rather than data elements. This should be enabled by default on all ports except for `nrf` (for which it is now explicitly enabled) and the `minimal` variants of the `unix` and `zephyr` ports. The latter two ports are meant to also run with the smallest available feature set, so `tests/basics/builtin_dir.py` had to be made optional to make CI run cleanly for those two ports' test runs in their minimal configuration. Signed-off-by: Alessandro Gatti --- ports/nrf/mpconfigport.h | 1 + py/modbuiltins.c | 4 ++++ py/mpconfig.h | 5 +++++ tests/basics/builtin_dir.py | 7 +++++++ 4 files changed, 17 insertions(+) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 946c7f42d70..d5cbab73103 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -277,6 +277,7 @@ #define MICROPY_PY_ATTRTUPLE (1) #define MICROPY_PY_BUILTINS_BYTEARRAY (1) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1) +#define MICROPY_PY_BUILTINS_DIR (1) #define MICROPY_PY_BUILTINS_ENUMERATE (1) #define MICROPY_PY_BUILTINS_EVAL_EXEC (1) #define MICROPY_PY_BUILTINS_FILTER (1) diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 9617b8fcdae..eabc562f19d 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -155,6 +155,7 @@ static mp_obj_t mp_builtin_chr(mp_obj_t o_in) { } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); +#if MICROPY_PY_BUILTINS_DIR static mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { mp_obj_t dir = mp_obj_new_list(0, NULL); if (n_args == 0) { @@ -187,6 +188,7 @@ static mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { return dir; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); +#endif static mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); @@ -687,7 +689,9 @@ static const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, #endif + #if MICROPY_PY_BUILTINS_DIR { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, #if MICROPY_PY_BUILTINS_EVAL_EXEC { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, diff --git a/py/mpconfig.h b/py/mpconfig.h index f46e79911ed..edc54bd26ef 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1479,6 +1479,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_ROUND_INT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to implement dir() to enumerate object fields. +#ifndef MICROPY_PY_BUILTINS_DIR +#define MICROPY_PY_BUILTINS_DIR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + // Whether to support complete set of special methods for user // classes, or only the most used ones. "Inplace" methods are // controlled by MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS below. diff --git a/tests/basics/builtin_dir.py b/tests/basics/builtin_dir.py index 1eecbd044b7..2aa89dfe0a2 100644 --- a/tests/basics/builtin_dir.py +++ b/tests/basics/builtin_dir.py @@ -1,5 +1,12 @@ # test builtin dir +try: + dir +except NameError: + print("SKIP") + raise SystemExit + + # dir of locals print('__name__' in dir()) From 7eaa5221075d51faa58ed821bf4b9fd54b0e4150 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 29 Aug 2025 09:51:25 -0500 Subject: [PATCH 2096/2098] py/asmarm: Fix UBsan diagnostics for left-shift of int. Ths fixes the following type of diagnostic: "runtime error: left shift of 14 by 28 places cannot be represented in type 'int'" and ensures the resulting 32-bit value is correct. Signed-off-by: Jeff Epler --- py/asmarm.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 5ae952ee8a7..0cc43fd5acc 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -50,21 +50,21 @@ #define ASM_ARM_REG_LR (ASM_ARM_REG_R14) #define ASM_ARM_REG_PC (ASM_ARM_REG_R15) -#define ASM_ARM_CC_EQ (0x0 << 28) -#define ASM_ARM_CC_NE (0x1 << 28) -#define ASM_ARM_CC_CS (0x2 << 28) -#define ASM_ARM_CC_CC (0x3 << 28) -#define ASM_ARM_CC_MI (0x4 << 28) -#define ASM_ARM_CC_PL (0x5 << 28) -#define ASM_ARM_CC_VS (0x6 << 28) -#define ASM_ARM_CC_VC (0x7 << 28) -#define ASM_ARM_CC_HI (0x8 << 28) -#define ASM_ARM_CC_LS (0x9 << 28) -#define ASM_ARM_CC_GE (0xa << 28) -#define ASM_ARM_CC_LT (0xb << 28) -#define ASM_ARM_CC_GT (0xc << 28) -#define ASM_ARM_CC_LE (0xd << 28) -#define ASM_ARM_CC_AL (0xe << 28) +#define ASM_ARM_CC_EQ (0x0u << 28) +#define ASM_ARM_CC_NE (0x1u << 28) +#define ASM_ARM_CC_CS (0x2u << 28) +#define ASM_ARM_CC_CC (0x3u << 28) +#define ASM_ARM_CC_MI (0x4u << 28) +#define ASM_ARM_CC_PL (0x5u << 28) +#define ASM_ARM_CC_VS (0x6u << 28) +#define ASM_ARM_CC_VC (0x7u << 28) +#define ASM_ARM_CC_HI (0x8u << 28) +#define ASM_ARM_CC_LS (0x9u << 28) +#define ASM_ARM_CC_GE (0xau << 28) +#define ASM_ARM_CC_LT (0xbu << 28) +#define ASM_ARM_CC_GT (0xcu << 28) +#define ASM_ARM_CC_LE (0xdu << 28) +#define ASM_ARM_CC_AL (0xeu << 28) typedef struct _asm_arm_t { mp_asm_base_t base; From 91dadc57d724ace6184162e2f76299795af88fd2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 29 Aug 2025 09:52:01 -0500 Subject: [PATCH 2097/2098] py/asmthumb: Fix UBsan diagnostics for left-shift of negative. This fixes the diagnostic "runtime error: left shift of negative value -1" and ensures the correct instruction is assembled. Signed-off-by: Jeff Epler --- py/asmthumb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index 58cc7aea880..4226ae28127 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -246,7 +246,7 @@ void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src) { void asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { assert(reg_dest < ASM_THUMB_REG_R15); // mov[wt] reg_dest, #i16_src - asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), ((i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); + asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), (((uint16_t)i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); } static void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { From 8ecd995041c2c65fd1d69f9f4b0bb763272d8f83 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 26 Dec 2025 08:59:17 -0600 Subject: [PATCH 2098/2098] py/emitinlinerv32: Fix UBsan diagnostics for left-shift of mp_int_t. Signed-off-by: Jeff Epler --- py/emitinlinerv32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index e81b152087d..7ed9243ec9f 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -505,7 +505,7 @@ static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, return false; } - mp_uint_t immediate = mp_obj_get_int_truncated(object) << shift; + mp_uint_t immediate = ((mp_uint_t)mp_obj_get_int_truncated(object)) << shift; if (kind & U) { if (!is_in_unsigned_mask(mask, immediate)) { goto out_of_range;